Repository navigation
Use after free in PyDict_Clear() due to re-entrancy #130555
Copy link
Copy link
Closed
Labels
3.13only security fixesonly security fixes3.14bugs and security fixesbugs and security fixesinterpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)type-crashA hard crash of the interpreter, possibly with a core dumpA hard crash of the interpreter, possibly with a core dump
Description
Crash report
The implementation of PyDict_Clear() isn't safe for dictionaries with values that are embedded in an object:
The calls to Py_CLEAR(oldvalues->values[i]) can execute arbitrary code via object destructors. oldvalues may no longer be valid because the object its embedded in may be freed during the Py_CLEAR() call.
Lines 2868 to 2880 in 0ef4ffe
| n = oldkeys->dk_nentries; | |
| for (i = 0; i < n; i++) { | |
| Py_CLEAR(oldvalues->values[i]); | |
| } | |
| if (oldvalues->embedded) { | |
| oldvalues->size = 0; | |
| } | |
| else { | |
| set_values(mp, NULL); | |
| set_keys(mp, Py_EMPTY_KEYS); | |
| free_values(oldvalues, IS_DICT_SHARED(mp)); | |
| dictkeys_decref(interp, oldkeys, false); | |
| } |
Repro
./configure --with-address-sanitizer --with-pydebug --without-pymalloc
class MyObj:
pass
class DelXOnDelete:
def __del__(self):
global x
del x
x = MyObj()
x.a = DelXOnDelete()
d = x.__dict__
d.clear()==3794958==ERROR: AddressSanitizer: heap-use-after-free on address 0x513000022ec2 at pc 0x56320dddb439 bp 0x7fff8205bb90 sp 0x7fff8205bb88
READ of size 1 at 0x513000022ec2 thread T0
#0 0x56320dddb438 in clear_lock_held /raid/sgross/cpython/Objects/dictobject.c:2872:24
#1 0x56320dddb438 in PyDict_Clear /raid/sgross/cpython/Objects/dictobject.c:2889:5
#2 0x56320ddf7e38 in dict_clear_impl /raid/sgross/cpython/Objects/dictobject.c:4452:5
#3 0x56320ddf7e38 in dict_clear /raid/sgross/cpython/Objects/clinic/dictobject.c.h:157:12
...
freed by thread T0 here:
#0 0x56320db400e6 in free (/raid/sgross/cpython/python+0x2fd0e6) (BuildId: f9a13329ff2de3d6fd6a01eae843cc226d0d8f7c)
#1 0x56320de99e6a in subtype_dealloc /raid/sgross/cpython/Objects/typeobject.c:2611:5
#2 0x56320de31448 in _Py_Dealloc /raid/sgross/cpython/Objects/object.c:3004:5
#3 0x56320dddc0e6 in Py_DECREF /raid/sgross/cpython/./Include/refcount.h:393:9
#4 0x56320dddc0e6 in _PyDict_Pop_KnownHash /raid/sgross/cpython/Objects/dictobject.c:3028:9
#5 0x56320e064bd4 in _PyEval_EvalFrameDefault /raid/sgross/cpython/Python/generated_cases.c.h:5006:23
...
Linked PRs
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
3.13only security fixesonly security fixes3.14bugs and security fixesbugs and security fixesinterpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)type-crashA hard crash of the interpreter, possibly with a core dumpA hard crash of the interpreter, possibly with a core dump
Activity
colesbury commentedon Feb 26, 2025
I don't see a way to fix this without copying
oldvaluesto a temporary array.DinoV commentedon Feb 26, 2025
We should detach the object from the dictionary when the object is freed (which should involve copying the values and assigning a new values).
So couldn't we check in the clear loop if the values have changed from
oldvaluesand if so then start over with the newoldvalues? Maybe only do that once in the embedded case as there's probably also recursion issues where clearing inserts into the dictionary and causes it to resize :Pcolesbury commentedon Feb 26, 2025
That might work, but I'm a bit unsure about it:
list.clear(),set.clear(), anddict.clear()prior to 3.13 ensured that the container was empty before decref'ing any previous elements. That makes the clear more atomic both with respect to re-entrancy and other threads.oldvaluesmay be a dangling pointer, in which case a check likeoldvalues != mp->ma_valueswould technically be undefined behavior. I'm also not sure what would happen if the newmp->ma_valueshappened to be allocated at the same address as the now freedoldvalues.PyDictValues.sizeto zero before or after we start clearing elements?colesbury commentedon Feb 26, 2026
Here is another reproducer:
pythongh-130555: Fix use-after-free in dict.clear() with embedded values
pythongh-130555: Fix use-after-free in dict.clear() with embedded values
gh-130555: Fix use-after-free in dict.clear() with embedded values (g…
[3.14] pythongh-130555: Fix use-after-free in dict.clear() with embed…
[3.13] pythongh-130555: Fix use-after-free in dict.clear() with embed…
[3.14] gh-130555: Fix use-after-free in dict.clear() with embedded va…
[3.13] gh-130555: Fix use-after-free in dict.clear() with embedded va…
pythongh-130555: Fix use-after-free in dict.clear() with embedded val…