Signed-off-by: Paolo Bonzini <pbonzini@xxxxxxxxxx> --- x86/vmx.c | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ x86/vmx.h | 5 +++ x86/vmx_tests.c | 31 ++++++++++++++++-- 3 files changed, 132 insertions(+), 3 deletions(-) diff --git a/x86/vmx.c b/x86/vmx.c index 5200dcd..0505534 100644 --- a/x86/vmx.c +++ b/x86/vmx.c @@ -421,6 +421,105 @@ unsigned long get_ept_pte(unsigned long *pml4, return pte; } +static void clear_ept_ad_pte(unsigned long *pml4, unsigned long guest_addr) +{ + int l; + unsigned long *pt = pml4; + u64 pte; + unsigned offset; + + for (l = EPT_PAGE_LEVEL; ; --l) { + offset = (guest_addr >> EPT_LEVEL_SHIFT(l)) & EPT_PGDIR_MASK; + pt[offset] &= ~(EPT_ACCESS_FLAG|EPT_DIRTY_FLAG); + pte = pt[offset]; + if (l == 1 || (l < 4 && (pte & EPT_LARGE_PAGE))) + break; + pt = (unsigned long *)(pte & EPT_ADDR_MASK); + } +} + +/* clear_ept_ad : Clear EPT A/D bits for the page table walk and the + final GPA of a guest address. */ +void clear_ept_ad(unsigned long *pml4, u64 guest_cr3, + unsigned long guest_addr) +{ + int l; + unsigned long *pt = (unsigned long *)guest_cr3, gpa; + u64 pte, offset_in_page; + unsigned offset; + + for (l = EPT_PAGE_LEVEL; ; --l) { + offset = (guest_addr >> EPT_LEVEL_SHIFT(l)) & EPT_PGDIR_MASK; + + clear_ept_ad_pte(pml4, (u64) &pt[offset]); + pte = pt[offset]; + if (l == 1 || (l < 4 && (pte & PT_PAGE_SIZE_MASK))) + break; + if (!(pte & PT_PRESENT_MASK)) + return; + pt = (unsigned long *)(pte & PT_ADDR_MASK); + } + + offset = (guest_addr >> EPT_LEVEL_SHIFT(l)) & EPT_PGDIR_MASK; + offset_in_page = guest_addr & ((1 << EPT_LEVEL_SHIFT(l)) - 1); + gpa = (pt[offset] & PT_ADDR_MASK) | (guest_addr & offset_in_page); + clear_ept_ad_pte(pml4, gpa); +} + +/* check_ept_ad : Check the content of EPT A/D bits for the page table + walk and the final GPA of a guest address. */ +void check_ept_ad(unsigned long *pml4, u64 guest_cr3, + unsigned long guest_addr, int expected_gpa_ad, + int expected_pt_ad) +{ + int l; + unsigned long *pt = (unsigned long *)guest_cr3, gpa; + u64 ept_pte, pte, offset_in_page; + unsigned offset; + bool bad_pt_ad = false; + + for (l = EPT_PAGE_LEVEL; ; --l) { + offset = (guest_addr >> EPT_LEVEL_SHIFT(l)) & EPT_PGDIR_MASK; + + ept_pte = get_ept_pte(pml4, (u64) &pt[offset], 1); + if (ept_pte == 0) + return; + + if (!bad_pt_ad) { + bad_pt_ad |= (ept_pte & (EPT_ACCESS_FLAG|EPT_DIRTY_FLAG)) != expected_pt_ad; + if (bad_pt_ad) + report("EPT - guest level %d page table A=%d/D=%d", + false, l, + !!(expected_pt_ad & EPT_ACCESS_FLAG), + !!(expected_pt_ad & EPT_DIRTY_FLAG)); + } + + pte = pt[offset]; + if (l == 1 || (l < 4 && (pte & PT_PAGE_SIZE_MASK))) + break; + if (!(pte & PT_PRESENT_MASK)) + return; + pt = (unsigned long *)(pte & PT_ADDR_MASK); + } + + if (!bad_pt_ad) + report("EPT - guest page table structures A=%d/D=%d", + true, + !!(expected_pt_ad & EPT_ACCESS_FLAG), + !!(expected_pt_ad & EPT_DIRTY_FLAG)); + + offset = (guest_addr >> EPT_LEVEL_SHIFT(l)) & EPT_PGDIR_MASK; + offset_in_page = guest_addr & ((1 << EPT_LEVEL_SHIFT(l)) - 1); + gpa = (pt[offset] & PT_ADDR_MASK) | (guest_addr & offset_in_page); + + ept_pte = get_ept_pte(pml4, gpa, 1); + report("EPT - guest physical address A=%d/D=%d", + (ept_pte & (EPT_ACCESS_FLAG|EPT_DIRTY_FLAG)) == expected_gpa_ad, + !!(expected_gpa_ad & EPT_ACCESS_FLAG), + !!(expected_gpa_ad & EPT_DIRTY_FLAG)); +} + + void ept_sync(int type, u64 eptp) { switch (type) { diff --git a/x86/vmx.h b/x86/vmx.h index a2bacd3..290e6bd 100644 --- a/x86/vmx.h +++ b/x86/vmx.h @@ -618,5 +618,10 @@ unsigned long get_ept_pte(unsigned long *pml4, unsigned long guest_addr, int level); int set_ept_pte(unsigned long *pml4, unsigned long guest_addr, int level, u64 pte_val); +void check_ept_ad(unsigned long *pml4, u64 guest_cr3, + unsigned long guest_addr, int expected_gpa_ad, + int expected_pt_ad); +void clear_ept_ad(unsigned long *pml4, u64 guest_cr3, + unsigned long guest_addr); #endif diff --git a/x86/vmx_tests.c b/x86/vmx_tests.c index 9dc64fc..a61c9f2 100644 --- a/x86/vmx_tests.c +++ b/x86/vmx_tests.c @@ -969,7 +969,11 @@ static int setup_ept(bool enable_ad) end_of_memory = fwcfg_get_u64(FW_CFG_RAM_SIZE); if (end_of_memory < (1ul << 32)) end_of_memory = (1ul << 32); - setup_ept_range(pml4, 0, end_of_memory, 0, support_2m, + /* Cannot use large EPT pages if we need to track EPT + * accessed/dirty bits at 4K granularity. + */ + setup_ept_range(pml4, 0, end_of_memory, + 0, !enable_ad && support_2m, EPT_WA | EPT_RA | EPT_EA); return 0; } @@ -1090,12 +1094,14 @@ bool invept_test(int type, u64 eptp) static int ept_exit_handler_common(bool have_ad) { u64 guest_rip; + u64 guest_cr3; ulong reason; u32 insn_len; u32 exit_qual; static unsigned long data_page1_pte, data_page1_pte_pte; guest_rip = vmcs_read(GUEST_RIP); + guest_cr3 = vmcs_read(GUEST_CR3); reason = vmcs_read(EXI_REASON) & 0xff; insn_len = vmcs_read(EXI_INST_LEN); exit_qual = vmcs_read(EXI_QUALIFICATION); @@ -1103,6 +1109,18 @@ static int ept_exit_handler_common(bool have_ad) case VMX_VMCALL: switch (vmx_get_test_stage()) { case 0: + check_ept_ad(pml4, guest_cr3, + (unsigned long)data_page1, + have_ad ? EPT_ACCESS_FLAG : 0, + have_ad ? EPT_ACCESS_FLAG | EPT_DIRTY_FLAG : 0); + check_ept_ad(pml4, guest_cr3, + (unsigned long)data_page2, + have_ad ? EPT_ACCESS_FLAG | EPT_DIRTY_FLAG : 0, + have_ad ? EPT_ACCESS_FLAG | EPT_DIRTY_FLAG : 0); + clear_ept_ad(pml4, guest_cr3, (unsigned long)data_page1); + clear_ept_ad(pml4, guest_cr3, (unsigned long)data_page2); + if (have_ad) + ept_sync(INVEPT_SINGLE, eptp);; if (*((u32 *)data_page1) == MAGIC_VAL_3 && *((u32 *)data_page2) == MAGIC_VAL_2) { vmx_inc_test_stage(); @@ -1125,10 +1143,11 @@ static int ept_exit_handler_common(bool have_ad) ept_sync(INVEPT_SINGLE, eptp); break; case 3: + clear_ept_ad(pml4, guest_cr3, (unsigned long)data_page1); data_page1_pte = get_ept_pte(pml4, (unsigned long)data_page1, 1); set_ept_pte(pml4, (unsigned long)data_page1, - 1, data_page1_pte & (~EPT_PRESENT)); + 1, data_page1_pte & ~EPT_PRESENT); ept_sync(INVEPT_SINGLE, eptp); break; case 4: @@ -1137,7 +1156,7 @@ static int ept_exit_handler_common(bool have_ad) data_page1_pte &= PAGE_MASK; data_page1_pte_pte = get_ept_pte(pml4, data_page1_pte, 2); set_ept_pte(pml4, data_page1_pte, 2, - data_page1_pte_pte & (~EPT_PRESENT)); + data_page1_pte_pte & ~EPT_PRESENT); ept_sync(INVEPT_SINGLE, eptp); break; case 6: @@ -1174,6 +1193,9 @@ static int ept_exit_handler_common(bool have_ad) case VMX_EPT_VIOLATION: switch(vmx_get_test_stage()) { case 3: + check_ept_ad(pml4, guest_cr3, (unsigned long)data_page1, 0, + have_ad ? EPT_ACCESS_FLAG | EPT_DIRTY_FLAG : 0); + clear_ept_ad(pml4, guest_cr3, (unsigned long)data_page1); if (exit_qual == (EPT_VLT_WR | EPT_VLT_LADDR_VLD | EPT_VLT_PADDR)) vmx_inc_test_stage(); @@ -1182,6 +1204,9 @@ static int ept_exit_handler_common(bool have_ad) ept_sync(INVEPT_SINGLE, eptp); break; case 4: + check_ept_ad(pml4, guest_cr3, (unsigned long)data_page1, 0, + have_ad ? EPT_ACCESS_FLAG | EPT_DIRTY_FLAG : 0); + clear_ept_ad(pml4, guest_cr3, (unsigned long)data_page1); if (exit_qual == (EPT_VLT_RD | (have_ad ? EPT_VLT_WR : 0) | EPT_VLT_LADDR_VLD)) -- 1.8.3.1