1 /*
2 * Testing framework for PID namespace translation
3 *
4 * Copyright (c) 2020 Ákos Uzonyi <uzonyi.akos@gmail.com>
5 * Copyright (c) 2020-2022 The strace developers.
6 * All rights reserved.
7 *
8 * SPDX-License-Identifier: GPL-2.0-or-later
9 */
10 #include "tests.h"
11 #include "pidns.h"
12 #include <linux/nsfs.h>
13
14 #include <errno.h>
15 #include <stdio.h>
16 #include <string.h>
17 #include <sys/types.h>
18 #include <signal.h>
19 #include <stdlib.h>
20 #include <sched.h>
21 #include <unistd.h>
22 #include <sys/wait.h>
23 #include <linux/sched.h>
24 #include <fcntl.h>
25 #include <sys/ioctl.h>
26
27 static bool pidns_translation = false;
28 static bool pidns_unshared = false;
29
30 /* Our PIDs in strace's namespace */
31 static pid_t pidns_strace_ids[PT_COUNT];
32
33 void
34 pidns_print_leader(void)
35 {
36 if (pidns_translation)
37 printf("%-5d ", pidns_strace_ids[PT_TID]);
38 }
39
40 const char *
41 pidns_pid2str(enum pid_type type)
42 {
43 static const char format[] = " /* %d in strace's PID NS */";
44 static char buf[PT_COUNT][sizeof(format) + sizeof(int) * 3];
45
46 if (type < 0 || type >= PT_COUNT)
47 return "";
48
49 if (!pidns_unshared || !pidns_strace_ids[type])
50 return "";
51
52 snprintf(buf[type], sizeof(buf[type]), format, pidns_strace_ids[type]);
53 return buf[type];
54 }
55
56 /**
57 * This function is like fork, but does a few more things. It sets up the
58 * child's PGID and SID according to the parameters. Also it fills the
59 * pidns_strace_ids array in the child's memory with the PIDs of the child in
60 * parent's PID namespace. In the parent it waits for the child to terminate
61 * (but leaves the zombie to use it later as a process group). If the child
62 * terminates with nonzero exit status, the test is failed.
63 *
64 * @param pgid The process group the child should be moved to. It's expected
65 * to be a PID of a zombie process (will be reaped). If
66 * negative, leave the child in the process group of the parent.
67 * If 0, move the process to its own process group.
68 * @param new_sid Whether child should be moved to a new session.
69 */
70 static pid_t
71 pidns_fork(pid_t pgid, bool new_sid)
72 {
73 int strace_ids_pipe[2];
74 if (pipe(strace_ids_pipe) < 0)
75 perror_msg_and_fail("pipe");
76
77 fflush(stdout);
78 pid_t pid = fork();
79 if (pid < 0)
80 perror_msg_and_fail("fork");
81
82 if (!pid) {
83 close(strace_ids_pipe[1]);
84
85 ssize_t len = read(strace_ids_pipe[0], pidns_strace_ids,
86 sizeof(pidns_strace_ids));
87 if (len < 0)
88 perror_msg_and_fail("read");
89 if (len != sizeof(pidns_strace_ids))
90 error_msg_and_fail("read returned < sizeof(pidns_strace_ids)");
91
92 close(strace_ids_pipe[0]);
93
94 if (pidns_strace_ids[PT_SID])
95 setsid();
96
97 return 0;
98 }
99
100 pidns_strace_ids[PT_TID] = pid;
101 pidns_strace_ids[PT_TGID] = pid;
102 pidns_strace_ids[PT_PGID] = 0;
103 pidns_strace_ids[PT_SID] = 0;
104
105 if (!pgid)
106 pgid = pid;
107
108 if (pgid > 0) {
109 if (setpgid(pid, pgid) < 0)
110 perror_msg_and_fail("setpgid");
111
112 pidns_strace_ids[PT_PGID] = pgid;
113 }
114
115 /* Reap group leader to test PGID decoding */
116 if (pgid > 0 && pgid != pid) {
117 int ret = waitpid(pgid, NULL, WNOHANG);
118 if (ret < 0)
119 perror_msg_and_fail("wait");
120 if (!ret)
121 error_msg_and_fail("could not reap group leader");
122 }
123
124 if (new_sid) {
125 pidns_strace_ids[PT_SID] = pid;
126 pidns_strace_ids[PT_PGID] = pid;
127 }
128
129 ssize_t len = write(strace_ids_pipe[1], pidns_strace_ids,
130 sizeof(pidns_strace_ids));
131 if (len < 0)
132 perror_msg_and_fail("write");
133 if (len != sizeof(pidns_strace_ids))
134 error_msg_and_fail("write returned < sizeof(pidns_strace_ids)");
135
136 close(strace_ids_pipe[0]);
137 close(strace_ids_pipe[1]);
138
139 /* WNOWAIT: leave the zombie, to be able to use it as a process group */
140 siginfo_t siginfo;
141 if (waitid(P_PID, pid, &siginfo, WEXITED | WNOWAIT) < 0)
142 perror_msg_and_fail("wait");
143 if (siginfo.si_code != CLD_EXITED || siginfo.si_status) {
144 if (siginfo.si_code == CLD_EXITED && siginfo.si_status == 77) {
145 error_msg_and_skip("child terminated with skip exit"
146 " status");
147 } else {
148 error_msg_and_fail("child terminated with nonzero exit"
149 " status");
150 }
151 }
152
153 return pid;
154 }
155
156 static void
157 create_init_process(void)
158 {
159 int child_pipe[2];
160 if (pipe(child_pipe) < 0)
161 perror_msg_and_fail("pipe");
162
163 pid_t pid = fork();
164 if (pid < 0)
165 perror_msg_and_fail("fork");
166
167 if (!pid) {
168 close(child_pipe[1]);
169 if (read(child_pipe[0], &child_pipe[1], sizeof(int)) != 0)
170 _exit(1);
171 _exit(0);
172 }
173
174 close(child_pipe[0]);
175 }
176
177 void
178 check_ns_ioctl(void)
179 {
180 int fd = open("/proc/self/ns/pid", O_RDONLY);
181 if (fd < 0) {
182 if (errno == ENOENT)
183 perror_msg_and_skip("opening /proc/self/ns/pid");
184 else
185 perror_msg_and_fail("opening /proc/self/ns/pid");
186 }
187
188 int userns_fd = ioctl(fd, NS_GET_USERNS);
189 if (userns_fd < 0) {
190 switch (errno) {
191 case ENOTTY:
192 error_msg_and_skip("NS_* ioctl commands are not "
193 "supported by the kernel");
194 break;
195 case EPERM:
196 error_msg_and_skip("NS_* ioctl commands are not "
197 "permitted by the kernel");
198 break;
199 default:
200 perror_msg_and_fail("ioctl(NS_GET_USERNS)");
201 }
202 }
203
204 close(userns_fd);
205 close(fd);
206 }
207
208 void
209 pidns_test_init(void)
210 {
211 pidns_translation = true;
212
213 check_ns_ioctl();
214
215 if (!pidns_fork(-1, false))
216 return;
217
218 /* Unshare user namespace too, so we do not need to be root */
219 if (unshare(CLONE_NEWUSER | CLONE_NEWPID) < 0) {
220 if (errno == EPERM)
221 perror_msg_and_skip("unshare");
222
223 perror_msg_and_fail("unshare");
224 }
225
226 pidns_unshared = true;
227
228 create_init_process();
229
230 if (!pidns_fork(-1, false))
231 return;
232
233 if (!pidns_fork(-1, true))
234 return;
235
236 pid_t pgid;
237 if (!(pgid = pidns_fork(0, false)))
238 return;
239
240 if (!pidns_fork(pgid, false))
241 return;
242
243 exit(0);
244 }