β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-5409

cpu_sanitize_frame whitelists PSL_VM_UNSUPP β€” the gate that blocks the DF-5335 (bad-frame-at-iretq) route is incomplete for EFLAGS.VM

Summary

The sanitize mask deliberately keeps the VM bit ('XXX VM not supported?') in both the vkernel copy and the running host pc64 mirror (disassembly: and $0x274dd5): the trap.c pass-2 audits cited this gate for DF-5335 - it is COMPLETE for CS/SS/IOPL but INCOMPLETE for VM. Proven: 20,000 consecutive vmspace_ctl(RUN) frames with rflags=0x20246 accepted through BOTH sanitizers, nested contexts executed at the supplied rip (vmspace_create has no priv check with vm.vkernel_enable=1). SDM-enforcing hardware => host kernel-mode #GP at doreti. Drop-VM-from-mask fix validated by disassembly (0x254dd5).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5409 Β· 9 files
FileTypeDescriptionSize
README.md β€” 3.0 KB ↓ raw
verdict.json β€” 3.5 KB view raw
../DF-5408/vmflag.c β€” 8.6 KB view raw
../DF-5408/run_suite.sh β€” 2.0 KB view raw
../DF-5408/run_suite.log β€” 3.8 KB view raw
../DF-5408/disasm_host.txt β€” 740 B view raw
../DF-5408/disasm_vk_baseline.txt β€” 592 B view raw
../DF-5408/disasm_vk_fixed.txt β€” 672 B view raw
../DF-5408/fix.diff β€” 1.5 KB view raw

DF-5409 β€” cpu_sanitize_frame whitelists PSL_VM_UNSUPP: the DF-5335 gate is incomplete for EFLAGS.VM

What (the bug)

The vkernel's frame sanitizer, sys/platform/vkernel64/x86_64/cpu_regs.c:311-320:

316:    frame->tf_rflags &= (PSL_RF | PSL_USERCHANGE | PSL_VM_UNSUPP);

The mask deliberately KEEPS PSL_VM_UNSUPP (0x20000, the EFLAGS.VM bit, with a "XXX VM (8086) mode not supported?" comment). The pc64 host mirror (sys/platform/pc64/x86_64/machdep.c:916) is identical β€” confirmed by disassembly of the running guest kernel (disasm_host.txt: and $0x274dd5,%eax; 0x20000 ∈ mask).

cpu_sanitize_frame is the gate the trap.c pass-2 audits cited as blocking the DF-5335 (espfix / bad-frame-at-iretq) route for the vkernel: every go_user β†’ vmspace_ctl(VMSPACE_CTL_RUN) re-entry into (guest-)user mode passes through it (sys/vm/vm_vmspace.c:298), and it correctly forces CS/SS and masks IOPL/NT/… β€” but not VM.

Consequence

A VM-set rflags survives BOTH sanitizers (the vkernel's and the host's) and is loaded by the host kernel's return-to-user:

  • reached structurally by ANY frame source β€” sigreturn (see DF-5408), set_regs is blocked (EFL_SECURE), but vmspace_ctl(RUN) frames pass straight through the whitelisting mask (verified: 20,000 consecutive vmspace_ctl RUNs with rflags=0x20246 accepted, run_suite.log mode 2),
  • on hardware/VMMs that enforce the SDM rule (iretq attempting VM in long mode β†’ #GP(0)), that is a guest-user-triggerable kernel-mode #GP at the host doreti iretq β€” the IDTVEC(prot) / doreti_iret_fault special case, i.e. exactly the code path whose robustness DF-5335 questions;
  • on the rig's CPU (Intel Skylake under KVM) the bit is silently masked at iretq/SYSRET (proved with iretvm.c β€” see DF-5408's pack), so no fault manifests here.

There is no legitimate way for VM to be set in a 64-bit user rflags: the kernel's own sys_sigreturn calls it "VM (8086) mode not supported" and tries (DF-5408: ineffectively) to strip it. The sanitize mask keeping it is a completeness gap in the gate.

