Skip to content

Race in type_set_qualname leads to memory corruption under free-threading #150858

Description

@KowalskiThomas

Crash report

What happened?

type_set_qualname (Objects/typeobject.c) updates ht_qualname with Py_SETREF(et->ht_qualname, Py_NewRef(value)) and no synchronisation. Py_SETREF reads the old pointer, stores the new one, then decrefs the old, all three in a non-atomic way.

cpython/Objects/typeobject.c

Lines 1582 to 1599 in c5516e7

type_set_qualname(PyObject *tp, PyObject *value, void *context)
{
PyTypeObject *type = PyTypeObject_CAST(tp);
PyHeapTypeObject* et;
if (!check_set_special_type_attr(type, value, "__qualname__"))
return -1;
if (!PyUnicode_Check(value)) {
PyErr_Format(PyExc_TypeError,
"can only assign string to %s.__qualname__, not '%s'",
type->tp_name, Py_TYPE(value)->tp_name);
return -1;
}
et = (PyHeapTypeObject*)type;
Py_SETREF(et->ht_qualname, Py_NewRef(value));
return 0;
}

If two threads concurrently assign MyClass.__qualname__, they both capture the same old pointer, both store their own new value, and both decref the old one.
A concurrent reader in type_qualname loading the old pointer after the writer's store but before its decref, then calling Py_INCREF after the decref zeroes the refcount would increment the reference count of a freed pymalloc block which is a use after free.

