Crash report
What happened?
POC
from collections import OrderedDict
eq_count = 0
class CollidingKey:
"""Key with fixed hash to cause collisions in od_copy."""
def __init__(self, name):
self.name = name
def __hash__(self):
return 42 # All keys collide
def __eq__(self, other):
global eq_count
eq_count += 1
# Fire on 4th call (during od_copy's probe into od_copy, not during od lookup)
if eq_count == 4:
# Trigger from inside SetItem's probe — this frees all nodes in od
od.clear()
return self.name == getattr(other, 'name', None)
od = OrderedDict()
od[CollidingKey('k1')] = 'v1'
od[CollidingKey('k2')] = 'v2'
# ASan should report heap-use-after-free at Objects/odictobject.c:1254
od.copy()
Analysis
_odict_FOREACH(od, node) {
PyObject *key = _odictnode_KEY(node);
PyObject *value = _odictnode_VALUE(node, od);
if (value == NULL) {
if (!PyErr_Occurred())
PyErr_SetObject(PyExc_KeyError, key);
goto fail;
}
if (_PyODict_SetItem_KnownHash_LockHeld((PyObject *)od_copy, key, value,
_odictnode_HASH(node)) != 0)
goto fail;
}
The _PyODict_SetItem_KnownHash_LockHeld fire user-defined python code, in which the __eq__ trigger od.clear() and clear all container being iterated. Thus when returning to the _odict_FOREACH(od, node) loop, next iteration use an freed object.
ASan
=================================================================
==2330421==ERROR: AddressSanitizer: heap-use-after-free on address 0x50600006bec0 at pc 0x5a4039bee5e1 bp 0x7ffd2ee588c0 sp 0x7ffd2ee588b0
READ of size 8 at 0x50600006bec0 thread T0
#0 0x5a4039bee5e0 in OrderedDict_copy_impl Objects/odictobject.c:1256
#1 0x5a4039bee691 in OrderedDict_copy Objects/clinic/odictobject.c.h:377
#2 0x5a4039b6b88a in method_vectorcall_NOARGS Objects/descrobject.c:448
#3 0x5a4039b4be65 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:177
#4 0x5a4039b4bf58 in PyObject_Vectorcall Objects/call.c:327
#5 0x5a4039dc0b15 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1621
#6 0x5a4039e078fa in _PyEval_EvalFrame Include/internal/pycore_ceval.h:120
#7 0x5a4039e07bee in _PyEval_Vector Python/ceval.c:2110
#8 0x5a4039e07e9e in PyEval_EvalCode Python/ceval.c:982
#9 0x5a4039efcea8 in run_eval_code_obj Python/pythonrun.c:1366
#10 0x5a4039efd0c4 in run_mod Python/pythonrun.c:1460
#11 0x5a4039efdf1b in pyrun_file Python/pythonrun.c:1294
#12 0x5a4039f00be2 in _PyRun_SimpleFileObject Python/pythonrun.c:521
#13 0x5a4039f00eb8 in _PyRun_AnyFileObject Python/pythonrun.c:81
#14 0x5a4039f5a631 in pymain_run_file_obj Modules/main.c:410
#15 0x5a4039f5a898 in pymain_run_file Modules/main.c:429
#16 0x5a4039f5bfa6 in pymain_run_python Modules/main.c:695
#17 0x5a4039f5c636 in Py_RunMain Modules/main.c:776
#18 0x5a4039f5c822 in pymain_main Modules/main.c:806
#19 0x5a4039f5cba7 in Py_BytesMain Modules/main.c:830
#20 0x5a40399d9645 in main Programs/python.c:15
#21 0x7faa6102a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#22 0x7faa6102a28a in __libc_start_main_impl ../csu/libc-start.c:360
#23 0x5a40399d9574 in _start (/home/kdsj/workspace/project-crosslang/benchmark/cpython/build/v3.14.4-asan/bin/python3.14+0x2c4574) (BuildId: e918d56abcb4f55a10536eb34bee4266005eb07f)
0x50600006bec0 is located 32 bytes inside of 56-byte region [0x50600006bea0,0x50600006bed8)
freed by thread T0 here:
#0 0x7faa614fc4d8 in free ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:52
#1 0x5a4039c13399 in _PyMem_RawFree Objects/obmalloc.c:91
#2 0x5a4039c15705 in _PyMem_DebugRawFree Objects/obmalloc.c:2959
#3 0x5a4039c15746 in _PyMem_DebugFree Objects/obmalloc.c:3104
#4 0x5a4039c3cd7f in PyMem_Free Objects/obmalloc.c:1089
#5 0x5a4039bea7f2 in _odict_clear_nodes Objects/odictobject.c:810
#6 0x5a4039bec36d in OrderedDict_clear_impl Objects/odictobject.c:1227
#7 0x5a4039bec387 in OrderedDict_clear Objects/clinic/odictobject.c.h:353
#8 0x5a4039b6b88a in method_vectorcall_NOARGS Objects/descrobject.c:448
#9 0x5a4039b4be65 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:177
#10 0x5a4039b4bf58 in PyObject_Vectorcall Objects/call.c:327
#11 0x5a4039dc0b15 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1621
#12 0x5a4039e078fa in _PyEval_EvalFrame Include/internal/pycore_ceval.h:120
#13 0x5a4039e07bee in _PyEval_Vector Python/ceval.c:2110
#14 0x5a4039b4b99e in _PyFunction_Vectorcall Objects/call.c:413
#15 0x5a4039c5fad5 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:177
#16 0x5a4039c7b6c5 in vectorcall_unbound Objects/typeobject.c:2957
#17 0x5a4039c7b6c5 in maybe_call_special_one_arg Objects/typeobject.c:3099
#18 0x5a4039c7b7f2 in _PyObject_MaybeCallSpecialOneArg Objects/typeobject.c:3114
#19 0x5a4039c7b838 in slot_tp_richcompare Objects/typeobject.c:10456
#20 0x5a4039c0bf3b in do_richcompare Objects/object.c:1060
#21 0x5a4039c0c1f1 in PyObject_RichCompare Objects/object.c:1109
#22 0x5a4039c0c261 in PyObject_RichCompareBool Objects/object.c:1131
#23 0x5a4039bd584d in compare_generic Objects/dictobject.c:1113
#24 0x5a4039bd22fe in do_lookup Objects/dictobject.c:1013
#25 0x5a4039bd2399 in dictkeys_generic_lookup Objects/dictobject.c:1132
#26 0x5a4039bdbc0c in _Py_dict_lookup Objects/dictobject.c:1298
#27 0x5a4039be341d in insertdict Objects/dictobject.c:1910
#28 0x5a4039be441d in _PyDict_SetItem_KnownHash_LockHeld Objects/dictobject.c:2750
#29 0x5a4039bec2c2 in _PyODict_SetItem_KnownHash_LockHeld Objects/odictobject.c:1626
#30 0x5a4039bee5bf in OrderedDict_copy_impl Objects/odictobject.c:1264
previously allocated by thread T0 here:
#0 0x7faa614fd9c7 in malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69
#1 0x5a4039c13cb0 in _PyMem_RawMalloc Objects/obmalloc.c:63
#2 0x5a4039c13081 in _PyMem_DebugRawAlloc Objects/obmalloc.c:2891
#3 0x5a4039c130e9 in _PyMem_DebugRawMalloc Objects/obmalloc.c:2924
#4 0x5a4039c14967 in _PyMem_DebugMalloc Objects/obmalloc.c:3089
#5 0x5a4039c3cc3b in PyMem_Malloc Objects/obmalloc.c:1060
#6 0x5a4039beb685 in _odict_add_new_node Objects/odictobject.c:705
#7 0x5a4039bec2e7 in _PyODict_SetItem_KnownHash_LockHeld Objects/odictobject.c:1628
#8 0x5a4039bec342 in PyODict_SetItem_LockHeld Objects/odictobject.c:1648
#9 0x5a4039bee6a0 in PyODict_SetItem Objects/odictobject.c:1656
#10 0x5a4039bee729 in odict_mp_ass_sub Objects/odictobject.c:879
#11 0x5a4039b1a55a in PyObject_SetItem Objects/abstract.c:235
#12 0x5a4039e00aaf in _PyEval_EvalFrameDefault Python/generated_cases.c.h:11487
#13 0x5a4039e078fa in _PyEval_EvalFrame Include/internal/pycore_ceval.h:120
#14 0x5a4039e07bee in _PyEval_Vector Python/ceval.c:2110
#15 0x5a4039e07e9e in PyEval_EvalCode Python/ceval.c:982
#16 0x5a4039efcea8 in run_eval_code_obj Python/pythonrun.c:1366
#17 0x5a4039efd0c4 in run_mod Python/pythonrun.c:1460
#18 0x5a4039efdf1b in pyrun_file Python/pythonrun.c:1294
#19 0x5a4039f00be2 in _PyRun_SimpleFileObject Python/pythonrun.c:521
#20 0x5a4039f00eb8 in _PyRun_AnyFileObject Python/pythonrun.c:81
#21 0x5a4039f5a631 in pymain_run_file_obj Modules/main.c:410
#22 0x5a4039f5a898 in pymain_run_file Modules/main.c:429
#23 0x5a4039f5bfa6 in pymain_run_python Modules/main.c:695
#24 0x5a4039f5c636 in Py_RunMain Modules/main.c:776
#25 0x5a4039f5c822 in pymain_main Modules/main.c:806
#26 0x5a4039f5cba7 in Py_BytesMain Modules/main.c:830
#27 0x5a40399d9645 in main Programs/python.c:15
#28 0x7faa6102a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#29 0x7faa6102a28a in __libc_start_main_impl ../csu/libc-start.c:360
SUMMARY: AddressSanitizer: heap-use-after-free Objects/odictobject.c:1256 in OrderedDict_copy_impl
Shadow bytes around the buggy address:
0x50600006bc00: 00 00 00 00 00 00 00 fa fa fa fa fa fd fd fd fd
0x50600006bc80: fd fd fd fd fa fa fa fa fd fd fd fd fd fd fd fd
0x50600006bd00: fa fa fa fa fd fd fd fd fd fd fd fa fa fa fa fa
0x50600006bd80: fd fd fd fd fd fd fd fd fa fa fa fa 00 00 00 00
0x50600006be00: 00 00 00 fa fa fa fa fa fd fd fd fd fd fd fd fa
=>0x50600006be80: fa fa fa fa fd fd fd fd[fd]fd fd fa fa fa fa fa
0x50600006bf00: 00 00 00 00 00 00 00 fa fa fa fa fa 00 00 00 00
0x50600006bf80: 00 00 00 fa fa fa fa fa fa fa fa fa fa fa fa fa
0x50600006c000: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x50600006c080: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x50600006c100: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 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
==2330421==ABORTING
```
### CPython versions tested on:
3.15
### Operating systems tested on:
Linux
### Output from running 'python -VV' on the command line:
Python 3.15.0a8 (tags/v3.15.0a8:55ea59e7dc3, Apr 16 2026, 21:14:44) [GCC 13.3.0]
Crash report
What happened?
POC
Analysis
The
_PyODict_SetItem_KnownHash_LockHeldfire user-defined python code, in which the__eq__trigger od.clear() and clear all container being iterated. Thus when returning to the_odict_FOREACH(od, node)loop, next iteration use an freed object.ASan