1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Copyright (C) 2025, Google, Inc. 4 */ 5 6 #include "test_util.h" 7 #include "kvm_util.h" 8 #include "processor.h" 9 #include "svm_util.h" 10 #include "vmx.h" 11 12 enum test_type { 13 TEST_FINAL_PAGE_UNMAPPED, /* Final data page not present */ 14 TEST_PT_PAGE_UNMAPPED, /* Page table page not present */ 15 TEST_FINAL_PAGE_WRITE_PROTECTED, /* Final data page read-only */ 16 TEST_PT_PAGE_WRITE_PROTECTED, /* Page table page read-only */ 17 }; 18 19 static gva_t l2_test_page; 20 static void (*l2_entry)(void); 21 22 #define TEST_IO_PORT 0x80 23 #define TEST1_VADDR 0x8000000ULL 24 #define TEST2_VADDR 0x10000000ULL 25 #define TEST3_VADDR 0x18000000ULL 26 #define TEST4_VADDR 0x20000000ULL 27 28 /* 29 * L2 executes OUTS reading from l2_test_page, triggering a nested page 30 * fault on the read access. 31 */ 32 static void l2_guest_code_outs(void) 33 { 34 asm volatile("outsb" ::"S"(l2_test_page), "d"(TEST_IO_PORT) : "memory"); 35 GUEST_FAIL("L2 should not reach here"); 36 } 37 38 /* 39 * L2 executes INS writing to l2_test_page, triggering a nested page 40 * fault on the write access. 41 */ 42 static void l2_guest_code_ins(void) 43 { 44 asm volatile("insb" ::"D"(l2_test_page), "d"(TEST_IO_PORT) : "memory"); 45 GUEST_FAIL("L2 should not reach here"); 46 } 47 48 #define GUEST_ASSERT_EXIT_QUAL(ac_eq, ex_eq) \ 49 __GUEST_ASSERT((ac_eq) == (ex_eq), \ 50 "Wanted EXIT_QUAL '0x%lx', got '0x%lx'", ex_eq, ac_eq) 51 52 static void l1_vmx_code(struct vmx_pages *vmx, u64 expected_fault_gpa, 53 u64 test_type) 54 { 55 u64 exit_qual; 56 57 GUEST_ASSERT(vmx->vmcs_gpa); 58 GUEST_ASSERT(prepare_for_vmx_operation(vmx)); 59 GUEST_ASSERT(load_vmcs(vmx)); 60 61 prepare_vmcs(vmx, l2_entry); 62 63 GUEST_ASSERT(!vmlaunch()); 64 65 /* Verify we got an EPT violation exit */ 66 __GUEST_ASSERT(vmreadz(VM_EXIT_REASON) == EXIT_REASON_EPT_VIOLATION, 67 "Expected EPT violation (0x%x), got 0x%lx", 68 EXIT_REASON_EPT_VIOLATION, 69 vmreadz(VM_EXIT_REASON)); 70 71 __GUEST_ASSERT(vmreadz(GUEST_PHYSICAL_ADDRESS) == expected_fault_gpa, 72 "Expected guest_physical_address = 0x%lx, got 0x%lx", 73 expected_fault_gpa, 74 vmreadz(GUEST_PHYSICAL_ADDRESS)); 75 76 exit_qual = vmreadz(EXIT_QUALIFICATION); 77 78 /* 79 * Note, EPT page table accesses are always read+write, e.g. so that 80 * the CPU can do A/D updates at-will. 81 */ 82 switch (test_type) { 83 case TEST_FINAL_PAGE_UNMAPPED: 84 GUEST_ASSERT_EXIT_QUAL(exit_qual, EPT_VIOLATION_ACC_READ | 85 EPT_VIOLATION_GVA_IS_VALID | 86 EPT_VIOLATION_GVA_TRANSLATED); 87 break; 88 case TEST_PT_PAGE_UNMAPPED: 89 GUEST_ASSERT_EXIT_QUAL(exit_qual, EPT_VIOLATION_ACC_READ | 90 EPT_VIOLATION_ACC_WRITE | 91 EPT_VIOLATION_GVA_IS_VALID); 92 break; 93 case TEST_FINAL_PAGE_WRITE_PROTECTED: 94 GUEST_ASSERT_EXIT_QUAL(exit_qual, EPT_VIOLATION_ACC_WRITE | 95 EPT_VIOLATION_PROT_READ | 96 EPT_VIOLATION_PROT_EXEC | 97 EPT_VIOLATION_GVA_IS_VALID | 98 EPT_VIOLATION_GVA_TRANSLATED); 99 break; 100 case TEST_PT_PAGE_WRITE_PROTECTED: 101 GUEST_ASSERT_EXIT_QUAL(exit_qual, EPT_VIOLATION_ACC_READ | 102 EPT_VIOLATION_ACC_WRITE | 103 EPT_VIOLATION_PROT_READ | 104 EPT_VIOLATION_PROT_EXEC | 105 EPT_VIOLATION_GVA_IS_VALID); 106 break; 107 } 108 109 GUEST_DONE(); 110 } 111 112 #define GUEST_ASSERT_NPF_EC(ac_ec, ex_ec) \ 113 __GUEST_ASSERT((ac_ec) == (ex_ec), \ 114 "Wanted NPF error code '0x%lx', got '0x%lx'", (u64)(ex_ec), ac_ec) 115 116 117 static void l1_svm_code(struct svm_test_data *svm, u64 expected_fault_gpa, 118 u64 test_type) 119 { 120 struct vmcb *vmcb = svm->vmcb; 121 u64 exit_info_1; 122 123 generic_svm_setup(svm, l2_entry); 124 125 run_guest(vmcb, svm->vmcb_gpa); 126 127 /* Verify we got an NPF exit */ 128 __GUEST_ASSERT(vmcb->control.exit_code == SVM_EXIT_NPF, 129 "Expected NPF exit (0x%x), got 0x%lx", SVM_EXIT_NPF, 130 vmcb->control.exit_code); 131 132 __GUEST_ASSERT(vmcb->control.exit_info_2 == expected_fault_gpa, 133 "Expected exit_info_2 = 0x%lx, got 0x%lx", 134 expected_fault_gpa, 135 vmcb->control.exit_info_2); 136 137 exit_info_1 = vmcb->control.exit_info_1; 138 139 /* 140 * Note, without GMET enabled, NPT walks are always user accesses. And 141 * like EPT, page table accesses are always read+write. 142 */ 143 switch (test_type) { 144 case TEST_FINAL_PAGE_UNMAPPED: 145 GUEST_ASSERT_NPF_EC(exit_info_1, PFERR_USER_MASK | 146 PFERR_GUEST_FINAL_MASK); 147 break; 148 case TEST_PT_PAGE_UNMAPPED: 149 GUEST_ASSERT_NPF_EC(exit_info_1, PFERR_WRITE_MASK | 150 PFERR_USER_MASK | 151 PFERR_GUEST_PAGE_MASK); 152 break; 153 case TEST_FINAL_PAGE_WRITE_PROTECTED: 154 GUEST_ASSERT_NPF_EC(exit_info_1, PFERR_PRESENT_MASK | 155 PFERR_WRITE_MASK | 156 PFERR_USER_MASK | 157 PFERR_GUEST_FINAL_MASK); 158 break; 159 case TEST_PT_PAGE_WRITE_PROTECTED: 160 GUEST_ASSERT_NPF_EC(exit_info_1, PFERR_PRESENT_MASK | 161 PFERR_WRITE_MASK | 162 PFERR_USER_MASK | 163 PFERR_GUEST_PAGE_MASK); 164 break; 165 } 166 167 GUEST_DONE(); 168 } 169 170 static void l1_guest_code(void *data, u64 expected_fault_gpa, 171 u64 test_type) 172 { 173 if (this_cpu_has(X86_FEATURE_VMX)) 174 l1_vmx_code(data, expected_fault_gpa, test_type); 175 else 176 l1_svm_code(data, expected_fault_gpa, test_type); 177 } 178 179 /* Returns the GPA of the PT page that maps @vaddr. */ 180 static u64 get_pt_gpa_for_vaddr(struct kvm_vm *vm, u64 vaddr) 181 { 182 u64 *pte; 183 184 pte = vm_get_pte(vm, vaddr); 185 TEST_ASSERT(pte && (*pte & 0x1), "PTE not present for vaddr 0x%lx", 186 (unsigned long)vaddr); 187 188 return addr_hva2gpa(vm, (void *)((u64)pte & ~0xFFFULL)); 189 } 190 191 static void run_test(enum test_type type) 192 { 193 gpa_t expected_fault_gpa; 194 gva_t nested_gva; 195 196 struct kvm_vcpu *vcpu; 197 struct kvm_vm *vm; 198 struct ucall uc; 199 200 vm = vm_create_with_one_vcpu(&vcpu, l1_guest_code); 201 vm_enable_tdp(vm); 202 203 if (kvm_cpu_has(X86_FEATURE_VMX)) 204 vcpu_alloc_vmx(vm, &nested_gva); 205 else 206 vcpu_alloc_svm(vm, &nested_gva); 207 208 switch (type) { 209 case TEST_FINAL_PAGE_UNMAPPED: 210 /* 211 * Unmap the final data page from NPT/EPT. The guest page 212 * table walk succeeds, but the final GPA->HPA translation 213 * fails. L2 reads from the page via OUTS. 214 */ 215 l2_entry = l2_guest_code_outs; 216 l2_test_page = vm_alloc(vm, vm->page_size, TEST1_VADDR); 217 expected_fault_gpa = addr_gva2gpa(vm, l2_test_page); 218 break; 219 case TEST_PT_PAGE_UNMAPPED: 220 /* 221 * Unmap a page table page from NPT/EPT. The hardware page 222 * table walk fails when translating the PT page's GPA 223 * through NPT/EPT. L2 reads from the page via OUTS. 224 */ 225 l2_entry = l2_guest_code_outs; 226 l2_test_page = vm_alloc(vm, vm->page_size, TEST2_VADDR); 227 expected_fault_gpa = get_pt_gpa_for_vaddr(vm, l2_test_page); 228 break; 229 case TEST_FINAL_PAGE_WRITE_PROTECTED: 230 /* 231 * Write-protect the final data page in NPT/EPT. The page 232 * is present and readable, but not writable. L2 writes to 233 * the page via INS, triggering a protection violation. 234 */ 235 l2_entry = l2_guest_code_ins; 236 l2_test_page = vm_alloc(vm, vm->page_size, TEST3_VADDR); 237 expected_fault_gpa = addr_gva2gpa(vm, l2_test_page); 238 break; 239 case TEST_PT_PAGE_WRITE_PROTECTED: 240 /* 241 * Write-protect a page table page in NPT/EPT. The page is 242 * present and readable, but not writable. The guest page 243 * table walk needs write access to set A/D bits, so it 244 * triggers a protection violation on the PT page. 245 * L2 reads from the page via OUTS. 246 */ 247 l2_entry = l2_guest_code_outs; 248 l2_test_page = vm_alloc(vm, vm->page_size, TEST4_VADDR); 249 expected_fault_gpa = get_pt_gpa_for_vaddr(vm, l2_test_page); 250 break; 251 } 252 253 tdp_identity_map_default_memslots(vm); 254 255 if (type == TEST_FINAL_PAGE_WRITE_PROTECTED || 256 type == TEST_PT_PAGE_WRITE_PROTECTED) 257 *tdp_get_pte(vm, expected_fault_gpa) &= ~PTE_WRITABLE_MASK(&vm->stage2_mmu); 258 else 259 *tdp_get_pte(vm, expected_fault_gpa) &= ~(PTE_PRESENT_MASK(&vm->stage2_mmu) | 260 PTE_READABLE_MASK(&vm->stage2_mmu) | 261 PTE_WRITABLE_MASK(&vm->stage2_mmu) | 262 PTE_EXECUTABLE_MASK(&vm->stage2_mmu)); 263 264 sync_global_to_guest(vm, l2_entry); 265 sync_global_to_guest(vm, l2_test_page); 266 vcpu_args_set(vcpu, 3, nested_gva, expected_fault_gpa, (u64)type); 267 268 /* 269 * For the INS-based write test, KVM emulates the instruction and 270 * first reads from the I/O port, which exits to userspace. 271 * Re-enter the guest so emulation can proceed to the memory 272 * write, where the nested page fault is triggered. 273 */ 274 for (;;) { 275 vcpu_run(vcpu); 276 277 if (vcpu->run->exit_reason == KVM_EXIT_IO && 278 vcpu->run->io.port == TEST_IO_PORT && 279 vcpu->run->io.direction == KVM_EXIT_IO_IN) { 280 continue; 281 } 282 break; 283 } 284 285 switch (get_ucall(vcpu, &uc)) { 286 case UCALL_DONE: 287 break; 288 case UCALL_ABORT: 289 REPORT_GUEST_ASSERT(uc); 290 default: 291 TEST_FAIL("Unexpected exit reason: %d", vcpu->run->exit_reason); 292 } 293 294 kvm_vm_free(vm); 295 } 296 297 int main(int argc, char *argv[]) 298 { 299 TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_VMX) || kvm_cpu_has(X86_FEATURE_SVM)); 300 TEST_REQUIRE(kvm_cpu_has_tdp()); 301 302 run_test(TEST_FINAL_PAGE_UNMAPPED); 303 run_test(TEST_PT_PAGE_UNMAPPED); 304 run_test(TEST_FINAL_PAGE_WRITE_PROTECTED); 305 run_test(TEST_PT_PAGE_WRITE_PROTECTED); 306 307 return 0; 308 } 309