Skip to content

Commit 72ec166

Browse files
miss-islingtonaisk
andauthored
[3.14] gh-142555: Fix null pointer dereference in array.__setitem__ via re-entrant __index__ (GH-142713) (#144396)
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 5af4e68 commit 72ec166

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
@@ -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;
@@ -429,6 +471,9 @@ II_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v)
429471
}
430472
return -1;
431473
}
474+
475+
CHECK_ARRAY_BOUNDS_WITH_CLEANUP(ap, i, v, do_decref);
476+
432477
if (i >= 0)
433478
((unsigned int *)ap->ob_item)[i] = (unsigned int)x;
434479

@@ -450,6 +495,9 @@ l_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v)
450495
long x;
451496
if (!PyArg_Parse(v, "l;array item must be integer", &x))
452497
return -1;
498+
499+
CHECK_ARRAY_BOUNDS(ap, i);
500+
453501
if (i >= 0)
454502
((long *)ap->ob_item)[i] = x;
455503
return 0;
@@ -481,6 +529,9 @@ LL_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v)
481529
}
482530
return -1;
483531
}
532+
533+
CHECK_ARRAY_BOUNDS_WITH_CLEANUP(ap, i, v, do_decref);
534+
484535
if (i >= 0)
485536
((unsigned long *)ap->ob_item)[i] = x;
486537

@@ -502,6 +553,9 @@ q_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v)
502553
long long x;
503554
if (!PyArg_Parse(v, "L;array item must be integer", &x))
504555
return -1;
556+
557+
CHECK_ARRAY_BOUNDS(ap, i);
558+
505559
if (i >= 0)
506560
((long long *)ap->ob_item)[i] = x;
507561
return 0;
@@ -534,6 +588,9 @@ QQ_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v)
534588
}
535589
return -1;
536590
}
591+
592+
CHECK_ARRAY_BOUNDS_WITH_CLEANUP(ap, i, v, do_decref);
593+
537594
if (i >= 0)
538595
((unsigned long long *)ap->ob_item)[i] = x;
539596

@@ -555,6 +612,9 @@ f_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v)
555612
float x;
556613
if (!PyArg_Parse(v, "f;array item must be float", &x))
557614
return -1;
615+
616+
CHECK_ARRAY_BOUNDS(ap, i);
617+
558618
if (i >= 0)
559619
((float *)ap->ob_item)[i] = x;
560620
return 0;
@@ -572,6 +632,9 @@ d_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v)
572632
double x;
573633
if (!PyArg_Parse(v, "d;array item must be float", &x))
574634
return -1;
635+
636+
CHECK_ARRAY_BOUNDS(ap, i);
637+
575638
if (i >= 0)
576639
((double *)ap->ob_item)[i] = x;
577640
return 0;

0 commit comments

Comments
 (0)