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 --- .../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