mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v2 0/4] KVM: x86: Honor EFER_LMSLE_MBZ
@ 2026-09-24  0:25 Jim Mattson
  2026-09-24  0:25 ` [PATCH v2 1/4] KVM: x86: Advertise EFER_LMSLE_MBZ when KVM disallows EFER.LMSLE Jim Mattson
                   ` (3 more replies)
  0 siblings, 4 replies; 5+ messages in thread
From: Jim Mattson @ 2026-09-24  0:25 UTC (permalink / raw)
  To: seanjc, pbonzini; +Cc: kvm, linux-kernel, nikunj, yosry, Jim Mattson

v1 was a single patch that made KVM reject EFER.LMSLE=1 when the guest's
CPUID enumerates EFER_LMSLE_MBZ, CPUID.80000008H:EBX[bit 20].  Review turned
up two more things: KVM's *own* enumeration of the defeature is wrong today,
and the new guest-CPUID check needs an escape hatch so that userspace can
still set the bit on a host where KVM doesn't advertise it.

The motivation, as discussed in the v1 thread, is to be able to defeature a
virtual Rome, i.e. to set EFER_LMSLE_MBZ even though Rome itself supports
long mode segment limits, so that the vCPU can be hosted on Milan and later.
The opposite direction isn't interesting; in general you can't host
generation N+1 on generation N.

Patch 1 fixes KVM's enumeration of the defeature.  Today KVM passes
hardware's EFER_LMSLE_MBZ through as-is, i.e. leaves the bit clear on Intel
and on AMD with kvm_amd.nested=0, which tells userspace that long mode
segment limits *are* available.  But KVM allows EFER.LMSLE if and only if
nested SVM is supported, so on exactly those hosts KVM then rejects
WRMSR(EFER) with EFER.LMSLE=1.  Set EFER_LMSLE_MBZ whenever KVM refuses
EFER.LMSLE; the bit means precisely "EFER.LMSLE must be zero".  Note, this
is guest visible for userspace that reflects KVM's supported CPUID into the
guest.

Patch 2 is v1, plus the partial-emulation hunk Sean suggested so that
userspace can set EFER_LMSLE_MBZ on a host that does support LMSLE.  It
also masks EFER_LMSLE out of the value consumed by efer_trap().  Under
SEV-ES, EFER writes are *trapped*, not intercepted.  Rejecting the write
would inject a #GP *and* leave EFER.LMSLE set, which is strictly worse than
honoring a write that hardware allowed.

Patches 3 and 4 rename svm_nested_clear_efer_svme to svm_nested_efer_test
and add coverage for the defeature.

Tested on Rome, which supports LMSLE and so actually exercises the emulated
path, and on Skylake.

v1: https://lore.kernel.org/all/20260918154530.4129698-1-jmattson@google.com

Jim Mattson (4):
  KVM: x86: Advertise EFER_LMSLE_MBZ when KVM disallows EFER.LMSLE
  KVM: x86: Honor the guest's EFER_LMSLE_MBZ
  KVM: selftests: Rename svm_nested_clear_efer_svme to
    svm_nested_efer_test
  KVM: selftests: Add coverage for the EFER_LMSLE_MBZ defeature

 Documentation/virt/kvm/api.rst                |  25 ++
 arch/x86/kvm/cpuid.c                          |  20 ++
 arch/x86/kvm/msrs.c                           |  12 +-
 arch/x86/kvm/svm/svm.c                        |  21 +-
 arch/x86/kvm/vmx/vmx.c                        |   7 +
 arch/x86/kvm/x86.c                            |   9 +-
 tools/testing/selftests/kvm/Makefile.kvm      |   2 +-
 .../selftests/kvm/include/x86/processor.h     |   1 +
 .../kvm/x86/svm_nested_clear_efer_svme.c      |  50 ----
 .../selftests/kvm/x86/svm_nested_efer_test.c  | 273 ++++++++++++++++++
 10 files changed, 366 insertions(+), 54 deletions(-)
 delete mode 100644 tools/testing/selftests/kvm/x86/svm_nested_clear_efer_svme.c
 create mode 100644 tools/testing/selftests/kvm/x86/svm_nested_efer_test.c


base-commit: f0100363d8c374bd8e9ea7c9ba02744f0b802ca4
-- 
2.56.0.rc1.315.gc6ed9934b7-goog


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH v2 1/4] KVM: x86: Advertise EFER_LMSLE_MBZ when KVM disallows EFER.LMSLE
  2026-09-24  0:25 [PATCH v2 0/4] KVM: x86: Honor EFER_LMSLE_MBZ Jim Mattson
@ 2026-09-24  0:25 ` Jim Mattson
  2026-09-24  0:25 ` [PATCH v2 2/4] KVM: x86: Honor the guest's EFER_LMSLE_MBZ Jim Mattson
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 5+ messages in thread
From: Jim Mattson @ 2026-09-24  0:25 UTC (permalink / raw)
  To: seanjc, pbonzini; +Cc: kvm, linux-kernel, nikunj, yosry, Jim Mattson

