Skip to content

gh-126080: fix UAF on task->task_context in task_call_step_soon due to an evil loop.__getattribute__ #126120

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

Merged
merged 7 commits into from
Oct 31, 2024

Conversation

picnixz
Copy link
Member

@picnixz picnixz commented Oct 29, 2024

I cleaned up the PoC a bit. The pure Python implementation should not be vulnerable to that kind of shenanigans although the interpreter raises an exception upon interpreter's exit (but it doesn't crash).

In order to align with the previous UAFs fixes, we use the temporary variable that we created instead of the structure's field (they are the same).
Copy link
Member

@1st1 1st1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm OK to accept the C code part of patch (after additional quick review), but I'm -1 on submitting the test, which looks extremely complicated and nearly impossible to maintain. Also due to how the test is structured it might be a pain to keep it up to date with future asyncio development.

@bedevere-app
Copy link

bedevere-app bot commented Oct 30, 2024

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

@encukou
Copy link
Member

encukou commented Oct 30, 2024

@1st1, would it make sense to

  • move the test to a separate file (test_asyncio/test_regressions.py perhaps)?
  • add a comment that the test uses internals, and can be removed if the internals change?

@willingc
Copy link
Contributor

@encukou @1st1 I agree that these tests are overly complicated and would be better in its own test file. I also think that the tests would benefit from better naming (ridiculous setup isn't very descriptive for future maintainers). I also think a one or two line docstring of what the test is testing would be helpful too.

@picnixz
Copy link
Member Author

picnixz commented Oct 30, 2024

I also think that the tests would benefit from better naming (ridiculous setup isn't very descriptive for future maintainers).

I can find a better name for that :)

I also think a one or two line docstring of what the test is testing would be helpful too.

I assumed that referencing the issue was sufficient but I can add some comments!

I agree that these tests are overly complicated and would be better in its own test file.

Would it be possible to delay that one until we fix all UAFs or make it the priority for now (either way is fine for me). @Nico-Posada has one PR that fixes another UAF and may be working on a second one for an even more contrived crash. If you want it to be now, I can create a PR just to isolate the existing tests (and to move ReachableCode exception to the test.support file because it could be a good place for future usage?).

@1st1
Copy link
Member

1st1 commented Oct 30, 2024

@encukou @willingc

move the test to a separate file (test_asyncio/test_regressions.py perhaps)?
add a comment that the test uses internals, and can be removed if the internals change?

I'm really not sure this all is worth the hassle. It's only a matter of time until someone stumbles upon this test and will not understand why it's there :)

This PR tests something very abstract and not occurring in real life scenarios, so I personally think it's OK to commit the fix for the sake of having cleaner code, but on the other hand there's no need to overthink it.

…uaf-126080

# Conflicts:
#	Lib/test/test_asyncio/test_tasks.py
@picnixz picnixz force-pushed the fix/task-call-soon-uaf-126080 branch from 401335c to 47e26c8 Compare October 31, 2024 09:36
@picnixz picnixz force-pushed the fix/task-call-soon-uaf-126080 branch from 47e26c8 to af8614d Compare October 31, 2024 09:36
@picnixz picnixz requested review from 1st1 and ZeroIntensity October 31, 2024 09:36
@picnixz
Copy link
Member Author

picnixz commented Oct 31, 2024

I've removed the contrived test as well as the small refactorization I needed. It's much cleaner and for posterity, people can just look at 02415df to get a test in the future.

Copy link
Member

@ZeroIntensity ZeroIntensity left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, LGTM. If any future problems come up with wrapping call_soon, let's move the job of incref'ing there instead of in the caller. For now, this is fine.

[EDITED by @picnixz]: The discussion in question (marked as resolved for visual purposes): #126120 (comment).

@Nico-Posada
Copy link
Contributor

Nico-Posada commented Oct 31, 2024

I'm curious if it's viable to do a refcount-based test to make sure that the refcount of our evil object is <num here> in __getattribute__. Can't test for UAF directly but it'll test to make sure the refcount is being increased properly which means the UAF is being prevented. The test case for this should be much shorter and easier to understand/implement.

@picnixz
Copy link
Member Author

picnixz commented Oct 31, 2024

I'm curious if it's viable to do a refcount-based test to make sure that the refcount of our evil object is in getattribute

Could be possible and we could transform previous tests like that but it might still require some globals to know when we need to trigger the check and I suspect it'll be ugly to read =/ (haven't tried though)

