1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * sigreturn.c - tests that x86 avoids Intel SYSRET pitfalls 4 * Copyright (c) 2014-2016 Andrew Lutomirski 5 */ 6 7 #define _GNU_SOURCE 8 9 #include <stdlib.h> 10 #include <unistd.h> 11 #include <stdio.h> 12 #include <string.h> 13 #include <inttypes.h> 14 #include <sys/signal.h> 15 #include <sys/ucontext.h> 16 #include <sys/syscall.h> 17 #include <err.h> 18 #include <stddef.h> 19 #include <stdbool.h> 20 #include <setjmp.h> 21 #include <sys/user.h> 22 #include <sys/mman.h> 23 #include <assert.h> 24 25 #include "helpers.h" 26 27 /* 28 * These items are in clang_helpers_64.S, in order to avoid clang inline asm 29 * limitations: 30 */ 31 void test_syscall_ins(void); 32 extern const char test_page[]; 33 34 static void const *current_test_page_addr = test_page; 35 36 /* State used by our signal handlers. */ 37 static gregset_t initial_regs; 38 39 static volatile unsigned long rip; 40 41 static void sigsegv_for_sigreturn_test(int sig, siginfo_t *info, void *ctx_void) 42 { 43 ucontext_t *ctx = (ucontext_t*)ctx_void; 44 45 if (rip != ctx->uc_mcontext.gregs[REG_RIP]) { 46 printf("[FAIL]\tRequested RIP=0x%lx but got RIP=0x%lx\n", 47 rip, (unsigned long)ctx->uc_mcontext.gregs[REG_RIP]); 48 fflush(stdout); 49 _exit(1); 50 } 51 52 memcpy(&ctx->uc_mcontext.gregs, &initial_regs, sizeof(gregset_t)); 53 54 printf("[OK]\tGot SIGSEGV at RIP=0x%lx\n", rip); 55 } 56 57 static void sigusr1(int sig, siginfo_t *info, void *ctx_void) 58 { 59 ucontext_t *ctx = (ucontext_t*)ctx_void; 60 61 memcpy(&initial_regs, &ctx->uc_mcontext.gregs, sizeof(gregset_t)); 62 63 /* Set IP and CX to match so that SYSRET can happen. */ 64 ctx->uc_mcontext.gregs[REG_RIP] = rip; 65 ctx->uc_mcontext.gregs[REG_RCX] = rip; 66 67 /* R11 and EFLAGS should already match. */ 68 assert(ctx->uc_mcontext.gregs[REG_EFL] == 69 ctx->uc_mcontext.gregs[REG_R11]); 70 71 sethandler(SIGSEGV, sigsegv_for_sigreturn_test, SA_RESETHAND); 72 73 return; 74 } 75 76 static void test_sigreturn_to(unsigned long ip) 77 { 78 rip = ip; 79 printf("[RUN]\tsigreturn to 0x%lx\n", ip); 80 raise(SIGUSR1); 81 } 82 83 static jmp_buf jmpbuf; 84 85 static void sigsegv_for_fallthrough(int sig, siginfo_t *info, void *ctx_void) 86 { 87 ucontext_t *ctx = (ucontext_t*)ctx_void; 88 89 if (rip != ctx->uc_mcontext.gregs[REG_RIP]) { 90 printf("[FAIL]\tExpected SIGSEGV at 0x%lx but got RIP=0x%lx\n", 91 rip, (unsigned long)ctx->uc_mcontext.gregs[REG_RIP]); 92 fflush(stdout); 93 _exit(1); 94 } 95 96 siglongjmp(jmpbuf, 1); 97 } 98 99 static void test_syscall_fallthrough_to(unsigned long ip) 100 { 101 void *new_address = (void *)(ip - 4096); 102 void *ret; 103 104 printf("[RUN]\tTrying a SYSCALL that falls through to 0x%lx\n", ip); 105 106 ret = mremap((void *)current_test_page_addr, 4096, 4096, 107 MREMAP_MAYMOVE | MREMAP_FIXED, new_address); 108 if (ret == MAP_FAILED) { 109 if (ip <= (1UL << 47) - PAGE_SIZE) { 110 err(1, "mremap to %p", new_address); 111 } else { 112 printf("[OK]\tmremap to %p failed\n", new_address); 113 return; 114 } 115 } 116 117 if (ret != new_address) 118 errx(1, "mremap malfunctioned: asked for %p but got %p\n", 119 new_address, ret); 120 121 current_test_page_addr = new_address; 122 rip = ip; 123 124 if (sigsetjmp(jmpbuf, 1) == 0) { 125 asm volatile ("call *%[syscall_insn]" :: "a" (SYS_getpid), 126 [syscall_insn] "rm" (ip - 2)); 127 errx(1, "[FAIL]\tSyscall trampoline returned"); 128 } 129 130 printf("[OK]\tWe survived\n"); 131 } 132 133 int main() 134 { 135 /* 136 * When the kernel returns from a slow-path syscall, it will 137 * detect whether SYSRET is appropriate. If it incorrectly 138 * thinks that SYSRET is appropriate when RIP is noncanonical, 139 * it'll crash on Intel CPUs. 140 */ 141 sethandler(SIGUSR1, sigusr1, 0); 142 for (int i = 47; i < 64; i++) 143 test_sigreturn_to(1UL<<i); 144 145 clearhandler(SIGUSR1); 146 147 sethandler(SIGSEGV, sigsegv_for_fallthrough, 0); 148 149 /* One extra test to check that we didn't screw up the mremap logic. */ 150 test_syscall_fallthrough_to((1UL << 47) - 2*PAGE_SIZE); 151 152 /* These are the interesting cases. */ 153 for (int i = 47; i < 64; i++) { 154 test_syscall_fallthrough_to((1UL<<i) - PAGE_SIZE); 155 test_syscall_fallthrough_to(1UL<<i); 156 } 157 158 return 0; 159 } 160