Set EFER_LMSLE_MBZ, CPUID.80000008H:EBX[bit 20], in KVM's supported CPUID
whenever KVM refuses to set EFER.LMSLE, not just when hardware enumerates the
defeature.  KVM allows EFER.LMSLE if and only if nested SVM is supported, but
passes through hardware's EFER_LMSLE_MBZ as-is, i.e. KVM tells userspace that
long mode segment limits are available on an Intel CPU, and on an AMD CPU with
kvm_amd.nested=0, and then rejects WRMSR(EFER) with EFER.LMSLE=1.  The bit
means precisely "EFER.LMSLE must be zero."

Note, this is purely an enumeration change.  EFER.LMSLE is already gated on
nested SVM: kvm_setup_efer_caps() adds EFER_LMSLE to supported_efer_bits only
if X86_FEATURE_SVM is supported, and svm_set_cpu_caps() sets X86_FEATURE_SVM
if and only if "nested" is true.  KVM's handling of EFER is unchanged; only
what KVM tells userspace changes.

Note #2, KVM now sets the defeature on Intel CPUs as well.  That is no
different than KVM setting NullSelectorClearsBase, another AMD-defined bit, on
CPUs that don't have the errata.  And LMSLE's dependency on nested SVM is a
historical artifact of commit eec4b140c924 ("KVM: SVM: Allow EFER.LMSLE to be
set with nested svm"), not an architectural requirement.

Be aware that this changes KVM_GET_SUPPORTED_CPUID on all Intel hosts and on
AMD hosts with kvm_amd.nested=0, i.e. userspace that reflects KVM's supported
CPUID into the guest will start enumerating EFER_LMSLE_MBZ=1 to newly created
guests.  That's the correct enumeration, and it takes away only an EFER bit
that KVM has never allowed to be set on such hosts, but it *is* guest visible.
Deliberately omit a Fixes: tag: the misenumeration is benign in practice, as
KVM's WRMSR(EFER) behavior is and always has been correct, and backporting a
guest-visible CPUID change isn't worth the risk.

Opportunistically key the EFER_LMSLE decision off kvm_cpu_cap_has() instead of
boot_cpu_has(), as PASSTHROUGH_F() sets EFER_LMSLE_MBZ based on *raw* CPUID,
i.e. "clearcpuid=13:20" would otherwise get KVM to enumerate the defeature and
allow EFER.LMSLE=1, which is guaranteed to fail on VMRUN.

Document KVM's enumeration of the defeature in api.rst.

Assisted-by: LLM
Signed-off-by: Jim Mattson <jmattson@google.com>
---
 Documentation/virt/kvm/api.rst | 11 +++++++++++
 arch/x86/kvm/cpuid.c           |  4 ++++
 arch/x86/kvm/svm/svm.c         | 10 ++++++++++
 arch/x86/kvm/vmx/vmx.c         |  7 +++++++
 arch/x86/kvm/x86.c             |  9 ++++++++-
 5 files changed, 40 insertions(+), 1 deletion(-)

diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst
index e0430cc750c9..3c5bcdb0923f 100644
--- a/Documentation/virt/kvm/api.rst
+++ b/Documentation/virt/kvm/api.rst
@@ -9635,6 +9635,17 @@ On older versions of Linux, CPU[EAX=1]:ECX[24] (TSC_DEADLINE) is not reported by
 is present and the kernel has enabled in-kernel emulation of the local APIC.
 On newer versions, ``KVM_GET_SUPPORTED_CPUID`` does report the bit as available.
 
+Long mode segment limits
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+CPU[EAX=0x80000008]:EBX[20] (EFER_LMSLE_MBZ) is a "defeature" bit: it is set
+when the CPU does *not* support long mode segment limits, and so requires
+EFER.LMSLE to be zero.  KVM reports the bit via ``KVM_GET_SUPPORTED_CPUID`` if
+and only if KVM refuses to set EFER.LMSLE, i.e. if the CPU doesn't support long
+mode segment limits, or if nested SVM is unsupported.  KVM therefore reports
+the bit on all Intel hosts, as KVM allows EFER.LMSLE only when nested SVM is
+enabled.
+
 CPU topology
 ~~~~~~~~~~~~
 
diff --git a/arch/x86/kvm/cpuid.c b/arch/x86/kvm/cpuid.c
index 851f151efb35..dbe20d5e6f80 100644
--- a/arch/x86/kvm/cpuid.c
+++ b/arch/x86/kvm/cpuid.c
@@ -1171,6 +1171,10 @@ void kvm_initialize_cpu_caps(void)
 		F(AMD_STIBP),
 		F(AMD_STIBP_ALWAYS_ON),
 		F(AMD_IBRS_SAME_MODE),
+		/*
+		 * Vendor code also sets EFER_LMSLE_MBZ if KVM itself
+		 * can't support EFER.LMSLE, e.g. if nested SVM is disabled.
+		 */
 		PASSTHROUGH_F(EFER_LMSLE_MBZ),
 		F(AMD_PSFD),
 		F(AMD_IBPB_RET),
diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
index 7d59d301e1e5..58768e49505e 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -5571,6 +5571,16 @@ static __init void svm_set_cpu_caps(void)
 	    boot_cpu_has(X86_FEATURE_AMD_SSBD))
 		kvm_cpu_cap_set(X86_FEATURE_VIRT_SSBD);
 
