1 // SPDX-License-Identifier: GPL-2.0-only
2 /* Check the signal context for INT instructions with IDT and FRED entry. */
3 #define _GNU_SOURCE
4
5 #include <cpuid.h>
6 #include <errno.h>
7 #include <stdbool.h>
8 #include <stddef.h>
9 #include <stdint.h>
10 #include <sys/ptrace.h>
11 #include <sys/user.h>
12 #include <sys/wait.h>
13 #include <unistd.h>
14 #include <ucontext.h>
15
16 #include "helpers.h"
17
18 #ifdef __x86_64__
19 #define REG_IP REG_RIP
20 #define USER_IP rip
21 #define STACK_PTR "%rsp"
22 #else
23 #define REG_IP REG_EIP
24 #define USER_IP eip
25 #define STACK_PTR "%esp"
26 #endif
27
28 /*
29 * Each instruction has normal and single-step entry points. Resume at the
30 * NOP after handling its signal, then expect a trace trap after that NOP
31 * when TF is set. Explicit labels avoid assuming the kernel's saved IP.
32 */
33 #define PROBE(name, insn) \
34 extern void name(void); \
35 extern void name##_tf(void); \
36 extern const char name##_end[], name##_step[]; \
37 asm(".pushsection .text\n" \
38 ".globl " #name "_tf\n" \
39 ".type " #name "_tf, @function\n" \
40 #name "_tf:\n" \
41 "pushf\n" \
42 "orl $0x100, (" STACK_PTR ")\n" \
43 "popf\n" \
44 ".globl " #name "\n" \
45 ".type " #name ", @function\n" \
46 #name ":\n" insn "\n" \
47 ".globl " #name "_end\n" \
48 #name "_end:\nnop\n" \
49 ".globl " #name "_step\n" \
50 #name "_step:\nret\n" \
51 ".size " #name ", .-" #name "\n" \
52 ".size " #name "_tf, .-" #name "_tf\n" \
53 ".popsection\n")
54
55 PROBE(int1, ".byte 0xcd, 0x01");
56 PROBE(int29, ".byte 0xcd, 0x29");
57 PROBE(int2c, ".byte 0xcd, 0x2c");
58 PROBE(int2d, ".byte 0xcd, 0x2d");
59 PROBE(prefixed_int2d, ".byte 0x66, 0xcd, 0x2d");
60 PROBE(long_int2d, ".fill 13, 1, 0x2e\n.byte 0xcd, 0x2d");
61 PROBE(int81, ".byte 0xcd, 0x81");
62 PROBE(intff, ".byte 0xcd, 0xff");
63 PROBE(short_int3, ".byte 0xcc");
64 PROBE(long_int3, ".byte 0xcd, 0x03");
65 PROBE(int4, ".byte 0xcd, 0x04");
66 PROBE(ud2, ".byte 0x0f, 0x0b");
67 PROBE(hlt, ".byte 0xf4");
68
69 struct test {
70 const char *name;
71 void (*run)(void);
72 void (*run_tf)(void);
73 const char *end, *step;
74 int signo, trap, error, ip_offset, flags, code;
75 };
76
77 #define TEST(name, sig, trap, error, offset, flags, code) \
78 { #name, name, name##_tf, name##_end, name##_step, \
79 sig, trap, error, offset, flags, code }
80
81 #define GP(name, error) \
82 TEST(name, SIGSEGV, 13, error, 0, X86_EFLAGS_RF, SI_KERNEL)
83
84 static const struct test tests[] = {
85 GP(int1, 0x00a),
86 GP(int29, 0x14a),
87 GP(int2c, 0x162),
88 GP(int2d, 0x16a),
89 GP(prefixed_int2d, 0x16a),
90 GP(long_int2d, 0x16a),
91 GP(int81, 0x40a),
92 GP(intff, 0x7fa),
93 GP(hlt, 0),
94 TEST(short_int3, SIGTRAP, 3, 0, 1, 0, SI_KERNEL),
95 TEST(long_int3, SIGTRAP, 3, 0, 2, 0, SI_KERNEL),
96 TEST(int4, SIGSEGV, 4, 0, 2, 0, SI_KERNEL),
97 TEST(ud2, SIGILL, 6, 0, 0, X86_EFLAGS_RF, ILL_ILLOPN),
98 };
99
100 static const struct test *active;
101 static volatile sig_atomic_t seen, signo, trap, error, ip_offset, flags;
102 static volatile sig_atomic_t code, addr_ok, single_step, stepped, step_ok;
103
handler(int sig,siginfo_t * info,void * context)104 static void handler(int sig, siginfo_t *info, void *context)
105 {
106 ucontext_t *uc = context;
107 uintptr_t ip = uc->uc_mcontext.gregs[REG_IP];
108 uintptr_t start = (uintptr_t)active->run;
109 uintptr_t end = (uintptr_t)active->end;
110
111 if (seen && single_step && sig == SIGTRAP) {
112 if (stepped++) {
113 ksft_print_msg("%s: second trace trap at %#lx\n",
114 active->name, (unsigned long)ip);
115 _exit(KSFT_FAIL);
116 }
117 step_ok = ip == (uintptr_t)active->step &&
118 uc->uc_mcontext.gregs[REG_TRAPNO] == 1 &&
119 info->si_code == TRAP_TRACE;
120 uc->uc_mcontext.gregs[REG_EFL] &= ~X86_EFLAGS_TF;
121 return;
122 }
123
124 if (seen || ip < start || ip > end) {
125 ksft_print_msg("%s: unexpected signal %d at %#lx\n",
126 active->name, sig, (unsigned long)ip);
127 _exit(KSFT_FAIL);
128 }
129
130 signo = sig;
131 trap = uc->uc_mcontext.gregs[REG_TRAPNO];
132 error = uc->uc_mcontext.gregs[REG_ERR];
133 ip_offset = ip - start;
134 flags = uc->uc_mcontext.gregs[REG_EFL] & (X86_EFLAGS_RF | X86_EFLAGS_TF);
135 code = info->si_code;
136 /* force_sig() reports no address, force_sig_fault() reports the IP. */
137 addr_ok = info->si_addr == (code == SI_KERNEL ? NULL : (void *)ip);
138 seen = 1;
139 uc->uc_mcontext.gregs[REG_IP] = end;
140 }
141
wait_for_child(pid_t child,int * status)142 static void wait_for_child(pid_t child, int *status)
143 {
144 pid_t ret;
145
146 do {
147 ret = waitpid(child, status, 0);
148 } while (ret < 0 && errno == EINTR);
149 if (ret != child)
150 ksft_exit_fail_perror("waitpid");
151 }
152
153 /* Resume the tracee and check where the next stop lands. */
resume_to(pid_t child,int * status,int request,int sig,const void * ip,const char * what)154 static bool resume_to(pid_t child, int *status, int request, int sig,
155 const void *ip, const char *what)
156 {
157 struct user_regs_struct regs;
158
159 if (ptrace(request, child, 0, 0))
160 return false;
161 wait_for_child(child, status);
162 if (!WIFSTOPPED(*status)) {
163 ksft_print_msg("%s: tracee did not stop\n", what);
164 return false;
165 }
166 if (WSTOPSIG(*status) != sig) {
167 ksft_print_msg("%s: stopped with signal %d, expected %d\n",
168 what, WSTOPSIG(*status), sig);
169 return false;
170 }
171 if (ptrace(PTRACE_GETREGS, child, 0, ®s))
172 return false;
173 if ((unsigned long)regs.USER_IP != (unsigned long)ip) {
174 ksft_print_msg("%s: stopped at %#lx, expected %#lx\n", what,
175 (unsigned long)regs.USER_IP, (unsigned long)ip);
176 return false;
177 }
178 return true;
179 }
180
set_ip(pid_t child,const void * ip,bool tf)181 static bool set_ip(pid_t child, const void *ip, bool tf)
182 {
183 struct user_regs_struct regs;
184
185 if (ptrace(PTRACE_GETREGS, child, 0, ®s))
186 return false;
187 regs.USER_IP = (unsigned long)ip;
188 if (tf)
189 regs.eflags |= X86_EFLAGS_TF;
190 return !ptrace(PTRACE_SETREGS, child, 0, ®s);
191 }
192
193 /*
194 * Exercise the tracer paths that resume through the fault frame rather than
195 * sigreturn. A stale FRED software event flag on that frame traps before the
196 * NOP executes instead of after it.
197 */
test_ptrace(void)198 static void test_ptrace(void)
199 {
200 bool into = false, step = false, cont = false;
201 pid_t child;
202 int status;
203
204 child = fork();
205 if (child < 0)
206 ksft_exit_fail_perror("fork");
207 if (!child) {
208 if (ptrace(PTRACE_TRACEME, 0, 0, 0))
209 _exit(KSFT_FAIL);
210 /* Start from a breakpoint frame, not the syscall frame of raise(). */
211 asm volatile("int3");
212 _exit(KSFT_FAIL);
213 }
214
215 wait_for_child(child, &status);
216 if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGTRAP)
217 goto out;
218 if (ptrace(PTRACE_SETOPTIONS, child, 0, PTRACE_O_EXITKILL))
219 goto out;
220
221 /* Single-step into the INT. The fault must report the INT's address. */
222 if (!set_ip(child, int2d, false))
223 goto out;
224 into = resume_to(child, &status, PTRACE_SINGLESTEP, SIGSEGV, int2d,
225 "single-step into INT");
226 if (!into)
227 goto out;
228
229 /* Suppress SIGSEGV and single-step the NOP. */
230 if (!set_ip(child, int2d_end, false))
231 goto out;
232 step = resume_to(child, &status, PTRACE_SINGLESTEP, SIGTRAP, int2d_step,
233 "single-step after INT");
234 if (!step)
235 goto out;
236
237 /* Fault again, then suppress SIGSEGV and continue with TF set. */
238 if (!set_ip(child, int2d, false))
239 goto out;
240 if (!resume_to(child, &status, PTRACE_CONT, SIGSEGV, int2d,
241 "continue to INT"))
242 goto out;
243 if (!set_ip(child, int2d_end, true))
244 goto out;
245 cont = resume_to(child, &status, PTRACE_CONT, SIGTRAP, int2d_step,
246 "continue with TF after INT");
247 out:
248 if (WIFSTOPPED(status)) {
249 kill(child, SIGKILL);
250 wait_for_child(child, &status);
251 }
252 ksft_test_result(into, "ptrace single-step into INT faults at the INT\n");
253 ksft_test_result(step, "ptrace single-step after suppressing SIGSEGV\n");
254 ksft_test_result(cont, "ptrace continue with TF after suppressing SIGSEGV\n");
255 }
256
cpu_has_fred(void)257 static bool cpu_has_fred(void)
258 {
259 unsigned int eax, ebx, ecx, edx;
260
261 if (__get_cpuid_max(0, NULL) < 7)
262 return false;
263 __cpuid_count(7, 1, eax, ebx, ecx, edx);
264 return eax & (1 << 17);
265 }
266
main(void)267 int main(void)
268 {
269 unsigned int i, tf;
270 int expected_flags, ok;
271
272 ksft_print_header();
273 ksft_set_plan(2 * ARRAY_SIZE(tests) + 3);
274 ksft_print_msg("CPU %s FRED\n", cpu_has_fred() ? "supports" : "lacks");
275 sethandler(SIGSEGV, handler, 0);
276 sethandler(SIGTRAP, handler, 0);
277 sethandler(SIGILL, handler, 0);
278
279 for (tf = 0; tf < 2; tf++) {
280 for (i = 0; i < ARRAY_SIZE(tests); i++) {
281 active = &tests[i];
282 single_step = tf;
283 seen = signo = trap = error = ip_offset = flags = 0;
284 code = addr_ok = stepped = step_ok = 0;
285 expected_flags = active->flags | (tf ? X86_EFLAGS_TF : 0);
286 if (tf)
287 active->run_tf();
288 else
289 active->run();
290
291 ok = seen && signo == active->signo && trap == active->trap &&
292 error == active->error && ip_offset == active->ip_offset &&
293 flags == expected_flags && code == active->code && addr_ok &&
294 (!tf || (stepped && step_ok));
295 ksft_test_result(ok, "%s%s\n", active->name, tf ? " with TF" : "");
296 if (!ok) {
297 ksft_print_msg("got signal=%d trap=%d error=%#x ip=%d\n",
298 signo, trap, error, ip_offset);
299 ksft_print_msg("got flags=%#x code=%d addr_ok=%d step_ok=%d\n",
300 flags, code, addr_ok, step_ok);
301 ksft_print_msg("expected signal=%d trap=%d error=%#x ip=%d\n",
302 active->signo, active->trap, active->error,
303 active->ip_offset);
304 ksft_print_msg("expected flags=%#x code=%d\n",
305 expected_flags, active->code);
306 }
307 }
308 }
309 test_ptrace();
310 ksft_finished();
311 }
312