1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Copyright (C) 2026, Google LLC. 4 */ 5 #include "kvm_util.h" 6 #include "vmx.h" 7 #include "svm_util.h" 8 #include "kselftest.h" 9 10 /* 11 * Allocate two VMCB pages for testing. Both pages have different GVAs (shared 12 * by both L1 and L2) and L1 GPAs. A single L2 GPA is used such that: 13 * - L2 GPA == L1 GPA for VMCB0. 14 * - L2 GPA is mapped to L1 GPA for VMCB1 using NPT in L1. 15 * 16 * This allows testing whether the GPA used by VMSAVE/VMLOAD in L2 is 17 * interpreted as a direct L1 GPA or translated using NPT as an L2 GPA, depends 18 * on which VMCB is accessed. 19 */ 20 #define TEST_MEM_SLOT_INDEX 1 21 #define TEST_MEM_PAGES 2 22 #define TEST_MEM_BASE 0xc0000000 23 24 #define TEST_GUEST_ADDR(idx) (TEST_MEM_BASE + (idx) * PAGE_SIZE) 25 26 #define TEST_VMCB_L1_GPA(idx) TEST_GUEST_ADDR(idx) 27 #define TEST_VMCB_GVA(idx) TEST_GUEST_ADDR(idx) 28 29 #define TEST_VMCB_L2_GPA TEST_VMCB_L1_GPA(0) 30 31 static void l2_guest_code_vmsave(void) 32 { 33 asm volatile("vmsave %0" : : "a"(TEST_VMCB_L2_GPA) : "memory"); 34 } 35 36 static void l2_guest_code_vmload(void) 37 { 38 asm volatile("vmload %0" : : "a"(TEST_VMCB_L2_GPA) : "memory"); 39 } 40 41 static void l2_guest_code_vmcb(int vmcb_idx) 42 { 43 wrmsr(MSR_KERNEL_GS_BASE, 0xaaaa); 44 l2_guest_code_vmsave(); 45 46 /* Verify the VMCB used by VMSAVE and update KERNEL_GS_BASE to 0xbbbb */ 47 GUEST_SYNC(vmcb_idx); 48 49 l2_guest_code_vmload(); 50 GUEST_ASSERT_EQ(rdmsr(MSR_KERNEL_GS_BASE), 0xbbbb); 51 52 /* Reset MSR_KERNEL_GS_BASE */ 53 wrmsr(MSR_KERNEL_GS_BASE, 0); 54 l2_guest_code_vmsave(); 55 56 vmmcall(); 57 } 58 59 static void l2_guest_code_vmcb0(void) 60 { 61 l2_guest_code_vmcb(0); 62 } 63 64 static void l2_guest_code_vmcb1(void) 65 { 66 l2_guest_code_vmcb(1); 67 } 68 69 static void l1_guest_code(struct svm_test_data *svm) 70 { 71 /* Each test case initializes the guest RIP below */ 72 generic_svm_setup(svm, NULL); 73 74 /* Set VMSAVE/VMLOAD intercepts and make sure they work with.. */ 75 svm->vmcb->control.intercept |= (BIT_ULL(INTERCEPT_VMSAVE) | 76 BIT_ULL(INTERCEPT_VMLOAD)); 77 78 /* ..SVM_MISC2_ENABLE_V_VMLOAD_VMSAVE cleared.. */ 79 svm->vmcb->control.misc_ctl2 &= ~SVM_MISC2_ENABLE_V_VMLOAD_VMSAVE; 80 81 svm->vmcb->save.rip = (u64)l2_guest_code_vmsave; 82 run_guest(svm->vmcb, svm->vmcb_gpa); 83 GUEST_ASSERT_EQ(svm->vmcb->control.exit_code, SVM_EXIT_VMSAVE); 84 85 svm->vmcb->save.rip = (u64)l2_guest_code_vmload; 86 run_guest(svm->vmcb, svm->vmcb_gpa); 87 GUEST_ASSERT_EQ(svm->vmcb->control.exit_code, SVM_EXIT_VMLOAD); 88 89 /* ..and SVM_MISC2_ENABLE_V_VMLOAD_VMSAVE set */ 90 svm->vmcb->control.misc_ctl2 |= SVM_MISC2_ENABLE_V_VMLOAD_VMSAVE; 91 92 svm->vmcb->save.rip = (u64)l2_guest_code_vmsave; 93 run_guest(svm->vmcb, svm->vmcb_gpa); 94 GUEST_ASSERT_EQ(svm->vmcb->control.exit_code, SVM_EXIT_VMSAVE); 95 96 svm->vmcb->save.rip = (u64)l2_guest_code_vmload; 97 run_guest(svm->vmcb, svm->vmcb_gpa); 98 GUEST_ASSERT_EQ(svm->vmcb->control.exit_code, SVM_EXIT_VMLOAD); 99 100 /* Now clear the intercepts to test VMSAVE/VMLOAD behavior */ 101 svm->vmcb->control.intercept &= ~(BIT_ULL(INTERCEPT_VMSAVE) | 102 BIT_ULL(INTERCEPT_VMLOAD)); 103 104 /* 105 * Without SVM_MISC2_ENABLE_V_VMLOAD_VMSAVE, the GPA will be 106 * interpreted as an L1 GPA, so VMCB0 should be used. 107 */ 108 svm->vmcb->save.rip = (u64)l2_guest_code_vmcb0; 109 svm->vmcb->control.misc_ctl2 &= ~SVM_MISC2_ENABLE_V_VMLOAD_VMSAVE; 110 run_guest(svm->vmcb, svm->vmcb_gpa); 111 GUEST_ASSERT_EQ(svm->vmcb->control.exit_code, SVM_EXIT_VMMCALL); 112 113 /* 114 * With SVM_MISC2_ENABLE_V_VMLOAD_VMSAVE, the GPA will be interpeted as 115 * an L2 GPA, and translated through the NPT to VMCB1. 116 */ 117 svm->vmcb->save.rip = (u64)l2_guest_code_vmcb1; 118 svm->vmcb->control.misc_ctl2 |= SVM_MISC2_ENABLE_V_VMLOAD_VMSAVE; 119 run_guest(svm->vmcb, svm->vmcb_gpa); 120 GUEST_ASSERT_EQ(svm->vmcb->control.exit_code, SVM_EXIT_VMMCALL); 121 122 GUEST_DONE(); 123 } 124 125 int main(int argc, char *argv[]) 126 { 127 gva_t nested_gva = 0; 128 struct vmcb *test_vmcb[2]; 129 struct kvm_vcpu *vcpu; 130 struct kvm_vm *vm; 131 int i; 132 133 TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_SVM)); 134 TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_NPT)); 135 TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_V_VMSAVE_VMLOAD)); 136 137 vm = vm_create_with_one_vcpu(&vcpu, l1_guest_code); 138 vm_enable_tdp(vm); 139 140 vcpu_alloc_svm(vm, &nested_gva); 141 vcpu_args_set(vcpu, 1, nested_gva); 142 143 vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, 144 TEST_MEM_BASE, TEST_MEM_SLOT_INDEX, 145 TEST_MEM_PAGES, 0); 146 147 for (i = 0; i <= 1; i++) { 148 virt_map(vm, TEST_VMCB_GVA(i), TEST_VMCB_L1_GPA(i), 1); 149 test_vmcb[i] = (struct vmcb *)addr_gva2hva(vm, TEST_VMCB_GVA(i)); 150 } 151 152 tdp_identity_map_default_memslots(vm); 153 154 /* 155 * L2 GPA == L1_GPA(0), but map it to L1_GPA(1), to allow testing 156 * whether the L2 GPA is interpreted as an L1 GPA or translated through 157 * the NPT. 158 */ 159 TEST_ASSERT_EQ(TEST_VMCB_L2_GPA, TEST_VMCB_L1_GPA(0)); 160 tdp_map(vm, TEST_VMCB_L2_GPA, TEST_VMCB_L1_GPA(1), PAGE_SIZE); 161 162 for (;;) { 163 struct ucall uc; 164 165 vcpu_run(vcpu); 166 TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO); 167 168 switch (get_ucall(vcpu, &uc)) { 169 case UCALL_ABORT: 170 REPORT_GUEST_ASSERT(uc); 171 case UCALL_SYNC: 172 i = uc.args[1]; 173 TEST_ASSERT(i == 0 || i == 1, "Unexpected VMCB idx: %d", i); 174 175 /* 176 * Check that only the expected VMCB has KERNEL_GS_BASE 177 * set to 0xaaaa, and update it to 0xbbbb. 178 */ 179 TEST_ASSERT_EQ(test_vmcb[i]->save.kernel_gs_base, 0xaaaa); 180 TEST_ASSERT_EQ(test_vmcb[1-i]->save.kernel_gs_base, 0); 181 test_vmcb[i]->save.kernel_gs_base = 0xbbbb; 182 break; 183 case UCALL_DONE: 184 goto done; 185 default: 186 TEST_FAIL("Unknown ucall %lu", uc.cmd); 187 } 188 } 189 190 done: 191 kvm_vm_free(vm); 192 return 0; 193 } 194