Title: gh-144475: Fix use-after-free in functools.partial.__repr__() by Nicolas0315 · Pull Request #145395 · python/cpython · GitHub
Open Graph Title: gh-144475: Fix use-after-free in functools.partial.__repr__() by Nicolas0315 · Pull Request #145395 · python/cpython
X Title: gh-144475: Fix use-after-free in functools.partial.__repr__() by Nicolas0315 · Pull Request #145395 · python/cpython
Description: 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. Issue: gh-144475
Open Graph Description: 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 ar...
X Description: 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 ar...
Opengraph URL: https://github.com/python/cpython/pull/145395
X: @github
Domain: github.com
| route-pattern | /:user_id/:repository/pull/:id/files(.:format) |
| route-controller | pull_requests |
| route-action | files |
| fetch-nonce | v2:2be9a181-aa4e-8260-9568-69b6b287cccb |
| current-catalog-service-hash | ae870bc5e265a340912cde392f23dad3671a0a881730ffdadd82f2f57d81641b |
| request-id | C558:1A2FA1:A5871B4:A8F998F:6A50B103 |
| html-safe-nonce | c1c6f7c46223672bda38bcf046bcdbad9e50ad2438b941fbd77cc992c06d939b |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJDNTU4OjFBMkZBMTpBNTg3MUI0OkE4Rjk5OEY6NkE1MEIxMDMiLCJ2aXNpdG9yX2lkIjoiNjA5MDUxMjczOTkwOTgwODM4NyIsInJlZ2lvbl9lZGdlIjoic2VhIiwicmVnaW9uX3JlbmRlciI6InNlYSJ9 |
| visitor-hmac | 89554bc91b462263ff71c50dbb5f9aa9ae772bed938b0e9e90b9c744b3641924 |
| hovercard-subject-tag | pull_request:3341299033 |
| github-keyboard-shortcuts | repository,pull-request-list,pull-request-conversation,pull-request-files-changed,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/python/cpython/pull/145395/files |
| twitter:image | https://avatars.githubusercontent.com/u/40846197?s=400&v=4 |
| twitter:card | summary_large_image |
| og:image | https://avatars.githubusercontent.com/u/40846197?s=400&v=4 |
| og:image:alt | 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 ar... |
| og:site_name | GitHub |
| og:type | object |
| hostname | github.com |
| expected-hostname | github.com |
| None | d6dc8294eb500fa36bbc57472d61fe87c522f9c3c1d64f70f4926f66a66a7efb |
| turbo-cache-control | no-preview |
| diff-view | unified |
| go-import | github.com/python/cpython git https://github.com/python/cpython.git |
| octolytics-dimension-user_id | 1525981 |
| octolytics-dimension-user_login | python |
| octolytics-dimension-repository_id | 81598961 |
| octolytics-dimension-repository_nwo | python/cpython |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 81598961 |
| octolytics-dimension-repository_network_root_nwo | python/cpython |
| turbo-body-classes | logged-out env-production page-responsive full-width |
| disable-turbo | true |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 7ac0ad2f2c7e4b9056617355fd9e33e22b0c8df9 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width