[PATCH v3 3/5] KVM: nVMX: Synthesize #GP if L2 writes a "bad" CR0 value via LMSW

From: Sean Christopherson

Date: Fri Oct 30 2026 - 17:42:53 EST


From: Mahmoud Abumandour <ma.mandourr@xxxxxxxxx>

Synthesize a #GP if writing CR0 when emulating LMSW fails, as it's possible
for LMSW to #GP on a bad value when executed post-VMXON, i.e. if LMSW
attempts to set/clear bits in violation of MSR_IA32_VMX_CR0_FIXED{0,1}.

Outside of VMX operation, LMSW can only arbitrarily modify bits 3:1 and can
only set bit 0 (PE). I.e. LMSW can't modify any CR0 bits that are
unsupported or are subject to additional checks and thus should never #GP
due to a bad value.

Note, KVM has a virtualization hole where it doesn't enforce must-be-1 CR0
or CR4 bits that KVM doesn't intercepted. E.g. if userspace marked CR0.TS
as a must-be-1 bit (while post-VMXON), then the guest could clear CR0.TS.
That hole will be plugged shortly.

Note #2, with the virtualization hole plugged, it *should* be impossible
for LMSW to #GP on a bad value. Unfortunately, KVM's save/restore ioctls
allow userspace to load a CR0 value that violates the must-be-1 masks, i.e.
userspace can make the impossible happen (and plugging *those* holes is all
but guaranteed to be a waste of time). Ideally, KVM would WARN, but since
userspace could trip the WARN, take the easy route and handle failure
appropriately.

Fixes: eeadf9e7558c ("KVM: nVMX: Handling of CR0 and CR4 modifying instructions")
Signed-off-by: Mahmoud Abumandour <ma.mandourr@xxxxxxxxx>
[sean: split to a separate patch]
Signed-off-by: Sean Christopherson <seanjc@xxxxxxxxxx>
---
arch/x86/kvm/vmx/vmx.c | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index 2d5feeb8a059..68c1361a92d1 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -5746,9 +5746,8 @@ static int handle_cr(struct kvm_vcpu *vcpu)
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);
+ err = handle_set_cr0(vcpu, val);
+ return kvm_complete_insn_gp(vcpu, err);
default:
break;
}
--
2.56.0.385.gac671c819a-goog