From 4ab7901f31ae2b475d74ae2d4ce35e2fa34ab879 Mon Sep 17 00:00:00 2001 From: youdie006 Date: Wed, 5 Aug 2026 13:34:17 +0900 Subject: [PATCH] index: guard posting iterator against overflowing varints newCompressedPostingIterator and (*compressedPostingIterator).next sliced blob[sz:] using the length returned by binary.Uvarint without checking it. binary.Uvarint returns a non-positive length when the varint is empty or overflows 64 bits, so a malformed posting list made the slice bound go negative and panic (index out of range [-11:]). Guard both call sites: on a non-positive length, treat the posting list as exhausted instead of slicing. Add a regression test using the overflowing-varint input from the report. --- index/hititer.go | 15 +++++++++++++++ index/hititer_test.go | 22 ++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/index/hititer.go b/index/hititer.go index 1d63fc797..4d55074b2 100644 --- a/index/hititer.go +++ b/index/hititer.go @@ -187,6 +187,15 @@ type compressedPostingIterator struct { func newCompressedPostingIterator(b []byte, w ngram) *compressedPostingIterator { d, sz := binary.Uvarint(b) + if sz <= 0 { + // binary.Uvarint returns a non-positive length when b is empty or the + // varint overflows 64 bits; slicing b[sz:] would panic, so yield an + // exhausted iterator (issue #1106). + return &compressedPostingIterator{ + _first: math.MaxUint32, + what: w, + } + } return &compressedPostingIterator{ _first: uint32(d), blob: b[sz:], @@ -212,6 +221,12 @@ func (i *compressedPostingIterator) next(limit uint32) { for i._first <= limit && len(i.blob) > 0 { delta, sz := binary.Uvarint(i.blob) + if sz <= 0 { + // Corrupt or overflowing varint; stop advancing rather than panic + // on i.blob[sz:] (issue #1106). + i.blob = nil + break + } i._first += uint32(delta) i.indexBytesLoaded += sz i.blob = i.blob[sz:] diff --git a/index/hititer_test.go b/index/hititer_test.go index ab40b3798..b2273d50d 100644 --- a/index/hititer_test.go +++ b/index/hititer_test.go @@ -16,6 +16,7 @@ package index import ( "fmt" + "math" "math/rand" "reflect" "testing" @@ -112,3 +113,24 @@ func genUints32(size int) []uint32 { } return nums } + +func TestCompressedPostingIterator_overflowVarint(t *testing.T) { + // A varint that overflows 64 bits makes binary.Uvarint return a negative + // length, so slicing blob[sz:] used to panic (issue #1106). The iterator + // must instead treat the posting list as exhausted. + overflow := []byte{0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01} + + // Construction from an overflowing first varint. + it := newCompressedPostingIterator(overflow, stringToNGram("abc")) + if got := it.first(); got != math.MaxUint32 { + t.Fatalf("first() after overflow varint = %d, want exhausted (%d)", got, uint32(math.MaxUint32)) + } + + // A valid first entry followed by an overflowing delta, hit while advancing. + blob := append([]byte{0x01}, overflow...) + it = newCompressedPostingIterator(blob, stringToNGram("abc")) + it.next(100) + if got := it.first(); got != math.MaxUint32 { + t.Fatalf("first() after overflow delta = %d, want exhausted (%d)", got, uint32(math.MaxUint32)) + } +}