File Index
Files and directories
Sign in
FILE

set_syscall_info.c

File preview with syntax highlighting.

linux-6.19.14/tools/testing/selftests/ptrace/set_syscall_info.c C
  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 */
 23typedef unsigned long long kernel_ulong_t;
 24#else
 25typedef __kernel_ulong_t kernel_ulong_t;
 26#endif
 27
 28struct si_entry {
 29	int nr;
 30	kernel_ulong_t args[6];
 31};
 32struct si_exit {
 33	unsigned int is_error;
 34	int rval;
 35};
 36
 37static unsigned int ptrace_stop;
 38static pid_t tracee_pid;
 39
 40static int
 41kill_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
 54static long
 55sys_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
 67static void
 68check_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
103static void
104check_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
129TEST(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
519TEST_HARNESS_MAIN
~/ Read-only file preview.