# DF-5470 VERDICT — REPRODUCED (impact: dos / anti-forensics)

## Result

| phase | kernel | observation |
|---|---|---|
| baseline | stock `#0 Jul 2 06:02:54` (X86_64_GENERIC, DDB+DDB_TRACE+INVARIANTS) | production `db_stack_trace_cmd()` walked 30000 frames sprayed by an **unprivileged user (uid 1001)** in **user VA 0x60000000**; 22754+ lines flooded msgbuf (~1.07MB ≥ MSGBUF_SIZE=1MB); walk start line + entire prior msgbuf content (boot context) **evicted** |
| fix.diff | rebuilt `#1 Oct 6 01:12:20` | walk refused: `0x60000000 is not in kernel space`; **0** flood lines; boot context intact; guest healthy |

## Why it is real

* `INKERNEL()` is defined at `sys/platform/pc64/x86_64/db_trace.c:131` and
  referenced **nowhere** in the file — the kernel-space validation of the
  ancestor code was dropped in the x86-64 port.
* The only frame validation in the walk is `(unsigned long)*fp < PAGE_SIZE`
  (db_trace.c:227). `db_nextframe()` adopts whatever 64-bit value sits at
  `[rbp]` as the next frame (db_trace.c:232,258) — user addresses, mapped
  kernel heap, cycles, or unmapped VA all pass.
* During a panic or DDB `trace` on a thread whose process has user
  mappings, user VA is directly readable by the walk: the panic-time
  `print_backtrace()` (stock DDB_TRACE, kern_shutdown.c:880-881) runs on the
  panicking thread with its page table still active. An attacker whose
  syscall panics the kernel (user-triggerable panics exist independently)
  fully controls every subsequent "frame": its `f_retaddr` (printed),
  and via the `calltrap`/`Xresume*`/`_Xsyscall` symbol-name heuristic
  (db_trace.c:240-251) the fake "trapframe" contents at `fp+16`
  (db_trace.c:268-302) — printed as trapno/rip/rsp/rbp.
* Output goes to msgbuf: `db_putchar()` routes through `kprintf()` when
  `db_active==0` (db_output.c:107-116) — true for the whole panic-time
  walk (db_active is only incremented later in kdb_trap).
* Demonstrated consequence: ~1MB of attacker-steered output evicts the
  real panic evidence from the 1MB msgbuf (run.log: pre-walk boot lines
  and the walk's own start line both gone). This is the anti-forensics
  amplifier of the already-reported address exposure DF-1077 (not
  re-reported here); it is also the enabling half of DF-5471 (unmapped-VA
  adoption → db_read_bytes fault → longjmp hazard).

## Honest scope notes

* The walk itself performs no *writes*; there is no memory-corruption
  primitive in this finding, hence no uid0 chain. The demonstrated impact
  is denial-of-service of forensic data (msgbuf eviction) plus
  attacker-steered kernel output. The crash consequence of the same
  missing validation is DF-5471.
* The harness trigger is an instrumentation sysctl (root-loadable KLD)
  standing in for the kernel's own invocation sites (panic()/DDB). The
  unprivileged-reachability claim rests on (a) user-triggerable panics
  existing elsewhere in the kernel and (b) the walk running in the
  attacker's address-space context — both cited; the walk mechanics
  demonstrated are the production code path (`db_stack_trace_cmd` +
  `db_nextframe` unmodified).
* `db_numargs()` returns 0 (db_trace.c:166) and `X_db_sym_numargs()` is
  hard-wired FALSE (db_kld.c:107-111), so the `argnames[MAXNARG]`
  stack-array path is dead on this platform — no overflow there.

## Fix

`fix.diff` (git-apply-able, validated): frames must be `INKERNEL()` and
strictly forward; `trace <addr>` refuses non-kernel addresses. Same diff
fixes DF-5471's trigger from the walk side.

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