xref: /linux/tools/testing/selftests/filesystems/devpts_pts.c (revision 40286d6379aacfcc053253ef78dc78b09addffda)
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 	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 
176 do_cleanup:
177 	if (master >= 0)
178 		close(master);
179 	if (slave >= 0)
180 		close(slave);
181 
182 	return fret;
183 }
184 
185 static 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 
232 static 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 
250 static 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 
278 int 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 }
315