xref: /linux/tools/testing/selftests/filesystems/devpts_pts.c (revision c532de5a67a70f8533d495f8f2aaa9a0491c3ad0)
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 }
317