https://github.com/python/cpython/commit/360540fd47d282721214f45ba05b6b9a57d972a7
commit: 360540fd47d282721214f45ba05b6b9a57d972a7
branch: 3.13
author: Bénédikt Tran <[email protected]>
committer: picnixz <[email protected]>
date: 2025-07-12T16:34:45+02:00
summary:

[3.13] gh-91153: prevent a crash in `bytearray.__setitem__(ind, ...)` when 
`ind.__index__` has side-effects (GH-132379) (#136582)

(cherry picked from commit 5e1e21dee35b8e9066692d08033bbbdb562e2c28)

Co-authored-by: Bast <[email protected]>

files:
A Misc/NEWS.d/next/Core and 
Builtins/2025-05-17-20-56-05.gh-issue-91153.afgtG2.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 f8edfe0a17a45d..b8f2a0217685b2 100644
--- a/Lib/test/test_bytes.py
+++ b/Lib/test/test_bytes.py
@@ -1827,6 +1827,8 @@ def test_repeat_after_setslice(self):
         self.assertEqual(b3, b'xcxcxc')
 
     def test_mutating_index(self):
+        # bytearray slice assignment can call into python code
+        # that reallocates the internal buffer
         # See gh-91153
 
         class Boom:
@@ -1844,6 +1846,39 @@ def __index__(self):
             with self.assertRaises(IndexError):
                 self._testlimitedcapi.sequence_setitem(b, 0, Boom())
 
+    def test_mutating_index_inbounds(self):
+        # gh-91153 continued
+        # Ensure buffer is not broken even if length is correct
+
+        class MutatesOnIndex:
+            def __init__(self):
+                self.ba = bytearray(0x180)
+
+            def __index__(self):
+                self.ba.clear()
+                self.new_ba = bytearray(0x180)  # to catch out-of-bounds writes
+                self.ba.extend([0] * 0x180)     # to check bounds checks
+                return 0
+
+        with self.subTest("skip_bounds_safety"):
+            instance = MutatesOnIndex()
+            instance.ba[instance] = ord("?")
+            self.assertEqual(instance.ba[0], ord("?"), "Assigned bytearray not 
altered")
+            self.assertEqual(instance.new_ba, bytearray(0x180), "Wrong object 
altered")
+
+        with self.subTest("skip_bounds_safety_capi"):
+            instance = MutatesOnIndex()
+            instance.ba[instance] = ord("?")
+            self._testlimitedcapi.sequence_setitem(instance.ba, instance, 
ord("?"))
+            self.assertEqual(instance.ba[0], ord("?"), "Assigned bytearray not 
altered")
+            self.assertEqual(instance.new_ba, bytearray(0x180), "Wrong object 
altered")
+
+        with self.subTest("skip_bounds_safety_slice"):
+            instance = MutatesOnIndex()
+            instance.ba[instance:1] = [ord("?")]
+            self.assertEqual(instance.ba[0], ord("?"), "Assigned bytearray not 
altered")
+            self.assertEqual(instance.new_ba, bytearray(0x180), "Wrong object 
altered")
+
 
 class AssortedBytesTest(unittest.TestCase):
     #
diff --git a/Misc/NEWS.d/next/Core and 
Builtins/2025-05-17-20-56-05.gh-issue-91153.afgtG2.rst b/Misc/NEWS.d/next/Core 
and Builtins/2025-05-17-20-56-05.gh-issue-91153.afgtG2.rst
new file mode 100644
index 00000000000000..dc2f1e22ba5b05
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and 
Builtins/2025-05-17-20-56-05.gh-issue-91153.afgtG2.rst  
@@ -0,0 +1 @@
+Fix a crash when a :class:`bytearray` is concurrently mutated during item 
assignment.
diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c
index a67f41bbfc7039..5382b1bdfa59aa 100644
--- a/Objects/bytearrayobject.c
+++ b/Objects/bytearrayobject.c
@@ -591,8 +591,10 @@ static int
 bytearray_ass_subscript(PyByteArrayObject *self, PyObject *index, PyObject 
*values)
 {
     Py_ssize_t start, stop, step, slicelen, needed;
-    char *buf, *bytes;
-    buf = PyByteArray_AS_STRING(self);
+    char *bytes;
+    // Do not store a reference to the internal buffer since
+    // index.__index__() or _getbytevalue() may alter 'self'.
+    // See https://github.com/python/cpython/issues/91153.
 
     if (_PyIndex_Check(index)) {
         Py_ssize_t i = PyNumber_AsSsize_t(index, PyExc_IndexError);
@@ -627,7 +629,7 @@ bytearray_ass_subscript(PyByteArrayObject *self, PyObject 
*index, PyObject *valu
         }
         else {
             assert(0 <= ival && ival < 256);
-            buf[i] = (char)ival;
+            PyByteArray_AS_STRING(self)[i] = (char)ival;
             return 0;
         }
     }
@@ -682,6 +684,7 @@ bytearray_ass_subscript(PyByteArrayObject *self, PyObject 
*index, PyObject *valu
             /* Delete slice */
             size_t cur;
             Py_ssize_t i;
+            char *buf = PyByteArray_AS_STRING(self);
 
             if (!_canresize(self))
                 return -1;
@@ -722,6 +725,7 @@ bytearray_ass_subscript(PyByteArrayObject *self, PyObject 
*index, PyObject *valu
             /* Assign slice */
             Py_ssize_t i;
             size_t cur;
+            char *buf = PyByteArray_AS_STRING(self);
 
             if (needed != slicelen) {
                 PyErr_Format(PyExc_ValueError,

_______________________________________________
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