-
-
Notifications
You must be signed in to change notification settings - Fork 33.7k
gh-143004: Fix UAF in _collections Counter update fast path #143044
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
Conversation
|
Most changes to Python require a NEWS entry. Add one using the blurb_it web app or the blurb command-line tool. If this change has little impact on Python users, wait for a maintainer to apply the |
StanFromIreland
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please create an issue and add a blurb.
@StanFromIreland, it's #143004. @Kaushalt2004, while technically this seems to be correct, this is not something that solves real-world problem. Could you please provide benchmarks to show how this will impact more typical use-cases for Counter()? |
|
I ran a small microbenchmark to estimate the impact of the Py_INCREF(oldval) / Py_DECREF(oldval) pair added around PyNumber_Add() in the dict fast-path. Setup Windows x64 Before: origin/main (b8d3fdd) subst X: "C:\Users\...\New folder (12)" update(unique) from empty: 26218.0 → 26438.0 (+0.84%) Deltas are within ~±1% on this machine; no meaningful regression observed, including the “preseeded” case that exercises the new INCREF/DECREF on every update. Setup Windows x64 Before: origin/main (b8d3fdd) subst X: "C:\Users\...\New folder (12)" update(unique) from empty: 26218.0 → 26438.0 (+0.84%) Deltas are within ~±1% on this machine; no meaningful regression observed, including the “preseeded” case that exercises the new INCREF/DECREF on every update. |
|
Most changes to Python require a NEWS entry. Add one using the blurb_it web app or the blurb command-line tool. If this change has little impact on Python users, wait for a maintainer to apply the |
I still see no benchmark code. PS: @Kaushalt2004, do not click Update branch without a good reason because it notifies everyone watching the PR that there are new changes, when there are not, and it uses up limited CI resources. |
Misc/NEWS.d/next/Library/2025-12-22-00-00-00.gh-issue-143004.uaf-counter.rst
Outdated
Show resolved
Hide resolved
|
Thanks for the feedback. I’ve removed the benchmark script from the PR as requested. Benchmark methodology and results were shared earlier in comments; Please let me know if you’d like the benchmark rerun using pyperf |
Misc/NEWS.d/next/Library/2025-12-22-00-00-00.gh-issue-143004.uaf-counter.rst
Outdated
Show resolved
Hide resolved
Misc/NEWS.d/next/Library/2025-12-22-00-00-00.gh-issue-143004.uaf-counter.rst
Outdated
Show resolved
Hide resolved
…af-counter.rst Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
Misc/NEWS.d/next/Library/2025-12-22-00-00-00.gh-issue-143004.uaf-counter.rst
Outdated
Show resolved
Hide resolved
…af-counter.rst Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
…new__ override, rely on __add__ only. Clarify with reviewer note. (python#1 branch)
|
Thanks for the clarification. You’re right that Python guarantees subclass construction, and the test does not demonstrate any loss of subclass identity. |
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
serhiy-storchaka
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. 👍
|
Thanks @Kaushalt2004 for the PR, and @serhiy-storchaka for merging it 🌮🎉.. I'm working now to backport this PR to: 3.13, 3.14. |
…pdate() (pythonGH-143044) This happened when the Counter was mutated when incrementing the value for an existing key. (cherry picked from commit 86d904588e8c84c7fccb8faf84b343f03461970d) Co-authored-by: kaushal trivedi <155625932+Kaushalt2004@users.noreply.github.com>
…pdate() (pythonGH-143044) This happened when the Counter was mutated when incrementing the value for an existing key. (cherry picked from commit 86d9045) Co-authored-by: kaushal trivedi <155625932+Kaushalt2004@users.noreply.github.com>
|
GH-143166 is a backport of this pull request to the 3.14 branch. |
|
GH-143167 is a backport of this pull request to the 3.13 branch. |
Problem
_collections._count_elements has a fast-path for dict that reads oldval via _PyDict_GetItem_KnownHash() (borrowed reference) and then calls PyNumber_Add(oldval, one). PyNumber_Add() can run arbitrary Python code (e.g. add), allowing re-entrant mutation such as Counter.clear(). That can drop the last reference to oldval while the C code still holds a borrowed pointer, resulting in a use-after-free (ASAN).
Reproducer
Fix
In the fast dict path, keep oldval alive across PyNumber_Add() by temporarily owning a reference:
Py_INCREF(oldval); newval = PyNumber_Add(oldval, one); Py_DECREF(oldval);
This prevents oldval from being freed if user code re-enters and clears/mutates the mapping during the add.
Changes
_collectionsmodule.c: INCREF/DECREF oldval around PyNumber_Add() in the fast path.
test_collections.py: add regression test test_update_reentrant_add_clears_counter.
Tests
./python -m test test_collections -k reentrant_add -v
Counter.updatevia re-entrant__add__#143004