Skip to content
Next Next commit
gh-148178: Validate remote debug offset tables on load
Treat the debug offset tables read from a target process as untrusted input
and validate them before the unwinder uses any reported sizes or offsets.

Add a shared validator in debug_offsets_validation.h and run it once when
_Py_DebugOffsets is loaded and once when AsyncioDebug is loaded. The checks
cover section sizes used for fixed local buffers and every offset that is
later dereferenced against a local buffer or local object view. This keeps
the bounds checks out of the sampling hot path while rejecting malformed
tables up front.
  • Loading branch information
pablogsal committed Apr 8, 2026
commit f6f2faf578dd7ab28c2e7ff621103518a9544035
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Hardened :mod:`!_remote_debugging` by validating remote debug offset tables
before using them to size memory reads or interpret remote layouts.
1 change: 1 addition & 0 deletions Modules/_remote_debugging/_remote_debugging.h
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,7 @@ extern void cached_code_metadata_destroy(void *ptr);
/* Validation */
extern int is_prerelease_version(uint64_t version);
extern int validate_debug_offsets(struct _Py_DebugOffsets *debug_offsets);
#define PY_REMOTE_DEBUG_INVALID_ASYNC_DEBUG_OFFSETS (-2)

/* ============================================================================
* MEMORY READING FUNCTION DECLARATIONS
Expand Down
14 changes: 12 additions & 2 deletions Modules/_remote_debugging/asyncio.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
******************************************************************************/

#include "_remote_debugging.h"
#include "debug_offsets_validation.h"

/* ============================================================================
* ASYNCIO DEBUG ADDRESS FUNCTIONS
Expand Down Expand Up @@ -71,8 +72,13 @@ read_async_debug(RemoteUnwinderObject *unwinder)
int result = _Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, async_debug_addr, size, &unwinder->async_debug_offsets);
if (result < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read AsyncioDebug offsets");
return result;
}
return result;
if (_PyRemoteDebug_ValidateAsyncDebugOffsets(&unwinder->async_debug_offsets) < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Invalid AsyncioDebug offsets");
return PY_REMOTE_DEBUG_INVALID_ASYNC_DEBUG_OFFSETS;
}
return 0;
}

int
Expand All @@ -85,7 +91,11 @@ ensure_async_debug_offsets(RemoteUnwinderObject *unwinder)

// Try to load async debug offsets (the target process may have
// loaded asyncio since we last checked)
if (read_async_debug(unwinder) < 0) {
int result = read_async_debug(unwinder);
if (result == PY_REMOTE_DEBUG_INVALID_ASYNC_DEBUG_OFFSETS) {
return -1;
}
if (result < 0) {
PyErr_Clear();
PyErr_SetString(PyExc_RuntimeError, "AsyncioDebug section not available");
set_exception_cause(unwinder, PyExc_RuntimeError,
Expand Down
Loading
Loading