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
next prev 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®