xref: /linux/tools/testing/selftests/ptrace/set_syscall_info.c (revision 00c010e130e58301db2ea0cec1eadc931e1cb8cf)
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