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
16static int
17kill_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
30static long
31sys_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
43TEST(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
271TEST_HARNESS_MAIN
272

source code of linux/tools/testing/selftests/ptrace/get_syscall_info.c