[PATCH 1/2] KVM: nVMX: Don't let L2's LMSW modify CR0 bits owned by L1

From: ma . mandourr

Date: Thu Oct 29 2026 - 19:51:27 EST


From: Mahmoud Abumandour <ma.mandourr@xxxxxxxxx>

When L2's LMSW exits to L0 but not to L1, emulate it through
handle_set_cr0(), as is done for MOV to CR0. kvm_lmsw() ignores
vmcs12's CR0 guest/host mask, so L2 could change CR0 bits that L1 owns,
e.g. clear TS, and read their effective values instead of the read shadow.

Fixes: eeadf9e7558c ("KVM: nVMX: Handling of CR0 and CR4 modifying instructions")
Signed-off-by: Mahmoud Abumandour <ma.mandourr@xxxxxxxxx>
---
arch/x86/kvm/vmx/vmx.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)

diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index b341af4c60..3633496299 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -5748,6 +5748,21 @@ static int handle_cr(struct kvm_vcpu *vcpu)
case 3: /* lmsw */
val = (exit_qualification >> LMSW_SOURCE_DATA_SHIFT) & 0x0f;
trace_kvm_cr_write(0, (kvm_read_cr0_bits(vcpu, ~0xful) | val));
+ if (is_guest_mode(vcpu)) {
+ struct vmcs12 *vmcs12 = get_vmcs12(vcpu);
+ unsigned long mask = vmcs12->cr0_guest_host_mask;
+ unsigned long cr0;
+
+ /*
+ * LMSW leaves the bits L1 owns unmodified, as does a MOV
+ * to CR0 that writes their read shadow values. Emulate
+ * it as that MOV: L2's view of CR0 with LMSW's bits.
+ */
+ cr0 = (kvm_read_cr0(vcpu) & ~mask) |
+ (vmcs12->cr0_read_shadow & mask);
+ err = handle_set_cr0(vcpu, (cr0 & ~0x0eul) | val);
+ return kvm_complete_insn_gp(vcpu, err);
+ }
kvm_lmsw(vcpu, val);

return kvm_skip_emulated_instruction(vcpu);
--
2.25.1