| #include "svm.h" |
| #include "vm.h" |
| #include "alloc_page.h" |
| #include "vmalloc.h" |
| |
| static void *scratch_page; |
| |
| static void null_test(struct svm_test *test) |
| { |
| } |
| |
| static void npt_np_prepare(struct svm_test *test) |
| { |
| u64 *pte; |
| |
| scratch_page = alloc_page(); |
| pte = npt_get_pte((u64) scratch_page); |
| |
| *pte &= ~1ULL; |
| } |
| |
| static void npt_np_test(struct svm_test *test) |
| { |
| (void)*(volatile u64 *)scratch_page; |
| } |
| |
| static bool npt_np_check(struct svm_test *test) |
| { |
| u64 *pte = npt_get_pte((u64) scratch_page); |
| |
| *pte |= 1ULL; |
| |
| return (vmcb->control.exit_code == SVM_EXIT_NPF) |
| && (vmcb->control.exit_info_1 == 0x100000004ULL); |
| } |
| |
| static void npt_nx_prepare(struct svm_test *test) |
| { |
| u64 *pte; |
| |
| test->scratch = rdmsr(MSR_EFER); |
| wrmsr(MSR_EFER, test->scratch | EFER_NX); |
| |
| /* Clear the guest's EFER.NX, it should not affect NPT behavior. */ |
| vmcb->save.efer &= ~EFER_NX; |
| |
| pte = npt_get_pte((u64) null_test); |
| |
| *pte |= PT64_NX_MASK; |
| } |
| |
| static bool npt_nx_check(struct svm_test *test) |
| { |
| u64 *pte = npt_get_pte((u64) null_test); |
| |
| wrmsr(MSR_EFER, test->scratch); |
| |
| *pte &= ~PT64_NX_MASK; |
| |
| return (vmcb->control.exit_code == SVM_EXIT_NPF) |
| && (vmcb->control.exit_info_1 == 0x100000015ULL); |
| } |
| |
| static void npt_us_prepare(struct svm_test *test) |
| { |
| u64 *pte; |
| |
| scratch_page = alloc_page(); |
| pte = npt_get_pte((u64) scratch_page); |
| |
| *pte &= ~(1ULL << 2); |
| } |
| |
| static void npt_us_test(struct svm_test *test) |
| { |
| (void)*(volatile u64 *)scratch_page; |
| } |
| |
| static bool npt_us_check(struct svm_test *test) |
| { |
| u64 *pte = npt_get_pte((u64) scratch_page); |
| |
| *pte |= (1ULL << 2); |
| |
| return (vmcb->control.exit_code == SVM_EXIT_NPF) |
| && (vmcb->control.exit_info_1 == 0x100000005ULL); |
| } |
| |
| static void npt_rw_prepare(struct svm_test *test) |
| { |
| |
| u64 *pte; |
| |
| pte = npt_get_pte(0x80000); |
| |
| *pte &= ~(1ULL << 1); |
| } |
| |
| static void npt_rw_test(struct svm_test *test) |
| { |
| u64 *data = (void *)(0x80000); |
| |
| *data = 0; |
| } |
| |
| static bool npt_rw_check(struct svm_test *test) |
| { |
| u64 *pte = npt_get_pte(0x80000); |
| |
| *pte |= (1ULL << 1); |
| |
| return (vmcb->control.exit_code == SVM_EXIT_NPF) |
| && (vmcb->control.exit_info_1 == 0x100000007ULL); |
| } |
| |
| static void npt_rw_pfwalk_prepare(struct svm_test *test) |
| { |
| |
| u64 *pte; |
| |
| pte = npt_get_pte(read_cr3()); |
| |
| *pte &= ~(1ULL << 1); |
| } |
| |
| static bool npt_rw_pfwalk_check(struct svm_test *test) |
| { |
| u64 *pte = npt_get_pte(read_cr3()); |
| |
| *pte |= (1ULL << 1); |
| |
| return (vmcb->control.exit_code == SVM_EXIT_NPF) |
| && (vmcb->control.exit_info_1 == 0x200000007ULL) |
| && (vmcb->control.exit_info_2 == read_cr3()); |
| } |
| |
| static void npt_l1mmio_prepare(struct svm_test *test) |
| { |
| } |
| |
| u32 nested_apic_version1; |
| u32 nested_apic_version2; |
| |
| static void npt_l1mmio_test(struct svm_test *test) |
| { |
| volatile u32 *data = (volatile void *)(0xfee00030UL); |
| |
| nested_apic_version1 = *data; |
| nested_apic_version2 = *data; |
| } |
| |
| static bool npt_l1mmio_check(struct svm_test *test) |
| { |
| volatile u32 *data = (volatile void *)(0xfee00030); |
| u32 lvr = *data; |
| |
| return nested_apic_version1 == lvr && nested_apic_version2 == lvr; |
| } |
| |
| static void npt_rw_l1mmio_prepare(struct svm_test *test) |
| { |
| |
| u64 *pte; |
| |
| pte = npt_get_pte(0xfee00080); |
| |
| *pte &= ~(1ULL << 1); |
| } |
| |
| static void npt_rw_l1mmio_test(struct svm_test *test) |
| { |
| volatile u32 *data = (volatile void *)(0xfee00080); |
| |
| *data = *data; |
| } |
| |
| static bool npt_rw_l1mmio_check(struct svm_test *test) |
| { |
| u64 *pte = npt_get_pte(0xfee00080); |
| |
| *pte |= (1ULL << 1); |
| |
| return (vmcb->control.exit_code == SVM_EXIT_NPF) |
| && (vmcb->control.exit_info_1 == 0x100000007ULL); |
| } |
| |
| static void basic_guest_main(struct svm_test *test) |
| { |
| } |
| |
| static void __svm_npt_rsvd_bits_test(u64 * pxe, u64 rsvd_bits, u64 efer, |
| ulong cr4, u64 guest_efer, ulong guest_cr4) |
| { |
| u64 pxe_orig = *pxe; |
| int exit_reason; |
| u64 pfec; |
| |
| wrmsr(MSR_EFER, efer); |
| write_cr4(cr4); |
| |
| vmcb->save.efer = guest_efer; |
| vmcb->save.cr4 = guest_cr4; |
| |
| *pxe |= rsvd_bits; |
| |
| exit_reason = svm_vmrun(); |
| |
| report(exit_reason == SVM_EXIT_NPF, |
| "Wanted #NPF on rsvd bits = 0x%lx, got exit = 0x%x", rsvd_bits, |
| exit_reason); |
| |
| if (pxe == npt_get_pdpe((u64) basic_guest_main) || pxe == npt_get_pml4e()) { |
| /* |
| * The guest's page tables will blow up on a bad PDPE/PML4E, |
| * before starting the final walk of the guest page. |
| */ |
| pfec = 0x20000000full; |
| } else { |
| /* RSVD #NPF on final walk of guest page. */ |
| pfec = 0x10000000dULL; |
| |
| /* PFEC.FETCH=1 if NX=1 *or* SMEP=1. */ |
| if ((cr4 & X86_CR4_SMEP) || (efer & EFER_NX)) |
| pfec |= 0x10; |
| |
| } |
| |
| report(vmcb->control.exit_info_1 == pfec, |
| "Wanted PFEC = 0x%lx, got PFEC = %lx, PxE = 0x%lx. " |
| "host.NX = %u, host.SMEP = %u, guest.NX = %u, guest.SMEP = %u", |
| pfec, vmcb->control.exit_info_1, *pxe, |
| !!(efer & EFER_NX), !!(cr4 & X86_CR4_SMEP), |
| !!(guest_efer & EFER_NX), !!(guest_cr4 & X86_CR4_SMEP)); |
| |
| *pxe = pxe_orig; |
| } |
| |
| static void _svm_npt_rsvd_bits_test(u64 * pxe, u64 pxe_rsvd_bits, u64 efer, |
| ulong cr4, u64 guest_efer, ulong guest_cr4) |
| { |
| u64 rsvd_bits; |
| int i; |
| |
| /* |
| * RDTSC or RDRAND can sometimes fail to generate a valid reserved bits |
| */ |
| if (!pxe_rsvd_bits) { |
| report_skip |
| ("svm_npt_rsvd_bits_test: Reserved bits are not valid"); |
| return; |
| } |
| |
| /* |
| * Test all combinations of guest/host EFER.NX and CR4.SMEP. If host |
| * EFER.NX=0, use NX as the reserved bit, otherwise use the passed in |
| * @pxe_rsvd_bits. |
| */ |
| for (i = 0; i < 16; i++) { |
| if (i & 1) { |
| rsvd_bits = pxe_rsvd_bits; |
| efer |= EFER_NX; |
| } else { |
| rsvd_bits = PT64_NX_MASK; |
| efer &= ~EFER_NX; |
| } |
| if (i & 2) |
| cr4 |= X86_CR4_SMEP; |
| else |
| cr4 &= ~X86_CR4_SMEP; |
| if (i & 4) |
| guest_efer |= EFER_NX; |
| else |
| guest_efer &= ~EFER_NX; |
| if (i & 8) |
| guest_cr4 |= X86_CR4_SMEP; |
| else |
| guest_cr4 &= ~X86_CR4_SMEP; |
| |
| __svm_npt_rsvd_bits_test(pxe, rsvd_bits, efer, cr4, |
| guest_efer, guest_cr4); |
| } |
| } |
| |
| static u64 get_random_bits(u64 hi, u64 low) |
| { |
| unsigned retry = 5; |
| u64 rsvd_bits = 0; |
| |
| if (this_cpu_has(X86_FEATURE_RDRAND)) { |
| do { |
| rsvd_bits = (rdrand() << low) & GENMASK_ULL(hi, low); |
| retry--; |
| } while (!rsvd_bits && retry); |
| } |
| |
| if (!rsvd_bits) { |
| retry = 5; |
| do { |
| rsvd_bits = (rdtsc() << low) & GENMASK_ULL(hi, low); |
| retry--; |
| } while (!rsvd_bits && retry); |
| } |
| |
| return rsvd_bits; |
| } |
| |
| static void svm_npt_rsvd_bits_test(void) |
| { |
| u64 saved_efer, host_efer, sg_efer, guest_efer; |
| ulong saved_cr4, host_cr4, sg_cr4, guest_cr4; |
| |
| if (!npt_supported()) { |
| report_skip("NPT not supported"); |
| return; |
| } |
| |
| saved_efer = host_efer = rdmsr(MSR_EFER); |
| saved_cr4 = host_cr4 = read_cr4(); |
| sg_efer = guest_efer = vmcb->save.efer; |
| sg_cr4 = guest_cr4 = vmcb->save.cr4; |
| |
| test_set_guest(basic_guest_main); |
| |
| /* |
| * 4k PTEs don't have reserved bits if MAXPHYADDR >= 52, just skip the |
| * sub-test. The NX test is still valid, but the extra bit of coverage |
| * isn't worth the extra complexity. |
| */ |
| if (cpuid_maxphyaddr() >= 52) |
| goto skip_pte_test; |
| |
| _svm_npt_rsvd_bits_test(npt_get_pte((u64) basic_guest_main), |
| get_random_bits(51, cpuid_maxphyaddr()), |
| host_efer, host_cr4, guest_efer, guest_cr4); |
| |
| skip_pte_test: |
| _svm_npt_rsvd_bits_test(npt_get_pde((u64) basic_guest_main), |
| get_random_bits(20, 13) | PT_PAGE_SIZE_MASK, |
| host_efer, host_cr4, guest_efer, guest_cr4); |
| |
| _svm_npt_rsvd_bits_test(npt_get_pdpe((u64) basic_guest_main), |
| PT_PAGE_SIZE_MASK | |
| (this_cpu_has(X86_FEATURE_GBPAGES) ? |
| get_random_bits(29, 13) : 0), host_efer, |
| host_cr4, guest_efer, guest_cr4); |
| |
| _svm_npt_rsvd_bits_test(npt_get_pml4e(), BIT_ULL(8), |
| host_efer, host_cr4, guest_efer, guest_cr4); |
| |
| wrmsr(MSR_EFER, saved_efer); |
| write_cr4(saved_cr4); |
| vmcb->save.efer = sg_efer; |
| vmcb->save.cr4 = sg_cr4; |
| } |
| |
| #define NPT_V1_TEST(name, prepare, guest_code, check) \ |
| { #name, npt_supported, prepare, default_prepare_gif_clear, guest_code, \ |
| default_finished, check } |
| |
| #define NPT_V2_TEST(name) { #name, .v2 = name } |
| |
| static struct svm_test npt_tests[] = { |
| NPT_V1_TEST(npt_nx, npt_nx_prepare, null_test, npt_nx_check), |
| NPT_V1_TEST(npt_np, npt_np_prepare, npt_np_test, npt_np_check), |
| NPT_V1_TEST(npt_us, npt_us_prepare, npt_us_test, npt_us_check), |
| NPT_V1_TEST(npt_rw, npt_rw_prepare, npt_rw_test, npt_rw_check), |
| NPT_V1_TEST(npt_rw_pfwalk, npt_rw_pfwalk_prepare, null_test, npt_rw_pfwalk_check), |
| NPT_V1_TEST(npt_l1mmio, npt_l1mmio_prepare, npt_l1mmio_test, npt_l1mmio_check), |
| NPT_V1_TEST(npt_rw_l1mmio, npt_rw_l1mmio_prepare, npt_rw_l1mmio_test, npt_rw_l1mmio_check), |
| NPT_V2_TEST(svm_npt_rsvd_bits_test), |
| { NULL, NULL, NULL, NULL, NULL, NULL, NULL } |
| }; |
| |
| int main(int ac, char **av) |
| { |
| pteval_t opt_mask = 0; |
| |
| __setup_vm(&opt_mask); |
| return run_svm_tests(ac, av, npt_tests); |
| } |