]> xenbits.xensource.com Git - people/andrewcoop/xen-test-framework.git/commitdiff
Functional test for an NMI-triggered task switch which increases privilege
authorAndrew Cooper <andrew.cooper3@citrix.com>
Thu, 1 Jun 2017 11:15:22 +0000 (12:15 +0100)
committerAndrew Cooper <andrew.cooper3@citrix.com>
Thu, 6 Jul 2017 18:42:04 +0000 (19:42 +0100)
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
arch/x86/include/arch/desc.h
docs/all-tests.dox
tests/nmi-taskswitch-priv/Makefile [new file with mode: 0644]
tests/nmi-taskswitch-priv/main.c [new file with mode: 0644]

index 5613450c25c60ba09deb9f4e64cc9a76fadf5ea9..f722d82e1e8c8397b83750a6d62e01bf6a0c55df 100644 (file)
@@ -139,6 +139,30 @@ extern desc_ptr  idt_ptr;
 extern env_tss tss;
 #endif
 
+static inline unsigned long user_desc_base(const user_desc *d)
+{
+    unsigned long base = (d->base0 |
+                          ((unsigned long)d->base1) << 16 |
+                          ((unsigned long)d->base2) << 24 );
+#ifdef __x86_64__
+    /* Long mode system segments use adjacent slot. */
+    if ( !d->s )
+        base |= ((unsigned long)d[1].lo) << 32;
+#endif
+
+    return base;
+}
+
+static inline unsigned int user_desc_limit(const user_desc *d)
+{
+    unsigned int limit = (d->limit0 |
+                          ((unsigned int)d->limit1) << 16);
+    if ( d->g )
+        limit = limit << 12 | 0xfff;
+
+    return limit;
+}
+
 #endif /* XTF_X86_DESC_H */
 
 /*
index 870ad0aa76c49fb57623ae747a8346703b1d4e7a..01a7a572f472450fa1b963d4334aaca7436d5c3f 100644 (file)
@@ -26,6 +26,8 @@ and functionality.
 
 @subpage test-livepatch-priv-check - Live Patch Privilege Check.
 
+@subpage test-nmi-taskswitch-priv - Task Gate handling of interrupts.
+
 @subpage test-pv-iopl - IOPL emulation for PV guests.
 
 @subpage test-swint-emulation - Software interrupt emulation for HVM guests.
diff --git a/tests/nmi-taskswitch-priv/Makefile b/tests/nmi-taskswitch-priv/Makefile
new file mode 100644 (file)
index 0000000..22d61e8
--- /dev/null
@@ -0,0 +1,9 @@
+include $(ROOT)/build/common.mk
+
+NAME      := nmi-taskswitch-priv
+CATEGORY  := functional
+TEST-ENVS := hvm32pae
+
+obj-perenv += main.o
+
+include $(ROOT)/build/gen.mk
diff --git a/tests/nmi-taskswitch-priv/main.c b/tests/nmi-taskswitch-priv/main.c
new file mode 100644 (file)
index 0000000..3ea0f9e
--- /dev/null
@@ -0,0 +1,186 @@
+/**
+ * @file tests/nmi-taskswitch-priv/main.c
+ * @ref test-nmi-taskswitch-priv
+ *
+ * @page test-nmi-taskswitch-priv NMI Taskswitch with increasing privilege.
+ *
+ * 32bit versions of Windows use a Task Gate for handling @#NMI, and use NMI
+ * IPIs between vCPUs in some circumstances.
+ *
+ * A regression was introduced during the Xen 4.9 development cycle which
+ * caused a task switch which changed CPL to corrupt VMCB/VMCS state and
+ * result in a vmentry failure.  To make this regression test reliable, it is
+ * simplified to a single vCPU using a self-NMI IPI to trigger the task
+ * switch.
+ *
+ * **Notes for people writing reusable x86 tasks:**
+ *
+ * %x86 Tasks and hardware task switching are basically unused in practice.
+ * As a result, there is precious little information online.  The following
+ * issues caught me by surprise while developing this code.  Some are now
+ * obvious in retrospect, while some are not.
+ *
+ * - Entering a task doesn't push an exception frame, although an error code
+ *   will be pushed if applicable.  To exit from the task using iret, you must
+ *   either land under a prepared iret frame with eflags.NT set, or have the
+ *   handler construct one itself.
+ *
+ * - Exiting a task overwrites all GPR state in the TSS, which gets recalled
+ *   on the subsequent entry.  For the task to be reusable, the iret to leave
+ *   it must be immediately before the entry point.
+ *
+ * - The state saved on a successful task exit will be the @%eip following the
+ *   iret instruction, but the @%esp at the start of the instruction;
+ *   **despite the impression by the SDM pseudocode**.  You either need to
+ *   land under a prepared frame, or leave 3 slots of room above the
+ *   entrypoint to construct a suitable frame.
+ *
+ * @see tests/nmi-taskswitch-priv/main.c
+ */
+#include <xtf.h>
+
+#include <arch/apic.h>
+
+const char test_title[] = "Test nmi-taskswitch-priv";
+
+bool test_wants_user_mappings = true;
+
+static uint32_t nmi_stack[PAGE_SIZE / sizeof(uint32_t)] __page_aligned_data =
+{
+    [ ARRAY_SIZE(nmi_stack) - 8 ] =
+    0xdeadbeef,                         /* %eip    */
+    0x0000dead,                         /* %cs     */
+    X86_EFLAGS_NT | X86_EFLAGS_MBS,     /* eflags  */
+    0xdeadbeef,                         /* %esp    */
+    0x0000dead,                         /* %ss     */
+    0xc2c2c2c2, 0xc2c2c2c2, 0xc2c2c2c2, /* poision */
+};
+
+void entry_NMI_task(void);
+asm("exit_NMI_task:"
+    "iret;"
+    "entry_NMI_task:;"
+
+    "push $0;"
+    "push $" STR(X86_EXC_NMI) ";"
+
+    "push %edi;"
+    "push %esi;"
+    "push %edx;"
+    "push %ecx;"
+    "push %eax;"
+    "push %ebx;"
+    "push %ebp;"
+
+    "push %esp;"
+    "call do_exception;"
+    "add $1*4, %esp;"
+
+    "pop %ebp;"
+    "pop %ebx;"
+    "pop %eax;"
+    "pop %ecx;"
+    "pop %edx;"
+    "pop %esi;"
+    "pop %edi;"
+
+    "add $2*4, %esp;" /* entry_vector/error_code. */
+
+    "jmp exit_NMI_task;");
+
+
+static env_tss nmi_tss __aligned(16) =
+{
+    .eip    = _u(entry_NMI_task),
+    .cs     = __KERN_CS,
+    .eflags = X86_EFLAGS_MBS,
+    .esp    = _u(&nmi_stack[ARRAY_SIZE(nmi_stack) - 8]),
+    .ss     = __KERN_DS,
+
+    .ds     = __USER_DS,
+    .es     = __USER_DS,
+    .fs     = __USER_DS,
+    .gs     = __USER_DS,
+
+    .cr3    = _u(cr3_target),
+    .iopb   = X86_TSS_INVALID_IO_BITMAP,
+};
+
+static bool unhandled_exception(struct cpu_regs *regs)
+{
+    if ( regs->entry_vector != X86_EXC_NMI )
+        return false;
+
+    unsigned int curr_ts = str();
+    if ( curr_ts != GDTE_AVAIL0 * 8 )
+        xtf_failure("Fail: Running NMI handler with unexpected %%tr\n"
+                    "  Expected %04x, got %04x\n", GDTE_AVAIL0 * 8, curr_ts);
+
+    /* Follow the TSS link pointer to get the interrupted state. */
+    env_tss *t = _p(user_desc_base(&gdt[nmi_tss.link >> 3]));
+
+    printk("  NMI at %04x:%p, stack %04x:%p\n",
+           t->cs, _p(t->eip), t->ss, _p(t->esp));
+
+    return true;
+}
+
+static void __user_text user_inject_nmi(void)
+{
+    apic_mmio_icr_write(APIC_DEST_SELF | APIC_DM_NMI);
+}
+
+void test_main(void)
+{
+    unsigned int curr_ts;
+    int rc = apic_init(APIC_MODE_XAPIC);
+
+    if ( rc )
+        return xtf_error("Error: Unable to set up xapic mode: %d\n", rc);
+
+    /*
+     * Set up NMI handling to be a task gate.
+     */
+    xtf_unhandled_exception_hook = unhandled_exception;
+    gdt[GDTE_AVAIL0] = (user_desc)INIT_GDTE(_u(&nmi_tss), 0x67, 0x89);
+    pack_gate(&idt[X86_EXC_NMI], 5, GDTE_AVAIL0 * 8, 0, 0, 0);
+    barrier();
+
+    /*
+     * Send an NMI from supervisor mode, checking that we task switch back to
+     * the expected TSS.
+     */
+    printk("First self-nmi, from supervisor mode\n");
+    apic_mmio_icr_write(APIC_DEST_SELF | APIC_DM_NMI);
+
+    if ( (curr_ts = str()) != (GDTE_TSS * 8) )
+        xtf_failure("Fail: Running NMI handler with unexpected %%tr\n"
+                    "  Expected %04x, got %04x\n", (GDTE_TSS * 8), curr_ts);
+
+    /*
+     * Send an NMI from user mode, and again check that we are in the expected
+     * task.
+     */
+    printk("Second self-nmi, from user mode\n");
+    exec_user_void(user_inject_nmi);
+
+    if ( (curr_ts = str()) != (GDTE_TSS * 8) )
+        xtf_failure("Fail: Running NMI handler with unexpected %%tr\n"
+                    "  Expected %04x, got %04x\n", (GDTE_TSS * 8), curr_ts);
+
+    /*
+     * If Xen is still alive, it handled the user=>supervisor task switch
+     * properly.
+     */
+    xtf_success(NULL);
+}
+
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "BSD"
+ * c-basic-offset: 4
+ * tab-width: 4
+ * indent-tabs-mode: nil
+ * End:
+ */