Skip to content

Commit 97ac28d

Browse files
cmaloneyclaude
authored andcommitted
gh-158928: Fix overflow in bytearray.__init__ from an iterator (GH-158947)
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 9ed62ac commit 97ac28d

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
@@ -1814,6 +1814,22 @@ def g():
18141814
alloc = b.__alloc__()
18151815
self.assertGreater(alloc, len(b))
18161816

1817+
def test_init_from_iterator_with_offset(self):
1818+
# gh-158928: Inserting from an iterator in __init__ needs to take into
1819+
# account if there is a start offset.
1820+
b = bytearray()
1821+
def iterator_which_resets():
1822+
# __init__ reset ob_start. Make it an offset inside by allocating
1823+
# then doing fast prefix delete.
1824+
nonlocal b
1825+
b.resize(200)
1826+
del b[:100]
1827+
# Fill remaining already allocated space
1828+
yield from b'A' * 100
1829+
# Fill to end. Used to land out of bounds and crash.
1830+
b.__init__(iterator_which_resets())
1831+
self.assertEqual(b, bytes(100) + b'A' * 100)
1832+
18171833
def test_extend(self):
18181834
orig = b'hello'
18191835
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
@@ -1072,17 +1072,26 @@ bytearray___init___impl(PyByteArrayObject *self, PyObject *arg,
10721072
/* Interpret it as an int (__index__) */
10731073
rc = _getbytevalue(item, &value);
10741074
Py_DECREF(item);
1075-
if (!rc)
1075+
if (!rc) {
10761076
goto error;
1077+
}
1078+
1079+
/* Append the byte.
10771080
1078-
/* Append the byte */
1079-
if (Py_SIZE(self) + 1 < self->ob_alloc) {
1081+
gh-158928: Iterators are arbitrary code which could modify the
1082+
bytearray so this must re-calculate if there is enough space(). */
1083+
Py_ssize_t needed =
1084+
self->ob_start - self->ob_bytes + Py_SIZE(self) + 1;
1085+
if (needed < self->ob_alloc) {
10801086
Py_SET_SIZE(self, Py_SIZE(self) + 1);
10811087
PyByteArray_AS_STRING(self)[Py_SIZE(self)] = '\0';
10821088
}
1083-
else if (PyByteArray_Resize((PyObject *)self, Py_SIZE(self)+1) < 0)
1089+
else if (PyByteArray_Resize((PyObject *)self, Py_SIZE(self)+1) < 0) {
10841090
goto error;
1091+
}
10851092
PyByteArray_AS_STRING(self)[Py_SIZE(self)-1] = value;
1093+
assert(self->ob_start - self->ob_bytes + Py_SIZE(self) <=
1094+
self->ob_alloc);
10861095
}
10871096

10881097
/* Clean up and return success */

0 commit comments

Comments
 (0)