Skip to content

Commit 360540f

Browse files
picnixzbast0006
andauthored
[3.13] gh-91153: prevent a crash in bytearray.__setitem__(ind, ...) when ind.__index__ has side-effects (GH-132379) (#136582)
(cherry picked from commit 5e1e21d) Co-authored-by: Bast <[email protected]>
1 parent 6176101 commit 360540f

File tree

3 files changed

+43
-3
lines changed

3 files changed

+43
-3
lines changed

Lib/test/test_bytes.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1827,6 +1827,8 @@ def test_repeat_after_setslice(self):
18271827
self.assertEqual(b3, b'xcxcxc')
18281828

18291829
def test_mutating_index(self):
1830+
# bytearray slice assignment can call into python code
1831+
# that reallocates the internal buffer
18301832
# See gh-91153
18311833

18321834
class Boom:
@@ -1844,6 +1846,39 @@ def __index__(self):
18441846
with self.assertRaises(IndexError):
18451847
self._testlimitedcapi.sequence_setitem(b, 0, Boom())
18461848

1849+
def test_mutating_index_inbounds(self):
1850+
# gh-91153 continued
1851+
# Ensure buffer is not broken even if length is correct
1852+
1853+
class MutatesOnIndex:
1854+
def __init__(self):
1855+
self.ba = bytearray(0x180)
1856+
1857+
def __index__(self):
1858+
self.ba.clear()
1859+
self.new_ba = bytearray(0x180) # to catch out-of-bounds writes
1860+
self.ba.extend([0] * 0x180) # to check bounds checks
1861+
return 0
1862+
1863+
with self.subTest("skip_bounds_safety"):
1864+
instance = MutatesOnIndex()
1865+
instance.ba[instance] = ord("?")
1866+
self.assertEqual(instance.ba[0], ord("?"), "Assigned bytearray not altered")
1867+
self.assertEqual(instance.new_ba, bytearray(0x180), "Wrong object altered")
1868+
1869+
with self.subTest("skip_bounds_safety_capi"):
1870+
instance = MutatesOnIndex()
1871+
instance.ba[instance] = ord("?")
1872+
self._testlimitedcapi.sequence_setitem(instance.ba, instance, ord("?"))
1873+
self.assertEqual(instance.ba[0], ord("?"), "Assigned bytearray not altered")
1874+
self.assertEqual(instance.new_ba, bytearray(0x180), "Wrong object altered")
1875+
1876+
with self.subTest("skip_bounds_safety_slice"):
1877+
instance = MutatesOnIndex()
1878+
instance.ba[instance:1] = [ord("?")]
1879+
self.assertEqual(instance.ba[0], ord("?"), "Assigned bytearray not altered")
1880+
self.assertEqual(instance.new_ba, bytearray(0x180), "Wrong object altered")
1881+
18471882

18481883
class AssortedBytesTest(unittest.TestCase):
18491884
#
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix a crash when a :class:`bytearray` is concurrently mutated during item assignment.

Objects/bytearrayobject.c

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -591,8 +591,10 @@ static int
591591
bytearray_ass_subscript(PyByteArrayObject *self, PyObject *index, PyObject *values)
592592
{
593593
Py_ssize_t start, stop, step, slicelen, needed;
594-
char *buf, *bytes;
595-
buf = PyByteArray_AS_STRING(self);
594+
char *bytes;
595+
// Do not store a reference to the internal buffer since
596+
// index.__index__() or _getbytevalue() may alter 'self'.
597+
// See https://github.com/python/cpython/issues/91153.
596598

597599
if (_PyIndex_Check(index)) {
598600
Py_ssize_t i = PyNumber_AsSsize_t(index, PyExc_IndexError);
@@ -627,7 +629,7 @@ bytearray_ass_subscript(PyByteArrayObject *self, PyObject *index, PyObject *valu
627629
}
628630
else {
629631
assert(0 <= ival && ival < 256);
630-
buf[i] = (char)ival;
632+
PyByteArray_AS_STRING(self)[i] = (char)ival;
631633
return 0;
632634
}
633635
}
@@ -682,6 +684,7 @@ bytearray_ass_subscript(PyByteArrayObject *self, PyObject *index, PyObject *valu
682684
/* Delete slice */
683685
size_t cur;
684686
Py_ssize_t i;
687+
char *buf = PyByteArray_AS_STRING(self);
685688

686689
if (!_canresize(self))
687690
return -1;
@@ -722,6 +725,7 @@ bytearray_ass_subscript(PyByteArrayObject *self, PyObject *index, PyObject *valu
722725
/* Assign slice */
723726
Py_ssize_t i;
724727
size_t cur;
728+
char *buf = PyByteArray_AS_STRING(self);
725729

726730
if (needed != slicelen) {
727731
PyErr_Format(PyExc_ValueError,

0 commit comments

Comments
 (0)