Files
rea/docs/recorded-crashes.md
gensokyo-dev 1c2c2f1e78 fix(process): settle native exit and repair ownership verification (#5249)
* fix(process): classify baseline cleanup with current ownership facts

Use the pre-launch baseline only to select candidates for current native
identity/token validation. Preserve confirmed absence and verified foreign
generations instead of carrying a premature baseline identity failure.

Exercise the real Linux enumeration/exit race and retain countercontrols for
unknown identities and live token-owned processes.

Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)

* fix(ci): distinguish process inventory from historical PID lookups

* test(ci): verify decoded historical PID controls before tracing

* fix(ci): trace historical PID handles and signal probes

* fix(ci): resolve traced directory paths across pathname syscalls

Classify producer-decoded FD/path pairs once instead of limiting failed relative
lookup detection to openat on /proc. This preserves attempted readlinkat, statx
and newfstatat accesses with no returned descriptor.

Three regression cases fail against the previous policy and all 33 controls
pass with the shared pathname interpretation.

Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)

* test(ci): declare unresolved legacy pathname coverage

Report the historical-PID access surfaces actually checked and explicitly mark
legacy cwd-relative pathname calls unverified. The existing tracer does not
provide their cwd identity, and a blanket rejection also rejects legitimate
GDB build-ID metadata queries.

Retain every committed access guard and document the preexisting limit without
claiming absence from missing path evidence. The complete real 44-case CLI/MCP
recorded-crash qualification passes with the coverage declaration.

Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)

* fix(ci): reject priority access to historical PIDs

* fix(ci): preserve C-quoted components in traced path checks

strace renders non-ASCII pathname bytes as C octal escapes, not JSON strings.
Keep those components opaque when resolving literal ASCII path separators and
proc PID selectors, matching the existing absolute-path interpretation.

Two controls fail before the correction and all 35 pass afterward. An actual
strace observation independently reproduces the rejection of an unrelated
Chinese filename and verifies the corrected classification.

Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)

* fix(ci): reject historical PID resource and scheduler calls

* fix(schema): preserve portable large base64 validation

* fix(ci): share explicit PID syscall contracts with tracing

Derive tracer selection and reported coverage from the same named target
contracts used by the access checker. Cover resource limits, scheduler calls,
process metadata, PID groups, capabilities and pidfd operations without mixing
UIDs, policy enums, self selectors or output values with target PIDs.

Interpret each target once and retain unresolved target facts as a distinct
failure. Emit the coverage catalog once per verifier result.

The previous oracle misses 28 new controls. All 83 controls pass after the
change; real native resource/scheduler probes capture 14 previously missed
calls with resource limits and affinity unchanged. All 35 selected syscall
names are accepted by the pinned local strace.

Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)

* fix(ci): classify I/O priority and qualify PID trace coverage

* fix(process): keep ownership retries on the native clock

* fix(ci): retain process edges in ownership trace inventory

Keep fork/vfork and resumed edges along with clone events so the shared attribution owner receives the complete process relation facts it consumes.

Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)

* fix(process): type native exit predicates and ownership fixtures precisely

* fix(ci): retain trace coverage limits after integration

Restore explicit unverified symlink aliases and same-Node stat/status purpose in the shared coverage contract and guide. The native-exit and trace-attribution integration does not establish those properties. Preserve both limitations rather than silently broadening the reported qualification.

Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)
2026-10-20 02:01:00 +08:00

7.9 KiB

Recorded crash evidence

inspect_recorded_crash / rea inspect-recorded-crash inspect a caller-selected Linux ELF64 little-endian core independently of an active binary target. Two architecture profiles are interpreted, x86-64 and AArch64; each binds registers to its own NT_PRSTATUS layout, and any other e_machine is refused as unsupported_target before note interpretation.

export REA_PWNTOOLS_PYTHON=/absolute/existing/venv/bin/python
rea inspect-recorded-crash ./crash.core --json

The selected Python needs unchanged pwntools 4.15.0, pyelftools 0.33 and Unicorn 2.1.2. REA does not install these dependencies. ELFFile/its original ELF structures and pwntools' original Linux amd64 and aarch64 ctypes provide parsing and ABI layout. The adapter adds bounds, representation preservation and lifecycle ownership. The initial Linux note profile accepts System V/unspecified and GNU/Linux OSABI with ABI version zero. Other declared ABIs return unsupported_target before note interpretation; an unspecified OSABI alone does not prove OS origin.

Recorded observations

  • Artifact path, original SHA-256 and size; full normalized result and Evidence are inline through CLI and MCP.
  • Original program headers and recorded segment file/memory ranges. Undumped memory and current executable/library identity remain unknown.
  • Every note's original identity, owner/descriptor bytes and source ranges, including unfamiliar owners and note kinds.
  • Every exact Linux CORE PRSTATUS note as a separate recorded thread, with historical PID, signal and lossless uint64 registers bound to source bytes. Each supported note has exactly one interpretation; each thread contains the complete ABI register set for its architecture (27 registers for amd64, 34 for AArch64), with names bound to their original ABI offsets. Physical note ranges cannot overlap or leave segment gaps. ELF and note headers are independently bound to the snapshot, including extended program counts.
  • Signed SIGINFO number/code/errno. Initially only SIGSEGV codes 1/2 establish the fault-address union; other variants retain raw bytes and unknown meaning.

