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

(cherry picked from commit 5e1e21dee3)

Co-authored-by: Bast <52266665+bast0006@users.noreply.github.com>
This commit is contained in:
Bénédikt Tran 2025-07-12 16:34:45 +02:00 committed by GitHub
parent 6176101b4a
commit 360540fd47
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 43 additions and 3 deletions

View file

@ -1827,6 +1827,8 @@ def test_repeat_after_setslice(self):
self.assertEqual(b3, b'xcxcxc') self.assertEqual(b3, b'xcxcxc')
def test_mutating_index(self): def test_mutating_index(self):
# bytearray slice assignment can call into python code
# that reallocates the internal buffer
# See gh-91153 # See gh-91153
class Boom: class Boom:
@ -1844,6 +1846,39 @@ def __index__(self):
with self.assertRaises(IndexError): with self.assertRaises(IndexError):
self._testlimitedcapi.sequence_setitem(b, 0, Boom()) 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): class AssortedBytesTest(unittest.TestCase):
# #

View file

@ -0,0 +1 @@
Fix a crash when a :class:`bytearray` is concurrently mutated during item assignment.

View file

@ -591,8 +591,10 @@ static int
bytearray_ass_subscript(PyByteArrayObject *self, PyObject *index, PyObject *values) bytearray_ass_subscript(PyByteArrayObject *self, PyObject *index, PyObject *values)
{ {
Py_ssize_t start, stop, step, slicelen, needed; Py_ssize_t start, stop, step, slicelen, needed;
char *buf, *bytes; char *bytes;
buf = PyByteArray_AS_STRING(self); // 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)) { if (_PyIndex_Check(index)) {
Py_ssize_t i = PyNumber_AsSsize_t(index, PyExc_IndexError); Py_ssize_t i = PyNumber_AsSsize_t(index, PyExc_IndexError);
@ -627,7 +629,7 @@ bytearray_ass_subscript(PyByteArrayObject *self, PyObject *index, PyObject *valu
} }
else { else {
assert(0 <= ival && ival < 256); assert(0 <= ival && ival < 256);
buf[i] = (char)ival; PyByteArray_AS_STRING(self)[i] = (char)ival;
return 0; return 0;
} }
} }
@ -682,6 +684,7 @@ bytearray_ass_subscript(PyByteArrayObject *self, PyObject *index, PyObject *valu
/* Delete slice */ /* Delete slice */
size_t cur; size_t cur;
Py_ssize_t i; Py_ssize_t i;
char *buf = PyByteArray_AS_STRING(self);
if (!_canresize(self)) if (!_canresize(self))
return -1; return -1;
@ -722,6 +725,7 @@ bytearray_ass_subscript(PyByteArrayObject *self, PyObject *index, PyObject *valu
/* Assign slice */ /* Assign slice */
Py_ssize_t i; Py_ssize_t i;
size_t cur; size_t cur;
char *buf = PyByteArray_AS_STRING(self);
if (needed != slicelen) { if (needed != slicelen) {
PyErr_Format(PyExc_ValueError, PyErr_Format(PyExc_ValueError,