[GH-ISSUE #4942] [Bug] Recorded-crash NT_FILE mapping paths can escape the owned debugger sysroot #3065

Closed
opened 2026-10-31 00:35:48 +00:00 by bsakaguchi · 1 comment
Owner

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

Before submitting

  • I searched open and closed reports (NT_FILE, sysroot; related: #4914).
  • The evidence below uses only source-owned temporary files.

Area

Native analysis or decompilation

Observed behavior

A controlled real GDB 16.3 producer probe opened an owned file outside the debugger's empty sysroot from an ELF64 NT_FILE pathname ../../fixture-program. The bridge returned available with executable: null. This remains possible after blocking #4914's separate automatic main-executable discovery.

Reproduction

Ran in a disposable Debian container with GDB 16.3, Linux arm64 and gdb-multiarch:

  1. Create an ELF64 little-endian x86-64 core with an NT_FILE descriptor: count=1, page_size=4096, one 24-byte mapping tuple, then ../../fixture-program\0.
  2. Keep a source-owned regular executable two directories above the debugger cwd.
  3. Run the actual bridge/pwndbg/core_context.py against its owned core snapshot. The controlled plugin registers the required parameter and uses real info proc mappings; it is not qualification of pwndbg itself.
  4. Trace file syscalls. Observed:
openat(AT_FDCWD, "../../fixture-program", O_RDONLY|O_CLOEXEC) = 13
openat(AT_FDCWD, "/tmp/core4914/fixture-program", O_RDONLY) = 13

Only the owned fixture was accessed. Public CLI/MCP reproduction was not run; reachability through their shared provider is source-derived.

Expected behavior

Before launching the optional debugger, establish that source-bound mapping names remain under the owned sysroot. Unknown/malformed or escaping names must refuse enrichment while preserving the basic decoded observation and original bytes. Do not rewrite caller evidence.

Impact and scope

GDB handles relative mapping paths outside sysroot; dot components can also escape a prefixed absolute pathname. progspace.filename is None is not proof that no backing file was read. The ordinary absolute-path fixture verifier does not cover these authored names. Frequency and non-GDB16 behavior: NOT_ENOUGH_INFO.

REA version

Source 847472acf; producer probe also included the pending #4914 executable-slot guard. No mapping admission check existed at that point.

Runtime and platform

Debian GDB 16.3-1, Linux arm64, synthetic x86-64 core, controlled plugin adapter, real GDB mapping query and strace. No full native pwndbg or Linux x64 CLI/MCP qualification.

Target and artifact context

Source-owned core and executable only; no third-party target or live target attachment.

Interface

Direct production Python producer; CLI/MCP reachability is source-derived.

Earliest failure stage

Optional debugger core loading.

Analysis

GDB 16 gdb/solib.c::exec_file_find bypasses sysroot for relative paths; solib_find_1 concatenates absolute names without removing dot segments. gdb/corelow.c::build_file_mappings opens these names independently of main executable discovery. Upstream GDB 16 source.

Originally created by @morluto on GitHub (Oct 18, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/4942 ### Before submitting - [x] I searched open and closed reports (`NT_FILE`, `sysroot`; related: #4914). - [x] The evidence below uses only source-owned temporary files. ### Area Native analysis or decompilation ### Observed behavior A controlled real GDB 16.3 producer probe opened an owned file outside the debugger's empty sysroot from an ELF64 `NT_FILE` pathname `../../fixture-program`. The bridge returned `available` with `executable: null`. This remains possible after blocking #4914's separate automatic main-executable discovery. ### Reproduction Ran in a disposable Debian container with GDB 16.3, Linux arm64 and `gdb-multiarch`: 1. Create an ELF64 little-endian x86-64 core with an `NT_FILE` descriptor: count=1, page_size=4096, one 24-byte mapping tuple, then `../../fixture-program\0`. 2. Keep a source-owned regular executable two directories above the debugger cwd. 3. Run the actual `bridge/pwndbg/core_context.py` against its owned core snapshot. The controlled plugin registers the required parameter and uses real `info proc mappings`; it is not qualification of pwndbg itself. 4. Trace file syscalls. Observed: ```text openat(AT_FDCWD, "../../fixture-program", O_RDONLY|O_CLOEXEC) = 13 openat(AT_FDCWD, "/tmp/core4914/fixture-program", O_RDONLY) = 13 ``` Only the owned fixture was accessed. Public CLI/MCP reproduction was not run; reachability through their shared provider is source-derived. ### Expected behavior Before launching the optional debugger, establish that source-bound mapping names remain under the owned sysroot. Unknown/malformed or escaping names must refuse enrichment while preserving the basic decoded observation and original bytes. Do not rewrite caller evidence. ### Impact and scope GDB handles relative mapping paths outside sysroot; dot components can also escape a prefixed absolute pathname. `progspace.filename is None` is not proof that no backing file was read. The ordinary absolute-path fixture verifier does not cover these authored names. Frequency and non-GDB16 behavior: NOT_ENOUGH_INFO. ### REA version Source 847472acf; producer probe also included the pending #4914 executable-slot guard. No mapping admission check existed at that point. ### Runtime and platform Debian GDB 16.3-1, Linux arm64, synthetic x86-64 core, controlled plugin adapter, real GDB mapping query and strace. No full native pwndbg or Linux x64 CLI/MCP qualification. ### Target and artifact context Source-owned core and executable only; no third-party target or live target attachment. ### Interface Direct production Python producer; CLI/MCP reachability is source-derived. ### Earliest failure stage Optional debugger core loading. ### Analysis GDB 16 `gdb/solib.c::exec_file_find` bypasses sysroot for relative paths; `solib_find_1` concatenates absolute names without removing dot segments. `gdb/corelow.c::build_file_mappings` opens these names independently of main executable discovery. [Upstream GDB 16 source](https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=gdb/solib.c;hb=gdb-16-branch).
bsakaguchi 2026-10-31 00:35:48 +00:00
  • closed this issue
  • added the
    bug
    label
Author
Owner

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

Fixed on main in b33c0df02.

The shared provider now interprets source-bound ELF64 NT_FILE descriptors once before debugger launch. It bounds counts/coverage by the descriptor, preserves pathname bytes, and refuses relative names, dot segments and unresolved coverage. GDB is explicitly configured for Unix pathname semantics. Refusal preserves decoded partial evidence; basic-only inspection remains available. Pre-admission cancellation retains its original reason.

Evidence: real GDB16.3/strace reproduced the escape using only an owned temporary executable. The compiled admission check accepts the real absolute-path core and rejects the relative-path core. Source/structure controls verify that unsafe mapping names remain valid retained evidence while lacking debugger authority.

Final series: 33 passing Vitest cases, five Python producer controls, passing typecheck/lint/docs/discovery. The Linux x64 provider refusal/basic-recovery control is authored but skipped on this macOS host (38 provider cases skipped). Full native pwndbg and Linux x64 CLI/MCP qualification remain unrun; the native probes used a controlled plugin. No CPU/token/family-memory improvement claimed.

<!-- gh-comment-id:6244730425 --> @morluto commented on GitHub (Oct 18, 2026): Fixed on main in b33c0df02. The shared provider now interprets source-bound ELF64 NT_FILE descriptors once before debugger launch. It bounds counts/coverage by the descriptor, preserves pathname bytes, and refuses relative names, dot segments and unresolved coverage. GDB is explicitly configured for Unix pathname semantics. Refusal preserves decoded partial evidence; basic-only inspection remains available. Pre-admission cancellation retains its original reason. Evidence: real GDB16.3/strace reproduced the escape using only an owned temporary executable. The compiled admission check accepts the real absolute-path core and rejects the relative-path core. Source/structure controls verify that unsafe mapping names remain valid retained evidence while lacking debugger authority. Final series: 33 passing Vitest cases, five Python producer controls, passing typecheck/lint/docs/discovery. The Linux x64 provider refusal/basic-recovery control is authored but skipped on this macOS host (38 provider cases skipped). Full native pwndbg and Linux x64 CLI/MCP qualification remain unrun; the native probes used a controlled plugin. No CPU/token/family-memory improvement claimed.
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#3065
No description provided.