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#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(fd: 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
178do_cleanup:
179 if (master >= 0)
180 close(master);
181 if (slave >= 0)
182 close(slave);
183
184 return fret;
185}
186
187static 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, expected_procfd_contents: mntpoint);
227 unlink(mntpoint);
228 if (ret < 0)
229 return -1;
230
231 return 0;
232}
233
234static 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(ptmx: "/dev/ptmx", expected_procfd_contents: "/dev/pts/");
246 if (ret < 0)
247 return -1;
248
249 return 0;
250}
251
252static 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, expected_procfd_contents: "/dev/pts/");
274 if (ret == 0)
275 return -1;
276
277 return 0;
278}
279
280int 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}
317

source code of linux/tools/testing/selftests/filesystems/devpts_pts.c