From ab9e1419fb729d6798f22550bd057c43c507e67a Mon Sep 17 00:00:00 2001 From: iabdullah215 Date: Fri, 10 Jul 2026 23:57:44 +0500 Subject: [PATCH 1/2] Validate UncompressedSize before allocating in DecompressPayload DecompressPayload used opts.UncompressedSize -- which is read unvalidated off the wire via wiremessage.ReadCompressedUncompressedSize in Operation.decompressWireMessage -- directly as an allocation size. A malformed or malicious OP_COMPRESSED frame could therefore set it to a large value (up to math.MaxInt32) to drive a ~2GB allocation (OOM), or to a negative value to trigger a make([]byte, negative) runtime panic. Reject sizes < 0 or > 48000000 (the wire protocol maximum message size, defaultMaxMessageSize) at the top of DecompressPayload, before any allocation on the zlib, zstd, and snappy paths. Add regression tests to TestDecompressFailures covering oversized and negative UncompressedSize values for zlib, zstd, and snappy. --- x/mongo/driver/compression.go | 15 +++++++++++++ x/mongo/driver/compression_test.go | 34 ++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/x/mongo/driver/compression.go b/x/mongo/driver/compression.go index 349781f4ec..cd66502fb1 100644 --- a/x/mongo/driver/compression.go +++ b/x/mongo/driver/compression.go @@ -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) + } + switch opts.Compressor { case wiremessage.CompressorNoOp: return in, nil diff --git a/x/mongo/driver/compression_test.go b/x/mongo/driver/compression_test.go index 10c35eb0e6..9d266ee6d5 100644 --- a/x/mongo/driver/compression_test.go +++ b/x/mongo/driver/compression_test.go @@ -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}, + } + 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 ( From 871110678fe2ad6bd46ff0949f3e3d5678550632 Mon Sep 17 00:00:00 2001 From: iabdullah215 Date: Sat, 11 Jul 2026 00:25:03 +0500 Subject: [PATCH 2/2] Address review: clarify error and add snappy oversized test Include the allowed range (0..maxDecompressedSize) in the invalid-size error so the rejected value is easier to diagnose. Add a "snappy oversized size" case to TestDecompressFailures so the out-of-range check is exercised for all three compressor IDs. --- x/mongo/driver/compression.go | 2 +- x/mongo/driver/compression_test.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/x/mongo/driver/compression.go b/x/mongo/driver/compression.go index cd66502fb1..690d3e10f4 100644 --- a/x/mongo/driver/compression.go +++ b/x/mongo/driver/compression.go @@ -167,7 +167,7 @@ func DecompressPayload(in []byte, opts CompressionOpts) ([]byte, error) { // 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) + return nil, fmt.Errorf("invalid uncompressed size %d: must be between 0 and %d", opts.UncompressedSize, maxDecompressedSize) } switch opts.Compressor { diff --git a/x/mongo/driver/compression_test.go b/x/mongo/driver/compression_test.go index 9d266ee6d5..c716df0bb4 100644 --- a/x/mongo/driver/compression_test.go +++ b/x/mongo/driver/compression_test.go @@ -120,6 +120,7 @@ func TestDecompressFailures(t *testing.T) { {"zlib negative size", wiremessage.CompressorZLib, validZlibEmptyBody, -1}, {"zstd oversized size", wiremessage.CompressorZstd, []byte{}, 2147483647}, {"zstd negative size", wiremessage.CompressorZstd, []byte{}, -1}, + {"snappy oversized size", wiremessage.CompressorSnappy, []byte{}, 2147483647}, {"snappy negative size", wiremessage.CompressorSnappy, []byte{}, -1}, } for _, tc := range oversized {