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