Re: [PATCH v2 1/2] KVM: nVMX: Don't let L2's LMSW modify CR0 bits owned by L1
From: Sean Christopherson
Date: Fri Oct 30 2026 - 14:44:57 EST
On Fri, Oct 30, 2026, Sean Christopherson wrote:
> On Thu, Oct 29, 2026, Mahmoud Abumandour wrote:
> diff --git arch/x86/kvm/vmx/vmx.c arch/x86/kvm/vmx/vmx.c
> index 544b7840897c..2d5feeb8a059 100644
> --- arch/x86/kvm/vmx/vmx.c
> +++ arch/x86/kvm/vmx/vmx.c
> @@ -5743,8 +5743,10 @@ static int handle_cr(struct kvm_vcpu *vcpu)
> break;
> case 3: /* lmsw */
> val = (exit_qualification >> LMSW_SOURCE_DATA_SHIFT) & 0x0f;
> - trace_kvm_cr_write(0, (kvm_read_cr0_bits(vcpu, ~0xful) | val));
> - kvm_lmsw(vcpu, val);
> + val |= kvm_read_cr0_bits(vcpu, ~0x0eul);
> +
> + trace_kvm_cr_write(cr, val);
> + (void)handle_set_cr0(vcpu, val);
>
> return kvm_skip_emulated_instruction(vcpu);
> default:
>
> > + err = handle_set_cr0(vcpu, (cr0 & ~0x0eul) | val);
> > + return kvm_complete_insn_gp(vcpu, err);
>
> This needs to (a) be a separate patch and (b) apply to L1 as well (though I'm not
> even sure if you/AI realize this is a bug fix). Emulation of LMSW originally
> suppressed the return value, because outside of VMX operation, LMSW can't #GP on
> a bad value. LMSW can't set reserved bits (for the CPU models KVM supports), and
> it can't toggle any bits that trigger additional checks, e.g. can't try to clear
> CR0.PG while in 64-bit mode.
>
> But with VMX, must-be-1 and must-be-0 bits become a thing, and so LMSW could #GP
> if it tried to set/clear a bit in violation of those masks.
>
> Ugh, never mind, screw this. KVM doesn't support arbitrary MSR_IA32_VMX_CR0_FIXED1
> values, i.e. doesn't let userspace require that arbitrary bits always be '0', and
> instead relies on common CR0 reserved bit checks. KVM (very, very stupidly) does
> allow userspace to expand the set of must-be-1 (MSR_IA32_VMX_CR0_FIXED0) bits), and
> so *in theory* LMSW could fault when clearing EM, MP, or TS. Of course, no sane
> VMM sets those as must-be-1 bits. And to make this a complete trainwreck, KVM
> doesn't update its own guest/host masks, i.e. it's own intercepts, to ensure it
> enforces the must-be-1 bits.
>
> E.g. if userspace made CR0.TS a must-be-1 bit, then the guest could violate that
> so long as it doesn't toggle any other bits that would trigger a VM-Exit.
>
> I see absolutely zero reason to try and fix KVM. Letting userspace make CR0.TS
> a must-be-1 bit is beyond stupid. KVM already requires CR0 and CR4 must-be-1
> masks to be a superset of KVM's "defaults", so userspace can only make things
> more restrictive.
>
> So, rather than synthesize #GPs, I think we should prevent nVMX MSRs from inducing
> #GPs in the first place, and then WARN.
Argh, that's not going to work. Thanks to KVM's massive "attack" surface, a.k.a.
save/restore APIs, it's possible for userspace to shove a value into vcpu->arch.cr0
that's in violation of must-be-1 bits. I.e. userspace can trigger the WARN.
So as much as I want to WARN, in practice no real world use case will induce a #GP
on LMSW (or hell, even trigger an exit on LMSW), so I'll go with your patch of
synthesizing a #GP (which is better than silently failing).