Repository navigation
UAF when using a malicious __getattribute__ when calling a class's cancel function in task_step_handle_result_impl in _asynciomodule.c #126138
Copy link
Copy link
Closed
Labels
3.12only security fixesonly security fixes3.13only security fixesonly 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
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
3.12only security fixesonly security fixes3.13only security fixesonly 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
- StatusShow more project fieldsDone
Activity
Nico-Posada commentedon Oct 29, 2024
Sidenote: This happens in 2 spots so both need to be fixed
First Usage:
cpython/Modules/_asynciomodule.c
Lines 2964 to 2967 in 9effa0f
Second Usage (the one I linked in my report):
cpython/Modules/_asynciomodule.c
Lines 3057 to 3060 in 9effa0f
picnixz commentedon Oct 29, 2024
As always, thank you! I really like how you incorporate the bits I put in the test for you previous issue. I am not on my dev environment now (and won't be before at least 12 hours) so if you want to patch this one as well, you can create the PR! (I can review it though).
[-]UAF in `_asynciomodule.c` when calling a class's `cancel` function using a malicious `__getattribute__` in `task_step_handle_result_impl`[/-][+]UAF when using a malicious `__getattribute__` when calling a class's `cancel` function in `task_step_handle_result_impl` in `_asynciomodule.c`[/+]gh-126138: Fix use-after-free in `_asyncio.Task` by evil `__getattrib…
pythongh-126138: Fix use-after-free in `_asyncio.Task` by evil `__get…
pythongh-126138: Fix use-after-free in `_asyncio.Task` by evil `__get…
[3.12] gh-126138: Fix use-after-free in `_asyncio.Task` by evil `__ge…
[3.13] gh-126138: Fix use-after-free in `_asyncio.Task` by evil `__ge…
pythongh-126138: Fix use-after-free in `_asyncio.Task` by evil `__get…
pythongh-126138: Fix use-after-free in `_asyncio.Task` by evil `__get…