@@ -31,6 +31,25 @@ def last_cb():
31
31
pass
32
32
33
33
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
+
34
53
class DuckFuture :
35
54
# Class that does not inherit from Future but aims to be duck-type
36
55
# compatible with it.
@@ -937,6 +956,7 @@ def __eq__(self, other):
937
956
fut .remove_done_callback (evil ())
938
957
939
958
def test_evil_call_soon_list_mutation (self ):
959
+ # see: https://github.com/python/cpython/issues/125969
940
960
called_on_fut_callback0 = False
941
961
942
962
pad = lambda : ...
@@ -951,9 +971,8 @@ def evil_call_soon(*args, **kwargs):
951
971
else :
952
972
called_on_fut_callback0 = True
953
973
954
- fake_event_loop = lambda : ...
974
+ fake_event_loop = SimpleEvilEventLoop ()
955
975
fake_event_loop .call_soon = evil_call_soon
956
- fake_event_loop .get_debug = lambda : False # suppress traceback
957
976
958
977
with mock .patch .object (self , 'loop' , fake_event_loop ):
959
978
fut = self ._new_future ()
@@ -969,6 +988,56 @@ def evil_call_soon(*args, **kwargs):
969
988
# returns an empty list but the C implementation returns None.
970
989
self .assertIn (fut ._callbacks , (None , []))
971
990
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
+
972
1041
973
1042
@unittest .skipUnless (hasattr (futures , '_CFuture' ),
974
1043
'requires the C _asyncio module' )
0 commit comments