Modify permissions for NPT entries that contain guest page table structures. Verify that NPF VM exit correctly reports fault occurred during the guest page table walk and correctly reports the right violation. Signed-off-by: Kevin Cheng --- x86/svm_npt.c | 223 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 221 insertions(+), 2 deletions(-) diff --git a/x86/svm_npt.c b/x86/svm_npt.c index ab744d41824f8..9380697f36ce9 100644 --- a/x86/svm_npt.c +++ b/x86/svm_npt.c @@ -51,6 +51,12 @@ static void *get_1g_page(void) return alloc; } +static void do_npt_access_op(enum npt_access_op op) +{ + npt_access_test_data.op = op; + svm_vmrun(); +} + static void diagnose_npt_violation_exit_code(u64 expected, u64 actual) { @@ -115,9 +121,8 @@ static void do_npt_access(enum npt_access_op op, u64 expected_fault, u64 exit_info_2; /* Try the access and observe the violation. */ - npt_access_test_data.op = op; vmcb->control.tlb_ctl = TLB_CONTROL_FLUSH_ALL_ASID; - svm_vmrun(); + do_npt_access_op(op); exit_code = vmcb->control.exit_code; exit_info_1 = vmcb->control.exit_info_1; @@ -422,6 +427,215 @@ static bool npt_rw_pfwalk_check(struct svm_test *test) && (vmcb->control.exit_info_2 == read_cr3()); } +/* + * This function modifies the NPT entry that maps the GPA that the guest page + * table entry mapping npt_access_test_data.gva resides on. + */ +static void npt_access_paddr(unsigned long npt_clear, unsigned long npt_set, + unsigned long pte_set, enum npt_access_op op, + bool expect_violation, u64 expected_fault) +{ + struct npt_access_test_data *data = &npt_access_test_data; + unsigned long *ptep; + unsigned long gpa; + unsigned long orig_npte; + unsigned long pte; + u64 orig_opt_mask = pte_opt_mask; + int level; + + /* Modify the guest PTE mapping data->gva according to @pte_set. */ + ptep = get_pte_level(current_page_table(), data->gva, 1); + report(ptep, "Get pte for gva 0x%lx", (unsigned long)data->gva); + report((*ptep & PT_ADDR_MASK) == data->gpa, "gva is correctly mapped"); + *ptep = (*ptep & ~PT_AD_MASK) | pte_set; + do_npt_access_op(OP_FLUSH_TLB); + + /* + * Now modify the access bits on the NPT entry for the GPA that the + * guest PTE resides on. Note that by modifying a single NPT entry, + * we're potentially affecting 512 guest PTEs. However, we've carefully + * constructed our test such that those other 511 PTEs aren't used by + * the guest: data->gva is at the beginning of a 1G huge page, thus the + * PTE we're modifying is at the beginning of a 4K page and the + * following 511 entries are also under our control (and not touched by + * the guest). + */ + gpa = virt_to_phys(ptep); + assert((gpa & ~PAGE_MASK) == 0); + + /* + * Make sure the guest page table page is mapped with a 4K NPT entry, + * otherwise our level=1 twiddling below will fail. We use the + * identity map (gpa = gpa) since page tables are shared with the host. + */ + pte_opt_mask |= PT_USER_MASK; + install_pte(npt_get_pml4e(), /*level=*/1, (void *)(ulong)gpa, + gpa | PT_PRESENT_MASK | PT_WRITABLE_MASK | PT_USER_MASK, 0); + pte_opt_mask = orig_opt_mask; + + orig_npte = npt_twiddle(gpa, /*mkhuge=*/0, /*level=1*/1, npt_clear, npt_set); + + if (expect_violation) { + do_npt_access(op, expected_fault, gpa); + npt_untwiddle(gpa, /*level=*/1, orig_npte); + do_npt_access_op(op); + TEST_EXPECT_EQ(vmcb->control.exit_code, SVM_EXIT_VMMCALL); + } else { + do_npt_access(op, 0, gpa); + for (level = PAGE_LEVEL; level > 0; level--) { + pte = *find_pte_level(npt_get_pml4e(), (void *)gpa, level).pte; + report(pte & PT_ACCESSED_MASK, + "Access flag set. PTE val: 0x%lx", + pte); + + if (level == 1) + report(pte & PT_DIRTY_MASK, + "Dirty flag set. PTE val: 0x%lx", + pte); + else + report(!(pte & PT_DIRTY_MASK), + "Dirty flag not set. PTE val: 0x%lx level: %d", + pte, level); + } + + npt_untwiddle(gpa, /*level=*/1, orig_npte); + } + + report(*ptep & PT_ACCESSED_MASK, "Access flag set"); + if ((pte_set & PT_DIRTY_MASK) || op == OP_WRITE) + report(*ptep & PT_DIRTY_MASK, "Dirty flag set"); +} + +static void npt_access_allowed_paddr(unsigned long npt_clear, unsigned long npt_set, + unsigned long pte_set, enum npt_access_op op) +{ + npt_access_paddr(npt_clear, npt_set, pte_set, op, false, 0); +} + +static void npt_access_npf_paddr(unsigned long npt_clear, unsigned long npt_set, + unsigned long pte_set, enum npt_access_op op, + u64 expected_fault) +{ + npt_access_paddr(npt_clear, npt_set, pte_set, op, true, expected_fault); +} + +/* + * All accesses to guest paging structures are considered as writes as far as + * NPT translation is concerned. + */ +static void npt_access_paddr_not_present_test(void) +{ + u32 pte_set_combinations[3] = {0, PT_ACCESSED_MASK, PT_DIRTY_MASK}; + + npt_access_test_setup(); + + for (int i = 0; i < ARRAY_SIZE(pte_set_combinations); i++) { + npt_access_npf_paddr(PT_PRESENT_MASK, 0, + pte_set_combinations[i], OP_READ, + PFERR_GUEST_PAGE_MASK | PFERR_USER_MASK | + PFERR_WRITE_MASK); + npt_access_npf_paddr(PT_PRESENT_MASK, 0, + pte_set_combinations[i], OP_WRITE, + PFERR_GUEST_PAGE_MASK | PFERR_USER_MASK | + PFERR_WRITE_MASK); + npt_access_npf_paddr(PT_PRESENT_MASK, 0, + pte_set_combinations[i], OP_EXEC, + PFERR_GUEST_PAGE_MASK | PFERR_USER_MASK | + PFERR_WRITE_MASK); + } + + npt_access_test_cleanup(); +} + +static void npt_access_paddr_read_only_test(void) +{ + u32 pte_set_combinations[3] = {0, PT_ACCESSED_MASK, PT_DIRTY_MASK}; + + npt_access_test_setup(); + + for (int i = 0; i < ARRAY_SIZE(pte_set_combinations); i++) { + npt_access_npf_paddr(PT_WRITABLE_MASK, PT64_NX_MASK, + pte_set_combinations[i], OP_READ, + PFERR_GUEST_PAGE_MASK | PFERR_USER_MASK | + PFERR_WRITE_MASK | + PFERR_PRESENT_MASK); + npt_access_npf_paddr(PT_WRITABLE_MASK, PT64_NX_MASK, + pte_set_combinations[i], OP_WRITE, + PFERR_GUEST_PAGE_MASK | PFERR_USER_MASK | + PFERR_WRITE_MASK | + PFERR_PRESENT_MASK); + npt_access_npf_paddr(PT_WRITABLE_MASK, PT64_NX_MASK, + pte_set_combinations[i], OP_EXEC, + PFERR_GUEST_PAGE_MASK | PFERR_USER_MASK | + PFERR_WRITE_MASK | + PFERR_PRESENT_MASK); + } + + npt_access_test_cleanup(); +} + +static void npt_access_paddr_read_execute_test(void) +{ + u32 pte_set_combinations[3] = {0, PT_ACCESSED_MASK, PT_DIRTY_MASK}; + + npt_access_test_setup(); + + for (int i = 0; i < ARRAY_SIZE(pte_set_combinations); i++) { + npt_access_npf_paddr( + PT_WRITABLE_MASK, 0, pte_set_combinations[i], OP_READ, + PFERR_GUEST_PAGE_MASK | PFERR_USER_MASK | + PFERR_WRITE_MASK | PFERR_PRESENT_MASK); + npt_access_npf_paddr( + PT_WRITABLE_MASK, 0, pte_set_combinations[i], OP_WRITE, + PFERR_GUEST_PAGE_MASK | PFERR_USER_MASK | + PFERR_WRITE_MASK | PFERR_PRESENT_MASK); + npt_access_npf_paddr( + PT_WRITABLE_MASK, 0, pte_set_combinations[i], OP_EXEC, + PFERR_GUEST_PAGE_MASK | PFERR_USER_MASK | + PFERR_WRITE_MASK | PFERR_PRESENT_MASK); + } + + npt_access_test_cleanup(); +} + +static void npt_access_paddr_read_write_test(void) +{ + u32 pte_set_combinations[3] = {0, PT_ACCESSED_MASK, PT_DIRTY_MASK}; + + npt_access_test_setup(); + + /* Read-write access to paging structure. */ + for (int i = 0; i < ARRAY_SIZE(pte_set_combinations); i++) { + npt_access_allowed_paddr(0, PT_WRITABLE_MASK | PT64_NX_MASK, + pte_set_combinations[i], OP_READ); + npt_access_allowed_paddr(0, PT_WRITABLE_MASK | PT64_NX_MASK, + pte_set_combinations[i], OP_WRITE); + npt_access_allowed_paddr(0, PT_WRITABLE_MASK | PT64_NX_MASK, + pte_set_combinations[i], OP_EXEC); + } + + npt_access_test_cleanup(); +} + +static void npt_access_paddr_read_write_execute_test(void) +{ + u32 pte_set_combinations[3] = {0, PT_ACCESSED_MASK, PT_DIRTY_MASK}; + + npt_access_test_setup(); + + /* RWX access to paging structure. */ + for (int i = 0; i < ARRAY_SIZE(pte_set_combinations); i++) { + npt_access_allowed_paddr(0, PT_WRITABLE_MASK, pte_set_combinations[i], + OP_READ); + npt_access_allowed_paddr(0, PT_WRITABLE_MASK, pte_set_combinations[i], + OP_WRITE); + npt_access_allowed_paddr(0, PT_WRITABLE_MASK, pte_set_combinations[i], + OP_EXEC); + } + + npt_access_test_cleanup(); +} + static bool was_x2apic; static void npt_apic_prepare(void) @@ -861,6 +1075,11 @@ static struct svm_test npt_tests[] = { NPT_V2_TEST(npt_rw_test), NPT_V2_TEST(npt_rwx_test), NPT_V2_TEST(npt_ignored_bits_test), + NPT_V2_TEST(npt_access_paddr_not_present_test), + NPT_V2_TEST(npt_access_paddr_read_only_test), + NPT_V2_TEST(npt_access_paddr_read_write_test), + NPT_V2_TEST(npt_access_paddr_read_write_execute_test), + NPT_V2_TEST(npt_access_paddr_read_execute_test), { NULL, NULL, NULL, NULL, NULL, NULL, NULL } }; -- 2.52.0.322.g1dd061c0dc-goog