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