marshal.dumps() crashes when a set is modified while it is being marshalled #159185
Description
Activity
- addedinterpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)stdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytype-crashA hard crash of the interpreter, possibly with a core dumpA hard crash of the interpreter, possibly with a core dump
on Oct 11, 2026 - removedstdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directory
on Oct 11, 2026 - added a commit that references this issue
on Oct 11, 2026 Automated backports failed because main has gh-156942, which is not on 3.15 or 3.14. On main the marshal writer raises the exception where the error is detected. On the older branches it stores a
WFERR_*code and turns it into an exception at the end, so an exception set inside the loop would be overwritten withValueError("unmarshallable object").The backport therefore follows the branch's own pattern: a new
WFERR_SET_CHANGED_SIZEcode, set where main raises directly, and a new case in the final switch that raisesRuntimeError("set changed size during iteration"). The in-loop check and the test are the same as on main. The marshal.c change is identical on both branches.One behaviour to know about
PyMarshal_WriteObjectToFile()still raises nothing on these branches. 😦 It ignores every marshalling error there; reporting them is gh-155907, which is not backported. With the fix it no longer crashes on a mutated set, but it returns silently after writing a truncated 5-byte file.marshal.dumps(),marshal.dump()andPyMarshal_WriteObjectToString()do raise. The NEWS entry only names dumps and dump, so it is still accurate.... keeping them draft while considering if the above should be backported first so it is a happier situation. [edit: Verdict, not backporting those].
- added3.14bugs and security fixesbugs and security fixes3.15bugs and security fixesbugs and security fixes
on Oct 11, 2026 - added 4 commits that reference this issue
on Oct 11, 2026 the doc note I added isn't anything I'd call pretty but this shouldn't be a widely used API and it at least reflects the pre-existing reality of its deficiencies.
- added a commit that references this issue
on Oct 11, 2026
Crash report
To sort the elements of a set,
w_complex_object()reads the size of theset, allocates a list of that size, then marshals each element on its own
in a loop. Marshalling an element can run Python code (an element's
__buffer__, an audit hook), and that code can change the set. If the setgrows, the loop writes past the end of the list. If it shrinks, the list
is left with NULL items and is then sorted.
On main:
shrink, release build: segmentation fault.grow, release build: no error; writes past the list allocation.grow, debug build:PyList_SET_ITEM: Assertion 'index < list->allocated' failed.On the free-threaded build another thread changing the set between the size
read and the critical section has the same effect.
Proposed fix
Stop the loop at the size that was read, and raise
RuntimeError("set changed size during iteration")if the number ofelements seen differs from it, as iterating a set does.
Discovered using Claude while doing a deep dive on pyc compilation determinism.
CPython versions tested on:
CPython main branch
Operating systems tested on:
No response
Output from running 'python -VV' on the command line:
No response
Linked PRs