Linux Audio

Check our new training course

Loading...
Note: File does not exist in v4.6.
  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
 15static 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
 26static 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
 41static 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
 61static int wait_for_pid(pid_t pid)
 62{
 63	int status, ret;
 64
 65again:
 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
 81static 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
 99static 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
175do_cleanup:
176	if (master >= 0)
177		close(master);
178	if (slave >= 0)
179		close(slave);
180
181	return fret;
182}
183
184static 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
231static 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
249static 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
277int 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}