gh-135543: Emit sys.remote_exec audit event when sys.remote_exec is called (GH-135544)

(cherry picked from commit 1ddfe59320)

Co-authored-by: Nadeshiko Manju <me@manjusaka.me>
This commit is contained in:
Miss Islington (bot) 2025-06-19 22:51:06 +02:00 committed by GitHub
parent 83e0ab17f6
commit 0370cb42da
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 79 additions and 20 deletions

View 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

View 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

View 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
@ -3070,6 +3071,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.

View 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()

View file

@ -1942,22 +1942,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):
@ -2116,7 +2101,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):

View file

@ -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``.

View 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;

View file

@ -2487,6 +2487,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;