Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions Lib/test/test_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1680,6 +1681,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()
Original file line number Diff line number Diff line change
@@ -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.
65 changes: 64 additions & 1 deletion Modules/arraymodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -429,6 +471,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;

Expand All @@ -450,6 +495,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;
Expand Down Expand Up @@ -481,6 +529,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;

Expand All @@ -502,6 +553,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;
Expand Down Expand Up @@ -534,6 +588,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;

Expand All @@ -555,6 +612,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;
Expand All @@ -572,6 +632,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;
Expand Down
Loading