]>
Commit | Line | Data |
---|---|---|
2025cf9e | 1 | // SPDX-License-Identifier: GPL-2.0-only |
66060214 AL |
2 | /* |
3 | * sigreturn.c - tests that x86 avoids Intel SYSRET pitfalls | |
4 | * Copyright (c) 2014-2016 Andrew Lutomirski | |
66060214 AL |
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(SIGSEGV, sigsegv_for_sigreturn_test, 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(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(1UL<<i); | |
184 | } | |
185 | ||
186 | return 0; | |
187 | } |