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 | |
26 | asm ( |
27 | ".pushsection \".text\", \"ax\"\n\t" |
28 | ".balign 4096\n\t" |
29 | "test_page: .globl test_page\n\t" |
30 | ".fill 4094,1,0xcc\n\t" |
31 | "test_syscall_insn:\n\t" |
32 | "syscall\n\t" |
33 | ".ifne . - test_page - 4096\n\t" |
34 | ".error \"test page is not one page long\"\n\t" |
35 | ".endif\n\t" |
36 | ".popsection" |
37 | ); |
38 | |
39 | extern const char test_page[]; |
40 | static void const *current_test_page_addr = test_page; |
41 | |
42 | static void sethandler(int sig, void (*handler)(int, siginfo_t *, void *), |
43 | int flags) |
44 | { |
45 | struct sigaction sa; |
46 | memset(&sa, 0, sizeof(sa)); |
47 | sa.sa_sigaction = handler; |
48 | sa.sa_flags = SA_SIGINFO | flags; |
49 | sigemptyset(&sa.sa_mask); |
50 | if (sigaction(sig, &sa, 0)) |
51 | err(1, "sigaction" ); |
52 | } |
53 | |
54 | static void clearhandler(int sig) |
55 | { |
56 | struct sigaction sa; |
57 | memset(&sa, 0, sizeof(sa)); |
58 | sa.sa_handler = SIG_DFL; |
59 | sigemptyset(&sa.sa_mask); |
60 | if (sigaction(sig, &sa, 0)) |
61 | err(1, "sigaction" ); |
62 | } |
63 | |
64 | /* State used by our signal handlers. */ |
65 | static gregset_t initial_regs; |
66 | |
67 | static volatile unsigned long rip; |
68 | |
69 | static void sigsegv_for_sigreturn_test(int sig, siginfo_t *info, void *ctx_void) |
70 | { |
71 | ucontext_t *ctx = (ucontext_t*)ctx_void; |
72 | |
73 | if (rip != ctx->uc_mcontext.gregs[REG_RIP]) { |
74 | printf("[FAIL]\tRequested RIP=0x%lx but got RIP=0x%lx\n" , |
75 | rip, (unsigned long)ctx->uc_mcontext.gregs[REG_RIP]); |
76 | fflush(stdout); |
77 | _exit(1); |
78 | } |
79 | |
80 | memcpy(&ctx->uc_mcontext.gregs, &initial_regs, sizeof(gregset_t)); |
81 | |
82 | printf("[OK]\tGot SIGSEGV at RIP=0x%lx\n" , rip); |
83 | } |
84 | |
85 | static void sigusr1(int sig, siginfo_t *info, void *ctx_void) |
86 | { |
87 | ucontext_t *ctx = (ucontext_t*)ctx_void; |
88 | |
89 | memcpy(&initial_regs, &ctx->uc_mcontext.gregs, sizeof(gregset_t)); |
90 | |
91 | /* Set IP and CX to match so that SYSRET can happen. */ |
92 | ctx->uc_mcontext.gregs[REG_RIP] = rip; |
93 | ctx->uc_mcontext.gregs[REG_RCX] = rip; |
94 | |
95 | /* R11 and EFLAGS should already match. */ |
96 | assert(ctx->uc_mcontext.gregs[REG_EFL] == |
97 | ctx->uc_mcontext.gregs[REG_R11]); |
98 | |
99 | sethandler(sig: SIGSEGV, handler: sigsegv_for_sigreturn_test, flags: SA_RESETHAND); |
100 | |
101 | return; |
102 | } |
103 | |
104 | static void test_sigreturn_to(unsigned long ip) |
105 | { |
106 | rip = ip; |
107 | printf("[RUN]\tsigreturn to 0x%lx\n" , ip); |
108 | raise(SIGUSR1); |
109 | } |
110 | |
111 | static jmp_buf jmpbuf; |
112 | |
113 | static void sigsegv_for_fallthrough(int sig, siginfo_t *info, void *ctx_void) |
114 | { |
115 | ucontext_t *ctx = (ucontext_t*)ctx_void; |
116 | |
117 | if (rip != ctx->uc_mcontext.gregs[REG_RIP]) { |
118 | printf("[FAIL]\tExpected SIGSEGV at 0x%lx but got RIP=0x%lx\n" , |
119 | rip, (unsigned long)ctx->uc_mcontext.gregs[REG_RIP]); |
120 | fflush(stdout); |
121 | _exit(1); |
122 | } |
123 | |
124 | siglongjmp(jmpbuf, 1); |
125 | } |
126 | |
127 | static void test_syscall_fallthrough_to(unsigned long ip) |
128 | { |
129 | void *new_address = (void *)(ip - 4096); |
130 | void *ret; |
131 | |
132 | printf("[RUN]\tTrying a SYSCALL that falls through to 0x%lx\n" , ip); |
133 | |
134 | ret = mremap((void *)current_test_page_addr, 4096, 4096, |
135 | MREMAP_MAYMOVE | MREMAP_FIXED, new_address); |
136 | if (ret == MAP_FAILED) { |
137 | if (ip <= (1UL << 47) - PAGE_SIZE) { |
138 | err(1, "mremap to %p" , new_address); |
139 | } else { |
140 | printf("[OK]\tmremap to %p failed\n" , new_address); |
141 | return; |
142 | } |
143 | } |
144 | |
145 | if (ret != new_address) |
146 | errx(1, "mremap malfunctioned: asked for %p but got %p\n" , |
147 | new_address, ret); |
148 | |
149 | current_test_page_addr = new_address; |
150 | rip = ip; |
151 | |
152 | if (sigsetjmp(jmpbuf, 1) == 0) { |
153 | asm volatile ("call *%[syscall_insn]" :: "a" (SYS_getpid), |
154 | [syscall_insn] "rm" (ip - 2)); |
155 | errx(1, "[FAIL]\tSyscall trampoline returned" ); |
156 | } |
157 | |
158 | printf("[OK]\tWe survived\n" ); |
159 | } |
160 | |
161 | int main() |
162 | { |
163 | /* |
164 | * When the kernel returns from a slow-path syscall, it will |
165 | * detect whether SYSRET is appropriate. If it incorrectly |
166 | * thinks that SYSRET is appropriate when RIP is noncanonical, |
167 | * it'll crash on Intel CPUs. |
168 | */ |
169 | sethandler(SIGUSR1, sigusr1, 0); |
170 | for (int i = 47; i < 64; i++) |
171 | test_sigreturn_to(ip: 1UL<<i); |
172 | |
173 | clearhandler(SIGUSR1); |
174 | |
175 | sethandler(SIGSEGV, sigsegv_for_fallthrough, 0); |
176 | |
177 | /* One extra test to check that we didn't screw up the mremap logic. */ |
178 | test_syscall_fallthrough_to((1UL << 47) - 2*PAGE_SIZE); |
179 | |
180 | /* These are the interesting cases. */ |
181 | for (int i = 47; i < 64; i++) { |
182 | test_syscall_fallthrough_to((1UL<<i) - PAGE_SIZE); |
183 | test_syscall_fallthrough_to(ip: 1UL<<i); |
184 | } |
185 | |
186 | return 0; |
187 | } |
188 | |