Skip to content

Commit a7e4364

Browse files
authored
Merge pull request #419 from bootjp/copilot/sub-pr-414
store: fix native snapshot restore bounds, sentinel errors, and reduce test allocations
2 parents 756c14a + 59715af commit a7e4364

3 files changed

Lines changed: 35 additions & 8 deletions

File tree

store/lsm_store.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -683,7 +683,7 @@ func readRestoreEntry(r io.Reader, keyBuf *[]byte) (kLen, vLen int, eof bool, er
683683
if _, err = io.ReadFull(r, (*keyBuf)[:kLen]); err != nil {
684684
return 0, 0, false, errors.WithStack(err)
685685
}
686-
vLen, err = readRestoreFieldLen(r, "snapshot value", maxSnapshotValueSize)
686+
vLen, err = readRestoreFieldLen(r, "snapshot value", maxSnapshotValueSize+valueHeaderSize)
687687
if err != nil {
688688
return 0, 0, false, err
689689
}
@@ -700,7 +700,14 @@ func readRestoreFieldLen(r io.Reader, field string, maxSize int) (int, error) {
700700

701701
func restoreFieldLenInt(raw uint64, field string, maxSize int) (int, error) {
702702
if raw > uint64(math.MaxInt) || int(raw) > maxSize {
703-
return 0, errors.WithStack(errors.Newf("%s length %d > %d", field, raw, maxSize))
703+
switch field {
704+
case "snapshot key":
705+
return 0, errors.WithStack(errors.Wrapf(ErrSnapshotKeyTooLarge, "length %d > %d", raw, maxSize))
706+
case "snapshot value":
707+
return 0, errors.WithStack(errors.Wrapf(ErrValueTooLarge, "length %d > %d", raw, maxSize))
708+
default:
709+
return 0, errors.WithStack(errors.Newf("%s length %d > %d", field, raw, maxSize))
710+
}
704711
}
705712
return int(raw), nil
706713
}

store/lsm_store_test.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -514,9 +514,12 @@ func TestPebbleStore_Restore_NativePebbleAtomic(t *testing.T) {
514514
}
515515

516516
// TestPebbleStore_PutAt_ValueTooLarge verifies that PutAt rejects values
517-
// exceeding maxSnapshotValueSize (256 MiB) to ensure write and restore are
518-
// consistent.
517+
// exceeding maxSnapshotValueSize to ensure write and restore are consistent.
519518
func TestPebbleStore_PutAt_ValueTooLarge(t *testing.T) {
519+
orig := maxSnapshotValueSize
520+
maxSnapshotValueSize = 100
521+
t.Cleanup(func() { maxSnapshotValueSize = orig })
522+
520523
dir, err := os.MkdirTemp("", "pebble-value-limit-*")
521524
require.NoError(t, err)
522525
defer os.RemoveAll(dir)
@@ -535,6 +538,10 @@ func TestPebbleStore_PutAt_ValueTooLarge(t *testing.T) {
535538
// TestPebbleStore_ApplyMutations_ValueTooLarge verifies that ApplyMutations
536539
// rejects Put mutations with values exceeding maxSnapshotValueSize.
537540
func TestPebbleStore_ApplyMutations_ValueTooLarge(t *testing.T) {
541+
orig := maxSnapshotValueSize
542+
maxSnapshotValueSize = 100
543+
t.Cleanup(func() { maxSnapshotValueSize = orig })
544+
538545
dir, err := os.MkdirTemp("", "pebble-apply-value-limit-*")
539546
require.NoError(t, err)
540547
defer os.RemoveAll(dir)
@@ -555,6 +562,10 @@ func TestPebbleStore_ApplyMutations_ValueTooLarge(t *testing.T) {
555562
// TestMVCCStore_PutAt_ValueTooLarge verifies that the in-memory mvccStore
556563
// also rejects oversized values.
557564
func TestMVCCStore_PutAt_ValueTooLarge(t *testing.T) {
565+
orig := maxSnapshotValueSize
566+
maxSnapshotValueSize = 100
567+
t.Cleanup(func() { maxSnapshotValueSize = orig })
568+
558569
s := NewMVCCStore()
559570
defer s.Close()
560571

@@ -568,6 +579,10 @@ func TestMVCCStore_PutAt_ValueTooLarge(t *testing.T) {
568579
// TestMVCCStore_ApplyMutations_ValueTooLarge verifies that the in-memory
569580
// mvccStore rejects oversized Put mutations.
570581
func TestMVCCStore_ApplyMutations_ValueTooLarge(t *testing.T) {
582+
orig := maxSnapshotValueSize
583+
maxSnapshotValueSize = 100
584+
t.Cleanup(func() { maxSnapshotValueSize = orig })
585+
571586
s := NewMVCCStore()
572587
defer s.Close()
573588

store/mvcc_store.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,16 @@ type VersionedValue struct {
2828
const (
2929
checksumSize = 4
3030
mvccSnapshotVersion = uint32(1)
31-
maxSnapshotKeySize = 1 << 20 // 1 MiB per key
32-
maxSnapshotVersionCount = 1 << 20 // 1M versions per key
33-
maxSnapshotValueSize = 256 << 20 // 256 MiB per value; prevents OOM from malformed snapshots
31+
maxSnapshotKeySize = 1 << 20 // 1 MiB per key
32+
maxSnapshotVersionCount = 1 << 20 // 1M versions per key
3433
)
3534

35+
// maxSnapshotValueSize caps the allowed size of a single value during snapshot
36+
// restore and write paths to prevent OOM from malformed or adversarial snapshots.
37+
// Declared as a var so tests can temporarily lower the limit without allocating
38+
// hundreds of MiB.
39+
var maxSnapshotValueSize = 256 << 20 // 256 MiB
40+
3641
var mvccSnapshotMagic = [8]byte{'E', 'K', 'V', 'M', 'V', 'C', 'C', '2'}
3742

3843
// mvccSnapshot is retained for backward compatibility with older gob-backed
@@ -848,7 +853,7 @@ func readMVCCSnapshotVersion(r io.Reader) (VersionedValue, error) {
848853
if err := binary.Read(r, binary.LittleEndian, &valueLen); err != nil {
849854
return VersionedValue{}, errors.WithStack(err)
850855
}
851-
if valueLen > maxSnapshotValueSize {
856+
if valueLen > uint64(maxSnapshotValueSize) {
852857
return VersionedValue{}, errors.Wrapf(ErrValueTooLarge, "%d > %d", valueLen, maxSnapshotValueSize)
853858
}
854859
value := make([]byte, valueLen)

0 commit comments

Comments
 (0)