Skip to content

Commit 4b619b7

Browse files
committed
compression: avoid msan crashes
Some of the compression algorithms use hand-written assembly code, which is "invislble" to the memory sanitizer. Together with the recent change to use `malloc` instead of `calloc` for the block cache, this results in uninitialized memory failures with `msan`. The fix is to call `runtime.MSanWrite()` to inform `msan` that the memory was written to. Note that `runtime.MSanWrite()` exists only in builds with the `msan` tag. Fixes #5275
1 parent dd58b84 commit 4b619b7

File tree

6 files changed

+50
-6
lines changed

6 files changed

+50
-6
lines changed

internal/compression/minlz.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ func (c *minlzCompressor) Compress(dst, src []byte) ([]byte, Setting) {
2929
if err != nil {
3030
panic(errors.Wrap(err, "minlz compression"))
3131
}
32+
msanWrite(compressed)
3233
return compressed, Setting{Algorithm: MinLZ, Level: uint8(c.level)}
3334
}
3435

@@ -54,11 +55,15 @@ var _ Decompressor = minlzDecompressor{}
5455

5556
func (minlzDecompressor) DecompressInto(buf, compressed []byte) error {
5657
result, err := minlz.Decode(buf, compressed)
58+
if err != nil {
59+
return err
60+
}
5761
if len(result) != len(buf) || (len(result) > 0 && &result[0] != &buf[0]) {
5862
return base.CorruptionErrorf("pebble/table: decompressed into unexpected buffer: %p != %p",
5963
errors.Safe(result), errors.Safe(buf))
6064
}
61-
return err
65+
msanWrite(result)
66+
return nil
6267
}
6368

6469
func (minlzDecompressor) DecompressedLen(b []byte) (decompressedLen int, err error) {

internal/compression/msan_off.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright 2025 The LevelDB-Go and Pebble Authors. All rights reserved. Use
2+
// of this source code is governed by a BSD-style license that can be found in
3+
// the LICENSE file.
4+
5+
//go:build !msan
6+
7+
package compression
8+
9+
// msanWrite is used to inform MemorySanitizer that the memory in p was written
10+
// to; this is necessary for some compression algorithms which use assembly code.
11+
func msanWrite(p []byte) {}

internal/compression/msan_on.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright 2025 The LevelDB-Go and Pebble Authors. All rights reserved. Use
2+
// of this source code is governed by a BSD-style license that can be found in
3+
// the LICENSE file.
4+
5+
//go:build msan
6+
7+
package compression
8+
9+
import (
10+
"runtime"
11+
"unsafe"
12+
)
13+
14+
// msanWrite is used to inform MemorySanitizer that the memory in p was written
15+
// to; this is necessary for some compression algorithms which use assembly code.
16+
func msanWrite(p []byte) {
17+
if len(p) > 0 {
18+
runtime.MSanWrite(unsafe.Pointer(&p[0]), len(p))
19+
}
20+
}

internal/compression/snappy.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ var _ Compressor = snappyCompressor{}
1717
func (snappyCompressor) Algorithm() Algorithm { return Snappy }
1818

1919
func (snappyCompressor) Compress(dst, src []byte) ([]byte, Setting) {
20-
dst = dst[:cap(dst):cap(dst)]
21-
return snappy.Encode(dst, src), SnappySetting
20+
result := snappy.Encode(dst[:cap(dst):cap(dst)], src)
21+
msanWrite(result)
22+
return result, SnappySetting
2223
}
2324

2425
func (snappyCompressor) Close() {}
@@ -36,6 +37,7 @@ func (snappyDecompressor) DecompressInto(buf, compressed []byte) error {
3637
return base.CorruptionErrorf("pebble: decompressed into unexpected buffer: %p != %p",
3738
errors.Safe(result), errors.Safe(buf))
3839
}
40+
msanWrite(buf)
3941
return nil
4042
}
4143

internal/compression/zstd_cgo.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ func (z *zstdCompressor) Compress(compressedBuf []byte, b []byte) ([]byte, Setti
6060
if &result[0] != &compressedBuf[varIntLen] {
6161
panic("Allocated a new buffer despite checking CompressBound.")
6262
}
63+
msanWrite(result)
6364

6465
return compressedBuf[:varIntLen+len(result)], Setting{Algorithm: Zstd, Level: uint8(z.level)}
6566
}
@@ -80,8 +81,7 @@ type zstdDecompressor struct {
8081

8182
var _ Decompressor = (*zstdDecompressor)(nil)
8283

83-
// DecompressInto decompresses src with the Zstandard algorithm. The destination
84-
// buffer must already be sufficiently sized, otherwise DecompressInto may error.
84+
// DecompressInto is part of the Decompressor interface.
8585
func (z *zstdDecompressor) DecompressInto(dst, src []byte) error {
8686
// The payload is prefixed with a varint encoding the length of
8787
// the decompressed block.
@@ -93,10 +93,14 @@ func (z *zstdDecompressor) DecompressInto(dst, src []byte) error {
9393
if len(dst) == 0 {
9494
return errors.Errorf("decodeZstd: empty dst buffer")
9595
}
96-
_, err := z.ctx.DecompressInto(dst, src)
96+
n, err := z.ctx.DecompressInto(dst, src)
9797
if err != nil {
9898
return err
9999
}
100+
if n != len(dst) {
101+
return base.CorruptionErrorf("ZSTD decompressed %d bytes, expected %d", n, len(dst))
102+
}
103+
msanWrite(dst)
100104
return nil
101105
}
102106

internal/compression/zstd_nocgo.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ func (z *zstdCompressor) Compress(compressedBuf, b []byte) ([]byte, Setting) {
4343
}
4444
varIntLen := binary.PutUvarint(compressedBuf, uint64(len(b)))
4545
res := z.encoder.EncodeAll(b, compressedBuf[:varIntLen])
46+
msanWrite(res)
4647
return res, Setting{Algorithm: Zstd, Level: uint8(z.level)}
4748
}
4849

@@ -84,6 +85,7 @@ func (zstdDecompressor) DecompressInto(dst, src []byte) error {
8485
return base.CorruptionErrorf("pebble/table: decompressed into unexpected buffer: %p != %p",
8586
errors.Safe(result), errors.Safe(dst))
8687
}
88+
msanWrite(result)
8789
return nil
8890
}
8991

0 commit comments

Comments
 (0)