From 2b42e25428171b73bd9408de0a66eedccc7c2014 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Fri, 9 Oct 2026 17:45:04 -0500 Subject: [PATCH 1/2] PYTHON-6111 Reject BSON documents where element data overlaps the document terminator --- bson/_cbsonmodule.c | 7 ++++++- doc/changelog.rst | 4 ++++ test/test_bson.py | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/bson/_cbsonmodule.c b/bson/_cbsonmodule.c index 0caf84a54d..40f4d9a68b 100644 --- a/bson/_cbsonmodule.c +++ b/bson/_cbsonmodule.c @@ -2432,6 +2432,9 @@ static PyObject* get_value(PyObject* self, PyObject* name, const char* buffer, } case 8: { + if (max < 1) { + goto invalid; + } char boolean_raw = buffer[(*position)++]; if (0 == boolean_raw) { value = Py_False; @@ -2485,7 +2488,9 @@ static PyObject* get_value(PyObject* self, PyObject* name, const char* buffer, } *position += (unsigned)pattern_length + 1; start += pattern_length + 1; - const char* flags_nul = memchr(start, 0, max - pattern_length); + /* PYTHON-6111: account for the pattern NUL consumed above so + * the flags terminator cannot overlap the document terminator. */ + const char* flags_nul = memchr(start, 0, max - pattern_length - 1); if (!flags_nul) { Py_DECREF(pattern); goto invalid; diff --git a/doc/changelog.rst b/doc/changelog.rst index fd8ef94334..212c9d946d 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -66,9 +66,13 @@ Bug fixes any field contains the reserved ``|`` delimiter (`PYTHON-6040`_). - Fixed a bug in SRV polling where invalid hosts where topology would not be updated if one returned host was invalid. +- 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`_). .. _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 Changes in Version 4.18.2 (2026/09/24) -------------------------------------- diff --git a/test/test_bson.py b/test/test_bson.py index 8367fdb95f..581b4c99df 100644 --- a/test/test_bson.py +++ b/test/test_bson.py @@ -461,6 +461,40 @@ def test_regex_empty_flags(self): self.assertEqual(decode(mm), expected) self.assertEqual(decode_all(mm), [expected]) + def test_element_overlaps_document_terminator(self): + # PYTHON-6111: an element whose value would consume the document + # terminator byte must be rejected, matching the pure-Python decoder. + bad_bsons = [ + # Boolean value byte is the document terminator. + b"\x08\x00\x00\x00\x08a\x00\x00", + # Regex pattern terminator is the document terminator. + b"\x08\x00\x00\x00\x0ba\x00\x00", + # Regex flags terminator is the document terminator. + b"\x0a\x00\x00\x00\x0ba\x00b\x00\x00", + ] + for data in bad_bsons: + msg = f"bad_bson={data!r}" + self.assertFalse(is_valid(data), msg=msg) + with self.assertRaises(InvalidBSON, msg=msg): + decode(data) + with self.assertRaises(InvalidBSON, msg=msg): + decode(bytearray(data)) + with self.assertRaises(InvalidBSON, msg=msg): + decode(memoryview(data)) + with self.assertRaises(InvalidBSON, msg=msg): + decode(array.array("B", data)) + with self.assertRaises(InvalidBSON, msg=msg): + list(decode_iter(data)) + with self.assertRaises(InvalidBSON, msg=msg): + decode_all(data) + with mmap.mmap(-1, len(data)) as mm: + mm.write(data) + mm.seek(0) + with self.assertRaises(InvalidBSON, msg=msg): + decode(mm) + with self.assertRaises(InvalidBSON, msg=msg): + decode_all(mm) + def test_data_timestamp(self): self.assertEqual( {"test": Timestamp(4, 20)}, From d2555b52ca2fc457c9f51a85c73ddc243a1d9fb5 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 13 Oct 2026 09:10:05 -0500 Subject: [PATCH 2/2] address review --- test/test_bson.py | 50 +++++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/test/test_bson.py b/test/test_bson.py index 581b4c99df..38e0d8c8d3 100644 --- a/test/test_bson.py +++ b/test/test_bson.py @@ -466,34 +466,34 @@ def test_element_overlaps_document_terminator(self): # terminator byte must be rejected, matching the pure-Python decoder. bad_bsons = [ # Boolean value byte is the document terminator. - b"\x08\x00\x00\x00\x08a\x00\x00", + ("boolean value", b"\x08\x00\x00\x00\x08a\x00\x00"), # Regex pattern terminator is the document terminator. - b"\x08\x00\x00\x00\x0ba\x00\x00", + ("regex pattern", b"\x08\x00\x00\x00\x0ba\x00\x00"), # Regex flags terminator is the document terminator. - b"\x0a\x00\x00\x00\x0ba\x00b\x00\x00", + ("regex flags", b"\x0a\x00\x00\x00\x0ba\x00b\x00\x00"), ] - for data in bad_bsons: - msg = f"bad_bson={data!r}" - self.assertFalse(is_valid(data), msg=msg) - with self.assertRaises(InvalidBSON, msg=msg): - decode(data) - with self.assertRaises(InvalidBSON, msg=msg): - decode(bytearray(data)) - with self.assertRaises(InvalidBSON, msg=msg): - decode(memoryview(data)) - with self.assertRaises(InvalidBSON, msg=msg): - decode(array.array("B", data)) - with self.assertRaises(InvalidBSON, msg=msg): - list(decode_iter(data)) - with self.assertRaises(InvalidBSON, msg=msg): - decode_all(data) - with mmap.mmap(-1, len(data)) as mm: - mm.write(data) - mm.seek(0) - with self.assertRaises(InvalidBSON, msg=msg): - decode(mm) - with self.assertRaises(InvalidBSON, msg=msg): - decode_all(mm) + for description, data in bad_bsons: + with self.subTest(description, bad_bson=data): + self.assertFalse(is_valid(data)) + with self.assertRaises(InvalidBSON): + decode(data) + with self.assertRaises(InvalidBSON): + decode(bytearray(data)) + with self.assertRaises(InvalidBSON): + decode(memoryview(data)) + with self.assertRaises(InvalidBSON): + decode(array.array("B", data)) + with self.assertRaises(InvalidBSON): + list(decode_iter(data)) + with self.assertRaises(InvalidBSON): + decode_all(data) + with mmap.mmap(-1, len(data)) as mm: + mm.write(data) + mm.seek(0) + with self.assertRaises(InvalidBSON): + decode(mm) + with self.assertRaises(InvalidBSON): + decode_all(mm) def test_data_timestamp(self): self.assertEqual(