Skip to content

Commit ce290a1

Browse files
Christian Braunergregkh
authored andcommitted
selftests: add devpts selftests
This adds tests to check: - bind-mounts from /dev/pts/ptmx to /dev/ptmx work - non-standard mounts of devpts work - bind-mounts of /dev/pts/ptmx to locations that do not resolve to a valid slave pty path under the originating devpts mount fail Signed-off-by: Christian Brauner <[email protected]> Acked-by: "Eric W. Biederman" <[email protected]> Acked-by: Linus Torvalds <[email protected]> Signed-off-by: Greg Kroah-Hartman <[email protected]>
1 parent 4e15f76 commit ce290a1

File tree

4 files changed

+316
-1
lines changed

4 files changed

+316
-1
lines changed

tools/testing/selftests/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ TARGETS += cpufreq
77
TARGETS += cpu-hotplug
88
TARGETS += efivarfs
99
TARGETS += exec
10+
TARGETS += filesystems
1011
TARGETS += firmware
1112
TARGETS += ftrace
1213
TARGETS += futex
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
dnotify_test
2+
devpts_pts

tools/testing/selftests/filesystems/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# SPDX-License-Identifier: GPL-2.0
2-
TEST_PROGS := dnotify_test
2+
TEST_PROGS := dnotify_test devpts_pts
33
all: $(TEST_PROGS)
44

55
include ../lib.mk
Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
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+
}

0 commit comments

Comments
 (0)