gh-143004: Fix UAF in _collections Counter update fast path#143044
gh-143004: Fix UAF in _collections Counter update fast path#143044serhiy-storchaka merged 8 commits intopython:mainfrom
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.
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>
|
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 86d9045) 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. |
…pdate() (pythonGH-143044) This happened when the Counter was mutated when incrementing the value for an existing key.
|
I’m interested in continuing work in this area and exploring whether it could form the basis of a structured GSoC project. I’d appreciate guidance on current priorities. |
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