You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Use-after-free by mutating set during set operations #90773
picklecrasher.py: Randomized crasher for uses of PyDict_Next in _pickle.c
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
assignee=Noneclosed_at=Nonecreated_at=<Date2022-02-02.18:01:22.729>labels= ['interpreter-core', '3.10', '3.9', 'type-crash', '3.11']
title='Use-after-free by mutating set during set operations'updated_at=<Date2022-02-19.05:24:06.142>user='https://github.com/sweeneyde'
The likely culprit is the set_next() loop. Perhaps it is never safe to use set_next() because any lookup can callback to __eq__ which can mutate the set.
Since set_isdisjoint() method isn't a mutating method, that is the easiest place to start investigating. Try disabling the exact set fast path to see if the issue persists.
Presumably _PyDict_Next is also suspect. Even the advertised "safe" calls to PyDict_SetItem() for existing keys would be a trigger. Calling clear() in either __eq__ or __hash__ would suffice.
If the next loops are the culprint, the new challenge is figuring out how to fix it without wrecking code clarity and performance (and having to deprecate PyDict_Next() which is part of the stable ABI).
Marking as low priority given that ehe next loop code has been deployed without incident for two decades (a little less for sets and a little more for dicts).
I agree with Dennis's observation that PyDict_Next is safe, provided it's used as intended: it returns borrowed references, but to things that absolutely are legitimate at the time. In the presence of mutations, *what* it returns isn't defined at all, but I don't see a way for it to blow up (unless its caller screws up by believing it owns the references). It doesn't assume anything about the structure of the dict across calls.
It does look like there are some pickle situations that crash. Attached is a randomized crasher. I haven't done too much careful reasoning about it, but adding INCREFs everywhere seems to fix most of the issues.
Activity
sweeneyde commentedon Feb 2, 2022
Maybe related to https://bugs.python.org/issue8420
Somewhat obscure, but using only standard Python, and no frame- or gc-hacks, it looks like we can get a use-after-free:
sweeneyde commentedon Feb 2, 2022
replacing
return Truewithreturn random() < 0.5makes all of the operations crash, except for|and|=.[-]Segfault in set intersection (&) and difference (-)[/-][+]Use-after-free by mutating set during set operations[/+][-]Segfault in set intersection (&) and difference (-)[/-][+]Use-after-free by mutating set during set operations[/+]sweeneyde commentedon Feb 2, 2022
set1.isdisjoint(set2) also crashes
rhettinger commentedon Feb 3, 2022
The likely culprit is the set_next() loop. Perhaps it is never safe to use set_next() because any lookup can callback to __eq__ which can mutate the set.
Since set_isdisjoint() method isn't a mutating method, that is the easiest place to start investigating. Try disabling the exact set fast path to see if the issue persists.
rhettinger commentedon Feb 3, 2022
Presumably _PyDict_Next is also suspect. Even the advertised "safe" calls to PyDict_SetItem() for existing keys would be a trigger. Calling clear() in either __eq__ or __hash__ would suffice.
If the next loops are the culprint, the new challenge is figuring out how to fix it without wrecking code clarity and performance (and having to deprecate PyDict_Next() which is part of the stable ABI).
rhettinger commentedon Feb 4, 2022
Marking as low priority given that ehe next loop code has been deployed without incident for two decades (a little less for sets and a little more for dicts).
sweeneyde commentedon Feb 4, 2022
It looks like usages of the PyDict_Next API assume the resulting references are borrowed and so INCREF them.
Usages of set_next do not, but should.
It should hopefully be a straightforward fix of adding INCREF/DECREFs.
tim-one commentedon Feb 4, 2022
Raised the priority back to normal.
I agree with Dennis's observation that PyDict_Next is safe, provided it's used as intended: it returns borrowed references, but to things that absolutely are legitimate at the time. In the presence of mutations, *what* it returns isn't defined at all, but I don't see a way for it to blow up (unless its caller screws up by believing it owns the references). It doesn't assume anything about the structure of the dict across calls.
sweeneyde commentedon Feb 11, 2022
New changeset 4a66615 by Dennis Sweeney in branch 'main':
bpo-46615: Don't crash when set operations mutate the sets (GH-31120)
4a66615
miss-islington commentedon Feb 11, 2022
New changeset 1f5fe99 by Miss Islington (bot) in branch '3.10':
bpo-46615: Don't crash when set operations mutate the sets (GH-31120)
1f5fe99
serhiy-storchaka commentedon Feb 13, 2022
New changeset c31b8a9 by Dennis Sweeney in branch '3.9':
bpo-46615: Don't crash when set operations mutate the sets (GH-31120) (GH-31312)
c31b8a9
serhiy-storchaka commentedon Feb 13, 2022
Thanks Dennis for your report and PRs.
Do you mind to analyze also uses of _PySet_NextEntry(), PyDict_Next() and _PyDict_Next()? Many of them look safe, but _pickle.c seems vulnerable.
sweeneyde commentedon Feb 19, 2022
It does look like there are some pickle situations that crash. Attached is a randomized crasher. I haven't done too much careful reasoning about it, but adding INCREFs everywhere seems to fix most of the issues.
sweeneyde commentedon May 18, 2022
The original issue is resolved. I opened #92930 about picking dicts.