Skip to content

Commit

Permalink
PEP 768: Add some minor changes to the APIs and some clarifications (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
pablogsal authored Dec 11, 2024
1 parent e4f3216 commit 7497971
Showing 1 changed file with 62 additions and 20 deletions.
82 changes: 62 additions & 20 deletions peps/pep-0768.rst
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,12 @@ are **never accessed during normal execution**. The ``debugger_pending_call`` fi
indicates when a debugger has requested execution, while ``debugger_script``
provides Python code to be executed when the interpreter reaches a safe point.

The value for ``MAX_SCRIPT_SIZE`` will be a trade-off between binary size and
how big debugging scripts can be. As most of the logic should be in libraries
and arbitrary code can be executed with very short ammount of Python we are
proposing to start with 4kb initially. This value can be extended in the future
if we ever need to.


Debug Offsets Table
-------------------
Expand Down Expand Up @@ -191,7 +197,8 @@ When a debugger wants to attach to a Python process, it follows these steps:

5. Write control information:

- Write python code to be executed into the ``debugger_script`` field in ``_PyRemoteDebuggerSupport``
- Write a string of Python code to be executed into the ``debugger_script``
field in ``_PyRemoteDebuggerSupport``.
- Set ``debugger_pending_call`` flag in ``_PyRemoteDebuggerSupport``
- Set ``_PY_EVAL_PLEASE_STOP_BIT`` in the ``eval_breaker`` field

Expand Down Expand Up @@ -232,6 +239,11 @@ is checked.
}
If the code being executed raises any Python exception it will be processed as
an `unraisable exception
<https://docs.python.org/3/c-api/exceptions.html#c.PyErr_WriteUnraisable>`__ in
the thread where the code was executed.

Python API
----------

Expand All @@ -242,13 +254,16 @@ arbitrary Python code within the context of a specified Python process:

.. code-block:: python
def remote_exec(pid: int, code: str) -> None:
def remote_exec(pid: int, code: str, timeout: int = 0) -> None:
"""
Executes a block of Python code in a given remote Python process.
Args:
pid (int): The process ID of the target Python process.
code (str): A string containing the Python code to be executed.
timeout (int): An optional timeout for waiting for the remote
process to execute the code. If the timeout is exceeded a
``TimeoutError`` will be raised.
"""
An example usage of the API would look like:
Expand All @@ -258,7 +273,9 @@ An example usage of the API would look like:
import sys
# Execute a print statement in a remote Python process with PID 12345
try:
sys.remote_exec(12345, "print('Hello from remote execution!')")
sys.remote_exec(12345, "print('Hello from remote execution!')", timeout=3)
except TimeoutError:
print(f"The remote process took too long to execute the code")
except Exception as e:
print(f"Failed to execute code: {e}")
Expand All @@ -270,7 +287,6 @@ This change has no impact on existing Python code or interpreter performance.
The added fields are only accessed during debugger attachment, and the checking
mechanism piggybacks on existing interpreter safe points.


Security Implications
=====================

Expand All @@ -280,23 +296,26 @@ the PEP doesn't specify how memory should be written to the target process, in p
this will be done using standard system calls that are already being used by other
debuggers and tools. Some examples are:

* On Linux, the ``process_vm_readv()`` and ``process_vm_writev()`` system calls
* On Linux, the `process_vm_readv() <https://man7.org/linux/man-pages/man2/process_vm_readv.2.html>`__
and `process_vm_writev() <https://man7.org/linux/man-pages/man2/process_vm_writev.2.html>`__ system calls
are used to read and write memory from another process. These operations are
controlled by ptrace access mode checks - the same ones that govern debugger
attachment. A process can only read from or write to another process's memory
if it has the appropriate permissions (typically requiring either root or the
``CAP_SYS_PTRACE`` capability, though less security minded distributions may
allow any process running as the same uid to attach).

