# DF-5472 — vkernel64 backtracer frame walk trusts stack content; INKERNEL() dead AND semantically wrong for the platform; walk escapes into guest-user VAs and attacker-chosen kernel symbols are printed (pc64 DF-5470 twin, with vkernel-specific amplification)

**File:** `sys/platform/vkernel64/x86_64/db_trace.c` (pass 2)
**Severity:** Medium · **Confidence:** certain · **Verified:** reproduced on the QEMU guest (in-vkernel rig)

## What

Identical defect class to the pc64 twin (DF-5470), plus two vkernel-specific
aggravations:

1. `INKERNEL(va)` is defined at db_trace.c:132 and **never referenced**. The
   only validation of an adopted frame pointer is `(unsigned long)*fp < PAGE_SIZE`
   (db_trace.c:227). Whatever QWORD sits at `[rbp]` becomes the next frame and
   is dereferenced (db_trace.c:231-232), and the "trapframe" heuristic reads
   `*fp+16` with a raw deref (db_trace.c:268).
2. Even if `INKERNEL()` were wired in, **the inherited definition is wrong for
   the vkernel**: `INKERNEL(va) = va >= USRSTACK` with `USRSTACK =
   VM_MAX_USER_ADDRESS ≈ 0x7FFF_FFFF_F000` (vmparam.h:94,100) — but the
   vkernel's kernel lives at `KERNEL_KVA_START = 512GB .. +512GB`
   (vmparam.h:80-81), i.e. *inside* `[0, USRSTACK)`. The inherited test would
   reject every real kernel frame and accept most guest-user addresses. The
   correct bound is `[KvaStart, KvaEnd)` (platform/init.c:609-621,
   vm/pmap.h:141). The tree's generic helper `kva_p()` (vm/pmap.h:162-171) is
   also useless here: for `_KERNEL_VIRTUAL` it is hardcoded `addr != NULL`.
3. The trap/syscall/interrupt frame heuristics (`calltrap`, `Xresume*`,
   `_Xsyscall`, db_trace.c:244-248) are **dead code on vkernel64** — those
   symbols do not exist anywhere in the platform (traps arrive as host signals
   via `exc_segfault` → `kern_trap`, exception.c:263, trap.c:583). The walk
   structurally cannot recognize a kernel↔user boundary.

## Vkernel-specific impact (guest-proven)

Guest-user memory of the process whose syscall is being traced lives in a
different host vmspace than the vkernel kernel: vkernel `copyin`
(platform/copyio.c:311-322) never raw-dereferences a user VA (it uses
`vm_fault_page` + kernel-window `lwbuf` copies). Consequently, unlike pc64
where the escaped walk *reads* user memory, on vkernel64 any adopted
guest-user frame pointer **guarantees a fault** in `db_read_bytes` — which
falls into the broken no-fault recovery (DF-5473) and destroys the vkernel
process with zero further diagnostics. Values that *do* stay in kernel KVA
are fully attacker-steerable: the trace prints the symbol of any kernel
address the corrupted stack happens to hold (`kprintf() at 0x50d850` printed
from a rig-chosen return address in the clean run), and raw hex for the rest
(the leak aspect is DF-1078's; the steering + escape + guaranteed fault is
this finding).

## Proof (this pack)

In-vkernel rig: a VKERNEL64 kernel (built on the guest, plus rig harness
`vk_trace_harness.c`) boots on a minimal UFS image; an ordinary guest process
(`vktrigger`) sprays a fake frame chain at guest-user VA 0x100000000 and asks
the harness to walk a kernel chain whose second frame points there
(`db_stack_trace_cmd(chain, TRUE, 6, NULL)` — the exact entry
`print_backtrace()` uses, db_trace.c:449 — from live syscall context with
`db_active == 0`, the panic-path state).

- `vkrun_clean.log` — control: chain stays in kernel KVA; walk completes,
  prints rig-chosen symbols, returns; vkernel survives.
- `vkrun_bad.log` — walk adopts 0x100000000:
  `Fatal trap 12: page fault, fault virtual address = 0x100000008`
  (= the `f_retaddr` read at db_trace.c:231) in process `vktrigger`, then the
  DF-5473 cascade.
- `vkrun_fixed.log` — with `fix.diff` (KvaStart/KvaEnd walk validation +
  forward-progress check) the same trigger stops cleanly at the boundary,
  "DFVK: walk RETURNED without crashing", vkernel survives.

## Fix

`fix.diff` (shared with DF-5473): redefine INKERNEL to `[KvaStart, KvaEnd)`,
reject non-kernel/non-forward frames at every adoption point
(db_nextframe entry :227, NORMAL path :256, trap epilogue :308, have_addr
entry :335). Validated: bad trigger returns cleanly, no crash, vkernel stays
up (`vkrun_fixed.log`).

## Build / run

See DF-5473 pack (`build_image.sh`, `run_vk.sh`); sources in `src/`.
