Skip to content

Commit 561e706

Browse files
committed
gh-142884: Use-After-Free Vulnerability Fixed in CPython array.array.tofile()
1 parent 39ecb17 commit 561e706

File tree

3 files changed

+37
-9
lines changed

3 files changed

+37
-9
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_use_after_free(self):
1371+
CHUNK = 64 * 1024
1372+
victim = array.array('B', b'\0' * (CHUNK * 2))
1373+
1374+
class Writer:
1375+
armed = True
1376+
def write(self, data):
1377+
if Writer.armed:
1378+
Writer.armed = False
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: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1587,35 +1587,47 @@ 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 */
15931592
int BLOCKSIZE = 64*1024;
1594-
Py_ssize_t nblocks = (nbytes + BLOCKSIZE - 1) / BLOCKSIZE;
1595-
Py_ssize_t i;
1593+
Py_ssize_t offset = 0;
15961594

15971595
if (Py_SIZE(self) == 0)
15981596
goto done;
15991597

1600-
16011598
array_state *state = get_array_state_by_class(cls);
16021599
assert(state != NULL);
16031600

1604-
for (i = 0; i < nblocks; i++) {
1605-
char* ptr = self->ob_item + i*BLOCKSIZE;
1601+
while (1) {
1602+
if (self->ob_item == NULL || Py_SIZE(self) == 0) {
1603+
break;
1604+
}
1605+
1606+
Py_ssize_t current_nbytes = Py_SIZE(self) * self->ob_descr->itemsize;
1607+
1608+
if (offset >= current_nbytes) {
1609+
break;
1610+
}
1611+
16061612
Py_ssize_t size = BLOCKSIZE;
1613+
if (offset + size > current_nbytes) {
1614+
size = current_nbytes - offset;
1615+
}
1616+
1617+
char* ptr = self->ob_item + offset;
16071618
PyObject *bytes, *res;
16081619

1609-
if (i*BLOCKSIZE + size > nbytes)
1610-
size = nbytes - i*BLOCKSIZE;
16111620
bytes = PyBytes_FromStringAndSize(ptr, size);
16121621
if (bytes == NULL)
16131622
return NULL;
1623+
16141624
res = PyObject_CallMethodOneArg(f, state->str_write, bytes);
16151625
Py_DECREF(bytes);
16161626
if (res == NULL)
16171627
return NULL;
1618-
Py_DECREF(res); /* drop write result */
1628+
Py_DECREF(res);
1629+
1630+
offset += size;
16191631
}
16201632

16211633
done:

0 commit comments

Comments
 (0)