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