Skip to content

Commit 13510c7

Browse files
cmaloneyclaude
andcommitted
gh-158928: Fix overflow in bytearray.__init__ from an iterator
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. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 978a8be commit 13510c7

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
@@ -2085,6 +2085,22 @@ def g():
20852085
alloc = b.__alloc__()
20862086
self.assertGreater(alloc, len(b))
20872087

2088+
def test_init_from_iterator_with_offset(self):
2089+
# gh-158928: Inserting form an iterator in __init__ needs to take into
2090+
# account if there is a start offset.
2091+
b = bytearray()
2092+
def iterator_which_resets():
2093+
# __init__ reset ob_start. Make it an offset inside by allocating
2094+
# then doing fast prefix delete.
2095+
nonlocal b
2096+
b.resize(200)
2097+
del b[:100]
2098+
# Fill remaining already allocated space
2099+
yield from b'A' * 100
2100+
# Fill to end. Used to land out of bounds and crash.
2101+
b.__init__(iterator_which_resets())
2102+
self.assertEqual(b, bytes(100) + b'A' * 100)
2103+
20882104
def test_extend(self):
20892105
orig = b'hello'
20902106
a = bytearray(orig)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix buffer overflow in :meth:`bytearray.__init__` when initializing 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+
Iterators are arbitrary code which could modify the bytearray so
1168+
this must always re-calculate if there is enough space(gh-158928). */
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)