Skip to content

gh-151046: Fix use-after-free in _Unpickler_ReadIntoFromFile#151048

Open
tonghuaroot wants to merge 2 commits into
python:mainfrom
tonghuaroot:fix-unpickler-readinto-uaf
Open

gh-151046: Fix use-after-free in _Unpickler_ReadIntoFromFile#151048
tonghuaroot wants to merge 2 commits into
python:mainfrom
tonghuaroot:fix-unpickler-readinto-uaf

Conversation

@tonghuaroot
Copy link
Copy Markdown

@tonghuaroot tonghuaroot commented Jun 7, 2026

When the C pickle.Unpickler reads from a file-like object with readinto(),
it wraps an internal, short-lived buffer in a memoryview and passes it to
readinto(). The view was never released after the call, so a readinto()
that retains the view could read or write the buffer after it was freed -- a
C-level use-after-free reachable from pure Python.

This keeps our own reference across the call (using PyObject_CallOneArg
instead of the reference-stealing _Pickle_FastCall) and releases the
memoryview as soon as readinto() returns. A surviving reference now raises
ValueError: operation forbidden on released memoryview object instead of
touching freed memory.

A well-behaved readinto() releases any view it acquired before it returns, so
by then the buffer's export count is already zero and the added release() is a
no-op -- ordinary file objects round-trip unchanged. The release only has an
effect when a readinto() improperly keeps the view alive. The readinto()
fast path dates back to bpo-39681, so the issue is present on all maintained
branches.

Adds a regression test (CUnpicklerTests.test_readinto_does_not_keep_buffer_alive)
that fails on the unpatched build and a Misc/NEWS.d entry.


AI Usage Statement

The discovery, ASAN reproduction, root-cause analysis, and review of the fix
and tests were done by the contributor. An AI assistant helped draft the
implementation and the regression test under the contributor's direction. All
changes were verified by hand (ASAN reproduction before/after, full
test_pickle / test_memoryview / test_picklebuffer / test_io runs). Per
CPython's policy, disclosure of AI assistance is appreciated and is made here.

When unpickling from a file-like object that provides readinto(), the C
Unpickler handed it a temporary memoryview over an internal buffer and never
released it. A readinto() implementation that kept a reference to the view
could read or write the buffer after it had been freed, a use-after-free
reachable from pure Python.

Keep an owned reference across the call and release the memoryview as soon as
readinto() returns, so a surviving reference raises ValueError instead of
dereferencing freed memory. Add a regression test.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant