@@ -31,6 +31,25 @@ def last_cb():
3131 pass
3232
3333
34+ class ReachableCode (Exception ):
35+ """Exception to raise to indicate that some code was reached.
36+
37+ Use this exception if using mocks is not a good alternative.
38+ """
39+
40+
41+ class SimpleEvilEventLoop (asyncio .base_events .BaseEventLoop ):
42+ """Base class for UAF and other evil stuff requiring an evil event loop."""
43+
44+ def get_debug (self ): # to suppress tracebacks
45+ return False
46+
47+ def __del__ (self ):
48+ # Automatically close the evil event loop to avoid warnings.
49+ if not self .is_closed () and not self .is_running ():
50+ self .close ()
51+
52+
3453class DuckFuture :
3554 # Class that does not inherit from Future but aims to be duck-type
3655 # compatible with it.
@@ -937,6 +956,7 @@ def __eq__(self, other):
937956 fut .remove_done_callback (evil ())
938957
939958 def test_evil_call_soon_list_mutation (self ):
959+ # see: https://github.com/python/cpython/issues/125969
940960 called_on_fut_callback0 = False
941961
942962 pad = lambda : ...
@@ -951,9 +971,8 @@ def evil_call_soon(*args, **kwargs):
951971 else :
952972 called_on_fut_callback0 = True
953973
954- fake_event_loop = lambda : ...
974+ fake_event_loop = SimpleEvilEventLoop ()
955975 fake_event_loop .call_soon = evil_call_soon
956- fake_event_loop .get_debug = lambda : False # suppress traceback
957976
958977 with mock .patch .object (self , 'loop' , fake_event_loop ):
959978 fut = self ._new_future ()
@@ -969,6 +988,56 @@ def evil_call_soon(*args, **kwargs):
969988 # returns an empty list but the C implementation returns None.
970989 self .assertIn (fut ._callbacks , (None , []))
971990
991+ def test_use_after_free_on_fut_callback_0_with_evil__getattribute__ (self ):
992+ # see: https://github.com/python/cpython/issues/125984
993+
994+ class EvilEventLoop (SimpleEvilEventLoop ):
995+ def call_soon (self , * args , ** kwargs ):
996+ super ().call_soon (* args , ** kwargs )
997+ raise ReachableCode
998+
999+ def __getattribute__ (self , name ):
1000+ nonlocal fut_callback_0
1001+ if name == 'call_soon' :
1002+ fut .remove_done_callback (fut_callback_0 )
1003+ del fut_callback_0
1004+ return object .__getattribute__ (self , name )
1005+
1006+ evil_loop = EvilEventLoop ()
1007+ with mock .patch .object (self , 'loop' , evil_loop ):
1008+ fut = self ._new_future ()
1009+ self .assertIs (fut .get_loop (), evil_loop )
1010+
1011+ fut_callback_0 = lambda : ...
1012+ fut .add_done_callback (fut_callback_0 )
1013+ self .assertRaises (ReachableCode , fut .set_result , "boom" )
1014+
1015+ def test_use_after_free_on_fut_context_0_with_evil__getattribute__ (self ):
1016+ # see: https://github.com/python/cpython/issues/125984
1017+
1018+ class EvilEventLoop (SimpleEvilEventLoop ):
1019+ def call_soon (self , * args , ** kwargs ):
1020+ super ().call_soon (* args , ** kwargs )
1021+ raise ReachableCode
1022+
1023+ def __getattribute__ (self , name ):
1024+ if name == 'call_soon' :
1025+ # resets the future's event loop
1026+ fut .__init__ (loop = SimpleEvilEventLoop ())
1027+ return object .__getattribute__ (self , name )
1028+
1029+ evil_loop = EvilEventLoop ()
1030+ with mock .patch .object (self , 'loop' , evil_loop ):
1031+ fut = self ._new_future ()
1032+ self .assertIs (fut .get_loop (), evil_loop )
1033+
1034+ fut_callback_0 = mock .Mock ()
1035+ fut_context_0 = mock .Mock ()
1036+ fut .add_done_callback (fut_callback_0 , context = fut_context_0 )
1037+ del fut_context_0
1038+ del fut_callback_0
1039+ self .assertRaises (ReachableCode , fut .set_result , "boom" )
1040+
9721041
9731042@unittest .skipUnless (hasattr (futures , '_CFuture' ),
9741043 'requires the C _asyncio module' )
0 commit comments