x86/selftests, x86/vm86: Improve entry_from_vm86 selftest
authorAndy Lutomirski <luto@kernel.org>
Fri, 10 Jul 2015 02:17:29 +0000 (19:17 -0700)
committerIngo Molnar <mingo@kernel.org>
Tue, 21 Jul 2015 08:51:20 +0000 (10:51 +0200)
The entry_from_vm86 selftest was very weak.  Improve it: test
more types of kernel entries from vm86 mode and test them more
carefully.

While we're at it, try to improve behavior on non-SEP CPUs.  The
old code was buggy because I misunderstood the intended
semantics of #UD in vm86, so I didn't handle a possible signal.

Signed-off-by: Andy Lutomirski <luto@kernel.org>
Cc: Andy Lutomirski <luto@amacapital.net>
Cc: Borislav Petkov <bp@alien8.de>
Cc: Brian Gerst <brgerst@gmail.com>
Cc: Denys Vlasenko <dvlasenk@redhat.com>
Cc: Denys Vlasenko <vda.linux@googlemail.com>
Cc: Frederic Weisbecker <fweisbec@gmail.com>
Cc: H. Peter Anvin <hpa@zytor.com>
Cc: Kees Cook <keescook@chromium.org>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Oleg Nesterov <oleg@redhat.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Rik van Riel <riel@redhat.com>
Cc: Shuah Khan <shuahkh@osg.samsung.com>
Cc: Thomas Gleixner <tglx@linutronix.de>
Link: http://lkml.kernel.org/r/d8ef1d7368ac70d8342481563ed50f9a7d2eea6f.1436492057.git.luto@kernel.org
Signed-off-by: Ingo Molnar <mingo@kernel.org>
tools/testing/selftests/x86/entry_from_vm86.c

index 5c38a18..f004b2a 100644 (file)
 static unsigned long load_addr = 0x10000;
 static int nerrs = 0;
 
