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 | |