aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Runxi Yu2026-03-05 21:09:07 +0800
committerGravatar Runxi Yu2026-03-05 21:14:24 +0800
commit355f5b3dc9ae560827cd274e113f43d09ee9ac49 (patch)
tree2046f5d51110fff82aecf35c75884222f51ac36c
parentobjectid, format/pack/ingest: Pack hash ID in algo (diff)
signatureNo signature
*: Fix overflows
-rw-r--r--format/pack/ingest/finalize.go9
-rw-r--r--format/pack/ingest/idx_write.go9
-rw-r--r--format/pack/ingest/resolve.go13
-rw-r--r--format/pack/ingest/rev_write.go11
-rw-r--r--format/pack/ingest/stream_scan.go17
-rw-r--r--format/pack/ingest/thin_fix.go65
-rw-r--r--internal/compress/zlib/reader.go17
-rw-r--r--internal/compress/zlib/reader_reset.go19
-rw-r--r--internal/intconv/intconv.go27
9 files changed, 165 insertions, 22 deletions
diff --git a/format/pack/ingest/finalize.go b/format/pack/ingest/finalize.go
index e0516c4b..9b04a7c1 100644
--- a/format/pack/ingest/finalize.go
+++ b/format/pack/ingest/finalize.go
@@ -5,6 +5,8 @@ import (
"fmt"
"io/fs"
"strings"
+
+ "codeberg.org/lindenii/furgit/internal/intconv"
)
// finalizeArtifacts links temporary files to final names and returns Result.
@@ -35,12 +37,17 @@ func finalizeArtifacts(state *ingestState) (Result, error) {
}
}
+ objectCount, err := intconv.IntToUint32(len(state.records))
+ if err != nil {
+ return Result{}, err
+ }
+
return Result{
PackName: packFinal,
IdxName: idxFinal,
RevName: revFinal,
PackHash: state.packHash,
- ObjectCount: uint32(len(state.records)),
+ ObjectCount: objectCount,
ThinFixed: state.thinFixed,
}, nil
}
diff --git a/format/pack/ingest/idx_write.go b/format/pack/ingest/idx_write.go
index 8db11ba0..904afb6c 100644
--- a/format/pack/ingest/idx_write.go
+++ b/format/pack/ingest/idx_write.go
@@ -7,6 +7,8 @@ import (
"hash"
"io"
"slices"
+
+ "codeberg.org/lindenii/furgit/internal/intconv"
)
const (
@@ -87,7 +89,12 @@ func writeIdx(state *ingestState) error {
for _, recordIdx := range order {
offset := state.records[recordIdx].offset
if offset >= 0x80000000 {
- word := 0x80000000 | uint32(len(largeOffsets))
+ largeOffsetIdx, err := intconv.IntToUint32(len(largeOffsets))
+ if err != nil {
+ return err
+ }
+
+ word := 0x80000000 | largeOffsetIdx
largeOffsets = append(largeOffsets, offset)
binary.BigEndian.PutUint32(scratch[:4], word)
diff --git a/format/pack/ingest/resolve.go b/format/pack/ingest/resolve.go
index f024bb62..e357e1aa 100644
--- a/format/pack/ingest/resolve.go
+++ b/format/pack/ingest/resolve.go
@@ -10,6 +10,7 @@ import (
deltaapply "codeberg.org/lindenii/furgit/format/delta/apply"
packfmt "codeberg.org/lindenii/furgit/format/pack"
"codeberg.org/lindenii/furgit/internal/compress/zlib"
+ "codeberg.org/lindenii/furgit/internal/intconv"
"codeberg.org/lindenii/furgit/objectheader"
"codeberg.org/lindenii/furgit/objectid"
"codeberg.org/lindenii/furgit/objecttype"
@@ -237,7 +238,17 @@ func inflateRecordPayload(state *ingestState, idx int) ([]byte, error) {
compressedOffset := record.offset + uint64(record.headerLen)
compressedLen := record.packedLen - uint64(record.headerLen)
- section := io.NewSectionReader(state.packFile, int64(compressedOffset), int64(compressedLen))
+ compressedOffsetInt64, err := intconv.Uint64ToInt64(compressedOffset)
+ if err != nil {
+ return nil, err
+ }
+
+ compressedLenInt64, err := intconv.Uint64ToInt64(compressedLen)
+ if err != nil {
+ return nil, err
+ }
+
+ section := io.NewSectionReader(state.packFile, compressedOffsetInt64, compressedLenInt64)
reader, err := zlib.NewReader(section)
if err != nil {
diff --git a/format/pack/ingest/rev_write.go b/format/pack/ingest/rev_write.go
index afa9b4a9..8d30474a 100644
--- a/format/pack/ingest/rev_write.go
+++ b/format/pack/ingest/rev_write.go
@@ -3,6 +3,8 @@ package ingest
import (
"encoding/binary"
"slices"
+
+ "codeberg.org/lindenii/furgit/internal/intconv"
)
const (
@@ -53,9 +55,14 @@ func writeRev(state *ingestState) error {
}
for _, recordIdx := range packOrder {
- binary.BigEndian.PutUint32(scratch[:4], uint32(recordToIdxPos[recordIdx]))
+ recordPos, err := intconv.IntToUint32(recordToIdxPos[recordIdx])
+ if err != nil {
+ return err
+ }
+
+ binary.BigEndian.PutUint32(scratch[:4], recordPos)
- err := writeAndHash(state.revFile, hashImpl, scratch[:4])
+ err = writeAndHash(state.revFile, hashImpl, scratch[:4])
if err != nil {
return err
}
diff --git a/format/pack/ingest/stream_scan.go b/format/pack/ingest/stream_scan.go
index b3cf4c00..97bb0ae8 100644
--- a/format/pack/ingest/stream_scan.go
+++ b/format/pack/ingest/stream_scan.go
@@ -8,6 +8,7 @@ import (
deltaapply "codeberg.org/lindenii/furgit/format/delta/apply"
packfmt "codeberg.org/lindenii/furgit/format/pack"
"codeberg.org/lindenii/furgit/internal/compress/zlib"
+ "codeberg.org/lindenii/furgit/internal/intconv"
"codeberg.org/lindenii/furgit/objectheader"
"codeberg.org/lindenii/furgit/objectid"
"codeberg.org/lindenii/furgit/objecttype"
@@ -215,7 +216,13 @@ func parseEntryPrefix(state *ingestState, startOffset uint64) (objectRecord, err
}
record.baseObject = baseID
- headerLen += uint32(len(baseRaw))
+
+ baseRawLen, err := intconv.IntToUint32(len(baseRaw))
+ if err != nil {
+ return record, err
+ }
+
+ headerLen += baseRawLen
case objecttype.TypeOfsDelta:
dist, consumed, err := readOfsDistanceFromStream(state.stream)
if err != nil {
@@ -227,7 +234,13 @@ func parseEntryPrefix(state *ingestState, startOffset uint64) (objectRecord, err
}
record.baseOffset = startOffset - dist
- headerLen += uint32(consumed)
+
+ consumedUint32, err := intconv.IntToUint32(consumed)
+ if err != nil {
+ return record, err
+ }
+
+ headerLen += consumedUint32
case objecttype.TypeInvalid, objecttype.TypeFuture:
return record, &ErrMalformedPackEntry{Offset: startOffset, Reason: fmt.Sprintf("unsupported object type %d", record.packedType)}
default:
diff --git a/format/pack/ingest/thin_fix.go b/format/pack/ingest/thin_fix.go
index 05ad9494..6ac3c75a 100644
--- a/format/pack/ingest/thin_fix.go
+++ b/format/pack/ingest/thin_fix.go
@@ -8,6 +8,7 @@ import (
"os"
"codeberg.org/lindenii/furgit/internal/compress/zlib"
+ "codeberg.org/lindenii/furgit/internal/intconv"
"codeberg.org/lindenii/furgit/objectid"
"codeberg.org/lindenii/furgit/objecttype"
)
@@ -45,7 +46,12 @@ func maybeFixThin(state *ingestState) error {
return err
}
- state.stream.consumed = uint64(newEnd)
+ consumed, err := intconv.Int64ToUint64(newEnd)
+ if err != nil {
+ return err
+ }
+
+ state.stream.consumed = consumed
baseIDs := unresolvedThinBaseIDs(state)
for _, id := range baseIDs {
@@ -76,12 +82,18 @@ func appendBaseObject(state *ingestState, id objectid.ObjectID, realType objectt
header := encodePackEntryHeader(realType, int64(len(content)))
- _, err := state.packFile.WriteAt(header, int64(start))
+ startInt64, err := intconv.Uint64ToInt64(start)
+ if err != nil {
+ return 0, err
+ }
+
+ _, err = state.packFile.WriteAt(header, startInt64)
if err != nil {
return 0, err
}
- section := &fileSectionWriter{file: state.packFile, off: int64(start) + int64(len(header))}
+ headerLenInt64 := int64(len(header))
+ section := &fileSectionWriter{file: state.packFile, off: startInt64 + headerLenInt64}
crc := crc32.NewIEEE()
_, _ = crc.Write(header)
counting := &countingWriter{dst: section}
@@ -98,19 +110,34 @@ func appendBaseObject(state *ingestState, id objectid.ObjectID, realType objectt
return 0, err
}
- packedLen := uint64(len(header)) + uint64(counting.n)
+ headerLenUint64, err := intconv.IntToUint64(len(header))
+ if err != nil {
+ return 0, err
+ }
+
+ countingNUint64, err := intconv.IntToUint64(counting.n)
+ if err != nil {
+ return 0, err
+ }
+
+ packedLen := headerLenUint64 + countingNUint64
end := start + packedLen
state.stream.consumed = end
+ headerLenUint32, err := intconv.IntToUint32(len(header))
+ if err != nil {
+ return 0, err
+ }
+
record := objectRecord{
offset: start,
- headerLen: uint32(len(header)),
+ headerLen: headerLenUint32,
packedLen: packedLen,
crc32: crc.Sum32(),
packedType: realType,
realType: realType,
declaredSize: int64(len(content)),
- dataOffset: start + uint64(len(header)),
+ dataOffset: start + headerLenUint64,
objectID: id,
resolved: true,
}
@@ -160,9 +187,14 @@ func (writer *countingWriter) Write(src []byte) (int, error) {
// rewritePackHeaderAndTrailer rewrites object count and trailer hash using ReadAt/WriteAt.
func rewritePackHeaderAndTrailer(state *ingestState) error {
var countRaw [4]byte
- binary.BigEndian.PutUint32(countRaw[:], uint32(len(state.records)))
+ recordCountUint32, err := intconv.IntToUint32(len(state.records))
+ if err != nil {
+ return err
+ }
+
+ binary.BigEndian.PutUint32(countRaw[:], recordCountUint32)
- _, err := state.packFile.WriteAt(countRaw[:], 8)
+ _, err = state.packFile.WriteAt(countRaw[:], 8)
if err != nil {
return err
}
@@ -217,8 +249,15 @@ func rewritePackHeaderAndTrailer(state *ingestState) error {
}
state.packHash = packHash
- state.objectCountHeader = uint32(len(state.records))
- state.stream.consumed = uint64(endWithoutTrailer + int64(len(sum)))
+ state.objectCountHeader = recordCountUint32
+
+ sumLenInt64 := int64(len(sum))
+ newConsumed, err := intconv.Int64ToUint64(endWithoutTrailer + sumLenInt64)
+ if err != nil {
+ return err
+ }
+
+ state.stream.consumed = newConsumed
return nil
}
@@ -228,7 +267,11 @@ func encodePackEntryHeader(ty objecttype.Type, size int64) []byte {
var out [16]byte
n := 0
- s := uint64(size)
+ s, err := intconv.Int64ToUint64(size)
+ if err != nil {
+ panic(err)
+ }
+
c := byte((uint8(ty) << 4) | byte(s&0x0f))
s >>= 4
diff --git a/internal/compress/zlib/reader.go b/internal/compress/zlib/reader.go
index a6497dec..f298a07f 100644
--- a/internal/compress/zlib/reader.go
+++ b/internal/compress/zlib/reader.go
@@ -41,6 +41,7 @@ import (
"sync"
"codeberg.org/lindenii/furgit/internal/compress/flate"
+ "codeberg.org/lindenii/furgit/internal/intconv"
)
const (
@@ -131,7 +132,14 @@ func (z *Reader) Read(p []byte) (int, error) {
// Finished file; check checksum.
readN, err := io.ReadFull(z.r, z.scratch[0:4])
- z.trailerRead += uint64(readN)
+ readNUint64, convErr := intconv.IntToUint64(readN)
+ if convErr != nil {
+ z.err = convErr
+
+ return n, z.err
+ }
+
+ z.trailerRead += readNUint64
if err != nil {
if errors.Is(err, io.EOF) {
@@ -160,7 +168,12 @@ func (z *Reader) Read(p []byte) (int, error) {
func (z *Reader) InputConsumed() uint64 {
out := z.headerRead + z.trailerRead
if z.progress != nil {
- out += uint64(z.progress.InputConsumed())
+ progressIn, err := intconv.Int64ToUint64(z.progress.InputConsumed())
+ if err != nil {
+ panic(err)
+ }
+
+ out += progressIn
}
return out
diff --git a/internal/compress/zlib/reader_reset.go b/internal/compress/zlib/reader_reset.go
index 32af0b49..3e78ab5d 100644
--- a/internal/compress/zlib/reader_reset.go
+++ b/internal/compress/zlib/reader_reset.go
@@ -12,6 +12,7 @@ import (
"codeberg.org/lindenii/furgit/internal/adler32"
"codeberg.org/lindenii/furgit/internal/compress/flate"
+ "codeberg.org/lindenii/furgit/internal/intconv"
)
// reset resets receiver to read a new zlib stream.
@@ -29,7 +30,14 @@ func (z *Reader) reset(r io.Reader, dict []byte) error {
// Read the header (RFC 1950 section 2.2.).
readN, err := io.ReadFull(z.r, z.scratch[0:2])
- z.headerRead += uint64(readN)
+ readNUint64, convErr := intconv.IntToUint64(readN)
+ if convErr != nil {
+ z.err = convErr
+
+ return z.err
+ }
+
+ z.headerRead += readNUint64
z.err = err
if z.err != nil {
@@ -51,7 +59,14 @@ func (z *Reader) reset(r io.Reader, dict []byte) error {
if haveDict {
readN, z.err = io.ReadFull(z.r, z.scratch[0:4])
- z.headerRead += uint64(readN)
+ readNUint64, err := intconv.IntToUint64(readN)
+ if err != nil {
+ z.err = err
+
+ return z.err
+ }
+
+ z.headerRead += readNUint64
if z.err != nil {
if errors.Is(z.err, io.EOF) {
z.err = io.ErrUnexpectedEOF
diff --git a/internal/intconv/intconv.go b/internal/intconv/intconv.go
index 67f99a14..c4b429e3 100644
--- a/internal/intconv/intconv.go
+++ b/internal/intconv/intconv.go
@@ -33,6 +33,33 @@ func IntToUint64(v int) (uint64, error) {
return uint64(v), nil
}
+// IntToUint32 converts v to uint32, returning an error if it overflows.
+func IntToUint32(v int) (uint32, error) {
+ if v < 0 || v > math.MaxUint32 {
+ return 0, fmt.Errorf("intconv: int %d overflows uint32", v)
+ }
+
+ return uint32(v), nil
+}
+
+// Uint64ToInt64 converts v to int64, returning an error if it overflows.
+func Uint64ToInt64(v uint64) (int64, error) {
+ if v > math.MaxInt64 {
+ return 0, fmt.Errorf("intconv: uint64 %d overflows int64", v)
+ }
+
+ return int64(v), nil
+}
+
+// Int64ToUint64 converts v to uint64, returning an error if v is negative.
+func Int64ToUint64(v int64) (uint64, error) {
+ if v < 0 {
+ return 0, fmt.Errorf("intconv: int64 %d is negative", v)
+ }
+
+ return uint64(v), nil
+}
+
// Int64ToInt32 converts v to int32, returning an error if it overflows.
func Int64ToInt32(v int64) (int32, error) {
if v < math.MinInt32 || v > math.MaxInt32 {