Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Crash: UAF intask_call_step_soon in_asynciomodule.c (with admittedly ridiculous setup) #126080

Closed
Assignees
picnixz
Labels
3.12only security fixes3.13bugs and security fixes3.14bugs and security fixesextension-modulesC modules in the Modules dirtopic-asynciotype-crashA hard crash of the interpreter, possibly with a core dump
@Nico-Posada

Description

@Nico-Posada

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 fault

I 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:3073

The fix for this is to just increftask->task_context before callingcall_soon to avoid deleting it in the evil func

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

Metadata

Metadata

Assignees

Labels

3.12only security fixes3.13bugs and security fixes3.14bugs and security fixesextension-modulesC modules in the Modules dirtopic-asynciotype-crashA hard crash of the interpreter, possibly with a core dump

Projects

Status

Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions


    [8]ページ先頭

    ©2009-2025 Movatter.jp