* [PATCH 1/4] Revert "KVM: x86: Skip EFER vs. guest CPUID checks for host-initiated writes"
2026-10-22 22:30 [PATCH 0/4] KVM: x86: More EFER.LMA vs. CR4.PAE fixes Sean Christopherson
@ 2026-10-22 22:30 ` Sean Christopherson
2026-10-22 22:30 ` [PATCH 2/4] KVM: nSVM: Synthesize #GP instead of SHUTDOWN if L2 attempts to clear EFER.SVME Sean Christopherson
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Sean Christopherson @ 2026-10-22 22:30 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini; +Cc: kvm, linux-kernel
Revert the misguided-in-hindsight decision to allow userspace to violate
architectural consistency when setting EFER as it has since been solidified
that userspace is required to configure guest CPUID prior to setting MSRs,
and skipping the CR0.PG vs. EFER.LME check in allows userspace to load an
end state of EFER.LMA=1 && EFER.LME=0, which is supposed to be impossible
and trips a variety of KVM_BUG_ON()s in the MMU.
This reverts commit 11988499e62b310f3bf6f6d0a807a06d3f9ccc96.
Fixes: 11988499e62b ("KVM: x86: Skip EFER vs. guest CPUID checks for host-initiated writes")
Cc: stable@vger.kernel.org
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/kvm/msrs.c | 30 +++++++++---------------------
1 file changed, 9 insertions(+), 21 deletions(-)
diff --git a/arch/x86/kvm/msrs.c b/arch/x86/kvm/msrs.c
index e91650af44c2..2df42276fd82 100644
--- a/arch/x86/kvm/msrs.c
+++ b/arch/x86/kvm/msrs.c
@@ -580,8 +580,11 @@ static int do_get_feature_msr(struct kvm_vcpu *vcpu, unsigned index, u64 *data)
kvm_get_feature_msr);
}
-static bool __kvm_valid_efer(struct kvm_vcpu *vcpu, u64 efer)
+bool kvm_valid_efer(struct kvm_vcpu *vcpu, u64 efer)
{
+ if (efer & ~kvm_caps.supported_efer_bits)
+ return false;
+
if (efer & EFER_AUTOIBRS && !guest_cpu_cap_has(vcpu, X86_FEATURE_AUTOIBRS))
return false;
@@ -610,33 +613,18 @@ static bool __kvm_valid_efer(struct kvm_vcpu *vcpu, u64 efer)
return true;
}
-
-bool kvm_valid_efer(struct kvm_vcpu *vcpu, u64 efer)
-{
- if (efer & ~kvm_caps.supported_efer_bits)
- return false;
-
- return __kvm_valid_efer(vcpu, efer);
-}
EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_valid_efer);
-static int set_efer(struct kvm_vcpu *vcpu, struct msr_data *msr_info)
+static int set_efer(struct kvm_vcpu *vcpu, u64 efer)
{
u64 old_efer = vcpu->arch.efer;
- u64 efer = msr_info->data;
int r;
- if (efer & ~kvm_caps.supported_efer_bits)
+ if (!kvm_valid_efer(vcpu, efer))
return 1;
- if (!msr_info->host_initiated) {
- if (!__kvm_valid_efer(vcpu, efer))
- return 1;
-
- if (is_paging(vcpu) &&
- (vcpu->arch.efer & EFER_LME) != (efer & EFER_LME))
- return 1;
- }
+ if (is_paging(vcpu) && (vcpu->arch.efer & EFER_LME) != (efer & EFER_LME))
+ return 1;
efer &= ~EFER_LMA;
efer |= vcpu->arch.efer & EFER_LMA;
@@ -1547,7 +1535,7 @@ int kvm_set_msr_common(struct kvm_vcpu *vcpu, struct msr_data *msr_info)
wrmsrq(MSR_IA32_FLUSH_CMD, L1D_FLUSH);
break;
case MSR_EFER:
- return set_efer(vcpu, msr_info);
+ return set_efer(vcpu, msr_info->data);
case MSR_K7_HWCR: {
/*
* Allow McStatusWrEn and TscFreqSel. (Linux guests from v3.2
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply [flat|nested] 5+ messages in thread* [PATCH 2/4] KVM: nSVM: Synthesize #GP instead of SHUTDOWN if L2 attempts to clear EFER.SVME
2026-10-22 22:30 [PATCH 0/4] KVM: x86: More EFER.LMA vs. CR4.PAE fixes Sean Christopherson
2026-10-22 22:30 ` [PATCH 1/4] Revert "KVM: x86: Skip EFER vs. guest CPUID checks for host-initiated writes" Sean Christopherson
@ 2026-10-22 22:30 ` Sean Christopherson
2026-10-22 22:30 ` [PATCH 3/4] KVM: x86: Recompute EFER.LMA instead of inheriting the current value on EFER writes Sean Christopherson
2026-10-22 22:30 ` [PATCH 4/4] KVM: selftests: Update nSVM EFER test to expect #GP when clearing SVME in L2 Sean Christopherson
3 siblings, 0 replies; 5+ messages in thread
From: Sean Christopherson @ 2026-10-22 22:30 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini; +Cc: kvm, linux-kernel
If L2 attempts to clear EFER.SVME (because L1 neglected to intercept writes
to EFER), synthesize a #GP instead of a triple-fault SHUTDOWN for L1, as
yanking the vCPU out of L2 and mixing the new (invalid) EFER with L2's
current state is unnecessarily risky for KVM since the TRIPLE_FAULT request
isn't fully handled until KVM attempts to enter the guest, and nothing
prevents userspace from ignoring the SHUTDOWN exit and continue running the
guest.
Apply the restriction to both guest and host (a.k.a. userspace) writes, as
unlike kvm_x86_ops.set_efer() => svm_set_efer(), set_efer() is used *only*
for targeted MSR writes, e.g. not for KVM_SET_SREGS{,2}, and KVM does NOT
advertise EFER as an MSR that needs to be saved (because it's handled via
sregs). I.e. there is far less chance of breaking userspace that manually
restores EFER with SVME=0 while L2 is active, because in all likelihood no
such VMM exists.
Keep the sanity check in svm_set_efer() that handles the KVM_RUN case, but
escalate it to a KVM_BUG_ON() + -EIO, as it should now be unreachable.
Cc: stable@vger.kernel.org
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/kvm/msrs.c | 12 ++++++++++++
arch/x86/kvm/svm/svm.c | 15 +++++----------
2 files changed, 17 insertions(+), 10 deletions(-)
diff --git a/arch/x86/kvm/msrs.c b/arch/x86/kvm/msrs.c
index 2df42276fd82..c3cb1575fe66 100644
--- a/arch/x86/kvm/msrs.c
+++ b/arch/x86/kvm/msrs.c
@@ -626,6 +626,18 @@ static int set_efer(struct kvm_vcpu *vcpu, u64 efer)
if (is_paging(vcpu) && (vcpu->arch.efer & EFER_LME) != (efer & EFER_LME))
return 1;
+ /*
+ * Architecturally (per the APM), clearing EFER.SVME while a guest is
+ * running yields undefined behavior, i.e. KVM can do literally
+ * anything. Synthesize a #GP as that is the safest option for KVM,
+ * as mixing the current vCPU state (from L2) with L1 and the new EFER
+ * (if KVM simply forces the vCPU out of L2) may yield an unexpected
+ * end result.
+ */
+ if (is_guest_mode(vcpu) &&
+ (vcpu->arch.efer & EFER_SVME) && !(efer & EFER_SVME))
+ return 1;
+
efer &= ~EFER_LMA;
efer |= vcpu->arch.efer & EFER_LMA;
diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
index 487897ef11dd..a6ff9635677a 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -227,17 +227,12 @@ int svm_set_efer(struct kvm_vcpu *vcpu, u64 efer)
if ((old_efer & EFER_SVME) != (efer & EFER_SVME)) {
if (!(efer & EFER_SVME)) {
/*
- * Architecturally, clearing EFER.SVME while a guest is
- * running yields undefined behavior, i.e. KVM can do
- * literally anything. Force the vCPU back into L1 as
- * that is the safest option for KVM, but synthesize a
- * triple fault (for L1!) so that KVM at least doesn't
- * run random L2 code in the context of L1. Do so if
- * and only if the vCPU is actively running, e.g. to
- * avoid positives if userspace is stuffing state.
+ * KVM is supposed to synthesize #GP if L2 attempts to
+ * clear SVME.
*/
- if (is_guest_mode(vcpu) && vcpu->wants_to_run)
- kvm_make_request(KVM_REQ_TRIPLE_FAULT, vcpu);
+ if (KVM_BUG_ON(is_guest_mode(vcpu) &&
+ vcpu->wants_to_run, vcpu->kvm))
+ return -EIO;
svm_leave_nested(vcpu);
/* #GP intercept is still needed for vmware backdoor */
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply [flat|nested] 5+ messages in thread* [PATCH 3/4] KVM: x86: Recompute EFER.LMA instead of inheriting the current value on EFER writes
2026-10-22 22:30 [PATCH 0/4] KVM: x86: More EFER.LMA vs. CR4.PAE fixes Sean Christopherson
2026-10-22 22:30 ` [PATCH 1/4] Revert "KVM: x86: Skip EFER vs. guest CPUID checks for host-initiated writes" Sean Christopherson
2026-10-22 22:30 ` [PATCH 2/4] KVM: nSVM: Synthesize #GP instead of SHUTDOWN if L2 attempts to clear EFER.SVME Sean Christopherson
@ 2026-10-22 22:30 ` Sean Christopherson
2026-10-22 22:30 ` [PATCH 4/4] KVM: selftests: Update nSVM EFER test to expect #GP when clearing SVME in L2 Sean Christopherson
3 siblings, 0 replies; 5+ messages in thread
From: Sean Christopherson @ 2026-10-22 22:30 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini; +Cc: kvm, linux-kernel
Recompute EFER.LMA on bare writes to EFER (via WRMSR or KVM_SET_MSRS)
instead of carrying forward the current EFER.LMA. While it _should_ be
impossible for EFER.LMA to end up in an inconsistent state on such a write,
KVM has had a rash of exactly those bugs, mostly because SVM is quite lax
when it comes to consistenty checks, which in turn bleeds into KVM's nested
SVM implementation. Retaining the existing EFER is also rather confusing,
as understanding why keeping EFER.LMA is correct requires a lot of a priori
knowledge of both x86-64 and KVM.
Keep the core concept of the existing logic as a sanity check: WARN and
reject the write if the computed EFER.LMA would be toggled, because as
described above, that _should_ be impossible.
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/kvm/msrs.c | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/arch/x86/kvm/msrs.c b/arch/x86/kvm/msrs.c
index c3cb1575fe66..41f3ec6eb286 100644
--- a/arch/x86/kvm/msrs.c
+++ b/arch/x86/kvm/msrs.c
@@ -638,8 +638,18 @@ static int set_efer(struct kvm_vcpu *vcpu, u64 efer)
(vcpu->arch.efer & EFER_SVME) && !(efer & EFER_SVME))
return 1;
- efer &= ~EFER_LMA;
- efer |= vcpu->arch.efer & EFER_LMA;
+ if ((efer & EFER_LME) && is_paging(vcpu) && is_pae(vcpu))
+ efer |= EFER_LMA;
+ else
+ efer &= ~EFER_LMA;
+
+ /*
+ * Toggling EFER.LMA on a bare write to EFER should be impossible, as
+ * it means KVM allowed architecturally invalid state at some point in
+ * the past, i.e. allowed EFER.LMA=1 && (!CR0.PG || !CR4.PAE).
+ */
+ if (WARN_ON_ONCE((efer & EFER_LMA) != (vcpu->arch.efer & EFER_LMA)))
+ return 1;
r = kvm_x86_call(set_efer)(vcpu, efer);
if (r) {
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply [flat|nested] 5+ messages in thread* [PATCH 4/4] KVM: selftests: Update nSVM EFER test to expect #GP when clearing SVME in L2
2026-10-22 22:30 [PATCH 0/4] KVM: x86: More EFER.LMA vs. CR4.PAE fixes Sean Christopherson
` (2 preceding siblings ...)
2026-10-22 22:30 ` [PATCH 3/4] KVM: x86: Recompute EFER.LMA instead of inheriting the current value on EFER writes Sean Christopherson
@ 2026-10-22 22:30 ` Sean Christopherson
3 siblings, 0 replies; 5+ messages in thread
From: Sean Christopherson @ 2026-10-22 22:30 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini; +Cc: kvm, linux-kernel
Adjust the nSVM EFER test to expect a #GP on WRMSR if L2 attempts to clear
EFER.SVME to match KVM's new behavior (synthesizing #GP is safer for KVM as
it doesn't require yanking the vCPU out of L2, and is fully synchronous).
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
.../selftests/kvm/x86/svm_nested_efer_test.c | 65 +++++++++++--------
1 file changed, 37 insertions(+), 28 deletions(-)
diff --git a/tools/testing/selftests/kvm/x86/svm_nested_efer_test.c b/tools/testing/selftests/kvm/x86/svm_nested_efer_test.c
index fa0d76a9c390..f612146d23ac 100644
--- a/tools/testing/selftests/kvm/x86/svm_nested_efer_test.c
+++ b/tools/testing/selftests/kvm/x86/svm_nested_efer_test.c
@@ -15,13 +15,18 @@ static bool l2_ran;
static void l2_clear_efer_svme(void)
{
u64 efer = rdmsr(MSR_EFER);
+ u8 vector;
/* generic_svm_setup() initializes EFER_SVME set for L2 */
GUEST_ASSERT(efer & EFER_SVME);
- wrmsr(MSR_EFER, efer & ~EFER_SVME);
- /* Unreachable, L1 should be shutdown */
- GUEST_ASSERT(0);
+ vector = wrmsr_safe(MSR_EFER, efer & ~EFER_SVME);
+ __GUEST_ASSERT(vector == GP_VECTOR,
+ "Expected #GP when clearing EFER.LME in L2, got %s",
+ ex_str(vector));
+
+ l2_ran = true;
+ vmmcall();
}
static void l1_clear_efer_svme(struct svm_test_data *svm)
@@ -29,8 +34,7 @@ static void l1_clear_efer_svme(struct svm_test_data *svm)
generic_svm_setup(svm, l2_clear_efer_svme);
run_guest(svm->vmcb, svm->vmcb_gpa);
- /* Unreachable, L1 should be shutdown */
- GUEST_ASSERT(0);
+ GUEST_DONE();
}
static void l2_lmsle(void)
@@ -130,31 +134,10 @@ static void test_enumeration(void)
ksft_test_result_pass("KVM enumerates EFER_LMSLE_MBZ=%d\n", lmsle_mbz);
}
-static void test_clear_efer_svme(void)
+static void test_efer_in_guest(struct kvm_vcpu *vcpu)
{
- struct kvm_vcpu *vcpu;
- struct kvm_vm *vm;
-
- vcpu = create_l1_vcpu(&vm, l1_clear_efer_svme);
-
- vcpu_run(vcpu);
- TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_SHUTDOWN);
-
- kvm_vm_free(vm);
- ksft_test_result_pass("L2 clearing EFER.SVME shuts down L1\n");
-}
-
-static void test_lmsle(bool lmsle_mbz)
-{
- struct kvm_vcpu *vcpu;
- struct kvm_vm *vm;
struct ucall uc;
- vcpu = create_l1_vcpu(&vm, l1_lmsle);
-
- vcpu_set_or_clear_cpuid_feature(vcpu, X86_FEATURE_EFER_LMSLE_MBZ,
- lmsle_mbz);
-
vcpu_run(vcpu);
TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
@@ -166,6 +149,32 @@ static void test_lmsle(bool lmsle_mbz)
default:
TEST_FAIL("Unexpected ucall: %lu", uc.cmd);
}
+}
+
+static void test_clear_efer_svme(void)
+{
+ struct kvm_vcpu *vcpu;
+ struct kvm_vm *vm;
+
+ vcpu = create_l1_vcpu(&vm, l1_clear_efer_svme);
+
+ test_efer_in_guest(vcpu);
+
+ kvm_vm_free(vm);
+ ksft_test_result_pass("L2 clearing EFER.SVME gets #GP\n");
+}
+
+static void test_lmsle(bool lmsle_mbz)
+{
+ struct kvm_vcpu *vcpu;
+ struct kvm_vm *vm;
+
+ vcpu = create_l1_vcpu(&vm, l1_lmsle);
+
+ vcpu_set_or_clear_cpuid_feature(vcpu, X86_FEATURE_EFER_LMSLE_MBZ,
+ lmsle_mbz);
+
+ test_efer_in_guest(vcpu);
kvm_vm_free(vm);
ksft_test_result_pass("Guest EFER_LMSLE_MBZ=%d\n", lmsle_mbz);
@@ -248,7 +257,7 @@ int main(int argc, char *argv[])
test_clear_efer_svme();
test_lmsle(true);
} else {
- ksft_test_result_skip("L2 clearing EFER.SVME shuts down L1\n");
+ ksft_test_result_skip("L2 clearing EFER.SVME gets #GP\n");
ksft_test_result_skip("Guest EFER_LMSLE_MBZ=1\n");
}
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply [flat|nested] 5+ messages in thread