Skip to content

Commit f8b3777

Browse files
authored
PEP 768: Flesh out the security design (#4169)
1 parent 2ffe706 commit f8b3777

File tree

1 file changed

+58
-32
lines changed

1 file changed

+58
-32
lines changed

peps/pep-0768.rst

Lines changed: 58 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -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

200200
5. 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

222222
When 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.
292305
Security 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:
327345
1. Only authorized processes can read/write memory
328346
2. The same security model that governs traditional debugger attachment applies
329347
3. 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

331351
The memory operations themselves are well-established and have been used safely
332352
for decades in tools like GDB, LLDB, and various system profilers.
333353

334354
It'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

339360
Further, the strict reliance on OS-level security controls ensures that existing
340361
system policies remain effective. For enterprise environments, this means
@@ -345,7 +366,7 @@ or macOS's ``taskgated`` to restrict debugger access will equally govern the
345366
proposed interface.
346367

347368
By 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

350371
How to Teach This
351372
=================
@@ -369,17 +390,22 @@ can be found `here
369390
Rejected 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

384410
Thanks
385411
======

0 commit comments

Comments
 (0)