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