Skip to content

Segfault with data race / double-decref in func_set_qualname under free-threading #153297

Description

@Naserume

What happened?

In the free-threaded build, the setters for a function's __qualname__ and __name__ replace the field with Py_XSETREF and no critical section:

func_set_qualname(PyObject*self, PyObject*value, void*Py_UNUSED(ignored))
{
PyFunctionObject*op=_PyFunction_CAST(self);
/* Not legal to del f.__qualname__ or to set it to anything
* other than a string object. */
if (value==NULL|| !PyUnicode_Check(value)) {
PyErr_SetString(PyExc_TypeError,
"__qualname__ must be set to a string object");
return-1;
}
handle_func_event(PyFunction_EVENT_MODIFY_QUALNAME, (PyFunctionObject*) op, value);
Py_XSETREF(op->func_qualname, Py_NewRef(value));
return0;
}

Py_XSETREF reads the old func_qualname, stores the new value, and decrefs the old one, with no lock. Two threads assigning f.__qualname__ can therefore both decref the same old string and free it while it is still referenced, a use-after-free.

The freed memory is then reused by a new allocation while another thread is still dereferencing the old pointer (e.g. repr() reading func_qualname), so that dangling access lands on reused memory and the interpreter segfaults.

Reproducer:

fromthreadingimportThreaddefshared_func():
return1defsetter():
foriinrange(20000):
try:
shared_func.__qualname__="q_%d_%d"% (id(i), i&15)
exceptException:
passdefreader():
for_inrange(20000):
try:
_=shared_func.__qualname__repr(shared_func)
exceptException:
passthreads= [Thread(target=setter) for_inrange(6)]
threads+= [Thread(target=reader) for_inrange(4)]
fortinthreads: t.start()
fortinthreads: t.join()

TSAN Report:

