diff --git a/Lib/test/test_interpreters/test_queues.py b/Lib/test/test_interpreters/test_queues.py index 77334aea3836b9..e5761d4ad8b5bd 100644 --- a/Lib/test/test_interpreters/test_queues.py +++ b/Lib/test/test_interpreters/test_queues.py @@ -381,6 +381,44 @@ def test_put_get_full_fallback(self): self.assertEqual(obj, obj2) self.assertIsNot(obj, obj2) + def _check_unpickle_attributeerror_arg(self, arg): + # Cross an object through a queue where get() must re-import its + # class via a module __getattr__ that raises AttributeError(arg). + source = dedent(""" + _attrerr_arg = None + + class Thing: + pass + + def break_module(mod, arg): + del mod.Thing + mod._attrerr_arg = arg + def __getattr__(name): + raise AttributeError(mod._attrerr_arg) + mod.__getattr__ = __getattr__ + """) + with import_helper.ready_to_import('_xi_attrerr', source) as (name, _): + mod = importlib.import_module(name) + queue = queues.create() + queue.put(mod.Thing()) + mod.break_module(mod, arg) + with self.assertRaises(interpreters.NotShareableError): + queue.get() + + def test_get_unpickle_fails_with_bad_attributeerror_arg(self): + # gh-151862: an AttributeError arg that can't be UTF-8 encoded used + # to crash (NULL deref); covers both non-str and surrogate-str args. + for arg in [42, b'x', None, '\ud800']: + with self.subTest(arg=arg): + self._check_unpickle_attributeerror_arg(arg) + + def test_get_unpickle_fails_with_str_attributeerror_arg(self): + # Positive control: a normal str arg (incl. the real missing-__main__ + # message) must not crash, locking in the non-NULL strncmp() path. + for arg in ['boom', "module '__main__' has no attribute 'Thing'"]: + with self.subTest(arg=arg): + self._check_unpickle_attributeerror_arg(arg) + def test_put_get_same_interpreter(self): interp = interpreters.create() interp.exec(dedent(""" diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-06-21-12-00-00.gh-issue-151862.Xq7vTm.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-21-12-00-00.gh-issue-151862.Xq7vTm.rst new file mode 100644 index 00000000000000..95b2a89a4a3aa0 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-21-12-00-00.gh-issue-151862.Xq7vTm.rst @@ -0,0 +1,3 @@ +Fixed a crash (``NULL`` dereference) when an object passed between +interpreters via :mod:`concurrent.interpreters` fails to unpickle with an +:exc:`AttributeError` whose first argument is not a string. diff --git a/Python/crossinterp.c b/Python/crossinterp.c index 6b489bf03f86ec..2d11d7442cf283 100644 --- a/Python/crossinterp.c +++ b/Python/crossinterp.c @@ -664,6 +664,11 @@ check_missing___main___attr(PyObject *exc) } } const char *err = PyUnicode_AsUTF8(msgobj); + if (err == NULL) { + PyErr_Clear(); + Py_DECREF(msgobj); + return 0; + } // Check if it's a missing __main__ attr. int cmp = strncmp(err, "module '__main__' has no attribute '", 36);