https://github.com/python/cpython/commit/39f16a93ef7b39c3fc5a1a5b706512168baee499
commit: 39f16a93ef7b39c3fc5a1a5b706512168baee499
branch: main
author: AN Long <[email protected]>
committer: encukou <[email protected]>
date: 2026-02-02T15:44:08+01:00
summary:
gh-142555: Fix null pointer dereference in array.__setitem__ via re-entrant
__index__ (GH-142713)
files:
A Misc/NEWS.d/next/Library/2025-12-15-02-02-45.gh-issue-142555.EC9QN_.rst
M Lib/test/test_array.py
M Modules/arraymodule.c
diff --git a/Lib/test/test_array.py b/Lib/test/test_array.py
index b49df029f0326f..5c919aea24ed94 100755
--- a/Lib/test/test_array.py
+++ b/Lib/test/test_array.py
@@ -8,6 +8,7 @@
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 @@ def test_gh_128961(self):
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 00000000000000..72cc7c634b5750
--- /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__ <object.__index__>` or :meth:`i.__float__
+<object.__float__>` mutates the array.
diff --git a/Modules/arraymodule.c b/Modules/arraymodule.c
index 22ec3c31fb3ee1..ec6a9840131e4d 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;
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]