Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork33.7k
Description
Crash report
What happened?
This is basically an extension to#125984 but it took me a bit to get a working PoC because I have never used asyncio.Task ever.
The crash is caused because of a missing incref before callingcall_soon intask_call_step_soon which allows us to corrupttask_context in an evil__getattribute__ class func before handing it off tocall_soon. There's probably a much simpler way to trigger the crash but this is the only working route I found.
importasyncioimporttypes@types.coroutinedefgen():# this just needs to stay alive after the first `send` callglobalcatcherwhileTrue:yieldcatcherasyncdefcoro():awaitgen()# this class is used to help return early from the Task.__init__ function just after# task_context gets set in the funcclassEvilStr:def__str__(self):raiseException("break")classEvilLoop:defget_debug(self):returnFalsedefis_running(self):returnTruedefcall_soon(self,cb,*,context):# if it hasnt crashed for you at this point, you'll see this is the same obj that was just freedprint("in call_soon",context)def__getattribute__(self,name):globalctxifname=="call_soon":try:# context needs to be `None` so that it uses Py_XSETREF instead of just using regular assignmenttask.__init__(co,loop=loop,context=None,name=EvilStr())except:passreturnobject.__getattribute__(self,name)classTaskWakeupCatch:def__init__(self):self._asyncio_future_blocking=Truedefget_loop(self):globalloopreturnloop# as far as i know, this is the only way to get access to the `task_wakeup` function# which is needed to abuse the UAFdefadd_done_callback(self,cb,*,context):globalwakeup_fnifwakeup_fn==None:wakeup_fn=cbclassDelTracker:def__del__(self):print("deleting",self)co=coro()loop=EvilLoop()catcher=TaskWakeupCatch()wakeup_fn=Nonetask=asyncio.Task(co,loop=loop,eager_start=True,name="init")# set ctx to any obj you want to use after free# im using an obj that tells us when it's been freed so we can see the UAF in actionctx=DelTracker()try:# use exception trick to return early from the init func just after task_context gets settask.__init__(co,loop=loop,context=ctx,name=EvilStr())except:passdelctxminimal=lambda: ...minimal.result=lambda:None# only needs to be a function that doesnt errorassertwakeup_fnisnotNonewakeup_fn(minimal)
Output:
deleting <__main__.DelTracker object at 0x7f28e01d5be0>in call_soon <__main__.DelTracker object at 0x7f28e01d5be0>Segmentation faultI am on a version of python that doesn't include all the recent fixes to asyncio, so just to confirm I was triggering this viatask_call_step_soon I made sure to check the crash backtrace in gdb.
#0 _PyWeakref_GetWeakrefCount (obj=0x7ffff6f6dbe0) at Objects/weakrefobject.c:52#1 _PyWeakref_GetWeakrefCount (obj=0x7ffff6f6dbe0) at Objects/weakrefobject.c:42#2 PyObject_ClearWeakRefs (object=0x7ffff6f6dbe0) at Objects/weakrefobject.c:1018#3 0x00005555556daf9e in subtype_dealloc (self=0x7ffff6f6dbe0) at Objects/typeobject.c:2322#4 0x00005555557c4ec7 in Py_DECREF (op=<optimized out>) at ./Include/object.h:949#5 Py_XDECREF (op=<optimized out>) at ./Include/object.h:1042#6 _PyFrame_ClearLocals (frame=0x7ffff7afa0a0) at Python/frame.c:104#7 _PyFrame_ClearExceptCode (frame=0x7ffff7afa0a0) at Python/frame.c:129#8 0x0000555555796d47 in clear_thread_frame (frame=0x7ffff7afa0a0, tstate=0x555555adfc60 <_PyRuntime+282976>) at Python/ceval.c:1668#9 _PyEval_FrameClearAndPop (tstate=0x555555adfc60 <_PyRuntime+282976>, frame=0x7ffff7afa0a0) at Python/ceval.c:1695#10 0x00005555555db84f in _PyEval_EvalFrameDefault (tstate=0x7ffff6f6dd10, frame=0x7fffffffd680, throwflag=1437070368) at Python/generated_cases.c.h:5204#11 0x0000555555644638 in _PyObject_VectorcallTstate (kwnames=0x7ffff713db10, nargsf=2, args=0x7fffffffd7f0, callable=0x7ffff6dc00e0, tstate=0x555555adfc60 <_PyRuntime+282976>) at ./Include/internal/pycore_call.h:168#12 method_vectorcall (method=<optimized out>, args=0x7fffffffd7f8, nargsf=<optimized out>, kwnames=0x7ffff713db10) at Objects/classobject.c:62#13 0x0000555555641743 in _PyObject_VectorcallTstate (kwnames=0x7ffff713db10, nargsf=<optimized out>, args=0x7fffffffd7f8, callable=0x7ffff6f588c0, tstate=0x555555adfc60 <_PyRuntime+282976>) at ./Include/internal/pycore_call.h:168#14 PyObject_VectorcallMethod (name=<optimized out>, args=0x7fffffffd7f8, args@entry=0x7fffffffd7f0, nargsf=<optimized out>, nargsf@entry=9223372036854775810, kwnames=0x7ffff713db10) at Objects/call.c:856#15 0x00007ffff7021504 in call_soon (ctx=<optimized out>, arg=0x0, func=0x7ffff6f5f100, loop=<optimized out>, state=0x7ffff7098b30) at ./Modules/_asynciomodule.c:311#16 task_call_step_soon (state=state@entry=0x7ffff7098b30, task=task@entry=0x7ffff6f54c00, arg=arg@entry=0x7ffff7a61b40) at ./Modules/_asynciomodule.c:2677#17 0x00007ffff70216a9 in task_set_error_soon (state=state@entry=0x7ffff7098b30, task=task@entry=0x7ffff6f54c00, et=<optimized out>, format=<optimized out>) at ./Modules/_asynciomodule.c:2703#18 0x00007ffff7022043 in task_step_handle_result_impl (result=<optimized out>, task=0x7ffff6f54c00, state=0x7ffff7098b30) at ./Modules/_asynciomodule.c:3052#19 task_step_impl (state=state@entry=0x7ffff7098b30, task=task@entry=0x7ffff6f54c00, exc=<optimized out>, exc@entry=0x0) at ./Modules/_asynciomodule.c:2847#20 0x00007ffff7023327 in task_step (state=0x7ffff7098b30, task=0x7ffff6f54c00, exc=0x0) at ./Modules/_asynciomodule.c:3073The fix for this is to just increftask->task_context before callingcall_soon to avoid deleting it in the evil func
cpython/Modules/_asynciomodule.c
Lines 2676 to 2680 in60403a5
| intret=call_soon(state,task->task_loop,cb,NULL,task->task_context); | |
| Py_DECREF(cb); | |
| returnret; | |
| } |
CPython versions tested on:
3.13
Operating systems tested on:
Linux
Output from running 'python -VV' on the command line:
Python 3.13.0 (tags/v3.13.0:60403a5409f, Oct 10 2024, 09:24:12) [GCC 13.2.0]
Linked PRs
- gh-126080: fix UAF on
task->task_contextintask_call_step_soondue to an evilloop.__getattribute__#126120 - [3.13] gh-126080: fix UAF on
task->task_contextintask_call_step_soondue to an evilloop.__getattribute__(GH-126120) #126250 - [3.12] gh-126080: fix UAF on
task->task_contextintask_call_step_soondue to an evilloop.__getattribute__(GH-126120) #126251
Metadata
Metadata
Assignees
Labels
Projects
Status