* [BUG] KVM: nVMX: guest-reachable KVM_BUG "Guest always owns CR0.TS" via stale vmcs02 CR0 read shadow
@ 2026-10-31 8:39 Amirmohammad Eftekhar
0 siblings, 0 replies; only message in thread
From: Amirmohammad Eftekhar @ 2026-10-31 8:39 UTC (permalink / raw)
To: seanjc, pbonzini
Cc: kvm, linux-kernel, amirmohammad.eftekhar, amirmohammadeftekhar
Hi,
Reporting a guest-reachable KVM_BUG() in the nested-VMX CR0 handling. On a
default host it is a WARN plus a forced self-kill of the VM (-EIO on every
vCPU); on a panic_on_warn=1 host it takes the host down. It is not a
memory-safety bug. It still reproduces on top of the in-flight "KVM: nVMX:
Honor L1-owned CR0 bits for LMSW" v3 series
(<20261030213747.2611815-1-seanjc@google.com>): that series fixes the LMSW
arm, but not the two paths below.
The assertion
-------------
handle_cr() treats a CLTS CR-access VM-exit from the guest as impossible:
case 2: /* clts */
KVM_BUG(1, vcpu->kvm, "Guest always owns CR0.TS"); /* vmx.c:5725 */
return -EIO;
That holds only while the vmcs02 CR0 read shadow stays equal to
nested_read_cr0(vmcs12). prepare_vmcs02() establishes it:
vmx_set_cr0(vcpu, vmcs12->guest_cr0);
vmcs_writel(CR0_READ_SHADOW, nested_read_cr0(vmcs12)); /* nested.c:2842-2843 */
and handle_set_cr0() re-establishes it on the MOV-to-CR0 exit path
(vmx.c:5623-5643). When L1 owns CR0.TS (cr0_guest_host_mask.TS=1), prepare_vmcs02()
also sets CR0.TS in the vmcs02 guest/host mask (nested.c:2808-2809), so a CLTS
by L2 *can* raise a CR-access VM-exit, and KVM relies on
nested_vmx_exit_handled_cr() (which reads vmcs12) to reflect it to L1 before
handle_cr() ever runs.
The break
---------
Three L0-side writers run while is_guest_mode() and overwrite CR0_READ_SHADOW
with the raw CR0, bypassing the nested merge. The write is unconditional:
vmcs_writel(CR0_READ_SHADOW, cr0); /* vmx_set_cr0, vmx.c:3466 */
- M1: a runtime KVM_SET_SREGS2 -> __set_sregs_common() -> vmx_set_cr0(). This is
reachable from the guest through QEMU's vmport: an IN/OUT to port 0x5658 from
L2 makes QEMU issue a runtime KVM_SET_SREGS2 of the current register set, which
round-trips the real CR0 (TS=1) into the read shadow. It needs L1 to expose
0x5658 to L2 (vmport is default-on in QEMU).
- M2: RSM back into L2. leave_smm() re-enters L2 before rsm_load_state_64() ->
rsm_enter_protected_mode() -> kvm_set_cr0() loads the SMRAM CR0 into the live
L2 (ordering from 72e9cbdb4338, "fix SMI injection in guest mode"). This needs
only an SMI taken while L2 runs (SMI IPI from a sibling vCPU, or a firmware
SMI); no vmport and no SMRAM edit are required.
- M3 (L0-handled LMSW) is the arm your v3 series fixes; excluded here.
With L1 owning TS (cr0_guest_host_mask.TS=1, cr0_read_shadow.TS=0,
guest_cr0.TS=1), a clean entry leaves vmcs02 CR0_READ_SHADOW.TS=0
(nested_read_cr0), so CLTS does not exit (verified control). After M1 or M2,
vmcs02 CR0_READ_SHADOW.TS=1 while the mask still owns TS, so hardware raises the
CLTS CR-access VM-exit. nested_vmx_exit_handled_cr() case 2 (nested.c:6386)
consults vmcs12 (cr0_guest_host_mask.TS=1 && cr0_read_shadow.TS=0 -> false), does
not reflect, and L0 runs handle_cr() case 2 and hits the KVM_BUG. KVM_BUG() then
WARNs and calls kvm_vm_bugged(), so the VM is marked dead and every vCPU returns
-EIO.
Confirmed
---------
Mainline (7.3.0-rc3, KASAN) and the kvmCTF 6.1.74 build, as throwaway nested L1s.
arm 33 = raise an SMI while L2 runs, RSM, then CLTS; arm 32 = IN 0x5658 then CLTS.
Both -> KVM_RUN -EIO with:
Guest always owns CR0.TS
WARNING: arch/x86/kvm/vmx/vmx.c:5725 at handle_cr+0x32e/0x1df0 [kvm_intel], CPU#1: t3vmm/132
Call Trace:
vmx_handle_exit+0x3e1/0x15a0 [kvm_intel]
vcpu_enter_guest.constprop.0+0x2110/0x3960 [kvm]
kvm_arch_vcpu_ioctl_run+0xd92/0x27b0 [kvm]
kvm_vcpu_ioctl+0x66c/0x1480 [kvm]
The control arm (CLTS with no preceding clobber) completes in L2 with no exit, as
expected. I also applied your v3 LMSW series and rebuilt the host: arm 33 still
fires (now at vmx.c:5726), confirming the series does not cover M1/M2.
Lower-severity side effects of the same clobber (L2->L1 fidelity, not the
KVM_BUG): until L2's next nested entry, an L2 MOV-CR whose L1-masked bits match
the real value no longer exits to L1 (lost CR intercept), and L2 can read
L1-hidden bits (CR0.TS, CR4.VMXE) from the clobbered read shadow.
Fixes / suggested direction
---------------------------
The desync dates to the original CR-shadow handling (eeadf9e7558c); the RSM arm
to 72e9cbdb4338; 673692735fdc turned the resulting CLTS case into a VM-fatal
KVM_BUG. The fix I would suggest is to keep the nested read shadow consistent on
the L0-side writers while is_guest_mode(): write nested_read_crX(get_vmcs12(vcpu))
to the read shadow in vmx_set_cr0/4() (or recompute the shadows after
rsm_load_state_*()), the same merge handle_set_cr0/4() already does. This is
adjacent to your v3 CR0 series, so it may be cleanest to fold it in there. I am
happy to send and test a patch if you would like it done that way.
Reproducer: a standalone C VMM (arms 32/33, an SMM handler that just RSMs) plus a
KVM-unit-tests variant that reproduces the read-shadow clobber under real QEMU
10.1.0-rc3 and 6.2.0. Happy to share either.
Thanks,
Amirmohammad
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2026-10-31 8:40 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-31 8:39 [BUG] KVM: nVMX: guest-reachable KVM_BUG "Guest always owns CR0.TS" via stale vmcs02 CR0 read shadow Amirmohammad Eftekhar
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®