From 749797171e63fee658e247733a2d8ba90c06f6b0 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado Date: Wed, 11 Dec 2024 16:24:01 +0100 Subject: [PATCH] PEP 768: Add some minor changes to the APIs and some clarifications (#4162) --- peps/pep-0768.rst | 80 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 61 insertions(+), 19 deletions(-) diff --git a/peps/pep-0768.rst b/peps/pep-0768.rst index 6d347ba3f..aa9766f47 100644 --- a/peps/pep-0768.rst +++ b/peps/pep-0768.rst @@ -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 ------------------- @@ -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 @@ -232,6 +239,11 @@ is checked. } +If the code being executed raises any Python exception it will be processed as +an `unraisable exception +`__ in +the thread where the code was executed. + Python API ---------- @@ -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: @@ -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}") @@ -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 ===================== @@ -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() `__ + and `process_vm_writev() `__ 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). + 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 +* On macOS, the interface would leverage `mach_vm_read_overwrite() `__ and + `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() `__ + and `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 `__ + and `PROCESS_VM_WRITE `__ 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. @@ -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. -It’s 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. @@ -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 `__ +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 ================= @@ -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 +`__. + +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