Skip to content

marshal.dumps() crashes when a set is modified while it is being marshalled #159185

Description

@gpshead

Crash report

To sort the elements of a set, w_complex_object() reads the size of the
set, 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 set
grows, the loop writes past the end of the list. If it shrinks, the list
is left with NULL items and is then sorted.

import marshal, sys

class Item:
    def __hash__(self):
        return 0
    def __buffer__(self, flags):
        if sys.argv[1] == 'grow':
            s.update(range(1000, 1400))
        else:
            s.difference_update(range(1, 60))
        return memoryview(b'abc')

s = {Item()}
s.update(range(1, 60))
marshal.dumps(s)

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 of
elements 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

Activity

  1. added
    interpreter-core(Objects, Python, Grammar, and Parser dirs)
    stdlibStandard Library Python modules in the Lib/ directory
    type-crashA hard crash of the interpreter, possibly with a core dump
    on Oct 11, 2026
  2. self-assigned this
    on Oct 11, 2026
  3. removed
    stdlibStandard Library Python modules in the Lib/ directory
    on Oct 11, 2026
  4. added a commit that references this issue on Oct 11, 2026
  5. gpshead commented on Oct 11, 2026

    @gpshead
    MemberAuthor

    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 with ValueError("unmarshallable object").

    The backport therefore follows the branch's own pattern: a new WFERR_SET_CHANGED_SIZE code, set where main raises directly, and a new case in the final switch that raises RuntimeError("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() and PyMarshal_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].

  6. added
    3.14bugs and security fixes
    3.15bugs and security fixes
    on Oct 11, 2026
  7. added 4 commits that reference this issue on Oct 11, 2026
  8. gpshead commented on Oct 11, 2026

    @gpshead
    MemberAuthor

    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.

  9. added a commit that references this issue on Oct 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

3.14bugs and security fixes3.15bugs and security fixesinterpreter-core(Objects, Python, Grammar, and Parser dirs)type-crashA hard crash of the interpreter, possibly with a core dump

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions