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

serhiy-storchaka <[email protected]>
Newsgroups gmane.comp.python.cvs
Message-ID <[email protected]>
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]
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.