@@ -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) {
@@ -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
@@ -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))
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> --- x86/vmx.c | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ x86/vmx.h | 5 +++ x86/vmx_tests.c | 31 ++++++++++++++++-- 3 files changed, 132 insertions(+), 3 deletions(-)