https://github.com/python/cpython/commit/ee21d992d6117eb7f0c6087ecb93d90cd0ed610c
commit: ee21d992d6117eb7f0c6087ecb93d90cd0ed610c
branch: main
author: tonghuaroot (童话) <[email protected]>
committer: vstinner <[email protected]>
date: 2026-07-24T10:15:01Z
summary:
gh-153570: Fix use-after-free in bytearray.take_bytes() with a reentrant
__index__ (#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).
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 e211c3d15a4ed2..1f701b3b7aa91f 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 696ddad8efaae5..e5db77ce14f31b 100644
--- a/Objects/bytearrayobject.c
+++ b/Objects/bytearrayobject.c
@@ -1555,6 +1555,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]