@Nico-Posada
Copy link
Contributor

Nico-Posada commented Oct 31, 2024

Here's something basic I was able to put together. On a build where this bug isn't patched, it says refcount 6, but on this version it says refcount 7. So in the test we would do something to assert the refcount at that spot >= 7`

Obviously there's a lot to clean up in this, but it's MUCH smaller now and we can probably add comments explaining why it's testing for refcounts in that spot.

import asyncio
import types
import sys
from contextvars import Context

async def none_coroutine():
    @types.coroutine
    def gen():
        while True:
            yield None
    await gen()

class TestDoneException(BaseException):
    pass

class TestLoop:
    get_debug = staticmethod(lambda: False)
    is_running = staticmethod(lambda: True)
    
    def __getattribute__(self, name):
        if name == "call_soon":
            print("refcount", sys.getrefcount(my_context))
            raise TestDoneException()
        
        return object.__getattribute__(self, name)

coro = none_coroutine()
my_context = Context()

try:
    task = asyncio.Task(coro, loop=TestLoop(), context=my_context, eager_start=True)
except Exception:
    print("done")

@picnixz
Copy link
Member Author

picnixz commented Oct 31, 2024

@1st1 Are you willing to consider this kind of test or do you prefer no test at all? if you want, we can try to remove previous tests that were also using intricate constructions just to trigger the UAF and make them adopt that kind of testing?

@encukou
Copy link
Member

encukou commented Oct 31, 2024

Refcount values can change when code is refactored, and if the refactor adds a refcount, a >= 7 assertion would pass even if this regresses. An ineffective test is worse than a failing one, IMO.

@picnixz
Copy link
Member Author

picnixz commented Oct 31, 2024

Oh right. Then I think no test is fine in this case.

Copy link
Member

@1st1 1st1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving as is without a test.

@1st1 1st1 merged commit 0e86655 into python:main Oct 31, 2024
42 of 43 checks passed
@miss-islington-app
Copy link

Thanks @picnixz for the PR, and @1st1 for merging it 🌮🎉.. I'm working now to backport this PR to: 3.12, 3.13.
🐍🍒⛏🤖

miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Oct 31, 2024
…oon` due to an evil `loop.__getattribute__` (pythonGH-126120)

(cherry picked from commit 0e86655)

Co-authored-by: Bénédikt Tran <[email protected]>
miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Oct 31, 2024
…oon` due to an evil `loop.__getattribute__` (pythonGH-126120)

(cherry picked from commit 0e86655)

Co-authored-by: Bénédikt Tran <[email protected]>
@bedevere-app
Copy link

bedevere-app bot commented Oct 31, 2024

GH-126250 is a backport of this pull request to the 3.13 branch.

@bedevere-app bedevere-app bot removed the needs backport to 3.13 bugs and security fixes label Oct 31, 2024
@bedevere-app
Copy link

bedevere-app bot commented Oct 31, 2024

GH-126251 is a backport of this pull request to the 3.12 branch.

@bedevere-app bedevere-app bot removed the needs backport to 3.12 only security fixes label Oct 31, 2024
@picnixz picnixz deleted the fix/task-call-soon-uaf-126080 branch October 31, 2024 17:16
willingc pushed a commit that referenced this pull request Oct 31, 2024
…soon` due to an evil `loop.__getattribute__` (GH-126120) (#126250)

gh-126080: fix UAF on `task->task_context` in `task_call_step_soon` due to an evil `loop.__getattribute__` (GH-126120)
(cherry picked from commit 0e86655)

Co-authored-by: Bénédikt Tran <[email protected]>
willingc pushed a commit that referenced this pull request Oct 31, 2024
…soon` due to an evil `loop.__getattribute__` (GH-126120) (#126251)

gh-126080: fix UAF on `task->task_context` in `task_call_step_soon` due to an evil `loop.__getattribute__` (GH-126120)
(cherry picked from commit 0e86655)

Co-authored-by: Bénédikt Tran <[email protected]>
picnixz added a commit to picnixz/cpython that referenced this pull request Dec 8, 2024
ebonnal pushed a commit to ebonnal/cpython that referenced this pull request Jan 12, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants