Skip to content

Commit 39f16a9

Browse files
authored
gh-142555: Fix null pointer dereference in array.__setitem__ via re-entrant __index__ (GH-142713)
1 parent afc2aeb commit 39f16a9

File tree

3 files changed

+107
-1
lines changed

3 files changed

+107
-1
lines changed

Lib/test/test_array.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from test.support import import_helper
99
from test.support import os_helper
1010
from test.support import _2G
11+
from test.support import subTests
1112
import weakref
1213
import pickle
1314
import operator
@@ -1697,6 +1698,45 @@ def test_gh_128961(self):
16971698
it.__setstate__(0)
16981699
self.assertRaises(StopIteration, next, it)
16991700

1701+
# Tests for NULL pointer dereference in array.__setitem__
1702+
# when the index conversion mutates the array.
1703+
# See: https://github.com/python/cpython/issues/142555.
1704+
1705+
@subTests("dtype", ["b", "B", "h", "H", "i", "l", "q", "I", "L", "Q"])
1706+
def test_setitem_use_after_clear_with_int_data(self, dtype):
1707+
victim = array.array(dtype, list(range(64)))
1708+
1709+
class Index:
1710+
def __index__(self):
1711+
victim.clear()
1712+
return 0
1713+
1714+
self.assertRaises(IndexError, victim.__setitem__, 1, Index())
1715+
self.assertEqual(len(victim), 0)
1716+
1717+
def test_setitem_use_after_shrink_with_int_data(self):
1718+
victim = array.array('b', [1, 2, 3])
1719+
1720+
class Index:
1721+
def __index__(self):
1722+
victim.pop()
1723+
victim.pop()
1724+
return 0
1725+
1726+
self.assertRaises(IndexError, victim.__setitem__, 1, Index())
1727+
1728+
@subTests("dtype", ["f", "d"])
1729+
def test_setitem_use_after_clear_with_float_data(self, dtype):
1730+
victim = array.array(dtype, [1.0, 2.0, 3.0])
1731+
1732+
class Float:
1733+
def __float__(self):
1734+
victim.clear()
1735+
return 0.0
1736+
1737+
self.assertRaises(IndexError, victim.__setitem__, 1, Float())
1738+
self.assertEqual(len(victim), 0)
1739+
17001740

17011741
if __name__ == "__main__":
17021742
unittest.main()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:mod:`array`: fix a crash in ``a[i] = v`` when converting *i* to
2+
an index via :meth:`i.__index__ <object.__index__>` or :meth:`i.__float__
3+
<object.__float__>` mutates the array.

Modules/arraymodule.c

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,33 @@ Note that the basic Get and Set functions do NOT check that the index is
205205
in bounds; that's the responsibility of the caller.
206206
****************************************************************************/
207207