+static void sethandler(int sig, void (*handler)(int, siginfo_t *, void *),
+                      int flags)
+{
+       struct sigaction sa;
+       memset(&sa, 0, sizeof(sa));
+       sa.sa_sigaction = handler;
+       sa.sa_flags = SA_SIGINFO | flags;
+       sigemptyset(&sa.sa_mask);
+       if (sigaction(sig, &sa, 0))
+               err(1, "sigaction");
+}
+
+static void clearhandler(int sig)
+{
+       struct sigaction sa;
+       memset(&sa, 0, sizeof(sa));
+       sa.sa_handler = SIG_DFL;
+       sigemptyset(&sa.sa_mask);
+       if (sigaction(sig, &sa, 0))
+               err(1, "sigaction");
+}
+
+static sig_atomic_t got_signal;
+
+static void sighandler(int sig, siginfo_t *info, void *ctx_void)
+{
+       ucontext_t *ctx = (ucontext_t*)ctx_void;
+
+       if (ctx->uc_mcontext.gregs[REG_EFL] & X86_EFLAGS_VM ||
+           (ctx->uc_mcontext.gregs[REG_CS] & 3) != 3) {
+               printf("[FAIL]\tSignal frame should not reflect vm86 mode\n");
+               nerrs++;
+       }
+
+       const char *signame;
+       if (sig == SIGSEGV)
+               signame = "SIGSEGV";
+       else if (sig == SIGILL)
+               signame = "SIGILL";
+       else
+               signame = "unexpected signal";
+
+       printf("[INFO]\t%s: FLAGS = 0x%lx, CS = 0x%hx\n", signame,
+              (unsigned long)ctx->uc_mcontext.gregs[REG_EFL],
+              (unsigned short)ctx->uc_mcontext.gregs[REG_CS]);
+
+       got_signal = 1;
+}
+
 asm (
        ".pushsection .rodata\n\t"
        ".type vmcode_bound, @object\n\t"
@@ -38,6 +87,14 @@ asm (
        "int3\n\t"
        "vmcode_sysenter:\n\t"
        "sysenter\n\t"
+       "vmcode_syscall:\n\t"
+       "syscall\n\t"
+       "vmcode_sti:\n\t"
+       "sti\n\t"
+       "vmcode_int3:\n\t"
+       "int3\n\t"
+       "vmcode_int80:\n\t"
+       "int $0x80\n\t"
        ".size vmcode, . - vmcode\n\t"
        "end_vmcode:\n\t"
        ".code32\n\t"
@@ -45,9 +102,11 @@ asm (
        );
 
 extern unsigned char vmcode[], end_vmcode[];
-extern unsigned char vmcode_bound[], vmcode_sysenter[];
+extern unsigned char vmcode_bound[], vmcode_sysenter[], vmcode_syscall[],
+       vmcode_sti[], vmcode_int3[], vmcode_int80[];
 
 static void do_test(struct vm86plus_struct *v86, unsigned long eip,
+                   unsigned int rettype, unsigned int retarg,
                    const char *text)
 {
        long ret;
@@ -73,13 +132,28 @@ static void do_test(struct vm86plus_struct *v86, unsigned long eip,
                else
                        sprintf(trapname, "%d", trapno);
 
-               printf("[OK]\tExited vm86 mode due to #%s\n", trapname);
+               printf("[INFO]\tExited vm86 mode due to #%s\n", trapname);
        } else if (VM86_TYPE(ret) == VM86_UNKNOWN) {
-               printf("[OK]\tExited vm86 mode due to unhandled GP fault\n");
+               printf("[INFO]\tExited vm86 mode due to unhandled GP fault\n");
+       } else if (VM86_TYPE(ret) == VM86_TRAP) {
+               printf("[INFO]\tExited vm86 mode due to a trap (arg=%ld)\n",
+                      VM86_ARG(ret));
+       } else if (VM86_TYPE(ret) == VM86_SIGNAL) {
+               printf("[INFO]\tExited vm86 mode due to a signal\n");
+       } else if (VM86_TYPE(ret) == VM86_STI) {
+               printf("[INFO]\tExited vm86 mode due to STI\n");
        } else {
-               printf("[OK]\tExited vm86 mode due to type %ld, arg %ld\n",
+               printf("[INFO]\tExited vm86 mode due to type %ld, arg %ld\n",
                       VM86_TYPE(ret), VM86_ARG(ret));
        }
+
+       if (rettype == -1 ||
+           (VM86_TYPE(ret) == rettype && VM86_ARG(ret) == retarg)) {
+               printf("[OK]\tReturned correctly\n");
+       } else {
+               printf("[FAIL]\tIncorrect return reason\n");
+               nerrs++;
+       }
 }
 
 int main(void)
@@ -105,10 +179,52 @@ int main(void)
        assert((v86.regs.cs & 3) == 0); /* Looks like RPL = 0 */
 
        /* #BR -- should deliver SIG??? */
-       do_test(&v86, vmcode_bound - vmcode, "#BR");
-
-       /* SYSENTER -- should cause #GP or #UD depending on CPU */
-       do_test(&v86, vmcode_sysenter - vmcode, "SYSENTER");
+       do_test(&v86, vmcode_bound - vmcode, VM86_INTx, 5, "#BR");
+
+       /*
+        * SYSENTER -- should cause #GP or #UD depending on CPU.
+        * Expected return type -1 means that we shouldn't validate
+        * the vm86 return value.  This will avoid problems on non-SEP
+        * CPUs.
+        */
+       sethandler(SIGILL, sighandler, 0);
+       do_test(&v86, vmcode_sysenter - vmcode, -1, 0, "SYSENTER");
+       clearhandler(SIGILL);
+
+       /*
+        * SYSCALL would be a disaster in VM86 mode.  Fortunately,
+        * there is no kernel that both enables SYSCALL and sets
+        * EFER.SCE, so it's #UD on all systems.  But vm86 is
+        * buggy (or has a "feature"), so the SIGILL will actually
+        * be delivered.
+        */
+       sethandler(SIGILL, sighandler, 0);
+       do_test(&v86, vmcode_syscall - vmcode, VM86_SIGNAL, 0, "SYSCALL");
+       clearhandler(SIGILL);
+
+       /* STI with VIP set */
+       v86.regs.eflags |= X86_EFLAGS_VIP;
+       v86.regs.eflags &= ~X86_EFLAGS_IF;
+       do_test(&v86, vmcode_sti - vmcode, VM86_STI, 0, "STI with VIP set");
+
+       /* INT3 -- should cause #BP */
+       do_test(&v86, vmcode_int3 - vmcode, VM86_TRAP, 3, "INT3");
+
+       /* INT80 -- should exit with "INTx 0x80" */
+       v86.regs.eax = (unsigned int)-1;
+       do_test(&v86, vmcode_int80 - vmcode, VM86_INTx, 0x80, "int80");
+
+       /* Execute a null pointer */
+       v86.regs.cs = 0;
+       v86.regs.ss = 0;
+       sethandler(SIGSEGV, sighandler, 0);
+       got_signal = 0;
+       do_test(&v86, 0, VM86_SIGNAL, 0, "Execute null pointer");
+       if (!got_signal) {
+               printf("[FAIL]\tDid not receive SIGSEGV\n");
+               nerrs++;
+       }
+       clearhandler(SIGSEGV);
 
        return (nerrs == 0 ? 0 : 1);
 }