Skip to content

gh-159185: Fix marshalling of a set modified while its elements are marshalled - #159195

Merged
gpshead merged 1 commit into
python:mainfrom
gpshead:marshal-set-changed-size
Oct 11, 2026
Merged

gpshead merged 1 commit into
python:mainfrom
gpshead:marshal-set-changed-size

Conversation

@gpshead

@gpshead gpshead commented Oct 11, 2026 •

Copy link
Copy Markdown
Member

To order the elements of a set, marshal marshals each element on its own. That can run Python code (an element's __buffer__(), an audit hook), and that code can change the set. The number of elements was read once, before the loop.

class Item:
    def __hash__(self):
        return 0
    def __buffer__(self, flags):
        s.update(range(1000, 1400))     # or: s.difference_update(range(1, 60))
        return memoryview(b'abc')

s = {Item()}
s.update(range(1, 60))
marshal.dumps(s)
on main release build debug build
set grows no error; writes past the end of the list PyList_SET_ITEM: Assertion 'index < list->allocated' failed
set shrinks segmentation fault w_complex_object: Assertion `i == n' failed

With this PR both raise RuntimeError: set changed size during iteration, as iterating the set would.

Change

flowchart TB
  subgraph before["main"]
    direction TB
    b1["n = size of the set"] --> b2["list of n slots"]
    b2 --> b3["for each element:<br>marshal it (may run Python code)<br>store in slot i++"]
    b3 -- "set grew" --> b4["writes past slot n"]
    b3 -- "set shrank" --> b5["slots left NULL,<br>then sorted"]
  end
  subgraph after["this PR"]
    direction TB
    a1["n = size of the set"] --> a2["list of n slots"]
    a2 --> a3["for each element:<br>stop if i == n<br>marshal it, store in slot i++"]
    a3 --> a4{"stopped early,<br>or i != n?"}
    a4 -- yes --> a5["RuntimeError"]
    a4 -- no --> a6["sort and write"]
  end
Loading

The change is two checks around the existing loop in w_complex_object(). On the free-threaded build the same checks cover another thread changing the set between the size being read and the critical section being entered. A set that is changed without changing its size is not detected; that is memory safe, and the output is whatever was iterated.

This touches the same block as the PR for gh-159184; whichever is merged second needs a one-line conflict resolved.

Tests

test_set_changed_size: grow and shrink, all marshal versions.

… are marshalled

Marshalling a set element can run Python code (__buffer__, an audit
hook) which changes the set.  The number of elements was read before
the loop, so a set which grew was written past the end of the list and
one which shrank left NULL items in the list which was then sorted.
Raise RuntimeError("set changed size during iteration") instead.
@gpshead gpshead self-assigned this Oct 11, 2026
@gpshead gpshead added needs backport to 3.14 bugs and security fixes needs backport to 3.15 bugs and security fixes labels Oct 11, 2026
@gpshead gpshead added the type-crash A hard crash of the interpreter, possibly with a core dump label Oct 11, 2026
@gpshead
gpshead merged commit cd71799 into python:main Oct 11, 2026
63 checks passed
@miss-islington-app

Copy link
Copy Markdown

Thanks @gpshead for the PR 🌮🎉.. I'm working now to backport this PR to: 3.14, 3.15.
🐍🍒⛏🤖 I'm not a witch! I'm not a witch!

@miss-islington-app

Copy link
Copy Markdown

Sorry, @gpshead, I could not cleanly backport this to 3.15 due to a conflict.

Please backport manually with cherry_picker, see the devguide for more information.

cherry_picker cd71799c47802f87e1469cc97cc99f5ce6cea27c 3.15

@miss-islington-app

Copy link
Copy Markdown

Sorry, @gpshead, I could not cleanly backport this to 3.14 due to a conflict.

Please backport manually with cherry_picker, see the devguide for more information.

cherry_picker cd71799c47802f87e1469cc97cc99f5ce6cea27c 3.14

@bedevere-app

bedevere-app Bot commented Oct 11, 2026

Copy link
Copy Markdown

GH-159199 is a backport of this pull request to the 3.15 branch.

@bedevere-app bedevere-app Bot removed the needs backport to 3.15 bugs and security fixes label Oct 11, 2026
@bedevere-app

bedevere-app Bot commented Oct 11, 2026

Copy link
Copy Markdown

GH-159200 is a backport of this pull request to the 3.14 branch.

@bedevere-app bedevere-app Bot removed the needs backport to 3.14 bugs and security fixes label Oct 11, 2026
gpshead added a commit that referenced this pull request Oct 11, 2026
…s are marshalled (GH-159195) (#159200)

Marshalling a set element can run Python code (__buffer__, an audit
hook) which changes the set.  The number of elements was read before
the loop, so a set which grew was written past the end of the list and
one which shrank left NULL items in the list which was then sorted.
Raise RuntimeError("set changed size during iteration") instead.

(cherry picked from commit cd71799)
gpshead added a commit that referenced this pull request Oct 11, 2026
…s are marshalled (GH-159195) (#159199)

Marshalling a set element can run Python code (__buffer__, an audit
hook) which changes the set.  The number of elements was read before
the loop, so a set which grew was written past the end of the list and
one which shrank left NULL items in the list which was then sorted.
Raise RuntimeError("set changed size during iteration") instead.

(cherry picked from commit cd71799)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type-crash A hard crash of the interpreter, possibly with a core dump

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant