From: Andrew Cooper Date: Thu, 1 Jun 2017 11:15:22 +0000 (+0100) Subject: Functional test for an NMI-triggered task switch which increases privilege X-Git-Url: http://xenbits.xensource.com/gitweb?a=commitdiff_plain;h=48efc1044ba1348ad21db131ffc08bece70db6f9;p=people%2Fandrewcoop%2Fxen-test-framework.git Functional test for an NMI-triggered task switch which increases privilege Signed-off-by: Andrew Cooper --- diff --git a/arch/x86/include/arch/desc.h b/arch/x86/include/arch/desc.h index 5613450..f722d82 100644 --- a/arch/x86/include/arch/desc.h +++ b/arch/x86/include/arch/desc.h @@ -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 */ /* diff --git a/docs/all-tests.dox b/docs/all-tests.dox index 870ad0a..01a7a57 100644 --- a/docs/all-tests.dox +++ b/docs/all-tests.dox @@ -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 index 0000000..22d61e8 --- /dev/null +++ b/tests/nmi-taskswitch-priv/Makefile @@ -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 index 0000000..3ea0f9e --- /dev/null +++ b/tests/nmi-taskswitch-priv/main.c @@ -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 + +#include + +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: + */