8000 Fix UAF (address Nico Posada's review) · python/cpython@22b21cc · GitHub
[go: up one dir, main page]

Skip to content

Commit 22b21cc

Browse files
committed
Fix UAF (address Nico Posada's review)
1 parent a05cde2 commit 22b21cc

File tree

2 files changed

+46
-3
lines changed

2 files changed

+46
-3
lines changed

Lib/test/test_asyncio/test_futures.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -970,6 +970,45 @@ def __eq__(self, other):
970970

971971
self.assertIs(mem, cb_pad)
972972

973+
# Use-After-Free sanity checks.
974+
# Credits to Nico-Posada for the PoCs.
975+
# See https://github.com/python/cpython/pull/125833.
976+
977+
def test_use_after_free_fixed_1(self):
978+
fut = self._new_future()
979+
980+
class setup:
981+
def __eq__(self, other):
982+
return False
983+
984+
# evil MUST be a subclass of setup for __eq__ to get called first
985+
class evil(setup):
986+
def __eq__(self, value):
987+
fut._callbacks.clear()
988+
return NotImplemented
989+
990+
cb_pad = lambda: ...
991+
fut.add_done_callback(cb_pad) # sets fut->fut_callback0
992+
fut.add_done_callback(setup()) # sets fut->fut_callbacks[0]
993+
# removes callback from fut->fut_callback0 setting it to NULL
994+
fut.remove_done_callback(cb_pad)
995+
fut.remove_done_callback(evil())
996+
997+
def test_use_after_free_fixed_2(self):
998+
fut = self._new_future()
999+
1000+
class cb_pad:
1001+
def __eq__(self, other):
1002+
return True
1003+
1004+
class evil(cb_pad):
1005+
def __eq__(self, other):
1006+
fut.remove_done_callback(None)
1007+
return NotImplemented
1008+
1009+
fut.add_done_callback(cb_pad())
1010+
fut.remove_done_callback(evil())
1011+
9731012

9741013
@unittest.skipUnless(hasattr(futures, '_CFuture'),
9751014
'requires the C _asyncio module')

Modules/_asynciomodule.c

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1036,8 +1036,12 @@ _asyncio_Future_remove_done_callback_impl(FutureObj *self, PyTypeObject *cls,
10361036

10371037
if (self->fut_callback0 != NULL) {
10381038
// Beware: An evil PyObject_RichCompareBool could change fut_callback0
1039-
// (see https://github.com/python/cpython/issues/125789 for details).
1040-
int cmp = PyObject_RichCompareBool(self->fut_callback0, fn, Py_EQ);
1039+
// (see https://github.com/python/cpython/issues/125789 for details)
1040+
// In addition, the reference to self->fut_callback0 may be cleared,
1041+
// so we need to temporarily hold it explicitly.
1042+
PyObject *fut_callback0 = Py_NewRef(self->fut_callback0);
1043+
int cmp = PyObject_RichCompareBool(fut_callback0, fn, Py_EQ);
1044+
Py_DECREF(fut_callback0);
10411045
if (cmp == -1) {
10421046
return NULL;
10431047
}
@@ -1076,7 +1080,7 @@ _asyncio_Future_remove_done_callback_impl(FutureObj *self, PyTypeObject *cls,
10761080
Py_INCREF(cb_tup);
10771081
PyObject *cb = PyTuple_GET_ITEM(cb_tup, 0);
10781082
int cmp = PyObject_RichCompareBool(cb, fn, Py_EQ);
1079-
Py_DECREF(cb_tup);
1083+
Py_XDECREF(cb_tup);
10801084
if (cmp == -1) {
10811085
return NULL;
10821086
}

0 commit comments

Comments
 (0)
0