Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
12 changes: 7 additions & 5 deletions bson/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1133,15 +1133,17 @@ def _decode_all(data: _ReadableBuffer, opts: CodecOptions[_DocumentType]) -> lis
data_len = len(data)
docs: list[_DocumentType] = []
position = 0
end = data_len - 1
use_raw = _raw_document_class(opts.document_class)
try:
while position < end:
while position < data_len:
# A document is at minimum 5 bytes: the 4-byte size and the EOO.
if data_len - position < 5:
raise InvalidBSON("not enough data for a BSON document")
obj_size = _UNPACK_INT_FROM(data, position)[0]
if obj_size < 5:
raise InvalidBSON("invalid message size")
if data_len - position < obj_size:
raise InvalidBSON(
f"invalid object size: expected {obj_size}, got {data_len - position}"
)
raise InvalidBSON("objsize too large")
obj_end = position + obj_size - 1
if data[obj_end] != 0:
raise InvalidBSON("bad eoo")
Expand Down
4 changes: 4 additions & 0 deletions doc/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,14 @@ Bug fixes
- Fixed a bug where the C extension's BSON decoder accepted documents whose
element data overlaps the document terminator instead of raising
``InvalidBSON`` (`PYTHON-6111`_).
- Fixed a bug where the pure-Python ``decode_all()`` accepted buffers ending
in a partial document instead of raising ``InvalidBSON``, now matching the
C extension (`PYTHON-6163`_).

.. _PYTHON-6074: https://jira.mongodb.org/browse/PYTHON-6074
.. _PYTHON-6040: https://jira.mongodb.org/browse/PYTHON-6040
.. _PYTHON-6111: https://jira.mongodb.org/browse/PYTHON-6111
.. _PYTHON-6163: https://jira.mongodb.org/browse/PYTHON-6163

Changes in Version 4.18.2 (2026/09/24)
--------------------------------------
Expand Down
53 changes: 53 additions & 0 deletions test/test_bson.py
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,59 @@ def test_element_overlaps_document_terminator(self):
with self.assertRaises(InvalidBSON):
decode_all(mm)

def test_decode_all_partial_document(self):
# PYTHON-6163: decode_all() must reject buffers ending in a partial
# document, matching the C extension, which raises InvalidBSON when
# fewer than 5 bytes (the minimum BSON document size) remain.
Comment thread
blink1073 marked this conversation as resolved.
doc = encode({"a": 1})
partial_bsons = [
# A bare 1-byte buffer.
("1-byte buffer", b"\x00"),
# 2-4 byte buffers, too short for even the size field.
("2-byte buffer", b"\x05\x00"),
("3-byte buffer", b"\x05\x00\x00"),
("4-byte buffer", b"\x05\x00\x00\x00"),
# A valid document followed by a 1-byte tail.
("1-byte tail", doc + b"\x00"),
]
for description, data in partial_bsons:
with self.subTest(description, data=data):
with self.assertRaisesRegex(InvalidBSON, "not enough data for a BSON document"):
decode_all(data)
with self.assertRaisesRegex(InvalidBSON, "not enough data for a BSON document"):
decode_all(bytearray(data))
with self.assertRaisesRegex(InvalidBSON, "not enough data for a BSON document"):
decode_all(memoryview(data))
# Empty input is not an error.
self.assertEqual(decode_all(b""), [])

def test_decode_all_truncated_document(self):
# PYTHON-6163: decode_all() must reject documents declaring more
# bytes than remain, matching the C extension, which raises
# InvalidBSON("objsize too large").
doc = encode({"a": 1})
truncated_bsons = [
# Declares 12 bytes, only 7 present.
("truncated first document", b"\x0c\x00\x00\x00\x10a\x00"),
# A valid document followed by a truncated one.
("truncated second document", doc + b"\x0c\x00\x00\x00\x10a\x00"),
]
for description, data in truncated_bsons:
for buffer in (data, bytearray(data), memoryview(data)):
with self.subTest(description, buffer_type=type(buffer).__name__):
with self.assertRaisesRegex(InvalidBSON, "objsize too large"):
decode_all(buffer)

def test_decode_all_invalid_message_size(self):
# PYTHON-6163: a 5-byte buffer whose declared size is below the
# minimum must raise InvalidBSON("invalid message size"), matching
# the C extension.
data = b"\x04\x00\x00\x00\x00"
for buffer in (data, bytearray(data), memoryview(data)):
with self.subTest(buffer_type=type(buffer).__name__):
with self.assertRaisesRegex(InvalidBSON, "invalid message size"):
decode_all(buffer)

def test_data_timestamp(self):
self.assertEqual(
{"test": Timestamp(4, 20)},
Expand Down
Loading