Historical PIDs are metadata. They never select a live process or authorize attach/control. Missing notes do not establish absence at the crash; note interpretation completeness and signal-thread association remain unknown.

Optional debugger mapping context

export REA_PWNDBG_GDB=/absolute/existing/gdb
export REA_PWNDBG_GDBINIT=/absolute/unchanged/pwndbg/gdbinit.py
export REA_PWNDBG_VENV_PATH=/absolute/existing/pwndbg-venv
rea inspect-recorded-crash ./crash.core --debugger-context --json

MCP uses { "path": "/artifacts/crash.core", "include_debugger_context": true }. Omitting this meaningful facet requests only recorded evidence. Requested but unavailable context returns an actionable failure; it never silently claims debugger coverage. GDB defaults to /usr/bin/gdb; the plugin and virtual environment require explicit absolute paths. Initial local verification covers Linux x64, GDB 13.1 and unchanged pwndbg 2026.09.15.

The outer provider owns both processes and one stable core snapshot. An owned Python launcher lowers inherited soft limits before replacing itself with GDB. GDB starts without initfiles, auto-load, debuginfod or automatic shared-library loading. Owned empty library search paths and caches are used, with plugin updates disabled. Debug-file lookup is also restricted to the owned empty search directory. GDB 16 can otherwise locate the recorded executable independently of these settings. While opening the core, fixed package code temporarily selects /proc/self/exe (the already running debugger) with exec-file, without loading symbols or running an inferior. This occupies GDB's executable slot and prevents its automatic executable discovery. The validated decoder profile selects the core architecture explicitly, so the placeholder cannot select it instead. The bridge discards that selection with file before querying mapping candidates, and verifies the core connection and absence of a main symbol file. Before launching GDB, the provider also checks source-bound NT_FILE descriptors: mapping names must be absolute Unix paths without dot segments. Relative names bypass GDB's sysroot, and traversal can escape it. Unresolved or unsafe mapping coverage refuses debugger enrichment while retaining the basic decoded observation and original bytes. No caller GDB command language is exposed. The exact observed GDB/plugin version strings are retained.

Mapping names are producer display metadata, with unknown current file identity. Map permissions are derived from reported flags; zero/unfamiliar flags leave permissions unknown. These maps do not establish page contents or an executable match. Core source bytes remain available independently of this enrichment. An Evidence envelope containing these maps is derived; inspection without debugger context is observed.

Bounds and verification

Initial Linux x64 limits: 32 MiB input, 64 MiB decoder reply, 1 MiB diagnostics per stage, 30 seconds owned wall deadline per stage, 3 GiB address-space soft ceiling and 30 seconds CPU soft ceiling. REA_PWNTOOLS_MAX_INPUT_BYTES raises the input bound alone, up to 1 GiB, and returns provider_unavailable when it is not a decimal integer in range; the reply, diagnostic and deadline bounds stay fixed, so a large core that outruns them still fails explicitly instead of partially. Tighter inherited soft/hard limits are retained; hard limits are not raised. Complete oversized results fail explicitly. These process limits do not constitute a sandbox for caller-supplied engine code.

Resource exit statuses need matching private failure-branch markers. Unmarked launcher failures retain their actual status/output and unknown resource cause. Cancellation, output truncation and independent owned cleanup remain explicit.

npm run verify:recorded:crash uses source-owned fixtures and caller-supplied GDB/Python/pwndbg/strace. Fixture generation explicitly launches its owned test program; subsequent inspection only reads the resulting core. Real acceptance checks recorded threads, high registers, missing/malformed notes, opaque owners, CLI/MCP parity, optional debugger context and historical PID collision with an owned live sentinel. An observed Node thread group must launch the exact ownership ps inventory before it can read that live process's stat or status for native identity/UID facts. Only traced CLONE_THREAD members share this allowance; exec-replaced threads, ordinary children, debugger and decoder processes cannot use it. The purpose of stat/status reads within the same Node thread group cannot be distinguished by strace. A second recording uses a valid historical PID above the host's assignable PID range and rejects resolved lookups, including Node stat/status reads, to detect historical-PID-driven access separately from live inventory. Descriptor paths come from strace's -yy decoding; exact PID-directory opens and relative accesses through those handles are rejected. Failed FD-relative pathname attempts are checked across syscall variants. Legacy relative pathname calls without dirfd/cwd evidence and symlink aliases remain unverified; the verification report declares these gaps rather than establishing their absence. One syscall/selector contract supplies trace selection, target interpretation and the report's explicit coverage catalog. Unlisted PID-directed syscalls remain unverified. The catalog includes PID-directed resource limits, scheduling, priority, process metadata, signals and pidfd controls. Self, group and UID selectors retain their syscall-defined meanings; unresolved targets cannot qualify. Coverage is reported once for all CLI/MCP traces and does not establish containment of every possible kernel interaction. The syscall trace also rejects access to the still-present source executable. The CI workflow defines Ubuntu 24.04 and Debian 13 (GDB 16) container lanes. Original inputs and host configuration remain unchanged.