1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * Copyright (c) 2018 Dmitry V. Levin <ldv@altlinux.org>
4  * All rights reserved.
5  *
6  * Check whether PTRACE_GET_SYSCALL_INFO semantics implemented in the kernel
7  * matches userspace expectations.
8  */
9 
10 #include "../kselftest_harness.h"
11 #include <err.h>
12 #include <signal.h>
13 #include <asm/unistd.h>
14 #include "linux/ptrace.h"
15 
16 static int
17 kill_tracee(pid_t pid)
18 {
19 	if (!pid)
20 		return 0;
21 
22 	int saved_errno = errno;
23 
24 	int rc = kill(pid, SIGKILL);
25 
26 	errno = saved_errno;
27 	return rc;
28 }
29 
30 static long
31 sys_ptrace(int request, pid_t pid, unsigned long addr, unsigned long data)
32 {
33 	return syscall(__NR_ptrace, request, pid, addr, data);
34 }
35 
36 #define LOG_KILL_TRACEE(fmt, ...)				\
37 	do {							\
38 		kill_tracee(pid);				\
39 		TH_LOG("wait #%d: " fmt,			\
40 		       ptrace_stop, ##__VA_ARGS__);		\
41 	} while (0)
42 
43 TEST(get_syscall_info)
44 {
45 	static const unsigned long args[][7] = {
46 		/* a sequence of architecture-agnostic syscalls */
47 		{
48 			__NR_chdir,
49 			(unsigned long) "",
50 			0xbad1fed1,
51 			0xbad2fed2,
52 			0xbad3fed3,
53 			0xbad4fed4,
54 			0xbad5fed5
55 		},
56 		{
57 			__NR_gettid,
58 			0xcaf0bea0,
59 			0xcaf1bea1,
60 			0xcaf2bea2,
61 			0xcaf3bea3,
62 			0xcaf4bea4,
63 			0xcaf5bea5
64 		},
65 		{
66 			__NR_exit_group,
67 			0,
68 			0xfac1c0d1,
69 			0xfac2c0d2,
70 			0xfac3c0d3,
71 			0xfac4c0d4,
72 			0xfac5c0d5
73 		}
74 	};
75 	const unsigned long *exp_args;
76 
77 	pid_t pid = fork();
78 
79 	ASSERT_LE(0, pid) {
80 		TH_LOG("fork: %m");
81 	}
82 
83 	if (pid == 0) {
84 		/* get the pid before PTRACE_TRACEME */
85 		pid = getpid();
86 		ASSERT_EQ(0, sys_ptrace(PTRACE_TRACEME, 0, 0, 0)) {
87 			TH_LOG("PTRACE_TRACEME: %m");
88 		}
89 		ASSERT_EQ(0, kill(pid, SIGSTOP)) {
90 			/* cannot happen */
91 			TH_LOG("kill SIGSTOP: %m");
92 		}
93 		for (unsigned int i = 0; i < ARRAY_SIZE(args); ++i) {
94 			syscall(args[i][0],
95 				args[i][1], args[i][2], args[i][3],
96 				args[i][4], args[i][5], args[i][6]);
97 		}
98 		/* unreachable */
99 		_exit(1);
100 	}
101 
102 	const struct {
103 		unsigned int is_error;
104 		int rval;
105 	} *exp_param, exit_param[] = {
106 		{ 1, -ENOENT },	/* chdir */
107 		{ 0, pid }	/* gettid */
108 	};
109 
110 	unsigned int ptrace_stop;
111 
112 	for (ptrace_stop = 0; ; ++ptrace_stop) {
113 		struct ptrace_syscall_info info = {
114 			.op = 0xff	/* invalid PTRACE_SYSCALL_INFO_* op */
115 		};
116 		const size_t size = sizeof(info);
117 		const int expected_none_size =
118 			(void *) &info.entry - (void *) &info;
119 		const int expected_entry_size =
120 			(void *) &info.entry.args[6] - (void *) &info;
121 		const int expected_exit_size =
122 			(void *) (&info.exit.is_error + 1) -
123 			(void *) &info;
124 		int status;
125 		long rc;
126 
127 		ASSERT_EQ(pid, wait(&status)) {
128 			/* cannot happen */
129 			LOG_KILL_TRACEE("wait: %m");
130 		}
131 		if (WIFEXITED(status)) {
132 			pid = 0;	/* the tracee is no more */
133 			ASSERT_EQ(0, WEXITSTATUS(status));
134 			break;
135 		}
136 		ASSERT_FALSE(WIFSIGNALED(status)) {
137 			pid = 0;	/* the tracee is no more */
138 			LOG_KILL_TRACEE("unexpected signal %u",
139 					WTERMSIG(status));
140 		}
141 		ASSERT_TRUE(WIFSTOPPED(status)) {
142 			/* cannot happen */
143 			LOG_KILL_TRACEE("unexpected wait status %#x", status);
144 		}
145 
146 		switch (WSTOPSIG(status)) {
147 		case SIGSTOP:
148 			ASSERT_EQ(0, ptrace_stop) {
149 				LOG_KILL_TRACEE("unexpected signal stop");
150 			}
151 			ASSERT_EQ(0, sys_ptrace(PTRACE_SETOPTIONS, pid, 0,
152 						PTRACE_O_TRACESYSGOOD)) {
153 				LOG_KILL_TRACEE("PTRACE_SETOPTIONS: %m");
154 			}
155 			ASSERT_LT(0, (rc = sys_ptrace(PTRACE_GET_SYSCALL_INFO,
156 						      pid, size,
157 						      (unsigned long) &info))) {
158 				LOG_KILL_TRACEE("PTRACE_GET_SYSCALL_INFO: %m");
159 			}
160 			ASSERT_EQ(expected_none_size, rc) {
161 				LOG_KILL_TRACEE("signal stop mismatch");
162 			}
163 			ASSERT_EQ(PTRACE_SYSCALL_INFO_NONE, info.op) {
164 				LOG_KILL_TRACEE("signal stop mismatch");
165 			}
166 			ASSERT_TRUE(info.arch) {
167 				LOG_KILL_TRACEE("signal stop mismatch");
168 			}
169 			ASSERT_TRUE(info.instruction_pointer) {
170 				LOG_KILL_TRACEE("signal stop mismatch");
171 			}
172 			ASSERT_TRUE(info.stack_pointer) {
173 				LOG_KILL_TRACEE("signal stop mismatch");
174 			}
175 			break;
176 
177 		case SIGTRAP | 0x80:
178 			ASSERT_LT(0, (rc = sys_ptrace(PTRACE_GET_SYSCALL_INFO,
179 						      pid, size,
180 						      (unsigned long) &info))) {
181 				LOG_KILL_TRACEE("PTRACE_GET_SYSCALL_INFO: %m");
182 			}
183 			switch (ptrace_stop) {
184 			case 1: /* entering chdir */
185 			case 3: /* entering gettid */
186 			case 5: /* entering exit_group */
187 				exp_args = args[ptrace_stop / 2];
188 				ASSERT_EQ(expected_entry_size, rc) {
189 					LOG_KILL_TRACEE("entry stop mismatch");
190 				}
191 				ASSERT_EQ(PTRACE_SYSCALL_INFO_ENTRY, info.op) {
192 					LOG_KILL_TRACEE("entry stop mismatch");
193 				}
194 				ASSERT_TRUE(info.arch) {
195 					LOG_KILL_TRACEE("entry stop mismatch");
196 				}
197 				ASSERT_TRUE(info.instruction_pointer) {
198 					LOG_KILL_TRACEE("entry stop mismatch");
199 				}
200 				ASSERT_TRUE(info.stack_pointer) {
201 					LOG_KILL_TRACEE("entry stop mismatch");
202 				}
203 				ASSERT_EQ(exp_args[0], info.entry.nr) {
204 					LOG_KILL_TRACEE("entry stop mismatch");
205 				}
206 				ASSERT_EQ(exp_args[1], info.entry.args[0]) {
207 					LOG_KILL_TRACEE("entry stop mismatch");
208 				}
209 				ASSERT_EQ(exp_args[2], info.entry.args[1]) {
210 					LOG_KILL_TRACEE("entry stop mismatch");
211 				}
212 				ASSERT_EQ(exp_args[3], info.entry.args[2]) {
213 					LOG_KILL_TRACEE("entry stop mismatch");
214 				}
215 				ASSERT_EQ(exp_args[4], info.entry.args[3]) {
216 					LOG_KILL_TRACEE("entry stop mismatch");
217 				}
218 				ASSERT_EQ(exp_args[5], info.entry.args[4]) {
219 					LOG_KILL_TRACEE("entry stop mismatch");
220 				}
221 				ASSERT_EQ(exp_args[6], info.entry.args[5]) {
222 					LOG_KILL_TRACEE("entry stop mismatch");
223 				}
224 				break;
225 			case 2: /* exiting chdir */
226 			case 4: /* exiting gettid */
227 				exp_param = &exit_param[ptrace_stop / 2 - 1];
228 				ASSERT_EQ(expected_exit_size, rc) {
229 					LOG_KILL_TRACEE("exit stop mismatch");
230 				}
231 				ASSERT_EQ(PTRACE_SYSCALL_INFO_EXIT, info.op) {
232 					LOG_KILL_TRACEE("exit stop mismatch");
233 				}
234 				ASSERT_TRUE(info.arch) {
235 					LOG_KILL_TRACEE("exit stop mismatch");
236 				}
237 				ASSERT_TRUE(info.instruction_pointer) {
238 					LOG_KILL_TRACEE("exit stop mismatch");
239 				}
240 				ASSERT_TRUE(info.stack_pointer) {
241 					LOG_KILL_TRACEE("exit stop mismatch");
242 				}
243 				ASSERT_EQ(exp_param->is_error,
244 					  info.exit.is_error) {
245 					LOG_KILL_TRACEE("exit stop mismatch");
246 				}
247 				ASSERT_EQ(exp_param->rval, info.exit.rval) {
248 					LOG_KILL_TRACEE("exit stop mismatch");
249 				}
250 				break;
251 			default:
252 				LOG_KILL_TRACEE("unexpected syscall stop");
253 				abort();
254 			}
255 			break;
256 
257 		default:
258 			LOG_KILL_TRACEE("unexpected stop signal %#x",
259 					WSTOPSIG(status));
260 			abort();
261 		}
262 
263 		ASSERT_EQ(0, sys_ptrace(PTRACE_SYSCALL, pid, 0, 0)) {
264 			LOG_KILL_TRACEE("PTRACE_SYSCALL: %m");
265 		}
266 	}
267 
268 	ASSERT_EQ(ARRAY_SIZE(args) * 2, ptrace_stop);
269 }
270 
271 TEST_HARNESS_MAIN
272