# DF-5732 VERDICT — reproduced (Low, dos)

## Bottom line

`sys/ddb/db_print.c` in this tree contains only `db_show_regs()` (69 LOC);
the "print/p modifier dispatch" the task described lives in `db_examine.c`
(`db_print_cmd`, db_examine.c:204) and `db_hex_value` does not exist (its role
is `db_format_radix`/`db_format_hex`, db_output.c:260/286, and
`db_num_to_str`, db_lex.c:127). The one live defect found on this file's own
code path is at **db_print.c:68**: the unconditional
`db_print_loc_and_inst(PC_REGS(DDB_REGS), DDB_REGS)` disassembles
`ddb_regs.tf_rip`, which any DDB console user can set to an arbitrary 64-bit
value via `set $rip = ...` (db_variables.c:120-129 writes the raw
`db_expr_t`). Disassembling a noncanonical (or unmapped) rip faults inside
`db_read_bytes` under the `db_nofault` guard (db_interface.c:242) — but
trap()'s `db_active` funnel (trap.c:414-427) sends **every** trap (except
T_DNA) to `trap_fatal()` first: a full "Fatal trap 9: general protection
fault" crash banner with register/segment disclosure, *then* the quiet
`db_nofault` longjmp recovery (db_interface.c:140-144) back to `db>`. The
longjmp also abandons the funnel's `++gd_trap_nesting_level` (trap.c:422),
leaking it, and the discarded nested trap dispatch leaves the session-resume
path corrupted — independently corroborated by the DF-5727 pack, which
captured `panic: trap: critical section count mismatch! -1892583040/-1` on
`cont` immediately after this exact funnel
(findings/poc/DF-5727/baseline-up.serial.log:525-549: `c` → Fatal trap 12 at
the same `0xffffffff80bccb70` (db_read_bytes loop) → `c` → crit-count panic).

## Reproduction (all on the stock snapshot kernel, live guest)

Run 4 (decisive, this file's trigger — run4-decisive-setrip-showregisters.log):
`sysctl debug.enter_debugger=ddb` → `set $rip = 0xdead000000000000` →
`show registers` → all 25 registers print cleanly (the value-formatting path
is fine) → `-0x2153000000000000:` printed by db_printsym → **Fatal trap 9**
banner (cpuid 3, RIP 0xffffffff80bccb70 = the db_read_bytes read loop) →
`db>` recovers via the nofault longjmp. Banner reproduced, twice total:

- run 4: `set $rip` + `show registers` (db_print.c:68 trigger)
- run 0 (session0-sweep.log): `x/i 0xdead000000000000` (sibling trigger,
  same funnel, trap 9 at the same RIP)

## Honest caveats (why Low and not higher)

1. **Trigger privilege**: DDB console access is already root-equivalent
   (serial console / kbd break / `debug.enter_debugger`). The bug degrades an
   already-privileged recovery tool; it is not an escalation.
2. **The post-`cont` wedge is NOT cleanly attributable to the fault**:
   my controls show that on this snapshot *even a perfectly clean*
   `debug.enter_debugger` DDB session (`x/i` on a *valid* address, then
   `cont`, driver connected or not) leaves the guest dead (silent console,
   ssh down, no panic output) — see run1-control-valid-xi.log and
   run3-control-held-connection.log (+run3-sshpoll.log: DOWN for 110 s with
   the connection held). Prior packs' "control" logs end identically at
   "restarted" (e.g. DF-5728 run3-controls.serial.log), so post-resume
   liveness was never actually established there either. Therefore I claim
   only: banner + register disclosure (deterministic, reproduced),
   trap-nesting leak (static, trap.c:422 vs longjmp), and resume-state
   corruption (captured in DF-5727's pack; my runs' resume failures are
   consistent with it but confounded by the snapshot-level resume problem).

## Pass-2 checklist results (negative results, with weight)

- (a) modifier dispatch: db_show_regs ignores `modif` entirely (dummy4,
  db_print.c:46); `show registers/x`, `/99` verified harmless live
  (session0-sweep.log). The stale-modifier default path (`p/16 1`) is
  consumer-level harmless: db_print_cmd's switch (db_examine.c:211-250) has
  no default — prints a bare newline. DF-5720 territory, not re-reported.
- (b) number formatting: db_num_to_str (static buf[25], ksnprintf-bounded,
  worst case "-%llo" of INT64_MIN = 24 bytes — fits); db_format_radix/_hex
  ksnprintf-bounded (db_output.c:279/296); db_print.c:60-63 tbuf[24] can only
  see offset ∈ [0,2^32) because db_search_symbol stores diff into an
  `unsigned int` (db_sym.c:210,221,225) — worst octal "01000000000000000000000"
  = 23 chars + NUL fits. `val = -val` on INT64_MIN (db_output.c:274-275,
  291-292) is UB-in-theory, wraps in practice, cosmetic only.
- (c) console reachability: `show registers` = db_show_cmds entry
  (db_command.c:88). `$maxoff=-1` (DF-5730's unvalidated write) on
  db_print.c:56's `(unsigned long)db_maxoff` cast produces only cosmetic
  garbage attribution (`kernphys+0xffdf0ff0`, session0-sweep.log) — DF-5730's
  blast radius, not re-reported. `db_find_xtrn_sym_and_offset` never leaves
  `name`/`offset` uninitialized (db_sym.c:225, 237-239) and never dereferences
  the probe address. Register table type-safety verified: all `tf_*` fields
  are `register_t` (cpu/x86_64/include/frame.h:79-83) == `db_expr_t`-sized.
- Killed candidates: stale-modifier strcpy OOB (unreachable — every dispatch
  re-lexes a tIDENT first, db_command.c:308,318; "String too long" path
  longjmps via db_error, db_command.c:481-487, before any strcpy);
  db_num_to_str static-buffer aliasing (single use per db_printf);
  unmapped-rip disasm hang (guarded by db_nofault longjmp — just noisily,
  which IS the filed bug); `offset != value` heuristic (upstream FreeBSD
  heritage, cosmetic).

## Fix

Honor `db_nofault` *before* the trap_fatal funnel — see fix.diff
(trap.c + db_interface.c + ddb.h). Not kernel-build-validated (Low/Info-class
robustness finding; fix-validation kernel builds are reserved for
memory-corruption findings per contract).

## 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.
