GNU Linux-libre 4.19.264-gnu1
[releases.git] / tools / testing / selftests / filesystems / devpts_pts.c
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
16 static 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
27 static 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
42 static 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
62 static int wait_for_pid(pid_t pid)
63 {
64         int status, ret;
65
66 again:
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
82 static 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
100 static 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 #ifdef TIOCGPTPEER
123         slave = ioctl(master, TIOCGPTPEER, O_RDWR | O_NOCTTY | O_CLOEXEC);
124 #endif
125         if (slave < 0) {
126                 if (errno == EINVAL) {
127                         fprintf(stderr, "TIOCGPTPEER is not supported. "
128                                         "Skipping test.\n");
129                         fret = KSFT_SKIP;
130                 } else {
131                         fprintf(stderr,
132                                 "Failed to perform TIOCGPTPEER ioctl\n");
133                         fret = EXIT_FAILURE;
134                 }
135                 goto do_cleanup;
136         }
137
138         pid_t pid = fork();
139         if (pid < 0)
140                 goto do_cleanup;
141
142         if (pid == 0) {
143                 char buf[4096];
144
145                 ret = login_pty(slave);
146                 if (ret < 0) {
147                         fprintf(stderr, "Failed to setup terminal\n");
148                         _exit(EXIT_FAILURE);
149                 }
150
151                 ret = resolve_procfd_symlink(STDIN_FILENO, buf, sizeof(buf));
152                 if (ret < 0) {
153                         fprintf(stderr, "Failed to retrieve pathname of pts "
154                                         "slave file descriptor\n");
155                         _exit(EXIT_FAILURE);
156                 }
157
158                 if (strncmp(expected_procfd_contents, buf,
159                             strlen(expected_procfd_contents)) != 0) {
160                         fprintf(stderr, "Received invalid contents for "
161                                         "\"/proc/<pid>/fd/%d\" symlink: %s\n",
162                                         STDIN_FILENO, buf);
163                         _exit(-1);
164                 }
165
166                 fprintf(stderr, "Contents of \"/proc/<pid>/fd/%d\" "
167                                 "symlink are valid: %s\n", STDIN_FILENO, buf);
168
169                 _exit(EXIT_SUCCESS);
170         }
171
172         ret = wait_for_pid(pid);
173         if (ret < 0)
174                 goto do_cleanup;
175
176         fret = EXIT_SUCCESS;
177
178 do_cleanup:
179         if (master >= 0)
180                 close(master);
181         if (slave >= 0)
182                 close(slave);
183
184         return fret;
185 }
186
187 static int verify_non_standard_devpts_mount(void)
188 {
189         char *mntpoint;
190         int ret = -1;
191         char devpts[] = P_tmpdir "/devpts_fs_XXXXXX";
192         char ptmx[] = P_tmpdir "/devpts_fs_XXXXXX/ptmx";
193
194         ret = umount("/dev/pts");
195         if (ret < 0) {
196                 fprintf(stderr, "Failed to unmount \"/dev/pts\": %s\n",
197                                 strerror(errno));
198                 return -1;
199         }
200
201         (void)umount("/dev/ptmx");
202
203         mntpoint = mkdtemp(devpts);
204         if (!mntpoint) {
205                 fprintf(stderr, "Failed to create temporary mountpoint: %s\n",
206                                  strerror(errno));
207                 return -1;
208         }
209
210         ret = mount("devpts", mntpoint, "devpts", MS_NOSUID | MS_NOEXEC,
211                     "newinstance,ptmxmode=0666,mode=0620,gid=5");
212         if (ret < 0) {
213                 fprintf(stderr, "Failed to mount devpts fs to \"%s\" in new "
214                                 "mount namespace: %s\n", mntpoint,
215                                 strerror(errno));
216                 unlink(mntpoint);
217                 return -1;
218         }
219
220         ret = snprintf(ptmx, sizeof(ptmx), "%s/ptmx", devpts);
221         if (ret < 0 || (size_t)ret >= sizeof(ptmx)) {
222                 unlink(mntpoint);
223                 return -1;
224         }
225
226         ret = do_tiocgptpeer(ptmx, mntpoint);
227         unlink(mntpoint);
228         if (ret < 0)
229                 return -1;
230
231         return 0;
232 }
233
234 static int verify_ptmx_bind_mount(void)
235 {
236         int ret;
237
238         ret = mount("/dev/pts/ptmx", "/dev/ptmx", NULL, MS_BIND, NULL);
239         if (ret < 0) {
240                 fprintf(stderr, "Failed to bind mount \"/dev/pts/ptmx\" to "
241                                 "\"/dev/ptmx\" mount namespace\n");
242                 return -1;
243         }
244
245         ret = do_tiocgptpeer("/dev/ptmx", "/dev/pts/");
246         if (ret < 0)
247                 return -1;
248
249         return 0;
250 }
251
252 static int verify_invalid_ptmx_bind_mount(void)
253 {
254         int ret;
255         char mntpoint_fd;
256         char ptmx[] = P_tmpdir "/devpts_ptmx_XXXXXX";
257
258         mntpoint_fd = mkstemp(ptmx);
259         if (mntpoint_fd < 0) {
260                 fprintf(stderr, "Failed to create temporary directory: %s\n",
261                                  strerror(errno));
262                 return -1;
263         }
264
265         ret = mount("/dev/pts/ptmx", ptmx, NULL, MS_BIND, NULL);
266         close(mntpoint_fd);
267         if (ret < 0) {
268                 fprintf(stderr, "Failed to bind mount \"/dev/pts/ptmx\" to "
269                                 "\"%s\" mount namespace\n", ptmx);
270                 return -1;
271         }
272
273         ret = do_tiocgptpeer(ptmx, "/dev/pts/");
274         if (ret == 0)
275                 return -1;
276
277         return 0;
278 }
279
280 int main(int argc, char *argv[])
281 {
282         int ret;
283
284         if (!isatty(STDIN_FILENO)) {
285                 fprintf(stderr, "Standard input file descriptor is not attached "
286                                 "to a terminal. Skipping test\n");
287                 exit(KSFT_SKIP);
288         }
289
290         ret = unshare(CLONE_NEWNS);
291         if (ret < 0) {
292                 fprintf(stderr, "Failed to unshare mount namespace\n");
293                 exit(EXIT_FAILURE);
294         }
295
296         ret = mount("", "/", NULL, MS_PRIVATE | MS_REC, 0);
297         if (ret < 0) {
298                 fprintf(stderr, "Failed to make \"/\" MS_PRIVATE in new mount "
299                                 "namespace\n");
300                 exit(EXIT_FAILURE);
301         }
302
303         ret = verify_ptmx_bind_mount();
304         if (ret < 0)
305                 exit(EXIT_FAILURE);
306
307         ret = verify_invalid_ptmx_bind_mount();
308         if (ret < 0)
309                 exit(EXIT_FAILURE);
310
311         ret = verify_non_standard_devpts_mount();
312         if (ret < 0)
313                 exit(EXIT_FAILURE);
314
315         exit(EXIT_SUCCESS);
316 }