Skip to content

Commit 7e5244e

Browse files
committed
gh-142884: Use-After-Free Vulnerability Fixed in CPython array.array.tofile()
1 parent 61ee048 commit 7e5244e

File tree

3 files changed

+43
-10
lines changed

3 files changed

+43
-10
lines changed

Lib/test/test_array.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1367,6 +1367,21 @@ def test_frombytearray(self):
13671367
b = array.array(self.typecode, a)
13681368
self.assertEqual(a, b)
13691369

1370+
def test_tofile_concurrent_mutation(self):
1371+
BLOCKSIZE = 64 * 1024
1372+
victim = array.array('B', b'\0' * (BLOCKSIZE * 2))
1373+
1374+
class Writer:
1375+
cleared = False
1376+
def write(self, data):
1377+
if not self.cleared:
1378+
self.cleared = True
1379+
victim.clear()
1380+
return 0
1381+
1382+
victim.tofile(Writer())
1383+
1384+
13701385
class IntegerNumberTest(NumberTest):
13711386
def test_type_error(self):
13721387
a = array.array(self.typecode)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Use-After-Free Vulnerability Fixed in CPython array.array.tofile().

Modules/arraymodule.c

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1587,35 +1587,52 @@ static PyObject *
15871587
array_array_tofile_impl(arrayobject *self, PyTypeObject *cls, PyObject *f)
15881588
/*[clinic end generated code: output=4560c628d9c18bc2 input=5a24da7a7b407b52]*/
15891589
{
1590-
Py_ssize_t nbytes = Py_SIZE(self) * self->ob_descr->itemsize;
15911590
/* Write 64K blocks at a time */
15921591
/* XXX Make the block size settable */
1593-
int BLOCKSIZE = 64*1024;
1592+
const Py_ssize_t BLOCKSIZE = 64*1024;
1593+
const Py_ssize_t itemsize = self->ob_descr->itemsize;
1594+
1595+
Py_ssize_t nbytes = Py_SIZE(self) * itemsize;
15941596
Py_ssize_t nblocks = (nbytes + BLOCKSIZE - 1) / BLOCKSIZE;
1595-
Py_ssize_t i;
15961597

15971598
if (Py_SIZE(self) == 0)
15981599
goto done;
15991600

1600-
16011601
array_state *state = get_array_state_by_class(cls);
16021602
assert(state != NULL);
16031603

1604-
for (i = 0; i < nblocks; i++) {
1605-
char* ptr = self->ob_item + i*BLOCKSIZE;
1606-
Py_ssize_t size = BLOCKSIZE;
1604+
for (Py_ssize_t i = 0; i < nblocks; i++) {
1605+
if (self->ob_item == NULL || Py_SIZE(self) == 0) {
1606+
break;
1607+
}
1608+
1609+
if (Py_SIZE(self) > PY_SSIZE_T_MAX / itemsize) {
1610+
return PyErr_NoMemory();
1611+
}
1612+
1613+
Py_ssize_t current_nbytes = Py_SIZE(self) * itemsize;
1614+
const Py_ssize_t offset = i * BLOCKSIZE;
1615+
if (offset >= current_nbytes) {
1616+
break;
1617+
}
1618+
1619+
Py_ssize_t size = current_nbytes - offset;
1620+
if (size > BLOCKSIZE) {
1621+
size = BLOCKSIZE;
1622+
}
1623+
1624+
char* ptr = self->ob_item + offset;
16071625
PyObject *bytes, *res;
16081626

1609-
if (i*BLOCKSIZE + size > nbytes)
1610-
size = nbytes - i*BLOCKSIZE;
16111627
bytes = PyBytes_FromStringAndSize(ptr, size);
16121628
if (bytes == NULL)
16131629
return NULL;
1630+
16141631
res = PyObject_CallMethodOneArg(f, state->str_write, bytes);
16151632
Py_DECREF(bytes);
16161633
if (res == NULL)
16171634
return NULL;
1618-
Py_DECREF(res); /* drop write result */
1635+
Py_DECREF(res);
16191636
}
16201637

16211638
done:

0 commit comments

Comments
 (0)