Skip to content

Commit 25181ce

Browse files
cmaloneyclaude
andauthored
gh-158928: Fix overflow in bytearray.__init__ from an iterator (#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. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 5346059 commit 25181ce

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
@@ -2098,6 +2098,22 @@ def g():
20982098
alloc = b.__alloc__()
20992099
self.assertGreater(alloc, len(b))
21002100

2101+
def test_init_from_iterator_with_offset(self):
2102+
# gh-158928: Inserting from an iterator in __init__ needs to take into
2103+
# account if there is a start offset.
2104+
b = bytearray()
2105+
def iterator_which_resets():
2106+
# __init__ reset ob_start. Make it an offset inside by allocating
2107+
# then doing fast prefix delete.
2108+
nonlocal b
2109+
b.resize(200)
2110+
del b[:100]
2111+
# Fill remaining already allocated space
2112+
yield from b'A' * 100
2113+
# Fill to end. Used to land out of bounds and crash.
2114+
b.__init__(iterator_which_resets())
2115+
self.assertEqual(b, bytes(100) + b'A' * 100)
2116+
21012117
def test_extend(self):
21022118
orig = b'hello'
21032119
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
@@ -1156,17 +1156,26 @@ bytearray___init___impl(PyByteArrayObject *self, PyObject *arg,
11561156
/* Interpret it as an int (__index__) */
11571157
rc = _getbytevalue(item, &value);
11581158
Py_DECREF(item);
1159-
if (!rc)
1159+
if (!rc) {
11601160
goto error;
1161+
}
1162+
1163+
/* Append the byte.
11611164
1162-
/* Append the byte */
1163-
if (Py_SIZE(self) + 1 < self->ob_alloc) {
1165+
gh-158928: Iterators are arbitrary code which could modify the
1166+
bytearray so this must re-calculate if there is enough space(). */
1167+
Py_ssize_t needed =
1168+
self->ob_start - self->ob_bytes + Py_SIZE(self) + 1;
1169+
if (needed < self->ob_alloc) {
11641170
Py_SET_SIZE(self, Py_SIZE(self) + 1);
11651171
bytearray_write_trailing_null_byte(self);
11661172
}
1167-
else if (PyByteArray_Resize((PyObject *)self, Py_SIZE(self)+1) < 0)
1173+
else if (PyByteArray_Resize((PyObject *)self, Py_SIZE(self)+1) < 0) {
11681174
goto error;
1175+
}
11691176
PyByteArray_AS_STRING(self)[Py_SIZE(self)-1] = value;
1177+
assert(self->ob_start - self->ob_bytes + Py_SIZE(self) <=
1178+
self->ob_alloc);
11701179
}
11711180

11721181
/* Clean up and return success */

0 commit comments

Comments
 (0)