GeezerVison is showing github.com — gh-144475: Fix use-after-free in functools.partial.__repr__() by Nicolas0315 · Pull Request #145395 · python/cpython
Conversation
Nicolas0315
commented
Mar 1, 2026
•
edited by bedevere-app
Bot
Loading
Uh oh!
There was an error while loading. Please reload this page.
Summary
Fix a heap-buffer-overflow (use-after-free) in functools.partial.__repr__() where a user-defined __repr__() on an argument could mutate the partial object via __setstate__(), freeing the args tuple while partial_repr() was still iterating over it.
Root Cause
partial_repr() captured the size of pto->args before the loop and accessed tuple items via borrowed references (PyTuple_GET_ITEM). If a __repr__() called during %R formatting invoked pto.__setstate__() with a new (smaller) args tuple, the original tuple was freed while iteration continued, causing an out-of-bounds read.
Fix
Hold strong references (Py_NewRef) to pto->args, pto->kw, and pto->fn before iterating/using them. This ensures the underlying objects remain alive even if user code mutates the partial via __setstate__() during formatting.
The same pattern is applied to:
pto->args: The positional arguments tuple iterated in the loop
pto->kw: The keyword arguments dict iterated via PyDict_Next
pto->fn: The callable whose __repr__ is invoked via %R
Reproducer (from the issue)
import gc
from functools import partial
g_partial = None
class EvilObject:
def __init__(self, name, is_trigger=False):
self.name = name
self.is_trigger = is_trigger
self.triggered = False
def __repr__(self):
global g_partial
if self.is_trigger and not self.triggered and g_partial is not None:
self.triggered = True
new_state = (lambda x: x, ("replaced",), {}, None)
g_partial.__setstate__(new_state)
gc.collect()
return f"EvilObject({self.name})"
evil1 = EvilObject("trigger", is_trigger=True)
evil2 = EvilObject("victim1")
evil3 = EvilObject("victim2")
p = partial(lambda: None, evil1, evil2, evil3)
g_partial = p
del evil1, evil2, evil3
repr(p) # heap-buffer-overflow without fix
Fixes #144475.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Hold strong references to pto->args, pto->kw, and pto->fn during
partial_repr() to prevent them from being freed by a user-defined
__repr__() that mutates the partial object via __setstate__().
Previously, partial_repr() iterated over pto->args using a size 'n'
captured before the loop, and accessed tuple items via borrowed
references. If a __repr__() called during formatting invoked
pto.__setstate__() with a new (smaller) args tuple, the original
tuple could be freed while the loop was still iterating, leading to
a heap-buffer-overflow (out-of-bounds read).
The fix takes a new reference (Py_NewRef) to the args tuple, kw dict,
and fn callable before using them, ensuring they stay alive regardless
of any mutations to the partial object during formatting.
|
All commit authors signed the Contributor License Agreement.

|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
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 skip news label instead.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
gh-144475: Add NEWS entry for functools.partial.__repr__ fix
Use inline code markup instead of :func: and :meth: roles for
partial.__repr__ and __setstate__ to avoid Sphinx reference
resolution failures in the docs CI.
|
There is already an active PR #145362 for this issue, so please don’t open another PR for the same fix.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Reviewers
rhettinger
Awaiting requested review from rhettinger
rhettinger is a code owner
Uh oh!
There was an error while loading. Please reload this page.
Assignees
No one assigned
Milestone
No milestone
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
|