Skip to content

Commit ed5059e

Browse files
authored
gh-125966: fix use-after-free on fut->fut_callback0 due to an evil callback's __eq__ in asyncio (#125967)
1 parent 0922a4a commit ed5059e

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
@@ -999,6 +999,24 @@ def evil_call_soon(*args, **kwargs):
999999
# returns an empty list but the C implementation returns None.
10001000
self.assertIn(fut._callbacks, (None, []))
10011001

1002+
def test_use_after_free_on_fut_callback_0_with_evil__eq__(self):
1003+
# Special thanks to Nico-Posada for the original PoC.
1004+
# See https://github.com/python/cpython/issues/125966.
1005+
1006+
fut = self._new_future()
1007+
1008+
class cb_pad:
1009+
def __eq__(self, other):
1010+
return True
1011+
1012+
class evil(cb_pad):
1013+
def __eq__(self, other):
1014+
fut.remove_done_callback(None)
1015+
return NotImplemented
1016+
1017+
fut.add_done_callback(cb_pad())
1018+
fut.remove_done_callback(evil())
1019+
10021020
def test_use_after_free_on_fut_callback_0_with_evil__getattribute__(self):
10031021
# see: https://github.com/python/cpython/issues/125984
10041022

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
@@ -1019,7 +1019,12 @@ _asyncio_Future_remove_done_callback_impl(FutureObj *self, PyTypeObject *cls,
10191019
ENSURE_FUTURE_ALIVE(state, self)
10201020

10211021
if (self->fut_callback0 != NULL) {
1022-
int cmp = PyObject_RichCompareBool(self->fut_callback0, fn, Py_EQ);
1022+
// Beware: An evil PyObject_RichCompareBool could free fut_callback0
1023+
// before a recursive call is made with that same arg. For details, see
1024+
// https://github.com/python/cpython/pull/125967#discussion_r1816593340.
1025+
PyObject *fut_callback0 = Py_NewRef(self->fut_callback0);
1026+
int cmp = PyObject_RichCompareBool(fut_callback0, fn, Py_EQ);
1027+
Py_DECREF(fut_callback0);
10231028
if (cmp == -1) {
10241029
return NULL;
10251030
}

0 commit comments

Comments
 (0)