Skip to content

Commit 8160453

Browse files
miss-islingtoncmaloneyclaude
authored
[3.15] gh-158928: Fix overflow in bytearray.__init__ from an iterator (GH-158947) (#161448)
Passing an iterator to `bytearray.__init__` didn't take into account that iterating that iterator could modify the bytearray position and `ob_start`. When that happened the iterator would write beyond the end of the allocation. Update the "enough space" check to account for `ob_start`. The assignment always included it. Add an assert after append that the offsets line up as expected. (cherry picked from commit 25181ce) Co-authored-by: Cody Maloney <cmaloney@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 476b725 commit 8160453

3 files changed

Lines changed: 31 additions & 4 deletions

File tree

‎Lib/test/test_bytes.py‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2027,6 +2027,22 @@ def g():
20272027
alloc = b.__alloc__()
20282028
self.assertGreater(alloc, len(b))
20292029

2030+
def test_init_from_iterator_with_offset(self):
2031+
# gh-158928: Inserting from an iterator in __init__ needs to take into
2032+
# account if there is a start offset.
2033+
b = bytearray()
2034+
def iterator_which_resets():
2035+
# __init__ reset ob_start. Make it an offset inside by allocating
2036+
# then doing fast prefix delete.
2037+
nonlocal b
2038+
b.resize(200)
2039+
del b[:100]
2040+
# Fill remaining already allocated space
2041+
yield from b'A' * 100
2042+
# Fill to end. Used to land out of bounds and crash.
2043+
b.__init__(iterator_which_resets())
2044+
self.assertEqual(b, bytes(100) + b'A' * 100)
2045+
20302046
def test_extend(self):
20312047
orig = b'hello'
20322048
a = bytearray(orig)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix a buffer overflow when initializing a :class:`bytearray` from an
2+
iterator.

‎Objects/bytearrayobject.c‎

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1158,17 +1158,26 @@ bytearray___init___impl(PyByteArrayObject *self, PyObject *arg,
11581158
/* Interpret it as an int (__index__) */
11591159
rc = _getbytevalue(item, &value);
11601160
Py_DECREF(item);
1161-
if (!rc)
1161+
if (!rc) {
11621162
goto error;
1163+
}
1164+
1165+
/* Append the byte.
11631166
1164-
/* Append the byte */
1165-
if (Py_SIZE(self) + 1 < self->ob_alloc) {
1167+
gh-158928: Iterators are arbitrary code which could modify the
1168+
bytearray so this must re-calculate if there is enough space(). */
1169+
Py_ssize_t needed =
1170+
self->ob_start - self->ob_bytes + Py_SIZE(self) + 1;
1171+
if (needed < self->ob_alloc) {
11661172
Py_SET_SIZE(self, Py_SIZE(self) + 1);
11671173
bytearray_write_trailing_null_byte(self);
11681174
}
1169-
else if (PyByteArray_Resize((PyObject *)self, Py_SIZE(self)+1) < 0)
1175+
else if (PyByteArray_Resize((PyObject *)self, Py_SIZE(self)+1) < 0) {
11701176
goto error;
1177+
}
11711178
PyByteArray_AS_STRING(self)[Py_SIZE(self)-1] = value;
1179+
assert(self->ob_start - self->ob_bytes + Py_SIZE(self) <=
1180+
self->ob_alloc);
11721181
}
11731182

11741183
/* Clean up and return success */

0 commit comments

Comments
 (0)