# DF-5571 — vkernel64 kdb_trap() longjmp re-entry double-increments db_active (DF-5561 twin); the leak routes every subsequent trap into trap_fatal()+kdb_trap() — permanent all-CPU ddb wedge of the whole vkernel

## What (twin of DF-5561, pc64)

`kdb_trap()` in `sys/platform/vkernel64/x86_64/db_interface.c`:

```c
150:  if (db_global_jmpbuf_valid)
151:      longjmp(db_global_jmpbuf, 1);      /* nested trap while ddb is live */
...
167:  setjmp(db_global_jmpbuf);              /* re-entry lands here           */
168:  db_global_jmpbuf_valid = TRUE;
169:  db_active++;                           /* runs AGAIN on re-entry       */
...
177:  db_active--;                           /* the abandoned outer call's
                                              decrement NEVER runs           */
```

A fault taken while a ddb session is live (e.g. the ddb `call 0x10()` —
`db_fncall` arms no fault guard) re-enters `kdb_trap()`, which longjmps back
to the `setjmp` at :167; the increment at :169 executes a second time while
the abandoned outer invocation's `db_active--` at :177 never runs. After the
session finally exits, `db_active` is stuck at >= 1 forever.

Consequence (vkernel64 trap.c routing):
- `user_trap()` — trap.c:366: `if (db_active) { trap_fatal(frame, TRUE, eva); ... }` — every USER-mode trap
- `kern_trap()` — trap.c:605: same for kernel traps
- `trap_fatal()` — trap.c:959: `if ((debugger_on_panic || db_active) && kdb_trap(...)) return;`

so with the leaked counter, every ordinary trap (user page faults, ASTs,
forced exceptions) is diverted into `trap_fatal()` + `kdb_trap()` →
`stop_cpus()` (all-CPU stop via pthread_kill on the vcpu threads,
mp.c:230-244) → `db>` prompt. The vkernel — a single host process hosting
every guest "process" — wedges permanently in ddb; even the panic path
re-enters ddb.

## Trust model

Identical to DF-5561: requires an interactive ddb session in the vkernel
(console operator, `sysctl debug.enter_debugger=ddb` as root inside the
vkernel, SIGQUIT on the vkernel process = `exc_debugger()` → `Debugger()`,
or a panic with `debugger_on_panic`). A ddb command that faults (e.g.
`call <bad addr>()`) then poisons the session; every subsequent continue
wedged the entire machine. Medium / local-DoS, deterministic.

## Reproduce (as run on the QEMU guest, 2026-10-07)

Build prerequisites:
1. stock `make nativekernel KERNCONF=VKERNEL64` fails to link (DF-5474:
   `__build_id_start/end` undefined) — apply `linkfix-df5474.diff` in the
   guest scratch tree first (validated link+boot, same as DF-5474).
2. `sh mk_image.sh` — pristine 96MB UFS `/vkroot.img`:
   `/sbin/init`=vkinit (opens /dev/console, execs /bin/sh),
   `/bin/sh`, `/bin/sleep`, `/bin/fault` (derefs 0x10),
   `/lib/libc.so.8`, `/libexec/ld-elf.so.2`.
   (vkinit is required: with `-e init_path=/bin/sh` the kernel-exec'd sh
   exits instantly — "init died (signal 0, exit 0)".)

```
sh vk_ddb_session.sh /usr/obj/usr/src/sys/VKERNEL64/kernel.debug baseline 0x11150a8
```

The driver (one ssh session) boots the vkernel (`-m 256m -n 2 -s`, fifo
console), waits for userspace, then performs the trigger sequence:
`kill -QUIT <child>` (the vkernel re-forks at startup, init.c:203; the
parent ignores SIGQUIT) → at `db>`:

```
x/x 0x11150a8          # &db_active from nm — vkernel ddb has no symtab
call 0x10()            # fault inside ddb -> longjmp re-entry
x/x 0x11150a8          # counter now 2
c                      # continue; counter decrements to 1 (leaked)
/bin/fault             # from the shell: first user trap after continue
echo POSTCONT-ALIVE    # never executes in baseline
```

### Expected — baseline (run.baseline.log)
- first prompt: `0x11150a8: 1`
- after `call 0x10()` → "Fatal trap 12 ... ip=0x2b:0x10" → "kernel: type 12
  trap" → re-entered `db>`: `0x11150a8: 2`   ← the double-increment
- after `c`: `Fatal trap 7: system forced exception while in user mode,
  current process = 1 (sh)` → `kernel: type 7 trap` → CPU0 stopping CPUs →
  `Stopped at ...` (no prompt echo), then a nested Fatal trap 12 cascade
- `echo POSTCONT-ALIVE` → `No such command` (input now owned by ddb — the
  shell/whole OS is dead)
- driver exit closes the fifo → `panic: vconsgetc: EOF` → which re-enters
  ddb AGAIN (Debugger("panic")) — the wedge survives even a panic.

### Expected — fixed (run.fixed.log)
- first prompt: `0x11150a8: 1`
- after `call 0x10()` re-entry: `0x11150a8: 1`  (guard `if (db_active == 0)`)
- after `c`: `/bin/fault` handled as a normal user SIGSEGV:
  `pid 2 (fault), uid 0: exited on signal 11 / Segmentation fault`
- `echo POSTCONT-ALIVE` → printed by the shell
- "Fatal trap" count: 1 (only the intentional `call 0x10()` one) vs 5 in
  baseline; clean `Caught SIGTERM ... Shutting down...`

## Fix validation

`fix.diff` (authored after verification, never applied to the audit tree):
guard the re-entry increment. Applied in the guest scratch tree, vkernel
rebuilt (`build.log`, RC=0, db_interface.o recompiled), identical sequence
re-run: counter stays 1, no storm, no wedge. **Fixed.**

## Files

- `run.baseline.log` / `run.fixed.log` — full untrimmed vkernel consoles
- `vk_ddb_session.sh` — the phase driver (trigger sequence)
- `mk_image.sh`, `vkinit.c`, `fault.c` — root-image rig
- `linkfix-df5474.diff` — prerequisite DF-5474 link fix
- `fix.diff` — the fix; `build.log` — patched rebuild
- `env.txt`, `hashes.txt`
- `VERDICT.md`, `manifest.json`, `verdict.json`
