Skip to content

Commit f4aae22

Browse files
aiskmiss-islington
authored andcommitted
gh-142555: Fix null pointer dereference in array.__setitem__ via re-entrant __index__ (GH-142713)
(cherry picked from commit 39f16a9) Co-authored-by: AN Long <aisk@users.noreply.github.com>
1 parent 51fccc6 commit f4aae22

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
@@ -1680,6 +1681,45 @@ def test_gh_128961(self):
16801681
it.__setstate__(0)
16811682
self.assertRaises(StopIteration, next, it)
16821683

1684+
# Tests for NULL pointer dereference in array.__setitem__
1685+
# when the index conversion mutates the array.
1686+
# See: https://github.com/python/cpython/issues/142555.
1687+
1688+
@subTests("dtype", ["b", "B", "h", "H", "i", "l", "q", "I", "L", "Q"])
1689+
def test_setitem_use_after_clear_with_int_data(self, dtype):
1690+
victim = array.array(dtype, list(range(64)))
1691+
1692+
class Index:
1693+
def __index__(self):
1694+
victim.clear()
1695+
return 0
1696+
1697+
self.assertRaises(IndexError, victim.__setitem__, 1, Index())
1698+
self.assertEqual(len(victim), 0)
1699+
1700+
def test_setitem_use_after_shrink_with_int_data(self):
1701+
victim = array.array('b', [1, 2, 3])
1702+
1703+
class Index:
1704+
def __index__(self):
1705+
victim.pop()
1706+
victim.pop()
1707+
return 0
1708+
1709+
self.assertRaises(IndexError, victim.__setitem__, 1, Index())
1710+
1711+
@subTests("dtype", ["f", "d"])
1712+
def test_setitem_use_after_clear_with_float_data(self, dtype):
1713+
victim = array.array(dtype, [1.0, 2.0, 3.0])
1714+
1715+
class Float:
1716+
def __float__(self):
1717+
victim.clear()
1718+
return 0.0
1719+
1720+
self.assertRaises(IndexError, victim.__setitem__, 1, Float())
1721+
self.assertEqual(len(victim), 0)
1722+
16831723

16841724
if __name__ == "__main__":
16851725
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
@@ -202,6 +202,33 @@ Note that the basic Get and Set functions do NOT check that the index is
202202
in bounds; that's the responsibility of the caller.
203203
****************************************************************************/
204204

