Skip to content

Commit 4fc1da1

Browse files
[3.12] gh-125966: fix use-after-free on fut->fut_callback0 due to an evil callback's __eq__ in asyncio (GH-125967) (#126048)
gh-125966: fix use-after-free on `fut->fut_callback0` due to an evil callback's `__eq__` in asyncio (GH-125967) (cherry picked from commit ed5059e) Co-authored-by: Bénédikt Tran <[email protected]>
1 parent fdedb26 commit 4fc1da1

File tree

3 files changed

+26
-1
lines changed

3 files changed

+26
-1
lines changed

Lib/test/test_asyncio/test_futures.py

+18
Original file line numberDiff line numberDiff line change
@@ -988,6 +988,24 @@ def evil_call_soon(*args, **kwargs):
988988
# returns an empty list but the C implementation returns None.
989989
self.assertIn(fut._callbacks, (None, []))
990990

991+
def test_use_after_free_on_fut_callback_0_with_evil__eq__(self):
992+
# Special thanks to Nico-Posada for the original PoC.
993+
# See https://github.com/python/cpython/issues/125966.
994+
995+
fut = self._new_future()
996+
997+
class cb_pad:
998+
def __eq__(self, other):
999+
return True
1000+
1001+
class evil(cb_pad):
1002+
def __eq__(self, other):
1003+
fut.remove_done_callback(None)
1004+
return NotImplemented
1005+
1006+
fut.add_done_callback(cb_pad())
1007+
fut.remove_done_callback(evil())
1008+
9911009
def test_use_after_free_on_fut_callback_0_with_evil__getattribute__(self):
9921010
# see: https://github.com/python/cpython/issues/125984
9931011

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix a use-after-free crash in :meth:`asyncio.Future.remove_done_callback`.
2+
Patch by Bénédikt Tran.

Modules/_asynciomodule.c

+6-1
Original file line numberDiff line numberDiff line change
@@ -1044,7 +1044,12 @@ _asyncio_Future_remove_done_callback_impl(FutureObj *self, PyTypeObject *cls,
10441044
ENSURE_FUTURE_ALIVE(state, self)
10451045

10461046
if (self->fut_callback0 != NULL) {
1047-
int cmp = PyObject_RichCompareBool(self->fut_callback0, fn, Py_EQ);
1047+
// Beware: An evil PyObject_RichCompareBool could free fut_callback0
1048+
// before a recursive call is made with that same arg. For details, see
1049+
// https://github.com/python/cpython/pull/125967#discussion_r1816593340.
1050+
PyObject *fut_callback0 = Py_NewRef(self->fut_callback0);
1051+
int cmp = PyObject_RichCompareBool(fut_callback0, fn, Py_EQ);
1052+
Py_DECREF(fut_callback0);
10481053
if (cmp == -1) {
10491054
return NULL;
10501055
}

0 commit comments

Comments
 (0)