@@ -171,7 +171,7 @@ debugger support:
171171 uint64_t eval_breaker; // Location of the eval breaker flag
172172 uint64_t remote_debugger_support; // Offset to our support structure
173173 uint64_t debugger_pending_call; // Where to write the pending flag
174- uint64_t debugger_script; // Where to write the script
174+ uint64_t debugger_script; // Where to write the script path
175175 } debugger_support;
176176
177177 These offsets allow debuggers to locate critical debugging control structures in
@@ -199,8 +199,8 @@ When a debugger wants to attach to a Python process, it follows these steps:
199199
2002005. Write control information:
201201
202- - Write a string of Python code to be executed into the `` debugger_script ``
203- field in ``_PyRemoteDebuggerSupport ``.
202+ - Write a filename containing Python code to be executed into the
203+ `` debugger_script `` field in ``_PyRemoteDebuggerSupport ``.
204204 - Set ``debugger_pending_call `` flag in ``_PyRemoteDebuggerSupport ``
205205 - Set ``_PY_EVAL_PLEASE_STOP_BIT `` in the ``eval_breaker `` field
206206
@@ -220,22 +220,35 @@ normal execution, allowing modern CPUs to effectively speculate past it.
220220
221221
222222When a debugger has set both the ``eval_breaker `` flag and ``debugger_pending_call ``,
223- the interpreter will execute the provided debugging code at the next safe point
224- and executes the provided code. This all happens in a completely safe context, since
225- the interpreter is guaranteed to be in a consistent state whenever the eval breaker
226- is checked.
223+ the interpreter will execute the provided debugging code at the next safe point.
224+ This all happens in a completely safe context, since the interpreter is
225+ guaranteed to be in a consistent state whenever the eval breaker is checked.
226+
227+ An audit event will be raised before the code is executed, allowing this mechanism
228+ to be audited or disabled if desired by a system's administrator.
227229
228230.. code-block :: c
229231
230232 // In ceval.c
231233 if (tstate->eval_breaker) {
232234 if (tstate->remote_debugger_support.debugger_pending_call) {
233235 tstate->remote_debugger_support.debugger_pending_call = 0;
234- if (tstate->remote_debugger_support.debugger_script[0]) {
235- if (PyRun_SimpleString(tstate->remote_debugger_support.debugger_script)<0) {
236- PyErr_Clear();
237- };
238- // ...
236+ const char *path = tstate->remote_debugger_support.debugger_script;
237+ if (*path) {
238+ if (0 != PySys_Audit("debugger_script", "%s", path)) {
239+ PyErr_Clear();
240+ } else {
241+ FILE* f = fopen(path, "r");
242+ if (!f) {
243+ PyErr_SetFromErrno(OSError);
244+ } else {
245+ PyRun_AnyFile(f, path);
246+ fclose(f);
247+ }
248+ if (PyErr_Occurred()) {
249+ PyErr_WriteUnraisable(...);
250+ }
251+ }
239252 }
240253 }
241254 }
@@ -292,11 +305,16 @@ mechanism piggybacks on existing interpreter safe points.
292305Security Implications
293306=====================
294307
295- This interface does not introduce new security concerns as it relies entirely on
296- existing operating system security mechanisms for process memory access. Although
297- the PEP doesn't specify how memory should be written to the target process, in practice
298- this will be done using standard system calls that are already being used by other
299- debuggers and tools. Some examples are:
308+ This interface does not introduce new security concerns as it is only usable by
309+ processes that can already write to arbitrary memory within your process and
310+ execute arbitrary code on the machine (in order to create the file containing
311+ the Python code to be executed).
312+
313+ Existing operating system security mechanisms are effective for guarding
314+ against attackers gaining arbitrary memory write access. Although the PEP
315+ doesn't specify how memory should be written to the target process, in practice
316+ this will be done using standard system calls that are already being used by
317+ other debuggers and tools. Some examples are:
300318
301319* On Linux, the `process_vm_readv() <https://man7.org/linux/man-pages/man2/process_vm_readv.2.html >`__
302320 and `process_vm_writev() <https://man7.org/linux/man-pages/man2/process_vm_writev.2.html >`__ system calls
@@ -327,14 +345,17 @@ All mechanisms ensure that:
3273451. Only authorized processes can read/write memory
3283462. The same security model that governs traditional debugger attachment applies
3293473. No additional attack surface is exposed beyond what the OS already provides for debugging
348+ 4. Even if an attacker can write arbitrary memory, they cannot escalate this
349+ to arbitrary code execution unless they already have filesystem access
330350
331351The memory operations themselves are well-established and have been used safely
332352for decades in tools like GDB, LLDB, and various system profilers.
333353
334354It's important to note that any attempt to attach to a Python process via this
335- mechanism would be detectable by system-level monitoring tools. This
336- transparency provides an additional layer of accountability, allowing
337- administrators to audit debugging operations in sensitive environments.
355+ mechanism would be detectable by system-level monitoring tools as well as by
356+ Python audit hooks. This transparency provides an additional layer of
357+ accountability, allowing administrators to audit debugging operations in
358+ sensitive environments.
338359
339360Further, the strict reliance on OS-level security controls ensures that existing
340361system policies remain effective. For enterprise environments, this means
@@ -345,7 +366,7 @@ or macOS's ``taskgated`` to restrict debugger access will equally govern the
345366proposed interface.
346367
347368By maintaining compatibility with existing security frameworks, this design
348- ensures that adopting the new interface requires no changes to established
369+ ensures that adopting the new interface requires no changes to established.
349370
350371How to Teach This
351372=================
@@ -369,17 +390,22 @@ can be found `here
369390Rejected Ideas
370391==============
371392
372- Using a path as the debugger input
373- ----------------------------------
374-
375- We have selected that the mechanism for executing remote code is that tools
376- write the code directly in the remote process to eliminate a possible security
377- vulnerability in which the file to be executed can be altered by parties other
378- than the debugger process if permissions are not set correctly or filesystem
379- configurations allow for this to happen. It is also trivial to write code that
380- executes the contents of a file so the current mechanism doesn't disallow tools
381- that want to just execute files to just do so if they are ok with the security
382- profile of such operation.
393+ Writing Python code into the buffer
394+ -----------------------------------
395+
396+ We have chosen to have debuggers write the code to be executed into a file
397+ whose path is written into a buffer in the remote process. This has been deemed
398+ more secure than writing the Python code to be executed itself into a buffer in
399+ the remote process, because it means that an attacker who has gained arbitrary
400+ writes in a process but not arbitrary code execution or file system
401+ manipulation can't escalate to arbitrary code execution through this interface.
402+
403+ This does require the attaching debugger to pay close attention to filesystem
404+ permissions when creating the file containing the code to be executed, however.
405+ If an attacker has the ability to overwrite the file, or to replace a symlink
406+ in the file path to point to somewhere attacker controlled, this would allow
407+ them to force their malicious code to be executed rather than the code the
408+ debugger intends to run.
383409
384410Thanks
385411======
0 commit comments