diff --git a/Misc/NEWS.d/next/Security/2026-04-06-13-55-00.gh-issue-148178.Rs7kLm.rst b/Misc/NEWS.d/next/Security/2026-04-06-13-55-00.gh-issue-148178.Rs7kLm.rst new file mode 100644 index 00000000000000..ed138a54a859de --- /dev/null +++ b/Misc/NEWS.d/next/Security/2026-04-06-13-55-00.gh-issue-148178.Rs7kLm.rst @@ -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. diff --git a/Modules/_remote_debugging/_remote_debugging.h b/Modules/_remote_debugging/_remote_debugging.h index 3722273dfd2998..aaa9d515a3b909 100644 --- a/Modules/_remote_debugging/_remote_debugging.h +++ b/Modules/_remote_debugging/_remote_debugging.h @@ -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 diff --git a/Modules/_remote_debugging/asyncio.c b/Modules/_remote_debugging/asyncio.c index 263c502a857004..fc7487d4044bfb 100644 --- a/Modules/_remote_debugging/asyncio.c +++ b/Modules/_remote_debugging/asyncio.c @@ -6,6 +6,7 @@ ******************************************************************************/ #include "_remote_debugging.h" +#include "debug_offsets_validation.h" /* ============================================================================ * ASYNCIO DEBUG ADDRESS FUNCTIONS @@ -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 @@ -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, diff --git a/Modules/_remote_debugging/debug_offsets_validation.h b/Modules/_remote_debugging/debug_offsets_validation.h new file mode 100644 index 00000000000000..bd264caa5ad643 --- /dev/null +++ b/Modules/_remote_debugging/debug_offsets_validation.h @@ -0,0 +1,430 @@ +#ifndef Py_REMOTE_DEBUG_OFFSETS_VALIDATION_H +#define Py_REMOTE_DEBUG_OFFSETS_VALIDATION_H + +/* + * The remote debugging tables are read from the target process and must be + * treated as untrusted input. This header centralizes the one-time validation + * that runs immediately after those tables are read, before the unwinder uses + * any reported sizes or offsets to copy remote structs into fixed local + * buffers or to interpret those local copies. + * + * The key rule is simple: every offset that is later dereferenced against a + * local buffer or local object view must appear in one of the field lists + * below. Validation then checks two bounds for each field: + * + * 1. The field must fit within the section size reported by the target. + * 2. The same field must also fit within the local buffer or local layout the + * debugger will actually use. + * + * Sections that are copied into fixed local buffers also have their reported + * size checked against the corresponding local buffer size up front. + * + * This is intentionally front-loaded. Once validation succeeds, the hot path + * can keep using the raw offsets without adding per-sample bounds checks. + * + * Maintenance rule: if either exported table grows, the static_asserts below + * should yell at you. When that happens, update the matching field lists in + * this file in the same change. And if you add a new field that the unwinder + * is going to poke at later, put it in the right list here too, so nobody has + * to rediscover this the annoying way. + */ +#define FIELD_SIZE(type, member) sizeof(((type *)0)->member) + +enum { + PY_REMOTE_DEBUG_OFFSETS_TOTAL_SIZE = 840, + PY_REMOTE_ASYNC_DEBUG_OFFSETS_TOTAL_SIZE = 104, +}; + +/* + * These asserts are the coordination tripwire for table growth. If either + * exported table changes size, update the validation lists below in the same + * change. + */ +static_assert( + sizeof(_Py_DebugOffsets) == PY_REMOTE_DEBUG_OFFSETS_TOTAL_SIZE, + "Update _remote_debugging validation for _Py_DebugOffsets"); +static_assert( + sizeof(struct _Py_AsyncioModuleDebugOffsets) == + PY_REMOTE_ASYNC_DEBUG_OFFSETS_TOTAL_SIZE, + "Update _remote_debugging validation for _Py_AsyncioModuleDebugOffsets"); + +/* + * This logic lives in a private header because it is shared by module.c and + * asyncio.c. Keep the helpers static inline so they stay local to those users + * without adding another compilation unit or exported symbols. + */ +static inline int +validate_section_size(const char *section_name, uint64_t size) +{ + if (size == 0) { + PyErr_Format( + PyExc_RuntimeError, + "Invalid debug offsets: %s.size must be greater than zero", + section_name); + return -1; + } + return 0; +} + +static inline int +validate_read_size(const char *section_name, uint64_t size, size_t buffer_size) +{ + if (validate_section_size(section_name, size) < 0) { + return -1; + } + if (size > buffer_size) { + PyErr_Format( + PyExc_RuntimeError, + "Invalid debug offsets: %s.size=%llu exceeds local buffer size %zu", + section_name, + (unsigned long long)size, + buffer_size); + return -1; + } + return 0; +} + +static inline int +validate_span( + const char *field_name, + uint64_t offset, + size_t width, + uint64_t limit, + const char *limit_name) +{ + uint64_t span = (uint64_t)width; + if (span > limit || offset > limit - span) { + PyErr_Format( + PyExc_RuntimeError, + "Invalid debug offsets: %s=%llu with width %zu exceeds %s %llu", + field_name, + (unsigned long long)offset, + width, + limit_name, + (unsigned long long)limit); + return -1; + } + return 0; +} + +static inline int +validate_field( + const char *field_name, + uint64_t reported_size, + uint64_t offset, + size_t width, + size_t buffer_size) +{ + if (validate_span(field_name, offset, width, reported_size, "reported size") < 0) { + return -1; + } + return validate_span(field_name, offset, width, buffer_size, "local buffer size"); +} + +static inline int +validate_nested_field( + const char *field_name, + uint64_t reported_size, + uint64_t base_offset, + uint64_t nested_offset, + size_t width, + size_t buffer_size) +{ + if (base_offset > UINT64_MAX - nested_offset) { + PyErr_Format( + PyExc_RuntimeError, + "Invalid debug offsets: %s overflows the offset calculation", + field_name); + return -1; + } + return validate_field( + field_name, + reported_size, + base_offset + nested_offset, + width, + buffer_size); +} + +static inline int +validate_fixed_field( + const char *field_name, + uint64_t offset, + size_t width, + size_t buffer_size) +{ + return validate_span(field_name, offset, width, buffer_size, "local buffer size"); +} + +#define PY_REMOTE_DEBUG_VALIDATE_SECTION(section) \ + do { \ + if (validate_section_size(#section, debug_offsets->section.size) < 0) { \ + return -1; \ + } \ + } while (0) + +#define PY_REMOTE_DEBUG_VALIDATE_READ_SECTION(section, buffer_size) \ + do { \ + if (validate_read_size(#section, debug_offsets->section.size, buffer_size) < 0) { \ + return -1; \ + } \ + } while (0) + +#define PY_REMOTE_DEBUG_VALIDATE_FIELD(section, field, field_size, buffer_size) \ + do { \ + if (validate_field( \ + #section "." #field, \ + debug_offsets->section.size, \ + debug_offsets->section.field, \ + field_size, \ + buffer_size) < 0) { \ + return -1; \ + } \ + } while (0) + +#define PY_REMOTE_DEBUG_VALIDATE_NESTED_FIELD(section, base, nested_section, field, field_size, buffer_size) \ + do { \ + if (validate_nested_field( \ + #section "." #base "." #field, \ + debug_offsets->section.size, \ + debug_offsets->section.base, \ + debug_offsets->nested_section.field, \ + field_size, \ + buffer_size) < 0) { \ + return -1; \ + } \ + } while (0) + +#define PY_REMOTE_DEBUG_VALIDATE_FIXED_FIELD(section, field, field_size, buffer_size) \ + do { \ + if (validate_fixed_field( \ + #section "." #field, \ + debug_offsets->section.field, \ + field_size, \ + buffer_size) < 0) { \ + return -1; \ + } \ + } while (0) + +/* + * Each list below must include every offset that is later dereferenced against + * a local buffer or local object view. The validator checks that each field + * stays within both the remote table's reported section size and the local + * buffer size we use when reading that section. If a new dereferenced field is + * added to the offset tables, add it to the matching list here. + */ +#define PY_REMOTE_DEBUG_RUNTIME_STATE_FIELDS(APPLY, buffer_size) \ + APPLY(runtime_state, interpreters_head, sizeof(uintptr_t), buffer_size) + +#define PY_REMOTE_DEBUG_THREAD_STATE_FIELDS(APPLY, buffer_size) \ + APPLY(thread_state, native_thread_id, sizeof(long), buffer_size); \ + APPLY(thread_state, interp, sizeof(uintptr_t), buffer_size); \ + APPLY(thread_state, datastack_chunk, sizeof(uintptr_t), buffer_size); \ + APPLY(thread_state, status, FIELD_SIZE(PyThreadState, _status), buffer_size); \ + APPLY(thread_state, holds_gil, sizeof(int), buffer_size); \ + APPLY(thread_state, gil_requested, sizeof(int), buffer_size); \ + APPLY(thread_state, current_exception, sizeof(uintptr_t), buffer_size); \ + APPLY(thread_state, thread_id, sizeof(long), buffer_size); \ + APPLY(thread_state, next, sizeof(uintptr_t), buffer_size); \ + APPLY(thread_state, current_frame, sizeof(uintptr_t), buffer_size); \ + APPLY(thread_state, base_frame, sizeof(uintptr_t), buffer_size); \ + APPLY(thread_state, last_profiled_frame, sizeof(uintptr_t), buffer_size) + +#define PY_REMOTE_DEBUG_INTERPRETER_STATE_FIELDS(APPLY, buffer_size) \ + APPLY(interpreter_state, id, sizeof(int64_t), buffer_size); \ + APPLY(interpreter_state, next, sizeof(uintptr_t), buffer_size); \ + APPLY(interpreter_state, threads_head, sizeof(uintptr_t), buffer_size); \ + APPLY(interpreter_state, threads_main, sizeof(uintptr_t), buffer_size); \ + APPLY(interpreter_state, gil_runtime_state_locked, sizeof(int), buffer_size); \ + APPLY(interpreter_state, gil_runtime_state_holder, sizeof(PyThreadState *), buffer_size); \ + APPLY(interpreter_state, code_object_generation, sizeof(uint64_t), buffer_size); \ + APPLY(interpreter_state, tlbc_generation, sizeof(uint32_t), buffer_size) + +#define PY_REMOTE_DEBUG_INTERPRETER_FRAME_FIELDS(APPLY, buffer_size) \ + APPLY(interpreter_frame, previous, sizeof(uintptr_t), buffer_size); \ + APPLY(interpreter_frame, executable, sizeof(uintptr_t), buffer_size); \ + APPLY(interpreter_frame, instr_ptr, sizeof(uintptr_t), buffer_size); \ + APPLY(interpreter_frame, owner, sizeof(char), buffer_size); \ + APPLY(interpreter_frame, stackpointer, sizeof(uintptr_t), buffer_size); \ + APPLY(interpreter_frame, tlbc_index, sizeof(int32_t), buffer_size) + +#define PY_REMOTE_DEBUG_CODE_OBJECT_FIELDS(APPLY, buffer_size) \ + APPLY(code_object, qualname, sizeof(uintptr_t), buffer_size); \ + APPLY(code_object, filename, sizeof(uintptr_t), buffer_size); \ + APPLY(code_object, linetable, sizeof(uintptr_t), buffer_size); \ + APPLY(code_object, firstlineno, sizeof(int), buffer_size); \ + APPLY(code_object, co_code_adaptive, sizeof(char), buffer_size); \ + APPLY(code_object, co_tlbc, sizeof(uintptr_t), buffer_size) + +#define PY_REMOTE_DEBUG_SET_OBJECT_FIELDS(APPLY, buffer_size) \ + APPLY(set_object, used, sizeof(Py_ssize_t), buffer_size); \ + APPLY(set_object, mask, sizeof(Py_ssize_t), buffer_size); \ + APPLY(set_object, table, sizeof(uintptr_t), buffer_size) + +#define PY_REMOTE_DEBUG_LONG_OBJECT_FIELDS(APPLY, buffer_size) \ + APPLY(long_object, lv_tag, sizeof(uintptr_t), buffer_size); \ + APPLY(long_object, ob_digit, sizeof(digit), buffer_size) + +#define PY_REMOTE_DEBUG_BYTES_OBJECT_FIELDS(APPLY, buffer_size) \ + APPLY(bytes_object, ob_size, sizeof(Py_ssize_t), buffer_size); \ + APPLY(bytes_object, ob_sval, sizeof(char), buffer_size) + +#define PY_REMOTE_DEBUG_UNICODE_OBJECT_FIELDS(APPLY, buffer_size) \ + APPLY(unicode_object, length, sizeof(Py_ssize_t), buffer_size); \ + APPLY(unicode_object, asciiobject_size, sizeof(char), buffer_size) + +#define PY_REMOTE_DEBUG_GEN_OBJECT_FIELDS(APPLY, buffer_size) \ + APPLY(gen_object, gi_frame_state, sizeof(int8_t), buffer_size); \ + APPLY(gen_object, gi_iframe, FIELD_SIZE(PyGenObject, gi_iframe), buffer_size) + +#define PY_REMOTE_DEBUG_TASK_OBJECT_FIELDS(APPLY, buffer_size) \ + APPLY(asyncio_task_object, task_name, sizeof(uintptr_t), buffer_size); \ + APPLY(asyncio_task_object, task_awaited_by, sizeof(uintptr_t), buffer_size); \ + APPLY(asyncio_task_object, task_is_task, sizeof(char), buffer_size); \ + APPLY(asyncio_task_object, task_awaited_by_is_set, sizeof(char), buffer_size); \ + APPLY(asyncio_task_object, task_coro, sizeof(uintptr_t), buffer_size); \ + APPLY(asyncio_task_object, task_node, SIZEOF_LLIST_NODE, buffer_size) + +#define PY_REMOTE_DEBUG_ASYNC_INTERPRETER_STATE_FIELDS(APPLY, buffer_size) \ + APPLY(asyncio_interpreter_state, asyncio_tasks_head, SIZEOF_LLIST_NODE, buffer_size) + +#define PY_REMOTE_DEBUG_ASYNC_THREAD_STATE_FIELDS(APPLY, buffer_size) \ + APPLY(asyncio_thread_state, asyncio_running_loop, sizeof(uintptr_t), buffer_size); \ + APPLY(asyncio_thread_state, asyncio_running_task, sizeof(uintptr_t), buffer_size); \ + APPLY(asyncio_thread_state, asyncio_tasks_head, SIZEOF_LLIST_NODE, buffer_size) + +/* Called once after reading _Py_DebugOffsets, before any hot-path use. */ +static inline int +_PyRemoteDebug_ValidateDebugOffsetsLayout(struct _Py_DebugOffsets *debug_offsets) +{ + PY_REMOTE_DEBUG_VALIDATE_SECTION(runtime_state); + PY_REMOTE_DEBUG_RUNTIME_STATE_FIELDS( + PY_REMOTE_DEBUG_VALIDATE_FIELD, + sizeof(_PyRuntimeState)); + + PY_REMOTE_DEBUG_VALIDATE_SECTION(interpreter_state); + PY_REMOTE_DEBUG_INTERPRETER_STATE_FIELDS( + PY_REMOTE_DEBUG_VALIDATE_FIELD, + INTERP_STATE_BUFFER_SIZE); + + PY_REMOTE_DEBUG_VALIDATE_READ_SECTION(thread_state, SIZEOF_THREAD_STATE); + PY_REMOTE_DEBUG_THREAD_STATE_FIELDS( + PY_REMOTE_DEBUG_VALIDATE_FIELD, + SIZEOF_THREAD_STATE); + PY_REMOTE_DEBUG_VALIDATE_FIXED_FIELD( + err_stackitem, + exc_value, + sizeof(uintptr_t), + sizeof(_PyErr_StackItem)); + PY_REMOTE_DEBUG_VALIDATE_NESTED_FIELD( + thread_state, + exc_state, + err_stackitem, + exc_value, + sizeof(uintptr_t), + SIZEOF_THREAD_STATE); + + PY_REMOTE_DEBUG_VALIDATE_READ_SECTION(gc, SIZEOF_GC_RUNTIME_STATE); + PY_REMOTE_DEBUG_VALIDATE_FIELD(gc, frame, sizeof(uintptr_t), SIZEOF_GC_RUNTIME_STATE); + PY_REMOTE_DEBUG_VALIDATE_NESTED_FIELD( + interpreter_state, + gc, + gc, + frame, + sizeof(uintptr_t), + INTERP_STATE_BUFFER_SIZE); + + PY_REMOTE_DEBUG_VALIDATE_SECTION(interpreter_frame); + PY_REMOTE_DEBUG_INTERPRETER_FRAME_FIELDS( + PY_REMOTE_DEBUG_VALIDATE_FIELD, + SIZEOF_INTERP_FRAME); + + PY_REMOTE_DEBUG_VALIDATE_SECTION(code_object); + PY_REMOTE_DEBUG_CODE_OBJECT_FIELDS( + PY_REMOTE_DEBUG_VALIDATE_FIELD, + SIZEOF_CODE_OBJ); + + PY_REMOTE_DEBUG_VALIDATE_SECTION(pyobject); + PY_REMOTE_DEBUG_VALIDATE_FIELD(pyobject, ob_type, sizeof(uintptr_t), SIZEOF_PYOBJECT); + + PY_REMOTE_DEBUG_VALIDATE_SECTION(type_object); + PY_REMOTE_DEBUG_VALIDATE_FIELD( + type_object, + tp_flags, + sizeof(unsigned long), + SIZEOF_TYPE_OBJ); + + PY_REMOTE_DEBUG_VALIDATE_SECTION(set_object); + PY_REMOTE_DEBUG_SET_OBJECT_FIELDS( + PY_REMOTE_DEBUG_VALIDATE_FIELD, + SIZEOF_SET_OBJ); + + PY_REMOTE_DEBUG_VALIDATE_READ_SECTION(long_object, SIZEOF_LONG_OBJ); + PY_REMOTE_DEBUG_LONG_OBJECT_FIELDS( + PY_REMOTE_DEBUG_VALIDATE_FIELD, + SIZEOF_LONG_OBJ); + + PY_REMOTE_DEBUG_VALIDATE_SECTION(bytes_object); + PY_REMOTE_DEBUG_BYTES_OBJECT_FIELDS( + PY_REMOTE_DEBUG_VALIDATE_FIELD, + SIZEOF_BYTES_OBJ); + + PY_REMOTE_DEBUG_VALIDATE_SECTION(unicode_object); + PY_REMOTE_DEBUG_UNICODE_OBJECT_FIELDS( + PY_REMOTE_DEBUG_VALIDATE_FIELD, + SIZEOF_UNICODE_OBJ); + + PY_REMOTE_DEBUG_VALIDATE_SECTION(gen_object); + PY_REMOTE_DEBUG_GEN_OBJECT_FIELDS( + PY_REMOTE_DEBUG_VALIDATE_FIELD, + SIZEOF_GEN_OBJ); + + PY_REMOTE_DEBUG_VALIDATE_FIXED_FIELD( + llist_node, + next, + sizeof(uintptr_t), + SIZEOF_LLIST_NODE); + + return 0; +} + +/* Called once when AsyncioDebug is loaded, before any task inspection uses it. */ +static inline int +_PyRemoteDebug_ValidateAsyncDebugOffsets( + struct _Py_AsyncioModuleDebugOffsets *debug_offsets) +{ + PY_REMOTE_DEBUG_VALIDATE_READ_SECTION(asyncio_task_object, SIZEOF_TASK_OBJ); + PY_REMOTE_DEBUG_TASK_OBJECT_FIELDS( + PY_REMOTE_DEBUG_VALIDATE_FIELD, + SIZEOF_TASK_OBJ); + PY_REMOTE_DEBUG_VALIDATE_SECTION(asyncio_interpreter_state); + PY_REMOTE_DEBUG_ASYNC_INTERPRETER_STATE_FIELDS( + PY_REMOTE_DEBUG_VALIDATE_FIELD, + sizeof(PyInterpreterState)); + PY_REMOTE_DEBUG_VALIDATE_SECTION(asyncio_thread_state); + PY_REMOTE_DEBUG_ASYNC_THREAD_STATE_FIELDS( + PY_REMOTE_DEBUG_VALIDATE_FIELD, + sizeof(_PyThreadStateImpl)); + return 0; +} + +#undef PY_REMOTE_DEBUG_VALIDATE_SECTION +#undef PY_REMOTE_DEBUG_VALIDATE_READ_SECTION +#undef PY_REMOTE_DEBUG_VALIDATE_FIELD +#undef PY_REMOTE_DEBUG_VALIDATE_NESTED_FIELD +#undef PY_REMOTE_DEBUG_VALIDATE_FIXED_FIELD +#undef PY_REMOTE_DEBUG_RUNTIME_STATE_FIELDS +#undef PY_REMOTE_DEBUG_THREAD_STATE_FIELDS +#undef PY_REMOTE_DEBUG_INTERPRETER_STATE_FIELDS +#undef PY_REMOTE_DEBUG_INTERPRETER_FRAME_FIELDS +#undef PY_REMOTE_DEBUG_CODE_OBJECT_FIELDS +#undef PY_REMOTE_DEBUG_SET_OBJECT_FIELDS +#undef PY_REMOTE_DEBUG_LONG_OBJECT_FIELDS +#undef PY_REMOTE_DEBUG_BYTES_OBJECT_FIELDS +#undef PY_REMOTE_DEBUG_UNICODE_OBJECT_FIELDS +#undef PY_REMOTE_DEBUG_GEN_OBJECT_FIELDS +#undef PY_REMOTE_DEBUG_TASK_OBJECT_FIELDS +#undef PY_REMOTE_DEBUG_ASYNC_INTERPRETER_STATE_FIELDS +#undef PY_REMOTE_DEBUG_ASYNC_THREAD_STATE_FIELDS +#undef FIELD_SIZE + +#endif diff --git a/Modules/_remote_debugging/frames.c b/Modules/_remote_debugging/frames.c index a0b4a1e8a1e542..bbdfce3f7201d9 100644 --- a/Modules/_remote_debugging/frames.c +++ b/Modules/_remote_debugging/frames.c @@ -148,7 +148,9 @@ find_frame_in_chunks(StackChunkList *chunks, uintptr_t remote_ptr) uintptr_t base = chunks->chunks[i].remote_addr + offsetof(_PyStackChunk, data); size_t payload = chunks->chunks[i].size - offsetof(_PyStackChunk, data); - if (remote_ptr >= base && remote_ptr < base + payload) { + if (payload >= SIZEOF_INTERP_FRAME && + remote_ptr >= base && + remote_ptr <= base + payload - SIZEOF_INTERP_FRAME) { return (char *)chunks->chunks[i].local_copy + (remote_ptr - chunks->chunks[i].remote_addr); } } diff --git a/Modules/_remote_debugging/module.c b/Modules/_remote_debugging/module.c index f86bbf8ce5526e..32f2cbacf2143b 100644 --- a/Modules/_remote_debugging/module.c +++ b/Modules/_remote_debugging/module.c @@ -7,6 +7,7 @@ #include "_remote_debugging.h" #include "binary_io.h" +#include "debug_offsets_validation.h" /* Forward declarations for clinic-generated code */ typedef struct { @@ -240,7 +241,7 @@ validate_debug_offsets(struct _Py_DebugOffsets *debug_offsets) return -1; } - return 0; + return _PyRemoteDebug_ValidateDebugOffsetsLayout(debug_offsets); } /* ============================================================================ @@ -374,7 +375,11 @@ _remote_debugging_RemoteUnwinder___init___impl(RemoteUnwinderObject *self, // Try to read async debug offsets, but don't fail if they're not available self->async_debug_offsets_available = 1; - if (read_async_debug(self) < 0) { + int async_debug_result = read_async_debug(self); + if (async_debug_result == PY_REMOTE_DEBUG_INVALID_ASYNC_DEBUG_OFFSETS) { + return -1; + } + if (async_debug_result < 0) { PyErr_Clear(); memset(&self->async_debug_offsets, 0, sizeof(self->async_debug_offsets)); self->async_debug_offsets_available = 0;