From: AN Long Date: Mon, 2 Feb 2026 14:44:08 +0000 (+0900) Subject: gh-142555: Fix null pointer dereference in array.__setitem__ via re-entrant __index__... X-Git-Tag: v3.15.0a6~98 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=39f16a93ef7b39c3fc5a1a5b706512168baee499;p=thirdparty%2FPython%2Fcpython.git gh-142555: Fix null pointer dereference in array.__setitem__ via re-entrant __index__ (GH-142713) --- diff --git a/Lib/test/test_array.py b/Lib/test/test_array.py index b49df029f032..5c919aea24ed 100755 --- a/Lib/test/test_array.py +++ b/Lib/test/test_array.py @@ -8,6 +8,7 @@ from test import support from test.support import import_helper from test.support import os_helper from test.support import _2G +from test.support import subTests import weakref import pickle import operator @@ -1697,6 +1698,45 @@ class LargeArrayTest(unittest.TestCase): it.__setstate__(0) self.assertRaises(StopIteration, next, it) + # Tests for NULL pointer dereference in array.__setitem__ + # when the index conversion mutates the array. + # See: https://github.com/python/cpython/issues/142555. + + @subTests("dtype", ["b", "B", "h", "H", "i", "l", "q", "I", "L", "Q"]) + def test_setitem_use_after_clear_with_int_data(self, dtype): + victim = array.array(dtype, list(range(64))) + + class Index: + def __index__(self): + victim.clear() + return 0 + + self.assertRaises(IndexError, victim.__setitem__, 1, Index()) + self.assertEqual(len(victim), 0) + + def test_setitem_use_after_shrink_with_int_data(self): + victim = array.array('b', [1, 2, 3]) + + class Index: + def __index__(self): + victim.pop() + victim.pop() + return 0 + + self.assertRaises(IndexError, victim.__setitem__, 1, Index()) + + @subTests("dtype", ["f", "d"]) + def test_setitem_use_after_clear_with_float_data(self, dtype): + victim = array.array(dtype, [1.0, 2.0, 3.0]) + + class Float: + def __float__(self): + victim.clear() + return 0.0 + + self.assertRaises(IndexError, victim.__setitem__, 1, Float()) + self.assertEqual(len(victim), 0) + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Library/2025-12-15-02-02-45.gh-issue-142555.EC9QN_.rst b/Misc/NEWS.d/next/Library/2025-12-15-02-02-45.gh-issue-142555.EC9QN_.rst new file mode 100644 index 000000000000..72cc7c634b57 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-12-15-02-02-45.gh-issue-142555.EC9QN_.rst @@ -0,0 +1,3 @@ +:mod:`array`: fix a crash in ``a[i] = v`` when converting *i* to +an index via :meth:`i.__index__ ` or :meth:`i.__float__ +` mutates the array. diff --git a/Modules/arraymodule.c b/Modules/arraymodule.c index 22ec3c31fb3e..ec6a9840131e 100644 --- a/Modules/arraymodule.c +++ b/Modules/arraymodule.c @@ -205,6 +205,33 @@ Note that the basic Get and Set functions do NOT check that the index is in bounds; that's the responsibility of the caller. ****************************************************************************/ +/* Macro to check array buffer validity and bounds after calling + user-defined methods (like __index__ or __float__) that might modify + the array during the call. +*/ +#define CHECK_ARRAY_BOUNDS(OP, IDX) \ + do { \ + if ((IDX) >= 0 && ((OP)->ob_item == NULL || \ + (IDX) >= Py_SIZE((OP)))) { \ + PyErr_SetString(PyExc_IndexError, \ + "array assignment index out of range"); \ + return -1; \ + } \ + } while (0) + +#define CHECK_ARRAY_BOUNDS_WITH_CLEANUP(OP, IDX, VAL, CLEANUP) \ + do { \ + if ((IDX) >= 0 && ((OP)->ob_item == NULL || \ + (IDX) >= Py_SIZE((OP)))) { \ + PyErr_SetString(PyExc_IndexError, \ + "array assignment index out of range"); \ + if (CLEANUP) { \ + Py_DECREF(VAL); \ + } \ + return -1; \ + } \ + } while (0) + static PyObject * b_getitem(arrayobject *ap, Py_ssize_t i) { @@ -221,7 +248,10 @@ b_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v) the overflow checking */ if (!PyArg_Parse(v, "h;array item must be integer", &x)) return -1; - else if (x < -128) { + + CHECK_ARRAY_BOUNDS(ap, i); + + if (x < -128) { PyErr_SetString(PyExc_OverflowError, "signed char is less than minimum"); return -1; @@ -250,6 +280,9 @@ BB_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v) /* 'B' == unsigned char, maps to PyArg_Parse's 'b' formatter */ if (!PyArg_Parse(v, "b;array item must be integer", &x)) return -1; + + CHECK_ARRAY_BOUNDS(ap, i); + if (i >= 0) ((unsigned char *)ap->ob_item)[i] = x; return 0; @@ -342,6 +375,9 @@ h_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v) /* 'h' == signed short, maps to PyArg_Parse's 'h' formatter */ if (!PyArg_Parse(v, "h;array item must be integer", &x)) return -1; + + CHECK_ARRAY_BOUNDS(ap, i); + if (i >= 0) ((short *)ap->ob_item)[i] = x; return 0; @@ -371,6 +407,9 @@ HH_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v) "unsigned short is greater than maximum"); return -1; } + + CHECK_ARRAY_BOUNDS(ap, i); + if (i >= 0) ((short *)ap->ob_item)[i] = (short)x; return 0; @@ -389,6 +428,9 @@ i_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v) /* 'i' == signed int, maps to PyArg_Parse's 'i' formatter */ if (!PyArg_Parse(v, "i;array item must be integer", &x)) return -1; + + CHECK_ARRAY_BOUNDS(ap, i); + if (i >= 0) ((int *)ap->ob_item)[i] = x; return 0; @@ -432,6 +474,9 @@ II_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v) } return -1; } + + CHECK_ARRAY_BOUNDS_WITH_CLEANUP(ap, i, v, do_decref); + if (i >= 0) ((unsigned int *)ap->ob_item)[i] = (unsigned int)x; @@ -453,6 +498,9 @@ l_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v) long x; if (!PyArg_Parse(v, "l;array item must be integer", &x)) return -1; + + CHECK_ARRAY_BOUNDS(ap, i); + if (i >= 0) ((long *)ap->ob_item)[i] = x; return 0; @@ -487,6 +535,9 @@ LL_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v) } return -1; } + + CHECK_ARRAY_BOUNDS_WITH_CLEANUP(ap, i, v, do_decref); + if (i >= 0) ((unsigned long *)ap->ob_item)[i] = x; @@ -508,6 +559,9 @@ q_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v) long long x; if (!PyArg_Parse(v, "L;array item must be integer", &x)) return -1; + + CHECK_ARRAY_BOUNDS(ap, i); + if (i >= 0) ((long long *)ap->ob_item)[i] = x; return 0; @@ -543,6 +597,9 @@ QQ_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v) } return -1; } + + CHECK_ARRAY_BOUNDS_WITH_CLEANUP(ap, i, v, do_decref); + if (i >= 0) ((unsigned long long *)ap->ob_item)[i] = x; @@ -564,6 +621,9 @@ f_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v) float x; if (!PyArg_Parse(v, "f;array item must be float", &x)) return -1; + + CHECK_ARRAY_BOUNDS(ap, i); + if (i >= 0) ((float *)ap->ob_item)[i] = x; return 0; @@ -581,6 +641,9 @@ d_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v) double x; if (!PyArg_Parse(v, "d;array item must be float", &x)) return -1; + + CHECK_ARRAY_BOUNDS(ap, i); + if (i >= 0) ((double *)ap->ob_item)[i] = x; return 0;