@@ -32,6 +32,25 @@ def last_cb():
3232 pass
3333
3434
35+ class ReachableCode (Exception ):
36+ """Exception to raise to indicate that some code was reached.
37+
38+ Use this exception if using mocks is not a good alternative.
39+ """
40+
41+
42+ class SimpleEvilEventLoop (asyncio .base_events .BaseEventLoop ):
43+ """Base class for UAF and other evil stuff requiring an evil event loop."""
44+
45+ def get_debug (self ): # to suppress tracebacks
46+ return False
47+
48+ def __del__ (self ):
49+ # Automatically close the evil event loop to avoid warnings.
50+ if not self .is_closed () and not self .is_running ():
51+ self .close ()
52+
53+
3554class DuckFuture :
3655 # Class that does not inherit from Future but aims to be duck-type
3756 # compatible with it.
@@ -948,6 +967,7 @@ def __eq__(self, other):
948967 fut .remove_done_callback (evil ())
949968
950969 def test_evil_call_soon_list_mutation (self ):
970+ # see: https://github.com/python/cpython/issues/125969
951971 called_on_fut_callback0 = False
952972
953973 pad = lambda : ...
@@ -962,9 +982,8 @@ def evil_call_soon(*args, **kwargs):
962982 else :
963983 called_on_fut_callback0 = True
964984
965- fake_event_loop = lambda : ...
985+ fake_event_loop = SimpleEvilEventLoop ()
966986 fake_event_loop .call_soon = evil_call_soon
967- fake_event_loop .get_debug = lambda : False # suppress traceback
968987
969988 with mock .patch .object (self , 'loop' , fake_event_loop ):
970989 fut = self ._new_future ()
@@ -980,6 +999,74 @@ def evil_call_soon(*args, **kwargs):
980999 # returns an empty list but the C implementation returns None.
9811000 self .assertIn (fut ._callbacks , (None , []))
9821001
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+
1020+ def test_use_after_free_on_fut_callback_0_with_evil__getattribute__ (self ):
1021+ # see: https://github.com/python/cpython/issues/125984
1022+
1023+ class EvilEventLoop (SimpleEvilEventLoop ):
1024+ def call_soon (self , * args , ** kwargs ):
1025+ super ().call_soon (* args , ** kwargs )
1026+ raise ReachableCode
1027+
1028+ def __getattribute__ (self , name ):
1029+ nonlocal fut_callback_0
1030+ if name == 'call_soon' :
1031+ fut .remove_done_callback (fut_callback_0 )
1032+ del fut_callback_0
1033+ return object .__getattribute__ (self , name )
1034+
1035+ evil_loop = EvilEventLoop ()
1036+ with mock .patch .object (self , 'loop' , evil_loop ):
1037+ fut = self ._new_future ()
1038+ self .assertIs (fut .get_loop (), evil_loop )
1039+
1040+ fut_callback_0 = lambda : ...
1041+ fut .add_done_callback (fut_callback_0 )
1042+ self .assertRaises (ReachableCode , fut .set_result , "boom" )
1043+
1044+ def test_use_after_free_on_fut_context_0_with_evil__getattribute__ (self ):
1045+ # see: https://github.com/python/cpython/issues/125984
1046+
1047+ class EvilEventLoop (SimpleEvilEventLoop ):
1048+ def call_soon (self , * args , ** kwargs ):
1049+ super ().call_soon (* args , ** kwargs )
1050+ raise ReachableCode
1051+
1052+ def __getattribute__ (self , name ):
1053+ if name == 'call_soon' :
1054+ # resets the future's event loop
1055+ fut .__init__ (loop = SimpleEvilEventLoop ())
1056+ return object .__getattribute__ (self , name )
1057+
1058+ evil_loop = EvilEventLoop ()
1059+ with mock .patch .object (self , 'loop' , evil_loop ):
1060+ fut = self ._new_future ()
1061+ self .assertIs (fut .get_loop (), evil_loop )
1062+
1063+ fut_callback_0 = mock .Mock ()
1064+ fut_context_0 = mock .Mock ()
1065+ fut .add_done_callback (fut_callback_0 , context = fut_context_0 )
1066+ del fut_context_0
1067+ del fut_callback_0
1068+ self .assertRaises (ReachableCode , fut .set_result , "boom" )
1069+
9831070
9841071@unittest .skipUnless (hasattr (futures , '_CFuture' ),
9851072 'requires the C _asyncio module' )
0 commit comments