[GH-ISSUE #4914] [Bug] Recorded-crash debugger context fails on GDB 16, which loads the core's executable by itself #3049

Closed
opened 2026-10-31 00:35:30 +00:00 by bsakaguchi · 3 comments
Owner

Originally created by @J0UH on GitHub (Oct 17, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/4914

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Native analysis or decompilation

Observed behavior

inspect_recorded_crash with debugger context fails with GDB 16 whenever the crashed executable still exists at the path the core recorded, which is the usual case:

"stage": "debugger", "reason": "Requested core-only boundary was not established; no mapping query performed."

GDB 16 locates and loads the executable for a core by itself during core-file. bridge/pwndbg/core_context.py then sees gdb.current_progspace().filename set and refuses the session. REA's recorded-crash workflow installs Ubuntu's GDB (15), which doesn't do this, so CI doesn't hit it.

Reproduction

GDB 16.3 (Debian 13), from a directory other than the binary's, with the bridge's settings:

$ printf 'int main(void){ volatile int *p = 0; return *p; }\n' > crash.c && gcc -O0 -o crash crash.c
$ (ulimit -c unlimited; ./crash)          # core_pattern "core"
$ cd /elsewhere && gdb -nx -batch -ex "set auto-load off" -ex "set debuginfod enabled off" \
    -ex "set sysroot ./no-libraries" -ex "set solib-search-path ./no-libraries" \
    -ex "core-file <dir>/core.<pid>" -ex 'python print(gdb.current_progspace().filename)'
<dir>/crash          # None when the binary has been moved away

npm run verify:recorded:crash at 7e4eed9dd fails the same way on this host: inspect-recorded-crash … --debugger-context returns execution_failure with the reason above.

Expected behavior

The debugger stage stays core-only on GDB 16 too, without depending on whether the original executable still exists.

Impact and scope

On GDB 16 and later, core-only debugger context is effectively unavailable for real crashes. The decoder stage (segments, notes, registers) is unaffected.

REA version

6.5.0 at 7e4eed9dd

Runtime and platform

Node.js 26.8.1, Debian 13 x86_64, GDB 16.3 (Debian 16.3-1), pwndbg 2026.09.15 (55596aa), pwntools 4.15.0

Target and artifact context

ELF64 x86-64 core from a small crashing C program, and the core the recorded-crash verifier generates.

Interface

CLI and MCP

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

Running exec-file and then symbol-file with no arguments right after core-file clears progspace.filename and keeps the core connection, and info proc mappings still reads from the core. With that change in the bridge, the verifier gets past the debugger stage. GDB has read the executable by then, though, so this may not meet the core-only intent; a setting that prevents the lookup would be cleaner, and I didn't find one.

With that change applied, the recorded-crash verifier then stops at a second check: Unexpected attempted host execution: execve(".../venv/bin/git", ["git", "--git-dir", ".../src/.git", "rev-parse", "--short", "HEAD"], …). That is pwndbg's pwndbg/lib/version.py build_id(), which runs git when .pwndbg_root sits next to the imported package. pwndbg's gdbinit.py puts the source checkout first on sys.path, as in the workflow's layout, so I'd expect CI to hit this as well once the first problem is gone. I haven't confirmed that.

Analysis

The boundary check is in bridge/pwndbg/core_context.py after gdb.execute("core-file ../object.snapshot", …). Found while running the real suites on a Debian 13 host; also reported alongside #4912.

Originally created by @J0UH on GitHub (Oct 17, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/4914 ### Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Native analysis or decompilation ### Observed behavior `inspect_recorded_crash` with debugger context fails with GDB 16 whenever the crashed executable still exists at the path the core recorded, which is the usual case: ``` "stage": "debugger", "reason": "Requested core-only boundary was not established; no mapping query performed." ``` GDB 16 locates and loads the executable for a core by itself during `core-file`. `bridge/pwndbg/core_context.py` then sees `gdb.current_progspace().filename` set and refuses the session. REA's recorded-crash workflow installs Ubuntu's GDB (15), which doesn't do this, so CI doesn't hit it. ### Reproduction GDB 16.3 (Debian 13), from a directory other than the binary's, with the bridge's settings: ``` $ printf 'int main(void){ volatile int *p = 0; return *p; }\n' > crash.c && gcc -O0 -o crash crash.c $ (ulimit -c unlimited; ./crash) # core_pattern "core" $ cd /elsewhere && gdb -nx -batch -ex "set auto-load off" -ex "set debuginfod enabled off" \ -ex "set sysroot ./no-libraries" -ex "set solib-search-path ./no-libraries" \ -ex "core-file <dir>/core.<pid>" -ex 'python print(gdb.current_progspace().filename)' <dir>/crash # None when the binary has been moved away ``` `npm run verify:recorded:crash` at 7e4eed9dd fails the same way on this host: `inspect-recorded-crash … --debugger-context` returns `execution_failure` with the reason above. ### Expected behavior The debugger stage stays core-only on GDB 16 too, without depending on whether the original executable still exists. ### Impact and scope On GDB 16 and later, core-only debugger context is effectively unavailable for real crashes. The decoder stage (segments, notes, registers) is unaffected. ### REA version 6.5.0 at 7e4eed9dd ### Runtime and platform Node.js 26.8.1, Debian 13 x86_64, GDB 16.3 (Debian 16.3-1), pwndbg 2026.09.15 (55596aa), pwntools 4.15.0 ### Target and artifact context ELF64 x86-64 core from a small crashing C program, and the core the recorded-crash verifier generates. ### Interface CLI and MCP ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence Running `exec-file` and then `symbol-file` with no arguments right after `core-file` clears `progspace.filename` and keeps the `core` connection, and `info proc mappings` still reads from the core. With that change in the bridge, the verifier gets past the debugger stage. GDB has read the executable by then, though, so this may not meet the core-only intent; a setting that prevents the lookup would be cleaner, and I didn't find one. With that change applied, the recorded-crash verifier then stops at a second check: `Unexpected attempted host execution: execve(".../venv/bin/git", ["git", "--git-dir", ".../src/.git", "rev-parse", "--short", "HEAD"], …)`. That is pwndbg's `pwndbg/lib/version.py` `build_id()`, which runs git when `.pwndbg_root` sits next to the imported package. pwndbg's `gdbinit.py` puts the source checkout first on `sys.path`, as in the workflow's layout, so I'd expect CI to hit this as well once the first problem is gone. I haven't confirmed that. ### Analysis The boundary check is in `bridge/pwndbg/core_context.py` after `gdb.execute("core-file ../object.snapshot", …)`. Found while running the real suites on a Debian 13 host; also reported alongside #4912.
Author
Owner

@XinwangTao commented on GitHub (Oct 18, 2026):

Good find — GDB 16 auto-resolving the executable from the core's recorded path silently breaks the core-only boundary, and CI misses it because Ubuntu's GDB 15 doesn't do the lookup. The exec-file + symbol-file with no args trick does clear progspace.filename, but as you note GDB has already read the executable by then, so it doesn't truly satisfy the core-only intent — worth documenting that tradeoff.

A couple of thoughts:

  1. If GDB 16 has no setting to disable the automatic executable lookup during core-file, the bridge could pass an explicit core-file after issuing set solib-search-path/set sysroot to a nonexistent dir (the reproduction already does this) and also set exec-file to a non-existent path before loading the core, so GDB's self-lookup finds nothing. That keeps the core-only claim honest.
  2. The pwndbg build_id() git exec issue is real and orthogonal — the bridge's host-execution guard (no execve of git) should probably whitelist or stub pwndbg's version lookup, or the workflow should install pwndbg from a wheel so .pwndbg_root doesn't sit next to a source checkout. Either way it deserves its own regression test.
<!-- gh-comment-id:6244209242 --> @XinwangTao commented on GitHub (Oct 18, 2026): Good find — GDB 16 auto-resolving the executable from the core's recorded path silently breaks the core-only boundary, and CI misses it because Ubuntu's GDB 15 doesn't do the lookup. The `exec-file` + `symbol-file` with no args trick does clear progspace.filename, but as you note GDB has already read the executable by then, so it doesn't truly satisfy the core-only intent — worth documenting that tradeoff. A couple of thoughts: 1. If GDB 16 has no setting to disable the automatic executable lookup during `core-file`, the bridge could pass an explicit `core-file` after issuing `set solib-search-path`/`set sysroot` to a nonexistent dir (the reproduction already does this) and also set `exec-file` to a non-existent path *before* loading the core, so GDB's self-lookup finds nothing. That keeps the core-only claim honest. 2. The pwndbg build_id() git exec issue is real and orthogonal — the bridge's host-execution guard (no execve of git) should probably whitelist or stub pwndbg's version lookup, or the workflow should install pwndbg from a wheel so `.pwndbg_root` doesn't sit next to a source checkout. Either way it deserves its own regression test.
Author
Owner

@morluto commented on GitHub (Oct 18, 2026):

Fixed on main in 5bf4fffc1 (with related mapping admission hardening in b33c0df02).

GDB now selects its own running executable temporarily to block core-triggered executable discovery, uses the validated decoder architecture explicitly, then discards the placeholder before querying mappings. Build-ID lookup is confined to the owned empty directory. The verifier keeps the original executable present and rejects accesses to it; CI now defines Ubuntu 24.04 and Debian 13/GDB16 lanes. The exact pwndbg version-lookup git invocation was already allowed by the existing verifier.

Evidence: real GDB16.3/strace on Linux arm64 opened the recorded executable with the old bridge, despite a successful core-only reply. The fix returned the same mapping without accessing that file. A controlled cross-architecture gdb-multiarch probe confirmed correct x86-64 selection. These probes used a controlled plugin, not full pwndbg.

Final series: 33 Vitest cases and five Python producer controls pass; typecheck/lint, docs checks and discovery pass. The 38 Linux x64 provider cases and full native pwndbg workflow were not run on this macOS host. The related NT_FILE path escape was reproduced, reported as #4942, and addressed separately. No performance improvement claimed.

<!-- gh-comment-id:6244730195 --> @morluto commented on GitHub (Oct 18, 2026): Fixed on main in 5bf4fffc1 (with related mapping admission hardening in b33c0df02). GDB now selects its own running executable temporarily to block core-triggered executable discovery, uses the validated decoder architecture explicitly, then discards the placeholder before querying mappings. Build-ID lookup is confined to the owned empty directory. The verifier keeps the original executable present and rejects accesses to it; CI now defines Ubuntu 24.04 and Debian 13/GDB16 lanes. The exact pwndbg version-lookup git invocation was already allowed by the existing verifier. Evidence: real GDB16.3/strace on Linux arm64 opened the recorded executable with the old bridge, despite a successful core-only reply. The fix returned the same mapping without accessing that file. A controlled cross-architecture gdb-multiarch probe confirmed correct x86-64 selection. These probes used a controlled plugin, not full pwndbg. Final series: 33 Vitest cases and five Python producer controls pass; typecheck/lint, docs checks and discovery pass. The 38 Linux x64 provider cases and full native pwndbg workflow were not run on this macOS host. The related NT_FILE path escape was reproduced, reported as #4942, and addressed separately. No performance improvement claimed.
Author
Owner

@XinwangTao commented on GitHub (Oct 18, 2026):

Thanks for the thorough fix — picking GDB's own running executable as a temporary placeholder to block core-triggered discovery is a clever way to keep the core-only boundary honest without depending on whether the original binary still exists. Explicitly validating the decoder architecture and discarding the placeholder before querying mappings closes the exact gap I flagged about GDB having already read the executable by then. Good call adding Debian 13 / GDB 16 lanes to CI so this regression is caught on real GDB 16 hosts going forward.

<!-- gh-comment-id:6245456974 --> @XinwangTao commented on GitHub (Oct 18, 2026): Thanks for the thorough fix — picking GDB's own running executable as a temporary placeholder to block core-triggered discovery is a clever way to keep the core-only boundary honest without depending on whether the original binary still exists. Explicitly validating the decoder architecture and discarding the placeholder before querying mappings closes the exact gap I flagged about GDB having already read the executable by then. Good call adding Debian 13 / GDB 16 lanes to CI so this regression is caught on real GDB 16 hosts going forward.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#3049
No description provided.