From 561e7068ccf89f8fbd513e42313917254f470416 Mon Sep 17 00:00:00 2001 From: fatelei Date: Thu, 18 Dec 2025 11:43:38 +0800 Subject: [PATCH] gh-142884: Use-After-Free Vulnerability Fixed in CPython array.array.tofile() --- Lib/test/test_array.py | 15 ++++++++++ ...-12-18-11-41-37.gh-issue-142884.kjgukd.rst | 1 + Modules/arraymodule.c | 30 +++++++++++++------ 3 files changed, 37 insertions(+), 9 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-12-18-11-41-37.gh-issue-142884.kjgukd.rst diff --git a/Lib/test/test_array.py b/Lib/test/test_array.py index 83b3c978da3581..8cb4ba1de19cf7 100755 --- a/Lib/test/test_array.py +++ b/Lib/test/test_array.py @@ -1367,6 +1367,21 @@ def test_frombytearray(self): b = array.array(self.typecode, a) self.assertEqual(a, b) + def test_tofile_use_after_free(self): + CHUNK = 64 * 1024 + victim = array.array('B', b'\0' * (CHUNK * 2)) + + class Writer: + armed = True + def write(self, data): + if Writer.armed: + Writer.armed = False + victim.clear() + return 0 + + victim.tofile(Writer()) + + class IntegerNumberTest(NumberTest): def test_type_error(self): a = array.array(self.typecode) diff --git a/Misc/NEWS.d/next/Library/2025-12-18-11-41-37.gh-issue-142884.kjgukd.rst b/Misc/NEWS.d/next/Library/2025-12-18-11-41-37.gh-issue-142884.kjgukd.rst new file mode 100644 index 00000000000000..7dd529c21575fc --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-12-18-11-41-37.gh-issue-142884.kjgukd.rst @@ -0,0 +1 @@ +Use-After-Free Vulnerability Fixed in CPython array.array.tofile(). diff --git a/Modules/arraymodule.c b/Modules/arraymodule.c index 729e085c19f006..89700cf3489e8f 100644 --- a/Modules/arraymodule.c +++ b/Modules/arraymodule.c @@ -1587,35 +1587,47 @@ static PyObject * array_array_tofile_impl(arrayobject *self, PyTypeObject *cls, PyObject *f) /*[clinic end generated code: output=4560c628d9c18bc2 input=5a24da7a7b407b52]*/ { - Py_ssize_t nbytes = Py_SIZE(self) * self->ob_descr->itemsize; /* Write 64K blocks at a time */ /* XXX Make the block size settable */ int BLOCKSIZE = 64*1024; - Py_ssize_t nblocks = (nbytes + BLOCKSIZE - 1) / BLOCKSIZE; - Py_ssize_t i; + Py_ssize_t offset = 0; if (Py_SIZE(self) == 0) goto done; - array_state *state = get_array_state_by_class(cls); assert(state != NULL); - for (i = 0; i < nblocks; i++) { - char* ptr = self->ob_item + i*BLOCKSIZE; + while (1) { + if (self->ob_item == NULL || Py_SIZE(self) == 0) { + break; + } + + Py_ssize_t current_nbytes = Py_SIZE(self) * self->ob_descr->itemsize; + + if (offset >= current_nbytes) { + break; + } + Py_ssize_t size = BLOCKSIZE; + if (offset + size > current_nbytes) { + size = current_nbytes - offset; + } + + char* ptr = self->ob_item + offset; PyObject *bytes, *res; - if (i*BLOCKSIZE + size > nbytes) - size = nbytes - i*BLOCKSIZE; bytes = PyBytes_FromStringAndSize(ptr, size); if (bytes == NULL) return NULL; + res = PyObject_CallMethodOneArg(f, state->str_write, bytes); Py_DECREF(bytes); if (res == NULL) return NULL; - Py_DECREF(res); /* drop write result */ + Py_DECREF(res); + + offset += size; } done: