blob: b2712b16d7fd76c1ddd58381ac6e42cd8548893e [file] [log] [blame]
Christian Braunerce290a12018-03-13 17:55:27 +01001// SPDX-License-Identifier: GPL-2.0
2#define _GNU_SOURCE
3#include <errno.h>
4#include <fcntl.h>
5#include <sched.h>
6#include <stdbool.h>
7#include <stdio.h>
8#include <stdlib.h>
9#include <string.h>
10#include <unistd.h>
Anders Roxelldd4b16b2018-04-18 09:52:55 +020011#include <asm/ioctls.h>
Christian Braunerce290a12018-03-13 17:55:27 +010012#include <sys/mount.h>
13#include <sys/wait.h>
14
15static bool terminal_dup2(int duplicate, int original)
16{
17 int ret;
18
19 ret = dup2(duplicate, original);
20 if (ret < 0)
21 return false;
22
23 return true;
24}
25
26static int terminal_set_stdfds(int fd)
27{
28 int i;
29
30 if (fd < 0)
31 return 0;
32
33 for (i = 0; i < 3; i++)
34 if (!terminal_dup2(fd, (int[]){STDIN_FILENO, STDOUT_FILENO,
35 STDERR_FILENO}[i]))
36 return -1;
37
38 return 0;
39}
40
41static int login_pty(int fd)
42{
43 int ret;
44
45 setsid();
46
47 ret = ioctl(fd, TIOCSCTTY, NULL);
48 if (ret < 0)
49 return -1;
50
51 ret = terminal_set_stdfds(fd);
52 if (ret < 0)
53 return -1;
54
55 if (fd > STDERR_FILENO)
56 close(fd);
57
58 return 0;
59}
60
61static int wait_for_pid(pid_t pid)
62{
63 int status, ret;
64
65again:
66 ret = waitpid(pid, &status, 0);
67 if (ret == -1) {
68 if (errno == EINTR)
69 goto again;
70 return -1;
71 }
72 if (ret != pid)
73 goto again;
74
75 if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
76 return -1;
77
78 return 0;
79}
80
81static int resolve_procfd_symlink(int fd, char *buf, size_t buflen)
82{
83 int ret;
84 char procfd[4096];
85
86 ret = snprintf(procfd, 4096, "/proc/self/fd/%d", fd);
87 if (ret < 0 || ret >= 4096)
88 return -1;
89
90 ret = readlink(procfd, buf, buflen);
91 if (ret < 0 || (size_t)ret >= buflen)
92 return -1;
93
94 buf[ret] = '\0';
95
96 return 0;
97}
98
99static int do_tiocgptpeer(char *ptmx, char *expected_procfd_contents)
100{
101 int ret;
102 int master = -1, slave = -1, fret = -1;
103
104 master = open(ptmx, O_RDWR | O_NOCTTY | O_CLOEXEC);
105 if (master < 0) {
106 fprintf(stderr, "Failed to open \"%s\": %s\n", ptmx,
107 strerror(errno));
108 return -1;
109 }
110
111 /*
112 * grantpt() makes assumptions about /dev/pts/ so ignore it. It's also
113 * not really needed.
114 */
115 ret = unlockpt(master);
116 if (ret < 0) {
117 fprintf(stderr, "Failed to unlock terminal\n");
118 goto do_cleanup;
119 }
120
121#ifdef TIOCGPTPEER
122 slave = ioctl(master, TIOCGPTPEER, O_RDWR | O_NOCTTY | O_CLOEXEC);
123#endif
124 if (slave < 0) {
125 if (errno == EINVAL) {
126 fprintf(stderr, "TIOCGPTPEER is not supported. "
127 "Skipping test.\n");
128 fret = EXIT_SUCCESS;
129 }
130
131 fprintf(stderr, "Failed to perform TIOCGPTPEER ioctl\n");
132 goto do_cleanup;
133 }
134
135 pid_t pid = fork();
136 if (pid < 0)
137 goto do_cleanup;
138
139 if (pid == 0) {
140 char buf[4096];
141
142 ret = login_pty(slave);
143 if (ret < 0) {
144 fprintf(stderr, "Failed to setup terminal\n");
145 _exit(EXIT_FAILURE);
146 }
147
148 ret = resolve_procfd_symlink(STDIN_FILENO, buf, sizeof(buf));
149 if (ret < 0) {
150 fprintf(stderr, "Failed to retrieve pathname of pts "
151 "slave file descriptor\n");
152 _exit(EXIT_FAILURE);
153 }
154
155 if (strncmp(expected_procfd_contents, buf,
156 strlen(expected_procfd_contents)) != 0) {
157 fprintf(stderr, "Received invalid contents for "
158 "\"/proc/<pid>/fd/%d\" symlink: %s\n",
159 STDIN_FILENO, buf);
160 _exit(-1);
161 }
162
163 fprintf(stderr, "Contents of \"/proc/<pid>/fd/%d\" "
164 "symlink are valid: %s\n", STDIN_FILENO, buf);
165
166 _exit(EXIT_SUCCESS);
167 }
168
169 ret = wait_for_pid(pid);
170 if (ret < 0)
171 goto do_cleanup;
172
173 fret = EXIT_SUCCESS;
174
175do_cleanup:
176 if (master >= 0)
177 close(master);
178 if (slave >= 0)
179 close(slave);
180
181 return fret;
182}
183
184static int verify_non_standard_devpts_mount(void)
185{
186 char *mntpoint;
187 int ret = -1;
188 char devpts[] = P_tmpdir "/devpts_fs_XXXXXX";
189 char ptmx[] = P_tmpdir "/devpts_fs_XXXXXX/ptmx";
190
191 ret = umount("/dev/pts");
192 if (ret < 0) {
193 fprintf(stderr, "Failed to unmount \"/dev/pts\": %s\n",
194 strerror(errno));
195 return -1;
196 }
197
198 (void)umount("/dev/ptmx");
199
200 mntpoint = mkdtemp(devpts);
201 if (!mntpoint) {
202 fprintf(stderr, "Failed to create temporary mountpoint: %s\n",
203 strerror(errno));
204 return -1;
205 }
206
207 ret = mount("devpts", mntpoint, "devpts", MS_NOSUID | MS_NOEXEC,
208 "newinstance,ptmxmode=0666,mode=0620,gid=5");
209 if (ret < 0) {
210 fprintf(stderr, "Failed to mount devpts fs to \"%s\" in new "
211 "mount namespace: %s\n", mntpoint,
212 strerror(errno));
213 unlink(mntpoint);
214 return -1;
215 }
216
217 ret = snprintf(ptmx, sizeof(ptmx), "%s/ptmx", devpts);
218 if (ret < 0 || (size_t)ret >= sizeof(ptmx)) {
219 unlink(mntpoint);
220 return -1;
221 }
222
223 ret = do_tiocgptpeer(ptmx, mntpoint);
224 unlink(mntpoint);
225 if (ret < 0)
226 return -1;
227
228 return 0;
229}
230
231static int verify_ptmx_bind_mount(void)
232{
233 int ret;
234
235 ret = mount("/dev/pts/ptmx", "/dev/ptmx", NULL, MS_BIND, NULL);
236 if (ret < 0) {
237 fprintf(stderr, "Failed to bind mount \"/dev/pts/ptmx\" to "
238 "\"/dev/ptmx\" mount namespace\n");
239 return -1;
240 }
241
242 ret = do_tiocgptpeer("/dev/ptmx", "/dev/pts/");
243 if (ret < 0)
244 return -1;
245
246 return 0;
247}
248
249static int verify_invalid_ptmx_bind_mount(void)
250{
251 int ret;
252 char mntpoint_fd;
253 char ptmx[] = P_tmpdir "/devpts_ptmx_XXXXXX";
254
255 mntpoint_fd = mkstemp(ptmx);
256 if (mntpoint_fd < 0) {
257 fprintf(stderr, "Failed to create temporary directory: %s\n",
258 strerror(errno));
259 return -1;
260 }
261
262 ret = mount("/dev/pts/ptmx", ptmx, NULL, MS_BIND, NULL);
263 close(mntpoint_fd);
264 if (ret < 0) {
265 fprintf(stderr, "Failed to bind mount \"/dev/pts/ptmx\" to "
266 "\"%s\" mount namespace\n", ptmx);
267 return -1;
268 }
269
270 ret = do_tiocgptpeer(ptmx, "/dev/pts/");
271 if (ret == 0)
272 return -1;
273
274 return 0;
275}
276
277int main(int argc, char *argv[])
278{
279 int ret;
280
281 if (!isatty(STDIN_FILENO)) {
Colin Ian King08529912018-04-29 12:54:25 +0100282 fprintf(stderr, "Standard input file descriptor is not attached "
Christian Braunerce290a12018-03-13 17:55:27 +0100283 "to a terminal. Skipping test\n");
284 exit(EXIT_FAILURE);
285 }
286
287 ret = unshare(CLONE_NEWNS);
288 if (ret < 0) {
289 fprintf(stderr, "Failed to unshare mount namespace\n");
290 exit(EXIT_FAILURE);
291 }
292
293 ret = mount("", "/", NULL, MS_PRIVATE | MS_REC, 0);
294 if (ret < 0) {
295 fprintf(stderr, "Failed to make \"/\" MS_PRIVATE in new mount "
296 "namespace\n");
297 exit(EXIT_FAILURE);
298 }
299
300 ret = verify_ptmx_bind_mount();
301 if (ret < 0)
302 exit(EXIT_FAILURE);
303
304 ret = verify_invalid_ptmx_bind_mount();
305 if (ret < 0)
306 exit(EXIT_FAILURE);
307
308 ret = verify_non_standard_devpts_mount();
309 if (ret < 0)
310 exit(EXIT_FAILURE);
311
312 exit(EXIT_SUCCESS);
313}