diff --git a/Lib/test/test_interpreters/test_queues.py b/Lib/test/test_interpreters/test_queues.py index 77334aea3836b98..0ea4384d27c4b74 100644 --- a/Lib/test/test_interpreters/test_queues.py +++ b/Lib/test/test_interpreters/test_queues.py @@ -1,6 +1,8 @@ import importlib import pickle +import sys import threading +import types from textwrap import dedent import unittest @@ -381,6 +383,40 @@ def test_put_get_full_fallback(self): self.assertEqual(obj, obj2) self.assertIsNot(obj, obj2) + def _check_unpickle_attributeerror_arg(self, arg): + # Put an object through a queue where get() must re-import its class + # via a module __getattr__ that raises AttributeError(arg). + modname = '_test_xi_attrerr' + mod = types.ModuleType(modname) + class Thing: + pass + Thing.__module__ = modname + Thing.__qualname__ = 'Thing' + mod.Thing = Thing + sys.modules[modname] = mod + self.addCleanup(sys.modules.pop, modname, None) + queue = queues.create() + queue.put(Thing()) + del mod.Thing + def raise_attributeerror(name): + raise AttributeError(arg) + mod.__getattr__ = raise_attributeerror + 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 must not crash, locking in the + # non-NULL strncmp() path. + with self.subTest(arg='boom'): + self._check_unpickle_attributeerror_arg('boom') + 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 000000000000000..95b2a89a4a3aa05 --- /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 6b489bf03f86ecd..2d11d7442cf283c 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);