1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3 * Copyright (c) 2018-2025 Dmitry V. Levin <ldv@strace.io>
4 * All rights reserved.
5 *
6 * Check whether PTRACE_SET_SYSCALL_INFO semantics implemented in the kernel
7 * matches userspace expectations.
8 */
9
10 #include "../kselftest_harness.h"
11 #include <err.h>
12 #include <fcntl.h>
13 #include <signal.h>
14 #include <asm/unistd.h>
15 #include <linux/types.h>
16 #include <linux/ptrace.h>
17
18 #if defined(_MIPS_SIM) && _MIPS_SIM == _MIPS_SIM_NABI32
19 /*
20 * MIPS N32 is the only architecture where __kernel_ulong_t
21 * does not match the bitness of syscall arguments.
22 */
23 typedef unsigned long long kernel_ulong_t;
24 #else
25 typedef __kernel_ulong_t kernel_ulong_t;
26 #endif
27
28 struct si_entry {
29 int nr;
30 kernel_ulong_t args[6];
31 };
32 struct si_exit {
33 unsigned int is_error;
34 int rval;
35 };
36
37 static unsigned int ptrace_stop;
38 static pid_t tracee_pid;
39
40 static int
kill_tracee(pid_t pid)41 kill_tracee(pid_t pid)
42 {
43 if (!pid)
44 return 0;
45
46 int saved_errno = errno;
47
48 int rc = kill(pid, SIGKILL);
49
50 errno = saved_errno;
51 return rc;
52 }
53
54 static long
sys_ptrace(int request,pid_t pid,unsigned long addr,unsigned long data)55 sys_ptrace(int request, pid_t pid, unsigned long addr, unsigned long data)
56 {
57 return syscall(__NR_ptrace, request, pid, addr, data);
58 }
59
60 #define LOG_KILL_TRACEE(fmt, ...) \
61 do { \
62 kill_tracee(tracee_pid); \
63 TH_LOG("wait #%d: " fmt, \
64 ptrace_stop, ##__VA_ARGS__); \
65 } while (0)
66
67 static void
check_psi_entry(struct __test_metadata * _metadata,const struct ptrace_syscall_info * info,const struct si_entry * exp_entry,const char * text)68 check_psi_entry(struct __test_metadata *_metadata,
69 const struct ptrace_syscall_info *info,
70 const struct si_entry *exp_entry,
71 const char *text)
72 {
73 unsigned int i;
74 int exp_nr = exp_entry->nr;
75 #if defined __s390__ || defined __s390x__
76 /* s390 is the only architecture that has 16-bit syscall numbers */
77 exp_nr &= 0xffff;
78 #endif
79
80 ASSERT_EQ(PTRACE_SYSCALL_INFO_ENTRY, info->op) {
81 LOG_KILL_TRACEE("%s: entry stop mismatch", text);
82 }
83 ASSERT_TRUE(info->arch) {
84 LOG_KILL_TRACEE("%s: entry stop mismatch", text);
85 }
86 ASSERT_TRUE(info->instruction_pointer) {
87 LOG_KILL_TRACEE("%s: entry stop mismatch", text);
88 }
89 ASSERT_TRUE(info->stack_pointer) {
90 LOG_KILL_TRACEE("%s: entry stop mismatch", text);
91 }
92 ASSERT_EQ(exp_nr, info->entry.nr) {
93 LOG_KILL_TRACEE("%s: syscall nr mismatch", text);
94 }
95 for (i = 0; i < ARRAY_SIZE(exp_entry->args); ++i) {
96 ASSERT_EQ(exp_entry->args[i], info->entry.args[i]) {
97 LOG_KILL_TRACEE("%s: syscall arg #%u mismatch",
98 text, i);
99 }
100 }
101 }
102
103 static void
check_psi_exit(struct __test_metadata * _metadata,const struct ptrace_syscall_info * info,const struct si_exit * exp_exit,const char * text)104 check_psi_exit(struct __test_metadata *_metadata,
105 const struct ptrace_syscall_info *info,
106 const struct si_exit *exp_exit,
107 const char *text)
108 {
109 ASSERT_EQ(PTRACE_SYSCALL_INFO_EXIT, info->op) {
110 LOG_KILL_TRACEE("%s: exit stop mismatch", text);
111 }
112 ASSERT_TRUE(info->arch) {
113 LOG_KILL_TRACEE("%s: exit stop mismatch", text);
114 }
115 ASSERT_TRUE(info->instruction_pointer) {
116 LOG_KILL_TRACEE("%s: exit stop mismatch", text);
117 }
118 ASSERT_TRUE(info->stack_pointer) {
119 LOG_KILL_TRACEE("%s: exit stop mismatch", text);
120 }
121 ASSERT_EQ(exp_exit->is_error, info->exit.is_error) {
122 LOG_KILL_TRACEE("%s: exit stop mismatch", text);
123 }
124 ASSERT_EQ(exp_exit->rval, info->exit.rval) {
125 LOG_KILL_TRACEE("%s: exit stop mismatch", text);
126 }
127 }
128
TEST(set_syscall_info)129 TEST(set_syscall_info)
130 {
131 const pid_t tracer_pid = getpid();
132 const kernel_ulong_t dummy[] = {
133 (kernel_ulong_t) 0xdad0bef0bad0fed0ULL,
134 (kernel_ulong_t) 0xdad1bef1bad1fed1ULL,
135 (kernel_ulong_t) 0xdad2bef2bad2fed2ULL,
136 (kernel_ulong_t) 0xdad3bef3bad3fed3ULL,
137 (kernel_ulong_t) 0xdad4bef4bad4fed4ULL,
138 (kernel_ulong_t) 0xdad5bef5bad5fed5ULL,
139 };
140 int splice_in[2], splice_out[2];
141
142 ASSERT_EQ(0, pipe(splice_in));
143 ASSERT_EQ(0, pipe(splice_out));
144 ASSERT_EQ(sizeof(dummy), write(splice_in[1], dummy, sizeof(dummy)));
145
146 const struct {
147 struct si_entry entry[2];
148 struct si_exit exit[2];
149 } si[] = {
150 /* change scno, keep non-error rval */
151 {
152 {
153 {
154 __NR_gettid,
155 {
156 dummy[0], dummy[1], dummy[2],
157 dummy[3], dummy[4], dummy[5]
158 }
159 }, {
160 __NR_getppid,
161 {
162 dummy[0], dummy[1], dummy[2],
163 dummy[3], dummy[4], dummy[5]
164 }
165 }
166 }, {
167 { 0, tracer_pid }, { 0, tracer_pid }
168 }
169 },
170
171 /* set scno to -1, keep error rval */
172 {
173 {
174 {
175 __NR_chdir,
176 {
177 (uintptr_t) ".",
178 dummy[1], dummy[2],
179 dummy[3], dummy[4], dummy[5]
180 }
181 }, {
182 -1,
183 {
184 (uintptr_t) ".",
185 dummy[1], dummy[2],
186 dummy[3], dummy[4], dummy[5]
187 }
188 }
189 }, {
190 { 1, -ENOSYS }, { 1, -ENOSYS }
191 }
192 },
193
194 /* keep scno, change non-error rval */
195 {
196 {
197 {
198 __NR_getppid,
199 {
200 dummy[0], dummy[1], dummy[2],
201 dummy[3], dummy[4], dummy[5]
202 }
203 }, {
204 __NR_getppid,
205 {
206 dummy[0], dummy[1], dummy[2],
207 dummy[3], dummy[4], dummy[5]
208 }
209 }
210 }, {
211 { 0, tracer_pid }, { 0, tracer_pid + 1 }
212 }
213 },
214
215 /* change arg1, keep non-error rval */
216 {
217 {
218 {
219 __NR_chdir,
220 {
221 (uintptr_t) "",
222 dummy[1], dummy[2],
223 dummy[3], dummy[4], dummy[5]
224 }
225 }, {
226 __NR_chdir,
227 {
228 (uintptr_t) ".",
229 dummy[1], dummy[2],
230 dummy[3], dummy[4], dummy[5]
231 }
232 }
233 }, {
234 { 0, 0 }, { 0, 0 }
235 }
236 },
237
238 /* set scno to -1, change error rval to non-error */
239 {
240 {
241 {
242 __NR_gettid,
243 {
244 dummy[0], dummy[1], dummy[2],
245 dummy[3], dummy[4], dummy[5]
246 }
247 }, {
248 -1,
249 {
250 dummy[0], dummy[1], dummy[2],
251 dummy[3], dummy[4], dummy[5]
252 }
253 }
254 }, {
255 { 1, -ENOSYS }, { 0, tracer_pid }
256 }
257 },
258
259 /* change scno, change non-error rval to error */
260 {
261 {
262 {
263 __NR_chdir,
264 {
265 dummy[0], dummy[1], dummy[2],
266 dummy[3], dummy[4], dummy[5]
267 }
268 }, {
269 __NR_getppid,
270 {
271 dummy[0], dummy[1], dummy[2],
272 dummy[3], dummy[4], dummy[5]
273 }
274 }
275 }, {
276 { 0, tracer_pid }, { 1, -EISDIR }
277 }
278 },
279
280 /* change scno and all args, change non-error rval */
281 {
282 {
283 {
284 __NR_gettid,
285 {
286 dummy[0], dummy[1], dummy[2],
287 dummy[3], dummy[4], dummy[5]
288 }
289 }, {
290 __NR_splice,
291 {
292 splice_in[0], 0, splice_out[1], 0,
293 sizeof(dummy), SPLICE_F_NONBLOCK
294 }
295 }
296 }, {
297 { 0, sizeof(dummy) }, { 0, sizeof(dummy) + 1 }
298 }
299 },
300
301 /* change arg1, no exit stop */
302 {
303 {
304 {
305 __NR_exit_group,
306 {
307 dummy[0], dummy[1], dummy[2],
308 dummy[3], dummy[4], dummy[5]
309 }
310 }, {
311 __NR_exit_group,
312 {
313 0, dummy[1], dummy[2],
314 dummy[3], dummy[4], dummy[5]
315 }
316 }
317 }, {
318 { 0, 0 }, { 0, 0 }
319 }
320 },
321 };
322
323 long rc;
324 unsigned int i;
325
326 tracee_pid = fork();
327
328 ASSERT_LE(0, tracee_pid) {
329 TH_LOG("fork: %m");
330 }
331
332 if (tracee_pid == 0) {
333 /* get the pid before PTRACE_TRACEME */
334 tracee_pid = getpid();
335 ASSERT_EQ(0, sys_ptrace(PTRACE_TRACEME, 0, 0, 0)) {
336 TH_LOG("PTRACE_TRACEME: %m");
337 }
338 ASSERT_EQ(0, kill(tracee_pid, SIGSTOP)) {
339 /* cannot happen */
340 TH_LOG("kill SIGSTOP: %m");
341 }
342 for (i = 0; i < ARRAY_SIZE(si); ++i) {
343 rc = syscall(si[i].entry[0].nr,
344 si[i].entry[0].args[0],
345 si[i].entry[0].args[1],
346 si[i].entry[0].args[2],
347 si[i].entry[0].args[3],
348 si[i].entry[0].args[4],
349 si[i].entry[0].args[5]);
350 if (si[i].exit[1].is_error) {
351 if (rc != -1 || errno != -si[i].exit[1].rval)
352 break;
353 } else {
354 if (rc != si[i].exit[1].rval)
355 break;
356 }
357 }
358 /*
359 * Something went wrong, but in this state tracee
360 * cannot reliably issue syscalls, so just crash.
361 */
362 *(volatile unsigned char *) (uintptr_t) i = 42;
363 /* unreachable */
364 _exit(i + 1);
365 }
366
367 for (ptrace_stop = 0; ; ++ptrace_stop) {
368 struct ptrace_syscall_info info = {
369 .op = 0xff /* invalid PTRACE_SYSCALL_INFO_* op */
370 };
371 const size_t size = sizeof(info);
372 const int expected_entry_size =
373 (void *) &info.entry.args[6] - (void *) &info;
374 const int expected_exit_size =
375 (void *) (&info.exit.is_error + 1) -
376 (void *) &info;
377 int status;
378
379 ASSERT_EQ(tracee_pid, wait(&status)) {
380 /* cannot happen */
381 LOG_KILL_TRACEE("wait: %m");
382 }
383 if (WIFEXITED(status)) {
384 tracee_pid = 0; /* the tracee is no more */
385 ASSERT_EQ(0, WEXITSTATUS(status)) {
386 LOG_KILL_TRACEE("unexpected exit status %u",
387 WEXITSTATUS(status));
388 }
389 break;
390 }
391 ASSERT_FALSE(WIFSIGNALED(status)) {
392 tracee_pid = 0; /* the tracee is no more */
393 LOG_KILL_TRACEE("unexpected signal %u",
394 WTERMSIG(status));
395 }
396 ASSERT_TRUE(WIFSTOPPED(status)) {
397 /* cannot happen */
398 LOG_KILL_TRACEE("unexpected wait status %#x", status);
399 }
400
401 ASSERT_LT(ptrace_stop, ARRAY_SIZE(si) * 2) {
402 LOG_KILL_TRACEE("ptrace stop overflow");
403 }
404
405 switch (WSTOPSIG(status)) {
406 case SIGSTOP:
407 ASSERT_EQ(0, ptrace_stop) {
408 LOG_KILL_TRACEE("unexpected signal stop");
409 }
410 ASSERT_EQ(0, sys_ptrace(PTRACE_SETOPTIONS, tracee_pid,
411 0, PTRACE_O_TRACESYSGOOD)) {
412 LOG_KILL_TRACEE("PTRACE_SETOPTIONS: %m");
413 }
414 break;
415
416 case SIGTRAP | 0x80:
417 ASSERT_LT(0, ptrace_stop) {
418 LOG_KILL_TRACEE("unexpected syscall stop");
419 }
420 ASSERT_LT(0, (rc = sys_ptrace(PTRACE_GET_SYSCALL_INFO,
421 tracee_pid, size,
422 (uintptr_t) &info))) {
423 LOG_KILL_TRACEE("PTRACE_GET_SYSCALL_INFO #1: %m");
424 }
425 if (ptrace_stop & 1) {
426 /* entering syscall */
427 const struct si_entry *exp_entry =
428 &si[ptrace_stop / 2].entry[0];
429 const struct si_entry *set_entry =
430 &si[ptrace_stop / 2].entry[1];
431
432 /* check ptrace_syscall_info before the changes */
433 ASSERT_EQ(expected_entry_size, rc) {
434 LOG_KILL_TRACEE("PTRACE_GET_SYSCALL_INFO #1"
435 ": entry stop mismatch");
436 }
437 check_psi_entry(_metadata, &info, exp_entry,
438 "PTRACE_GET_SYSCALL_INFO #1");
439
440 /* apply the changes */
441 info.entry.nr = set_entry->nr;
442 for (i = 0; i < ARRAY_SIZE(set_entry->args); ++i)
443 info.entry.args[i] = set_entry->args[i];
444 ASSERT_EQ(0, sys_ptrace(PTRACE_SET_SYSCALL_INFO,
445 tracee_pid, size,
446 (uintptr_t) &info)) {
447 LOG_KILL_TRACEE("PTRACE_SET_SYSCALL_INFO: %m");
448 }
449
450 /* check ptrace_syscall_info after the changes */
451 memset(&info, 0, sizeof(info));
452 info.op = 0xff;
453 ASSERT_LT(0, (rc = sys_ptrace(PTRACE_GET_SYSCALL_INFO,
454 tracee_pid, size,
455 (uintptr_t) &info))) {
456 LOG_KILL_TRACEE("PTRACE_GET_SYSCALL_INFO: %m");
457 }
458 ASSERT_EQ(expected_entry_size, rc) {
459 LOG_KILL_TRACEE("PTRACE_GET_SYSCALL_INFO #2"
460 ": entry stop mismatch");
461 }
462 check_psi_entry(_metadata, &info, set_entry,
463 "PTRACE_GET_SYSCALL_INFO #2");
464 } else {
465 /* exiting syscall */
466 const struct si_exit *exp_exit =
467 &si[ptrace_stop / 2 - 1].exit[0];
468 const struct si_exit *set_exit =
469 &si[ptrace_stop / 2 - 1].exit[1];
470
471 /* check ptrace_syscall_info before the changes */
472 ASSERT_EQ(expected_exit_size, rc) {
473 LOG_KILL_TRACEE("PTRACE_GET_SYSCALL_INFO #1"
474 ": exit stop mismatch");
475 }
476 check_psi_exit(_metadata, &info, exp_exit,
477 "PTRACE_GET_SYSCALL_INFO #1");
478
479 /* apply the changes */
480 info.exit.is_error = set_exit->is_error;
481 info.exit.rval = set_exit->rval;
482 ASSERT_EQ(0, sys_ptrace(PTRACE_SET_SYSCALL_INFO,
483 tracee_pid, size,
484 (uintptr_t) &info)) {
485 LOG_KILL_TRACEE("PTRACE_SET_SYSCALL_INFO: %m");
486 }
487
488 /* check ptrace_syscall_info after the changes */
489 memset(&info, 0, sizeof(info));
490 info.op = 0xff;
491 ASSERT_LT(0, (rc = sys_ptrace(PTRACE_GET_SYSCALL_INFO,
492 tracee_pid, size,
493 (uintptr_t) &info))) {
494 LOG_KILL_TRACEE("PTRACE_GET_SYSCALL_INFO #2: %m");
495 }
496 ASSERT_EQ(expected_exit_size, rc) {
497 LOG_KILL_TRACEE("PTRACE_GET_SYSCALL_INFO #2"
498 ": exit stop mismatch");
499 }
500 check_psi_exit(_metadata, &info, set_exit,
501 "PTRACE_GET_SYSCALL_INFO #2");
502 }
503 break;
504
505 default:
506 LOG_KILL_TRACEE("unexpected stop signal %u",
507 WSTOPSIG(status));
508 abort();
509 }
510
511 ASSERT_EQ(0, sys_ptrace(PTRACE_SYSCALL, tracee_pid, 0, 0)) {
512 LOG_KILL_TRACEE("PTRACE_SYSCALL: %m");
513 }
514 }
515
516 ASSERT_EQ(ptrace_stop, ARRAY_SIZE(si) * 2);
517 }
518
519 TEST_HARNESS_MAIN
520