-
- Notifications
You must be signed in to change notification settings - Fork 33.6k
Closed
Labels
3.12only security fixesonly security fixes3.13bugs and security fixesbugs and security fixes3.14bugs and security fixesbugs and security fixesextension-modulesC modules in the Modules dirC modules in the Modules dirtopic-asynciotype-crashA hard crash of the interpreter, possibly with a core dumpA hard crash of the interpreter, possibly with a core dump
Description
Crash report
What happened?
This is the bug I mentioned I was looking into in #126080 (comment), but it's the same as all the ones that came before this.
cpython/Modules/_asynciomodule.c
Lines 3057 to 3060 in 9effa0f
| PyObject *r; | |
| int is_true; | |
| r = PyObject_CallMethodOneArg(result, &_Py_ID(cancel), | |
| task->task_cancel_msg); |
task->task_cancel_msg is missing an incref before usage so we can use a malicious __getattribute__ function in our class to free it before it gets sent to our cancel function.
PoC
import asyncio import types async def evil_coroutine(): @types.coroutine def sync_generator(): # ensure to keep obj alive after the first send() call global evil while 1: yield evil await sync_generator() class Loop: is_running = staticmethod(lambda: True) get_debug = staticmethod(lambda: False) class Evil: _asyncio_future_blocking = True get_loop = staticmethod(lambda: normal_loop) def add_done_callback(self, callback, *args, **kwargs): # sets task_cancel_msg to our victim object which will be deleted asyncio.Task.cancel(task, to_uaf) def cancel(self, msg): # if hasn't crashed at this point, you'll see its the same object that was just deleted print("in cancel", hex(id(msg))) def __getattribute__(self, name): global to_uaf if name == "cancel": class Break: def __str__(self): raise RuntimeError("break") # at this point, our obj to uaf only has 2 refs, `to_uaf` and `task->task_cancel_msg`. Doing a partial task init will clear # fut->fut_cancel_msg (same thing as task_cancel_msg, it's just been cast to a fut obj), and then we can just `del to_uaf` to free # the object before it gets sent to our `cancel` func try: task.__init__(coro, loop=normal_loop, name=Break()) except Exception as e: assert type(e) == RuntimeError and e.args[0] == "break" del to_uaf # to_uaf has now been deleted, but it will still be sent to our `cancel` func return object.__getattribute__(self, name) class DelTracker: def __del__(self): print("deleting", hex(id(self))) to_uaf = DelTracker() normal_loop = Loop() coro = evil_coroutine() evil = Evil() task = asyncio.Task.__new__(asyncio.Task) task.__init__(coro, loop=normal_loop, name="init", eager_start=True)Output
deleting 0x7f7a49cf9940 in cancel 0x7f7a49cf9940 Segmentation fault 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 fixesonly security fixes3.13bugs and security fixesbugs and security fixes3.14bugs and security fixesbugs and security fixesextension-modulesC modules in the Modules dirC modules in the Modules dirtopic-asynciotype-crashA hard crash of the interpreter, possibly with a core dumpA hard crash of the interpreter, possibly with a core dump
Projects
Status
Done