Python/crossinterp.c's check_missing___main___attr() passes the result of PyUnicode_AsUTF8() straight to strncmp() without a NULL check:
const char *err = PyUnicode_AsUTF8(msgobj);
// Check if it's a missing __main__ attr.
int cmp = strncmp(err, "module '__main__' has no attribute '", 36);
msgobj is args[0] of an AttributeError raised on the receive side of the cross-interpreter pickle fallback, reached when an object is moved between interpreters through a concurrent.interpreters queue or channel and its unpickling in the receiving interpreter fails.
PyUnicode_AsUTF8() returns NULL (and sets an exception) when args[0] is not a str — e.g. AttributeError(42), AttributeError(b'x'), AttributeError(None) — or is a str containing lone surrogates (AttributeError('\ud800')). When err == NULL, strncmp(NULL, ...) dereferences NULL and the interpreter crashes (SIGSEGV).
Reproduction
Put an object into a concurrent.interpreters queue whose unpickling on the receive side raises an AttributeError with a non-string (or surrogate) first argument, then get() it — the receiving interpreter segfaults instead of raising NotShareableError. A self-contained reproduction is added as a regression test (test_get_unpickle_fails_with_bad_attributeerror_arg in Lib/test/test_interpreters/test_queues.py), exercising args 42, b'x', None, and '\ud800'.
Scope
This is the check_missing___main___attr() NULL dereference reached through the queue/channel receive path. It does not by itself make every cross-interpreter unpickle failure safe — Interpreter.call() has a separate crash on its result-preserve path that I will report independently.
Fix
Add a NULL guard immediately after PyUnicode_AsUTF8, matching the correctly-checked sibling helper _copy_string_obj_raw(): on NULL, clear the exception (the function asserts !PyErr_Occurred() on entry), Py_DECREF(msgobj), and return 0 (not a missing-__main__ attribute).
Affected versions
concurrent.interpreters (PEP 734) is public in 3.14+; this reproduces on main and 3.14.
Linked PR
A PR follows.
Linked PRs
Python/crossinterp.c'scheck_missing___main___attr()passes the result ofPyUnicode_AsUTF8()straight tostrncmp()without aNULLcheck:msgobjisargs[0]of anAttributeErrorraised on the receive side of the cross-interpreter pickle fallback, reached when an object is moved between interpreters through aconcurrent.interpretersqueue or channel and its unpickling in the receiving interpreter fails.PyUnicode_AsUTF8()returnsNULL(and sets an exception) whenargs[0]is not astr— e.g.AttributeError(42),AttributeError(b'x'),AttributeError(None)— or is astrcontaining lone surrogates (AttributeError('\ud800')). Whenerr == NULL,strncmp(NULL, ...)dereferencesNULLand the interpreter crashes (SIGSEGV).Reproduction
Put an object into a
concurrent.interpretersqueue whose unpickling on the receive side raises anAttributeErrorwith a non-string (or surrogate) first argument, thenget()it — the receiving interpreter segfaults instead of raisingNotShareableError. A self-contained reproduction is added as a regression test (test_get_unpickle_fails_with_bad_attributeerror_arginLib/test/test_interpreters/test_queues.py), exercising args42,b'x',None, and'\ud800'.Scope
This is the
check_missing___main___attr()NULLdereference reached through the queue/channel receive path. It does not by itself make every cross-interpreter unpickle failure safe —Interpreter.call()has a separate crash on its result-preserve path that I will report independently.Fix
Add a
NULLguard immediately afterPyUnicode_AsUTF8, matching the correctly-checked sibling helper_copy_string_obj_raw(): onNULL, clear the exception (the function asserts!PyErr_Occurred()on entry),Py_DECREF(msgobj), and return 0 (not a missing-__main__attribute).Affected versions
concurrent.interpreters(PEP 734) is public in 3.14+; this reproduces onmainand 3.14.Linked PR
A PR follows.
Linked PRs