diff options
| author | 2026-03-05 21:09:07 +0800 | |
|---|---|---|
| committer | 2026-03-05 21:14:24 +0800 | |
| commit | 355f5b3dc9ae560827cd274e113f43d09ee9ac49 (patch) | |
| tree | 2046f5d51110fff82aecf35c75884222f51ac36c | |
| parent | objectid, format/pack/ingest: Pack hash ID in algo (diff) | |
| signature | No signature | |
*: Fix overflows
| -rw-r--r-- | format/pack/ingest/finalize.go | 9 | ||||
| -rw-r--r-- | format/pack/ingest/idx_write.go | 9 | ||||
| -rw-r--r-- | format/pack/ingest/resolve.go | 13 | ||||
| -rw-r--r-- | format/pack/ingest/rev_write.go | 11 | ||||
| -rw-r--r-- | format/pack/ingest/stream_scan.go | 17 | ||||
| -rw-r--r-- | format/pack/ingest/thin_fix.go | 65 | ||||
| -rw-r--r-- | internal/compress/zlib/reader.go | 17 | ||||
| -rw-r--r-- | internal/compress/zlib/reader_reset.go | 19 | ||||
| -rw-r--r-- | internal/intconv/intconv.go | 27 |
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 { |
