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