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 <asm/ioctls.h> |
12 | #include <sys/mount.h> |
13 | #include <sys/wait.h> |
14 | #include "../kselftest.h" |
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" ); |
129 | fret = KSFT_SKIP; |
130 | } else { |
131 | fprintf(stderr, |
132 | "Failed to perform TIOCGPTPEER ioctl\n" ); |
133 | fret = EXIT_FAILURE; |
134 | } |
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(fd: 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, expected_procfd_contents: 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(ptmx: "/dev/ptmx" , expected_procfd_contents: "/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, expected_procfd_contents: "/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)) { |
285 | fprintf(stderr, "Standard input file descriptor is not attached " |
286 | "to a terminal. Skipping test\n" ); |
287 | exit(KSFT_SKIP); |
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 | } |
317 | |