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

Reply via email to