Skip to content

Commit 97e7b90

Browse files
committed
fix: guard against nil chunk in bucket.Get to prevent panic
When cache data is loaded from a corrupted file, map entries may point to chunk indices that are allocated but nil (beyond the saved chunksLen). The existing bounds check verified chunkIdx against len(chunks) but did not verify the chunk itself was non-nil before slicing it, which could cause a runtime panic instead of being counted as a corruption miss. Add a nil check for the chunk immediately after retrieval from the chunks slice, before any slice expressions are evaluated. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent cef9ae9 commit 97e7b90

2 files changed

Lines changed: 38 additions & 2 deletions

File tree

fastcache.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -393,9 +393,12 @@ func (b *bucket) Get(dst, k []byte, h uint64, returnDst bool) ([]byte, bool) {
393393
goto end
394394
}
395395
chunk := chunks[chunkIdx]
396+
if chunk == nil {
397+
atomic.AddUint64(&b.corruptions, 1)
398+
goto end
399+
}
396400
idx %= chunkSize
397401
if idx+4 >= chunkSize {
398-
// Corrupted data during the load from file. Just skip it.
399402
atomic.AddUint64(&b.corruptions, 1)
400403
goto end
401404
}
@@ -404,7 +407,6 @@ func (b *bucket) Get(dst, k []byte, h uint64, returnDst bool) ([]byte, bool) {
404407
valLen := (uint64(kvLenBuf[2]) << 8) | uint64(kvLenBuf[3])
405408
idx += 4
406409
if idx+keyLen+valLen >= chunkSize {
407-
// Corrupted data during the load from file. Just skip it.
408410
atomic.AddUint64(&b.corruptions, 1)
409411
goto end
410412
}

fastcache_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"sync"
77
"testing"
88
"time"
9+
10+
xxhash "github.com/cespare/xxhash/v2"
911
)
1012

1113
func TestCacheSmall(t *testing.T) {
@@ -222,6 +224,38 @@ func testCacheGetSet(c *Cache, itemsCount int) error {
222224
return nil
223225
}
224226

227+
func TestBucketGetNilChunkNoCorruptionPanic(t *testing.T) {
228+
var b bucket
229+
b.Init(64 * 1024)
230+
231+
k := []byte("testkey")
232+
v := []byte("testvalue")
233+
h := xxhash.Sum64(k)
234+
b.Set(k, v, h)
235+
236+
got, found := b.Get(nil, k, h, true)
237+
if !found || string(got) != "testvalue" {
238+
t.Fatalf("expected to find key before corruption; found=%v val=%q", found, got)
239+
}
240+
241+
b.mu.Lock()
242+
mapVal := b.m[h]
243+
chunkIdx := (mapVal & ((1 << bucketSizeBits) - 1)) / chunkSize
244+
b.chunks[chunkIdx] = nil
245+
b.mu.Unlock()
246+
247+
got, found = b.Get(nil, k, h, true)
248+
if found {
249+
t.Fatalf("expected miss for nil chunk, but got found=true val=%q", got)
250+
}
251+
252+
var s Stats
253+
b.UpdateStats(&s)
254+
if s.Corruptions != 1 {
255+
t.Fatalf("expected 1 corruption; got %d", s.Corruptions)
256+
}
257+
}
258+
225259
func TestCacheResetUpdateStatsSetConcurrent(t *testing.T) {
226260
c := New(12334)
227261

0 commit comments

Comments
 (0)