@@ -32,6 +32,25 @@ def last_cb():
32
32
pass
33
33
34
34
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
+
35
54
class DuckFuture :
36
55
# Class that does not inherit from Future but aims to be duck-type
37
56
# compatible with it.
@@ -948,6 +967,7 @@ def __eq__(self, other):
948
967
fut .remove_done_callback (evil ())
949
968
950
969
def test_evil_call_soon_list_mutation (self ):
970
+ # see: https://github.com/python/cpython/issues/125969
951
971
called_on_fut_callback0 = False
952
972
953
973
pad = lambda : ...
@@ -962,9 +982,8 @@ def evil_call_soon(*args, **kwargs):
962
982
else :
963
983
called_on_fut_callback0 = True
964
984
965
- fake_event_loop = lambda : ...
985
+ fake_event_loop = SimpleEvilEventLoop ()
966
986
fake_event_loop .call_soon = evil_call_soon
967
- fake_event_loop .get_debug = lambda : False # suppress traceback
968
987
969
988
with mock .patch .object (self , 'loop' , fake_event_loop ):
970
989
fut = self ._new_future ()
@@ -980,6 +999,56 @@ def evil_call_soon(*args, **kwargs):
980
999
# returns an empty list but the C implementation returns None.
981
1000
self .assertIn (fut ._callbacks , (None , []))
982
1001
1002
+ def test_use_after_free_on_fut_callback_0_with_evil__getattribute__ (self ):
1003
+ # see: https://github.com/python/cpython/issues/125984
1004
+
1005
+ class EvilEventLoop (SimpleEvilEventLoop ):
1006
+ def call_soon (self , * args , ** kwargs ):
1007
+ super ().call_soon (* args , ** kwargs )
1008
+ raise ReachableCode
1009
+
1010
+ def __getattribute__ (self , name ):
1011
+ nonlocal fut_callback_0
1012
+ if name == 'call_soon' :
1013
+ fut .remove_done_callback (fut_callback_0 )
1014
+ del fut_callback_0
1015
+ return object .__getattribute__ (self , name )
1016
+
1017
+ evil_loop = EvilEventLoop ()
1018
+ with mock .patch .object (self , 'loop' , evil_loop ):
1019
+ fut = self ._new_future ()
1020
+ self .assertIs (fut .get_loop (), evil_loop )
1021
+
1022
+ fut_callback_0 = lambda : ...
1023
+ fut .add_done_callback (fut_callback_0 )
1024
+ self .assertRaises (ReachableCode , fut .set_result , "boom" )
1025
+
1026
+ def test_use_after_free_on_fut_context_0_with_evil__getattribute__ (self ):
1027
+ # see: https://github.com/python/cpython/issues/125984
1028
+
1029
+ class EvilEventLoop (SimpleEvilEventLoop ):
1030
+ def call_soon (self , * args , ** kwargs ):
1031
+ super ().call_soon (* args , ** kwargs )
1032
+ raise ReachableCode
1033
+
1034
+ def __getattribute__ (self , name ):
1035
+ if name == 'call_soon' :
1036
+ # resets the future's event loop
1037
+ fut .__init__ (loop = SimpleEvilEventLoop ())
1038
+ return object .__getattribute__ (self , name )
1039
+
1040
+ evil_loop = EvilEventLoop ()
1041
+ with mock .patch .object (self , 'loop' , evil_loop ):
1042
+ fut = self ._new_future ()
1043
+ self .assertIs (fut .get_loop (), evil_loop )
1044
+
1045
+ fut_callback_0 = mock .Mock ()
1046
+ fut_context_0 = mock .Mock ()
1047
+ fut .add_done_callback (fut_callback_0 , context = fut_context_0 )
1048
+ del fut_context_0
1049
+ del fut_callback_0
1050
+ self .assertRaises (ReachableCode , fut .set_result , "boom" )
1051
+
983
1052
984
1053
@unittest .skipUnless (hasattr (futures , '_CFuture' ),
985
1054
'requires the C _asyncio module' )
0 commit comments