| 1 | |
| 2 | |
| 3 | * single_step_syscall.c - single-steps various x86 syscalls
|
| 4 | * Copyright (c) 2014-2015 Andrew Lutomirski
|
| 5 | *
|
| 6 | * This is a very simple series of tests that makes system calls with
|
| 7 | * the TF flag set. This exercises some nasty kernel code in the
|
| 8 | * SYSENTER case: SYSENTER does not clear TF, so SYSENTER with TF set
|
| 9 | * immediately issues #DB from CPL 0. This requires special handling in
|
| 10 | * the kernel.
|
| 11 |
|
| 12 |
|
| 13 | #define _GNU_SOURCE
|
| 14 |
|
| 15 | #include <sys/time.h>
|
| 16 | #include <time.h>
|
| 17 | #include <stdlib.h>
|
| 18 | #include <sys/syscall.h>
|
| 19 | #include <unistd.h>
|
| 20 | #include <stdio.h>
|
| 21 | #include <string.h>
|
| 22 | #include <inttypes.h>
|
| 23 | #include <sys/mman.h>
|
| 24 | #include <sys/signal.h>
|
| 25 | #include <sys/ucontext.h>
|
| 26 | #include <asm/ldt.h>
|
| 27 | #include <err.h>
|
| 28 | #include <setjmp.h>
|
| 29 | #include <stddef.h>
|
| 30 | #include <stdbool.h>
|
| 31 | #include <sys/ptrace.h>
|
| 32 | #include <sys/user.h>
|
| 33 |
|
| 34 | #include "helpers.h"
|
| 35 |
|
| 36 | static volatile sig_atomic_t sig_traps, sig_eflags;
|
| 37 | sigjmp_buf jmpbuf;
|
| 38 |
|
| 39 | #ifdef __x86_64__
|
| 40 | # define REG_IP REG_RIP
|
| 41 | # define WIDTH "q"
|
| 42 | # define INT80_CLOBBERS "r8", "r9", "r10", "r11"
|
| 43 | #else
|
| 44 | # define REG_IP REG_EIP
|
| 45 | # define WIDTH "l"
|
| 46 | # define INT80_CLOBBERS
|
| 47 | #endif
|
| 48 |
|
| 49 | static void sigtrap(int sig, siginfo_t *info, void *ctx_void)
|
| 50 | {
|
| 51 | ucontext_t *ctx = (ucontext_t*)ctx_void;
|
| 52 |
|
| 53 | if (get_eflags() & X86_EFLAGS_TF) {
|
| 54 | set_eflags(get_eflags() & ~X86_EFLAGS_TF);
|
| 55 | printf("[WARN]\tSIGTRAP handler had TF set\n");
|
| 56 | _exit(1);
|
| 57 | }
|
| 58 |
|
| 59 | sig_traps++;
|
| 60 |
|
| 61 | if (sig_traps == 10000 || sig_traps == 10001) {
|
| 62 | printf("[WARN]\tHit %d SIGTRAPs with si_addr 0x%lx, ip 0x%lx\n",
|
| 63 | (int)sig_traps,
|
| 64 | (unsigned long)info->si_addr,
|
| 65 | (unsigned long)ctx->uc_mcontext.gregs[REG_IP]);
|
| 66 | }
|
| 67 | }
|
| 68 |
|
| 69 | static char const * const signames[] = {
|
| 70 | [SIGSEGV] = "SIGSEGV",
|
| 71 | [SIGBUS] = "SIBGUS",
|
| 72 | [SIGTRAP] = "SIGTRAP",
|
| 73 | [SIGILL] = "SIGILL",
|
| 74 | };
|
| 75 |
|
| 76 | static void print_and_longjmp(int sig, siginfo_t *si, void *ctx_void)
|
| 77 | {
|
| 78 | ucontext_t *ctx = ctx_void;
|
| 79 |
|
| 80 | printf("\tGot %s with RIP=%lx, TF=%ld\n", signames[sig],
|
| 81 | (unsigned long)ctx->uc_mcontext.gregs[REG_IP],
|
| 82 | (unsigned long)ctx->uc_mcontext.gregs[REG_EFL] & X86_EFLAGS_TF);
|
| 83 |
|
| 84 | sig_eflags = (unsigned long)ctx->uc_mcontext.gregs[REG_EFL];
|
| 85 | siglongjmp(jmpbuf, 1);
|
| 86 | }
|
| 87 |
|
| 88 | static void check_result(void)
|
| 89 | {
|
| 90 | unsigned long new_eflags = get_eflags();
|
| 91 | set_eflags(new_eflags & ~X86_EFLAGS_TF);
|
| 92 |
|
| 93 | if (!sig_traps) {
|
| 94 | printf("[FAIL]\tNo SIGTRAP\n");
|
| 95 | exit(1);
|
| 96 | }
|
| 97 |
|
| 98 | if (!(new_eflags & X86_EFLAGS_TF)) {
|
| 99 | printf("[FAIL]\tTF was cleared\n");
|
| 100 | exit(1);
|
| 101 | }
|
| 102 |
|
| 103 | printf("[OK]\tSurvived with TF set and %d traps\n", (int)sig_traps);
|
| 104 | sig_traps = 0;
|
| 105 | }
|
| 106 |
|
| 107 | static void fast_syscall_no_tf(void)
|
| 108 | {
|
| 109 | sig_traps = 0;
|
| 110 | printf("[RUN]\tFast syscall with TF cleared\n");
|
| 111 | fflush(stdout);
|
| 112 | if (get_eflags() & X86_EFLAGS_TF) {
|
| 113 | printf("[FAIL]\tTF is now set\n");
|
| 114 | exit(1);
|
| 115 | }
|
| 116 | if (sig_traps) {
|
| 117 | printf("[FAIL]\tGot SIGTRAP\n");
|
| 118 | exit(1);
|
| 119 | }
|
| 120 | printf("[OK]\tNothing unexpected happened\n");
|
| 121 | }
|
| 122 |
|
| 123 | int main()
|
| 124 | {
|
| 125 | #ifdef CAN_BUILD_32
|
| 126 | int tmp;
|
| 127 | #endif
|
| 128 |
|
| 129 | sethandler(SIGTRAP, sigtrap, 0);
|
| 130 |
|
| 131 | printf("[RUN]\tSet TF and check nop\n");
|
| 132 | set_eflags(get_eflags() | X86_EFLAGS_TF);
|
| 133 | asm volatile ("nop");
|
| 134 | check_result();
|
| 135 |
|
| 136 | #ifdef __x86_64__
|
| 137 | printf("[RUN]\tSet TF and check syscall-less opportunistic sysret\n");
|
| 138 | set_eflags(get_eflags() | X86_EFLAGS_TF);
|
| 139 | extern unsigned char post_nop[];
|
| 140 | asm volatile ("pushf" WIDTH "\n\t"
|
| 141 | "pop" WIDTH " %%r11\n\t"
|
| 142 | "nop\n\t"
|
| 143 | "post_nop:"
|
| 144 | : : "c" (post_nop) : "r11");
|
| 145 | check_result();
|
| 146 | #endif
|
| 147 | #ifdef CAN_BUILD_32
|
| 148 | printf("[RUN]\tSet TF and check int80\n");
|
| 149 | set_eflags(get_eflags() | X86_EFLAGS_TF);
|
| 150 | asm volatile ("int $0x80" : "=a" (tmp) : "a" (SYS_getpid)
|
| 151 | : INT80_CLOBBERS);
|
| 152 | check_result();
|
| 153 | #endif
|
| 154 |
|
| 155 | |
| 156 | * This test is particularly interesting if fast syscalls use
|
| 157 | * SYSENTER: it triggers a nasty design flaw in SYSENTER.
|
| 158 | * Specifically, SYSENTER does not clear TF, so either SYSENTER
|
| 159 | * or the next instruction traps at CPL0. (Of course, Intel
|
| 160 | * mostly forgot to document exactly what happens here.) So we
|
| 161 | * get a CPL0 fault with usergs (on 64-bit kernels) and possibly
|
| 162 | * no stack. The only sane way the kernel can possibly handle
|
| 163 | * it is to clear TF on return from the #DB handler, but this
|
| 164 | * happens way too early to set TF in the saved pt_regs, so the
|
| 165 | * kernel has to do something clever to avoid losing track of
|
| 166 | * the TF bit.
|
| 167 | *
|
| 168 | * Needless to say, we've had bugs in this area.
|
| 169 |
|
| 170 | syscall(SYS_getpid);
|
| 171 | printf("[RUN]\tSet TF and check a fast syscall\n");
|
| 172 | set_eflags(get_eflags() | X86_EFLAGS_TF);
|
| 173 | syscall(SYS_getpid);
|
| 174 | check_result();
|
| 175 |
|
| 176 |
|
| 177 | fast_syscall_no_tf();
|
| 178 |
|
| 179 | |
| 180 | * And do a forced SYSENTER to make sure that this works even if
|
| 181 | * fast syscalls don't use SYSENTER.
|
| 182 | *
|
| 183 | * Invoking SYSENTER directly breaks all the rules. Just handle
|
| 184 | * the SIGSEGV.
|
| 185 |
|
| 186 | if (sigsetjmp(jmpbuf, 1) == 0) {
|
| 187 | unsigned long nr = SYS_getpid;
|
| 188 | printf("[RUN]\tSet TF and check SYSENTER\n");
|
| 189 | stack_t stack = {
|
| 190 | .ss_sp = malloc(sizeof(char) * SIGSTKSZ),
|
| 191 | .ss_size = SIGSTKSZ,
|
| 192 | };
|
| 193 | if (sigaltstack(&stack, NULL) != 0)
|
| 194 | err(1, "sigaltstack");
|
| 195 | sethandler(SIGSEGV, print_and_longjmp,
|
| 196 | SA_RESETHAND | SA_ONSTACK);
|
| 197 | sethandler(SIGILL, print_and_longjmp, SA_RESETHAND);
|
| 198 | set_eflags(get_eflags() | X86_EFLAGS_TF);
|
| 199 | free(stack.ss_sp);
|
| 200 |
|
| 201 | asm volatile ("xorl %%ebp, %%ebp; SYSENTER" : "+a" (nr) :: "flags", "rcx"
|
| 202 | #ifdef __x86_64__
|
| 203 | , "r11"
|
| 204 | #endif
|
| 205 | );
|
| 206 |
|
| 207 |
|
| 208 | }
|
| 209 | clearhandler(SIGSEGV);
|
| 210 | clearhandler(SIGILL);
|
| 211 | if (!(sig_eflags & X86_EFLAGS_TF)) {
|
| 212 | printf("[FAIL]\tTF was cleared\n");
|
| 213 | exit(1);
|
| 214 | }
|
| 215 |
|
| 216 |
|
| 217 | fast_syscall_no_tf();
|
| 218 |
|
| 219 | return 0;
|
| 220 | }
|