From 8ebab58927508673c09534c566ba433fe983f3fd Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sat, 6 Jan 2024 01:47:01 -0800 Subject: [PATCH] Check for script shadowing stdlib --- Lib/test/test_import/__init__.py | 22 ++++++ Objects/moduleobject.c | 112 ++++++++++++++++++++++++------- 2 files changed, 110 insertions(+), 24 deletions(-) diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py index 7b0126226c4aba2..3ce45179ac11f0e 100644 --- a/Lib/test/test_import/__init__.py +++ b/Lib/test/test_import/__init__.py @@ -790,6 +790,28 @@ def test_issue105979(self): self.assertIn("Frozen object named 'x' is invalid", str(cm.exception)) + def test_delete_sys_stdlib_module_names(self): + args = ["-c", "import sys; del sys.stdlib_module_names; sys.stdlib_module_names"] + popen = script_helper.spawn_python(*args) + stdout, stderr = popen.communicate() + self.assertIn( + b"AttributeError: module 'sys' has no attribute 'stdlib_module_names'", + stdout + ) + + def test_cwd_script_shadowing_stdlib(self): + with CleanImport('collections'): + import collections + collections.__spec__ = types.SimpleNamespace() + collections.__spec__.origin = os.path.join(os.getcwd(), 'collections.py') + with self.assertRaisesRegex( + AttributeError, + r"module 'collections' has no attribute 'does_not_exist' \(most " + r"likely due to '.*collections.py' shadowing the standard " + r"library module named 'collections'\)" + ): + collections.does_not_exist + @skip_if_dont_write_bytecode class FilePermissionTests(unittest.TestCase): diff --git a/Objects/moduleobject.c b/Objects/moduleobject.c index 9b7df10e7cc2a9b..7b9f2fb9401d071 100644 --- a/Objects/moduleobject.c +++ b/Objects/moduleobject.c @@ -848,39 +848,103 @@ _Py_module_getattro_impl(PyModuleObject *m, PyObject *name, int suppress) Py_DECREF(origin); } - int rc = _PyModuleSpec_IsInitializing(spec); - if (rc > 0) { - if (valid_origin == 1) { - PyErr_Format(PyExc_AttributeError, - "partially initialized " - "module '%U' from '%U' has no attribute '%U' " - "(most likely due to a circular import)", - mod_name, origin, name); - } - else { - PyErr_Format(PyExc_AttributeError, - "partially initialized " - "module '%U' has no attribute '%U' " - "(most likely due to a circular import)", - mod_name, name); + int is_script_shadowing_stdlib = 0; + // Check mod.__name__ in sys.stdlib_module_names + // and os.path.dirname(mod.__spec__.origin) == os.getcwd() + PyObject *stdlib = NULL; + if (valid_origin == 1) { + if ( + // avoid bad recursion + PyUnicode_CompareWithASCIIString(mod_name, "sys") != 0 + && PyUnicode_CompareWithASCIIString(mod_name, "os") != 0 + && PyUnicode_CompareWithASCIIString(mod_name, "builtins") != 0 + ) { + stdlib = _PyImport_GetModuleAttrString("sys", "stdlib_module_names"); + if (!stdlib) { + goto done; + } + if (PySequence_Contains(stdlib, mod_name)) { + PyObject *os_path = _PyImport_GetModuleAttrString("os", "path"); + if (!os_path) { + goto done; + } + PyObject *dirname = PyObject_GetAttrString(os_path, "dirname"); + Py_DECREF(os_path); + if (!dirname) { + goto done; + } + PyObject *origin_dir = _PyObject_CallOneArg(dirname, origin); + Py_DECREF(dirname); + if (!origin_dir) { + goto done; + } + + PyObject *getcwd = _PyImport_GetModuleAttrString("os", "getcwd"); + if (!getcwd) { + Py_DECREF(origin_dir); + goto done; + } + PyObject *cwd = _PyObject_CallNoArgs(getcwd); + Py_DECREF(getcwd); + if (!cwd) { + Py_DECREF(origin_dir); + goto done; + } + + is_script_shadowing_stdlib = PyObject_RichCompareBool(origin_dir, cwd, Py_EQ); + Py_DECREF(origin_dir); + Py_DECREF(cwd); + if (is_script_shadowing_stdlib < 0) { + goto done; + } + } } } - else if (rc == 0) { - rc = _PyModuleSpec_IsUninitializedSubmodule(spec, name); + + if (is_script_shadowing_stdlib == 1) { + PyErr_Format(PyExc_AttributeError, + "module '%U' has no attribute '%U' " + "(most likely due to '%U' shadowing the standard library " + "module named '%U')", + mod_name, name, origin, mod_name); + } else { + int rc = _PyModuleSpec_IsInitializing(spec); if (rc > 0) { - PyErr_Format(PyExc_AttributeError, - "cannot access submodule '%U' of module '%U' " - "(most likely due to a circular import)", - name, mod_name); + if (valid_origin == 1) { + PyErr_Format(PyExc_AttributeError, + "partially initialized " + "module '%U' from '%U' has no attribute '%U' " + "(most likely due to a circular import)", + mod_name, origin, name); + } + else { + PyErr_Format(PyExc_AttributeError, + "partially initialized " + "module '%U' has no attribute '%U' " + "(most likely due to a circular import)", + mod_name, name); + } } else if (rc == 0) { - PyErr_Format(PyExc_AttributeError, - "module '%U' has no attribute '%U'", - mod_name, name); + rc = _PyModuleSpec_IsUninitializedSubmodule(spec, name); + if (rc > 0) { + PyErr_Format(PyExc_AttributeError, + "cannot access submodule '%U' of module '%U' " + "(most likely due to a circular import)", + name, mod_name); + } + else if (rc == 0) { + PyErr_Format(PyExc_AttributeError, + "module '%U' has no attribute '%U'", + mod_name, name); + } } } + +done: Py_XDECREF(spec); Py_XDECREF(origin); + Py_XDECREF(stdlib); Py_DECREF(mod_name); return NULL; }