python3-pymongo: Fix CVE-2026-106435

This patch applies the upstream fix as referenced in [3],
using the commit shown in [1] and regression commit from [2].

Follow up the PYTHON-6110 CVE fix: its regex flags scan leaves a
one-byte boundary error after consuming the pattern terminator.
Apply PYTHON-6111's correction and terminator-overlap regression test.

[1] https://github.com/mongodb/mongo-python-driver/commit/bb8d4e195275e75bd94ebeffb53cc747d357fcf4
[2] https://github.com/mongodb/mongo-python-driver/commit/b5a36ccbdb97298fbf9b5b5767affe92c534461f
[3] https://jira.mongodb.org/browse/PYTHON-6110
[4] https://jira.mongodb.org/browse/PYTHON-6111

Signed-off-by: Yogita Urade <yurade@cisco.com>
Signed-off-by: Anuj Mittal <anuj.mittal@oss.qualcomm.com>
This commit is contained in:
Yogita Urade authored and Anuj Mittal committed 2026-11-09 08:44:44 +05:30
1 parent a5890d9b37
commit 2332d83cbd
3 files changed
+218 -1

No files matched your search

@@ -0,0 +1,92 @@
From b5a36ccbdb97298fbf9b5b5767affe92c534461f Mon Sep 17 00:00:00 2001
From: Steven Silvester <steve.silvester@mongodb.com>
Date: Tue, 13 Oct 2026 09:30:43 -0500
Subject: [PATCH] PYTHON-6111 C extension accepts BSON documents where element
data overlaps the document terminator (#3113)
CVE: CVE-2026-106435
Upstream-Status: Backport [https://github.com/mongodb/mongo-python-driver/commit/b5a36ccbdb97298fbf9b5b5767affe92c534461f]
Backport Changes:
- Omitted doc/changelog.rst because the release note is documentation-only.
(cherry picked from commit b5a36ccbdb97298fbf9b5b5767affe92c534461f)
Signed-off-by: Yogita Urade <yurade@cisco.com>
---
bson/_cbsonmodule.c | 7 ++++++-
test/test_bson.py | 34 ++++++++++++++++++++++++++++++++++
2 files changed, 40 insertions(+), 1 deletion(-)
diff --git a/bson/_cbsonmodule.c b/bson/_cbsonmodule.c
index 3f141040..44cc9375 100644
--- a/bson/_cbsonmodule.c
+++ b/bson/_cbsonmodule.c
@@ -2221,6 +2221,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;
@@ -2274,7 +2277,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/test/test_bson.py b/test/test_bson.py
index bc367bc2..47511567 100644
--- a/test/test_bson.py
+++ b/test/test_bson.py
@@ -446,6 +446,40 @@ class TestBSON(unittest.TestCase):
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.
+ ("boolean value", b"\x08\x00\x00\x00\x08a\x00\x00"),
+ # Regex pattern terminator is the document terminator.
+ ("regex pattern", b"\x08\x00\x00\x00\x0ba\x00\x00"),
+ # Regex flags terminator is the document terminator.
+ ("regex flags", b"\x0a\x00\x00\x00\x0ba\x00b\x00\x00"),
+ ]
+ 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(
{"test": Timestamp(4, 20)},
--
2.35.6
@@ -0,0 +1,122 @@
From bb8d4e195275e75bd94ebeffb53cc747d357fcf4 Mon Sep 17 00:00:00 2001
From: Jeffrey 'Alex' Clark <aclark@aclark.net>
Date: Thu, 8 Oct 2026 14:45:52 -0400
Subject: [PATCH] PYTHON-6110 Bound Regex element scans to the remaining
document bytes (#3109)
Co-authored-by: Steven Silvester <steve.silvester@mongodb.com>
CVE: CVE-2026-106435
Upstream-Status: Backport [https://github.com/mongodb/mongo-python-driver/commit/bb8d4e195275e75bd94ebeffb53cc747d357fcf4]
(cherry picked from commit bb8d4e195275e75bd94ebeffb53cc747d357fcf4)
Signed-off-by: Yogita Urade <yurade@cisco.com>
---
bson/_cbsonmodule.c | 23 ++++++++++++++++-------
test/test_bson.py | 45 +++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 61 insertions(+), 7 deletions(-)
diff --git a/bson/_cbsonmodule.c b/bson/_cbsonmodule.c
index 7d184641..3f141040 100644
--- a/bson/_cbsonmodule.c
+++ b/bson/_cbsonmodule.c
@@ -2255,24 +2255,33 @@ static PyObject* get_value(PyObject* self, PyObject* name, const char* buffer,
PyObject* pattern;
int flags;
size_t flags_length, i;
- size_t pattern_length = strlen(buffer + *position);
- if (pattern_length > BSON_MAX_SIZE || max < pattern_length) {
+ /* PYTHON-6110: Search for each NUL within the bytes remaining
+ * in the document rather than scanning the buffer. */
+ const char* start = buffer + *position;
+ const char* pattern_nul = memchr(start, 0, max);
+ if (!pattern_nul) {
+ goto invalid;
+ }
+ size_t pattern_length = (size_t)(pattern_nul - start);
+ if (pattern_length > BSON_MAX_SIZE) {
goto invalid;
}
pattern = PyUnicode_DecodeUTF8(
- buffer + *position, pattern_length,
+ start, pattern_length,
options->unicode_decode_error_handler);
if (!pattern) {
goto invalid;
}
*position += (unsigned)pattern_length + 1;
- flags_length = strlen(buffer + *position);
- if (flags_length > BSON_MAX_SIZE ||
- (BSON_MAX_SIZE - pattern_length) < flags_length) {
+ start += pattern_length + 1;
+ const char* flags_nul = memchr(start, 0, max - pattern_length);
+ if (!flags_nul) {
Py_DECREF(pattern);
goto invalid;
}
- if (max < pattern_length + flags_length) {
+ flags_length = (size_t)(flags_nul - start);
+ if (flags_length > BSON_MAX_SIZE ||
+ (BSON_MAX_SIZE - pattern_length) < flags_length) {
Py_DECREF(pattern);
goto invalid;
}
diff --git a/test/test_bson.py b/test/test_bson.py
index ffc02965..bc367bc2 100644
--- a/test/test_bson.py
+++ b/test/test_bson.py
@@ -401,6 +401,51 @@ class TestBSON(unittest.TestCase):
# Assert that the InvalidBSON error message is not empty.
self.assertTrue(str(ctx.exception))
+ def test_malformed_regex(self):
+ # PYTHON-6110: decoding a malformed Regex element must raise
+ # InvalidBSON on every accepted buffer type.
+ malformed = []
+ for pattern in ("", "x", "xx"):
+ doc = bytearray(encode({"a": Regex(pattern, "")}))
+ # Drop the trailing flags NUL and document EOO bytes, leaving
+ # the flags string unterminated after the pattern's NUL.
+ del doc[-2:]
+ # Keep the length prefix consistent with the shorter document.
+ doc[:4] = struct.pack("<i", len(doc))
+ malformed.append(bytes(doc))
+ for data in malformed:
+ 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_regex_empty_flags(self):
+ # PYTHON-6110: a valid regex with an empty flags string must
+ # continue to decode.
+ valid = encode({"a": Regex("b", "")})
+ expected = {"a": Regex("b", 0)}
+ self.assertEqual(decode(valid), expected)
+ with mmap.mmap(-1, len(valid)) as mm:
+ mm.write(valid)
+ mm.seek(0)
+ self.assertEqual(decode(mm), expected)
+ self.assertEqual(decode_all(mm), [expected])
+
def test_data_timestamp(self):
self.assertEqual(
{"test": Timestamp(4, 20)},
--
2.35.6
@@ -8,7 +8,10 @@ HOMEPAGE = "https://github.com/mongodb/mongo-python-driver"
LICENSE = "Apache-2.0"
LIC_FILES_CHKSUM = "file://LICENSE;md5=86d3f3a95c324c9479bd8986968f4327"
SRC_URI += "file://CVE-2026-88029.patch"
SRC_URI += "file://CVE-2026-88029.patch \
file://CVE-2026-106435.patch \
file://CVE-2026-106435-regression.patch \
"
SRC_URI[sha256sum] = "8ba8405065f6e258a6f872fe62d797a28f383a12178c7153c01ed04e845c600c"