]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-135543: Emit sys.remote_exec audit event when sys.remote_exec is called (GH-135544)
authorNadeshiko Manju <me@manjusaka.me>
Thu, 19 Jun 2025 20:23:38 +0000 (04:23 +0800)
committerGitHub <noreply@github.com>
Thu, 19 Jun 2025 20:23:38 +0000 (21:23 +0100)
Doc/library/sys.rst
Lib/test/audit-tests.py
Lib/test/support/__init__.py
Lib/test/test_audit.py
Lib/test/test_sys.py
Misc/NEWS.d/next/Core_and_Builtins/2025-06-16-02-31-42.gh-issue-135543.6b0HOF.rst [new file with mode: 0644]
Python/ceval_gil.c
Python/sysmodule.c

index 71f9999464ab524437383acaf6f03b548c7edb16..f8f727f4a23410407e67e1cb3d5db96b22f01b23 100644 (file)
@@ -1933,12 +1933,21 @@ always available. Unless explicitly noted otherwise, all variables are read-only
    interpreter is pre-release (alpha, beta, or release candidate) then the
    local and remote interpreters must be the same exact version.
 
-   .. audit-event:: remote_debugger_script script_path
+   .. audit-event:: sys.remote_exec pid script_path
+
+      When the code is executed in the remote process, an
+      :ref:`auditing event <auditing>` ``sys.remote_exec`` is raised with
+      the *pid* and the path to the script file.
+      This event is raised in the process that called :func:`sys.remote_exec`.
+
+   .. audit-event:: cpython.remote_debugger_script script_path
 
       When the script is executed in the remote process, an
       :ref:`auditing event <auditing>`
-      ``sys.remote_debugger_script`` is raised
+      ``cpython.remote_debugger_script`` is raised
       with the path in the remote process.
+      This event is raised in the remote process, not the one
+      that called :func:`sys.remote_exec`.
 
    .. availability:: Unix, Windows.
    .. versionadded:: 3.14
index 08b638e4b8d524579d45d576e4fff3739686d2bf..6884ac0dbe6ff03b9b0b6d99e4bc4bbe0c8aae2b 100644 (file)
@@ -643,6 +643,34 @@ def test_assert_unicode():
     else:
         raise RuntimeError("Expected sys.audit(9) to fail.")
 
+def test_sys_remote_exec():
+    import tempfile
+
+    pid = os.getpid()
+    event_pid = -1
+    event_script_path = ""
+    remote_event_script_path = ""
+    def hook(event, args):
+        if event not in ["sys.remote_exec", "cpython.remote_debugger_script"]:
+            return
+        print(event, args)
+        match event:
+            case "sys.remote_exec":
+                nonlocal event_pid, event_script_path
+                event_pid = args[0]
+                event_script_path = args[1]
+            case "cpython.remote_debugger_script":
+                nonlocal remote_event_script_path
+                remote_event_script_path = args[0]
+
+    sys.addaudithook(hook)
+    with tempfile.NamedTemporaryFile(mode='w+', delete=True) as tmp_file:
+        tmp_file.write("a = 1+1\n")
+        tmp_file.flush()
+        sys.remote_exec(pid, tmp_file.name)
+        assertEqual(event_pid, pid)
+        assertEqual(event_script_path, tmp_file.name)
+        assertEqual(remote_event_script_path, tmp_file.name)
 
 if __name__ == "__main__":
     from test.support import suppress_msvcrt_asserts
