Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 | /* * sigreturn.c - tests that x86 avoids Intel SYSRET pitfalls * Copyright (c) 2014-2016 Andrew Lutomirski * * This program is free software; you can redistribute it and/or modify * it under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. */ #define _GNU_SOURCE #include <stdlib.h> #include <unistd.h> #include <stdio.h> #include <string.h> #include <inttypes.h> #include <sys/signal.h> #include <sys/ucontext.h> #include <sys/syscall.h> #include <err.h> #include <stddef.h> #include <stdbool.h> #include <setjmp.h> #include <sys/user.h> #include <sys/mman.h> #include <assert.h> asm ( ".pushsection \".text\", \"ax\"\n\t" ".balign 4096\n\t" "test_page: .globl test_page\n\t" ".fill 4094,1,0xcc\n\t" "test_syscall_insn:\n\t" "syscall\n\t" ".ifne . - test_page - 4096\n\t" ".error \"test page is not one page long\"\n\t" ".endif\n\t" ".popsection" ); extern const char test_page[]; static void const *current_test_page_addr = test_page; 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"); } /* State used by our signal handlers. */ static gregset_t initial_regs; static volatile unsigned long rip; static void sigsegv_for_sigreturn_test(int sig, siginfo_t *info, void *ctx_void) { ucontext_t *ctx = (ucontext_t*)ctx_void; if (rip != ctx->uc_mcontext.gregs[REG_RIP]) { printf("[FAIL]\tRequested RIP=0x%lx but got RIP=0x%lx\n", rip, (unsigned long)ctx->uc_mcontext.gregs[REG_RIP]); fflush(stdout); _exit(1); } memcpy(&ctx->uc_mcontext.gregs, &initial_regs, sizeof(gregset_t)); printf("[OK]\tGot SIGSEGV at RIP=0x%lx\n", rip); } static void sigusr1(int sig, siginfo_t *info, void *ctx_void) { ucontext_t *ctx = (ucontext_t*)ctx_void; memcpy(&initial_regs, &ctx->uc_mcontext.gregs, sizeof(gregset_t)); /* Set IP and CX to match so that SYSRET can happen. */ ctx->uc_mcontext.gregs[REG_RIP] = rip; ctx->uc_mcontext.gregs[REG_RCX] = rip; /* R11 and EFLAGS should already match. */ assert(ctx->uc_mcontext.gregs[REG_EFL] == ctx->uc_mcontext.gregs[REG_R11]); sethandler(SIGSEGV, sigsegv_for_sigreturn_test, SA_RESETHAND); return; } static void test_sigreturn_to(unsigned long ip) { rip = ip; printf("[RUN]\tsigreturn to 0x%lx\n", ip); raise(SIGUSR1); } static jmp_buf jmpbuf; static void sigsegv_for_fallthrough(int sig, siginfo_t *info, void *ctx_void) { ucontext_t *ctx = (ucontext_t*)ctx_void; if (rip != ctx->uc_mcontext.gregs[REG_RIP]) { printf("[FAIL]\tExpected SIGSEGV at 0x%lx but got RIP=0x%lx\n", rip, (unsigned long)ctx->uc_mcontext.gregs[REG_RIP]); fflush(stdout); _exit(1); } siglongjmp(jmpbuf, 1); } static void test_syscall_fallthrough_to(unsigned long ip) { void *new_address = (void *)(ip - 4096); void *ret; printf("[RUN]\tTrying a SYSCALL that falls through to 0x%lx\n", ip); ret = mremap((void *)current_test_page_addr, 4096, 4096, MREMAP_MAYMOVE | MREMAP_FIXED, new_address); if (ret == MAP_FAILED) { if (ip <= (1UL << 47) - PAGE_SIZE) { err(1, "mremap to %p", new_address); } else { printf("[OK]\tmremap to %p failed\n", new_address); return; } } if (ret != new_address) errx(1, "mremap malfunctioned: asked for %p but got %p\n", new_address, ret); current_test_page_addr = new_address; rip = ip; if (sigsetjmp(jmpbuf, 1) == 0) { asm volatile ("call *%[syscall_insn]" :: "a" (SYS_getpid), [syscall_insn] "rm" (ip - 2)); errx(1, "[FAIL]\tSyscall trampoline returned"); } printf("[OK]\tWe survived\n"); } int main() { /* * When the kernel returns from a slow-path syscall, it will * detect whether SYSRET is appropriate. If it incorrectly * thinks that SYSRET is appropriate when RIP is noncanonical, * it'll crash on Intel CPUs. */ sethandler(SIGUSR1, sigusr1, 0); for (int i = 47; i < 64; i++) test_sigreturn_to(1UL<<i); clearhandler(SIGUSR1); sethandler(SIGSEGV, sigsegv_for_fallthrough, 0); /* One extra test to check that we didn't screw up the mremap logic. */ test_syscall_fallthrough_to((1UL << 47) - 2*PAGE_SIZE); /* These are the interesting cases. */ for (int i = 47; i < 64; i++) { test_syscall_fallthrough_to((1UL<<i) - PAGE_SIZE); test_syscall_fallthrough_to(1UL<<i); } return 0; } |