# DF-1078 — vkernel64 DDB backtrace leaks kernel addresses to msgbuf

## Verdict

**NOT REPRODUCED (runtime) — STATIC VERIFICATION CONFIRMED.**

The cited code path and bug exist verbatim in
`sys/platform/vkernel64/x86_64/db_trace.c`. The runtime trigger requires
a **running vkernel64 binary** — a separate user-mode kernel build that
is **not** the running GENERIC kernel. The audit's running kernel is
`6.5-DEVELOPMENT #0` `X86_64_GENERIC` (confirmed via `sysctl
kern.version`); there is **no vkernel binary in `/boot/kernel/`** (`ls
/boot/kernel/ | grep -i vkernel` returns nothing) and the vkernel64
platform is built only when the operator explicitly configures
`VKERNEL64`. The audit does not build or run a vkernel.

So this is a **non-default-kernel** defect: real in source, present in
the vkernel64 platform tree, but not exercisable on the GENERIC kernel
that the audit guest runs. The finding itself classifies this correctly
as Medium with CVSS `AV:L/AC:L/PR:L` (an unprivileged process **inside a
vkernel** guest reading `/dev/klog`).

## Mechanism (confirmed by source trace)

`db_nextframe()` (`vkernel64/db_trace.c:264-`) prints raw `rip`/`rsp`/
`rbp` values via `db_printf` with `%016lx` format specifiers in three
cases:

- **TRAP** at `:280-283`: `db_printf("--- trap %016lx, rip = %016lx, rsp
  = %016lx, rbp = %016lx ---\n", tf->tf_trapno, rip, rsp, rbp);`
- **SYSCALL** at `:289-292`: identical pattern with `tf->tf_rax`
- **INTERRUPT** at `:299-302`: `db_printf("--- interrupt, rip = %016lx,
  rsp = %016lx, rbp = %016lx ---\n", rip, rsp, rbp);`

`db_stack_trace_cmd` (`:345`): `db_printf("%p does not look like a stack
frame, skipping\n", ...)` and (`:352`) `db_printf("Trace beginning at
frame %p\n", frame)` — both leak stack-frame addresses via `%p`.

When `print_backtrace()` is called from non-DDB paths (panic with
`trace_on_panic=1`, the **default** for `VKERNEL64` —
`sys/config/VKERNEL64:59 options DDB_TRACE` sets `trace_on_panic=1` at
`kern_shutdown.c:116`), `db_active` is 0, so `db_putchar`
(`sys/ddb/db_output.c:108-119`) routes output through `kprintf("%c", c)`
which writes to **both** the vkernel's msgbuf **and** host stdout
(`console.c:437 vconsputc → pwrite(1, ...)`). Vkernel guest processes
read msgbuf via `/dev/klog` or `dmesg` (world-readable) — leaking the
vkernel kernel's virtual address layout. The audit verified
`db_output.c:108-119` end-to-end:

```c
/* db_output.c:108-119 */
if (!db_active) {
    if (c == '\r' || c == '\n' || c == '\t' || isprint(c)) {
        kprintf("%c", c);              /* <-- routes to msgbuf */
    } else {
        kprintf("?");
    }
    if (!db_active)
        return;
    ...
}
```

The vkernel64 platform is also **missing the `Xfast_syscall` boundary
stop** that pc64 has (`sys/platform/pc64/x86_64/db_trace.c:434`:
`if (name && strcmp(name, "Xfast_syscall") == 0) break;`). Confirmed by
`grep -n Xfast_syscall sys/platform/vkernel64/x86_64/db_trace.c` — **no
match**. Without it, `db_stack_trace_cmd`'s `while(count--)` loop
continues past the syscall entry point into user-space / junk stack
frames, where return addresses are unlikely to resolve to symbols.
`db_printsym` (`db_sym.c:289-292`) then prints raw hex addresses when
`name == NULL` or `offset >= db_maxoff` — additional unresolved-address
leak.

## Why it cannot be triggered from the audit guest

The audit guest runs the `X86_64_GENERIC` kernel, not a vkernel. The
vkernel64 platform is a separate kernel build that must be configured
(`VKERNEL64`) and compiled; even then it runs as a userland process on
the host, not as the guest kernel. Building and running a vkernel is a
**non-default-kernel** action; per the audit's bright-line rule, results
that hold only on a non-default kernel build (without labelling) are
circular. The audit therefore confines the demonstration for this
finding to the static-verification fallback.

## Exploit chain

None developed — the leak is from a vkernel64 binary (not running), and
the primitive is a KASLR-bypass / address-layout leak rather than memory
corruption. The finding's threat model chains this with a separate
vkernel kernel bug for code execution within the vkernel process; that
chain is not presentable on the audit's GENERIC guest.

## PoC

`verify.sh` — static-verification script that walks the cited path with
`grep`/`sed` against `sys/`, confirming: (1) the raw `%016lx`
`rip/rsp/rbp` prints in all three cases of `db_nextframe` (`:280-302`);
(2) the `%p` stack-frame prints in `db_stack_trace_cmd` (`:345`, `:352`);
(3) the missing `Xfast_syscall` boundary stop in vkernel64 vs the
present one in pc64 (`:434`); (4) `db_putchar` routing to `kprintf` →
msgbuf when `!db_active` (`db_output.c:108-119`); (5) `VKERNEL64`
config enables `DDB` + `DDB_TRACE` + `INVARIANTS` by default
(`sys/config/VKERNEL64:58-60`); (6) the running kernel is GENERIC, not
vkernel64. Run from the repo root: `sh findings/poc/DF-1078/verify.sh`.

## Fix

`fix.diff` — three changes in `db_trace.c`:
1. **Add the missing `Xfast_syscall` boundary stop** (port from pc64
   `db_trace.c:434`) to stop the stack walker at the syscall boundary.
2. **Gate the raw-address (`%016lx`, `%p`) prints on `db_active`**: when
   `db_active == 0` (the `print_backtrace` panic/warning context), `rsp`
   and `rbp` are suppressed; `rip` is printed symbolically via
   `db_printsym`. The `%p` "does not look like a stack frame" and "Trace
   beginning at frame" messages are also gated on `db_active`.
3. The `panic`/`syscall`/`trap` *numbers* (`tf_trapno`, `tf_rax`) are
   not addresses and remain visible.

A more complete fix would change `db_putchar` itself to use `cnputc`
(console only, no msgbuf) when `!db_active`, but that is in
`db_output.c`, outside `db_trace.c`. **Matches** the finding markdown's
recommended fix.

## Reproduce

```
sh findings/poc/DF-1078/verify.sh     # static source verification
```
