From: Andrew Cooper Date: Sat, 19 Mar 2016 15:36:16 +0000 (+0000) Subject: PV IOPL emulation testing X-Git-Url: http://xenbits.xensource.com/gitweb?a=commitdiff_plain;h=7040e27af4371db8f4fb308eba9079a2ba0d01f2;p=people%2Froyger%2Fxen-test-framework.git PV IOPL emulation testing A test to verify correct behaviour of vIOPL shadowing in Xen. Signed-off-by: Andrew Cooper --- diff --git a/docs/all-tests.dox b/docs/all-tests.dox index 537e9e6..dae450d 100644 --- a/docs/all-tests.dox +++ b/docs/all-tests.dox @@ -16,6 +16,8 @@ and functionality. @section index-functional Functional tests +@subpage test-pv-iopl - IOPL emulation for PV guests. + @subpage test-swint-emulation - Software interrupt emulation for HVM guests. Coveres XSA-106 and XSA-156. diff --git a/include/xen/physdev.h b/include/xen/physdev.h new file mode 100644 index 0000000..9245535 --- /dev/null +++ b/include/xen/physdev.h @@ -0,0 +1,30 @@ +/* + * Xen public physdev_op hypercall interface + */ + +#ifndef XEN_PUBLIC_PHYSDEV_H +#define XEN_PUBLIC_PHYSDEV_H + +/* + * Set the current VCPU's I/O privilege level. + * @arg == pointer to physdev_set_iopl structure. + */ +#define PHYSDEVOP_set_iopl 6 +#ifndef __ASSEMBLY__ +struct physdev_set_iopl { + /* IN */ + uint32_t iopl; +}; +#endif + +#endif /* XEN_PUBLIC_PHYSDEV_H */ + +/* + * Local variables: + * mode: C + * c-file-style: "BSD" + * c-basic-offset: 4 + * tab-width: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/include/xtf/hypercall.h b/include/xtf/hypercall.h index 885586d..c48541d 100644 --- a/include/xtf/hypercall.h +++ b/include/xtf/hypercall.h @@ -31,6 +31,7 @@ extern uint8_t hypercall_page[PAGE_SIZE]; #include #include #include +#include #include #include #include @@ -89,6 +90,11 @@ static inline long hypercall_event_channel_op(unsigned int cmd, void *arg) return HYPERCALL2(long, __HYPERVISOR_event_channel_op, cmd, arg); } +static inline long hypercall_physdev_op(unsigned int cmd, void *arg) +{ + return HYPERCALL2(long, __HYPERVISOR_physdev_op, cmd, arg); +} + static inline long hypercall_hvm_op(unsigned int cmd, void *arg) { return HYPERCALL2(long, __HYPERVISOR_hvm_op, cmd, arg); diff --git a/tests/pv-iopl/Makefile b/tests/pv-iopl/Makefile new file mode 100644 index 0000000..2458d0e --- /dev/null +++ b/tests/pv-iopl/Makefile @@ -0,0 +1,12 @@ +MAKEFLAGS += -r +ROOT := $(abspath $(CURDIR)/../..) + +include $(ROOT)/build/common.mk + +NAME := pv-iopl +CATEGORY := functional +TEST-ENVS := $(PV_ENVIRONMENTS) + +obj-perenv += main.o + +include $(ROOT)/build/gen.mk diff --git a/tests/pv-iopl/main.c b/tests/pv-iopl/main.c new file mode 100644 index 0000000..d4e0a5b --- /dev/null +++ b/tests/pv-iopl/main.c @@ -0,0 +1,216 @@ +/** + * @file tests/pv-iopl/main.c + * @ref test-pv-iopl - PV IOPL Emulation + * + * @page test-pv-iopl PV IOPL Emulation + * + * Tests for the behaviour of Xen's virtual IOPL handling for PV guests. + * + * Xen cannot actually run PV guests with an IOPL other than 0, or PV guests + * would be able to play with the real interrupt flag behind Xen's back. + * Therefore, the guests desired IOPL is shadowed, and consulted when a IO + * related fault occurs. Xen either completes the IO on behalf of the guest, + * or bounces the @#GP fault back to the guest kernel. + * + * The instructions: + * - `cli` + * - `outb %al, $0x80` + * + * are tested in both user and kernel context, while varying IOPL, and + * verifying that a @#GP fault was correctly (or not) delivered. + * + * Methods of varying IOPL: + * 1. PHYSDEVOP_set_iopl + * + * The PHYSDEVOP_set_iopl has existed in the Xen ABI for a very long time. + * Kernel context is considered to have a cpl of 1, even for 64bit PV + * guests executing in ring3. A side effect of this is that a PV guest + * kernel will suffer faults from IO accesses until it sets an IOPL of 1. + * A guest may set the current IOPL, but cannot query the value. + * + * @sa tests/pv-iopl/main.c + */ +#include + +#include + +/** Stub CLI instruction with @#GP fixup. */ +static void stub_cli(void) +{ + asm volatile ("1: cli; 2:" + _ASM_EXTABLE(1b, 2b)); +} + +/** Stub OUTB instruction with @#GP fixup. */ +static void stub_outb(void) +{ + asm volatile ("1: outb %b0, $0x80; 2:" + _ASM_EXTABLE(1b, 2b) + :: "a" (0)); +} + +static const struct insn +{ + const char *name; + void (*fn)(void); +} /** Sequence of instructions to run. */ + insn_sequence[] = +{ + { "cli", stub_cli, }, + { "outb", stub_outb, }, +}; + +static struct expectation +{ + const char *insn; + bool user; + bool fault; +} /** Details about the stub under test. */ +expectation; + +/** Latch details of the stub under test. */ +static void expect(const char *insn, bool user, bool fault) +{ + expectation = (struct expectation){insn, user, fault, }; + xtf_exlog_reset(); +} + +/** Check the exception long against the expected details. */ +static void check(void) +{ + unsigned int entries = xtf_exlog_entries(); + const char *mode = expectation.user ? "user" : "kernel"; + + if ( expectation.fault ) + { + if ( entries != 1 ) + { + xtf_failure("Fail (%s %s): Expected 1 exception, got %u\n", + mode, expectation.insn, entries); + xtf_exlog_dump_log(); + return; + } + + exlog_entry_t *entry = xtf_exlog_entry(0); + if ( !entry ) + { + xtf_failure("Fail (%s %s): Unable to retrieve exception log\n", + mode, expectation.insn); + return; + } + + if ( (entry->ev != X86_EXC_GP) || (entry->ec != 0) ) + { + xtf_failure("Fail (%s %s): Expected #GP[0], got %2u[%04x]\n", + mode, expectation.insn, entry->ev, entry->ec); + return; + } + } + else + { + if ( entries != 0 ) + { + xtf_failure("Fail (%s %s): Expected no exceptions, got %u\n", + mode, expectation.insn, entries); + xtf_exlog_dump_log(); + return; + } + } +} + +struct test +{ + void (*set_iopl)(unsigned int iopl); + bool (*should_fault)(bool user, unsigned int iopl); +}; + +/** Test the instruction sequence using a specific iopl interface. */ +static void run_test(const struct test *t) +{ + unsigned int i, iopl; + + for ( iopl = 0; iopl <= 3; ++iopl ) + { + /* vIOPL 2 is not interesting to test. */ + if ( iopl == 2 ) + continue; + + printk(" vIOPL %u\n", iopl); + t->set_iopl(iopl); + + for ( i = 0; i < ARRAY_SIZE(insn_sequence); ++i ) + { + const struct insn *seq = &insn_sequence[i]; + + /* Run insn in kernel. */ + expect(seq->name, 0, t->should_fault(0, iopl)); + seq->fn(); + check(); + + /* Run insn in userspace. */ + expect(seq->name, 1, t->should_fault(1, iopl)); + exec_user(seq->fn); + check(); + } + } +} + +static void hypercall_set_iopl(unsigned int iopl) +{ + hypercall_physdev_op(PHYSDEVOP_set_iopl, &iopl); +} + +static bool hypercall_should_fault(bool user, unsigned int iopl) +{ + /* + * Kernel has vCPL 1, userspace has vCPL 3 + */ + switch ( iopl ) + { + case 0: + /* Both kernel and userspace should fault. */ + return true; + + case 1: + case 2: + /* Kernel should succeed, user should fault. */ + return user; + + case 3: + /* Both kernel and userspace should succeed. */ + return false; + + default: + panic("Bad vIOPL %u\n", iopl); + } +} + +/** Hypercall based IOPL interface. */ +static const struct test hypercall = +{ + .set_iopl = hypercall_set_iopl, + .should_fault = hypercall_should_fault, +}; + +void test_main(void) +{ + printk("PV IOPL emulation\n"); + + xtf_exlog_start(); + + printk("Test: PHYSDEVOP_set_iopl\n"); + run_test(&hypercall); + + xtf_exlog_stop(); + xtf_success(NULL); +} + +/* + * Local variables: + * mode: C + * c-file-style: "BSD" + * c-basic-offset: 4 + * tab-width: 4 + * indent-tabs-mode: nil + * End: + */