https://github.com/python/cpython/commit/1e66eae427df71453572d6af4cb765ccaa4ab23e
commit: 1e66eae427df71453572d6af4cb765ccaa4ab23e
branch: 3.14
author: Miss Islington (bot) <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-08-18T12:59:14Z
summary:

[3.14] gh-153578: Fix out-of-bounds write in bytearray.extend() with a 
reentrant __buffer__ (GH-153579) (GH-156008)

bytearray.extend() clamped only the high bound of the append range to the
current size after acquiring the argument's buffer, so a __buffer__ that shrinks
the bytearray left the low bound past the high bound and ran a negative-size
memmove. Clamp the low bound too, matching bytearray.__iadd__.
(cherry picked from commit e675e37421357cf0319c5bca2cec533f0909d5b8)

Co-authored-by: tonghuaroot (童话) <[email protected]>

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2026-07-11-15-45-00.gh-issue-153578.Qm4Zt9.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 28d566633eefda1..7fb274ce4270024 100644
--- a/Lib/test/test_bytes.py
+++ b/Lib/test/test_bytes.py
@@ -1670,6 +1670,30 @@ def test_setslice_trap(self):
         b[8:] = b
         self.assertEqual(b, bytearray(list(range(8)) + list(range(256))))
 
+    def test_setslice_reentrant_resize(self):
+        # gh-153578: a buffer argument whose __buffer__ resizes the bytearray
+        # while the buffer is being acquired must not leave the slice bounds
+        # with lo > hi, which drove a negative-size memmove (an out-of-bounds
+        # write) in the setslice path reached through extend().
+        class Evil:
+            def __init__(self, resize):
+                self.resize = resize
+            def __buffer__(self, flags):
+                self.resize()
+                return memoryview(b'ABCDEFGH')
+        # clear() during __buffer__: extend appends to the emptied bytearray.
+        b = bytearray(b'x' * 100)
+        b.extend(Evil(b.clear))
+        self.assertEqual(b, b'ABCDEFGH')
+        # partial shrink during __buffer__.
+        b = bytearray(b'x' * 100)
+        b.extend(Evil(lambda: b.__delitem__(slice(30, None))))
+        self.assertEqual(b, b'x' * 30 + b'ABCDEFGH')
+        # grow during __buffer__: the data lands at the original end.
+        b = bytearray(b'x' * 10)
+        b.extend(Evil(lambda: b.extend(b'y' * 100)))
+        self.assertEqual(b, b'x' * 10 + b'ABCDEFGH' + b'y' * 100)
+
     def test_iconcat(self):
         b = bytearray(b"abc")
         b1 = b
diff --git 
a/Misc/NEWS.d/next/Core_and_Builtins/2026-07-11-15-45-00.gh-issue-153578.Qm4Zt9.rst
 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-11-15-45-00.gh-issue-153578.Qm4Zt9.rst
new file mode 100644
index 000000000000000..2d5d4058a04ef2c
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-11-15-45-00.gh-issue-153578.Qm4Zt9.rst
@@ -0,0 +1,3 @@
+Fix an out-of-bounds write in :meth:`bytearray.extend` when the bytearray is
+resized while the argument's :meth:`~object.__buffer__` is being acquired, for
+example by another thread. Patch by tonghuaroot.
diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c
index 1209125c70bd8ed..e8d47e50d7f7a82 100644
--- a/Objects/bytearrayobject.c
+++ b/Objects/bytearrayobject.c
@@ -668,8 +668,11 @@ bytearray_setslice(PyByteArrayObject *self, Py_ssize_t lo, 
Py_ssize_t hi,
         bytes = vbytes.buf;
     }
 
+    // gh-153578: __buffer__() may have resized self; re-clamp both bounds.
     if (lo < 0)
         lo = 0;
+    else if (lo > Py_SIZE(self))
+        lo = Py_SIZE(self);
     if (hi < lo)
         hi = lo;
     if (hi > Py_SIZE(self))

_______________________________________________
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]

Reply via email to