Skip to content

BloomFilter: Panic on insufficient buffer size when hash offset exceeds capacity #940

@thanhtoantnt

Description

@thanhtoantnt

Describe the bug(Bug 描述)

DefaultOneHitBloomFilter and NewOneHitBloomFilter panic with slice bounds out of range when the buffer size is too small for the hash values provided. There is no input validation or documentation of minimum size requirements.

Reproduction

func TestBloomFilter_PanicOnSmallSize(t *testing.T) {
    bf := bloomfilter.DefaultOneHitBloomFilter(0, 8)
    hash := uint64(0x2000000000000)
    bf.Add(hash)  // panic: slice bounds out of range [:9] with capacity 8
}

All three versions (V0, V2, V3) are affected:

// V0: offset = hash >> 49, max offset = 32767, min size = 32775
bf := bloomfilter.DefaultOneHitBloomFilter(0, 8)
bf.Add(uint64(0x2000000000000))  // panic

// V2: offset = hash >> 46, max offset = 262143, min size = 262151
bf := bloomfilter.DefaultOneHitBloomFilter(2, 8)
bf.Add(uint64(0x400000000000))  // panic

// V3: offset = hash >> 46, max offset = 262143, min size = 262151
bf := bloomfilter.DefaultOneHitBloomFilter(3, 8)
bf.Add(uint64(0x400000000000))  // panic

Even a single byte below the minimum causes a panic:

bf := bloomfilter.DefaultOneHitBloomFilter(0, 32774) // min is 32775
bf.Add(uint64(0x7FFF)<<49)  // panic: slice bounds out of range [:32775] with capacity 32774

Root Cause

The Add() and Hit() methods compute offsets from hash values without bounds checking:

// bloomfilter.go:89-94
func (b *OneHitBloomFilterV0) Add(hash uint64) {
    var offset = int(hash >> 49)           // offset can be 0..32767
    // ...
    s := binary.LittleEndian.Uint64(b.bytes[offset : offset+8]) // panic if offset+8 > len(b.bytes)
}
Version Offset Calculation Max Offset Min Required Size
V0/V1 hash >> 49 32,767 32,775 bytes
V2/V3 hash >> 46 262,143 262,151 bytes

Production Status

Production code is not currently affected because it always uses the correct sizes from constants:

// lib/logstore/constant.go
LogStoreConstantV0 = InitConstant(32*1024+64, ...)  // 32,832 bytes (57 byte margin)
LogStoreConstantV2 = InitConstant(256*1024+64, ...) // 262,208 bytes (57 byte margin)

However, nothing prevents future code from passing an invalid size. The API accepts any int64 with no validation or documentation.

Suggested Fix

Add minimum size validation:

func DefaultOneHitBloomFilter(version uint32, bloomfiterSize int64) Bloomfilter {
    minSize := int64(32775) // version <= 1
    if version >= 2 {
        minSize = 262151
    }
    if bloomfiterSize < minSize {
        panic(fmt.Sprintf("bloomfilter version %d requires at least %d bytes, got %d",
            version, minSize, bloomfiterSize))
    }
    bytes := make([]byte, bloomfiterSize)
    return NewOneHitBloomFilter(bytes, version)
}

To Reproduce(Bug 复现步骤)

No response

Expected behavior(期望结果)

No response

Screenshots(屏幕截图)

No response

Logs(完整的错误日志)

No response

Additional context(其他的一些补充内容)

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions