Skip to content

Commit 289fd2c

Browse files
authored
gh-148178: Validate remote debug offset tables on load (#148187)
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.
1 parent 4adffd9 commit 289fd2c

File tree

6 files changed

+511
-7
lines changed

6 files changed

+511
-7
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Hardened :mod:`!_remote_debugging` by validating remote debug offset tables
2+
before using them to size memory reads or interpret remote layouts.

Modules/_remote_debugging/_remote_debugging.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,10 @@ typedef enum _WIN32_THREADSTATE {
120120
* MACROS AND CONSTANTS
121121
* ============================================================================ */
122122

123-
#define GET_MEMBER(type, obj, offset) (*(type*)((char*)(obj) + (offset)))
123+
#define GET_MEMBER(type, obj, offset) \
124+
(*(const type *)memcpy(&(type){0}, (const char *)(obj) + (offset), sizeof(type)))
124125
#define CLEAR_PTR_TAG(ptr) (((uintptr_t)(ptr) & ~Py_TAG_BITS))
125-
#define GET_MEMBER_NO_TAG(type, obj, offset) (type)(CLEAR_PTR_TAG(*(type*)((char*)(obj) + (offset))))
126+
#define GET_MEMBER_NO_TAG(type, obj, offset) (type)(CLEAR_PTR_TAG(GET_MEMBER(type, obj, offset)))
126127

127128
/* Size macros for opaque buffers */
128129
#define SIZEOF_BYTES_OBJ sizeof(PyBytesObject)
@@ -417,6 +418,7 @@ extern void cached_code_metadata_destroy(void *ptr);
417418
/* Validation */
418419
extern int is_prerelease_version(uint64_t version);
419420
extern int validate_debug_offsets(struct _Py_DebugOffsets *debug_offsets);
421+
#define PY_REMOTE_DEBUG_INVALID_ASYNC_DEBUG_OFFSETS (-2)
420422

421423
/* ============================================================================
422424
* MEMORY READING FUNCTION DECLARATIONS

Modules/_remote_debugging/asyncio.c

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
******************************************************************************/
77

88
#include "_remote_debugging.h"
9+
#include "debug_offsets_validation.h"
910

1011
/* ============================================================================
1112
* ASYNCIO DEBUG ADDRESS FUNCTIONS
@@ -71,8 +72,13 @@ read_async_debug(RemoteUnwinderObject *unwinder)
7172
int result = _Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, async_debug_addr, size, &unwinder->async_debug_offsets);
7273
if (result < 0) {
7374
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read AsyncioDebug offsets");
75+
return result;
7476
}
75-
return result;
77+
if (_PyRemoteDebug_ValidateAsyncDebugOffsets(&unwinder->async_debug_offsets) < 0) {
78+
set_exception_cause(unwinder, PyExc_RuntimeError, "Invalid AsyncioDebug offsets");
79+
return PY_REMOTE_DEBUG_INVALID_ASYNC_DEBUG_OFFSETS;
80+
}
81+
return 0;
7682
}
7783

7884
int
@@ -85,7 +91,11 @@ ensure_async_debug_offsets(RemoteUnwinderObject *unwinder)
8591

8692
// Try to load async debug offsets (the target process may have
8793
// loaded asyncio since we last checked)
88-
if (read_async_debug(unwinder) < 0) {
94+
int result = read_async_debug(unwinder);
95+
if (result == PY_REMOTE_DEBUG_INVALID_ASYNC_DEBUG_OFFSETS) {
96+
return -1;
97+
}
98+
if (result < 0) {
8999
PyErr_Clear();
90100
PyErr_SetString(PyExc_RuntimeError, "AsyncioDebug section not available");
91101
set_exception_cause(unwinder, PyExc_RuntimeError,

0 commit comments

Comments
 (0)