Skip to content

Commit 1ddfe59

Browse files
authored
gh-135543: Emit sys.remote_exec audit event when sys.remote_exec is called (GH-135544)
1 parent bb9596f commit 1ddfe59

File tree

8 files changed

+79
-20
lines changed

8 files changed

+79
-20
lines changed

Doc/library/sys.rst

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1933,12 +1933,21 @@ always available. Unless explicitly noted otherwise, all variables are read-only
19331933
interpreter is pre-release (alpha, beta, or release candidate) then the
19341934
local and remote interpreters must be the same exact version.
19351935

1936-
.. audit-event:: remote_debugger_script script_path
1936+
.. audit-event:: sys.remote_exec pid script_path
1937+
1938+
When the code is executed in the remote process, an
1939+
:ref:`auditing event <auditing>` ``sys.remote_exec`` is raised with
1940+
the *pid* and the path to the script file.
1941+
This event is raised in the process that called :func:`sys.remote_exec`.
1942+
1943+
.. audit-event:: cpython.remote_debugger_script script_path
19371944

19381945
When the script is executed in the remote process, an
19391946
:ref:`auditing event <auditing>`
1940-
``sys.remote_debugger_script`` is raised
1947+
``cpython.remote_debugger_script`` is raised
19411948
with the path in the remote process.
1949+
This event is raised in the remote process, not the one
1950+
that called :func:`sys.remote_exec`.
19421951

19431952
.. availability:: Unix, Windows.
19441953
.. versionadded:: 3.14

Lib/test/audit-tests.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,34 @@ def test_assert_unicode():
643643
else:
644644
raise RuntimeError("Expected sys.audit(9) to fail.")
645645

646+
def test_sys_remote_exec():
647+
import tempfile
648+
649+
pid = os.getpid()
650+
event_pid = -1
651+
event_script_path = ""
652+
remote_event_script_path = ""
653+
def hook(event, args):
654+
if event not in ["sys.remote_exec", "cpython.remote_debugger_script"]:
655+
return
656+
print(event, args)
657+
match event:
658+
case "sys.remote_exec":
659+
nonlocal event_pid, event_script_path
660+
event_pid = args[0]
661+
event_script_path = args[1]
662+
case "cpython.remote_debugger_script":
663+
nonlocal remote_event_script_path
664+
remote_event_script_path = args[0]
665+
666+
sys.addaudithook(hook)
667+
with tempfile.NamedTemporaryFile(mode='w+', delete=True) as tmp_file:
668+
tmp_file.write("a = 1+1\n")
669+
tmp_file.flush()
670+
sys.remote_exec(pid, tmp_file.name)
671+
assertEqual(event_pid, pid)
672+
assertEqual(event_script_path, tmp_file.name)
673+
assertEqual(remote_event_script_path, tmp_file.name)
646674

647675
if __name__ == "__main__":
648676
from test.support import suppress_msvcrt_asserts

Lib/test/support/__init__.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
# sys
4747
"MS_WINDOWS", "is_jython", "is_android", "is_emscripten", "is_wasi",
4848
"is_apple_mobile", "check_impl_detail", "unix_shell", "setswitchinterval",
49+
"support_remote_exec_only",
4950
# os
5051
"get_pagesize",
5152
# network
@@ -3069,6 +3070,27 @@ def is_libssl_fips_mode():
30693070
return False # more of a maybe, unless we add this to the _ssl module.
30703071
return get_fips_mode() != 0
30713072

3073+
def _supports_remote_attaching():
3074+
PROCESS_VM_READV_SUPPORTED = False
3075+
3076+
try:
3077+
from _remote_debugging import PROCESS_VM_READV_SUPPORTED
3078+
except ImportError:
3079+
pass
3080+
3081+
return PROCESS_VM_READV_SUPPORTED
3082+
3083+
def _support_remote_exec_only_impl():
3084+
if not sys.is_remote_debug_enabled():
3085+
return unittest.skip("Remote debugging is not enabled")
3086+
if sys.platform not in ("darwin", "linux", "win32"):
3087+
return unittest.skip("Test only runs on Linux, Windows and macOS")
3088+
if sys.platform == "linux" and not _supports_remote_attaching():
3089+
return unittest.skip("Test only runs on Linux with process_vm_readv support")
3090+
return _id
3091+
3092+
def support_remote_exec_only(test):
3093+
return _support_remote_exec_only_impl()(test)
30723094

