https://github.com/python/cpython/commit/848f0bafc689e5d76f9c20f8c96a2454fd32fd79 commit: 848f0bafc689e5d76f9c20f8c96a2454fd32fd79 branch: 3.15 author: Miss Islington (bot) <[email protected]> committer: vstinner <[email protected]> date: 2026-07-25T00:06:49+02:00 summary:
[3.15] gh-153570: Fix use-after-free in bytearray.take_bytes() with a reentrant __index__ (GH-153572) (#154648) gh-153570: Fix use-after-free in bytearray.take_bytes() with a reentrant __index__ (GH-153572) bytearray.take_bytes() cached the size before the argument's __index__ call and used it for the bounds check and the buffer reads, so an __index__ that resizes the bytearray left it reading freed memory. Re-read the size after __index__, matching bytearray.__setitem__ (GH-91153). (cherry picked from commit ee21d992d6117eb7f0c6087ecb93d90cd0ed610c) Co-authored-by: tonghuaroot (童话) <[email protected]> files: A Misc/NEWS.d/next/Core_and_Builtins/2026-07-11-15-30-00.gh-issue-153570.Kb7Xd2.rst M Lib/test/test_bytes.py M Objects/bytearrayobject.c diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index e3e703fef1eb5d..720b38cb508cbe 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -1637,6 +1637,35 @@ def test_take_bytes_optimization(self): bytes_header_size = sys.getsizeof(b'') self.assertEqual(ba.__alloc__(), 499 + bytes_header_size) + def test_take_bytes_reentrant_resize(self): + # gh-153570: n.__index__() can resize the bytearray, so take_bytes() + # must re-read the size afterwards. It cached the size before the + # call and used it for the bounds check and the buffer reads, so a + # reentrant clear() returned freed memory (a use-after-free read). + def take(target, resize, n): + class Evil: + def __index__(self): + resize(target) + return n + return target.take_bytes(Evil()) + + # clear() during __index__: nothing is left to take. + ba = bytearray(b'abcdefgh') + with self.assertRaises(IndexError): + take(ba, lambda b: b.clear(), 8) + self.assertEqual(ba, b'') + + # shrink during __index__: n past the new size is out of range. + ba = bytearray(b'abcdefgh') + with self.assertRaises(IndexError): + take(ba, lambda b: b.__delitem__(slice(4, None)), 8) + self.assertEqual(ba, b'abcd') + + # grow during __index__: the take runs against the new, larger size. + ba = bytearray(b'abcd') + self.assertEqual(take(ba, lambda b: b.extend(b'efgh'), 8), b'abcdefgh') + self.assertEqual(ba, b'') + def test_setitem(self): def setitem_as_mapping(b, i, val): b[i] = val diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-07-11-15-30-00.gh-issue-153570.Kb7Xd2.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-11-15-30-00.gh-issue-153570.Kb7Xd2.rst new file mode 100644 index 00000000000000..655fd614d7f551 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-11-15-30-00.gh-issue-153570.Kb7Xd2.rst @@ -0,0 +1,2 @@ +Fix a use-after-free in :meth:`bytearray.take_bytes` when the argument's +:meth:`~object.__index__` method resizes the bytearray. Patch by tonghuaroot. diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 70e9e87210b60b..b16fb8751f5f73 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -1576,6 +1576,8 @@ bytearray_take_bytes_impl(PyByteArrayObject *self, PyObject *n) if (to_take == -1 && PyErr_Occurred()) { return NULL; } + // n.__index__() may have resized self; use the current size. + size = Py_SIZE(self); if (to_take < 0) { to_take += size; } _______________________________________________ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3//lists/python-checkins.python.org Member address: [email protected]
