Backtracer frame walk trusts stack content β INKERNEL() defined but never used AND semantically wrong for the vkernel; walk escapes into guest-user VAs (guaranteed fault on this platform) and prints attacker-chosen kernel symbols (DF-5470 twin, vkernel-amplified)
Summary
The fork shares the pc64 DF-5470 defect and aggravates it: INKERNEL() is never referenced AND the inherited >= USRSTACK definition is itself wrong for the vkernel (kernel KVA lives inside the user range; the correct bound is [KvaStart,KvaEnd)). Any adopted guest-user frame pointer guarantees a SIGSEGV in db_read_bytes (vkernel copyin never raw-derefs user VAs) - the escape doesn't merely read attacker memory, it detonates DF-5473; kernel-KVA values are steerable (rig-chosen kprintf() symbol printed). In-vkernel-proven: clean chain prints rig-chosen frames, bad chain adopts 0x100000000 -> Fatal trap 12 at the f_retaddr read -> DF-5473 cascade kills the vkernel (host: exited signal 4, core dumped). KvaStart/KvaEnd + forward-progress validation fix validated (walk returns cleanly).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5472 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 4.2 KB | β raw | |
| VERDICT.md | β | 4.2 KB | β raw | |
| verdict.json | β | 4.2 KB | view raw | |
| src/vk_trace_harness.c | β | 3.8 KB | view raw | |
| src/vktrigger.c | β | 2.2 KB | view raw | |
| src/run_vk.sh | β | 1.9 KB | view raw | |
| src/build_image.sh | β | 2.1 KB | view raw | |
| vkrun_clean.log | β | 2.2 KB | view raw | |
| vkrun_bad.log | β | 2.1 KB | view raw | |
| vkrun_fixed.log | β | 2.3 KB | view raw | |
| vkrun_smoke.log | β | 3.7 KB | view raw | |
| core_gdb.txt | β | 496 B | view raw | |
| env.txt | β | 250 B | view raw |
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:
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+16with a raw deref (db_trace.c:268).- Even if
INKERNEL()were wired in, the inherited definition is wrong for the vkernel:INKERNEL(va) = va >= USRSTACKwithUSRSTACK = VM_MAX_USER_ADDRESS β 0x7FFF_FFFF_F000(vmparam.h:94,100) β but the vkernel's kernel lives atKERNEL_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 helperkva_p()(vm/pmap.h:162-171) is also useless here: for_KERNEL_VIRTUALit is hardcodedaddr != NULL. - 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 viaexc_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(= thef_retaddrread at db_trace.c:231) in processvktrigger, then the DF-5473 cascade.vkrun_fixed.logβ withfix.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/.
DF-5472 VERDICT β reproduced (in-vkernel rig)
Bottom line: The vkernel64 backtracer adopts arbitrary 64-bit frame
pointers from stack content. INKERNEL() (db_trace.c:132) is never called,
and β a pass-2 discovery β it is semantically wrong for this platform
anyway (kernel KVA 512GB..1TB lies inside [0, USRSTACKβ128TB); vmparam.h:80,94,100).
On vkernel, guest-user memory lives in a separate host vmspace
(platform/copyio.c:311 never raw-derefs user VA), so any adopted guest-user
frame guarantees a fault in db_read_bytes β the walk does not merely
read attacker memory like pc64 (DF-5470), it detonates the broken no-fault
path (DF-5473). Kernel-KVA values remaining in the chain are fully steerable:
the clean run prints the rig-chosen symbol (kprintf() at 0x50d850) β an
unprivileged guest process corrupting its own stack content (or any kernel
stack corruption) chooses which "functions" appear in the panic trace.
How reproduced (2 deliberate runs + 1 accidental, fresh boots each)
- rig: guest-built VKERNEL64 kernel + in-kernel harness (
debug.df_vk_walksysctl runsdb_stack_trace_cmd(chain, TRUE, 6, NULL)β the exact entry ofprint_backtrace(), db_trace.c:443-450 β from live syscall context,db_active=0,db_jmpbufall-zero, verified in the log lineDFVK: db_active=0 db_jmpbuf[0]=0000000000000000 db_jmpbuf[3]=0000000000000000). vkrun_clean.log: chain kept in kernel KVA β 2Γ(null)() at 0x8002037030kprintf() at 0x50d850(rig-chosen return address symbolized), walk returned, vkernel survived (VK_EXIT=0).vkrun_bad.log: chain points into guest-user VA 0x100000000 (mapped for the guest process β vtrigger's mmap succeeded β but not dereferenceable from kernel context) βFatal trap 12: page fault, fault virtual address = 0x100000008β thef_retaddrread at db_trace.c:231 (faulting insn verified in the disassembly:0x71ca40: movzbl (%rdi),%ecx, the db_read_bytes copy loop, db_interface.c:239-240) β DF-5473 cascade kills the vkernel (signal 4, core dumped).
Exploit chain
Not a host-privesc class: the vkernel is an unprivileged host process; impact is inside the vkernel's own threat model (steered/destroyed panic forensics, kernel-process death from a live debug-print site). Chain: guest process influences stack content walked during a vkernel panic/debug-print β walk escapes kernel KVA β guaranteed SIGSEGV β broken no-fault longjmp β vkernel process destroyed with zero diagnostics (anti-forensics amplifier).
Fix validation
fix.diff (in DF-5473 pack, shared): INKERNEL β [KvaStart,KvaEnd), frame
adoption validated at db_trace.c:227/256/308 + have_addr entry :335.
Rebuilt vkernel, identical bad trigger β 2 frames printed, walk stops at the
guest-user boundary, DFVK: walk RETURNED without crashing, sysctl returned,
clean halt, no signal-4 death (vkrun_fixed.log, guest dmesg clean).
fix_status: fixed.
Negative results (classes hunted, killed)
db_sym_numargs/argnames OOB (db_trace.c:421-426): DEAD β vkernel uses db_kld.c:107X_db_sym_numargswhich is hardcodedreturn FALSE; narg comes fromdb_numargs()which returns 0 (db_trace.c:163-167).- trapframe heuristics (db_trace.c:244-248): dead code β
calltrap,Xresume*,_Xsyscallexist nowhere in vkernel64 (signal-based traps); contributes to the boundary-blindness covered by this finding. - watchpoint paths (kx86_64_set_watch etc., db_trace.c:473-652): DDB-console only (operator), not attacker-reachable.
dl_symbol_values/hostdladdr()on garbage callpc (db_trace.c:659-669): dladdr walks link_map ranges without dereferencing callpc β no memory safety issue found.db_numargsinstruction-decode OOB read: disabled at db_trace.c:166 (#if 1 return 0).
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.
(No fix.diff for this item: documented recommendation only - see Fix above.)
Fix verification
fixedpatched vkernel with identical bad trigger: walk stops at the guest-user boundary, prints 2 frames, 'DFVK: walk RETURNED without crashing', clean halt, no signal death (vkrun_fixed.log); guest dmesg shows no new core dump
findings/poc/DF-5472/vkrun_fixed.log lines 34-44 vs findings/poc/DF-5472/vkrun_bad.log lines 36-54; fix.diff in findings/poc/DF-5473/fix.diff (git apply --check clean against repo tree)
Confirmed kernel references
- sys/platform/vkernel64/x86_64/db_trace.c:132
- sys/platform/vkernel64/x86_64/db_trace.c:227
- sys/platform/vkernel64/x86_64/db_trace.c:231
- sys/platform/vkernel64/x86_64/db_trace.c:244
- sys/platform/vkernel64/x86_64/db_trace.c:256
- sys/platform/vkernel64/x86_64/db_trace.c:268
- sys/platform/vkernel64/x86_64/db_trace.c:335
- sys/platform/vkernel64/include/vmparam.h:80
- sys/platform/vkernel64/include/vmparam.h:100
- sys/platform/vkernel64/platform/copyio.c:311
- sys/vm/pmap.h:162
Detail
Exploit chain
guest process / stack corruption steers frame pointers -> walk escapes kernel KVA -> guaranteed SIGSEGV in db_read_bytes -> broken longjmp -> vkernel process killed with zero diagnostics (anti-forensics: the panic trace that trace_on_panic exists for is destroyed). No host privesc: the vkernel is an unprivileged host process.
Evidence (decisive lines)
vkrun_clean.log lines 34-44 (walk mechanics + steered symbol + survival), vkrun_bad.log lines 36-54 (escape -> Fatal trap 12 -> silence), VERDICT.md negative-results section (db_sym_numargs hardcoded FALSE at db_kld.c:107, dead trap heuristics, dladdr safe)
PoC changes
authored fresh in-vkernel rig (no seed): kernel-built harness sysctl debug.df_vk_walk driving db_stack_trace_cmd from live syscall context; guest-user spray via mmap MAP_FIXED at 0x100000000; required building the VKERNEL64 kernel + a minimal UFS root image on the guest (DF-5474 build fix needed first)
Verified recommended fix
redefine INKERNEL to [KvaStart,KvaEnd) and validate every adopted frame (kernel KVA + strictly forward) in db_nextframe and the have_addr entry β see fix.diff in DF-5473 pack
Verdict
The vkernel64 fork shares the pc64 DF-5470 defect and aggravates it: INKERNEL() (db_trace.c:132) is defined but never called, the only frame validation is *fp < PAGE_SIZE (db_trace.c:227), and the inherited INKERNEL definition (>= USRSTACK) is itself wrong for the vkernel (kernel KVA 512GB..1TB lies inside [0,USRSTACK~128TB); the correct bound is [KvaStart,KvaEnd)). Guest-proven on the QEMU guest with an in-vkernel rig: a walk whose chain leaves kernel KVA adopts a guest-user VA, and because vkernel copyin (copyio.c:311) never raw-derefs user VAs, the walk's db_read_bytes deref is a guaranteed fault (Fatal trap 12, VA=0x100000008, the f_retaddr read at db_trace.c:231, faulting insn movzbl (%rdi),%ecx at 0x71ca40) which then detonates the broken no-fault recovery (DF-5473) and kills the vkernel process. Kernel-KVA values in the chain are steerable: the clean run printed the rig-chosen symbol 'kprintf() at 0x50d850'.
No comments yet.