30733095
class EqualToForwardRef:
30743096
"""Helper to ease use of annotationlib.ForwardRef in tests.

Lib/test/test_audit.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,14 @@ def test_assert_unicode(self):
322322
if returncode:
323323
self.fail(stderr)
324324

325+
@support.support_remote_exec_only
326+
@support.cpython_only
327+
def test_sys_remote_exec(self):
328+
returncode, events, stderr = self.run_python("test_sys_remote_exec")
329+
self.assertTrue(any(["sys.remote_exec" in event for event in events]))
330+
self.assertTrue(any(["cpython.remote_debugger_script" in event for event in events]))
331+
if returncode:
332+
self.fail(stderr)
325333

326334
if __name__ == "__main__":
327335
unittest.main()

Lib/test/test_sys.py

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1943,22 +1943,7 @@ def write(self, s):
19431943
self.assertEqual(out, b"")
19441944
self.assertEqual(err, b"")
19451945

1946-
1947-
def _supports_remote_attaching():
1948-
PROCESS_VM_READV_SUPPORTED = False
1949-
1950-
try:
1951-
from _remote_debugging import PROCESS_VM_READV_SUPPORTED
1952-
except ImportError:
1953-
pass
1954-
1955-
return PROCESS_VM_READV_SUPPORTED
1956-
1957-
@unittest.skipIf(not sys.is_remote_debug_enabled(), "Remote debugging is not enabled")
1958-
@unittest.skipIf(sys.platform != "darwin" and sys.platform != "linux" and sys.platform != "win32",
1959-
"Test only runs on Linux, Windows and MacOS")
1960-
@unittest.skipIf(sys.platform == "linux" and not _supports_remote_attaching(),
1961-
"Test only runs on Linux with process_vm_readv support")
1946+
@test.support.support_remote_exec_only
19621947
@test.support.cpython_only
19631948
class TestRemoteExec(unittest.TestCase):
19641949
def tearDown(self):
@@ -2117,7 +2102,7 @@ def audit_hook(event, arg):
21172102
returncode, stdout, stderr = self._run_remote_exec_test(script, prologue=prologue)
21182103
self.assertEqual(returncode, 0)
21192104
self.assertIn(b"Remote script executed successfully!", stdout)
2120-
self.assertIn(b"Audit event: remote_debugger_script, arg: ", stdout)
2105+
self.assertIn(b"Audit event: cpython.remote_debugger_script, arg: ", stdout)
21212106
self.assertEqual(stderr, b"")
21222107

21232108
def test_remote_exec_with_exception(self):
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Emit ``sys.remote_exec`` audit event when :func:`sys.remote_exec` is called
2+
and migrate ``remote_debugger_script`` to ``cpython.remote_debugger_script``.

Python/ceval_gil.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1220,7 +1220,7 @@ static inline int run_remote_debugger_source(PyObject *source)
12201220
// that would be an easy target for a ROP gadget.
12211221
static inline void run_remote_debugger_script(PyObject *path)
12221222
{
1223-
if (0 != PySys_Audit("remote_debugger_script", "O", path)) {
1223+
if (0 != PySys_Audit("cpython.remote_debugger_script", "O", path)) {
12241224
PyErr_FormatUnraisable(
12251225
"Audit hook failed for remote debugger script %U", path);
12261226
return;

Python/sysmodule.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2488,6 +2488,11 @@ sys_remote_exec_impl(PyObject *module, int pid, PyObject *script)
24882488
if (PyUnicode_FSConverter(script, &path) == 0) {
24892489
return NULL;
24902490
}
2491+
2492+
if (PySys_Audit("sys.remote_exec", "iO", pid, script) < 0) {
2493+
return NULL;
2494+
}
2495+
24912496
debugger_script_path = PyBytes_AS_STRING(path);
24922497
#ifdef MS_WINDOWS
24932498
PyObject *unicode_path;

0 commit comments

Comments
 (0)