PEP 768: Add some minor changes to the APIs and some clarifications (#4162)
This commit is contained in:
parent
e4f3216938
commit
749797171e
|
@ -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
|
||||
<https://docs.python.org/3/c-api/exceptions.html#c.PyErr_WriteUnraisable>`__ 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() <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).
|
||||
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()`` and
|
||||
``mach_vm_write()`` through the Mach task system. These operations require
|
||||
* 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.
|
||||
|
@ -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 <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
|
||||
=================
|
||||
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue