diff options
author | Christian Brauner <christian.brauner@ubuntu.com> | 2018-03-13 12:55:27 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2018-03-14 08:31:23 -0400 |
commit | ce290a19609d5ecd4a240a4750e0ed9a476cf015 (patch) | |
tree | da67976d66ac15ecac83dade8f543146ab238853 | |
parent | 4e15f760a43c7cb88e2b7ad6882501ccab5de29f (diff) |
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 <christian.brauner@ubuntu.com>
Acked-by: "Eric W. Biederman" <ebiederm@xmission.com>
Acked-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r-- | tools/testing/selftests/Makefile | 1 | ||||
-rw-r--r-- | tools/testing/selftests/filesystems/.gitignore | 1 | ||||
-rw-r--r-- | tools/testing/selftests/filesystems/Makefile | 2 | ||||
-rw-r--r-- | tools/testing/selftests/filesystems/devpts_pts.c | 313 |
4 files changed, 316 insertions, 1 deletions
diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index 7442dfb73b7f..dbda89c9d9b9 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile | |||
@@ -7,6 +7,7 @@ TARGETS += cpufreq | |||
7 | TARGETS += cpu-hotplug | 7 | TARGETS += cpu-hotplug |
8 | TARGETS += efivarfs | 8 | TARGETS += efivarfs |
9 | TARGETS += exec | 9 | TARGETS += exec |
10 | TARGETS += filesystems | ||
10 | TARGETS += firmware | 11 | TARGETS += firmware |
11 | TARGETS += ftrace | 12 | TARGETS += ftrace |
12 | TARGETS += futex | 13 | TARGETS += futex |
diff --git a/tools/testing/selftests/filesystems/.gitignore b/tools/testing/selftests/filesystems/.gitignore index 31d6e426b6d4..8449cf6716ce 100644 --- a/tools/testing/selftests/filesystems/.gitignore +++ b/tools/testing/selftests/filesystems/.gitignore | |||
@@ -1 +1,2 @@ | |||
1 | dnotify_test | 1 | dnotify_test |
2 | devpts_pts | ||
diff --git a/tools/testing/selftests/filesystems/Makefile b/tools/testing/selftests/filesystems/Makefile index 13a73bf725b5..4e6d09fb166f 100644 --- a/tools/testing/selftests/filesystems/Makefile +++ b/tools/testing/selftests/filesystems/Makefile | |||
@@ -1,5 +1,5 @@ | |||
1 | # SPDX-License-Identifier: GPL-2.0 | 1 | # SPDX-License-Identifier: GPL-2.0 |
2 | TEST_PROGS := dnotify_test | 2 | TEST_PROGS := dnotify_test devpts_pts |
3 | all: $(TEST_PROGS) | 3 | all: $(TEST_PROGS) |
4 | 4 | ||
5 | include ../lib.mk | 5 | include ../lib.mk |
diff --git a/tools/testing/selftests/filesystems/devpts_pts.c b/tools/testing/selftests/filesystems/devpts_pts.c new file mode 100644 index 000000000000..b9055e974289 --- /dev/null +++ b/tools/testing/selftests/filesystems/devpts_pts.c | |||
@@ -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 | } | ||