205+
/* Macro to check array buffer validity and bounds after calling
206+
user-defined methods (like __index__ or __float__) that might modify
207+
the array during the call.
208+
*/
209+
#define CHECK_ARRAY_BOUNDS(OP, IDX) \
210+
do { \
211+
if ((IDX) >= 0 && ((OP)->ob_item == NULL || \
212+
(IDX) >= Py_SIZE((OP)))) { \
213+
PyErr_SetString(PyExc_IndexError, \
214+
"array assignment index out of range"); \
215+
return -1; \
216+
} \
217+
} while (0)
218+
219+
#define CHECK_ARRAY_BOUNDS_WITH_CLEANUP(OP, IDX, VAL, CLEANUP) \
220+
do { \
221+
if ((IDX) >= 0 && ((OP)->ob_item == NULL || \
222+
(IDX) >= Py_SIZE((OP)))) { \
223+
PyErr_SetString(PyExc_IndexError, \
224+
"array assignment index out of range"); \
225+
if (CLEANUP) { \
226+
Py_DECREF(VAL); \
227+
} \
228+
return -1; \
229+
} \
230+
} while (0)
231+
205232
static PyObject *
206233
b_getitem(arrayobject *ap, Py_ssize_t i)
207234
{
@@ -218,7 +245,10 @@ b_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v)
218245
the overflow checking */
219246
if (!PyArg_Parse(v, "h;array item must be integer", &x))
220247
return -1;
221-
else if (x < -128) {
248+
249+
CHECK_ARRAY_BOUNDS(ap, i);
250+
251+
if (x < -128) {
222252
PyErr_SetString(PyExc_OverflowError,
223253
"signed char is less than minimum");
224254
return -1;
@@ -247,6 +277,9 @@ BB_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v)
247277
/* 'B' == unsigned char, maps to PyArg_Parse's 'b' formatter */
248278
if (!PyArg_Parse(v, "b;array item must be integer", &x))
249279
return -1;
280+
281+
CHECK_ARRAY_BOUNDS(ap, i);
282+
250283
if (i >= 0)
251284
((unsigned char *)ap->ob_item)[i] = x;
252285
return 0;
@@ -323,6 +356,9 @@ h_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v)
323356
/* 'h' == signed short, maps to PyArg_Parse's 'h' formatter */
324357
if (!PyArg_Parse(v, "h;array item must be integer", &x))
325358
return -1;
359+
360+
CHECK_ARRAY_BOUNDS(ap, i);
361+
326362
if (i >= 0)
327363
((short *)ap->ob_item)[i] = x;
328364
return 0;
@@ -352,6 +388,9 @@ HH_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v)
352388
"unsigned short is greater than maximum");
353389
return -1;
354390
}
391+
392+
CHECK_ARRAY_BOUNDS(ap, i);
393+
355394
if (i >= 0)
356395
((short *)ap->ob_item)[i] = (short)x;
357396
return 0;
@@ -370,6 +409,9 @@ i_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v)
370409
/* 'i' == signed int, maps to PyArg_Parse's 'i' formatter */
371410
if (!PyArg_Parse(v, "i;array item must be integer", &x))
372411
return -1;
412+
413+
CHECK_ARRAY_BOUNDS(ap, i);
414+
373415
if (i >= 0)
374416
((int *)ap->ob_item)[i] = x;
375417
return 0;
@@ -410,6 +452,9 @@ II_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v)
410452
}
411453
return -1;
412454
}
455+
456+
CHECK_ARRAY_BOUNDS_WITH_CLEANUP(ap, i, v, do_decref);
457+
413458
if (i >= 0)
414459
((unsigned int *)ap->ob_item)[i] = (unsigned int)x;
415460

@@ -431,6 +476,9 @@ l_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v)
431476
long x;
432477
if (!PyArg_Parse(v, "l;array item must be integer", &x))
433478
return -1;
479+
480+
CHECK_ARRAY_BOUNDS(ap, i);
481+
434482
if (i >= 0)
435483
((long *)ap->ob_item)[i] = x;
436484
return 0;
@@ -462,6 +510,9 @@ LL_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v)
462510
}
463511
return -1;
464512
}
513+
514+
CHECK_ARRAY_BOUNDS_WITH_CLEANUP(ap, i, v, do_decref);
515+
465516
if (i >= 0)
466517
((unsigned long *)ap->ob_item)[i] = x;
467518

@@ -483,6 +534,9 @@ q_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v)
483534
long long x;
484535
if (!PyArg_Parse(v, "L;array item must be integer", &x))
485536
return -1;
537+
538+
CHECK_ARRAY_BOUNDS(ap, i);
539+
486540
if (i >= 0)
487541
((long long *)ap->ob_item)[i] = x;
488542
return 0;
@@ -515,6 +569,9 @@ QQ_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v)
515569
}
516570
return -1;
517571
}
572+
573+
CHECK_ARRAY_BOUNDS_WITH_CLEANUP(ap, i, v, do_decref);
574+
518575
if (i >= 0)
519576
((unsigned long long *)ap->ob_item)[i] = x;
520577

@@ -536,6 +593,9 @@ f_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v)
536593
float x;
537594
if (!PyArg_Parse(v, "f;array item must be float", &x))
538595
return -1;
596+
597+
CHECK_ARRAY_BOUNDS(ap, i);
598+
539599
if (i >= 0)
540600
((float *)ap->ob_item)[i] = x;
541601
return 0;
@@ -553,6 +613,9 @@ d_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v)
553613
double x;
554614
if (!PyArg_Parse(v, "d;array item must be float", &x))
555615
return -1;
616+
617+
CHECK_ARRAY_BOUNDS(ap, i);
618+
556619
if (i >= 0)
557620
((double *)ap->ob_item)[i] = x;
558621
return 0;

0 commit comments

Comments
 (0)