1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * single_step_syscall.c - single-steps various x86 syscalls |
4 | * Copyright (c) 2014-2015 Andrew Lutomirski |
5 | * |
6 | * This is a very simple series of tests that makes system calls with |
7 | * the TF flag set. This exercises some nasty kernel code in the |
8 | * SYSENTER case: SYSENTER does not clear TF, so SYSENTER with TF set |
9 | * immediately issues #DB from CPL 0. This requires special handling in |
10 | * the kernel. |
11 | */ |
12 | |
13 | #define _GNU_SOURCE |
14 | |
15 | #include <sys/time.h> |
16 | #include <time.h> |
17 | #include <stdlib.h> |
18 | #include <sys/syscall.h> |
19 | #include <unistd.h> |
20 | #include <stdio.h> |
21 | #include <string.h> |
22 | #include <inttypes.h> |
23 | #include <sys/mman.h> |
24 | #include <sys/signal.h> |
25 | #include <sys/ucontext.h> |
26 | #include <asm/ldt.h> |
27 | #include <err.h> |
28 | #include <setjmp.h> |
29 | #include <stddef.h> |
30 | #include <stdbool.h> |
31 | #include <sys/ptrace.h> |
32 | #include <sys/user.h> |
33 | |
34 | #include "helpers.h" |
35 | |
36 | static void sethandler(int sig, void (*handler)(int, siginfo_t *, void *), |
37 | int flags) |
38 | { |
39 | struct sigaction sa; |
40 | memset(&sa, 0, sizeof(sa)); |
41 | sa.sa_sigaction = handler; |
42 | sa.sa_flags = SA_SIGINFO | flags; |
43 | sigemptyset(&sa.sa_mask); |
44 | if (sigaction(sig, &sa, 0)) |
45 | err(1, "sigaction" ); |
46 | } |
47 | |
48 | static void clearhandler(int sig) |
49 | { |
50 | struct sigaction sa; |
51 | memset(&sa, 0, sizeof(sa)); |
52 | sa.sa_handler = SIG_DFL; |
53 | sigemptyset(&sa.sa_mask); |
54 | if (sigaction(sig, &sa, 0)) |
55 | err(1, "sigaction" ); |
56 | } |
57 | |
58 | static volatile sig_atomic_t sig_traps, sig_eflags; |
59 | sigjmp_buf jmpbuf; |
60 | |
61 | #ifdef __x86_64__ |
62 | # define REG_IP REG_RIP |
63 | # define WIDTH "q" |
64 | # define INT80_CLOBBERS "r8", "r9", "r10", "r11" |
65 | #else |
66 | # define REG_IP REG_EIP |
67 | # define WIDTH "l" |
68 | # define INT80_CLOBBERS |
69 | #endif |
70 | |
71 | static void sigtrap(int sig, siginfo_t *info, void *ctx_void) |
72 | { |
73 | ucontext_t *ctx = (ucontext_t*)ctx_void; |
74 | |
75 | if (get_eflags() & X86_EFLAGS_TF) { |
76 | set_eflags(get_eflags() & ~X86_EFLAGS_TF); |
77 | printf("[WARN]\tSIGTRAP handler had TF set\n" ); |
78 | _exit(1); |
79 | } |
80 | |
81 | sig_traps++; |
82 | |
83 | if (sig_traps == 10000 || sig_traps == 10001) { |
84 | printf("[WARN]\tHit %d SIGTRAPs with si_addr 0x%lx, ip 0x%lx\n" , |
85 | (int)sig_traps, |
86 | (unsigned long)info->si_addr, |
87 | (unsigned long)ctx->uc_mcontext.gregs[REG_IP]); |
88 | } |
89 | } |
90 | |
91 | static char const * const signames[] = { |
92 | [SIGSEGV] = "SIGSEGV" , |
93 | [SIGBUS] = "SIBGUS" , |
94 | [SIGTRAP] = "SIGTRAP" , |
95 | [SIGILL] = "SIGILL" , |
96 | }; |
97 | |
98 | static void print_and_longjmp(int sig, siginfo_t *si, void *ctx_void) |
99 | { |
100 | ucontext_t *ctx = ctx_void; |
101 | |
102 | printf("\tGot %s with RIP=%lx, TF=%ld\n" , signames[sig], |
103 | (unsigned long)ctx->uc_mcontext.gregs[REG_IP], |
104 | (unsigned long)ctx->uc_mcontext.gregs[REG_EFL] & X86_EFLAGS_TF); |
105 | |
106 | sig_eflags = (unsigned long)ctx->uc_mcontext.gregs[REG_EFL]; |
107 | siglongjmp(jmpbuf, 1); |
108 | } |
109 | |
110 | static void check_result(void) |
111 | { |
112 | unsigned long new_eflags = get_eflags(); |
113 | set_eflags(new_eflags & ~X86_EFLAGS_TF); |
114 | |
115 | if (!sig_traps) { |
116 | printf("[FAIL]\tNo SIGTRAP\n" ); |
117 | exit(1); |
118 | } |
119 | |
120 | if (!(new_eflags & X86_EFLAGS_TF)) { |
121 | printf("[FAIL]\tTF was cleared\n" ); |
122 | exit(1); |
123 | } |
124 | |
125 | printf("[OK]\tSurvived with TF set and %d traps\n" , (int)sig_traps); |
126 | sig_traps = 0; |
127 | } |
128 | |
129 | static void fast_syscall_no_tf(void) |
130 | { |
131 | sig_traps = 0; |
132 | printf("[RUN]\tFast syscall with TF cleared\n" ); |
133 | fflush(stdout); /* Force a syscall */ |
134 | if (get_eflags() & X86_EFLAGS_TF) { |
135 | printf("[FAIL]\tTF is now set\n" ); |
136 | exit(1); |
137 | } |
138 | if (sig_traps) { |
139 | printf("[FAIL]\tGot SIGTRAP\n" ); |
140 | exit(1); |
141 | } |
142 | printf("[OK]\tNothing unexpected happened\n" ); |
143 | } |
144 | |
145 | int main() |
146 | { |
147 | #ifdef CAN_BUILD_32 |
148 | int tmp; |
149 | #endif |
150 | |
151 | sethandler(sig: SIGTRAP, handler: sigtrap, flags: 0); |
152 | |
153 | printf("[RUN]\tSet TF and check nop\n" ); |
154 | set_eflags(get_eflags() | X86_EFLAGS_TF); |
155 | asm volatile ("nop" ); |
156 | check_result(); |
157 | |
158 | #ifdef __x86_64__ |
159 | printf("[RUN]\tSet TF and check syscall-less opportunistic sysret\n" ); |
160 | set_eflags(get_eflags() | X86_EFLAGS_TF); |
161 | extern unsigned char post_nop[]; |
162 | asm volatile ("pushf" WIDTH "\n\t" |
163 | "pop" WIDTH " %%r11\n\t" |
164 | "nop\n\t" |
165 | "post_nop:" |
166 | : : "c" (post_nop) : "r11" ); |
167 | check_result(); |
168 | #endif |
169 | #ifdef CAN_BUILD_32 |
170 | printf("[RUN]\tSet TF and check int80\n" ); |
171 | set_eflags(get_eflags() | X86_EFLAGS_TF); |
172 | asm volatile ("int $0x80" : "=a" (tmp) : "a" (SYS_getpid) |
173 | : INT80_CLOBBERS); |
174 | check_result(); |
175 | #endif |
176 | |
177 | /* |
178 | * This test is particularly interesting if fast syscalls use |
179 | * SYSENTER: it triggers a nasty design flaw in SYSENTER. |
180 | * Specifically, SYSENTER does not clear TF, so either SYSENTER |
181 | * or the next instruction traps at CPL0. (Of course, Intel |
182 | * mostly forgot to document exactly what happens here.) So we |
183 | * get a CPL0 fault with usergs (on 64-bit kernels) and possibly |
184 | * no stack. The only sane way the kernel can possibly handle |
185 | * it is to clear TF on return from the #DB handler, but this |
186 | * happens way too early to set TF in the saved pt_regs, so the |
187 | * kernel has to do something clever to avoid losing track of |
188 | * the TF bit. |
189 | * |
190 | * Needless to say, we've had bugs in this area. |
191 | */ |
192 | syscall(SYS_getpid); /* Force symbol binding without TF set. */ |
193 | printf("[RUN]\tSet TF and check a fast syscall\n" ); |
194 | set_eflags(get_eflags() | X86_EFLAGS_TF); |
195 | syscall(SYS_getpid); |
196 | check_result(); |
197 | |
198 | /* Now make sure that another fast syscall doesn't set TF again. */ |
199 | fast_syscall_no_tf(); |
200 | |
201 | /* |
202 | * And do a forced SYSENTER to make sure that this works even if |
203 | * fast syscalls don't use SYSENTER. |
204 | * |
205 | * Invoking SYSENTER directly breaks all the rules. Just handle |
206 | * the SIGSEGV. |
207 | */ |
208 | if (sigsetjmp(jmpbuf, 1) == 0) { |
209 | unsigned long nr = SYS_getpid; |
210 | printf("[RUN]\tSet TF and check SYSENTER\n" ); |
211 | stack_t stack = { |
212 | .ss_sp = malloc(sizeof(char) * SIGSTKSZ), |
213 | .ss_size = SIGSTKSZ, |
214 | }; |
215 | if (sigaltstack(&stack, NULL) != 0) |
216 | err(1, "sigaltstack" ); |
217 | sethandler(sig: SIGSEGV, handler: print_and_longjmp, |
218 | flags: SA_RESETHAND | SA_ONSTACK); |
219 | sethandler(sig: SIGILL, handler: print_and_longjmp, flags: SA_RESETHAND); |
220 | set_eflags(get_eflags() | X86_EFLAGS_TF); |
221 | free(stack.ss_sp); |
222 | /* Clear EBP first to make sure we segfault cleanly. */ |
223 | asm volatile ("xorl %%ebp, %%ebp; SYSENTER" : "+a" (nr) :: "flags" , "rcx" |
224 | #ifdef __x86_64__ |
225 | , "r11" |
226 | #endif |
227 | ); |
228 | |
229 | /* We're unreachable here. SYSENTER forgets RIP. */ |
230 | } |
231 | clearhandler(sig: SIGSEGV); |
232 | clearhandler(sig: SIGILL); |
233 | if (!(sig_eflags & X86_EFLAGS_TF)) { |
234 | printf("[FAIL]\tTF was cleared\n" ); |
235 | exit(1); |
236 | } |
237 | |
238 | /* Now make sure that another fast syscall doesn't set TF again. */ |
239 | fast_syscall_no_tf(); |
240 | |
241 | return 0; |
242 | } |
243 | |