* [PATCH v2 0/2] KVM: nVMX: Don't let L2's LMSW modify CR0 bits owned by L1
@ 2026-10-30 3:29 Mahmoud Abumandour
2026-10-30 3:29 ` [PATCH v2 1/2] " Mahmoud Abumandour
2026-10-30 3:29 ` [PATCH v2 2/2] KVM: selftests: Verify that L2's LMSW leaves L1-owned CR0 bits alone Mahmoud Abumandour
0 siblings, 2 replies; 3+ messages in thread
From: Mahmoud Abumandour @ 2026-10-30 3:29 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini; +Cc: kvm, linux-kernel, Mahmoud Abumandour
When L2's LMSW exits to L0 but not to L1, KVM emulates it with
kvm_lmsw(), which ignores vmcs12's CR0 guest/host mask, so L2 can change
CR0 bits that L1 owns.
v2:
- Patch 2: order the CR0 store before FNOP and check L2's whole CR0.
v1: https://lore.kernel.org/all/20261029235040.12013-1-ma.mandourr@gmail.com
Mahmoud Abumandour (2):
KVM: nVMX: Don't let L2's LMSW modify CR0 bits owned by L1
KVM: selftests: Verify that L2's LMSW leaves L1-owned CR0 bits alone
arch/x86/kvm/vmx/vmx.c | 15 ++++++++
.../kvm/x86/nested_exceptions_test.c | 36 +++++++++++++++++++
2 files changed, 51 insertions(+)
base-commit: dea4f6380f4ec96e8f3b0aaf2343764bd0d25ef4
--
2.25.1
^ permalink raw reply [flat|nested] 3+ messages in thread
* [PATCH v2 1/2] KVM: nVMX: Don't let L2's LMSW modify CR0 bits owned by L1
2026-10-30 3:29 [PATCH v2 0/2] KVM: nVMX: Don't let L2's LMSW modify CR0 bits owned by L1 Mahmoud Abumandour
@ 2026-10-30 3:29 ` Mahmoud Abumandour
2026-10-30 3:29 ` [PATCH v2 2/2] KVM: selftests: Verify that L2's LMSW leaves L1-owned CR0 bits alone Mahmoud Abumandour
1 sibling, 0 replies; 3+ messages in thread
From: Mahmoud Abumandour @ 2026-10-30 3:29 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini; +Cc: kvm, linux-kernel, Mahmoud Abumandour
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@gmail.com>
---
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
^ permalink raw reply related [flat|nested] 3+ messages in thread
* [PATCH v2 2/2] KVM: selftests: Verify that L2's LMSW leaves L1-owned CR0 bits alone
2026-10-30 3:29 [PATCH v2 0/2] KVM: nVMX: Don't let L2's LMSW modify CR0 bits owned by L1 Mahmoud Abumandour
2026-10-30 3:29 ` [PATCH v2 1/2] " Mahmoud Abumandour
@ 2026-10-30 3:29 ` Mahmoud Abumandour
1 sibling, 0 replies; 3+ messages in thread
From: Mahmoud Abumandour @ 2026-10-30 3:29 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini; +Cc: kvm, linux-kernel, Mahmoud Abumandour
Verify that an LMSW in L2 that exits to L0 only leaves an L1-owned CR0.TS
set, and that L2 reads an L1-owned CR0.NE from the read shadow.
Assisted-by: LLM
Signed-off-by: Mahmoud Abumandour <ma.mandourr@gmail.com>
---
.../kvm/x86/nested_exceptions_test.c | 36 +++++++++++++++++++
1 file changed, 36 insertions(+)
diff --git a/tools/testing/selftests/kvm/x86/nested_exceptions_test.c b/tools/testing/selftests/kvm/x86/nested_exceptions_test.c
index f47432526e..e33d2e0db0 100644
--- a/tools/testing/selftests/kvm/x86/nested_exceptions_test.c
+++ b/tools/testing/selftests/kvm/x86/nested_exceptions_test.c
@@ -109,6 +109,40 @@ static void l1_svm_code(struct svm_test_data *svm)
GUEST_DONE();
}
+static u64 l2_cr0;
+
+static void l2_lmsw_test(void)
+{
+ /* Toggle CR0.MP, which KVM intercepts and L1 doesn't. */
+ asm volatile("lmsw %w0" : : "r" (get_cr0() ^ X86_CR0_MP));
+ l2_cr0 = get_cr0();
+ asm volatile("fnop" ::: "memory");
+ GUEST_FAIL("L2's FNOP didn't take #NM");
+}
+
+/*
+ * L1 owns CR0.TS (set) and CR0.NE, both with a read shadow of 0. L2's LMSW
+ * must leave both alone: FNOP takes #NM and L2 reads both as 0.
+ */
+static void vmx_run_l2_lmsw(void)
+{
+ u64 cr0 = vmread(GUEST_CR0);
+
+ vmwrite(CR0_GUEST_HOST_MASK, X86_CR0_TS | X86_CR0_NE);
+ vmwrite(CR0_READ_SHADOW, cr0 & ~(X86_CR0_TS | X86_CR0_NE));
+ vmwrite(GUEST_CR0, cr0 | X86_CR0_TS);
+ vmwrite(EXCEPTION_BITMAP, BIT(NM_VECTOR));
+ vmwrite(GUEST_RIP, (u64)l2_lmsw_test);
+ vmresume();
+
+ GUEST_ASSERT_EQ(vmread(VM_EXIT_REASON), EXIT_REASON_EXCEPTION_NMI);
+ GUEST_ASSERT_EQ(vmread(VM_EXIT_INTR_INFO) & 0xff, NM_VECTOR);
+ GUEST_ASSERT_EQ(l2_cr0, (cr0 & ~(X86_CR0_TS | X86_CR0_NE)) ^ X86_CR0_MP);
+
+ vmwrite(CR0_GUEST_HOST_MASK, 0);
+ vmwrite(GUEST_CR0, cr0);
+}
+
static void vmx_run_l2(void *l2_code, int vector, u32 error_code)
{
vmwrite(GUEST_RIP, (u64)l2_code);
@@ -149,6 +183,8 @@ static void l1_vmx_code(struct vmx_pages *vmx)
vmwrite(EXCEPTION_BITMAP, INTERCEPT_SS_DF);
vmx_run_l2(l2_ss_injected_df_test, DF_VECTOR, DF_ERROR_CODE);
+ vmx_run_l2_lmsw();
+
vmwrite(EXCEPTION_BITMAP, INTERCEPT_SS);
vmx_run_l2(l2_ss_injected_tf_test, FAKE_TRIPLE_FAULT_VECTOR, 0);
GUEST_ASSERT_EQ(vmread(VM_EXIT_REASON), EXIT_REASON_TRIPLE_FAULT);
--
2.25.1
^ permalink raw reply related [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-10-30 3:29 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-30 3:29 [PATCH v2 0/2] KVM: nVMX: Don't let L2's LMSW modify CR0 bits owned by L1 Mahmoud Abumandour
2026-10-30 3:29 ` [PATCH v2 1/2] " Mahmoud Abumandour
2026-10-30 3:29 ` [PATCH v2 2/2] KVM: selftests: Verify that L2's LMSW leaves L1-owned CR0 bits alone Mahmoud Abumandour
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.