From 2b42e25428171b73bd9408de0a66eedccc7c2014 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Fri, 9 Oct 2026 17:45:04 -0500 Subject: [PATCH] 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)},