208+
/* Macro to check array buffer validity and bounds after calling
209+
user-defined methods (like __index__ or __float__) that might modify
210+
the array during the call.
211+
*/
212+
#define CHECK_ARRAY_BOUNDS(OP, IDX) \
213+
do { \
214+
if ((IDX) >= 0 && ((OP)->ob_item == NULL || \
215+
(IDX) >= Py_SIZE((OP)))) { \
216+
PyErr_SetString(PyExc_IndexError, \
217+
"array assignment index out of range"); \
218+
return -1; \
219+
} \
220+
} while (0)
221+
222+
#define CHECK_ARRAY_BOUNDS_WITH_CLEANUP(OP, IDX, VAL, CLEANUP) \
223+
do { \
224+
if ((IDX) >= 0 && ((OP)->ob_item == NULL || \
225+
(IDX) >= Py_SIZE((OP)))) { \
226+
PyErr_SetString(PyExc_IndexError, \
227+
"array assignment index out of range"); \
228+
if (CLEANUP) { \
229+
Py_DECREF(VAL); \
230+
} \
231+
return -1; \
232+
} \
233+
} while (0)
234+
208235
static PyObject *
209236
b_getitem(arrayobject *ap, Py_ssize_t i)
210237
{
@@ -221,7 +248,10 @@ b_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v)
221248
the overflow checking */
222249
if (!PyArg_Parse(v, "h;array item must be integer", &x))
223250
return -1;
224-
else if (x < -128) {
251+
252+
CHECK_ARRAY_BOUNDS(ap, i);
253+
254+
if (x < -128) {
225255
PyErr_SetString(PyExc_OverflowError,
226256
"signed char is less than minimum");
227257
return -1;
@@ -250,6 +280,9 @@ BB_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v)
250280
/* 'B' == unsigned char, maps to PyArg_Parse's 'b' formatter */
251281
if (!PyArg_Parse(v, "b;array item must be integer", &x))
252282
return -1;
283+
284+
CHECK_ARRAY_BOUNDS(ap, i);
285+
253286
if (i >= 0)
254287
((unsigned char *)ap->ob_item)[i] = x;
255288
return 0;
@@ -342,6 +375,9 @@ h_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v)
342375
/* 'h' == signed short, maps to PyArg_Parse's 'h' formatter */
343376
if (!PyArg_Parse(v, "h;array item must be integer", &x))
344377
return -1;
378+
379+
CHECK_ARRAY_BOUNDS(ap, i);
380+
345381
if (i >= 0)
346382
((short *)ap->ob_item)[i] = x;
347383
return 0;
@@ -371,6 +407,9 @@ HH_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v)
371407
"unsigned short is greater than maximum");
372408
return -1;
373409
}
410+
411+
CHECK_ARRAY_BOUNDS(ap, i);
412+
374413
if (i >= 0)
375414
((short *)ap->ob_item)[i] = (short)x;
376415
return 0;
@@ -389,6 +428,9 @@ i_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v)
389428
/* 'i' == signed int, maps to PyArg_Parse's 'i' formatter */
390429
if (!PyArg_Parse(v, "i;array item must be integer", &x))
391430
return -1;
431+
432+
CHECK_ARRAY_BOUNDS(ap, i);
433+
392434
if (i >= 0)
393435
((int *)ap->ob_item)[i] = x;
394436
return 0;
@@ -432,6 +474,9 @@ II_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v)
432474
}
433475
return -1;
434476
}
477+
478+
CHECK_ARRAY_BOUNDS_WITH_CLEANUP(ap, i, v, do_decref);
479+
435480
if (i >= 0)
436481
((unsigned int *)ap->ob_item)[i] = (unsigned int)x;
437482

@@ -453,6 +498,9 @@ l_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v)
453498
long x;
454499
if (!PyArg_Parse(v, "l;array item must be integer", &x))
455500
return -1;
501+
502+
CHECK_ARRAY_BOUNDS(ap, i);
503+
456504
if (i >= 0)
457505
((long *)ap->ob_item)[i] = x;
458506
return 0;
@@ -487,6 +535,9 @@ LL_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v)
487535
}
488536
return -1;
489537
}
538+
539+
CHECK_ARRAY_BOUNDS_WITH_CLEANUP(ap, i, v, do_decref);
540+
490541
if (i >= 0)
491542
((unsigned long *)ap->ob_item)[i] = x;
492543

@@ -508,6 +559,9 @@ q_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v)
508559
long long x;
509560
if (!PyArg_Parse(v, "L;array item must be integer", &x))
510561
return -1;
562+
563+
CHECK_ARRAY_BOUNDS(ap, i);
564+
511565
if (i >= 0)
512566
((long long *)ap->ob_item)[i] = x;
513567
return 0;
@@ -543,6 +597,9 @@ QQ_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v)
543597
}
544598
return -1;
545599
}
600+
601+
CHECK_ARRAY_BOUNDS_WITH_CLEANUP(ap, i, v, do_decref);
602+
546603
if (i >= 0)
547604
((unsigned long long *)ap->ob_item)[i] = x;
548605

@@ -564,6 +621,9 @@ f_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v)
564621
float x;
565622
if (!PyArg_Parse(v, "f;array item must be float", &x))
566623
return -1;
624+
625+
CHECK_ARRAY_BOUNDS(ap, i);
626+
567627
if (i >= 0)
568628
((float *)ap->ob_item)[i] = x;
569629
return 0;
@@ -581,6 +641,9 @@ d_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v)
581641
double x;
582642
if (!PyArg_Parse(v, "d;array item must be float", &x))
583643
return -1;
644+
645+
CHECK_ARRAY_BOUNDS(ap, i);
646+
584647
if (i >= 0)
585648
((double *)ap->ob_item)[i] = x;
586649
return 0;

0 commit comments

Comments
 (0)