@@ -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.
@@ -956,6 +975,7 @@ def __eq__(self, other):
956975 fut .remove_done_callback (evil ())
957976
958977 def test_evil_call_soon_list_mutation (self ):
978+ # see: https://github.com/python/cpython/issues/125969
959979 called_on_fut_callback0 = False
960980
961981 pad = lambda : ...
@@ -970,9 +990,8 @@ def evil_call_soon(*args, **kwargs):
970990 else :
971991 called_on_fut_callback0 = True
972992
973- fake_event_loop = lambda : ...
993+ fake_event_loop = SimpleEvilEventLoop ()
974994 fake_event_loop .call_soon = evil_call_soon
975- fake_event_loop .get_debug = lambda : False # suppress traceback
976995
977996 with mock .patch .object (self , 'loop' , fake_event_loop ):
978997 fut = self ._new_future ()
@@ -988,6 +1007,56 @@ def evil_call_soon(*args, **kwargs):
9881007 # returns an empty list but the C implementation returns None.
9891008 self .assertIn (fut ._callbacks , (None , []))
9901009
1010+ def test_use_after_free_on_fut_callback_0_with_evil__getattribute__ (self ):
1011+ # see: https://github.com/python/cpython/issues/125984
1012+
1013+ class EvilEventLoop (SimpleEvilEventLoop ):
1014+ def call_soon (self , * args , ** kwargs ):
1015+ super ().call_soon (* args , ** kwargs )
1016+ raise ReachableCode
1017+
1018+ def __getattribute__ (self , name ):
1019+ nonlocal fut_callback_0
1020+ if name == 'call_soon' :
1021+ fut .remove_done_callback (fut_callback_0 )
1022+ del fut_callback_0
1023+ return object .__getattribute__ (self , name )
1024+
1025+ evil_loop = EvilEventLoop ()
1026+ with mock .patch .object (self , 'loop' , evil_loop ):
1027+ fut = self ._new_future ()
1028+ self .assertIs (fut .get_loop (), evil_loop )
1029+
1030+ fut_callback_0 = lambda : ...
1031+ fut .add_done_callback (fut_callback_0 )
1032+ self .assertRaises (ReachableCode , fut .set_result , "boom" )
1033+
1034+ def test_use_after_free_on_fut_context_0_with_evil__getattribute__ (self ):
1035+ # see: https://github.com/python/cpython/issues/125984
1036+
1037+ class EvilEventLoop (SimpleEvilEventLoop ):
1038+ def call_soon (self , * args , ** kwargs ):
1039+ super ().call_soon (* args , ** kwargs )
1040+ raise ReachableCode
1041+
1042+ def __getattribute__ (self , name ):
1043+ if name == 'call_soon' :
1044+ # resets the future's event loop
1045+ fut .__init__ (loop = SimpleEvilEventLoop ())
1046+ return object .__getattribute__ (self , name )
1047+
1048+ evil_loop = EvilEventLoop ()
1049+ with mock .patch .object (self , 'loop' , evil_loop ):
1050+ fut = self ._new_future ()
1051+ self .assertIs (fut .get_loop (), evil_loop )
1052+
1053+ fut_callback_0 = mock .Mock ()
1054+ fut_context_0 = mock .Mock ()
1055+ fut .add_done_callback (fut_callback_0 , context = fut_context_0 )
1056+ del fut_context_0
1057+ del fut_callback_0
1058+ self .assertRaises (ReachableCode , fut .set_result , "boom" )
1059+
9911060
9921061@unittest .skipUnless (hasattr (futures , '_CFuture' ),
9931062 'requires the C _asyncio module' )
0 commit comments