+	/*
+	 * Tell userspace that EFER.LMSLE must be zero if nested SVM is
+	 * disabled, as KVM allows EFER.LMSLE if and only if nested SVM is
+	 * supported (a historical artifact of commit eec4b140c924 ("KVM: SVM:
+	 * Allow EFER.LMSLE to be set with nested svm"), not an architectural
+	 * requirement).
+	 */
+	if (!nested)
+		kvm_cpu_cap_set(X86_FEATURE_EFER_LMSLE_MBZ);
+
 	if (enable_pmu) {
 		/*
 		 * Enumerate support for PERFCTR_CORE if and only if KVM has
diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index 612ab07d4100..f144c1e1c63f 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -8137,6 +8137,13 @@ static __init void vmx_set_cpu_caps(void)
 		kvm_cpu_cap_clear(X86_FEATURE_IBT);
 	}
 
+	/*
+	 * CPUID 0x80000008.  Tell userspace that EFER.LMSLE must be zero; KVM
+	 * never allows EFER.LMSLE to be set on Intel CPUs, as KVM supports long
+	 * mode segment limits only in conjunction with nested SVM.
+	 */
+	kvm_cpu_cap_set(X86_FEATURE_EFER_LMSLE_MBZ);
+
 	kvm_setup_xss_caps();
 	kvm_finalize_cpu_caps();
 }
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 79468ddfe473..830cb9320d89 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -6930,7 +6930,14 @@ static void kvm_setup_efer_caps(void)
 
 	if (kvm_cpu_cap_has(X86_FEATURE_SVM)) {
 		kvm_caps.supported_efer_bits |= EFER_SVME;
-		if (!boot_cpu_has(X86_FEATURE_EFER_LMSLE_MBZ))
+
+		/*
+		 * Enumerating EFER_LMSLE_MBZ and allowing EFER.LMSLE=1
+		 * would be nonsensical.  Note, vendor code sets the defeature
+		 * if KVM can't support EFER.LMSLE for any reason, i.e. this
+		 * needs to consult KVM's capabilities, not just raw CPUID.
+		 */
+		if (!kvm_cpu_cap_has(X86_FEATURE_EFER_LMSLE_MBZ))
 			kvm_caps.supported_efer_bits |= EFER_LMSLE;
 	}
 }
-- 
2.56.0.rc1.315.gc6ed9934b7-goog


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH v2 2/4] KVM: x86: Honor the guest's EFER_LMSLE_MBZ
  2026-09-24  0:25 [PATCH v2 0/4] KVM: x86: Honor EFER_LMSLE_MBZ Jim Mattson
  2026-09-24  0:25 ` [PATCH v2 1/4] KVM: x86: Advertise EFER_LMSLE_MBZ when KVM disallows EFER.LMSLE Jim Mattson
@ 2026-09-24  0:25 ` Jim Mattson
  2026-09-24  0:25 ` [PATCH v2 3/4] KVM: selftests: Rename svm_nested_clear_efer_svme to svm_nested_efer_test Jim Mattson
  2026-09-24  0:25 ` [PATCH v2 4/4] KVM: selftests: Add coverage for the EFER_LMSLE_MBZ defeature Jim Mattson
  3 siblings, 0 replies; 5+ messages in thread
From: Jim Mattson @ 2026-09-24  0:25 UTC (permalink / raw)
  To: seanjc, pbonzini; +Cc: kvm, linux-kernel, nikunj, yosry, Jim Mattson

Reject guest attempts to set EFER.LMSLE if userspace enumerates
EFER_LMSLE_MBZ, CPUID.80000008H:EBX[bit 20], in guest CPUID, so that userspace
can hide long mode segment limits from the guest even on CPUs that support
LMSLE, e.g. for migration compatibility between Rome and Milan.

Enumerate the defeature as *partially* emulated, i.e. as supported by KVM but
never advertised, so that kvm_vcpu_after_set_cpuid() propagates userspace's bit
into vcpu->arch.cpu_caps.  Checking guest_cpu_cap_has() in __kvm_valid_efer()
doesn't suffice on its own, because KVM clears the defeature in kvm_cpu_caps
precisely on the CPUs where it needs to be emulated, i.e. cpu_caps would never
hold the bit no matter what userspace puts in guest CPUID.  Don't advertise the
defeature in KVM_GET_SUPPORTED_CPUID, as that would take EFER.LMSLE away from
userspace that blindly stuffs KVM's supported CPUID into KVM_SET_CPUID2.
MONITOR/MWAIT gets the same treatment.