index 48e74adcce3bb751cfabff3b02a7e8071f1f4756..51c0ce11e8269d41a690136f00abe138aba23bc2 100644 (file)
@@ -46,6 +46,7 @@ __all__ = [
     # sys
     "MS_WINDOWS", "is_jython", "is_android", "is_emscripten", "is_wasi",
     "is_apple_mobile", "check_impl_detail", "unix_shell", "setswitchinterval",
+    "support_remote_exec_only",
     # os
     "get_pagesize",
     # network
@@ -3069,6 +3070,27 @@ def is_libssl_fips_mode():
         return False  # more of a maybe, unless we add this to the _ssl module.
     return get_fips_mode() != 0
 
+def _supports_remote_attaching():
+    PROCESS_VM_READV_SUPPORTED = False
+
+    try:
+        from _remote_debugging import PROCESS_VM_READV_SUPPORTED
+    except ImportError:
+        pass
+
+    return PROCESS_VM_READV_SUPPORTED
+
+def _support_remote_exec_only_impl():
+    if not sys.is_remote_debug_enabled():
+        return unittest.skip("Remote debugging is not enabled")
+    if sys.platform not in ("darwin", "linux", "win32"):
+        return unittest.skip("Test only runs on Linux, Windows and macOS")
+    if sys.platform == "linux" and not _supports_remote_attaching():
+        return unittest.skip("Test only runs on Linux with process_vm_readv support")
+    return _id
+
+def support_remote_exec_only(test):
+    return _support_remote_exec_only_impl()(test)
 
 class EqualToForwardRef:
     """Helper to ease use of annotationlib.ForwardRef in tests.
index 5f9eb381f605d92746d8e507885922f48de749d4..077765fcda210a6d39a06437489c7d8a4d475cab 100644 (file)
@@ -322,6 +322,14 @@ class AuditTest(unittest.TestCase):
         if returncode:
             self.fail(stderr)
 
+    @support.support_remote_exec_only
+    @support.cpython_only
+    def test_sys_remote_exec(self):
+        returncode, events, stderr = self.run_python("test_sys_remote_exec")
+        self.assertTrue(any(["sys.remote_exec" in event for event in events]))
+        self.assertTrue(any(["cpython.remote_debugger_script" in event for event in events]))
+        if returncode:
+            self.fail(stderr)
 
 if __name__ == "__main__":
     unittest.main()
index 39e62027f03e5a6f5ca7dd18c7baad1ec5a73d15..73a72024bba84ddbfdbb1f7501ce59969c1979a5 100644 (file)
@@ -1943,22 +1943,7 @@ class SizeofTest(unittest.TestCase):
         self.assertEqual(out, b"")
         self.assertEqual(err, b"")
 
-
-def _supports_remote_attaching():
-    PROCESS_VM_READV_SUPPORTED = False
-
-    try:
-        from _remote_debugging import PROCESS_VM_READV_SUPPORTED
-    except ImportError:
-        pass
-
-    return PROCESS_VM_READV_SUPPORTED
-
-@unittest.skipIf(not sys.is_remote_debug_enabled(), "Remote debugging is not enabled")
-@unittest.skipIf(sys.platform != "darwin" and sys.platform != "linux" and sys.platform != "win32",
-                    "Test only runs on Linux, Windows and MacOS")
-@unittest.skipIf(sys.platform == "linux" and not _supports_remote_attaching(),
-                    "Test only runs on Linux with process_vm_readv support")
+@test.support.support_remote_exec_only
 @test.support.cpython_only
 class TestRemoteExec(unittest.TestCase):
     def tearDown(self):
@@ -2117,7 +2102,7 @@ print("Remote script executed successfully!")
         returncode, stdout, stderr = self._run_remote_exec_test(script, prologue=prologue)
         self.assertEqual(returncode, 0)
         self.assertIn(b"Remote script executed successfully!", stdout)
-        self.assertIn(b"Audit event: remote_debugger_script, arg: ", stdout)
+        self.assertIn(b"Audit event: cpython.remote_debugger_script, arg: ", stdout)
         self.assertEqual(stderr, b"")
 
     def test_remote_exec_with_exception(self):
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-06-16-02-31-42.gh-issue-135543.6b0HOF.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-16-02-31-42.gh-issue-135543.6b0HOF.rst
new file mode 100644 (file)
index 0000000..6efe2a4
--- /dev/null
@@ -0,0 +1,2 @@
+Emit ``sys.remote_exec`` audit event when :func:`sys.remote_exec` is called
+and migrate ``remote_debugger_script`` to ``cpython.remote_debugger_script``.
index 6d2383ac7c1c65ef9003dbfd91e4c921a7faf27c..57d8f68b000b60470ae3d27dcec064dbe9eb74c4 100644 (file)
@@ -1220,7 +1220,7 @@ static inline int run_remote_debugger_source(PyObject *source)
 // that would be an easy target for a ROP gadget.
 static inline void run_remote_debugger_script(PyObject *path)
 {
-    if (0 != PySys_Audit("remote_debugger_script", "O", path)) {
+    if (0 != PySys_Audit("cpython.remote_debugger_script", "O", path)) {
         PyErr_FormatUnraisable(
             "Audit hook failed for remote debugger script %U", path);
         return;
index e5ae841d195d4f9b1abb011c36dac381923380be..b3a2512a99d2fcf20dfb004694d6d4e14c238cb2 100644 (file)
@@ -2488,6 +2488,11 @@ sys_remote_exec_impl(PyObject *module, int pid, PyObject *script)
     if (PyUnicode_FSConverter(script, &path) == 0) {
         return NULL;
     }
+
+    if (PySys_Audit("sys.remote_exec", "iO", pid, script) < 0) {
+        return NULL;
+    }
+
     debugger_script_path = PyBytes_AS_STRING(path);
 #ifdef MS_WINDOWS
     PyObject *unicode_path;