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 const void *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 74 static void test_sigreturn_to(unsigned long ip) 75 { 76 rip = ip; 77 printf("[RUN]\tsigreturn to 0x%lx\n", ip); 78 raise(SIGUSR1); 79 } 80 81 static jmp_buf jmpbuf; 82 83 static void sigsegv_for_fallthrough(int sig, siginfo_t *info, void *ctx_void) 84 { 85 ucontext_t *ctx = (ucontext_t *)ctx_void; 86 87 if (rip != ctx->uc_mcontext.gregs[REG_RIP]) { 88 printf("[FAIL]\tExpected SIGSEGV at 0x%lx but got RIP=0x%lx\n", 89 rip, (unsigned long)ctx->uc_mcontext.gregs[REG_RIP]); 90 fflush(stdout); 91 _exit(1); 92 } 93 94 siglongjmp(jmpbuf, 1); 95 } 96 97 static void test_syscall_fallthrough_to(unsigned long ip) 98 { 99 void *new_address = (void *)(ip - 4096); 100 void *ret; 101 102 printf("[RUN]\tTrying a SYSCALL that falls through to 0x%lx\n", ip); 103 104 ret = mremap((void *)current_test_page_addr, 4096, 4096, 105 MREMAP_MAYMOVE | MREMAP_FIXED, new_address); 106 if (ret == MAP_FAILED) { 107 if (ip <= (1UL << 47) - PAGE_SIZE) { 108 err(1, "mremap to %p", new_address); 109 } else { 110 printf("[OK]\tmremap to %p failed\n", new_address); 111 return; 112 } 113 } 114 115 if (ret != new_address) 116 errx(1, "mremap malfunctioned: asked for %p but got %p\n", 117 new_address, ret); 118 119 current_test_page_addr = new_address; 120 rip = ip; 121 122 if (sigsetjmp(jmpbuf, 1) == 0) { 123 asm volatile ("call *%[syscall_insn]" :: "a" (SYS_getpid), 124 [syscall_insn] "rm" (ip - 2)); 125 errx(1, "[FAIL]\tSyscall trampoline returned"); 126 } 127 128 printf("[OK]\tWe survived\n"); 129 } 130 131 int main(void) 132 { 133 /* 134 * When the kernel returns from a slow-path syscall, it will 135 * detect whether SYSRET is appropriate. If it incorrectly 136 * thinks that SYSRET is appropriate when RIP is noncanonical, 137 * it'll crash on Intel CPUs. 138 */ 139 sethandler(SIGUSR1, sigusr1, 0); 140 for (int i = 47; i < 64; i++) 141 test_sigreturn_to(1UL<<i); 142 143 clearhandler(SIGUSR1); 144 145 sethandler(SIGSEGV, sigsegv_for_fallthrough, 0); 146 147 /* One extra test to check that we didn't screw up the mremap logic. */ 148 test_syscall_fallthrough_to((1UL << 47) - 2*PAGE_SIZE); 149 150 /* These are the interesting cases. */ 151 for (int i = 47; i < 64; i++) { 152 test_syscall_fallthrough_to((1UL<<i) - PAGE_SIZE); 153 test_syscall_fallthrough_to(1UL<<i); 154 } 155 156 return 0; 157 } 158