Skip to content

Commit 9487e8d

Browse files
GH-91153: Handle mutating __index__ methods in bytearray item assignment (GH-94891)
(cherry picked from commit f365895) Co-authored-by: Brandt Bucher <brandtbucher@microsoft.com>
1 parent d2be442 commit 9487e8d

File tree

4 files changed

+60
-11
lines changed

4 files changed

+60
-11
lines changed

Lib/test/test_bytes.py

+17
Original file line numberDiff line numberDiff line change
@@ -1710,6 +1710,23 @@ def test_repeat_after_setslice(self):
17101710
self.assertEqual(b1, b)
17111711
self.assertEqual(b3, b'xcxcxc')
17121712

1713+
def test_mutating_index(self):
1714+
class Boom:
1715+
def __index__(self):
1716+
b.clear()
1717+
return 0
1718+
1719+
with self.subTest("tp_as_mapping"):
1720+
b = bytearray(b'Now you see me...')
1721+
with self.assertRaises(IndexError):
1722+
b[0] = Boom()
1723+
1724+
with self.subTest("tp_as_sequence"):
1725+
_testcapi = import_helper.import_module('_testcapi')
1726+
b = bytearray(b'Now you see me...')
1727+
with self.assertRaises(IndexError):
1728+
_testcapi.sequence_setitem(b, 0, Boom())
1729+
17131730

17141731
class AssortedBytesTest(unittest.TestCase):
17151732
#
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix an issue where a :class:`bytearray` item assignment could crash if it's
2+
resized by the new value's :meth:`__index__` method.

Modules/_testcapimodule.c

+16
Original file line numberDiff line numberDiff line change
@@ -5489,6 +5489,21 @@ sequence_getitem(PyObject *self, PyObject *args)
54895489
}
54905490

54915491

5492+
static PyObject *
5493+
sequence_setitem(PyObject *self, PyObject *args)
5494+
{
5495+
Py_ssize_t i;
5496+
PyObject *seq, *val;
5497+
if (!PyArg_ParseTuple(args, "OnO", &seq, &i, &val)) {
5498+
return NULL;
5499+
}
5500+
if (PySequence_SetItem(seq, i, val)) {
5501+
return NULL;
5502+
}
5503+
Py_RETURN_NONE;
5504+
}
5505+
5506+
54925507
/* Functions for testing C calling conventions (METH_*) are named meth_*,
54935508
* e.g. "meth_varargs" for METH_VARARGS.
54945509
*
@@ -6272,6 +6287,7 @@ static PyMethodDef TestMethods[] = {
62726287
#endif
62736288
{"write_unraisable_exc", test_write_unraisable_exc, METH_VARARGS},
62746289
{"sequence_getitem", sequence_getitem, METH_VARARGS},
6290+
{"sequence_setitem", sequence_setitem, METH_VARARGS},
62756291
{"meth_varargs", meth_varargs, METH_VARARGS},
62766292
{"meth_varargs_keywords", _PyCFunction_CAST(meth_varargs_keywords), METH_VARARGS|METH_KEYWORDS},
62776293
{"meth_o", meth_o, METH_O},

Objects/bytearrayobject.c

+25-11
Original file line numberDiff line numberDiff line change
@@ -563,22 +563,28 @@ bytearray_setslice(PyByteArrayObject *self, Py_ssize_t lo, Py_ssize_t hi,
563563
static int
564564
bytearray_setitem(PyByteArrayObject *self, Py_ssize_t i, PyObject *value)
565565
{
566-
int ival;
566+
int ival = -1;
567567

568-
if (i < 0)
568+
// GH-91153: We need to do this *before* the size check, in case value has a
569+
// nasty __index__ method that changes the size of the bytearray:
570+
if (value && !_getbytevalue(value, &ival)) {
571+
return -1;
572+
}
573+
574+
if (i < 0) {
569575
i += Py_SIZE(self);
576+
}
570577

571578
if (i < 0 || i >= Py_SIZE(self)) {
572579
PyErr_SetString(PyExc_IndexError, "bytearray index out of range");
573580
return -1;
574581
}
575582

576-
if (value == NULL)
583+
if (value == NULL) {
577584
return bytearray_setslice(self, i, i+1, NULL);
585+
}
578586

579-
if (!_getbytevalue(value, &ival))
580-
return -1;
581-
587+
assert(0 <= ival && ival < 256);
582588
PyByteArray_AS_STRING(self)[i] = ival;
583589
return 0;
584590
}
@@ -593,11 +599,21 @@ bytearray_ass_subscript(PyByteArrayObject *self, PyObject *index, PyObject *valu
593599
if (_PyIndex_Check(index)) {
594600
Py_ssize_t i = PyNumber_AsSsize_t(index, PyExc_IndexError);
595601

596-
if (i == -1 && PyErr_Occurred())
602+
if (i == -1 && PyErr_Occurred()) {
597603
return -1;
604+
}
598605

599-
if (i < 0)
606+
int ival = -1;
607+
608+
// GH-91153: We need to do this *before* the size check, in case values
609+
// has a nasty __index__ method that changes the size of the bytearray:
610+
if (values && !_getbytevalue(values, &ival)) {
611+
return -1;
612+
}
613+
614+
if (i < 0) {
600615
i += PyByteArray_GET_SIZE(self);
616+
}
601617

602618
if (i < 0 || i >= Py_SIZE(self)) {
603619
PyErr_SetString(PyExc_IndexError, "bytearray index out of range");
@@ -612,9 +628,7 @@ bytearray_ass_subscript(PyByteArrayObject *self, PyObject *index, PyObject *valu
612628
slicelen = 1;
613629
}
614630
else {
615-
int ival;
616-
if (!_getbytevalue(values, &ival))
617-
return -1;
631+
assert(0 <= ival && ival < 256);
618632
buf[i] = (char)ival;
619633
return 0;
620634
}

0 commit comments

Comments
 (0)