Skip to content

Commit fd0787a

Browse files
authored
PYTHON-4615 Address sign-compare warning, improve array_of_documents_to_buffer validation (mongodb#1804)
1 parent 81ea92b commit fd0787a

File tree

3 files changed

+53
-22
lines changed

3 files changed

+53
-22
lines changed

bson/__init__.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -284,10 +284,10 @@ def _get_object_size(data: Any, position: int, obj_end: int) -> Tuple[int, int]:
284284
except struct.error as exc:
285285
raise InvalidBSON(str(exc)) from None
286286
end = position + obj_size - 1
287-
if data[end] != 0:
288-
raise InvalidBSON("bad eoo")
289287
if end >= obj_end:
290288
raise InvalidBSON("invalid object length")
289+
if data[end] != 0:
290+
raise InvalidBSON("bad eoo")
291291
# If this is the top-level document, validate the total size too.
292292
if position == 0 and obj_size != obj_end:
293293
raise InvalidBSON("invalid object length")
@@ -1180,9 +1180,10 @@ def _decode_selective(
11801180
return doc
11811181

11821182

1183-
def _array_of_documents_to_buffer(view: memoryview) -> bytes:
1183+
def _array_of_documents_to_buffer(data: Union[memoryview, bytes]) -> bytes:
11841184
# Extract the raw bytes of each document.
11851185
position = 0
1186+
view = memoryview(data)
11861187
_, end = _get_object_size(view, position, len(view))
11871188
position += 4
11881189
buffers: list[memoryview] = []

bson/_cbsonmodule.c

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2901,11 +2901,31 @@ static PyObject* _cbson_array_of_documents_to_buffer(PyObject* self, PyObject* a
29012901
"not enough data for a BSON document");
29022902
Py_DECREF(InvalidBSON);
29032903
}
2904-
goto done;
2904+
goto fail;
29052905
}
29062906

29072907
memcpy(&size, string, 4);
29082908
size = BSON_UINT32_FROM_LE(size);
2909+
2910+
/* validate the size of the array */
2911+
if (view.len != (int32_t)size || (int32_t)size < BSON_MIN_SIZE) {
2912+
PyObject* InvalidBSON = _error("InvalidBSON");
2913+
if (InvalidBSON) {
2914+
PyErr_SetString(InvalidBSON, "objsize too large");
2915+
Py_DECREF(InvalidBSON);
2916+
}
2917+
goto fail;
2918+
}
2919+
2920+
if (string[size - 1]) {
2921+
PyObject* InvalidBSON = _error("InvalidBSON");
2922+
if (InvalidBSON) {
2923+
PyErr_SetString(InvalidBSON, "bad eoo");
2924+
Py_DECREF(InvalidBSON);
2925+
}
2926+
goto fail;
2927+
}
2928+
29092929
/* save space for length */
29102930
if (pymongo_buffer_save_space(buffer, size) == -1) {
29112931
goto fail;
@@ -2948,30 +2968,22 @@ static PyObject* _cbson_array_of_documents_to_buffer(PyObject* self, PyObject* a
29482968
goto fail;
29492969
}
29502970

2951-
if (view.len < size) {
2952-
PyObject* InvalidBSON = _error("InvalidBSON");
2953-
if (InvalidBSON) {
2954-
PyErr_SetString(InvalidBSON, "objsize too large");
2955-
Py_DECREF(InvalidBSON);
2956-
}
2957-
goto fail;
2958-
}
2959-
2960-
if (string[size - 1]) {
2961-
PyObject* InvalidBSON = _error("InvalidBSON");
2962-
if (InvalidBSON) {
2963-
PyErr_SetString(InvalidBSON, "bad eoo");
2964-
Py_DECREF(InvalidBSON);
2965-
}
2966-
goto fail;
2967-
}
2968-
29692971
if (pymongo_buffer_write(buffer, string + position, value_length) == 1) {
29702972
goto fail;
29712973
}
29722974
position += value_length;
29732975
}
29742976

2977+
if (position != size - 1) {
2978+
PyObject* InvalidBSON = _error("InvalidBSON");
2979+
if (InvalidBSON) {
2980+
PyErr_SetString(InvalidBSON,
2981+
"bad object or element length");
2982+
Py_DECREF(InvalidBSON);
2983+
}
2984+
goto fail;
2985+
}
2986+
29752987
/* objectify buffer */
29762988
result = Py_BuildValue("y#", pymongo_buffer_get_buffer(buffer),
29772989
(Py_ssize_t)pymongo_buffer_get_position(buffer));

test/test_bson.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
EPOCH_AWARE,
4242
DatetimeMS,
4343
Regex,
44+
_array_of_documents_to_buffer,
4445
_datetime_to_millis,
4546
decode,
4647
decode_all,
@@ -1366,6 +1367,23 @@ def __int__(self):
13661367
with self.assertRaisesRegex(InvalidBSON, re.compile(re.escape(_DATETIME_ERROR_SUGGESTION))):
13671368
decode(encode({"a": DatetimeMS(small_ms)}))
13681369

1370+
def test_array_of_documents_to_buffer(self):
1371+
doc = dict(a=1)
1372+
buf = _array_of_documents_to_buffer(encode({"0": doc}))
1373+
self.assertEqual(buf, encode(doc))
1374+
buf = _array_of_documents_to_buffer(encode({"0": doc, "1": doc}))
1375+
self.assertEqual(buf, encode(doc) + encode(doc))
1376+
with self.assertRaises(InvalidBSON):
1377+
_array_of_documents_to_buffer(encode({"0": doc, "1": doc}) + b"1")
1378+
buf = encode({"0": doc, "1": doc})
1379+
buf = buf[:-1] + b"1"
1380+
with self.assertRaises(InvalidBSON):
1381+
_array_of_documents_to_buffer(buf)
1382+
# We replace the size of the array with \xff\xff\xff\x00 which is -221 as an int32.
1383+
buf = b"\x14\x00\x00\x00\x04a\x00\xff\xff\xff\x00\x100\x00\x01\x00\x00\x00\x00\x00"
1384+
with self.assertRaises(InvalidBSON):
1385+
_array_of_documents_to_buffer(buf)
1386+
13691387

13701388
class TestLongLongToString(unittest.TestCase):
13711389
def test_long_long_to_string(self):

0 commit comments

Comments
 (0)