Enforce the defeature only for guest-initiated writes, as it's a guest CPUID
consistency check, not a host capability.  See commit 11988499e62b ("KVM: x86:
Skip EFER vs. guest CPUID checks for host-initiated writes").  Note,
KVM_SET_SREGS does reject EFER.LMSLE, as it runs the full set of guest CPUID
checks, as does nested VMRUN, i.e. L1 can't sneak EFER.LMSLE into L2 via
vmcb12.

Mask EFER_LMSLE out of the value consumed by efer_trap(), which handles
SVM_EXIT_EFER_WRITE_TRAP.  EFER writes are *trapped*, not intercepted, i.e.
hardware has already committed the write by the time KVM gains control, and
the trap is enabled if and only if the guest is SEV-ES, whose EFER lives in
the encrypted VMSA and so can't be fixed up by KVM.  Rejecting the write would
inject a #GP *and* leave EFER.LMSLE set in the guest, which is strictly worse
than honoring a write that hardware itself allowed.  EFER_SVME is already
masked out for a similar "KVM can't enforce this here" reason.

Opportunistically fix the whitespace damage around __kvm_valid_efer().

Document the resulting two-level contract in api.rst, i.e. that userspace may
set the defeature even when KVM doesn't enumerate it.

Suggested-by: Sean Christopherson <seanjc@google.com>
Assisted-by: LLM
Signed-off-by: Jim Mattson <jmattson@google.com>
---
 Documentation/virt/kvm/api.rst | 14 ++++++++++++++
 arch/x86/kvm/cpuid.c           | 16 ++++++++++++++++
 arch/x86/kvm/msrs.c            | 12 +++++++++++-
 arch/x86/kvm/svm/svm.c         | 11 ++++++++++-
 4 files changed, 51 insertions(+), 2 deletions(-)

diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst
index 3c5bcdb0923f..2852bf94828b 100644
--- a/Documentation/virt/kvm/api.rst
+++ b/Documentation/virt/kvm/api.rst
@@ -9646,6 +9646,20 @@ mode segment limits, or if nested SVM is unsupported.  KVM therefore reports
 the bit on all Intel hosts, as KVM allows EFER.LMSLE only when nested SVM is
 enabled.
 
+KVM never reports the bit via ``KVM_GET_EMULATED_CPUID``, but userspace may set
+it via ``KVM_SET_CPUID2`` even on a host where KVM doesn't report it.  KVM
+honors the guest's enumeration and rejects EFER.LMSLE=1 accordingly.  That lets
+userspace defeature a vCPU on a host that *does* support long mode segment
+limits, so that the vCPU can later be migrated to a host that doesn't, e.g. so
+that a vCPU created on AMD Rome can be migrated to Milan and later, which
+dropped support for long mode segment limits.  The opposite direction needs no
+emulation, as a host that lacks long mode segment limits already enumerates the
+defeature.
+
+Note, ``KVM_SET_MSRS`` is exempt from the check, as host-initiated MSR writes
+skip guest CPUID checks so that userspace can set MSRs before it sets guest
+CPUID.  ``KVM_SET_SREGS`` and nested VMRUN are not exempt.
+
 CPU topology
 ~~~~~~~~~~~~
 
diff --git a/arch/x86/kvm/cpuid.c b/arch/x86/kvm/cpuid.c
index dbe20d5e6f80..53d205eda335 100644
--- a/arch/x86/kvm/cpuid.c
+++ b/arch/x86/kvm/cpuid.c
@@ -1419,6 +1419,22 @@ static int cpuid_func_emulated(struct kvm_cpuid_entry2 *entry, u32 func, u32 ind
 		if (kvm_cpu_cap_has(X86_FEATURE_RDTSCP))
 			entry->ecx = feature_bit(RDPID);
 		return 1;
+	case 0x80000008:
+		/*
+		 * Honor the guest's EFER_LMSLE_MBZ even if the underlying CPU
+		 * allows setting EFER.LMSLE, e.g. to allow migrating a vCPU
+		 * between hosts with and without EFER.LMSLE support.  To avoid
+		 * breaking existing setups that reflect KVM's supported CPUID
+		 * into the guest, KVM doesn't advertise EFER_LMSLE_MBZ unless
+		 * KVM *can't* support EFER.LMSLE=1.
+		 */
+		if (include_partially_emulated &&
+		    !kvm_cpu_cap_has(X86_FEATURE_EFER_LMSLE_MBZ)) {
+			entry->ebx |= feature_bit(EFER_LMSLE_MBZ);
+			return 1;
+		}
+		/* Nothing in 0x80000008 is fully emulated, don't emit an entry. */
+		return 0;
 	default:
 		return 0;
 	}
diff --git a/arch/x86/kvm/msrs.c b/arch/x86/kvm/msrs.c
index dd3bb04878ca..b519fb90776e 100644
--- a/arch/x86/kvm/msrs.c
+++ b/arch/x86/kvm/msrs.c
@@ -598,9 +598,19 @@ static bool __kvm_valid_efer(struct kvm_vcpu *vcpu, u64 efer)
 	if (efer & EFER_NX && !guest_cpu_cap_has(vcpu, X86_FEATURE_NX))
 		return false;
 
-	return true;
+	/*
+	 * EFER_LMSLE_MBZ is a "defeature" bit, i.e. is set when the CPU does
+	 * *not* support long mode segment limits, and so is the only EFER
+	 * check whose polarity is inverted: EFER.LMSLE is legal if and only if
+	 * the guest does *not* have the defeature.
+	 */
+	if (efer & EFER_LMSLE &&
+	    guest_cpu_cap_has(vcpu, X86_FEATURE_EFER_LMSLE_MBZ))
+		return false;
 
+	return true;
 }
+
 bool kvm_valid_efer(struct kvm_vcpu *vcpu, u64 efer)
 {
 	if (efer & ~kvm_caps.supported_efer_bits)
diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
index 58768e49505e..90a80aad672f 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -2759,10 +2759,19 @@ static int efer_trap(struct kvm_vcpu *vcpu)
 	 * bit in svm_set_efer(), but __kvm_valid_efer() checks it against
 	 * whether the guest has X86_FEATURE_SVM - this avoids a failure if
 	 * the guest doesn't have X86_FEATURE_SVM.
+	 *
+	 * Clear EFER_LMSLE for a related reason: EFER writes are *trapped*,
+	 * not intercepted, i.e. hardware has already committed the write by
+	 * the time KVM gains control, and the trap is enabled if and only if
+	 * the guest is SEV-ES, whose EFER lives in the encrypted VMSA and so
+	 * can't be fixed up by KVM.  Rejecting EFER.LMSLE=1 would inject a #GP
+	 * *and* leave EFER.LMSLE set in the guest, which is strictly worse
+	 * than honoring a write that hardware itself allowed.
 	 */
 	msr_info.host_initiated = false;
 	msr_info.index = MSR_EFER;
-	msr_info.data = to_svm(vcpu)->vmcb->control.exit_info_1 & ~EFER_SVME;
+	msr_info.data = to_svm(vcpu)->vmcb->control.exit_info_1 &
+			~(EFER_SVME | EFER_LMSLE);
 	ret = kvm_set_msr_common(vcpu, &msr_info);
 
 	return kvm_complete_insn_gp(vcpu, ret);
-- 
2.56.0.rc1.315.gc6ed9934b7-goog


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH v2 3/4] KVM: selftests: Rename svm_nested_clear_efer_svme to svm_nested_efer_test
  2026-09-24  0:25 [PATCH v2 0/4] KVM: x86: Honor EFER_LMSLE_MBZ Jim Mattson
  2026-09-24  0:25 ` [PATCH v2 1/4] KVM: x86: Advertise EFER_LMSLE_MBZ when KVM disallows EFER.LMSLE Jim Mattson
  2026-09-24  0:25 ` [PATCH v2 2/4] KVM: x86: Honor the guest's EFER_LMSLE_MBZ Jim Mattson
@ 2026-09-24  0:25 ` Jim Mattson
  2026-09-24  0:25 ` [PATCH v2 4/4] KVM: selftests: Add coverage for the EFER_LMSLE_MBZ defeature Jim Mattson
  3 siblings, 0 replies; 5+ messages in thread
From: Jim Mattson @ 2026-09-24  0:25 UTC (permalink / raw)
  To: seanjc, pbonzini; +Cc: kvm, linux-kernel, nikunj, yosry, Jim Mattson

Rename svm_nested_clear_efer_svme.c to svm_nested_efer_test.c so that the
test can host coverage for other EFER bits whose behavior is tied to nested
SVM, and so that the name matches the svm_nested_<topic>_test convention used
by svm_nested_pat_test, svm_nested_shutdown_test, etc.

No functional change intended.

Assisted-by: LLM
Signed-off-by: Jim Mattson <jmattson@google.com>
---
 tools/testing/selftests/kvm/Makefile.kvm                        | 2 +-
 .../{svm_nested_clear_efer_svme.c => svm_nested_efer_test.c}    | 0
 2 files changed, 1 insertion(+), 1 deletion(-)
 rename tools/testing/selftests/kvm/x86/{svm_nested_clear_efer_svme.c => svm_nested_efer_test.c} (100%)

diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 96bab7002d39..2554464d8b96 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -117,7 +117,7 @@ TEST_GEN_PROGS_x86 += x86/state_test
 TEST_GEN_PROGS_x86 += x86/vmx_preemption_timer_test
 TEST_GEN_PROGS_x86 += x86/svm_vmcall_test
 TEST_GEN_PROGS_x86 += x86/svm_int_ctl_test
-TEST_GEN_PROGS_x86 += x86/svm_nested_clear_efer_svme
+TEST_GEN_PROGS_x86 += x86/svm_nested_efer_test
 TEST_GEN_PROGS_x86 += x86/svm_nested_shutdown_test
 TEST_GEN_PROGS_x86 += x86/svm_nested_soft_inject_test
 TEST_GEN_PROGS_x86 += x86/svm_nested_vmcb12_gpa
diff --git a/tools/testing/selftests/kvm/x86/svm_nested_clear_efer_svme.c b/tools/testing/selftests/kvm/x86/svm_nested_efer_test.c
similarity index 100%
rename from tools/testing/selftests/kvm/x86/svm_nested_clear_efer_svme.c
rename to tools/testing/selftests/kvm/x86/svm_nested_efer_test.c
-- 
2.56.0.rc1.315.gc6ed9934b7-goog


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH v2 4/4] KVM: selftests: Add coverage for the EFER_LMSLE_MBZ defeature
  2026-09-24  0:25 [PATCH v2 0/4] KVM: x86: Honor EFER_LMSLE_MBZ Jim Mattson
                   ` (2 preceding siblings ...)
  2026-09-24  0:25 ` [PATCH v2 3/4] KVM: selftests: Rename svm_nested_clear_efer_svme to svm_nested_efer_test Jim Mattson
@ 2026-09-24  0:25 ` Jim Mattson
  3 siblings, 0 replies; 5+ messages in thread
From: Jim Mattson @ 2026-09-24  0:25 UTC (permalink / raw)
  To: seanjc, pbonzini; +Cc: kvm, linux-kernel, nikunj, yosry, Jim Mattson

Add coverage for KVM's virtualization of EFER_LMSLE_MBZ,
CPUID.80000008H:EBX[bit 20], which is set when the CPU does *not* support long
mode segment limits.  Verify that KVM's enumeration of the defeature matches
the expectation derived from hardware plus kvm_amd's "nested" module param, so
that the assertion doesn't compare KVM's enumeration against itself, and that
WRMSR(EFER), nested VMRUN, and KVM_SET_SREGS reject EFER.LMSLE=1 if and only
if guest CPUID enumerates the defeature.  Also verify that host-initiated
KVM_SET_MSRS is exempt from the check, as host-initiated writes skip guest
CPUID checks so that userspace can set MSRs before it sets guest CPUID.

Extend svm_nested_efer_test rather than add yet another test binary for a
single CPUID bit; the test already has the L1/L2 harness needed to exercise
EFER.LMSLE.  Note, only a CPU that supports LMSLE, i.e. Rome and earlier,
exercises the emulation path; elsewhere KVM enumerates the defeature straight
from hardware.

Opportunistically drop the unnecessary #include of vmx.h.

Assisted-by: LLM
Signed-off-by: Jim Mattson <jmattson@google.com>
---
 .../selftests/kvm/include/x86/processor.h     |   1 +
 .../selftests/kvm/x86/svm_nested_efer_test.c  | 249 +++++++++++++++++-
 2 files changed, 237 insertions(+), 13 deletions(-)

diff --git a/tools/testing/selftests/kvm/include/x86/processor.h b/tools/testing/selftests/kvm/include/x86/processor.h
index 6e6f70035508..ba5d8b37edc1 100644
--- a/tools/testing/selftests/kvm/include/x86/processor.h
+++ b/tools/testing/selftests/kvm/include/x86/processor.h
@@ -216,6 +216,7 @@ struct kvm_x86_cpu_feature {
 #define	X86_FEATURE_INVTSC		KVM_X86_CPU_FEATURE(0x80000007, 0, EDX, 8)
 #define	X86_FEATURE_RDPRU		KVM_X86_CPU_FEATURE(0x80000008, 0, EBX, 4)
 #define	X86_FEATURE_AMD_IBPB		KVM_X86_CPU_FEATURE(0x80000008, 0, EBX, 12)
+#define	X86_FEATURE_EFER_LMSLE_MBZ	KVM_X86_CPU_FEATURE(0x80000008, 0, EBX, 20)
 #define	X86_FEATURE_NPT			KVM_X86_CPU_FEATURE(0x8000000A, 0, EDX, 0)
 #define	X86_FEATURE_LBRV		KVM_X86_CPU_FEATURE(0x8000000A, 0, EDX, 1)
 #define	X86_FEATURE_NRIPS		KVM_X86_CPU_FEATURE(0x8000000A, 0, EDX, 3)
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 6bc301207cbc..765476c79c96 100644
--- a/tools/testing/selftests/kvm/x86/svm_nested_efer_test.c
+++ b/tools/testing/selftests/kvm/x86/svm_nested_efer_test.c
@@ -1,16 +1,20 @@
 // SPDX-License-Identifier: GPL-2.0-only
 /*
+ * Tests for KVM's handling of EFER bits whose behavior is tied to nested SVM.
+ *
  * Copyright (C) 2026, Google LLC.
  */
+#include "test_util.h"
 #include "kvm_util.h"
-#include "vmx.h"
+#include "processor.h"
 #include "svm_util.h"
 #include "kselftest.h"
 
+static bool l2_ran;
 
-static void l2_guest_code(void)
+static void l2_clear_efer_svme(void)
 {
-	unsigned long efer = rdmsr(MSR_EFER);
+	u64 efer = rdmsr(MSR_EFER);
 
 	/* generic_svm_setup() initializes EFER_SVME set for L2 */
 	GUEST_ASSERT(efer & EFER_SVME);
@@ -20,31 +24,250 @@ static void l2_guest_code(void)
 	GUEST_ASSERT(0);
 }
 
-static void l1_guest_code(struct svm_test_data *svm)
+static void l1_clear_efer_svme(struct svm_test_data *svm)
 {
-	generic_svm_setup(svm, l2_guest_code);
+	generic_svm_setup(svm, l2_clear_efer_svme);
 	run_guest(svm->vmcb, svm->vmcb_gpa);
 
 	/* Unreachable, L1 should be shutdown */
 	GUEST_ASSERT(0);
 }
 
-int main(int argc, char *argv[])
+static void l2_lmsle(void)
+{
+	GUEST_ASSERT(rdmsr(MSR_EFER) & EFER_LMSLE);
+	l2_ran = true;
+	vmmcall();
+}
+
+static void l1_lmsle(struct svm_test_data *svm)
+{
+	bool lmsle_mbz = this_cpu_has(X86_FEATURE_EFER_LMSLE_MBZ);
+	struct vmcb *vmcb = svm->vmcb;
+	u64 efer = rdmsr(MSR_EFER);
+
+	/*
+	 * Selftests' vCPUs are created with EFER.LMSLE clear; the sub-tests
+	 * below need to start from a clean slate.
+	 */
+	GUEST_ASSERT(!(efer & EFER_LMSLE));
+	GUEST_ASSERT(!l2_ran);
+
+	/*
+	 * Per AMD APM vol. 2, if CPUID.80000008H:EBX[bit 20] is set, "64-bit
+	 * mode segment limit checking is not supported and attempting to set
+	 * EFER.LMSLE = 1 causes a #GP exception".
+	 */
+	if (lmsle_mbz) {
+		GUEST_ASSERT_EQ(wrmsr_safe(MSR_EFER, efer | EFER_LMSLE), GP_VECTOR);
+		GUEST_ASSERT(!(rdmsr(MSR_EFER) & EFER_LMSLE));
+	} else {
+		GUEST_ASSERT_EQ(wrmsr_safe(MSR_EFER, efer | EFER_LMSLE), 0);
+		GUEST_ASSERT(rdmsr(MSR_EFER) & EFER_LMSLE);
+
+		/*
+		 * Restore EFER so that generic_svm_setup() doesn't propagate
+		 * EFER.LMSLE into vmcb12 on its own, i.e. so that the VMRUN
+		 * sub-test actually tests what it thinks it's testing.
+		 */
+		wrmsr(MSR_EFER, efer);
+	}
+
+	/*
+	 * VMRUN's consistency checks reject "any MBZ bit of EFER", i.e. a
+	 * vmcb12 with EFER.LMSLE set must generate VMEXIT_INVALID when the
+	 * defeature is enumerated.
+	 */
+	generic_svm_setup(svm, l2_lmsle);
+	vmcb->save.efer |= EFER_LMSLE;
+	run_guest(vmcb, svm->vmcb_gpa);
+
+	if (lmsle_mbz) {
+		GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_ERR);
+		GUEST_ASSERT(!l2_ran);
+	} else {
+		GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_VMMCALL);
+		GUEST_ASSERT(l2_ran);
+		GUEST_ASSERT(vmcb->save.efer & EFER_LMSLE);
+	}
+
+	GUEST_DONE();
+}
+
+static struct kvm_vcpu *create_l1_vcpu(struct kvm_vm **vm, void *l1_guest_code)
 {
 	struct kvm_vcpu *vcpu;
-	struct kvm_vm *vm;
-	gva_t nested_gva = 0;
+	gva_t svm_gva;
 
-	TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_SVM));
+	*vm = vm_create_with_one_vcpu(&vcpu, l1_guest_code);
 
-	vm = vm_create_with_one_vcpu(&vcpu, l1_guest_code);
+	vcpu_alloc_svm(*vm, &svm_gva);
+	vcpu_args_set(vcpu, 1, svm_gva);
+
+	return vcpu;
+}
+
+static void test_enumeration(void)
+{
+	bool lmsle_mbz;
 
-	vcpu_alloc_svm(vm, &nested_gva);
-	vcpu_args_set(vcpu, 1, nested_gva);
+	/*
+	 * EFER_LMSLE_MBZ, CPUID.80000008H:EBX[bit 20], is a "defeature" bit,
+	 * i.e. is set when the CPU does *not* support long mode segment
+	 * limits.  KVM enumerates the defeature if and only if KVM refuses to
+	 * set EFER.LMSLE, i.e. if the CPU doesn't support LMSLE, or if KVM
+	 * doesn't support nested SVM.  Derive the expectation from raw CPUID
+	 * and kvm_amd's "nested" module param rather than from
+	 * kvm_cpu_has(X86_FEATURE_SVM), so that the assertion doesn't simply
+	 * compare KVM's enumeration to itself.
+	 *
+	 * Note, kvm_amd's "nested" is an "int" module param, i.e. reads back
+	 * as '1'/'0' and not as 'Y'/'N'.  Query the param if and only if the
+	 * CPU supports SVM, i.e. if and only if kvm_amd is the module in
+	 * play.  Checking the vendor string wouldn't suffice, e.g. Zhaoxin
+	 * and Centaur CPUs are "GenuineIntel"-adjacent at best, but run VMX
+	 * and thus load kvm_intel.
+	 */
+	lmsle_mbz = this_cpu_has(X86_FEATURE_EFER_LMSLE_MBZ) ||
+		    !this_cpu_has(X86_FEATURE_SVM) ||
+		    !get_kvm_amd_param_integer("nested");
+
+	TEST_ASSERT_EQ(kvm_cpu_has(X86_FEATURE_EFER_LMSLE_MBZ), lmsle_mbz);
+
+	ksft_test_result_pass("KVM enumerates EFER_LMSLE_MBZ=%d\n", lmsle_mbz);
+}
+
+static void test_clear_efer_svme(void)
+{
+	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);
-	return 0;
+	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);
+
+	switch (get_ucall(vcpu, &uc)) {
+	case UCALL_ABORT:
+		REPORT_GUEST_ASSERT(uc);
+	case UCALL_DONE:
+		break;
+	default:
+		TEST_FAIL("Unexpected ucall: %lu", uc.cmd);
+	}
+
+	kvm_vm_free(vm);
+	ksft_test_result_pass("Guest EFER_LMSLE_MBZ=%d\n", lmsle_mbz);
+}
+
+static void test_host_initiated_lmsle(void)
+{
+	struct kvm_vcpu *vcpu;
+	struct kvm_vm *vm;
+	u64 efer;
+
+	vm = vm_create_with_one_vcpu(&vcpu, NULL);
+	vcpu_set_cpuid_feature(vcpu, X86_FEATURE_EFER_LMSLE_MBZ);
+
+	/*
+	 * EFER_LMSLE_MBZ is a guest CPUID consistency check, not a host
+	 * capability, i.e. must not be enforced against host-initiated writes,
+	 * so that userspace can set MSRs before it sets guest CPUID.
+	 */
+	efer = vcpu_get_msr(vcpu, MSR_EFER);
+	TEST_ASSERT(!(efer & EFER_LMSLE), "EFER.LMSLE unexpectedly set");
+
+	vcpu_set_msr(vcpu, MSR_EFER, efer | EFER_LMSLE);
+	TEST_ASSERT_EQ(vcpu_get_msr(vcpu, MSR_EFER), efer | EFER_LMSLE);
+
+	kvm_vm_free(vm);
+	ksft_test_result_pass("Host-initiated EFER.LMSLE=1 is allowed\n");
+}
+
+static void test_sregs_lmsle(void)
+{
+	struct kvm_vcpu *vcpu;
+	struct kvm_sregs sregs;
+	struct kvm_vm *vm;
+	int rc;
+
+	vm = vm_create_with_one_vcpu(&vcpu, NULL);
+	vcpu_set_cpuid_feature(vcpu, X86_FEATURE_EFER_LMSLE_MBZ);
+
+	/*
+	 * Unlike KVM_SET_MSRS, KVM_SET_SREGS runs the full set of guest CPUID
+	 * checks, i.e. rejects EFER.LMSLE even though it's host-initiated.
+	 */
+	vcpu_sregs_get(vcpu, &sregs);
+	TEST_ASSERT(!(sregs.efer & EFER_LMSLE), "EFER.LMSLE unexpectedly set");
+
+	sregs.efer |= EFER_LMSLE;
+	rc = _vcpu_sregs_set(vcpu, &sregs);
+	TEST_ASSERT(rc, "KVM allowed EFER.LMSLE with EFER_LMSLE_MBZ set");
+
+	kvm_vm_free(vm);
+	ksft_test_result_pass("KVM_SET_SREGS rejects EFER.LMSLE=1\n");
+}
+
+int main(int argc, char *argv[])
+{
+	bool has_nested_svm, has_lmsle;
+
+	ksft_print_header();
+	ksft_set_plan(6);
+
+	test_enumeration();
+
+	/*
+	 * The sub-tests below need to actually run a nested guest, and the
+	 * EFER.LMSLE sub-tests additionally need KVM to allow EFER.LMSLE.
+	 * It's KVM's view of the world, not raw CPUID, that dictates whether
+	 * EFER.LMSLE is allowed, i.e. whether the defeature is emulated.
+	 */
+	has_nested_svm = kvm_cpu_has(X86_FEATURE_SVM);
+	has_lmsle = has_nested_svm &&
+		    !kvm_cpu_has(X86_FEATURE_EFER_LMSLE_MBZ);
+
+	if (!has_nested_svm)
+		ksft_print_msg("Nested SVM unsupported\n");
+	else if (!has_lmsle)
+		ksft_print_msg("KVM doesn't support EFER.LMSLE\n");
+
+	if (has_nested_svm) {
+		test_clear_efer_svme();
+		test_lmsle(true);
+	} else {
+		ksft_test_result_skip("L2 clearing EFER.SVME shuts down L1\n");
+		ksft_test_result_skip("Guest EFER_LMSLE_MBZ=1\n");
+	}
+
+	if (has_lmsle) {
+		test_lmsle(false);
+		test_host_initiated_lmsle();
+		test_sregs_lmsle();
+	} else {
+		ksft_test_result_skip("Guest EFER_LMSLE_MBZ=0\n");
+		ksft_test_result_skip("Host-initiated EFER.LMSLE=1 is allowed\n");
+		ksft_test_result_skip("KVM_SET_SREGS rejects EFER.LMSLE=1\n");
+	}
+
+	ksft_finished();
 }
-- 
2.56.0.rc1.315.gc6ed9934b7-goog


^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-09-24  0:25 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-24  0:25 [PATCH v2 0/4] KVM: x86: Honor EFER_LMSLE_MBZ Jim Mattson
2026-09-24  0:25 ` [PATCH v2 1/4] KVM: x86: Advertise EFER_LMSLE_MBZ when KVM disallows EFER.LMSLE Jim Mattson
2026-09-24  0:25 ` [PATCH v2 2/4] KVM: x86: Honor the guest's EFER_LMSLE_MBZ Jim Mattson
2026-09-24  0:25 ` [PATCH v2 3/4] KVM: selftests: Rename svm_nested_clear_efer_svme to svm_nested_efer_test Jim Mattson
2026-09-24  0:25 ` [PATCH v2 4/4] KVM: selftests: Add coverage for the EFER_LMSLE_MBZ defeature Jim Mattson

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®