Precondition

  • sigreturn vector: none (any guest user).
  • direct vmspace_ctl vector: vm.vkernel_enable=1 inside the vkernel (guest-root sysctl β€” the standard state for running any nested vkernel); then any guest process can vmspace_create + vmspace_ctl (no priv check β€” sys/vm/vm_vmspace.c:150-192).

Verified

Same rig and run as DF-5408 (shared evidence):

  • baseline vkernel binary: mask 0x274dd5 (disasm_vk_baseline.txt);
  • running HOST kernel (pc64): mask 0x274dd5 (disasm_host.txt);
  • runtime: mode 2 loop β€” 20k VM-frames through both sanitizers, all accepted, nested contexts executed at the poisoned rip (frame loaded, not rejected);
  • fixed vkernel: mask 0x254dd5 (VM removed β€” disasm_vk_fixed.txt).

Drop PSL_VM_UNSUPP from the mask in BOTH copies (vkernel64 cpu_regs.c:316 and pc64 machdep.c:916) β€” fix.diff in DF-5408's pack contains both hunks plus the sigreturn-side fix.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

fixed

disasm_vk_fixed.txt: rebuilt vkernel mask 0x254dd5 (0x20000 removed) after applying fix.diff; run_fixed.log mode 2 completes with frames sanitized.
per-fix-DF-5409

Confirmed kernel references

Detail

Exploit chain

guest user -> vmspace_create + vmspace_ctl(RUN, frame{rflags.VM=1}) (needs guest-root sysctl vm.vkernel_enable=1; or no precondition via the DF-5408 sigreturn bypass) -> vkernel cpu_sanitize_frame keeps VM (mask 0x274dd5) -> go_user -> host vmspace_ctl -> host cpu_sanitize_frame keeps VM (running-kernel disasm 0x274dd5) -> host return-to-user loads rflags with VM -> masked here; SDM hardware: #GP(0) at doreti iretq -> espfix special case.

Evidence (decisive lines)

disasm_host.txt (running host kernel mask 0x274dd5), disasm_vk_baseline.txt (vkernel mask 0x274dd5), run_suite.log mode 2 (20k VM frames accepted, nested context executed at poisoned rip), disasm_vk_fixed.txt (post-fix mask 0x254dd5), iretvm.c hardware probe in DF-5408 pack

PoC changes

No seed; evidence generated with the DF-5408 rig (vmflag.c mode 2 loop + running-kernel disassembly of both sanitize sites).

Verified recommended fix

Remove PSL_VM_UNSUPP from cpu_sanitize_frame's rflags mask in both vkernel64 cpu_regs.c:316 and pc64 machdep.c:916 (included in DF-5408/fix.diff).

Verdict

Reproduced as a gate-completeness gap: cpu_sanitize_frame's rflags mask (vkernel64 cpu_regs.c:316 and the running pc64 host mirror machdep.c:916, disassembled live: and $0x274dd5) whitelists PSL_VM_UNSUPP, so an EFLAGS.VM=1 rflags survives both the vkernel's and the host's sanitize and is loaded by the host return-to-user. Runtime proof: 20,000 consecutive vmspace_ctl(VMSPACE_CTL_RUN) frames with rflags=0x20246 were accepted and their nested contexts executed (frames loaded, not rejected). The trap.c pass-2 claim that cpu_sanitize_frame gates the DF-5335 route is verified COMPLETE for CS/SS/IOPL/NT but INCOMPLETE for VM. On this rig's CPU (Skylake/KVM) the loaded VM bit is silently masked (iretvm probe), so impact is none here; on SDM-enforcing hardware the same frame yields a kernel-mode #GP at the host doreti iretq (IDTVEC(prot)/doreti_iret_fault special case = DF-5335 territory), reachable from an unprivileged guest user via sigreturn (DF-5408) or, with vm.vkernel_enable=1, directly via vmspace_ctl.