# DF-5471 VERDICT — REPRODUCED (impact: panic — deterministic crash from the backtrace fault path; forensic destruction)

## Result

| phase | kernel | observation |
|---|---|---|
| baseline (run 1) | stock, fresh boot, DDB never entered | `sysctl -w debug.df_kfault=1` → `Fatal trap 12` at `0xffffdead00000008` → `kernel: type 12 trap` → **`Fatal double fault, rip=0xffffffff80bcb71f (inside longjmp), rsp=0x0, rbp=0x0`** → `panic: double fault` → auto-trace → DDB wedge; ssh dead; `vm.sh status` ⇒ down |
| baseline (run 2) | same binary (reboot had silently not happened — see narrative) | identical cascade, identical signature |
| fix.diff | rebuilt `#1 Oct 6 01:12:20` | same trigger: two valid frames printed, then `DFTRACE: kfault: walk returned without crashing`; `dmesg | grep -c "Fatal\|DOUBLE FAULT\|panic"` = 0; guest stays up |

## Mechanism (all source-cited)

1. `db_stack_trace_cmd()` → `db_nextframe()` adopts garbage `rbp`
   (`0xffffdead00000000`) — only guard is `< PAGE_SIZE`
   (db_trace.c:227) — and reads `(*fp)->f_retaddr` (db_trace.c:231).
2. `db_get_value()` → `db_read_bytes()` arms `db_nofault = &db_jmpbuf`
   (db_interface.c:242) and dereferences (db_interface.c:244-246).
3. `#PF` → kernel-mode fault → `trap_fatal()` (trap.c:1019) →
   `(debugger_on_panic || db_active) && kdb_trap(...)` (trap.c:1096;
   `debugger_on_panic=1` by default, kern_shutdown.c:110).
4. `kdb_trap()` default case sees armed `db_nofault` →
   `longjmp(*no_fault, 1)` (db_interface.c:140-143) = `longjmp(db_jmpbuf)`.
5. `db_jmpbuf` is BSS (db_command.c:62), set ONLY by `setjmp` inside an
   established DDB session (db_command.c:467, db_trap.c:68). On a fresh
   boot it is **all zeros** — exactly the state of the stock panic path,
   where `print_backtrace(6)` (kern_shutdown.c:880-881; `trace_on_panic=1`
   because `options DDB_TRACE` is in X86_64_GENERIC) runs BEFORE
   `Debugger("panic")`. Also the state at every unguarded live debug site
   (uipc_mbuf.c:2413 `m_copyback`, uipc_mbuf.c:1339 `mfree`; INVARIANTS
   sites kern_lock.c:389 / indefinite2.h:116).
6. `longjmp` restores rsp=0/rbp=0/rip-from-zeroed (support.s:803-810);
   the very next instruction `movq %rdx,0(%rsp)` (support.s:811) writes to
   VA 0 → #DF. Serial confirms: `rip=0xffffffff80bcb71f` (inside longjmp),
   `rsp=0x0`, `rbp=0x0`.
7. `dblfault_handler()` (trap.c:1125) → `panic("double fault")` →
   nested-panic path → DDB wedge. The original panic context (or, on the
   live-path variant, a merely-diagnostic warning) is destroyed; no clean
   dump of the original event.

## Narrative honesty / deviations

* Trigger is a harness sysctl standing in for the kernel's own invocation
  of the same walk (`print_backtrace`/ddb `trace`); the exercised code
  (`db_stack_trace_cmd` + `db_nextframe` + `db_read_bytes` + `kdb_trap` +
  `longjmp`) is stock, unmodified.
* "Run 2": the fix-validation cycle initially failed to reboot the guest
  (my `&&` chain aborted on a `kenv` non-zero exit), so the "patched" run
  in fact executed the stock kernel again — producing a second,
  independent baseline crash with an identical signature before the true
  patched run. Both are documented in run.log; the true patched run
  followed after a forced power cycle (uname confirmed `#1 Oct 6 01:12:20`,
  fix string present in `/boot/kernel/kernel`).
* The **stale-jmpbuf** variant (DDB entered and exited earlier in the boot;
  `db_jmpbuf` then holds a dead `db_command_loop` context on a possibly
  reused kernel stack → longjmp resumes a ghost frame in arbitrary thread
  context) is real by the same code path but was not separately
  demonstrated; the zeroed variant is deterministic and fully proven.
* No attacker-controlled register restore is available in the zeroed
  variant (BSS is not attacker-writable), so there is no uid0 chain from
  this primitive alone; impact is crash/DoS + forensic destruction.

## Fix

`fix.diff` (shared with DF-5470) prevents the walk from adopting
out-of-kernel-space / non-forward frames, eliminating the demonstrated
trigger from the trace side (validated: crash gone). Complete remediation
should additionally make the no-fault recovery safe in
`db_interface.c` (e.g. a `setjmp`'d local buffer established by
`print_backtrace()`/`db_stack_trace_cmd()` before any `db_get_value`, or a
validity flag for `db_jmpbuf` checked in `kdb_trap`), since other DDB
read paths can fault outside a DDB session too.

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
