Skip to content

segfault in ga_iternext: sharing a types.GenericAlias iterator across threads double-frees under free-threading #154043

Description

@devdanzin

Crash report

What happened?

On a free-threaded (--disable-gil) build, iterating a single, shared types.GenericAlias iterator (e.g. iter(list[int])) from multiple threads segfaults. ga_iternext (Objects/genericaliasobject.c) is a one-shot iterator with no synchronization:

staticPyObject*ga_iternext(PyObject*op)
{
gaiterobject*gi= (gaiterobject*)op;
if (gi->obj==NULL) { // read gi->objPyErr_SetNone(PyExc_StopIteration);
returnNULL;
}
gaobject*alias= (gaobject*)gi->obj;
PyObject*starred_alias=Py_GenericAlias(alias->origin, alias->args); // use gi->objif (starred_alias==NULL)
returnNULL;
((gaobject*)starred_alias)->starred= true;
Py_SETREF(gi->obj, NULL); // Py_DECREF(gi->obj); gi->obj = NULLreturnstarred_alias;
}

Two threads both observe gi->obj != NULL, both build the starred alias from it, and both reach Py_SETREF(gi->obj, NULL) (which is tmp = gi->obj; gi->obj = NULL; Py_DECREF(tmp)). With gi->obj's refcount at 1 (the iterator is its only holder), the two Py_DECREFs free it twice — a double-free / refcount underflow — and the reads of alias->origin/alias->args become a use-after-free once the other thread frees it. The process crashes.

Reproducer

importthreadingNT=16defworker(it, barrier):
barrier.wait()
try:
next(it)
exceptStopIteration:
passfor_roundinrange(20000):
shared=iter(list[int]) # ONE shared GenericAlias iterator; gi->obj refcount == 1bar=threading.Barrier(NT)
threads= [threading.Thread(target=worker, args=(shared, bar)) for_inrange(NT)]
fortinthreads:
t.start()
fortinthreads:
t.join()
print("done")

PYTHON_GIL=0 ./python repro.py segfaults within the first few rounds — 5/5 runs on both a debug and a release (-O0, no sanitizer) free-threaded build, so it is neither debug-only nor sanitizer-only:

Thread NNNN received signal SIGSEGV, Segmentation fault.
#0 ga_iternext (op=...) at Objects/genericaliasobject.c:952 (Py_SETREF(gi->obj, NULL))
#1 builtin_next Python/bltinmodule.c:1776
#2 _Py_BuiltinCallFast_StackRef Python/ceval.c:817
#3 _PyEval_EvalFrameDefault Python/generated_cases.c.h:2510 (next(it))
...

ThreadSanitizer reports the same as a data race on gi->obj (WARNING: ThreadSanitizer: data race... in ga_iternext, exit 66).

Suggested fix

Consume gi->obj atomically so exactly one thread takes it:

PyObject*obj=_Py_atomic_exchange_ptr(&gi->obj, NULL);
if (obj==NULL) {
PyErr_SetNone(PyExc_StopIteration);
returnNULL;
}
gaobject*alias= (gaobject*)obj;
PyObject*starred_alias=Py_GenericAlias(alias->origin, alias->args);
if (starred_alias==NULL) {
Py_DECREF(obj);
returnNULL;
}
((gaobject*)starred_alias)->starred= true;
Py_DECREF(obj);
returnstarred_alias;

(Or wrap the body in Py_BEGIN_CRITICAL_SECTION(gi).) This matches the "consume once" semantics and keeps the GIL build unchanged.

Per the iterator free-threading strategy (gh-124397), concurrent iteration may return duplicate or skipped values or raise — but it must not crash; this crashes. Distinct from gh-153298, which is the GenericAlias.__parameters__ lazy-init race, not the iterator.

(Found by fusil --tsan, a ThreadSanitizer fuzzer; crash confirmed by re-running the reproducer without a sanitizer on a plain free-threaded build. Draft and reproducer by Claude Code, minimized and reviewed by hand.)

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 (heads/main:a1d580430c8, Jul 18 2026, 20:36:44) [Clang 21.1.8 (6ubuntu1)]

Linked PRs

Activity

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