mmu_role_test.c (4013B)
1// SPDX-License-Identifier: GPL-2.0 2 3#include "kvm_util.h" 4#include "processor.h" 5 6#define VCPU_ID 1 7 8#define MMIO_GPA 0x100000000ull 9 10static void guest_code(void) 11{ 12 (void)READ_ONCE(*((uint64_t *)MMIO_GPA)); 13 (void)READ_ONCE(*((uint64_t *)MMIO_GPA)); 14 15 GUEST_ASSERT(0); 16} 17 18static void guest_pf_handler(struct ex_regs *regs) 19{ 20 /* PFEC == RSVD | PRESENT (read, kernel). */ 21 GUEST_ASSERT(regs->error_code == 0x9); 22 GUEST_DONE(); 23} 24 25static void mmu_role_test(u32 *cpuid_reg, u32 evil_cpuid_val) 26{ 27 u32 good_cpuid_val = *cpuid_reg; 28 struct kvm_run *run; 29 struct kvm_vm *vm; 30 uint64_t cmd; 31 int r; 32 33 /* Create VM */ 34 vm = vm_create_default(VCPU_ID, 0, guest_code); 35 run = vcpu_state(vm, VCPU_ID); 36 37 /* Map 1gb page without a backing memlot. */ 38 __virt_pg_map(vm, MMIO_GPA, MMIO_GPA, PG_LEVEL_1G); 39 40 r = _vcpu_run(vm, VCPU_ID); 41 42 /* Guest access to the 1gb page should trigger MMIO. */ 43 TEST_ASSERT(r == 0, "vcpu_run failed: %d\n", r); 44 TEST_ASSERT(run->exit_reason == KVM_EXIT_MMIO, 45 "Unexpected exit reason: %u (%s), expected MMIO exit (1gb page w/o memslot)\n", 46 run->exit_reason, exit_reason_str(run->exit_reason)); 47 48 TEST_ASSERT(run->mmio.len == 8, "Unexpected exit mmio size = %u", run->mmio.len); 49 50 TEST_ASSERT(run->mmio.phys_addr == MMIO_GPA, 51 "Unexpected exit mmio address = 0x%llx", run->mmio.phys_addr); 52 53 /* 54 * Effect the CPUID change for the guest and re-enter the guest. Its 55 * access should now #PF due to the PAGE_SIZE bit being reserved or 56 * the resulting GPA being invalid. Note, kvm_get_supported_cpuid() 57 * returns the struct that contains the entry being modified. Eww. 58 */ 59 *cpuid_reg = evil_cpuid_val; 60 vcpu_set_cpuid(vm, VCPU_ID, kvm_get_supported_cpuid()); 61 62 /* 63 * Add a dummy memslot to coerce KVM into bumping the MMIO generation. 64 * KVM does not "officially" support mucking with CPUID after KVM_RUN, 65 * and will incorrectly reuse MMIO SPTEs. Don't delete the memslot! 66 * KVM x86 zaps all shadow pages on memslot deletion. 67 */ 68 vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, 69 MMIO_GPA << 1, 10, 1, 0); 70 71 /* Set up a #PF handler to eat the RSVD #PF and signal all done! */ 72 vm_init_descriptor_tables(vm); 73 vcpu_init_descriptor_tables(vm, VCPU_ID); 74 vm_install_exception_handler(vm, PF_VECTOR, guest_pf_handler); 75 76 r = _vcpu_run(vm, VCPU_ID); 77 TEST_ASSERT(r == 0, "vcpu_run failed: %d\n", r); 78 79 cmd = get_ucall(vm, VCPU_ID, NULL); 80 TEST_ASSERT(cmd == UCALL_DONE, 81 "Unexpected guest exit, exit_reason=%s, ucall.cmd = %lu\n", 82 exit_reason_str(run->exit_reason), cmd); 83 84 /* 85 * Restore the happy CPUID value for the next test. Yes, changes are 86 * indeed persistent across VM destruction. 87 */ 88 *cpuid_reg = good_cpuid_val; 89 90 kvm_vm_free(vm); 91} 92 93int main(int argc, char *argv[]) 94{ 95 struct kvm_cpuid_entry2 *entry; 96 int opt; 97 98 /* 99 * All tests are opt-in because TDP doesn't play nice with reserved #PF 100 * in the GVA->GPA translation. The hardware page walker doesn't let 101 * software change GBPAGES or MAXPHYADDR, and KVM doesn't manually walk 102 * the GVA on fault for performance reasons. 103 */ 104 bool do_gbpages = false; 105 bool do_maxphyaddr = false; 106 107 setbuf(stdout, NULL); 108 109 while ((opt = getopt(argc, argv, "gm")) != -1) { 110 switch (opt) { 111 case 'g': 112 do_gbpages = true; 113 break; 114 case 'm': 115 do_maxphyaddr = true; 116 break; 117 case 'h': 118 default: 119 printf("usage: %s [-g (GBPAGES)] [-m (MAXPHYADDR)]\n", argv[0]); 120 break; 121 } 122 } 123 124 if (!do_gbpages && !do_maxphyaddr) { 125 print_skip("No sub-tests selected"); 126 return 0; 127 } 128 129 entry = kvm_get_supported_cpuid_entry(0x80000001); 130 if (!(entry->edx & CPUID_GBPAGES)) { 131 print_skip("1gb hugepages not supported"); 132 return 0; 133 } 134 135 if (do_gbpages) { 136 pr_info("Test MMIO after toggling CPUID.GBPAGES\n\n"); 137 mmu_role_test(&entry->edx, entry->edx & ~CPUID_GBPAGES); 138 } 139 140 if (do_maxphyaddr) { 141 pr_info("Test MMIO after changing CPUID.MAXPHYADDR\n\n"); 142 entry = kvm_get_supported_cpuid_entry(0x80000008); 143 mmu_role_test(&entry->eax, (entry->eax & ~0xff) | 0x20); 144 } 145 146 return 0; 147}