# DF-5471 — backtrace fault outside a DDB session longjmps into uninitialized/stale global db_jmpbuf → double fault → panic

**File:** `sys/platform/pc64/x86_64/db_trace.c` (pass 2, cross-file with
`db_interface.c`, `db_command.c`)
**Severity:** Medium · **Bucket:** base:ddb / DoS-amplification
**Status:** REPRODUCED on stock guest (twice), crash signature in `panic.txt`

## What

Every dereference the backtracer performs goes through `db_get_value()` →
`db_read_bytes()`, which arms the global no-fault recovery
`db_nofault = &db_jmpbuf` (db_interface.c:242) around a raw pointer
dereference. On a fault, `kdb_trap()` executes
`longjmp(*no_fault, 1)` (db_interface.c:140-143).

`db_jmpbuf` (db_command.c:62, BSS-zeroed) is only initialized by
`setjmp()` at db_command.c:467 / db_trap.c:68 — i.e. **inside an
established DDB session**. But `db_stack_trace_cmd()` also runs:

1. **On every panic, before DDB exists** — stock `X86_64_GENERIC` has
   `options DDB_TRACE`, so `trace_on_panic=1` and panic()
   (kern_shutdown.c:880-881) calls `print_backtrace(6)` *before*
   `Debugger("panic")` ever runs `setjmp(db_jmpbuf)`.
2. **From live kernel debug sites with no debugger involved**, e.g.
   `m_copyback()` failure (uipc_mbuf.c:2413, unguarded) and `mfree()`
   anomaly printing (uipc_mbuf.c:1339, unguarded), plus INVARIANTS
   `lock_test_mode` sites (kern_lock.c:389, indefinite2.h:116 —
   INVARIANTS is in the stock config).

If the walk adopts a garbage frame pointer (precisely what happens when the
panic *was caused by* stack/memory corruption — the moment you need the
dump most) and dereferences an unmapped page:

```
#PF in db_read_bytes
  → trap_pfault → trap_fatal (trap.c:1096, debugger_on_panic=1 default)
  → kdb_trap: db_nofault armed → longjmp(db_jmpbuf)
  → db_jmpbuf all-zero (or stale from a previous DDB session on another
    thread/stack) → longjmp restores rsp=0, rbp=0, rip=0
    (support.s:802-814; first faulting insn is the write `movq %rdx,0(%rsp)`
     at support.s:811 with rsp=0)
  → DOUBLE FAULT → dblfault_handler → panic("double fault")
```

The original panic's dump/forensics are destroyed or hopelessly
interleaved; a *live* debug print site becomes a full system crash.

## Proof (this pack)

KLD harness: `sysctl -w debug.df_kfault=1` walks a kmalloc'd chain whose
2nd frame pointer is `0xffffdead00000000` (unmapped kernel VA), from live
syscall context, on a fresh boot (db_jmpbuf zeroed — same state as the
panic path). Observed on serial (`panic.txt`): `Fatal trap 12` at
`0xffffdead00000008` (the `f_retaddr` read, db_trace.c:231) →
`kernel: type 12 trap` → **`Fatal double fault: rip=0xffffffff80bcb71f
(longjmp), rsp=0x0, rbp=0x0`** → `panic: double fault` → auto-trace →
DDB wedge, guest down. Reproduced twice.

`fix.diff` (shared with DF-5470) makes the walk refuse out-of-kernel-space
and non-forward frames, so `db_read_bytes` is never pointed at the unmapped
VA from the walk. Verified on a rebuilt kernel: the same trigger prints two
valid frames and returns cleanly — `DFTRACE: kfault: walk returned without
crashing`, zero panic lines, guest stays up (`run.log`, `fix_validation.txt`).

Complete remediation additionally needs a `db_interface.c` change (arm a
jmpbuf that is valid whenever db_nofault can fire, e.g. `setjmp` a local
buffer in `print_backtrace()`/`db_stack_trace_cmd()` entry, or make
`kdb_trap` fall back to `db_global_jmpbuf` when the DDB loop never ran) —
out of this file's scope, noted in the finding's recommended fix.

## Build / run

```
./build.sh          # push src/ to guest, build KLD + user_chain, load
./run.sh            # kfault trigger; CRASHES the stock guest (by design)
```
