-
-
Notifications
You must be signed in to change notification settings - Fork 33.7k
Open
Labels
interpreter-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
What happened?
delattr(obj, name) and setattr(obj, name) ends up in PyObject_SetAttr(..., value=NULL), which calls the generic setter that deletes from obj.__dict__ via _PyDict_SetItem_LockHeld(value=NULL). If name is a str subclass with a re-entrant __hash__, hashing name runs user code that swaps out and frees the old __dict__. The deletion then continues using the stale dict pointer in _PyDict_DelItem_KnownHash_LockHeld, leading to a heap-use-after-free.
Proof of Concept:
PoC
class Evil(str):
def __hash__(self):
old = target.__dict__
target.__dict__ = {}
del old
dicts = []
for i in range(100):
dicts.append({f"key{i}": f"value{i}"})
return 0
class Victim:
pass
target = Victim()
delattr(target, Evil("marker"))import gc
class Evil(str):
def __hash__(self):
old = target.__dict__
target.__dict__ = {}
del old
gc.collect()
return 0
class Victim:
pass
target = Victim()
setattr(target, Evil("marker"))Related Code Snippet
Details
int
_PyDict_SetItem_LockHeld(PyDictObject *dict, PyObject *name, PyObject *value)
{
if (value == NULL) {
// Bug: Trigger the override __hash__ method that free the dict
Py_hash_t hash = _PyObject_HashFast(name);
if (hash == -1) {
dict_unhashable_type(name);
return -1;
}
// Access the freed dict buffer.
return _PyDict_DelItem_KnownHash_LockHeld((PyObject *)dict, name, hash);
} else {
return setitem_lock_held(dict, name, value);
}
}
int
_PyDict_DelItem_KnownHash_LockHeld(PyObject *op, PyObject *key, Py_hash_t hash)
{
Py_ssize_t ix;
PyDictObject *mp;
PyObject *old_value;
if (!PyDict_Check(op)) {
PyErr_BadInternalCall();
return -1;
}
ASSERT_DICT_LOCKED(op);
assert(key);
assert(hash != -1);
mp = (PyDictObject *)op;
// Leak the op/mp->me_key pointer back
ix = _Py_dict_lookup(mp, key, hash, &old_value);
if (ix == DKIX_ERROR)
return -1;
if (ix == DKIX_EMPTY || old_value == NULL) {
_PyErr_SetKeyError(key);
return -1;
}
PyInterpreterState *interp = _PyInterpreterState_GET();
_PyDict_NotifyEvent(interp, PyDict_EVENT_DELETED, mp, key, NULL);
delitem_common(mp, hash, ix, old_value);
return 0;
}Affected Versions:
Details
| Python Version | Status | Exit Code |
|---|---|---|
Python 3.9.24+ (heads/3.9:9c4638d, Oct 17 2025, 11:19:30) |
ASAN | 1 |
Python 3.10.19+ (heads/3.10:0142619, Oct 17 2025, 11:20:05) [GCC 13.3.0] |
ASAN | 1 |
Python 3.11.14+ (heads/3.11:88f3f5b, Oct 17 2025, 11:20:44) [GCC 13.3.0] |
ASAN | 1 |
Python 3.12.12+ (heads/3.12:8cb2092, Oct 17 2025, 11:21:35) [GCC 13.3.0] |
ASAN | 1 |
Python 3.13.9+ (heads/3.13:0760a57, Oct 17 2025, 11:22:25) [GCC 13.3.0] |
ASAN | 1 |
Python 3.14.0+ (heads/3.14:889e918, Oct 17 2025, 11:23:02) [GCC 13.3.0] |
ASAN | 1 |
Python 3.15.0a1+ (heads/main:fbf0843, Oct 17 2025, 11:23:37) [GCC 13.3.0] |
ASAN | 1 |
Sanitizer Output:
Details
=================================================================
==1553427==ERROR: AddressSanitizer: heap-use-after-free on address 0x5080000a5748 at pc 0x63af6ffd3df9 bp 0x7fff69a92e30 sp 0x7fff69a92e20
READ of size 8 at 0x5080000a5748 thread T0
#0 0x63af6ffd3df8 in _Py_TYPE Include/object.h:277
#1 0x63af6ffd3df8 in _PyDict_DelItem_KnownHash_LockHeld Objects/dictobject.c:2829
#2 0x63af6ffd4001 in _PyDict_SetItem_LockHeld Objects/dictobject.c:6914
#3 0x63af6ffd7cc4 in store_instance_attr_dict Objects/dictobject.c:7032
#4 0x63af6ffd7d56 in _PyObject_StoreInstanceAttribute Objects/dictobject.c:7053
#5 0x63af6fffe5e7 in _PyObject_GenericSetAttrWithDict Objects/object.c:1969
#6 0x63af6fffe85e in PyObject_GenericSetAttr Objects/object.c:2031
#7 0x63af6fffac11 in PyObject_SetAttr Objects/object.c:1476
#8 0x63af6fffb35a in PyObject_DelAttr Objects/object.c:1512
#9 0x63af7019c7c6 in builtin_delattr_impl Python/bltinmodule.c:1747
#10 0x63af7019c841 in builtin_delattr Python/clinic/bltinmodule.c.h:716
#11 0x63af6ffed364 in cfunction_vectorcall_FASTCALL Objects/methodobject.c:449
#12 0x63af6ff3ae7f in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
#13 0x63af6ff3af72 in PyObject_Vectorcall Objects/call.c:327
#14 0x63af701b9056 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
#15 0x63af701fce54 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
#16 0x63af701fd148 in _PyEval_Vector Python/ceval.c:2001
#17 0x63af701fd3f8 in PyEval_EvalCode Python/ceval.c:884
#18 0x63af702f4507 in run_eval_code_obj Python/pythonrun.c:1365
#19 0x63af702f4723 in run_mod Python/pythonrun.c:1459
#20 0x63af702f557a in pyrun_file Python/pythonrun.c:1293
#21 0x63af702f8220 in _PyRun_SimpleFileObject Python/pythonrun.c:521
#22 0x63af702f84f6 in _PyRun_AnyFileObject Python/pythonrun.c:81
#23 0x63af7034974d in pymain_run_file_obj Modules/main.c:410
#24 0x63af703499b4 in pymain_run_file Modules/main.c:429
#25 0x63af7034b1b2 in pymain_run_python Modules/main.c:691
#26 0x63af7034b842 in Py_RunMain Modules/main.c:772
#27 0x63af7034ba2e in pymain_main Modules/main.c:802
#28 0x63af7034bdb3 in Py_BytesMain Modules/main.c:826
#29 0x63af6fdcf645 in main Programs/python.c:15
#30 0x73d687c2a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#31 0x73d687c2a28a in __libc_start_main_impl ../csu/libc-start.c:360
#32 0x63af6fdcf574 in _start (/home/jackfromeast/Desktop/entropy/tasks/grammar-afl++-latest/targets/cpython/python+0x2dd574) (BuildId: ff3dc40ea460bd4beb2c3a72283cca525b319bf0)
0x5080000a5748 is located 40 bytes inside of 88-byte region [0x5080000a5720,0x5080000a5778)
freed by thread T0 here:
#0 0x73d6880fc4d8 in free ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:52
#1 0x63af7000196d in _PyMem_RawFree Objects/obmalloc.c:91
#2 0x63af70003cd9 in _PyMem_DebugRawFree Objects/obmalloc.c:2955
#3 0x63af70003d1a in _PyMem_DebugFree Objects/obmalloc.c:3100
#4 0x63af7002c06c in PyObject_Free Objects/obmalloc.c:1522
#5 0x63af7026acf7 in PyObject_GC_Del Python/gc.c:2435
#6 0x63af6fffa4cc in free_object Objects/object.c:916
#7 0x63af6fff5e1e in clear_freelist Objects/object.c:902
#8 0x63af6fff71ba in _PyObject_ClearFreeLists Objects/object.c:933
#9 0x63af7026af8d in _PyGC_ClearAllFreeLists Python/gc_gil.c:14
#10 0x63af70268da2 in gc_collect_full Python/gc.c:1735
#11 0x63af7026a045 in _PyGC_Collect Python/gc.c:2096
#12 0x63af7034d0f0 in gc_collect_impl Modules/gcmodule.c:93
#13 0x63af7034d268 in gc_collect Modules/clinic/gcmodule.c.h:143
#14 0x63af701bda49 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:2361
#15 0x63af701fce54 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
#16 0x63af701fd148 in _PyEval_Vector Python/ceval.c:2001
#17 0x63af6ff3a9b8 in _PyFunction_Vectorcall Objects/call.c:413
#18 0x63af6ff3ae7f in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
#19 0x63af6ff3b03f in PyObject_CallOneArg Objects/call.c:395
#20 0x63af7004d648 in call_unbound_noarg Objects/typeobject.c:3040
#21 0x63af70068fa0 in maybe_call_special_no_args Objects/typeobject.c:3153
#22 0x63af700695e4 in slot_tp_hash Objects/typeobject.c:10564
#23 0x63af6fff73d6 in PyObject_Hash Objects/object.c:1157
#24 0x63af6ffc4dda in _PyObject_HashFast Include/internal/pycore_object.h:872
#25 0x63af6ffd3fed in _PyDict_SetItem_LockHeld Objects/dictobject.c:6909
#26 0x63af6ffd7cc4 in store_instance_attr_dict Objects/dictobject.c:7032
#27 0x63af6ffd7d56 in _PyObject_StoreInstanceAttribute Objects/dictobject.c:7053
#28 0x63af6fffe5e7 in _PyObject_GenericSetAttrWithDict Objects/object.c:1969
#29 0x63af6fffe85e in PyObject_GenericSetAttr Objects/object.c:2031
previously allocated by thread T0 here:
#0 0x73d6880fd9c7 in malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69
#1 0x63af70002284 in _PyMem_RawMalloc Objects/obmalloc.c:63
#2 0x63af70001655 in _PyMem_DebugRawAlloc Objects/obmalloc.c:2887
#3 0x63af700016bd in _PyMem_DebugRawMalloc Objects/obmalloc.c:2920
#4 0x63af70002f3b in _PyMem_DebugMalloc Objects/obmalloc.c:3085
#5 0x63af7002bf28 in PyObject_Malloc Objects/obmalloc.c:1493
#6 0x63af7026a734 in _PyObject_MallocWithType Include/internal/pycore_object_alloc.h:46
#7 0x63af7026a734 in gc_alloc Python/gc.c:2327
#8 0x63af7026a88a in _PyObject_GC_New Python/gc.c:2347
#9 0x63af6ffc8cad in new_dict Objects/dictobject.c:875
#10 0x63af6ffca396 in PyDict_New Objects/dictobject.c:973
#11 0x63af6ffca3e5 in dict_new_presized Objects/dictobject.c:2203
#12 0x63af6ffd2877 in _PyDict_FromItems Objects/dictobject.c:2244
#13 0x63af701b6e63 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1272
#14 0x63af701fce54 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
#15 0x63af701fd148 in _PyEval_Vector Python/ceval.c:2001
#16 0x63af6ff3a9b8 in _PyFunction_Vectorcall Objects/call.c:413
#17 0x63af6ff3ae7f in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
#18 0x63af6ff3cbc0 in object_vacall Objects/call.c:819
#19 0x63af6ff3ce3e in PyObject_CallMethodObjArgs Objects/call.c:886
#20 0x63af70289cc9 in import_find_and_load Python/import.c:3701
#21 0x63af70290c01 in PyImport_ImportModuleLevelObject Python/import.c:3783
#22 0x63af701ac4a3 in _PyEval_ImportName Python/ceval.c:3017
#23 0x63af701d51b0 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:6219
#24 0x63af701fce54 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
#25 0x63af701fd148 in _PyEval_Vector Python/ceval.c:2001
#26 0x63af701fd3f8 in PyEval_EvalCode Python/ceval.c:884
#27 0x63af7019b94a in builtin_exec_impl Python/bltinmodule.c:1180
#28 0x63af7019bc4c in builtin_exec Python/clinic/bltinmodule.c.h:571
#29 0x63af6ffed123 in cfunction_vectorcall_FASTCALL_KEYWORDS Objects/methodobject.c:465
#30 0x63af6ff3e1d2 in _PyVectorcall_Call Objects/call.c:273
SUMMARY: AddressSanitizer: heap-use-after-free Include/object.h:277 in _Py_TYPE
Shadow bytes around the buggy address:
0x5080000a5480: fa fa fa fa 00 00 00 00 00 00 00 00 00 00 00 fa
0x5080000a5500: fa fa fa fa 00 00 00 00 00 00 00 00 00 00 00 fa
0x5080000a5580: fa fa fa fa 00 00 00 00 00 00 00 00 00 00 00 fa
0x5080000a5600: fa fa fa fa 00 00 00 00 00 00 00 00 00 00 00 00
0x5080000a5680: fa fa fa fa fd fd fd fd fd fd fd fd fd fd fd fa
=>0x5080000a5700: fa fa fa fa fd fd fd fd fd[fd]fd fd fd fd fd fa
0x5080000a5780: fa fa fa fa fd fd fd fd fd fd fd fd fd fd fd fa
0x5080000a5800: fa fa fa fa fd fd fd fd fd fd fd fd fd fd fd fa
0x5080000a5880: fa fa fa fa fd fd fd fd fd fd fd fd fd fd fd fa
0x5080000a5900: fa fa fa fa fd fd fd fd fd fd fd fd fd fd fd fa
0x5080000a5980: fa fa fa fa 00 00 00 00 00 00 00 00 00 00 02 fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==1553427==ABORTING
Metadata
Metadata
Assignees
Labels
interpreter-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