Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions x/mongo/driver/compression.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,23 @@ var zstdReaderPool = sync.Pool{
},
}

// maxDecompressedSize is the largest UncompressedSize that DecompressPayload
// will honor. It matches the MongoDB wire protocol maximum message size
// (defaultMaxMessageSize in x/mongo/driver/topology/connection.go, 48000000
// bytes / ~48MB): a legitimate uncompressed message can never exceed it.
const maxDecompressedSize = 48000000

// DecompressPayload takes a byte slice that has been compressed and undoes it according to the options passed
func DecompressPayload(in []byte, opts CompressionOpts) ([]byte, error) {
// UncompressedSize is read directly off the wire (see
// wiremessage.ReadCompressedUncompressedSize) and is used below as an
// allocation size. Validate it before allocating so a malformed or
// malicious OP_COMPRESSED frame cannot drive an unbounded allocation
// (out of memory) or a make([]byte, negative) panic.
if opts.UncompressedSize < 0 || opts.UncompressedSize > maxDecompressedSize {
return nil, fmt.Errorf("invalid uncompressed size: %d", opts.UncompressedSize)
}
Comment thread
iabdullah215 marked this conversation as resolved.

switch opts.Compressor {
case wiremessage.CompressorNoOp:
return in, nil
Expand Down
34 changes: 34 additions & 0 deletions x/mongo/driver/compression_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,40 @@ func TestDecompressFailures(t *testing.T) {
_, err = DecompressPayload(compressedData, opts)
assert.Error(t, err)
})

// UncompressedSize is attacker-controlled (read straight off the wire in
// Operation.decompressWireMessage). An out-of-range value must be rejected
// with an error before it is used as an allocation size, otherwise a
// malicious server can drive an unbounded allocation (OOM) or a
// make([]byte, negative) panic.
validZlibEmptyBody := []byte{0x78, 0x9c, 0x03, 0x00, 0x00, 0x00, 0x00, 0x01}

oversized := []struct {
name string
compressor wiremessage.CompressorID
in []byte
size int32
}{
{"zlib oversized size", wiremessage.CompressorZLib, validZlibEmptyBody, 2147483647},
{"zlib negative size", wiremessage.CompressorZLib, validZlibEmptyBody, -1},
{"zstd oversized size", wiremessage.CompressorZstd, []byte{}, 2147483647},
{"zstd negative size", wiremessage.CompressorZstd, []byte{}, -1},
{"snappy negative size", wiremessage.CompressorSnappy, []byte{}, -1},
Comment thread
iabdullah215 marked this conversation as resolved.
}
for _, tc := range oversized {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

opts := CompressionOpts{
Compressor: tc.compressor,
UncompressedSize: tc.size,
}
// Must return an error rather than allocating tc.size bytes or panicking.
_, err := DecompressPayload(tc.in, opts)
assert.Error(t, err)
})
}
}

var (
Expand Down