-
Type:
Bug
-
Resolution: Unresolved
-
Priority:
Unknown
-
None
-
Affects Version/s: None
-
Component/s: None
-
None
-
None
-
Python Drivers
-
None
-
None
-
None
-
None
-
None
-
None
Summary
receive_message (sync path) is missing the OP_COMPRESSED minimum-length guard that exists in the async PyMongoProtocol.process_header.
Affected code
pymongo/network_layer.py — receive_message (sync), line ~777:
if op_code == 2012: op_code, _, compressor_id = _UNPACK_COMPRESSION_HEADER(receive_data(conn, 9, deadline)) data = decompress(receive_data(conn, length - 25, deadline), compressor_id) # ← negative count possible
Root cause
The only length guard in receive_message is if length <= 16: raise ProtocolError(...), which applies to all opcodes. For op_code == 2012 (OP_COMPRESSED), the minimum valid total message length is 26 (16-byte header + 9-byte compression sub-header + at least 1 byte of payload). The sync path does not enforce this.
For a malformed OP_COMPRESSED frame with 16 < length ≤ 24, length - 25 is negative. receive_data immediately calls bytearray(negative_number), which raises ValueError: bytearray() argument 2 cannot be negative — not ProtocolError.
For length == 25, receive_data(conn, 0, deadline) succeeds (returns an empty buffer), and decompress is called on 0 bytes — behavior is compressor-dependent.
Async parity
PyMongoProtocol.process_header has the correct guard:
if op_code == 2012: if length <= 25: raise ProtocolError( f"Message length ({length!r}) not longer than standard OP_COMPRESSED message header size (25)" )
Fix
Add the same guard to receive_message before entering the op_code == 2012 branch:
if op_code == 2012: if length <= 25: raise ProtocolError( f"Message length ({length!r}) not longer than standard OP_COMPRESSED message header size (25)" ) ...
Discovery
Found during code review of PR #2774 (coverage increase for network_layer.py). The new tests do not cover the OP_COMPRESSED sync path, so this gap was not caught by the test suite.