From a0de16a54844be0fb0a7c458098e30697e7ada27 Mon Sep 17 00:00:00 2001 From: Itamar Oren Date: Sat, 12 Sep 2026 15:07:56 -0700 Subject: [PATCH] gh-157384: Refuse inittab names in PyImport_CreateModuleFromInitfunc() Modules created by PyImport_CreateModuleFromInitfunc() share the extensions cache and sys.modules with built-in modules. Passing a spec whose name is registered in PyImport_Inittab either silently ignored the init function (for an already loaded single-phase builtin, or for "sys" and "builtins") or replaced the built-in module in sys.modules (for a multi-phase builtin). Raise ImportError instead. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01HoZE3WdRWQshvinfBZ137N --- Doc/c-api/import.rst | 5 +++++ Lib/test/test_embed.py | 9 +++++++++ ...-09-12-18-37-23.gh-issue-157384.aQkHID.rst | 3 +++ Programs/_testembed.c | 19 +++++++++++++++++++ Python/import.c | 19 ++++++++++++++++--- 5 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/C_API/2026-09-12-18-37-23.gh-issue-157384.aQkHID.rst diff --git a/Doc/c-api/import.rst b/Doc/c-api/import.rst index 7a3e5357e0c2cac..9cf3364588083e2 100644 --- a/Doc/c-api/import.rst +++ b/Doc/c-api/import.rst @@ -417,6 +417,11 @@ Importing Modules (Custom importers should do this in their :py:meth:`~importlib.abc.Loader.exec_module` method.) + If the spec's name is registered in :c:var:`PyImport_Inittab` (which + includes the :mod:`sys` and :mod:`builtins` modules), raise + :exc:`ImportError`; a module cannot be both a built-in module and one + created by this function. + On error, return NULL with an exception set. .. versionadded:: 3.15 diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py index 1ff600e30bf4cbd..07784eedea0e73e 100644 --- a/Lib/test/test_embed.py +++ b/Lib/test/test_embed.py @@ -278,6 +278,15 @@ def test_create_module_from_initfunc(self): "my_test_extension.exec_slot_ran='yes'\n" "\n" "embedded_ext.executed='yes'\n" + "ImportError: cannot create module 'sys' from an " + "init function: a built-in module with this name " + "is registered in PyImport_Inittab\n" + "ImportError: cannot create module " + "'create_static_module' from an init function: " + "a built-in module with this name is registered " + "in PyImport_Inittab\n" + "sys.modules[\"create_static_module\"] " + "is create_static_module=True\n" ) def test_inittab_submodule_multiphase(self): diff --git a/Misc/NEWS.d/next/C_API/2026-09-12-18-37-23.gh-issue-157384.aQkHID.rst b/Misc/NEWS.d/next/C_API/2026-09-12-18-37-23.gh-issue-157384.aQkHID.rst new file mode 100644 index 000000000000000..3b26c9ec066ebcd --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2026-09-12-18-37-23.gh-issue-157384.aQkHID.rst @@ -0,0 +1,3 @@ +:c:func:`PyImport_CreateModuleFromInitfunc` now raises :exc:`ImportError` if +the spec's name is registered in :c:var:`PyImport_Inittab`, instead of +silently ignoring *initfunc* or replacing the built-in module. diff --git a/Programs/_testembed.c b/Programs/_testembed.c index 418609abc5f6b82..0f69776e4792bf1 100644 --- a/Programs/_testembed.c +++ b/Programs/_testembed.c @@ -2425,6 +2425,13 @@ create_module(PyObject* self, PyObject* spec) Py_DECREF(name); return PyImport_CreateModuleFromInitfunc(spec, PyInit_embedded_ext); } + if (PyUnicode_EqualToUTF8(name, "sys") + || PyUnicode_EqualToUTF8(name, "create_static_module")) { + // names registered in the inittab (core module / builtin): + // must be refused before the init function is called + Py_DECREF(name); + return PyImport_CreateModuleFromInitfunc(spec, PyInit_embedded_ext); + } PyErr_Format(PyExc_LookupError, "static module %R not found", name); Py_DECREF(name); return NULL; @@ -2472,6 +2479,10 @@ test_create_module_from_initfunc(void) L"import embedded_ext;" L"print(embedded_ext);" L"print(f'{embedded_ext.executed=}');" + // Names registered in the inittab are refused + L"try_create('sys');" + L"try_create('create_static_module');" + L"print(f'{sys.modules[\"create_static_module\"] is create_static_module=}');" }; PyConfig config; if (PyImport_AppendInittab("create_static_module", @@ -2502,6 +2513,14 @@ test_create_module_from_initfunc(void) " create_static_module.exec_module(module)\n" " module.executed = 'yes'\n" "sys.meta_path.append(StaticExtensionImporter)\n" + "def try_create(name):\n" + " spec = spec_from_loader(name, StaticExtensionImporter)\n" + " try:\n" + " create_static_module.create_module(spec)\n" + " except ImportError as exc:\n" + " print(f'ImportError: {exc}')\n" + " else:\n" + " print(f'no ImportError for {name}!')\n" ); if (result < 0) { fprintf(stderr, "PyRun_SimpleString() failed\n"); diff --git a/Python/import.c b/Python/import.c index 037f15d4ca2bafa..74f75c9f9136a61 100644 --- a/Python/import.c +++ b/Python/import.c @@ -2486,10 +2486,10 @@ is_builtin(PyObject *name) } static struct _inittab* -lookup_inittab_entry(const struct _Py_ext_module_loader_info* info) +lookup_inittab_entry(PyObject *name) { for (struct _inittab *p = INITTAB; p->name != NULL; p++) { - if (_PyUnicode_EqualToASCIIString(info->name, p->name)) { + if (_PyUnicode_EqualToASCIIString(name, p->name)) { return p; } } @@ -2534,7 +2534,7 @@ create_builtin( PyModInitFunction p0 = NULL; if (initfunc == NULL) { - struct _inittab *entry = lookup_inittab_entry(&info); + struct _inittab *entry = lookup_inittab_entry(info.name); if (entry == NULL) { mod = NULL; _PyErr_SetModuleNotFoundError(name); @@ -2601,6 +2601,19 @@ PyImport_CreateModuleFromInitfunc( return NULL; } + /* Built-in modules (including the core "sys" and "builtins" modules) + * share the extensions cache and sys.modules with modules created here. + * Refuse to create a module under a name registered in the inittab, + * rather than silently ignoring either initfunc or the other module. */ + if (lookup_inittab_entry(name) != NULL) { + PyErr_Format(PyExc_ImportError, + "cannot create module %R from an init function: " + "a built-in module with this name is registered " + "in PyImport_Inittab", name); + Py_DECREF(name); + return NULL; + } + PyObject *mod = create_builtin(tstate, name, spec, initfunc); Py_DECREF(name); return mod;