https://github.com/python/cpython/commit/a94c7528b596e9ec234f12ebeeb45fc731412b18 commit: a94c7528b596e9ec234f12ebeeb45fc731412b18 branch: main author: Matt Wozniski <mwozni...@bloomberg.net> committer: pablogsal <pablog...@gmail.com> date: 2025-04-23T23:40:24Z summary:
gh-132859: Run debugger scripts in their own namespaces (#132860) Run debugger scripts in their own namespaces Previously scripts injected by `sys.remote_exec` were run with the globals of the `__main__` module. Instead, run each injected script with an empty set of globals. If someone really wants to use the `__main__` module's namespace, they can always `import __main__`. files: M Lib/test/test_sys.py M Python/ceval_gil.c diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 44e6bdf5f00712..f9bac3fe351867 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -2125,6 +2125,19 @@ def test_remote_exec_with_exception(self): self.assertIn(b"Remote script exception", stderr) self.assertEqual(stdout.strip(), b"Target process running...") + def test_new_namespace_for_each_remote_exec(self): + """Test that each remote_exec call gets its own namespace.""" + script = textwrap.dedent( + """ + assert globals() is not __import__("__main__").__dict__ + print("Remote script executed successfully!") + """ + ) + returncode, stdout, stderr = self._run_remote_exec_test(script) + self.assertEqual(returncode, 0) + self.assertEqual(stderr, b"") + self.assertIn(b"Remote script executed successfully", stdout) + def test_remote_exec_disabled_by_env(self): """Test remote exec is disabled when PYTHON_DISABLE_REMOTE_DEBUG is set""" env = os.environ.copy() diff --git a/Python/ceval_gil.c b/Python/ceval_gil.c index 1040433663361f..5b5018a63731ab 100644 --- a/Python/ceval_gil.c +++ b/Python/ceval_gil.c @@ -1193,6 +1193,29 @@ _PyEval_DisableGIL(PyThreadState *tstate) #endif #if defined(Py_REMOTE_DEBUG) && defined(Py_SUPPORTS_REMOTE_DEBUG) +// Note that this function is inline to avoid creating a PLT entry +// that would be an easy target for a ROP gadget. +static inline int run_remote_debugger_source(PyObject *source) +{ + const char *str = PyBytes_AsString(source); + if (!str) { + return -1; + } + + PyObject *ns = PyDict_New(); + if (!ns) { + return -1; + } + + PyObject *res = PyRun_String(str, Py_file_input, ns, ns); + Py_DECREF(ns); + if (!res) { + return -1; + } + Py_DECREF(res); + return 0; +} + // Note that this function is inline to avoid creating a PLT entry // that would be an easy target for a ROP gadget. static inline void run_remote_debugger_script(const char *path) @@ -1225,22 +1248,11 @@ static inline void run_remote_debugger_script(const char *path) Py_DECREF(fileobj); if (source) { - const char *str = PyBytes_AsString(source); - if (str) { - // PyRun_SimpleString() automatically raises an unraisable - // exception if it fails so we don't need to check the return value. - PyRun_SimpleString(str); - } else { - PyErr_FormatUnraisable("Error reading debugger script %s", path); + if (0 != run_remote_debugger_source(source)) { + PyErr_FormatUnraisable("Error executing debugger script %s", path); } Py_DECREF(source); } - - // Just in case something went wrong, don't leave this function - // with an unhandled exception. - if (PyErr_Occurred()) { - PyErr_FormatUnraisable("Error executing debugger script %s", path); - } } int _PyRunRemoteDebugger(PyThreadState *tstate) _______________________________________________ Python-checkins mailing list -- python-checkins@python.org To unsubscribe send an email to python-checkins-le...@python.org https://mail.python.org/mailman3/lists/python-checkins.python.org/ Member address: arch...@mail-archive.com