From 07ffdaf04f2505b44e226f29cc554dec9416239d Mon Sep 17 00:00:00 2001 From: Shardul D Date: Thu, 25 Jun 2026 14:03:27 +0530 Subject: [PATCH] gh-152166: Fix array.array.fromlist() exposing uninitialized memory on reentrant resize array.array.fromlist() preallocated n slots with array_resize() and then filled them at an index recomputed from the live Py_SIZE(self) each iteration, guarding only against the source list changing size. When an element's __index__ resized self as a side effect of the setitem call, the write index slid forward, the reserved slots were left unwritten, and the array exposed uninitialized heap memory (with the items misplaced) on a successful return. Fill the fixed slot old_size + i instead, and raise RuntimeError if self is resized mid-iteration, mirroring the existing list-mutation guard. This is distinct from gh-144128/gh-144138, which fixed a use-after-free in the *_setitem conversion helpers and did not touch fromlist's index logic. --- Lib/test/test_array.py | 22 +++++++++++++++++++ ...-06-25-14-03-16.gh-issue-152166.hH7w1v.rst | 4 ++++ Modules/arraymodule.c | 14 ++++++++++-- 3 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-06-25-14-03-16.gh-issue-152166.hH7w1v.rst diff --git a/Lib/test/test_array.py b/Lib/test/test_array.py index 23d9f3c9667d868..8f1ccab361c2b8b 100755 --- a/Lib/test/test_array.py +++ b/Lib/test/test_array.py @@ -82,6 +82,28 @@ def __index__(self): with self.assertRaises(TypeError): a.fromlist(lst) + def test_fromlist_reentrant_self_resize(self): + # gh-152166: if an element's __index__ resizes the array being + # filled, fromlist() must not skip the preallocated slots (which + # exposes uninitialized memory) or misplace items. It raises + # RuntimeError and rolls back, mirroring the list-mutation guard. + for label, mutate in (("grow", lambda a: a.append(0)), + ("shrink", lambda a: a.pop())): + for typecode in ('i', 'I', 'l', 'L', 'q', 'Q'): + with self.subTest(typecode=typecode, mutate=label): + a = array.array(typecode, [1, 2, 3]) + before = a.tolist() + + class Evil: + def __index__(self, _a=a, _m=mutate): + _m(_a) + return 0 + + with self.assertRaises(RuntimeError): + a.fromlist([Evil(), 4, 5]) + # The failed call must leave the array unchanged. + self.assertEqual(a.tolist(), before) + def test_typecodes(self): self.assertIsInstance(array.typecodes, tuple) for typecode in array.typecodes: diff --git a/Misc/NEWS.d/next/Library/2026-06-25-14-03-16.gh-issue-152166.hH7w1v.rst b/Misc/NEWS.d/next/Library/2026-06-25-14-03-16.gh-issue-152166.hH7w1v.rst new file mode 100644 index 000000000000000..aedb6739832eae6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-25-14-03-16.gh-issue-152166.hH7w1v.rst @@ -0,0 +1,4 @@ +Fix :meth:`array.array.fromlist` exposing uninitialized memory (and misplacing +items) when an element's :meth:`~object.__index__` method resizes the array +during the conversion. The reserved slots are now filled at a fixed offset and +the operation raises :exc:`RuntimeError` if the array is resized mid-iteration. diff --git a/Modules/arraymodule.c b/Modules/arraymodule.c index 9f613927be63159..a37af7b5cb62abf 100644 --- a/Modules/arraymodule.c +++ b/Modules/arraymodule.c @@ -1805,8 +1805,12 @@ array_array_fromlist_impl(arrayobject *self, PyObject *list) return NULL; for (i = 0; i < n; i++) { PyObject *v = PyList_GET_ITEM(list, i); - if ((*self->ob_descr->setitem)(self, - Py_SIZE(self) - n + i, v) != 0) { + /* setitem may run arbitrary Python (e.g. __index__), which can + resize self. Write to the fixed slot reserved above rather + than an offset recomputed from the live Py_SIZE, and bail out + if self was resized -- otherwise we would skip a preallocated + slot (exposing uninitialized memory) or misplace items. */ + if ((*self->ob_descr->setitem)(self, old_size + i, v) != 0) { array_resize(self, old_size); return NULL; } @@ -1816,6 +1820,12 @@ array_array_fromlist_impl(arrayobject *self, PyObject *list) array_resize(self, old_size); return NULL; } + if (Py_SIZE(self) != old_size + n) { + PyErr_SetString(PyExc_RuntimeError, + "array changed size during iteration"); + array_resize(self, old_size); + return NULL; + } } } Py_RETURN_NONE;