Crash report
getattr() can retain a pointer to an object's original heap type while hashing or comparing a str subclass used as the attribute name. The callback can assign a compatible replacement to obj.__class__ and collect the original type. Attribute lookup then resumes with the freed PyTypeObject *.
I can reproduce two paths on current main at 0ec3aee262b03276a18aeb23cb9957e1b57c9d08 with a release-style AddressSanitizer build. Both are single-threaded.
Reproducer 1: re-entrant __hash__
import gc
class Replacement:
pass
class Hashy(str):
target = None
def __hash__(self):
Hashy.target.__class__ = Replacement
gc.collect()
return super().__hash__()
def build():
class Victim:
pass
return Victim()
Hashy.target = build()
getattr(Hashy.target, Hashy("boom"))
This reaches a heap-use-after-free in lookup_tp_mro() from _PyType_LookupStackRefAndVersion().
Complete AddressSanitizer report for reproducer 1
=================================================================
==1==ERROR: AddressSanitizer: heap-use-after-free on address 0x519000039ee8 at pc 0xaaaabf49fd68 bp 0xffffe3597380 sp 0xffffe3597370
READ of size 8 at 0x519000039ee8 thread T0
#0 0xaaaabf49fd64 in lookup_tp_mro Objects/typeobject.c:650
#1 0xaaaabf49fd64 in find_name_in_mro Objects/typeobject.c:6125
#2 0xaaaabf4a132c in _PyType_LookupStackRefAndVersion Objects/typeobject.c:6255
#3 0xaaaabf411bd0 in _PyObject_GenericGetAttrWithDict Objects/object.c:1920
#4 0xaaaabf4114f4 in PyObject_GetAttr Objects/object.c:1323
#5 0xaaaabf5e04e4 in builtin_getattr Python/bltinmodule.c:1335
#6 0xaaaabf308fec in _PyObject_VectorcallTstate Include/internal/pycore_call.h:143
#7 0xaaaabf308fec in PyObject_Vectorcall Objects/call.c:327
#8 0xaaaabf5f1e94 in _Py_VectorCallInstrumentation_StackRefSteal Python/ceval.c:770
#9 0xaaaabf1cbcbc in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1906
#10 0xaaaabf5fef0c in _PyEval_EvalFrame Include/internal/pycore_ceval.h:122
#11 0xaaaabf5fef0c in _PyEval_Vector Python/ceval.c:2176
#12 0xaaaabf5fef0c in PyEval_EvalCode Python/ceval.c:681
#13 0xaaaabf72064c in run_mod Python/pythonrun.c:1509
#14 0xaaaabf722aac in _PyRun_File Python/pythonrun.c:1332
#15 0xaaaabf722aac in _PyRun_SimpleFile Python/pythonrun.c:544
#16 0xaaaabf724d30 in _PyRun_AnyFile Python/pythonrun.c:92
#17 0xaaaabf7923e0 in pymain_run_file_obj Modules/main.c:478
#18 0xaaaabf7923e0 in pymain_run_file Modules/main.c:494
#19 0xaaaabf7923e0 in pymain_run_python Modules/main.c:812
#20 0xaaaabf7923e0 in Py_RunMain Modules/main.c:900
#21 0xaaaabf793284 in pymain_main Modules/main.c:927
#22 0xaaaabf793284 in Py_BytesMain Modules/main.c:951
#23 0xffffab8684c0 (/lib/aarch64-linux-gnu/libc.so.6+0x284c0) (BuildId: 27027b96e5b8c475fc327aa445bea1c71d37b4e2)
#24 0xffffab868594 in __libc_start_main (/lib/aarch64-linux-gnu/libc.so.6+0x28594) (BuildId: 27027b96e5b8c475fc327aa445bea1c71d37b4e2)
#25 0xaaaabf1ed5ac in _start (/src/cpython/python+0x20d5ac) (BuildId: c7b91d0d6fc70a572b6ef078849822c6fb3085ae)
0x519000039ee8 is located 360 bytes inside of 1008-byte region [0x519000039d80,0x51900003a170)
freed by thread T0 here:
#0 0xffffabae61b4 in free ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:52
#1 0xaaaabf40b1e0 in py_dealloc Objects/object.c:3346
#2 0xaaaabf67e5f4 in Py_DECREF Include/refcount.h:427
#3 0xaaaabf67e5f4 in delete_garbage Python/gc.c:1110
#4 0xaaaabf67e5f4 in gc_collect_main Python/gc.c:1587
#5 0xaaaabf794570 in gc_collect_impl Modules/gcmodule.c:93
#6 0xaaaabf794570 in gc_collect Modules/clinic/gcmodule.c.h:143
#7 0xaaaabf308fec in _PyObject_VectorcallTstate Include/internal/pycore_call.h:143
#8 0xaaaabf308fec in PyObject_Vectorcall Objects/call.c:327
#9 0xaaaabf5f1e94 in _Py_VectorCallInstrumentation_StackRefSteal Python/ceval.c:770
#10 0xaaaabf1cbcbc in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1906
#11 0xaaaabf5ff694 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:122
#12 0xaaaabf5ff694 in _PyEval_Vector Python/ceval.c:2176
#13 0xaaaabf3092d4 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:143
#14 0xaaaabf3092d4 in PyObject_CallOneArg Objects/call.c:395
#15 0xaaaabf4aadc4 in call_unbound_noarg Objects/typeobject.c:3039
#16 0xaaaabf4aadc4 in maybe_call_special_no_args Objects/typeobject.c:3152
#17 0xaaaabf4aadc4 in slot_tp_hash Objects/typeobject.c:10846
#18 0xaaaabf49fb24 in _PyObject_HashDictKey Include/internal/pycore_object.h:846
#19 0xaaaabf49fb24 in find_name_in_mro Objects/typeobject.c:6118
#20 0xaaaabf4a132c in _PyType_LookupStackRefAndVersion Objects/typeobject.c:6255
#21 0xaaaabf411bd0 in _PyObject_GenericGetAttrWithDict Objects/object.c:1920
#22 0xaaaabf4114f4 in PyObject_GetAttr Objects/object.c:1323
#23 0xaaaabf5e04e4 in builtin_getattr Python/bltinmodule.c:1335
#24 0xaaaabf308fec in _PyObject_VectorcallTstate Include/internal/pycore_call.h:143
#25 0xaaaabf308fec in PyObject_Vectorcall Objects/call.c:327
#26 0xaaaabf5f1e94 in _Py_VectorCallInstrumentation_StackRefSteal Python/ceval.c:770
#27 0xaaaabf1cbcbc in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1906
#28 0xaaaabf5fef0c in _PyEval_EvalFrame Include/internal/pycore_ceval.h:122
#29 0xaaaabf5fef0c in _PyEval_Vector Python/ceval.c:2176
#30 0xaaaabf5fef0c in PyEval_EvalCode Python/ceval.c:681
#31 0xaaaabf72064c in run_mod Python/pythonrun.c:1509
#32 0xaaaabf722aac in _PyRun_File Python/pythonrun.c:1332
#33 0xaaaabf722aac in _PyRun_SimpleFile Python/pythonrun.c:544
#34 0xaaaabf724d30 in _PyRun_AnyFile Python/pythonrun.c:92
#35 0xaaaabf7923e0 in pymain_run_file_obj Modules/main.c:478
#36 0xaaaabf7923e0 in pymain_run_file Modules/main.c:494
#37 0xaaaabf7923e0 in pymain_run_python Modules/main.c:812
#38 0xaaaabf7923e0 in Py_RunMain Modules/main.c:900
#39 0xaaaabf793284 in pymain_main Modules/main.c:927
#40 0xaaaabf793284 in Py_BytesMain Modules/main.c:951
#41 0xffffab8684c0 (/lib/aarch64-linux-gnu/libc.so.6+0x284c0) (BuildId: 27027b96e5b8c475fc327aa445bea1c71d37b4e2)
#42 0xffffab868594 in __libc_start_main (/lib/aarch64-linux-gnu/libc.so.6+0x28594) (BuildId: 27027b96e5b8c475fc327aa445bea1c71d37b4e2)
#43 0xaaaabf1ed5ac in _start (/src/cpython/python+0x20d5ac) (BuildId: c7b91d0d6fc70a572b6ef078849822c6fb3085ae)
previously allocated by thread T0 here:
#0 0xffffabae76d0 in malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69
#1 0xaaaabf48cdbc in _PyObject_MallocWithType Include/internal/pycore_object_alloc.h:46
#2 0xaaaabf48cdbc in _PyType_AllocNoTrack Objects/typeobject.c:2498
#3 0xaaaabf48d000 in PyType_GenericAlloc Objects/typeobject.c:2529
#4 0xaaaabf4ac29c in type_new_alloc Objects/typeobject.c:4396
#5 0xaaaabf4ac29c in type_new_init Objects/typeobject.c:4897
#6 0xaaaabf4ac29c in type_new_impl Objects/typeobject.c:4926
#7 0xaaaabf4ac29c in type_new Objects/typeobject.c:5089
#8 0xaaaabf485950 in type_call Objects/typeobject.c:2442
#9 0xaaaabf307a64 in _PyObject_MakeTpCall Objects/call.c:242
#10 0xaaaabf30de98 in _PyObject_VectorcallDictTstate Objects/call.c:135
#11 0xaaaabf30de98 in PyObject_VectorcallDict Objects/call.c:159
#12 0xaaaabf5ea340 in builtin___build_class__ Python/bltinmodule.c:215
#13 0xaaaabf308fec in _PyObject_VectorcallTstate Include/internal/pycore_call.h:143
#14 0xaaaabf308fec in PyObject_Vectorcall Objects/call.c:327
#15 0xaaaabf5f1e94 in _Py_VectorCallInstrumentation_StackRefSteal Python/ceval.c:770
#16 0xaaaabf1cbcbc in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1906
#17 0xaaaabf5fef0c in _PyEval_EvalFrame Include/internal/pycore_ceval.h:122
#18 0xaaaabf5fef0c in _PyEval_Vector Python/ceval.c:2176
#19 0xaaaabf5fef0c in PyEval_EvalCode Python/ceval.c:681
#20 0xaaaabf72064c in run_mod Python/pythonrun.c:1509
#21 0xaaaabf722aac in _PyRun_File Python/pythonrun.c:1332
#22 0xaaaabf722aac in _PyRun_SimpleFile Python/pythonrun.c:544
#23 0xaaaabf724d30 in _PyRun_AnyFile Python/pythonrun.c:92
#24 0xaaaabf7923e0 in pymain_run_file_obj Modules/main.c:478
#25 0xaaaabf7923e0 in pymain_run_file Modules/main.c:494
#26 0xaaaabf7923e0 in pymain_run_python Modules/main.c:812
#27 0xaaaabf7923e0 in Py_RunMain Modules/main.c:900
#28 0xaaaabf793284 in pymain_main Modules/main.c:927
#29 0xaaaabf793284 in Py_BytesMain Modules/main.c:951
#30 0xffffab8684c0 (/lib/aarch64-linux-gnu/libc.so.6+0x284c0) (BuildId: 27027b96e5b8c475fc327aa445bea1c71d37b4e2)
#31 0xffffab868594 in __libc_start_main (/lib/aarch64-linux-gnu/libc.so.6+0x28594) (BuildId: 27027b96e5b8c475fc327aa445bea1c71d37b4e2)
#32 0xaaaabf1ed5ac in _start (/src/cpython/python+0x20d5ac) (BuildId: c7b91d0d6fc70a572b6ef078849822c6fb3085ae)
SUMMARY: AddressSanitizer: heap-use-after-free Objects/typeobject.c:650 in lookup_tp_mro
Shadow bytes around the buggy address:
0x519000039c00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 fa fa
0x519000039c80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x519000039d00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x519000039d80: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x519000039e00: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
=>0x519000039e80: fd fd fd fd fd fd fd fd fd fd fd fd fd[fd]fd fd
0x519000039f00: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x519000039f80: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x51900003a000: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x51900003a080: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x51900003a100: fd fd fd fd fd fd fd fd fd fd fd fd fd fd 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
==1==ABORTING
Reproducer 2: re-entrant __eq__
import gc
New = type("New", (), {})
Old = type("Old", (), {})
obj = Old()
obj.pad = None
class Key(str):
armed = True
victim = obj
new = New
old = Old
hash_value = hash("pad")
def __hash__(self):
return Key.hash_value
def __eq__(self, other):
if Key.armed:
Key.armed = False
Key.victim.__class__ = Key.new
Key.old = None
gc.collect()
return False
del Old
getattr(obj, Key("missing"))
This reaches a heap-use-after-free in _PyObject_GenericGetAttrWithDict() after dictionary comparison returns.
Complete AddressSanitizer report for reproducer 2
=================================================================
==1==ERROR: AddressSanitizer: heap-use-after-free on address 0x519000038ea8 at pc 0xaaaad00b2540 bp 0xffffcaaf0af0 sp 0xffffcaaf0ae0
READ of size 8 at 0x519000038ea8 thread T0
#0 0xaaaad00b253c in _PyObject_GenericGetAttrWithDict Objects/object.c:2011
#1 0xaaaad00b14f4 in PyObject_GetAttr Objects/object.c:1323
#2 0xaaaad02804e4 in builtin_getattr Python/bltinmodule.c:1335
#3 0xaaaacffa8fec in _PyObject_VectorcallTstate Include/internal/pycore_call.h:143
#4 0xaaaacffa8fec in PyObject_Vectorcall Objects/call.c:327
#5 0xaaaad0291e94 in _Py_VectorCallInstrumentation_StackRefSteal Python/ceval.c:770
#6 0xaaaacfe6bcbc in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1906
#7 0xaaaad029ef0c in _PyEval_EvalFrame Include/internal/pycore_ceval.h:122
#8 0xaaaad029ef0c in _PyEval_Vector Python/ceval.c:2176
#9 0xaaaad029ef0c in PyEval_EvalCode Python/ceval.c:681
#10 0xaaaad03c064c in run_mod Python/pythonrun.c:1509
#11 0xaaaad03c2aac in _PyRun_File Python/pythonrun.c:1332
#12 0xaaaad03c2aac in _PyRun_SimpleFile Python/pythonrun.c:544
#13 0xaaaad03c4d30 in _PyRun_AnyFile Python/pythonrun.c:92
#14 0xaaaad04323e0 in pymain_run_file_obj Modules/main.c:478
#15 0xaaaad04323e0 in pymain_run_file Modules/main.c:494
#16 0xaaaad04323e0 in pymain_run_python Modules/main.c:812
#17 0xaaaad04323e0 in Py_RunMain Modules/main.c:900
#18 0xaaaad0433284 in pymain_main Modules/main.c:927
#19 0xaaaad0433284 in Py_BytesMain Modules/main.c:951
#20 0xffffbd0684c0 (/lib/aarch64-linux-gnu/libc.so.6+0x284c0) (BuildId: 27027b96e5b8c475fc327aa445bea1c71d37b4e2)
#21 0xffffbd068594 in __libc_start_main (/lib/aarch64-linux-gnu/libc.so.6+0x28594) (BuildId: 27027b96e5b8c475fc327aa445bea1c71d37b4e2)
#22 0xaaaacfe8d5ac in _start (/src/cpython/python+0x20d5ac) (BuildId: c7b91d0d6fc70a572b6ef078849822c6fb3085ae)
0x519000038ea8 is located 40 bytes inside of 1008-byte region [0x519000038e80,0x519000039270)
freed by thread T0 here:
#0 0xffffbd2e61b4 in free ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:52
#1 0xaaaad00ab1e0 in py_dealloc Objects/object.c:3346
#2 0xaaaad031e5f4 in Py_DECREF Include/refcount.h:427
#3 0xaaaad031e5f4 in delete_garbage Python/gc.c:1110
#4 0xaaaad031e5f4 in gc_collect_main Python/gc.c:1587
#5 0xaaaad0434570 in gc_collect_impl Modules/gcmodule.c:93
#6 0xaaaad0434570 in gc_collect Modules/clinic/gcmodule.c.h:143
#7 0xaaaacffa8fec in _PyObject_VectorcallTstate Include/internal/pycore_call.h:143
#8 0xaaaacffa8fec in PyObject_Vectorcall Objects/call.c:327
#9 0xaaaad0291e94 in _Py_VectorCallInstrumentation_StackRefSteal Python/ceval.c:770
#10 0xaaaacfe6bcbc in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1906
#11 0xaaaad029f694 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:122
#12 0xaaaad029f694 in _PyEval_Vector Python/ceval.c:2176
#13 0xaaaad0148eec in _PyObject_VectorcallTstate Include/internal/pycore_call.h:143
#14 0xaaaad0148eec in vectorcall_unbound Objects/typeobject.c:3032
#15 0xaaaad0148eec in maybe_call_special_one_arg Objects/typeobject.c:3174
#16 0xaaaad0148eec in _PyObject_MaybeCallSpecialOneArg Objects/typeobject.c:3189
#17 0xaaaad0148eec in slot_tp_richcompare Objects/typeobject.c:11012
#18 0xaaaad00af36c in do_richcompare Objects/object.c:1059
#19 0xaaaad00af36c in PyObject_RichCompare Objects/object.c:1114
#20 0xaaaad00af36c in PyObject_RichCompareBool Objects/object.c:1136
#21 0xaaaad0062dbc in compare_unicode_generic Objects/dictobject.c:1172
#22 0xaaaad0062dbc in do_lookup Objects/dictobject.c:1128
#23 0xaaaad0062dbc in unicodekeys_lookup_generic Objects/dictobject.c:1192
#24 0xaaaad0062dbc in _Py_dict_lookup Objects/dictobject.c:1391
#25 0xaaaad0066818 in _PyDict_GetItemRef_KnownHash_LockHeld Objects/dictobject.c:2571
#26 0xaaaad0066818 in PyDict_GetItemRef Objects/dictobject.c:2631
#27 0xaaaad00b1c78 in _PyObject_GenericGetAttrWithDict Objects/object.c:1980
#28 0xaaaad00b14f4 in PyObject_GetAttr Objects/object.c:1323
#29 0xaaaad02804e4 in builtin_getattr Python/bltinmodule.c:1335
#30 0xaaaacffa8fec in _PyObject_VectorcallTstate Include/internal/pycore_call.h:143
#31 0xaaaacffa8fec in PyObject_Vectorcall Objects/call.c:327
#32 0xaaaad0291e94 in _Py_VectorCallInstrumentation_StackRefSteal Python/ceval.c:770
#33 0xaaaacfe6bcbc in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1906
#34 0xaaaad029ef0c in _PyEval_EvalFrame Include/internal/pycore_ceval.h:122
#35 0xaaaad029ef0c in _PyEval_Vector Python/ceval.c:2176
#36 0xaaaad029ef0c in PyEval_EvalCode Python/ceval.c:681
#37 0xaaaad03c064c in run_mod Python/pythonrun.c:1509
#38 0xaaaad03c2aac in _PyRun_File Python/pythonrun.c:1332
#39 0xaaaad03c2aac in _PyRun_SimpleFile Python/pythonrun.c:544
#40 0xaaaad03c4d30 in _PyRun_AnyFile Python/pythonrun.c:92
#41 0xaaaad04323e0 in pymain_run_file_obj Modules/main.c:478
#42 0xaaaad04323e0 in pymain_run_file Modules/main.c:494
#43 0xaaaad04323e0 in pymain_run_python Modules/main.c:812
#44 0xaaaad04323e0 in Py_RunMain Modules/main.c:900
#45 0xaaaad0433284 in pymain_main Modules/main.c:927
#46 0xaaaad0433284 in Py_BytesMain Modules/main.c:951
#47 0xffffbd0684c0 (/lib/aarch64-linux-gnu/libc.so.6+0x284c0) (BuildId: 27027b96e5b8c475fc327aa445bea1c71d37b4e2)
#48 0xffffbd068594 in __libc_start_main (/lib/aarch64-linux-gnu/libc.so.6+0x28594) (BuildId: 27027b96e5b8c475fc327aa445bea1c71d37b4e2)
#49 0xaaaacfe8d5ac in _start (/src/cpython/python+0x20d5ac) (BuildId: c7b91d0d6fc70a572b6ef078849822c6fb3085ae)
previously allocated by thread T0 here:
#0 0xffffbd2e76d0 in malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69
#1 0xaaaad012cdbc in _PyObject_MallocWithType Include/internal/pycore_object_alloc.h:46
#2 0xaaaad012cdbc in _PyType_AllocNoTrack Objects/typeobject.c:2498
#3 0xaaaad012d000 in PyType_GenericAlloc Objects/typeobject.c:2529
#4 0xaaaad014c29c in type_new_alloc Objects/typeobject.c:4396
#5 0xaaaad014c29c in type_new_init Objects/typeobject.c:4897
#6 0xaaaad014c29c in type_new_impl Objects/typeobject.c:4926
#7 0xaaaad014c29c in type_new Objects/typeobject.c:5089
#8 0xaaaad0125950 in type_call Objects/typeobject.c:2442
#9 0xaaaacffa7a64 in _PyObject_MakeTpCall Objects/call.c:242
#10 0xaaaacffa8fec in _PyObject_VectorcallTstate Include/internal/pycore_call.h:143
#11 0xaaaacffa8fec in PyObject_Vectorcall Objects/call.c:327
#12 0xaaaad0291e94 in _Py_VectorCallInstrumentation_StackRefSteal Python/ceval.c:770
#13 0xaaaacfe6bcbc in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1906
#14 0xaaaad029ef0c in _PyEval_EvalFrame Include/internal/pycore_ceval.h:122
#15 0xaaaad029ef0c in _PyEval_Vector Python/ceval.c:2176
#16 0xaaaad029ef0c in PyEval_EvalCode Python/ceval.c:681
#17 0xaaaad03c064c in run_mod Python/pythonrun.c:1509
#18 0xaaaad03c2aac in _PyRun_File Python/pythonrun.c:1332
#19 0xaaaad03c2aac in _PyRun_SimpleFile Python/pythonrun.c:544
#20 0xaaaad03c4d30 in _PyRun_AnyFile Python/pythonrun.c:92
#21 0xaaaad04323e0 in pymain_run_file_obj Modules/main.c:478
#22 0xaaaad04323e0 in pymain_run_file Modules/main.c:494
#23 0xaaaad04323e0 in pymain_run_python Modules/main.c:812
#24 0xaaaad04323e0 in Py_RunMain Modules/main.c:900
#25 0xaaaad0433284 in pymain_main Modules/main.c:927
#26 0xaaaad0433284 in Py_BytesMain Modules/main.c:951
#27 0xffffbd0684c0 (/lib/aarch64-linux-gnu/libc.so.6+0x284c0) (BuildId: 27027b96e5b8c475fc327aa445bea1c71d37b4e2)
#28 0xffffbd068594 in __libc_start_main (/lib/aarch64-linux-gnu/libc.so.6+0x28594) (BuildId: 27027b96e5b8c475fc327aa445bea1c71d37b4e2)
#29 0xaaaacfe8d5ac in _start (/src/cpython/python+0x20d5ac) (BuildId: c7b91d0d6fc70a572b6ef078849822c6fb3085ae)
SUMMARY: AddressSanitizer: heap-use-after-free Objects/object.c:2011 in _PyObject_GenericGetAttrWithDict
Shadow bytes around the buggy address:
0x519000038c00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x519000038c80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x519000038d00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 fa fa
0x519000038d80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x519000038e00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x519000038e80: fd fd fd fd fd[fd]fd fd fd fd fd fd fd fd fd fd
0x519000038f00: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x519000038f80: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x519000039000: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x519000039080: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x519000039100: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
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
==1==ABORTING
The common pattern is that _PyObject_GenericGetAttrWithDict() saves tp = Py_TYPE(obj), calls code that can re-enter Python through the attribute-name object, and later reads tp without keeping the heap type alive. Re-reading Py_TYPE(obj) after the callback would refer to the replacement type, so the intended fix may need to preserve or deliberately refresh the type depending on the lookup semantics.
CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux
Output from running python -VV on the command line:
Python 3.16.0a0 (main, Oct 10 2026, 01:00:06) [GCC 13.3.0]
Build platform: Ubuntu 24.04.5 LTS (aarch64)
Linked PRs
Crash report
getattr()can retain a pointer to an object's original heap type while hashing or comparing astrsubclass used as the attribute name. The callback can assign a compatible replacement toobj.__class__and collect the original type. Attribute lookup then resumes with the freedPyTypeObject *.I can reproduce two paths on current
mainat0ec3aee262b03276a18aeb23cb9957e1b57c9d08with a release-style AddressSanitizer build. Both are single-threaded.Reproducer 1: re-entrant
__hash__This reaches a heap-use-after-free in
lookup_tp_mro()from_PyType_LookupStackRefAndVersion().Complete AddressSanitizer report for reproducer 1
Reproducer 2: re-entrant
__eq__This reaches a heap-use-after-free in
_PyObject_GenericGetAttrWithDict()after dictionary comparison returns.Complete AddressSanitizer report for reproducer 2
The common pattern is that
_PyObject_GenericGetAttrWithDict()savestp = Py_TYPE(obj), calls code that can re-enter Python through the attribute-name object, and later readstpwithout keeping the heap type alive. Re-readingPy_TYPE(obj)after the callback would refer to the replacement type, so the intended fix may need to preserve or deliberately refresh the type depending on the lookup semantics.CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux
Output from running
python -VVon the command line:Python 3.16.0a0 (main, Oct 10 2026, 01:00:06) [GCC 13.3.0]Build platform: Ubuntu 24.04.5 LTS (aarch64)
Linked PRs