|
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [Xen-devel] [RFC XTF PATCH] Pagetable Emulation testing
Signed-off-by: Andrew Cooper <andrew.cooper3@xxxxxxxxxx>
---
tests/pagetable-emulation/Makefile | 11 +
tests/pagetable-emulation/main.c | 1119 ++++++++++++++++++++++++++++++++++++
tests/pagetable-emulation/stubs.S | 94 +++
tests/pagetable-emulation/stubs.h | 25 +
4 files changed, 1249 insertions(+)
create mode 100644 tests/pagetable-emulation/Makefile
create mode 100644 tests/pagetable-emulation/main.c
create mode 100644 tests/pagetable-emulation/stubs.S
create mode 100644 tests/pagetable-emulation/stubs.h
diff --git a/tests/pagetable-emulation/Makefile
b/tests/pagetable-emulation/Makefile
new file mode 100644
index 0000000..63f232a
--- /dev/null
+++ b/tests/pagetable-emulation/Makefile
@@ -0,0 +1,11 @@
+include $(ROOT)/build/common.mk
+
+NAME := pagetable-emulation
+CATEGORY := functional
+TEST-ENVS := hvm32pse hvm32pae hvm64
+
+VARY-CFG := hap shadow
+
+obj-perenv += stubs.o main.o
+
+include $(ROOT)/build/gen.mk
diff --git a/tests/pagetable-emulation/main.c b/tests/pagetable-emulation/main.c
new file mode 100644
index 0000000..98f69e0
--- /dev/null
+++ b/tests/pagetable-emulation/main.c
@@ -0,0 +1,1119 @@
+/**
+ * @file tests/pagetable-emulation/main.c
+ * @ref test-pagetable-emulation - TODO.
+ *
+ * @page test-pagetable-emulation TODO
+ *
+ * @sa tests/pagetable-emulation/main.c
+ */
+#include <xtf.h>
+
+#include <arch/decode.h>
+#include <arch/exinfo.h>
+#include <arch/idt.h>
+#include <arch/msr-index.h>
+#include <arch/pagetable.h>
+#include <arch/processor.h>
+#include <arch/symbolic-const.h>
+
+#include "stubs.h"
+
+const char test_title[] = "Test pagetable-emulation";
+
+intpte_t l1t[PAGE_SIZE / sizeof(intpte_t)] __aligned(PAGE_SIZE);
+intpte_t l2t[PAGE_SIZE / sizeof(intpte_t)] __aligned(PAGE_SIZE);
+
+#if CONFIG_PAGING_LEVELS > 3
+intpte_t l3t[PAGE_SIZE / sizeof(intpte_t)] __aligned(PAGE_SIZE);
+#else
+extern intpte_t l3t[PAGE_SIZE / sizeof(intpte_t)];
+#endif
+
+#define LDT_SEL 0x000F /* Entry 0x8, LDT, RPL3 */
+
+#define PFEC_P X86_PFEC_PRESENT
+#define PFEC_W X86_PFEC_WRITE
+#define PFEC_U X86_PFEC_USER
+#define PFEC_R X86_PFEC_RSVD
+#define PFEC_I X86_PFEC_INSN
+#define PFEC(...) TOK_OR(PFEC_, ##__VA_ARGS__)
+
+uint64_t efer;
+unsigned long cr0, cr4;
+bool host_nx_leaked;
+bool amd_fam10_erratum;
+bool shadow_paging;
+
+static struct {
+ unsigned long va;
+ bool active;
+ bool user;
+ const char *desc;
+
+ intpte_t pteval;
+ bool pte_printed;
+} under_test;
+
+static const struct stubs
+{
+ unsigned long (*read) (unsigned long va);
+ unsigned long (*implicit) (unsigned long va);
+ unsigned long (*write) (unsigned long va);
+ unsigned long (*exec) (unsigned long va);
+ unsigned long (*read_user) (unsigned long va);
+ unsigned long (*implicit_user) (unsigned long va);
+ unsigned long (*write_user) (unsigned long va);
+ unsigned long (*exec_user) (unsigned long va);
+} regular_stubs = {
+ .read = stub_read,
+ .implicit = stub_implicit,
+ .write = stub_write,
+ .exec = stub_exec,
+ .read_user = stub_read_user,
+ .implicit_user = stub_implicit_user,
+ .write_user = stub_write_user,
+ .exec_user = stub_exec_user,
+}, force_stubs = {
+ .read = stub_force_read,
+ .implicit = stub_force_implicit,
+ .write = stub_force_write,
+ .exec = stub_force_exec,
+ .read_user = stub_force_read_user,
+ .implicit_user = stub_force_implicit_user,
+ .write_user = stub_force_write_user,
+ .exec_user = stub_force_exec_user,
+};
+
+struct mapping_info
+{
+ unsigned int level, order;
+ void *va;
+ intpte_t *pte, *fe_pte;
+ uint64_t paddr;
+
+ union
+ {
+ intpte_t *ptes[4];
+ struct
+ {
+ intpte_t *l1e, *l2e, *l3e, *l4e;
+ };
+ };
+};
+
+void flush_tlb(bool global)
+{
+ write_cr3(read_cr3());
+
+ if ( global && (cr4 & X86_CR4_PGE) )
+ {
+ write_cr4(cr4 & ~X86_CR4_PGE);
+ write_cr4(cr4);
+ }
+}
+
+bool ex_check_pf(struct cpu_regs *regs,
+ const struct extable_entry *ex)
+{
+ if ( regs->entry_vector == X86_EXC_PF )
+ {
+ unsigned long cr2 = read_cr2();
+
+ if ( (cr2 != under_test.va) &&
+ (cr2 != under_test.va + (LDT_SEL & ~7)) )
+ xtf_failure("Bad %%cr2: expected %p, got %p\n",
+ _p(under_test.va), _p(cr2));
+
+ regs->ax = EXINFO_SYM(PF, regs->error_code);
+
+ if ( ex->fixup )
+ regs->ip = ex->fixup;
+ else
+ regs->ip = *(unsigned long *)cpu_regs_sp(regs);
+
+ return true;
+ }
+
+ return false;
+}
+
+void __printf(1, 2) fail(const char *fmt, ...)
+{
+ va_list args;
+
+ if ( !under_test.active )
+ return;
+
+ if ( !under_test.pte_printed )
+ {
+ intpte_t pte = under_test.pteval;
+ printk(" PTE %"PRIpte":%s%s%s%s%s%s%s%s\n", pte,
+ pte & _PAGE_NX ? " Nx" : "",
+ pte & 0x7ff0000000000000ULL ? " Av" : "",
+ pte & ((1ULL << 52) - 1) & ~((1ULL << maxphysaddr) - 1) ? " Rs"
: "",
+ pte & _PAGE_GLOBAL ? " G" : "",
+ pte & _PAGE_PSE ? " +" : "",
+ pte & _PAGE_USER ? " U" : "",
+ pte & _PAGE_RW ? " W" : "",
+ pte & _PAGE_PRESENT ? " P" : ""
+ );
+
+ under_test.pte_printed = true;
+ }
+
+ va_start(args, fmt);
+ vprintk(fmt, args);
+ va_end(args);
+ xtf_failure(NULL);
+}
+
+bool unhandled_exception(struct cpu_regs *regs)
+{
+ fail("ERROR: Unhandled exception during %s %s\n",
+ under_test.user ? "User" : "Supervisor",
+ under_test.desc);
+ return false;
+}
+
+static void prepare_mappings(struct mapping_info *m, unsigned int level, bool
super, paddr_t paddr)
+{
+ bool pse36 = CONFIG_PAGING_LEVELS == 2 && paddr != (uint32_t)paddr;
+
+ memset(m, 0, sizeof(*m));
+
+#define PAGE_COMMON PF_SYM(AD, U, RW, P)
+ /*
+ * For 4-level paging, we use l4[1/2].
+ */
+ if ( CONFIG_PAGING_LEVELS == 4 )
+ {
+
+ pae_l4_identmap[1] = (unsigned long)l3t | PAGE_COMMON;
+ pae_l4_identmap[2] = (unsigned long)l3t | PAGE_COMMON;
+
+ l3t[0] = (unsigned long)l2t | PAGE_COMMON;
+ l3t[511] = (unsigned long)l2t | PAGE_COMMON;
+
+ l2t[0] = (unsigned long)l1t | PAGE_COMMON;
+ l2t[511] = (unsigned long)l1t | PAGE_COMMON;
+
+ l1t[0] = paddr | PAGE_COMMON;
+ l1t[511] = ((paddr - 1) & ~0xfff) | PAGE_COMMON;
+
+ m->va = _p(2ULL << PAE_L4_PT_SHIFT);
+ m->l1e = &l1t[0];
+ m->l2e = &l2t[0];
+ m->l3e = &l3t[0];
+ m->l4e = _p(&pae_l4_identmap[2]);
+ m->fe_pte = &l1t[511];
+
+ asm(_ASM_EXTABLE_HANDLER(2 << PAE_L4_PT_SHIFT, 0, ex_check_pf));
+ under_test.va = (unsigned long)m->va;
+ }
+ else if ( CONFIG_PAGING_LEVELS == 3 )
+ {
+ pae32_l3_identmap[1] = (unsigned long)l2t | _PAGE_PRESENT;
+ pae32_l3_identmap[2] = (unsigned long)l2t | _PAGE_PRESENT;
+
+ l2t[0] = (unsigned long)l1t | PAGE_COMMON;
+ l2t[511] = (unsigned long)l1t | PAGE_COMMON;
+
+ l1t[0] = paddr | PAGE_COMMON;
+ l1t[511] = ((paddr - 1) & ~0xfff) | PAGE_COMMON;
+
+ m->va = _p(2ULL << PAE_L3_PT_SHIFT);
+ m->l1e = &l1t[0];
+ m->l2e = &l2t[0];
+ m->l3e = _p(&pae32_l3_identmap[2]);
+ m->l4e = NULL;
+ m->fe_pte = &l1t[511];
+
+ asm(_ASM_EXTABLE_HANDLER(2 << PAE_L3_PT_SHIFT, 0, ex_check_pf));
+ under_test.va = (unsigned long)m->va;
+ }
+ else if ( CONFIG_PAGING_LEVELS == 2 )
+ {
+ if ( pse36 )
+ {
+ ASSERT(super);
+ ASSERT(IS_ALIGNED(paddr, MB(4)));
+
+ pse_l2_identmap[511] = fold_pse36((paddr - MB(4)) | PAGE_COMMON |
_PAGE_PSE);
+ pse_l2_identmap[512] = fold_pse36(paddr | PAGE_COMMON | _PAGE_PSE);
+ }
+ else
+ {
+ pse_l2_identmap[511] = (unsigned long)l1t | PAGE_COMMON;
+ pse_l2_identmap[512] = (unsigned long)l1t | PAGE_COMMON;
+
+ l1t[0] = paddr | PAGE_COMMON;
+ l1t[1023] = ((paddr - 1) & ~0xfff) | PAGE_COMMON;
+ }
+
+ m->va = _p(2ULL << PAE_L3_PT_SHIFT);
+ m->l1e = pse36 ? NULL : &l1t[0];
+ m->l2e = _p(&pse_l2_identmap[512]);
+ m->l3e = NULL;
+ m->l4e = NULL;
+ m->fe_pte = pse36 ? _p(&pse_l2_identmap[511]) : &l1t[1023];
+
+ asm(_ASM_EXTABLE_HANDLER(2 << PAE_L3_PT_SHIFT, 0, ex_check_pf));
+ under_test.va = (unsigned long)m->va;
+ }
+ else
+ panic("%s() PAGING_LEVELS %u not implemented yet\n",
+ __func__, CONFIG_PAGING_LEVELS);
+
+#undef PAGE_COMMON
+
+ /* Flush the TLB before trying to use the new mappings. */
+ flush_tlb(false);
+
+ /* Put FEP immediately before va, and a ret instruction at va. */
+ memcpy(m->va - 5, "\x0f\x0bxen\xc3", 6);
+ barrier();
+
+ /* Read them back, to confirm that RAM is properly in place. */
+ if ( memcmp(m->va - 5, "\x0f\x0bxen\xc3", 6) )
+ panic("Bad phys or virtual setup\n");
+
+ /* Construct the LDT at va. */
+ user_desc *ldt = m->va;
+
+ ldt[LDT_SEL >> 3] = (typeof(*ldt))INIT_GDTE_SYM(0, 0xfffff, COMMON, DATA,
DPL3, B, W);
+ gdt[GDTE_AVAIL0] = (typeof(*gdt))INIT_GDTE((unsigned long)m->va,
PAGE_SIZE, 0x82);
+#if __x86_64__
+ /* For 64bit, put the upper 32 bits of base into the adjacent entry. */
+ gdt[GDTE_AVAIL0 + 1] =
+ (user_desc){{{ .lo = ((unsigned long)m->va) >> 32, .hi = 0 }}};
+#endif
+ lldt(GDTE_AVAIL0 << 3);
+ write_fs(LDT_SEL);
+
+ m->level = level;
+ m->pte = m->ptes[level - 1];
+
+ if ( pse36 )
+ {
+ /* No l1e at all. */
+ m->order = PT_ORDER + PAGE_SHIFT;
+ m->paddr = *m->pte & ~0xfff;
+ }
+ else if ( super && (cr4 & (X86_CR4_PAE|X86_CR4_PSE)) )
+ {
+ /* Superpage in effect. */
+ m->order = ((level - 1) * PT_ORDER) + PAGE_SHIFT;
+ m->paddr = *m->l1e & ~0xfff;
+ }
+ else
+ {
+ /* Small page, or superpage not in effect. */
+ m->order = 0;
+ m->paddr = *m->pte & ~0xfff;
+ }
+}
+
+void clear_ad(struct mapping_info *m)
+{
+ unsigned int i;
+
+ for ( i = 0; i < ARRAY_SIZE(m->ptes); ++i )
+ if ( m->ptes[i] )
+ *m->ptes[i] &= ~_PAGE_AD;
+
+ invlpg(m->va);
+}
+
+enum modifier
+{
+ /* Calc. */
+ WP = 1 << 0,
+ NX = 1 << 1,
+ SMEP = 1 << 2,
+ SMAP = 1 << 3,
+ AC = 1 << 4,
+ IMP = 1 << 5,
+
+ /* Check only. */
+ WRITE = 1 << 6,
+};
+
+void check(struct mapping_info *m, exinfo_t actual, exinfo_t expected, enum
modifier mod)
+{
+ /* Check that the actual pagefault matched our expectation. */
+ if ( actual != expected )
+ {
+ const char *user_sup = under_test.user ? "User" : "Supervisor";
+ bool ac_fault = !!actual, ex_fault = !!expected;
+ char ac_ec[16], ex_ec[16];
+
+ if ( ac_fault )
+ x86_exc_decode_ec(ac_ec, ARRAY_SIZE(ac_ec),
+ X86_EXC_PF, (uint16_t)actual);
+ if ( ex_fault )
+ x86_exc_decode_ec(ex_ec, ARRAY_SIZE(ex_ec),
+ X86_EXC_PF, (uint16_t)expected);
+
+ if ( ac_fault && !ex_fault )
+ fail(" Fail: expected no fault, got #PF[%s] for %s %s\n",
+ ac_ec, user_sup, under_test.desc);
+ else if ( !ac_fault && ex_fault )
+ fail(" Fail: expected #PF[%s], got no fault for %s %s\n",
+ ex_ec, user_sup, under_test.desc);
+ else
+ fail(" Fail: expected #PF[%s], got #PF[%s] for %s %s\n",
+ ex_ec, ac_ec, user_sup, under_test.desc);
+ }
+
+ /* Check that A/D bits got updated as expected. */
+ unsigned int leaf_level =
+ m->order ? ((m->order - PAGE_SHIFT) / PT_ORDER) : 0;
+ unsigned int i; /* NB - Levels are 0-indexed. */
+
+ if ( amd_fam10_erratum )
+ {
+ /*
+ * AMD Fam10 appears to defer the setting of access bits for implicit
+ * loads. As a result, the implicit tests (which load %fs) don't
+ * necessarily observe the access bits being set on the pagewalk to
+ * the LDT.
+ *
+ * Experimentally, a serialising instruction fixes things, or a loop
+ * of 1000 nops, but so does forcing the use of the loaded segment.
+ *
+ * If this is an implicit load which didn't fault, read through %fs to
+ * force it to be loaded into the segment cache.
+ */
+ if ( (mod & IMP) && !actual )
+ asm volatile ("mov %%fs:0x1000, %0" : "=r" (i));
+ }
+
+ for ( i = 0; i < ARRAY_SIZE(m->ptes); ++i )
+ {
+ int exp_a, exp_d;
+
+ if ( !m->ptes[i] )
+ continue;
+
+ if ( CONFIG_PAGING_LEVELS == 3 && i == 2 )
+ {
+ /*
+ * 32bit PAE paging is special. The 4 PDPTE's are read from
+ * memory, cached in the processor and don't strictly count as
+ * pagetables. The A/D bits are not updated.
+ */
+ exp_a = 0;
+ exp_d = 0;
+ }
+ else if ( leaf_level > i )
+ {
+ /*
+ * Logically below a superpage. Nothing should consider this part
+ * of the pagetable structure, and neither A or D should be set.
+ */
+ exp_a = 0;
+ exp_d = 0;
+ }
+ else if ( leaf_level == i )
+ {
+ /*
+ * At a leaf page. If there was no fault, we expect A to be set,
+ * optionally D if a write occurred.
+ */
+ exp_a = (actual == 0);
+ exp_d = exp_a && (mod & WRITE);
+ }
+ else
+ {
+ /*
+ * Higher level translation structure. A processor is free to
+ * cache the partial translation or not, at its discretion, but
+ * they will never be dirty.
+ */
+ exp_a = -1;
+ exp_d = 0;
+ }
+
+ bool act_a = *m->ptes[i] & _PAGE_ACCESSED;
+ bool act_d = *m->ptes[i] & _PAGE_DIRTY;
+
+ if ( (exp_a >= 0 && exp_a != act_a) || (exp_d != act_d) )
+ fail(" Fail: expected L%u AD = %c%u, got %u%u for %s %s\n",
+ i + 1, exp_a == 1 ? '1' : exp_a == 0 ? '0' : 'x', exp_d,
+ act_a, act_d,
+ under_test.user ? "User" : "Supervisor", under_test.desc);
+ }
+
+ clear_ad(m);
+ write_fs(0);
+}
+
+exinfo_t calc(struct mapping_info *m, uint64_t new, unsigned int walk, enum
modifier mod)
+{
+ bool nx_valid = CONFIG_PAGING_LEVELS >= 3 && (host_nx_leaked || (mod &
NX));
+ bool insn_valid = nx_valid || (mod & SMEP);
+ uint64_t rsvd = ((1ULL << 52) - 1) & ~((1ULL << maxphysaddr) - 1);
+
+ /* Accumulate additional bits which are reserved. */
+ if ( !nx_valid )
+ rsvd |= _PAGE_NX;
+
+ if ( m->level == 4 )
+ rsvd |= _PAGE_PSE | (vendor_is_amd ? _PAGE_GLOBAL : 0);
+ else if ( m->level == 3 && !cpu_has_page1gb )
+ rsvd |= _PAGE_PSE;
+
+ if ( m->order )
+ {
+ if ( CONFIG_PAGING_LEVELS > 2 || !cpu_has_pse36 )
+ rsvd |= ((1ULL << m->order) - 1) & ~(_PAGE_PSE_PAT |
(_PAGE_PSE_PAT - 1));
+ else
+ rsvd |= (1ULL << 21) | fold_pse36(rsvd);
+ }
+
+ if ( CONFIG_PAGING_LEVELS == 3 )
+ rsvd |= 0x7ff0000000000000ULL;
+
+
+ if ( !insn_valid )
+ walk &= ~PFEC(I);
+
+ exinfo_t base = EXINFO_SYM(PF, walk & PFEC(I, U, W));
+
+ /* Check whether a translation exists. */
+ if ( !(new & _PAGE_PRESENT) )
+ return base;
+ base |= PFEC(P);
+
+ if ( new & rsvd )
+ return base | PFEC(R);
+
+ /* Check access rights. */
+
+ if ( (walk & PFEC(I)) && (new & _PAGE_NX) )
+ /* Insn fetch of NX page? Always fail. */
+ return base;
+
+ if ( walk & PFEC(U) )
+ {
+ /* User walk. */
+
+ if ( !(new & _PAGE_USER) )
+ /* Supervisor page? Always fail. */
+ return base;
+
+ if ( (walk & PFEC(W)) && !(new & _PAGE_RW) )
+ /* Write to a read-only page? */
+ return base;
+ }
+ else
+ {
+ /* Supervisor Walk. */
+
+ if ( new & _PAGE_USER )
+ {
+ /* User page. */
+
+ if ( (walk & PFEC(I)) && (mod & SMEP) )
+ /* Insn fetch with SMEP? */
+ return base;
+
+ if ( !(walk & PFEC(I)) && (mod & SMAP) &&
+ ((mod & IMP) || !(mod & AC)) )
+ /* data fetch with SMAP and (Implicit or !AC)? */
+ return base;
+ }
+
+ if ( (walk & PFEC(W)) && !(new & _PAGE_RW) && (mod & WP) )
+ /* Write to a read-only page with WP active? */
+ return base;
+ }
+
+ /* Should succeed. */
+ return 0;
+}
+
+void test_pte(const struct stubs *stubs, struct mapping_info *m, uint64_t
overlay)
+{
+ uint64_t new = m->paddr | overlay;
+ bool user = false;
+
+ under_test.pteval = *m->pte = new;
+ under_test.pte_printed = false;
+ clear_ad(m);
+
+ under_test.active = true;
+
+ for ( ; ; user = true )
+ {
+ unsigned int base = user ? PFEC(U) : 0;
+
+#define CALL(fn, va) \
+ (user ? exec_user_param(fn ## _user, (unsigned long)(va)) \
+ : fn((unsigned long)(va)))
+
+ under_test.user = user;
+
+ /* Map the exec FEP stub with suitable permissions. */
+ if ( stubs == &force_stubs )
+ {
+ *m->fe_pte &= ~_PAGE_USER;
+ if ( user )
+ *m->fe_pte |= _PAGE_USER;
+ invlpg(m->va - 5);
+ }
+
+ /* Basic read. */
+ under_test.desc = "Read";
+ check(m, CALL(stubs->read, m->va),
+ calc(m, new, base | PFEC(), 0),
+ 0);
+
+ /* Implicit read (always supervisor). `mov $LDT_SEL, %fs`. */
+ under_test.desc = "Read, Implicit";
+ check(m, CALL(stubs->implicit, _p(LDT_SEL)),
+ calc(m, new, PFEC(), IMP),
+ IMP);
+
+ /* Read, SMAP. */
+ if ( cpu_has_smap )
+ {
+ write_cr4(cr4 | X86_CR4_SMAP);
+
+ asm volatile ("clac");
+
+ under_test.desc = "Read, SMAP AC0";
+ check(m, CALL(stubs->read, m->va),
+ calc(m, new, base | PFEC(), SMAP),
+ 0);
+
+ under_test.desc = "Read, Implicit, SMAP AC0";
+ check(m, CALL(stubs->implicit, _p(LDT_SEL)),
+ calc(m, new, PFEC(), SMAP | IMP),
+ IMP);
+
+ asm volatile ("stac");
+
+ under_test.desc = "Read, SMAP AC1";
+ check(m, CALL(stubs->read, m->va),
+ calc(m, new, base | PFEC(), SMAP | AC),
+ 0);
+
+ if ( !user && !shadow_paging )
+ {
+ /*
+ * This corner case loses information in the pagefault error
+ * code, which the shadow pagetable logic in the hypervisor
+ * can't account for.
+ *
+ * Executing this test in supervisor mode with shadow paging
+ * will livelock with no further progress made.
+ */
+ under_test.desc = "Read, Implicit, SMAP AC1";
+ check(m, CALL(stubs->implicit, _p(LDT_SEL)),
+ calc(m, new, PFEC(), SMAP | AC | IMP),
+ IMP);
+ }
+
+ asm volatile ("clac");
+
+ write_cr4(cr4);
+ }
+
+ /* Basic write. */
+ under_test.desc = "Write";
+ check(m, CALL(stubs->write, m->va),
+ calc(m, new, base | PFEC(W), 0),
+ WRITE);
+
+ /* Write, WP. */
+ write_cr0(cr0 | X86_CR0_WP);
+
+ under_test.desc = "Write, WP";
+ check(m, CALL(stubs->write, m->va),
+ calc(m, new, base | PFEC(W), WP),
+ WRITE);
+
+ write_cr0(cr0);
+
+ /* Write, SMAP. */
+ if ( cpu_has_smap )
+ {
+ write_cr4(cr4 | X86_CR4_SMAP);
+
+ asm volatile ("clac");
+
+ under_test.desc = "Write, SMAP AC0";
+ check(m, CALL(stubs->write, m->va),
+ calc(m, new, base | PFEC(W), SMAP),
+ WRITE);
+
+ asm volatile ("stac");
+
+ under_test.desc = "Write, SMAP AC1";
+ check(m, CALL(stubs->write, m->va),
+ calc(m, new, base | PFEC(W), SMAP | AC),
+ WRITE);
+
+ asm volatile ("clac");
+
+
+ /* Write, SMAP + WP. */
+ write_cr0(cr0 | X86_CR0_WP);
+
+ under_test.desc = "Write, SMAP AC0, WP";
+ check(m, CALL(stubs->write, m->va),
+ calc(m, new, base | PFEC(W), SMAP | WP),
+ WRITE);
+
+ asm volatile ("stac");
+
+ under_test.desc = "Write, SMAP AC1, WP";
+ check(m, CALL(stubs->write, m->va),
+ calc(m, new, base | PFEC(W), SMAP | AC | WP),
+ WRITE);
+
+ asm volatile ("clac");
+
+ write_cr0(cr0);
+ write_cr4(cr4);
+ }
+
+
+ /* Basic exec. */
+ under_test.desc = "Exec";
+ check(m, CALL(stubs->exec, m->va),
+ calc(m, new, base | PFEC(I), 0),
+ 0);
+
+ /* Exec, SMEP. */
+ if ( cpu_has_smep )
+ {
+ write_cr4(cr4 | X86_CR4_SMEP);
+
+ under_test.desc = "Exec, SMEP";
+ check(m, CALL(stubs->exec, m->va),
+ calc(m, new, base | PFEC(I), SMEP),
+ 0);
+
+ write_cr4(cr4);
+ }
+
+ /* Exec, NX. */
+ if ( cpu_has_nx )
+ {
+ wrmsr(MSR_EFER, efer | EFER_NXE);
+
+ under_test.desc = "Exec, NX";
+ check(m, CALL(stubs->exec, m->va),
+ calc(m, new, base | PFEC(I), NX),
+ 0);
+
+ /* Exec, NX and SMEP. */
+ if ( cpu_has_smep )
+ {
+ write_cr4(cr4 | X86_CR4_SMEP);
+
+ under_test.desc = "Exec, NX, SMEP";
+ check(m, CALL(stubs->exec, m->va),
+ calc(m, new, base | PFEC(I), NX | SMEP),
+ 0);
+
+ write_cr4(cr4);
+ }
+
+ wrmsr(MSR_EFER, efer);
+ }
+
+ if ( user )
+ break;
+ }
+
+#undef CALL
+
+ under_test.active = false;
+}
+
+void run_test(const struct stubs *stubs, unsigned int level, bool super,
paddr_t paddr)
+{
+ const uint64_t base = super ? _PAGE_PSE : 0;
+ struct mapping_info m;
+ struct
+ {
+ bool cond;
+ uint64_t bit;
+ } trans_bits[] =
+ {
+ { 1, 0 },
+ { 1, _PAGE_GLOBAL },
+
+#if CONFIG_PAGING_LEVELS == 2
+
+ { super && (cr4 & X86_CR4_PSE) && !cpu_has_pse36, 1ULL << 13 },
+ { super && (cr4 & X86_CR4_PSE) && !cpu_has_pse36, 1ULL << 21 },
+
+ { super && paddr != (uint32_t)paddr, 1ULL << 21 },
+
+ { super && paddr != (uint32_t)paddr && maxphysaddr < 39,
+ fold_pse36(1ULL << 39) },
+ { super && paddr != (uint32_t)paddr && maxphysaddr < 38,
+ fold_pse36(1ULL << maxphysaddr) },
+
+#else
+
+ { super, 1ULL << 13 },
+ { super, PAGE_SIZE << (((level - 1) * PT_ORDER) - 1) },
+
+ { maxphysaddr < 50, 1ULL << maxphysaddr },
+ { maxphysaddr < 51, 1ULL << 51 },
+ { 1, 1ULL << 52 },
+ { 1, _PAGE_NX },
+
+#endif
+ };
+ uint32_t ar_bits[] =
+ {
+ 0,
+ PF_SYM(P),
+ PF_SYM(RW, P),
+ PF_SYM(U, P),
+ PF_SYM(U, RW, P),
+ };
+ unsigned int trans, ar;
+
+ printk("Test%s L%ue%s%s\n",
+ (stubs == &force_stubs) ? " emulated" : "",
+ level, super ? " Superpage" : "",
+ CONFIG_PAGING_LEVELS == 2 && !(cr4 & X86_CR4_PSE) ? " (No PSE)" : ""
+ );
+
+ prepare_mappings(&m, level, super, paddr);
+
+ for ( ar = 0; ar < ARRAY_SIZE(ar_bits); ++ar )
+ {
+ for ( trans = 0; trans < ARRAY_SIZE(trans_bits); ++trans )
+ {
+ if ( trans_bits[trans].cond )
+ test_pte(stubs, &m, base | trans_bits[trans].bit |
ar_bits[ar]);
+ }
+ }
+}
+
+static void shatter_console_superpage(void)
+{
+ /*
+ * Shatter the superpage mapping the PV console. We want to test with
+ * CR4.PSE disabled, at which point superpages stop working.
+ */
+ uint64_t raw_pfn;
+
+ if ( hvm_get_param(HVM_PARAM_CONSOLE_PFN, &raw_pfn) == 0 )
+ {
+ unsigned int l2o = l2_table_offset(raw_pfn << PAGE_SHIFT);
+
+ if ( (l2_identmap[l2o] & PF_SYM(PSE, P)) == PF_SYM(PSE, P) )
+ {
+ static intpte_t conl1t[L1_PT_ENTRIES] __aligned(PAGE_SIZE);
+ paddr_t base_gfn = l2_identmap[l2o] >> PAGE_SHIFT;
+ unsigned int i;
+
+ for ( i = 0; i < ARRAY_SIZE(conl1t); ++i )
+ conl1t[i] = pte_from_gfn(base_gfn + i, PF_SYM(AD, RW, P));
+
+ l2_identmap[l2o] = pte_from_virt(conl1t, PF_SYM(AD, U, RW, P));
+ }
+ }
+
+ flush_tlb(true);
+}
+
+static void populate_physmap_around(paddr_t paddr)
+{
+ unsigned long extents[] =
+ {
+ (paddr >> PAGE_SHIFT) - 1,
+ paddr >> PAGE_SHIFT,
+ };
+ struct xen_memory_reservation mr =
+ {
+ .extent_start = extents,
+ .nr_extents = ARRAY_SIZE(extents),
+ .domid = DOMID_SELF,
+ };
+ int rc = hypercall_memory_op(XENMEM_populate_physmap, &mr);
+
+ if ( rc != ARRAY_SIZE(extents) )
+ panic("Failed populate_physmap: %d\n", rc);
+}
+
+static void nx_leak_check(const struct stubs *stubs)
+{
+ struct mapping_info m;
+
+ /*
+ * Always use RAM at 12k, which is present and encodable even in 2level
+ * paging.
+ */
+ prepare_mappings(&m, 1, false, 0x3000);
+
+ *m.pte &= ~_PAGE_PRESENT;
+ invlpg(m.va);
+
+ exinfo_t res = stubs->exec((unsigned long)m.va);
+
+ if ( !res || exinfo_vec(res) != X86_EXC_PF )
+ panic("Testing for NX leak didn't generate #PF\n");
+
+ host_nx_leaked = exinfo_ec(res) & PFEC(I);
+
+ printk("Host NX %sleaked%s\n",
+ host_nx_leaked ? "" : "not ",
+ stubs == &force_stubs ? " in emulation" : "");
+}
+
+void run_tests(const struct stubs *stubs, paddr_t paddr)
+{
+ nx_leak_check(stubs);
+
+ if ( CONFIG_PAGING_LEVELS == 2 )
+ {
+ if ( paddr == (uint32_t)paddr )
+ {
+ /*
+ * If paddr fits within 32bits, run all the tests.
+ */
+ run_test(stubs, 1, false, paddr);
+
+ cr4 &= ~X86_CR4_PSE;
+ write_cr4(cr4);
+
+ run_test(stubs, 2, false, paddr);
+ run_test(stubs, 2, true, paddr);
+
+ cr4 |= X86_CR4_PSE;
+ write_cr4(cr4);
+
+ run_test(stubs, 2, false, paddr);
+ run_test(stubs, 2, true, paddr);
+ }
+ else if ( cpu_has_pse36 )
+ /*
+ * Otherwise, paddrs above 32bits can only be encoded with pse36
+ * superpages.
+ */
+ run_test(stubs, 2, true, paddr);
+ else
+ printk("No applicable tests\n");
+ }
+ else
+ {
+ run_test(stubs, 1, false, paddr);
+ run_test(stubs, 2, false, paddr);
+ run_test(stubs, 2, true, paddr);
+
+ if ( CONFIG_PAGING_LEVELS > 3 )
+ {
+ run_test(stubs, 3, false, paddr);
+ run_test(stubs, 3, true, paddr);
+ run_test(stubs, 4, false, paddr);
+ run_test(stubs, 4, true, paddr);
+ }
+ }
+}
+
+static void probe_shadow_paging(void)
+{
+ /*
+ * Shadow paging vs hap should be indistinguishable to guests.
+ *
+ * Shadow paging doesn't support PSE36, so this feature is nominally
+ * hidden from guests.
+ *
+ * Luckily(?), because old versions of HyperV refuse to boot if they don't
+ * see PSE36, it is purposefully leaked once PAE is enabled to keep HyperV
+ * happy.
+ *
+ * As a result, our shadow paging heuristic is that the visibility of the
+ * PSE36 feature changes as we flip in and out of PAE paging mode.
+ */
+ unsigned long tmp;
+ unsigned int _1d;
+
+ switch ( CONFIG_PAGING_LEVELS )
+ {
+ case 2:
+ write_cr0(cr0 & ~X86_CR0_PG);
+
+ write_cr3((unsigned long)pae32_l3_identmap);
+
+ write_cr4(cr4 | X86_CR4_PAE);
+ write_cr0(cr0);
+
+ _1d = cpuid_edx(1);
+
+ write_cr0(cr0 & ~X86_CR0_PG);
+ write_cr4(cr4 & ~X86_CR4_PAE);
+
+ write_cr3((unsigned long)cr3_target);
+
+ write_cr0(cr0);
+ break;
+
+ case 3:
+ write_cr0(cr0 & ~X86_CR0_PG);
+ write_cr4(cr4 & ~X86_CR4_PAE);
+
+ write_cr3((unsigned long)pse_l2_identmap);
+
+ write_cr0(cr0);
+
+ _1d = cpuid_edx(1);
+
+ write_cr0(cr0 & ~X86_CR0_PG);
+
+ write_cr3((unsigned long)cr3_target);
+
+ write_cr4(cr4);
+ write_cr0(cr0);
+ break;
+
+ case 4:
+ asm volatile (/* Drop into a 32bit compat code segment. */
+ "push $%c[cs32];"
+ "push $1f;"
+ "lretq; 1:"
+
+ ".code32;"
+ "start_32bit:;"
+
+ /* Flip %CR4.PAE */
+ "mov %k[cr0], %%cr0;"
+ "mov %k[cr4], %%cr4;"
+ "mov $pse_l2_identmap, %k[cr3];"
+ "mov %k[cr3], %%cr3;"
+ "rdmsr;"
+ "and $~" STR(EFER_LME) ", %k[a];"
+ "wrmsr;"
+ "or $" STR(X86_CR0_PG) ", %k[cr0];"
+ "mov %k[cr0], %%cr0;"
+
+ "mov $1, %k[a];"
+ "cpuid;"
+ "mov %k[d], %k[b];"
+
+ /* Flip %CR4.PAE back. */
+ "and $~" STR(X86_CR0_PG) ", %k[cr0];"
+ "mov %k[cr0], %%cr0;"
+ "mov $" STR(MSR_EFER) ", %k[c];"
+ "rdmsr;"
+ "or $" STR(EFER_LME) ", %k[a];"
+ "wrmsr;"
+ "or $" STR(X86_CR4_PAE) ", %k[cr4];"
+ "mov %k[cr4], %%cr4;"
+ "mov $pae_l4_identmap, %k[cr3];"
+ "mov %k[cr3], %%cr3;"
+ "or $" STR(X86_CR0_PG) ", %k[cr0];"
+ "mov %k[cr0], %%cr0;"
+
+ /* Return to 64bit. */
+ "ljmpl $%c[cs], $1f;"
+ "end_32bit:;"
+ ".code64; 1:"
+ : [a] "=&a" (tmp),
+ [b] "=&b" (_1d),
+ [c] "=&c" (tmp),
+ [d] "=&d" (tmp),
+ [cr3] "=&r" (tmp)
+ : "c" (MSR_EFER),
+ [cr0] "R" (cr0 & ~X86_CR0_PG),
+ [cr4] "R" (cr4 & ~X86_CR4_PAE),
+ [cs32] "i" (GDTE_CS32_DPL0 * 8),
+ [cs] "i" (__KERN_CS));
+ break;
+ }
+
+ shadow_paging = cpu_has_pse36 ^ !!(_1d & cpufeat_mask(X86_FEATURE_PSE36));
+
+ printk(" Paging mode heuristic: %s\n", shadow_paging ? "Shadow" : "Hap");
+}
+
+void test_main(void)
+{
+ xtf_unhandled_exception_hook = unhandled_exception;
+
+ printk(" Info: Vendor %s, Family %u, Model %u, Stepping %u, paddr %u,
vaddr %u\n"
+ " Features:%s%s%s%s%s%s%s%s%s%s%s\n",
+ x86_vendor_name(x86_vendor),
+ x86_family, x86_model, x86_stepping, maxphysaddr, maxvirtaddr,
+ cpu_has_pse ? " PSE" : "",
+ cpu_has_pae ? " PAE" : "",
+ cpu_has_pge ? " PGE" : "",
+ cpu_has_pat ? " PAT" : "",
+ cpu_has_pse36 ? " PSE36" : "",
+ cpu_has_pcid ? " PCID" : "",
+ cpu_has_nx ? " NX" : "",
+ cpu_has_page1gb ? " PAGE1G" : "",
+ cpu_has_smep ? " SMEP" : "",
+ cpu_has_smap ? " SMAP" : "",
+ cpu_has_pku ? " PKU" : ""
+ );
+
+ if ( CONFIG_PAGING_LEVELS == 2 )
+ shatter_console_superpage();
+
+ if ( !vendor_is_intel && !vendor_is_amd )
+ xtf_warning("Unknown CPU vendor. Something might go wrong\n");
+ if ( !xtf_has_fep )
+ xtf_skip("FEP support not detected - some tests will be skipped\n");
+
+ if ( vendor_is_amd && x86_family == 0x10 )
+ {
+ amd_fam10_erratum = true;
+ printk(" Working around suspected AMD Fam10h erratum\n");
+ }
+
+ /* Sanitise environment. */
+ efer = rdmsr(MSR_EFER) & ~EFER_NXE;
+ wrmsr(MSR_EFER, efer);
+ cr0 = read_cr0() & ~X86_CR0_WP;
+ write_cr0(cr0);
+ cr4 = read_cr4() & ~(X86_CR4_SMEP | X86_CR4_SMAP);
+ write_cr4(cr4);
+
+ probe_shadow_paging();
+
+ unsigned int i;
+ paddr_t paddrs[] =
+ {
+ GB(1),
+ 1ULL << (min(maxphysaddr,
+ CONFIG_PAGING_LEVELS == 2
+ ? 40U
+ : (BITS_PER_LONG + PAGE_SHIFT)) - 1),
+ };
+
+ for ( i = 0; i < ARRAY_SIZE(paddrs); ++i )
+ {
+ paddr_t paddr = paddrs[i];
+
+ printk("Using paddr 0x%"PRIpaddr"\n", paddr);
+ populate_physmap_around(paddr);
+
+ run_tests(®ular_stubs, paddr);
+
+ if ( xtf_has_fep )
+ run_tests(&force_stubs, paddr);
+ }
+
+ xtf_success(NULL);
+}
+
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "BSD"
+ * c-basic-offset: 4
+ * tab-width: 4
+ * indent-tabs-mode: nil
+ * End:
+ */
diff --git a/tests/pagetable-emulation/stubs.S
b/tests/pagetable-emulation/stubs.S
new file mode 100644
index 0000000..068b712
--- /dev/null
+++ b/tests/pagetable-emulation/stubs.S
@@ -0,0 +1,94 @@
+#include <xtf/extable.h>
+#include <xtf/asm_macros.h>
+#include <xen/arch-x86/xen.h>
+
+#include <arch/idt.h>
+#include <arch/page.h>
+#include <arch/processor.h>
+#include <arch/segment.h>
+
+#ifdef __i386__
+# define REG edx
+# define REG8 dl
+#else
+# define REG rdi
+# define REG8 dil
+#endif
+
+.macro GEN_SINGLE name, type, force=0
+
+ENTRY(stub_\name)
+
+ xor %eax, %eax
+
+#ifdef __i386__
+ mov 4(%esp), %REG
+#endif
+
+ .ifnc \type, X
+ .if \force
+ _ASM_XEN_FEP
+ .endif
+ .endif
+
+.Lstub_\name\()_fault:
+
+ .ifc \type, R
+ /* Read (and trash) the pointer. */
+ mov (%REG), %REG8
+ .endif
+
+ .ifc \type, I
+ /* Load %fs with the selector in %REG. Causes implicit GDT/LDT
access. */
+ mov %REG, %fs
+ .endif
+
+ .ifc \type, W
+ /* Write `ret` to the pointer. */
+ movb $0xc3, (%REG)
+ .endif
+
+ .ifc \type, X
+ /* Call the pointer. */
+
+ .if \force
+ /* If FORCE, move the pointer to include the force emulation prefix. */
+ sub $5, %REG
+ .endif
+
+ call *%REG
+ .endif
+
+.Lstub_\name\()_fixup:
+ ret
+
+ENDFUNC(stub_\name)
+
+ .ifnc \type, X
+ _ASM_EXTABLE_HANDLER(.Lstub_\name\()_fault, \
+ .Lstub_\name\()_fixup, \
+ ex_check_pf)
+ .endif
+.endm
+
+GEN_SINGLE read R
+GEN_SINGLE implicit I
+GEN_SINGLE write W
+GEN_SINGLE exec X
+GEN_SINGLE force_read R 1
+GEN_SINGLE force_implicit I 1
+GEN_SINGLE force_write W 1
+GEN_SINGLE force_exec X 1
+
+.pushsection .text.user, "ax", @progbits
+
+GEN_SINGLE read_user R
+GEN_SINGLE implicit_user I
+GEN_SINGLE write_user W
+GEN_SINGLE exec_user X
+GEN_SINGLE force_read_user R 1
+GEN_SINGLE force_implicit_user I 1
+GEN_SINGLE force_write_user W 1
+GEN_SINGLE force_exec_user X 1
+
+.popsection
diff --git a/tests/pagetable-emulation/stubs.h
b/tests/pagetable-emulation/stubs.h
new file mode 100644
index 0000000..00db033
--- /dev/null
+++ b/tests/pagetable-emulation/stubs.h
@@ -0,0 +1,25 @@
+#ifndef _LOWLEVEL_H_
+#define _LOWLEVEL_H_
+
+unsigned long stub_read(unsigned long va);
+unsigned long stub_implicit(unsigned long sel); /* unsigned long sel */
+unsigned long stub_write(unsigned long va);
+unsigned long stub_exec(unsigned long va);
+
+unsigned long stub_force_read(unsigned long va);
+unsigned long stub_force_implicit(unsigned long sel); /* unsigned long sel */
+unsigned long stub_force_write(unsigned long va);
+unsigned long stub_force_exec(unsigned long va);
+
+unsigned long stub_read_user(unsigned long va);
+unsigned long stub_implicit_user(unsigned long sel); /* unsigned long sel */
+unsigned long stub_write_user(unsigned long va);
+unsigned long stub_exec_user(unsigned long va);
+
+unsigned long stub_force_read_user(unsigned long va);
+unsigned long stub_force_implicit_user(unsigned long sel); /* unsigned long
sel */
+unsigned long stub_force_write_user(unsigned long va);
+unsigned long stub_force_exec_user(unsigned long va);
+
+#endif
+
--
2.1.4
_______________________________________________
Xen-devel mailing list
Xen-devel@xxxxxxxxxxxxx
https://lists.xen.org/xen-devel
|
![]() |
Lists.xenproject.org is hosted with RackSpace, monitoring our |