* On macOS, the interface would leverage ``mach_vm_read_overwrite()`` and
``mach_vm_write()`` through the Mach task system. These operations require
controlled by `ptrace <https://man7.org/linux/man-pages/man2/ptrace.2.html>`__ access mode
checks - the same ones that govern debugger attachment. A process can only read from
or write to another process's memory if it has the appropriate permissions (typically
requiring either root or the `CAP_SYS_PTRACE <https://man7.org/linux/man-pages/man7/capabilities.7.html>`__
capability, though less security minded distributions may allow any process running as the same uid to attach).

* On macOS, the interface would leverage `mach_vm_read_overwrite() <https://developer.apple.com/documentation/kernel/1402127-mach_vm_read_overwrite>`__ and
`mach_vm_write() <https://developer.apple.com/documentation/kernel/1402070-mach_vm_write>`__ through the Mach task system. These operations require
``task_for_pid()`` access, which is strictly controlled by the operating
system. By default, access is limited to processes running as root or those
with specific entitlements granted by Apple's security framework.

* On Windows, the ``ReadProcessMemory()`` and ``WriteProcessMemory()`` functions
* On Windows, the `ReadProcessMemory() <https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-readprocessmemory>`__
and `WriteProcessMemory() <https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-writeprocessmemory>`__ functions
provide similar functionality. Access is controlled through the Windows
security model - a process needs ``PROCESS_VM_READ`` and ``PROCESS_VM_WRITE``
security model - a process needs `PROCESS_VM_READ <https://learn.microsoft.com/en-us/windows/win32/procthread/process-security-and-access-rights>`__
and `PROCESS_VM_WRITE <https://learn.microsoft.com/en-us/windows/win32/procthread/process-security-and-access-rights>`__
permissions, which typically require the same user context or appropriate
privileges. These are the same permissions required by debuggers, ensuring
consistent security semantics across platforms.
Expand All @@ -310,7 +329,7 @@ All mechanisms ensure that:
The memory operations themselves are well-established and have been used safely
for decades in tools like GDB, LLDB, and various system profilers.

Its important to note that any attempt to attach to a Python process via this
It's important to note that any attempt to attach to a Python process via this
mechanism would be detectable by system-level monitoring tools. This
transparency provides an additional layer of accountability, allowing
administrators to audit debugging operations in sensitive environments.
Expand All @@ -319,12 +338,12 @@ Further, the strict reliance on OS-level security controls ensures that existing
system policies remain effective. For enterprise environments, this means
administrators can continue to enforce debugging restrictions using standard
tools and policies without requiring additional configuration. For instance,
leveraging Linux’s ``ptrace_scope`` or macOS’s ``taskgated`` to restrict
debugger access will equally govern the proposed interface.
leveraging Linux's `ptrace_scope <https://www.kernel.org/doc/Documentation/security/Yama.txt>`__
or macOS's ``taskgated`` to restrict debugger access will equally govern the
proposed interface.

By maintaining compatibility with existing security frameworks, this design
ensures that adopting the new interface requires no changes to established
security practices, thereby minimizing barriers to adoption.

How to Teach This
=================
Expand All @@ -341,7 +360,30 @@ debugging tool stability and reliability.
Reference Implementation
========================

https://github.com/pablogsal/cpython/commits/remote_pdb/
A reference implementation with a prototype adding remote support for ``pdb``
can be found `here
<https://github.com/pablogsal/cpython/compare/60ff67d010078eca15a74b1429caf779ac4f9c74...remote_pdb>`__.

Rejected Ideas
==============

Using a path as the debugger input
----------------------------------

We have selected that the mechanism for executing remote code is that tools
write the code directly in the remote process to eliminate a possible security
vulnerability in which the file to be executed can be altered by parties other
than the debugger process if permissions are not set correctly or filesystem
configurations allow for this to happen. It is also trivial to write code that
executes the contents of a file so the current mechanism doesn't disallow tools
that want to just execute files to just do so if they are ok with the security
profile of such operation.

Thanks
======

We would like to thank Carl Friedrich Bolz-Tereick for his insightful comments and suggestions
when discussing this proposal.


Copyright
Expand Down

0 comments on commit 7497971

Please sign in to comment.