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)
PoC verification
Evidence pack
findings/poc/DF-5409 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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_regsis blocked (EFL_SECURE), butvmspace_ctl(RUN)frames pass straight through the whitelisting mask (verified: 20,000 consecutivevmspace_ctlRUNs with rflags=0x20246 accepted,run_suite.logmode 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
doretiiretq β theIDTVEC(prot)/doreti_iret_faultspecial 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
sigreturnvector: none (any guest user).- direct
vmspace_ctlvector:vm.vkernel_enable=1inside the vkernel (guest-root sysctl β the standard state for running any nested vkernel); then any guest process canvmspace_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).
Recommended fix
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
fixedfixed
disasm_vk_fixed.txt: rebuilt vkernel mask 0x254dd5 (0x20000 removed) after applying fix.diff; run_fixed.log mode 2 completes with frames sanitized.
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.
No comments yet.