Skip to content

Conversation

@gaogaotiantian
Copy link
Member

@gaogaotiantian gaogaotiantian commented Jun 9, 2024

@gaogaotiantian gaogaotiantian changed the title gh-120289: Warn if the external timer changes the cprofile context gh-120289: Do not free memory in disable() to prevent use-after-free Jun 10, 2024
@markshannon
Copy link
Member

I'm not familiar with this code, so I might be mistaken, but...

Needing a flag to determine if something is in a freelist seems risky. What if it is both in the freelist and in use, can't it end up being used twice?

The safer (if maybe a tad slower) approach would be to NULL out the context whenever it is freed and check before use.
It looks like you are already adding a NULL check here anyway.

@gaogaotiantian
Copy link
Member Author

Yeah that's one possible way to go. I thought of that but I also thought I can avoid the NULL checking with the free list solution. I was wrong and the ultimate solution was not as clean as I expected. I changed the solution to NULL the context when something goes wrong. Do you think it's better?

Copy link
Member

@markshannon markshannon left a comment

Choose a reason for hiding this comment

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

The news entry needs to be fixed. Should be good to merge once that's done.

@@ -0,0 +1 @@
Generate an unraisable exception if the external timer :mod:`cProfile` uses changes the profile context.
Copy link
Member

Choose a reason for hiding this comment

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

I don't think this is correct any more.

Copy link
Member Author

Choose a reason for hiding this comment

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

I changed that. I did not put implementation detail in it because I remember that we are not supposed to do that. So I just said I fixed the issue and mentioned the context.

@bedevere-app
Copy link

bedevere-app bot commented Jun 11, 2024

When you're done making the requested changes, leave the comment: I have made the requested changes; please review again.

Copy link
Member

@vstinner vstinner left a comment

Choose a reason for hiding this comment

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

This approach sounds complicated. Can't we modify timer.disable() to raise an exception if the timer is "not supposed to be disabled"?

@gaogaotiantian
Copy link
Member Author

I just realized when I tried to click "quote reply", I accidentally clicked "edit" ..

So here's my reply, and I'll try to restore your reply..

This approach sounds complicated. Can't we modify timer.disable() to raise an exception if the timer is "not supposed to be disabled"?

That's not the issue. The issue is that the profiler is disabled in the timer, which frees the context memory and caused the UAF. It's possible to enforce that the profiler should not be disabled in the timer, but the code of the timer does not propagate exceptions.

static inline PyTime_t
call_timer(ProfilerObject *pObj)
{
    if (pObj->externalTimer != NULL) {
        return CallExternalTimer(pObj);
    }
    else {
        PyTime_t t;
        (void)PyTime_PerfCounterRaw(&t);
        return t;
    }
}

More than that, initContext in ptrace_enter_call is also not friendly with exceptions. It's not possible to do that, but it's definitely not trivial. _lsprof.c is old and not quite well-maintained. To be honest cProfile is too old for a modern profiler.

@vstinner
Copy link
Member

It's possible to enforce that the profiler should not be disabled in the timer, but the code of the timer does not propagate exceptions.

You can emit a warning and/or log an "unraisable exception". Or simply ignore the attempt to disable the profiler silently.

@gaogaotiantian
Copy link
Member Author

Attempting to ignore the disable is very difficult - it requires the code to know whether it's in a timer which would make the code even more complicated. Yes emitting a warning/unraisable exception is an options, but do you mean in addition to the current code? Because this is a UAF, you need to deal with it. Simply sending a warning does not fix anything.

@gaogaotiantian
Copy link
Member Author

Hey @markshannon , do you still have comments on this PR? Thanks!

Copy link
Member

@markshannon markshannon left a comment

Choose a reason for hiding this comment

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

LGTM.

I have one question.


self.assertEqual(cm.unraisable.exc_type, TypeError)

@unittest.skipUnless(support.check_sanitizer(address=True), "only used to fail with ASAN")
Copy link
Member

Choose a reason for hiding this comment

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

Is this particularly slow?
I was wondering why not test it for all builds?

Copy link
Member Author

Choose a reason for hiding this comment

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

Not really. I added this just because that was the place where it failed. I can remove this so we can test it in all environments for regression.

Copy link
Member

@sobolevn sobolevn left a comment

Choose a reason for hiding this comment

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

Can you copy the second's issue reproducer to the tests please?

@gaogaotiantian
Copy link
Member Author

The original reproducer in the second issue won't be quite applicable because the actual trigger (clear()) won't be executed - disable() will raise an exception and stop clear() from running. What we can do is to do a pure clear() - I'm not sure if that would repro before the patch.

Copy link
Member

@serhiy-storchaka serhiy-storchaka left a comment

Choose a reason for hiding this comment

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

LGTM.

@gaogaotiantian gaogaotiantian added the 3.13 bugs and security fixes label Jul 18, 2024
@gaogaotiantian gaogaotiantian merged commit 1ab1778 into python:main Jul 18, 2024
@gaogaotiantian gaogaotiantian deleted the cprofile-uaf-warning branch July 18, 2024 19:47
@gaogaotiantian gaogaotiantian added needs backport to 3.13 bugs and security fixes and removed 3.13 bugs and security fixes labels Jul 18, 2024
@miss-islington-app
Copy link

Thanks @gaogaotiantian for the PR 🌮🎉.. I'm working now to backport this PR to: 3.13.
🐍🍒⛏🤖

miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Jul 18, 2024
…prevent use-after-free (pythonGH-120297)

(cherry picked from commit 1ab1778)

Co-authored-by: Tian Gao <[email protected]>
@bedevere-app
Copy link

bedevere-app bot commented Jul 18, 2024

GH-121984 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 Jul 18, 2024
gaogaotiantian added a commit that referenced this pull request Jul 18, 2024
… prevent use-after-free (GH-120297) (#121984)

gh-120289: Disallow disable() and clear() in external timer to prevent use-after-free (GH-120297)
(cherry picked from commit 1ab1778)

Co-authored-by: Tian Gao <[email protected]>
@gaogaotiantian gaogaotiantian added the needs backport to 3.12 only security fixes label Jul 18, 2024
@miss-islington-app
Copy link

Thanks @gaogaotiantian for the PR 🌮🎉.. I'm working now to backport this PR to: 3.12.
🐍🍒⛏🤖

miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Jul 18, 2024
…prevent use-after-free (pythonGH-120297)

(cherry picked from commit 1ab1778)

Co-authored-by: Tian Gao <[email protected]>
@bedevere-app
Copy link

bedevere-app bot commented Jul 18, 2024

GH-121989 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 Jul 18, 2024
gaogaotiantian added a commit that referenced this pull request Jul 18, 2024
… prevent use-after-free (GH-120297) (#121989)

gh-120289: Disallow disable() and clear() in external timer to prevent use-after-free (GH-120297)
(cherry picked from commit 1ab1778)

Co-authored-by: Tian Gao <[email protected]>
@colesbury
Copy link
Contributor

@gaogaotiantian
Copy link
Member Author

I'll take a look at it soon, thanks for the notice!

@gaogaotiantian
Copy link
Member Author

It turned out that the new code did not cause the issue, it triggered the issue. The root cause is that the profiler did not check the external timer, which could be a container, in traverse. I had the fix in #121998 and ./python -m test -R3:3 test_cprofile passed after the fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants