]> granicus.if.org Git - strace/blob - tests/ptrace_syscall_info.c
Implement PTRACE_GET_SYSCALL_INFO decoder
[strace] / tests / ptrace_syscall_info.c
1 /*
2  * Check decoding of ptrace PTRACE_GET_SYSCALL_INFO request.
3  *
4  * Copyright (c) 2018 Dmitry V. Levin <ldv@altlinux.org>
5  * All rights reserved.
6  *
7  * SPDX-License-Identifier: GPL-2.0-or-later
8  */
9
10 #include "tests.h"
11
12 #include "ptrace.h"
13 #include <asm/unistd.h>
14 #include "scno.h"
15
16 #include <errno.h>
17 #include <stddef.h>
18 #include <stdio.h>
19 #include <string.h>
20 #include <signal.h>
21 #include <sys/wait.h>
22 #include <unistd.h>
23 #include <linux/audit.h>
24
25 #include "xlat.h"
26 #include "xlat/audit_arch.h"
27
28 static const char *errstr;
29
30 static long
31 do_ptrace(unsigned long request, unsigned long pid,
32           unsigned long addr, unsigned long data)
33 {
34         long rc = syscall(__NR_ptrace, request, pid, addr, data);
35         errstr = sprintrc(rc);
36         return rc;
37 }
38
39 static pid_t pid;
40
41 static void
42 kill_tracee(void)
43 {
44         if (!pid)
45                 return;
46         int saved_errno = errno;
47         kill(pid, SIGKILL);
48         errno = saved_errno;
49 }
50
51 #define FAIL(fmt_, ...)                                                 \
52         do {                                                            \
53                 kill_tracee();                                          \
54                 error_msg_and_fail("%s:%d: " fmt_,                      \
55                                    __FILE__, __LINE__, ##__VA_ARGS__);  \
56         } while (0)
57
58 #define PFAIL(fmt_, ...)                                                \
59         do {                                                            \
60                 kill_tracee();                                          \
61                 perror_msg_and_fail("%s:%d: " fmt_,                     \
62                                     __FILE__, __LINE__, ##__VA_ARGS__); \
63         } while (0)
64
65 static const unsigned long args[][7] = {
66         /* a sequence of architecture-agnostic syscalls */
67         {
68                 __NR_chdir,
69                 (unsigned long) "",
70                 0xbad1fed1,
71                 0xbad2fed2,
72                 0xbad3fed3,
73                 0xbad4fed4,
74                 0xbad5fed5
75         },
76         {
77                 __NR_gettid,
78                 0xcaf0bea0,
79                 0xcaf1bea1,
80                 0xcaf2bea2,
81                 0xcaf3bea3,
82                 0xcaf4bea4,
83                 0xcaf5bea5
84         },
85         {
86                 __NR_exit_group,
87                 0,
88                 0xfac1c0d1,
89                 0xfac2c0d2,
90                 0xfac3c0d3,
91                 0xfac4c0d4,
92                 0xfac5c0d5
93         }
94 };
95
96 static const unsigned int expected_none_size =
97         offsetof(struct ptrace_syscall_info, entry);
98 static const unsigned int expected_entry_size =
99         offsetofend(struct ptrace_syscall_info, entry.args);
100 static const unsigned int expected_exit_size =
101         offsetofend(struct ptrace_syscall_info, exit.is_error);
102
103 static unsigned long end_of_page;
104 static unsigned int ptrace_stop;
105
106 static bool
107 test_none(void)
108 {
109         do_ptrace(PTRACE_GET_SYSCALL_INFO, pid, 1, 0);
110         printf("ptrace(PTRACE_GET_SYSCALL_INFO, %d, 1, NULL) = %s\n",
111                pid, errstr);
112
113         do_ptrace(PTRACE_GET_SYSCALL_INFO, pid, 1, end_of_page);
114         printf("ptrace(PTRACE_GET_SYSCALL_INFO, %d, 1, %#lx) = %s\n",
115                pid, end_of_page, errstr);
116
117         for (unsigned int size = 0;
118              size <= sizeof(struct ptrace_syscall_info); ++size) {
119                 unsigned long buf = end_of_page - size;
120                 memset((void *) buf, -1, size);
121
122                 long rc = do_ptrace(PTRACE_GET_SYSCALL_INFO, pid, size, buf);
123                 if (rc < 0) {
124                         printf("ptrace(PTRACE_GET_SYSCALL_INFO, %d, %u, %#lx)"
125                                " = %s\n",
126                                pid, (unsigned int) size, buf, errstr);
127                         return false;
128                 }
129                 if (rc < (long) expected_none_size)
130                         FAIL("signal stop mismatch");
131
132                 printf("ptrace(PTRACE_GET_SYSCALL_INFO, %d, %u, ",
133                        pid, size);
134                 if (!size) {
135                         printf("%#lx) = %s\n", buf, errstr);
136                         continue;
137                 }
138
139                 /* copy to a local structure to avoid unaligned access */
140                 struct ptrace_syscall_info info;
141                 memcpy(&info, (void *) buf,  MIN(size, expected_none_size));
142
143                 if (info.op != PTRACE_SYSCALL_INFO_NONE)
144                         FAIL("signal stop mismatch");
145                 printf("{op=PTRACE_SYSCALL_INFO_NONE");
146
147                 if (size < offsetofend(struct ptrace_syscall_info, arch))
148                         goto printed_none;
149                 if (!info.arch)
150                         FAIL("signal stop mismatch");
151                 printf(", arch=");
152                 printxval(audit_arch, info.arch, "AUDIT_ARCH_???");
153
154                 if (size < offsetofend(struct ptrace_syscall_info,
155                                        instruction_pointer))
156                         goto printed_none;
157                 if (!info.instruction_pointer)
158                         FAIL("signal stop mismatch");
159                 printf(", instruction_pointer=%#llx",
160                        (unsigned long long) info.instruction_pointer);
161
162                 if (size < offsetofend(struct ptrace_syscall_info,
163                                        stack_pointer))
164                         goto printed_none;
165                 if (!info.stack_pointer)
166                         FAIL("signal stop mismatch");
167                 printf(", stack_pointer=%#llx",
168                        (unsigned long long) info.stack_pointer);
169
170 printed_none:
171                 printf("}) = %s\n", errstr);
172         }
173
174         return true;
175 }
176
177 static void
178 test_entry(void)
179 {
180         for (unsigned int size = 0;
181              size <= sizeof(struct ptrace_syscall_info); ++size) {
182                 unsigned long buf = end_of_page - size;
183                 memset((void *) buf, -1, size);
184
185                 long rc = do_ptrace(PTRACE_GET_SYSCALL_INFO, pid, size, buf);
186                 if (rc < 0)
187                         PFAIL("PTRACE_GET_SYSCALL_INFO");
188
189                 if (rc < (long) expected_entry_size)
190                         FAIL("#%d: entry stop mismatch", ptrace_stop);
191
192                 printf("ptrace(PTRACE_GET_SYSCALL_INFO, %d, %u, ",
193                        pid, size);
194                 if (!size) {
195                         printf("%#lx) = %s\n", buf, errstr);
196                         continue;
197                 }
198
199                 /* copy to a local structure to avoid unaligned access */
200                 struct ptrace_syscall_info info;
201                 memcpy(&info, (void *) buf,  MIN(size, expected_entry_size));
202
203                 if (info.op != PTRACE_SYSCALL_INFO_ENTRY)
204                         FAIL("#%d: entry stop mismatch", ptrace_stop);
205                 printf("{op=PTRACE_SYSCALL_INFO_ENTRY");
206
207                 if (size < offsetofend(struct ptrace_syscall_info, arch))
208                         goto printed_entry_common;
209                 if (!info.arch)
210                         FAIL("#%d: entry stop mismatch", ptrace_stop);
211                 printf(", arch=");
212                 printxval(audit_arch, info.arch, "AUDIT_ARCH_???");
213
214                 if (size < offsetofend(struct ptrace_syscall_info,
215                                        instruction_pointer))
216                         goto printed_entry_common;
217                 if (!info.instruction_pointer)
218                         FAIL("#%d: entry stop mismatch", ptrace_stop);
219                 printf(", instruction_pointer=%#llx",
220                        (unsigned long long) info.instruction_pointer);
221
222                 if (size < offsetofend(struct ptrace_syscall_info,
223                                        stack_pointer))
224                         goto printed_entry_common;
225                 if (!info.stack_pointer)
226                         FAIL("#%d: entry stop mismatch", ptrace_stop);
227                 printf(", stack_pointer=%#llx",
228                        (unsigned long long) info.stack_pointer);
229
230                 if (size < offsetofend(struct ptrace_syscall_info, entry.nr))
231                         goto printed_entry_common;
232                 const unsigned long *exp_args = args[ptrace_stop / 2];
233                 if (info.entry.nr != exp_args[0])
234                         FAIL("#%d: entry stop mismatch", ptrace_stop);
235                 printf(", entry={nr=%llu", (unsigned long long) info.entry.nr);
236
237                 for (unsigned int i = 0; i < ARRAY_SIZE(info.entry.args); ++i) {
238                         const unsigned int i_size =
239                                 offsetofend(struct ptrace_syscall_info,
240                                             entry.args[i]);
241                         if (size < i_size) {
242                                 if (i)
243                                         break;
244                                 goto printed_entry_nr;
245                         }
246                         if (info.entry.args[i] != exp_args[i + 1])
247                                 FAIL("#%d: entry stop mismatch", ptrace_stop);
248                         printf("%s%#llx", (i ? ", " : ", arg=["),
249                                (unsigned long long) info.entry.args[i]);
250                 }
251                 printf("]");
252
253 printed_entry_nr:
254                 printf("}");
255
256 printed_entry_common:
257                 printf("}) = %s\n", errstr);
258         }
259 }
260
261 static void
262 test_exit(void)
263 {
264         for (unsigned int size = 0;
265              size <= sizeof(struct ptrace_syscall_info); ++size) {
266                 unsigned long buf = end_of_page - size;
267                 memset((void *) buf, -1, size);
268
269                 long rc = do_ptrace(PTRACE_GET_SYSCALL_INFO, pid, size, buf);
270                 if (rc < 0)
271                         PFAIL("PTRACE_GET_SYSCALL_INFO");
272
273                 if (rc < (long) expected_exit_size)
274                         FAIL("#%d: exit stop mismatch", ptrace_stop);
275
276                 printf("ptrace(PTRACE_GET_SYSCALL_INFO, %d, %u, ",
277                        pid, size);
278                 if (!size) {
279                         printf("%#lx) = %s\n", buf, errstr);
280                         continue;
281                 }
282
283                 /* copy to a local structure to avoid unaligned access */
284                 struct ptrace_syscall_info info;
285                 memcpy(&info, (void *) buf,  MIN(size, expected_exit_size));
286
287                 if (info.op != PTRACE_SYSCALL_INFO_EXIT)
288                         FAIL("#%d: exit stop mismatch", ptrace_stop);
289                 printf("{op=PTRACE_SYSCALL_INFO_EXIT");
290
291                 if (size < offsetofend(struct ptrace_syscall_info, arch))
292                         goto printed_exit_common;
293                 if (!info.arch)
294                         FAIL("#%d: exit stop mismatch", ptrace_stop);
295                 printf(", arch=");
296                 printxval(audit_arch, info.arch, "AUDIT_ARCH_???");
297
298                 if (size < offsetofend(struct ptrace_syscall_info,
299                                        instruction_pointer))
300                         goto printed_exit_common;
301                 if (!info.instruction_pointer)
302                         FAIL("#%d: exit stop mismatch", ptrace_stop);
303                 printf(", instruction_pointer=%#llx",
304                        (unsigned long long) info.instruction_pointer);
305
306                 if (size < offsetofend(struct ptrace_syscall_info,
307                                        stack_pointer))
308                         goto printed_exit_common;
309                 if (!info.stack_pointer)
310                         FAIL("#%d: exit stop mismatch", ptrace_stop);
311                 printf(", stack_pointer=%#llx",
312                        (unsigned long long) info.stack_pointer);
313
314                 const struct {
315                         unsigned int is_error;
316                         int rval;
317                         const char *str;
318                 } exit_param[] = {
319                         { 1, -ENOENT, "-ENOENT" },      /* chdir */
320                         { 0, pid, NULL }                /* gettid */
321                 }, *exp_param = &exit_param[ptrace_stop / 2 - 1];
322
323                 if (size < offsetofend(struct ptrace_syscall_info, exit.rval))
324                         goto printed_exit_common;
325                 if (info.exit.rval != exp_param->rval)
326                         FAIL("#%d: exit stop mismatch", ptrace_stop);
327                 if (size >= expected_exit_size && info.exit.is_error) {
328                         printf(", exit={rval=%s", exp_param->str);
329                 } else {
330                         printf(", exit={rval=%lld", (long long) info.exit.rval);
331                 }
332
333                 if (size >= expected_exit_size) {
334                         if (info.exit.is_error != exp_param->is_error)
335                                 FAIL("#%d: exit stop mismatch", ptrace_stop);
336                         printf(", is_error=%u",
337                                (unsigned int) info.exit.is_error);
338                 }
339
340                 printf("}");
341
342 printed_exit_common:
343                 printf("}) = %s\n", errstr);
344         }
345 }
346
347 int
348 main(void)
349 {
350         end_of_page = (unsigned long) tail_alloc(1) + 1;
351
352         pid = getpid();
353         do_ptrace(PTRACE_GET_SYSCALL_INFO, pid, 0, 0);
354         printf("ptrace(PTRACE_GET_SYSCALL_INFO, %d, 0, NULL) = %s\n",
355                pid, errstr);
356
357         pid = fork();
358         if (pid < 0)
359                 PFAIL("fork");
360
361         if (pid == 0) {
362                 /* get the pid before PTRACE_TRACEME */
363                 pid = getpid();
364                 if (do_ptrace(PTRACE_TRACEME, 0, 0, 0) < 0) {
365                         /* exit with a nonzero exit status */
366                         PFAIL("PTRACE_TRACEME");
367                 }
368                 kill(pid, SIGSTOP);
369                 for (unsigned int i = 0; i < ARRAY_SIZE(args); ++i) {
370                         syscall(args[i][0],
371                                 args[i][1], args[i][2], args[i][3],
372                                 args[i][4], args[i][5], args[i][6]);
373                 }
374                 /* unreachable */
375                 _exit(1);
376         }
377
378         for (ptrace_stop = 0; ; ++ptrace_stop) {
379                 int status;
380                 long rc = waitpid(pid, &status, 0);
381                 if (rc != pid) {
382                         /* cannot happen */
383                         PFAIL("#%d: unexpected wait result %ld",
384                                             ptrace_stop, rc);
385                 }
386                 if (WIFEXITED(status)) {
387                         /* tracee is no more */
388                         pid = 0;
389                         if (WEXITSTATUS(status) == 0)
390                                 break;
391                         FAIL("#%d: unexpected exit status %u",
392                              ptrace_stop, WEXITSTATUS(status));
393                 }
394                 if (WIFSIGNALED(status)) {
395                         /* tracee is no more */
396                         pid = 0;
397                         FAIL("#%d: unexpected signal %u",
398                              ptrace_stop, WTERMSIG(status));
399                 }
400                 if (!WIFSTOPPED(status)) {
401                         /* cannot happen */
402                         FAIL("#%d: unexpected wait status %#x",
403                              ptrace_stop, status);
404                 }
405
406                 switch (WSTOPSIG(status)) {
407                 case SIGSTOP:
408                         if (ptrace_stop)
409                                 FAIL("#%d: unexpected signal stop",
410                                                ptrace_stop);
411                         if (do_ptrace(PTRACE_SETOPTIONS, pid, 0,
412                                    PTRACE_O_TRACESYSGOOD) < 0) {
413                                 /* cannot happen */
414                                 PFAIL("PTRACE_SETOPTIONS");
415                         }
416                         printf("ptrace(PTRACE_SETOPTIONS, %d, NULL"
417                                ", PTRACE_O_TRACESYSGOOD) = 0\n", pid);
418
419                         if (!test_none())
420                                 goto done;
421                         break;
422
423                 case SIGTRAP | 0x80:
424                         switch (ptrace_stop) {
425                         case 1: /* entering chdir */
426                         case 3: /* entering gettid */
427                         case 5: /* entering exit_group */
428                                 test_entry();
429                                 break;
430                         case 2: /* exiting chdir */
431                         case 4: /* exiting gettid */
432                                 test_exit();
433                                 break;
434                         default:
435                                 FAIL("#%d: unexpected syscall stop",
436                                      ptrace_stop);
437                         }
438                         break;
439
440                 default:
441                         FAIL("#%d: unexpected stop signal %#x",
442                              ptrace_stop, WSTOPSIG(status));
443                 }
444
445                 if (do_ptrace(PTRACE_SYSCALL, pid, 0, 0) < 0) {
446                         /* cannot happen */
447                         PFAIL("PTRACE_SYSCALL");
448                 }
449                 printf("ptrace(PTRACE_SYSCALL, %d, NULL, 0) = 0\n", pid);
450         }
451
452 done:
453         if (pid) {
454                 kill_tracee();
455                 waitpid(pid, NULL, 0);
456         }
457
458         puts("+++ exited with 0 +++");
459         return 0;
460 }