xref: /linux/tools/testing/selftests/x86/int_signal.c (revision 156fa7417fac89fd9dcf3a4ee88785ff90ab6411)
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, &regs))
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, &regs))
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, &regs);
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