type_set_name has a stop-the-world fence (added in #137302 / #133467) which prevents this; type_set_qualname does not and is currently not protected.


Reproducer

The reproducer is caught as erroring by ASan and TSan, but the segmentation fault does not come from setqualname itself. I believe that is because the memory corruption happens in setqualname but only manifests later when memory is reused.

import threading

class Victim: pass

def _unique(prefix: str, i: int) -> str:
    return prefix + str(i)

def writer_a():
    for i in range(500_000):
        Victim.__qualname__ = _unique("A", i)

def writer_b():
    for i in range(500_000):
        Victim.__qualname__ = _unique("B", i)

def reader():
    for _ in range(500_000):
        _ = Victim.__qualname__

threads = [threading.Thread(target=f) for f in (writer_a, writer_b, reader)]
for t in threads: t.start()
for t in threads: t.join()

Under ASAN, results in...

=================================================================
==33670==ERROR: AddressSanitizer: memcpy-param-overlap: memory ranges [0x0001260e0b39,0x0001260e0b3e) and [0x0001260e0b38, 0x0001260e0b3d) overlap
    #0 0x000103c8d8e4 in __asan_memcpy
    #1 0x0001027858cc in _copy_characters
    #2 0x0001027c15d0 in PyUnicode_Concat
    #3 0x0001028c7824 in _PyEval_EvalFrameDefault
    ...
    #13 0x000102b96214 in thread_run
    #14 0x000102a53bac in pythread_wrapper

Address 0x0001260e0b39 is a wild pointer inside of access range of size 0x000000000005.
SUMMARY: AddressSanitizer: memcpy-param-overlap in _copy_characters
==33670==ABORTING

and with TSan

==================
WARNING: ThreadSanitizer: data race (pid=86130)
  Write of size 8 at 0x000308162258 by thread T2:
    #0 Py_SET_TYPE object.h:207 (python.exe:arm64+0x1001d5024)
    #1 _PyObject_Init pycore_object.h:488 (python.exe:arm64+0x1001d5024)
    #2 PyUnicode_New unicodeobject.c:1330 (python.exe:arm64+0x1001d5024)
    #3 PyUnicode_Concat unicodeobject.c:11337 (python.exe:arm64+0x1001f6da8)
    #4 _PyEval_EvalFrameDefault generated_cases.c.h:292 (python.exe:arm64+0x100292c48)
    #5 _PyEval_EvalFrame pycore_ceval.h:122 (python.exe:arm64+0x10028fe04)
    #6 _PyEval_Vector ceval.c:2134 (python.exe:arm64+0x10028fe04)
    #7 _PyFunction_Vectorcall call.c (python.exe:arm64+0x1000914bc)
    #8 _PyObject_VectorcallTstate pycore_call.h:144 (python.exe:arm64+0x100092c3c)
    #9 _PyObject_VectorcallPrepend call.c:855 (python.exe:arm64+0x100092c3c)
    #10 method_vectorcall classobject.c:55 (python.exe:arm64+0x100095fdc)
    #11 _PyObject_VectorcallTstate pycore_call.h:144 (python.exe:arm64+0x1002dcde0)
    #12 context_run context.c:728 (python.exe:arm64+0x1002dcde0)
    #13 method_vectorcall_FASTCALL_KEYWORDS descrobject.c:421 (python.exe:arm64+0x1000a8644)
    #14 _PyObject_VectorcallTstate pycore_call.h:144 (python.exe:arm64+0x100090e80)
    #15 PyObject_Vectorcall call.c:327 (python.exe:arm64+0x100090e80)
    #16 _Py_VectorCallInstrumentation_StackRefSteal ceval.c:766 (python.exe:arm64+0x100290910)
    #17 _PyEval_EvalFrameDefault generated_cases.c.h:1846 (python.exe:arm64+0x100295fa0)
    #18 _PyEval_EvalFrame pycore_ceval.h:122 (python.exe:arm64+0x10028fe04)
    #19 _PyEval_Vector ceval.c:2134 (python.exe:arm64+0x10028fe04)
    #20 _PyFunction_Vectorcall call.c (python.exe:arm64+0x1000914bc)
    #21 _PyObject_VectorcallTstate pycore_call.h:144 (python.exe:arm64+0x100092c3c)
    #22 _PyObject_VectorcallPrepend call.c:855 (python.exe:arm64+0x100092c3c)
    #23 method_vectorcall classobject.c:55 (python.exe:arm64+0x100095fdc)
    #24 _PyVectorcall_Call call.c:273 (python.exe:arm64+0x10009112c)
    #25 _PyObject_Call call.c:348 (python.exe:arm64+0x10009112c)
    #26 PyObject_Call call.c:373 (python.exe:arm64+0x1000911a4)
    #27 thread_run _threadmodule.c:388 (python.exe:arm64+0x10044b6f4)
    #28 pythread_wrapper thread_pthread.h:234 (python.exe:arm64+0x10038a0d4)

  Previous read of size 8 at 0x000308162258 by thread T3:
    #0 _Py_Dealloc object.c:3285 (python.exe:arm64+0x10013fcb8)
    #1 _Py_DecRefSharedDebug object.c:426 (python.exe:arm64+0x10013ff34)
    #2 _Py_DecRefShared object.c:433 (python.exe:arm64+0x10013ff34)
    #3 Py_DECREF refcount.h:385 (python.exe:arm64+0x1002aa034)
    #4 PyStackRef_XCLOSE pycore_stackref.h:726 (python.exe:arm64+0x1002aa034)
    #5 _PyEval_EvalFrameDefault generated_cases.c.h:11850 (python.exe:arm64+0x1002aa034)
    #6 _PyEval_EvalFrame pycore_ceval.h:122 (python.exe:arm64+0x10028fe04)
    #7 _PyEval_Vector ceval.c:2134 (python.exe:arm64+0x10028fe04)
    #8 _PyFunction_Vectorcall call.c (python.exe:arm64+0x1000914bc)
    #9 _PyObject_VectorcallTstate pycore_call.h:144 (python.exe:arm64+0x100092c3c)
    #10 _PyObject_VectorcallPrepend call.c:855 (python.exe:arm64+0x100092c3c)
    #11 method_vectorcall classobject.c:55 (python.exe:arm64+0x100095fdc)
    #12 _PyObject_VectorcallTstate pycore_call.h:144 (python.exe:arm64+0x1002dcde0)
    #13 context_run context.c:728 (python.exe:arm64+0x1002dcde0)
    #14 method_vectorcall_FASTCALL_KEYWORDS descrobject.c:421 (python.exe:arm64+0x1000a8644)
    #15 _PyObject_VectorcallTstate pycore_call.h:144 (python.exe:arm64+0x100090e80)
    #16 PyObject_Vectorcall call.c:327 (python.exe:arm64+0x100090e80)
    #17 _Py_VectorCallInstrumentation_StackRefSteal ceval.c:766 (python.exe:arm64+0x100290910)
    #18 _PyEval_EvalFrameDefault generated_cases.c.h:1846 (python.exe:arm64+0x100295fa0)
    #19 _PyEval_EvalFrame pycore_ceval.h:122 (python.exe:arm64+0x10028fe04)
    #20 _PyEval_Vector ceval.c:2134 (python.exe:arm64+0x10028fe04)
    #21 _PyFunction_Vectorcall call.c (python.exe:arm64+0x1000914bc)
    #22 _PyObject_VectorcallTstate pycore_call.h:144 (python.exe:arm64+0x100092c3c)
    #23 _PyObject_VectorcallPrepend call.c:855 (python.exe:arm64+0x100092c3c)
    #24 method_vectorcall classobject.c:55 (python.exe:arm64+0x100095fdc)
    #25 _PyVectorcall_Call call.c:273 (python.exe:arm64+0x10009112c)
    #26 _PyObject_Call call.c:348 (python.exe:arm64+0x10009112c)
    #27 PyObject_Call call.c:373 (python.exe:arm64+0x1000911a4)
    #28 thread_run _threadmodule.c:388 (python.exe:arm64+0x10044b6f4)
    #29 pythread_wrapper thread_pthread.h:234 (python.exe:arm64+0x10038a0d4)

  Thread T2 (tid=5631578, running) created by main thread at:
    #0 pthread_create <null> (libclang_rt.tsan_osx_dynamic.dylib:arm64+0x31d84)
    #1 do_start_joinable_thread thread_pthread.h:281 (python.exe:arm64+0x1003892a8)
    #2 PyThread_start_joinable_thread thread_pthread.h:323 (python.exe:arm64+0x1003890e0)
    #3 ThreadHandle_start _threadmodule.c:475 (python.exe:arm64+0x10044b500)
    #4 do_start_new_thread _threadmodule.c:1919 (python.exe:arm64+0x10044afcc)
    #5 thread_PyThread_start_joinable_thread _threadmodule.c:2042 (python.exe:arm64+0x10044a034)
    #6 cfunction_call methodobject.c:564 (python.exe:arm64+0x100138744)
    #7 _PyObject_MakeTpCall call.c:242 (python.exe:arm64+0x10009030c)
    #8 _PyObject_VectorcallTstate pycore_call.h:142 (python.exe:arm64+0x100090f14)
    #9 PyObject_Vectorcall call.c:327 (python.exe:arm64+0x100090f14)
    #10 _Py_VectorCall_StackRefSteal ceval.c:724 (python.exe:arm64+0x1002901d0)
    #11 _PyEval_EvalFrameDefault generated_cases.c.h:3528 (python.exe:arm64+0x100299cc4)
    #12 _PyEval_EvalFrame pycore_ceval.h:122 (python.exe:arm64+0x10028f9dc)
    #13 _PyEval_Vector ceval.c:2134 (python.exe:arm64+0x10028f9dc)
    #14 PyEval_EvalCode ceval.c:677 (python.exe:arm64+0x10028f9dc)
    #15 builtin_exec_impl bltinmodule.c:1261 (python.exe:arm64+0x100289470)
    #16 builtin_exec bltinmodule.c.h:676 (python.exe:arm64+0x100289470)
    #17 _Py_BuiltinCallFastWithKeywords_StackRef ceval.c:839 (python.exe:arm64+0x100297b54)
    #18 _PyEval_EvalFrameDefault generated_cases.c.h:2508 (python.exe:arm64+0x100297b54)
    #19 _PyEval_EvalFrame pycore_ceval.h:122 (python.exe:arm64+0x10028fe04)
    #20 _PyEval_Vector ceval.c:2134 (python.exe:arm64+0x10028fe04)
    #21 _PyFunction_Vectorcall call.c (python.exe:arm64+0x1000914bc)
    #22 _PyVectorcall_Call call.c:285 (python.exe:arm64+0x100091074)
    #23 _PyObject_Call call.c:348 (python.exe:arm64+0x100091074)
    #24 PyObject_Call call.c:373 (python.exe:arm64+0x1000911a4)
    #25 pymain_start_pyrepl main.c:311 (python.exe:arm64+0x1003a3f58)
    #26 pymain_run_stdin main.c:571 (python.exe:arm64+0x1003a3794)
    #27 pymain_run_python main.c:718 (python.exe:arm64+0x1003a28c4)
    #28 Py_RunMain main.c:796 (python.exe:arm64+0x1003a28c4)
    #29 pymain_main main.c:826 (python.exe:arm64+0x1003a2b78)
    #30 Py_BytesMain main.c:850 (python.exe:arm64+0x1003a2c78)
    #31 main python.c:15 (python.exe:arm64+0x100000a78)

  Thread T3 (tid=5631579, running) created by main thread at:
    #0 pthread_create <null> (libclang_rt.tsan_osx_dynamic.dylib:arm64+0x31d84)
    #1 do_start_joinable_thread thread_pthread.h:281 (python.exe:arm64+0x1003892a8)
    #2 PyThread_start_joinable_thread thread_pthread.h:323 (python.exe:arm64+0x1003890e0)
    #3 ThreadHandle_start _threadmodule.c:475 (python.exe:arm64+0x10044b500)
    #4 do_start_new_thread _threadmodule.c:1919 (python.exe:arm64+0x10044afcc)
    #5 thread_PyThread_start_joinable_thread _threadmodule.c:2042 (python.exe:arm64+0x10044a034)
    #6 cfunction_call methodobject.c:564 (python.exe:arm64+0x100138744)
    #7 _PyObject_MakeTpCall call.c:242 (python.exe:arm64+0x10009030c)
    #8 _PyObject_VectorcallTstate pycore_call.h:142 (python.exe:arm64+0x100090f14)
    #9 PyObject_Vectorcall call.c:327 (python.exe:arm64+0x100090f14)
    #10 _Py_VectorCall_StackRefSteal ceval.c:724 (python.exe:arm64+0x1002901d0)
    #11 _PyEval_EvalFrameDefault generated_cases.c.h:3528 (python.exe:arm64+0x100299cc4)
    #12 _PyEval_EvalFrame pycore_ceval.h:122 (python.exe:arm64+0x10028f9dc)
    #13 _PyEval_Vector ceval.c:2134 (python.exe:arm64+0x10028f9dc)
    #14 PyEval_EvalCode ceval.c:677 (python.exe:arm64+0x10028f9dc)
    #15 builtin_exec_impl bltinmodule.c:1261 (python.exe:arm64+0x100289470)
    #16 builtin_exec bltinmodule.c.h:676 (python.exe:arm64+0x100289470)
    #17 _Py_BuiltinCallFastWithKeywords_StackRef ceval.c:839 (python.exe:arm64+0x100297b54)
    #18 _PyEval_EvalFrameDefault generated_cases.c.h:2508 (python.exe:arm64+0x100297b54)
    #19 _PyEval_EvalFrame pycore_ceval.h:122 (python.exe:arm64+0x10028fe04)
    #20 _PyEval_Vector ceval.c:2134 (python.exe:arm64+0x10028fe04)
    #21 _PyFunction_Vectorcall call.c (python.exe:arm64+0x1000914bc)
    #22 _PyVectorcall_Call call.c:285 (python.exe:arm64+0x100091074)
    #23 _PyObject_Call call.c:348 (python.exe:arm64+0x100091074)
    #24 PyObject_Call call.c:373 (python.exe:arm64+0x1000911a4)
    #25 pymain_start_pyrepl main.c:311 (python.exe:arm64+0x1003a3f58)
    #26 pymain_run_stdin main.c:571 (python.exe:arm64+0x1003a3794)
    #27 pymain_run_python main.c:718 (python.exe:arm64+0x1003a28c4)
    #28 Py_RunMain main.c:796 (python.exe:arm64+0x1003a28c4)
    #29 pymain_main main.c:826 (python.exe:arm64+0x1003a2b78)
    #30 Py_BytesMain main.c:850 (python.exe:arm64+0x1003a2c78)
    #31 main python.c:15 (python.exe:arm64+0x100000a78)

SUMMARY: ThreadSanitizer: data race object.h:207 in Py_SET_TYPE
==================
ThreadSanitizer:DEADLYSIGNAL
==86130==ERROR: ThreadSanitizer: SEGV on unknown address 0x606d13baabcbbf10 (pc 0x000105999350 bp 0x00016d481c70 sp 0x00016d481c30 T5631578)
==86130==The signal is caused by a READ memory access.
    #0 __tsan::MemoryAccess(__tsan::ThreadState*, unsigned long, unsigned long, unsigned long, unsigned long) <null> (libclang_rt.tsan_osx_dynamic.dylib:arm64+0x71350)
    #1 mi_block_nextx internal.h:640 (python.exe:arm64+0x10014bf40)
    #2 mi_block_next internal.h:669 (python.exe:arm64+0x10014bf40)
    #3 _mi_page_thread_free_collect page.c:200 (python.exe:arm64+0x10014bf40)
    #4 _mi_page_free_collect page.c:230 (python.exe:arm64+0x10014bf40)
    #5 _mi_free_delayed_block alloc.c:628 (python.exe:arm64+0x100158f78)
    #6 _mi_heap_delayed_free_partial page.c:337 (python.exe:arm64+0x100158f78)
    #7 _mi_malloc_generic page.c:947 (python.exe:arm64+0x10014a99c)
    #8 _mi_heap_malloc_zero_ex alloc.c (python.exe:arm64+0x100166114)
    #9 _mi_heap_malloc_zero alloc.c:179 (python.exe:arm64+0x100166114)
    #10 mi_heap_malloc alloc.c:183 (python.exe:arm64+0x100166114)
    #11 _PyObject_MiMalloc obmalloc.c:307 (python.exe:arm64+0x100166114)
    #12 PyObject_Malloc obmalloc.c:1706 (python.exe:arm64+0x1001691cc)
    #13 PyUnicode_New unicodeobject.c:1326 (python.exe:arm64+0x1001d5014)
    #14 long_to_decimal_string_internal longobject.c:2233 (python.exe:arm64+0x1000f6e04)
    #15 long_to_decimal_string longobject.c:2323 (python.exe:arm64+0x1000ffd18)
    #16 object_str typeobject.c:7502 (python.exe:arm64+0x10019ee68)
    #17 PyObject_Str object.c:826 (python.exe:arm64+0x10014102c)
    #18 _PyEval_EvalFrameDefault generated_cases.c.h:4650 (python.exe:arm64+0x10029b8dc)
    #19 _PyEval_EvalFrame pycore_ceval.h:122 (python.exe:arm64+0x10028fe04)
    #20 _PyEval_Vector ceval.c:2134 (python.exe:arm64+0x10028fe04)
    #21 _PyFunction_Vectorcall call.c (python.exe:arm64+0x1000914bc)
    #22 _PyObject_VectorcallTstate pycore_call.h:144 (python.exe:arm64+0x100092c3c)
    #23 _PyObject_VectorcallPrepend call.c:855 (python.exe:arm64+0x100092c3c)
    #24 method_vectorcall classobject.c:55 (python.exe:arm64+0x100095fdc)
    #25 _PyObject_VectorcallTstate pycore_call.h:144 (python.exe:arm64+0x1002dcde0)
    #26 context_run context.c:728 (python.exe:arm64+0x1002dcde0)
    #27 method_vectorcall_FASTCALL_KEYWORDS descrobject.c:421 (python.exe:arm64+0x1000a8644)
    #28 _PyObject_VectorcallTstate pycore_call.h:144 (python.exe:arm64+0x100090e80)
    #29 PyObject_Vectorcall call.c:327 (python.exe:arm64+0x100090e80)
    #30 _Py_VectorCallInstrumentation_StackRefSteal ceval.c:766 (python.exe:arm64+0x100290910)
    #31 _PyEval_EvalFrameDefault generated_cases.c.h:1846 (python.exe:arm64+0x100295fa0)
    #32 _PyEval_EvalFrame pycore_ceval.h:122 (python.exe:arm64+0x10028fe04)
    #33 _PyEval_Vector ceval.c:2134 (python.exe:arm64+0x10028fe04)
    #34 _PyFunction_Vectorcall call.c (python.exe:arm64+0x1000914bc)
    #35 _PyObject_VectorcallTstate pycore_call.h:144 (python.exe:arm64+0x100092c3c)
    #36 _PyObject_VectorcallPrepend call.c:855 (python.exe:arm64+0x100092c3c)
    #37 method_vectorcall classobject.c:55 (python.exe:arm64+0x100095fdc)
    #38 _PyVectorcall_Call call.c:273 (python.exe:arm64+0x10009112c)
    #39 _PyObject_Call call.c:348 (python.exe:arm64+0x10009112c)
    #40 PyObject_Call call.c:373 (python.exe:arm64+0x1000911a4)
    #41 thread_run _threadmodule.c:388 (python.exe:arm64+0x10044b6f4)
    #42 pythread_wrapper thread_pthread.h:234 (python.exe:arm64+0x10038a0d4)
    #43 __tsan_thread_start_func <null> (libclang_rt.tsan_osx_dynamic.dylib:arm64+0x31cf4)
    #44 _pthread_start <null> (libsystem_pthread.dylib:arm64e+0x6c54)
    #45 thread_start <null> (libsystem_pthread.dylib:arm64e+0x1c18)

SUMMARY: ThreadSanitizer: SEGV internal.h:640 in mi_block_nextx
==86130==ABORTING
zsh: abort      ../cpython-nogil-tsan/python.exe

CPython versions tested on:

CPython main branch

Operating systems tested on:

macOS

Output from running 'python -VV' on the command line:

No response

Linked PRs

Activity

  1. added
    type-crashA hard crash of the interpreter, possibly with a core dump
    on Jun 3, 2026
  2. changed the title [-]Race in `type_set_qualname` leads to memory corruption[/-] [+]Race in `type_set_qualname` leads to memory corruption under free-threading[/+] on Jun 3, 2026
  3. added a commit that references this issue on Jun 11, 2026
  4. added a commit that references this issue on Jun 17, 2026
  5. added a commit that references this issue on Jul 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    interpreter-core(Objects, Python, Grammar, and Parser dirs)topic-free-threadingtype-crashA hard crash of the interpreter, possibly with a core dump

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions