mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Jim Mattson <jmattson@google.com>
To: Sean Christopherson <seanjc@google.com>,
	Paolo Bonzini <pbonzini@redhat.com>
Cc: Shuah Khan <shuah@kernel.org>,
	kvm@vger.kernel.org, linux-kselftest@vger.kernel.org,
	 linux-kernel@vger.kernel.org, Yosry Ahmed <yosry@kernel.org>,
	 Jim Mattson <jmattson@google.com>
Subject: [PATCH 3/3] KVM: selftests: Add MSR_VM_CR tests to svm_msrs_test
Date: Thu, 29 Oct 2026 13:28:16 -0700	[thread overview]
Message-ID: <20261029202817.901145-3-jmattson@google.com> (raw)
In-Reply-To: <20261029202817.901145-1-jmattson@google.com>

Add sub-tests to svm_msrs_test to verify KVM's emulation of guest and
host-initiated writes to MSR_VM_CR:

  - Writing reserved bits 63:5 faults with #GP(0) (or fails KVM_SET_MSRS)
    without modifying MSR_VM_CR.
  - Guest WRMSR setting SVMDIS while EFER.SVME=1 faults with #GP(0)
    without modifying MSR_VM_CR.
  - Guest WRMSR setting SVMDIS while EFER.SVME=0 succeeds, after which
    guest writes to LOCK and SVMDIS are silently ignored while bits 2:0
    remain writable.
  - Host-initiated writes via KVM_SET_MSRS can set any valid MSR_VM_CR
    bit regardless of EFER.SVME and can clear SVMDIS and LOCK.

Signed-off-by: Jim Mattson <jmattson@google.com>
---
 .../testing/selftests/kvm/x86/svm_msrs_test.c | 167 ++++++++++++++++--
 1 file changed, 153 insertions(+), 14 deletions(-)

diff --git a/tools/testing/selftests/kvm/x86/svm_msrs_test.c b/tools/testing/selftests/kvm/x86/svm_msrs_test.c
index fa0d76a9c390..e1f386ee0e7e 100644
--- a/tools/testing/selftests/kvm/x86/svm_msrs_test.c
+++ b/tools/testing/selftests/kvm/x86/svm_msrs_test.c
@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0-only
 /*
- * Tests for KVM's handling of EFER bits whose behavior is tied to nested SVM.
+ * Tests for KVM's handling of SVM MSRs (EFER and VM_CR).
  *
  * Copyright (C) 2026, Google LLC.
  */
@@ -33,6 +33,74 @@ static void l1_clear_efer_svme(struct svm_test_data *svm)
 	GUEST_ASSERT(0);
 }
 
+static void guest_wrmsr_vm_cr(u64 val, u64 expected_val, bool expect_gp)
+{
+	u8 expected_vec = expect_gp ? GP_VECTOR : 0;
+	u64 actual_val;
+	u8 vec;
+
+	vec = wrmsr_safe(MSR_VM_CR, val);
+	__GUEST_ASSERT(vec == expected_vec,
+		       "Wanted %s on WRMSR(MSR_VM_CR, 0x%lx), got %s",
+		       ex_str(expected_vec), val, ex_str(vec));
+
+	actual_val = rdmsr(MSR_VM_CR);
+	__GUEST_ASSERT(actual_val == expected_val,
+		       "Wanted MSR_VM_CR == 0x%lx after WRMSR(0x%lx), got 0x%lx",
+		       expected_val, val, actual_val);
+}
+
+static void guest_vm_cr(void)
+{
+	const u64 svme_valid_mask = SVM_VM_CR_VALID_MASK & ~SVM_VM_CR_SVM_DIS_MASK;
+	const u64 unlocked_mask = SVM_VM_CR_VALID_MASK &
+				  ~(SVM_VM_CR_SVM_LOCK_MASK | SVM_VM_CR_SVM_DIS_MASK);
+	u64 efer, val, expected = 0;
+	bool valid;
+	int bit;
+
+	GUEST_ASSERT_EQ(rdmsr(MSR_VM_CR), 0);
+
+	efer = rdmsr(MSR_EFER);
+	wrmsr(MSR_EFER, efer | EFER_SVME);
+
+	/*
+	 * With EFER.SVME=1, setting non-SVMDIS bits in MSR_VM_CR is allowed,
+	 * whereas setting SVMDIS or reserved bits must fault with #GP(0)
+	 * without modifying MSR_VM_CR.
+	 */
+	guest_wrmsr_vm_cr(SVM_VM_CR_VALID_MASK, 0, true);
+
+	for (bit = 0; bit < BITS_PER_LONG; bit++) {
+		val = BIT_ULL(bit);
+		valid = val & svme_valid_mask;
+		if (valid)
+			expected = val;
+
+		guest_wrmsr_vm_cr(val, expected, !valid);
+	}
+
+	/*
+	 * Verify that prior faulting attempts to set SVMDIS while EFER.SVME=1
+	 * did not latch SVMDIS and cause subsequent valid writes to fault.
+	 */
+	guest_wrmsr_vm_cr(0, 0, false);
+
+	/*
+	 * With EFER.SVME=0, setting SVMDIS succeeds.  Once set, guest writes
+	 * to LOCK and SVMDIS are silently ignored while bits 2:0 remain
+	 * writable.
+	 */
+	wrmsr(MSR_EFER, efer & ~EFER_SVME);
+
+	guest_wrmsr_vm_cr(SVM_VM_CR_SVM_DIS_MASK, SVM_VM_CR_SVM_DIS_MASK, false);
+	guest_wrmsr_vm_cr(svme_valid_mask,
+			  SVM_VM_CR_SVM_DIS_MASK | unlocked_mask, false);
+	guest_wrmsr_vm_cr(0, SVM_VM_CR_SVM_DIS_MASK, false);
+
+	GUEST_DONE();
+}
+
 static void l2_lmsle(void)
 {
 	GUEST_ASSERT(rdmsr(MSR_EFER) & EFER_LMSLE);
@@ -144,17 +212,10 @@ static void test_clear_efer_svme(void)
 	ksft_test_result_pass("L2 clearing EFER.SVME shuts down L1\n");
 }
 
-static void test_lmsle(bool lmsle_mbz)
+static void run_vcpu_to_done(struct kvm_vcpu *vcpu)
 {
-	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 +227,79 @@ static void test_lmsle(bool lmsle_mbz)
 	default:
 		TEST_FAIL("Unexpected ucall: %lu", uc.cmd);
 	}
+}
+
+static void test_vm_cr(void)
+{
+	struct kvm_vcpu *vcpu;
+	struct kvm_vm *vm;
+
+	vm = vm_create_with_one_vcpu(&vcpu, guest_vm_cr);
+
+	run_vcpu_to_done(vcpu);
+
+	kvm_vm_free(vm);
+	ksft_test_result_pass("Guest writes to MSR_VM_CR\n");
+}
+
+static void test_host_initiated_vm_cr(void)
+{
+	u64 efer, val, actual, expected = 0;
+	struct kvm_vcpu *vcpu;
+	struct kvm_vm *vm;
+	int bit, rc;
+	bool valid;
+
+	vm = vm_create_with_one_vcpu(&vcpu, NULL);
+
+	/*
+	 * Host-initiated writes must be able to set SVMDIS even when
+	 * EFER.SVME=1 (e.g. when restoring MSR_VM_CR before MSR_EFER), and
+	 * must be able to modify and clear SVMDIS and LOCK after SVMDIS has
+	 * been set.
+	 */
+	TEST_ASSERT_EQ(vcpu_get_msr(vcpu, MSR_VM_CR), 0);
+
+	efer = vcpu_get_msr(vcpu, MSR_EFER);
+	vcpu_set_msr(vcpu, MSR_EFER, efer | EFER_SVME);
+
+	for (bit = 0; bit < BITS_PER_LONG; bit++) {
+		val = BIT_ULL(bit);
+		valid = val & SVM_VM_CR_VALID_MASK;
+
+		rc = _vcpu_set_msr(vcpu, MSR_VM_CR, val);
+		TEST_ASSERT(valid ? rc == 1 : !rc,
+			    "Expected KVM_SET_MSRS(MSR_VM_CR, 0x%lx) to %s, got rc=%d",
+			    val, valid ? "succeed" : "fail", rc);
+		if (valid)
+			expected = val;
+		actual = vcpu_get_msr(vcpu, MSR_VM_CR);
+		TEST_ASSERT(actual == expected,
+			    "Wanted MSR_VM_CR == 0x%lx after KVM_SET_MSRS(0x%lx), got 0x%lx",
+			    expected, val, actual);
+	}
+
+	vcpu_set_msr(vcpu, MSR_VM_CR, SVM_VM_CR_VALID_MASK);
+	TEST_ASSERT_EQ(vcpu_get_msr(vcpu, MSR_VM_CR), SVM_VM_CR_VALID_MASK);
+
+	vcpu_set_msr(vcpu, MSR_VM_CR, 0);
+	TEST_ASSERT_EQ(vcpu_get_msr(vcpu, MSR_VM_CR), 0);
+
+	kvm_vm_free(vm);
+	ksft_test_result_pass("Host-initiated writes to MSR_VM_CR\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);
+
+	run_vcpu_to_done(vcpu);
 
 	kvm_vm_free(vm);
 	ksft_test_result_pass("Guest EFER_LMSLE_MBZ=%d\n", lmsle_mbz);
@@ -225,15 +359,16 @@ int main(int argc, char *argv[])
 	bool has_nested_svm, has_lmsle;
 
 	ksft_print_header();
-	ksft_set_plan(6);
+	ksft_set_plan(8);
 
 	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.
+	 * The sub-tests below need to actually run a nested guest or set
+	 * EFER.SVME, 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 &&
@@ -246,9 +381,13 @@ int main(int argc, char *argv[])
 
 	if (has_nested_svm) {
 		test_clear_efer_svme();
+		test_vm_cr();
+		test_host_initiated_vm_cr();
 		test_lmsle(true);
 	} else {
 		ksft_test_result_skip("L2 clearing EFER.SVME shuts down L1\n");
+		ksft_test_result_skip("Guest writes to MSR_VM_CR\n");
+		ksft_test_result_skip("Host-initiated writes to MSR_VM_CR\n");
 		ksft_test_result_skip("Guest EFER_LMSLE_MBZ=1\n");
 	}
 
-- 
2.56.0.317.g454f8302e1-goog


  parent reply	other threads:[~2026-10-29 20:28 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-29 20:28 [PATCH 1/3] KVM: SVM: Check EFER.SVME before updating MSR_VM_CR on guest writes Jim Mattson
2026-10-29 20:28 ` [PATCH 2/3] KVM: selftests: Rename svm_nested_efer_test to svm_msrs_test Jim Mattson
2026-10-29 20:28 ` Jim Mattson [this message]
2026-10-29 21:19 ` [PATCH 1/3] KVM: SVM: Check EFER.SVME before updating MSR_VM_CR on guest writes Sean Christopherson
2026-10-29 21:46   ` Jim Mattson
2026-10-29 21:52     ` Sean Christopherson
2026-10-29 22:04       ` Jim Mattson
2026-10-30  0:00         ` Jim Mattson

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261029202817.901145-3-jmattson@google.com \
    --to=jmattson@google.com \
    --cc=kvm@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=pbonzini@redhat.com \
    --cc=seanjc@google.com \
    --cc=shuah@kernel.org \
    --cc=yosry@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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®