==================
WARNING: ThreadSanitizer: data race (pid=46190)
Read of size 8 at 0x7e6f48678c78 by thread T2:
#0 func_set_qualname /cpython/Objects/funcobject.c:734:5 #1 getset_set /cpython/Objects/descrobject.c:250:16 #2 _PyObject_GenericSetAttrWithDict /cpython/Objects/object.c:2049:19 #3 PyObject_GenericSetAttr /cpython/Objects/object.c:2120:12
#4 PyObject_SetAttr /cpython/Objects/object.c:1533:15 #5 _PyEval_EvalFrameDefault /cpython/Python/generated_cases.c.h:12146:27 Previous write of size 8 at 0x7e6f48678c78 by thread T1:
#0 func_set_qualname /cpython/Objects/funcobject.c:734:5 #1 getset_set /cpython/Objects/descrobject.c:250:16 #2 _PyObject_GenericSetAttrWithDict /cpython/Objects/object.c:2049:19 #3 PyObject_GenericSetAttr /cpython/Objects/object.c:2120:12 #4 PyObject_SetAttr /cpython/Objects/object.c:1533:15 #5 _PyEval_EvalFrameDefault /cpython/Python/generated_cases.c.h:12146:27 SUMMARY: ThreadSanitizer: data race /cpython/Objects/funcobject.c:734:5 in func_set_qualname
==================
...
==================
WARNING: ThreadSanitizer: data race (pid=46190)
Read of size 4 at 0x7e6f4a07019c by thread T4:
#0 _Py_ExplicitMergeRefcount /cpython/Objects/object.c:477:40 #1 _Py_brc_queue_object /cpython/Python/brc.c:73:31 #2 _Py_DecRefSharedIsDead /cpython/Objects/object.c:413:9 #3 _Py_DecRefSharedDebug /cpython/Objects/object.c:425:9 #4 _Py_DecRefShared /cpython/Objects/object.c:433:5 #5 Py_DECREF /cpython/./Include/refcount.h:385:9 #6 Py_XDECREF /cpython/./Include/refcount.h:520:9 #7 func_set_qualname /cpython/Objects/funcobject.c:734:5 #8 getset_set /cpython/Objects/descrobject.c:250:16 #9 _PyObject_GenericSetAttrWithDict /cpython/Objects/object.c:2049:19 #10 PyObject_GenericSetAttr /cpython/Objects/object.c:2120:12 #11 PyObject_SetAttr /cpython/Objects/object.c:1533:15 #12 _PyEval_EvalFrameDefault /cpython/Python/generated_cases.c.h:12146:27 Previous atomic write of size 4 at 0x7e6f4a07019c by thread T1:
#0 _Py_atomic_store_uint32_relaxed /cpython/./Include/cpython/pyatomic_gcc.h:493:3 #1 new_reference /cpython/Objects/object.c:2743:5 #2 _Py_NewReferenceNoTotal /cpython/Objects/object.c:2770:5 #3 _PyUnicode_ResizeCompact /cpython/Objects/unicodeobject.c:1117:5 #4 _PyUnicodeWriter_Finish /cpython/Objects/unicode_writer.c:620:16 #5 PyUnicode_Format /cpython/Objects/unicode_format.c:1102:12 #6 unicode_mod /cpython/Objects/unicodeobject.c:13721:12 #7 binary_op1 /cpython/Objects/abstract.c:974:13 #8 binary_op /cpython/Objects/abstract.c:1013:24 #9 PyNumber_Remainder /cpython/Objects/abstract.c:1197:1 #10 _PyEval_EvalFrameDefault /cpython/Python/generated_cases.c.h:67:35 SUMMARY: ThreadSanitizer: data race /cpython/Objects/object.c:477:40 in _Py_ExplicitMergeRefcount
==================
...
==================
WARNING: ThreadSanitizer: data race (pid=46190)
Read of size 8 at 0x7e6f52070160 by thread T7:
#0 unicode_fromformat_write_str /cpython/Objects/unicodeobject.c:2527:14 #1 unicode_fromformat_arg /cpython/Objects/unicodeobject.c #2 unicode_from_format /cpython/Objects/unicodeobject.c:3081:17 #3 PyUnicode_FromFormatV /cpython/Objects/unicodeobject.c:3115:9 #4 PyUnicode_FromFormat /cpython/Objects/unicodeobject.c:3129:11
#5 func_repr /cpython/Objects/funcobject.c:1150:12 #6 PyObject_Repr /cpython/Objects/object.c:784:11 #7 builtin_repr /cpython/Python/bltinmodule.c:2677:12 #8 cfunction_vectorcall_O /cpython/Objects/methodobject.c:536:24 #9 _PyObject_VectorcallTstate /cpython/./Include/internal/pycore_call.h:144:11 #10 PyObject_Vectorcall /cpython/Objects/call.c:327:12 #11 _Py_VectorCallInstrumentation_StackRefSteal /cpython/Python/ceval.c:768:11 #12 _PyEval_EvalFrameDefault /cpython/Python/generated_cases.c.h:1906:35 Previous write of size 8 at 0x7e6f52070160 by thread T3:
#0 __tsan_memcpy <null> (python3.16t+0xf514f) #1 _mi_memcpy /cpython/./Include/internal/mimalloc/mimalloc/internal.h:929:3 #2 _PyObject_MiRealloc /cpython/Objects/obmalloc.c:363:9 #3 PyObject_Realloc /cpython/Objects/obmalloc.c:1731:12
#4 _PyUnicode_ResizeCompact /cpython/Objects/unicodeobject.c:1110:31 #5 _PyUnicodeWriter_Finish /cpython/Objects/unicode_writer.c:620:16 #6 PyUnicode_Format /cpython/Objects/unicode_format.c:1102:12 #7 unicode_mod /cpython/Objects/unicodeobject.c:13721:12 #8 binary_op1 /cpython/Objects/abstract.c:974:13 #9 binary_op /cpython/Objects/abstract.c:1013:24 #10 PyNumber_Remainder /cpython/Objects/abstract.c:1197:1 #11 _PyEval_EvalFrameDefault /cpython/Python/generated_cases.c.h:67:35 SUMMARY: ThreadSanitizer: data race /cpython/Objects/unicodeobject.c:2527:14 in unicode_fromformat_write_str
==================
...
==================
WARNING: ThreadSanitizer: data race (pid=46190)
Read of size 8 at 0x7e6f5a074ed8 by thread T8:
#0 _Py_Dealloc /cpython/Objects/object.c:3292:26 #1 _Py_brc_queue_object /cpython/Python/brc.c #2 _Py_DecRefSharedIsDead /cpython/Objects/object.c:413:9 #3 _Py_DecRefSharedDebug /cpython/Objects/object.c:425:9 #4 _Py_DecRefShared /cpython/Objects/object.c:433:5 #5 Py_DECREF /cpython/./Include/refcount.h:385:9
#6 PyStackRef_XCLOSE /cpython/./Include/internal/pycore_stackref.h:726:9 #7 _PyEval_EvalFrameDefault /cpython/Python/generated_cases.c.h:12475:17 Previous write of size 8 at 0x7e6f5a074ed8 by thread T5:
#0 __tsan_memcpy <null> (python3.16t+0xf514f) #1 _mi_memcpy /cpython/./Include/internal/mimalloc/mimalloc/internal.h:929:3 #2 _PyObject_MiRealloc /cpython/Objects/obmalloc.c:363:9 #3 PyObject_Realloc /cpython/Objects/obmalloc.c:1731:12 #4 _PyUnicode_ResizeCompact /cpython/Objects/unicodeobject.c:1110:31 #5 _PyUnicodeWriter_Finish /cpython/Objects/unicode_writer.c:620:16 #6 PyUnicode_Format /cpython/Objects/unicode_format.c:1102:12 #7 unicode_mod /cpython/Objects/unicodeobject.c:13721:12 #8 binary_op1 /cpython/Objects/abstract.c:974:13 #9 binary_op /cpython/Objects/abstract.c:1013:24 #10 PyNumber_Remainder /cpython/Objects/abstract.c:1197:1 #11 _PyEval_EvalFrameDefault /cpython/Python/generated_cases.c.h:67:35 SUMMARY: ThreadSanitizer: data race /cpython/Objects/object.c:3292:26 in _Py_Dealloc
==================
ThreadSanitizer:DEADLYSIGNAL
==46190==ERROR: ThreadSanitizer: SEGV on unknown address (pc 0x56b51dd549ac bp 0x7e6f3f9fc8b0 sp 0x7e6f3f9fc858 T46198)
==46190==The signal is caused by a READ memory access.
==46190==Hint: this fault was caused by a dereference of a high value address (see register values below). Disassemble the provided pc to learn which register was used.
ThreadSanitizer:DEADLYSIGNAL
ThreadSanitizer: nested bug in the same thread, aborting.

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 free-threading build (tags/v3.15.0b1-614-g7928a8b730b:7928a8b730b, Jul 7 2026, 18:49:27) [Clang 18.1.3 (1ubuntu1)]

Linked PRs

Metadata

Metadata

Assignees

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