| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
…verflow The depth-bounded recursion still overflowed the debugger's C stack on platforms with a small default thread stack (Windows uses 1 MiB): every level keeps a SIZEOF_TASK_OBJ buffer alive in process_task_awaited_by, so the MAX_TASK_AWAITED_BY_DEPTH limit of 1000 was only reached after several MiB of stack had already been consumed, and the process aborted with a stack overflow before the limit could fire. Walk the awaited_by graph with an explicit, heap-allocated work-stack instead of mutual recursion, so the C stack depth stays constant no matter how deep the graph is. The depth limit is retained as a cycle guard for corrupted or concurrently-mutated remote memory. Also make the regression test deterministic under load: signal readiness from the leaf task itself, immediately before it busy-spins, so the observer always inspects while the full chain is built and rooted at a running task. The previous handshake was sent before the leaf started running and could race, letting the observer see a shallow graph.
|
This PR is stale because it has been open for 90 days with no activity. |
Sorry, something went wrong.
|
@pablogsal This is fixed by #153365 |
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
_remote_debugging.RemoteUnwinder.get_async_stack_trace() walks the target
process's asyncio awaited_by graph with an unbounded three-function C recursion
(process_task_and_waiters → process_task_awaited_by → process_waiter_task →
process_task_and_waiters, in Modules/_remote_debugging/asyncio.c). Each level
also stack-allocates char task_obj[SIZEOF_TASK_OBJ] (4096 bytes), so a deep or
cyclic graph in the target overflows the debugger's C stack and crashes it with
SIGSEGV. This is asymmetric with the iterative sibling path
(append_awaited_by_for_thread), which already bounds its walk with
MAX_ITERATIONS.
This PR bounds the recursion with an explicit depth cap
(MAX_TASK_AWAITED_BY_DEPTH, defined next to the existing MAX_ITERATIONS /
MAX_SET_TABLE_SIZE / MAX_* constants in _remote_debugging.h). The depth is
threaded through the existing generic processor/context callback via a small
waiter_context_t { result, depth }, so the public process_task_and_waiters
signature is unchanged (it stays a depth == 0 wrapper over the new
process_task_and_waiters_impl). On overflow the code raises a RuntimeError
through the module's existing set_exception_cause error path, matching how the
module reports every other "corrupted remote memory" condition. A depth counter
(rather than a visited-set) also covers a cyclic awaited_by graph from corrupted
remote memory, since each recursion still increments the depth.
The module already documents that the target's memory is untrusted input
(debug_offsets_validation.h), so a debugger reading an attacker-controlled or
merely deeply-nested target must not be crashable by the target's graph shape;
bounding this traversal is consistent with the module's existing invariants.
Adds test_async_deep_awaited_by_chain_is_bounded to
Lib/test/test_external_inspection.py: it spawns a target with a deep linear
awaited_by chain whose leaf is the running task and asserts that
get_async_stack_trace() raises RuntimeError ("too deep or cyclic") instead of
crashing.
Verified that the pre-fix interpreter segfaults (SIGSEGV) on a deep chain while the
patched interpreter raises a clean RuntimeError, and that a shallow chain still
returns a stack trace normally (negative control). The existing asyncio
stack-trace tests continue to pass.
Closes #151535
This change was prepared with AI assistance; I have reviewed and verified the code,
the reasoning, and the test myself.