From 97ac28dbdc6a22f14b4e1ed0b7cab9ff520e92b3 Mon Sep 17 00:00:00 2001 From: Cody Maloney Date: Thu, 5 Nov 2026 13:29:02 -0800 Subject: [PATCH] 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 25181cee14359a0fbdbe54c83ab3ac44147d22ed) Co-authored-by: Cody Maloney Co-Authored-By: Claude Opus 4.8 --- Lib/test/test_bytes.py | 16 ++++++++++++++++ ...26-10-06-12-30-00.gh-issue-158928.Zd9Ybv.rst | 2 ++ Objects/bytearrayobject.c | 17 +++++++++++++---- 3 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Security/2026-10-06-12-30-00.gh-issue-158928.Zd9Ybv.rst diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index d180de1884a2f6..110690b573829b 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -1814,6 +1814,22 @@ def g(): alloc = b.__alloc__() self.assertGreater(alloc, len(b)) + def test_init_from_iterator_with_offset(self): + # gh-158928: Inserting from an iterator in __init__ needs to take into + # account if there is a start offset. + b = bytearray() + def iterator_which_resets(): + # __init__ reset ob_start. Make it an offset inside by allocating + # then doing fast prefix delete. + nonlocal b + b.resize(200) + del b[:100] + # Fill remaining already allocated space + yield from b'A' * 100 + # Fill to end. Used to land out of bounds and crash. + b.__init__(iterator_which_resets()) + self.assertEqual(b, bytes(100) + b'A' * 100) + def test_extend(self): orig = b'hello' a = bytearray(orig) diff --git a/Misc/NEWS.d/next/Security/2026-10-06-12-30-00.gh-issue-158928.Zd9Ybv.rst b/Misc/NEWS.d/next/Security/2026-10-06-12-30-00.gh-issue-158928.Zd9Ybv.rst new file mode 100644 index 00000000000000..2b9597c5ed94f3 --- /dev/null +++ b/Misc/NEWS.d/next/Security/2026-10-06-12-30-00.gh-issue-158928.Zd9Ybv.rst @@ -0,0 +1,2 @@ +Fix a buffer overflow when initializing a :class:`bytearray` from an +iterator. diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 5486c0353d7173..cc519962052e7e 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -1072,17 +1072,26 @@ bytearray___init___impl(PyByteArrayObject *self, PyObject *arg, /* Interpret it as an int (__index__) */ rc = _getbytevalue(item, &value); Py_DECREF(item); - if (!rc) + if (!rc) { goto error; + } + + /* Append the byte. - /* Append the byte */ - if (Py_SIZE(self) + 1 < self->ob_alloc) { + gh-158928: Iterators are arbitrary code which could modify the + bytearray so this must re-calculate if there is enough space(). */ + Py_ssize_t needed = + self->ob_start - self->ob_bytes + Py_SIZE(self) + 1; + if (needed < self->ob_alloc) { Py_SET_SIZE(self, Py_SIZE(self) + 1); PyByteArray_AS_STRING(self)[Py_SIZE(self)] = '\0'; } - else if (PyByteArray_Resize((PyObject *)self, Py_SIZE(self)+1) < 0) + else if (PyByteArray_Resize((PyObject *)self, Py_SIZE(self)+1) < 0) { goto error; + } PyByteArray_AS_STRING(self)[Py_SIZE(self)-1] = value; + assert(self->ob_start - self->ob_bytes + Py_SIZE(self) <= + self->ob_alloc); } /* Clean up and return success */