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 slave = ioctl(master, TIOCGPTPEER, O_RDWR | O_NOCTTY | O_CLOEXEC); 123 if (slave < 0) { 124 if (errno == EINVAL) { 125 fprintf(stderr, "TIOCGPTPEER is not supported. " 126 "Skipping test.\n"); 127 fret = KSFT_SKIP; 128 } else { 129 fprintf(stderr, 130 "Failed to perform TIOCGPTPEER ioctl\n"); 131 fret = EXIT_FAILURE; 132 } 133 goto do_cleanup; 134 } 135 136 pid_t pid = fork(); 137 if (pid < 0) 138 goto do_cleanup; 139 140 if (pid == 0) { 141 char buf[4096]; 142 143 ret = login_pty(slave); 144 if (ret < 0) { 145 fprintf(stderr, "Failed to setup terminal\n"); 146 _exit(EXIT_FAILURE); 147 } 148 149 ret = resolve_procfd_symlink(STDIN_FILENO, buf, sizeof(buf)); 150 if (ret < 0) { 151 fprintf(stderr, "Failed to retrieve pathname of pts " 152 "slave file descriptor\n"); 153 _exit(EXIT_FAILURE); 154 } 155 156 if (strncmp(expected_procfd_contents, buf, 157 strlen(expected_procfd_contents)) != 0) { 158 fprintf(stderr, "Received invalid contents for " 159 "\"/proc/<pid>/fd/%d\" symlink: %s\n", 160 STDIN_FILENO, buf); 161 _exit(-1); 162 } 163 164 fprintf(stderr, "Contents of \"/proc/<pid>/fd/%d\" " 165 "symlink are valid: %s\n", STDIN_FILENO, buf); 166 167 _exit(EXIT_SUCCESS); 168 } 169 170 ret = wait_for_pid(pid); 171 if (ret < 0) 172 goto do_cleanup; 173 174 fret = EXIT_SUCCESS; 175 176 do_cleanup: 177 if (master >= 0) 178 close(master); 179 if (slave >= 0) 180 close(slave); 181 182 return fret; 183 } 184 185 static int verify_non_standard_devpts_mount(void) 186 { 187 char *mntpoint; 188 int ret = -1; 189 char devpts[] = P_tmpdir "/devpts_fs_XXXXXX"; 190 char ptmx[] = P_tmpdir "/devpts_fs_XXXXXX/ptmx"; 191 192 ret = umount("/dev/pts"); 193 if (ret < 0) { 194 fprintf(stderr, "Failed to unmount \"/dev/pts\": %s\n", 195 strerror(errno)); 196 return -1; 197 } 198 199 (void)umount("/dev/ptmx"); 200 201 mntpoint = mkdtemp(devpts); 202 if (!mntpoint) { 203 fprintf(stderr, "Failed to create temporary mountpoint: %s\n", 204 strerror(errno)); 205 return -1; 206 } 207 208 ret = mount("devpts", mntpoint, "devpts", MS_NOSUID | MS_NOEXEC, 209 "newinstance,ptmxmode=0666,mode=0620,gid=5"); 210 if (ret < 0) { 211 fprintf(stderr, "Failed to mount devpts fs to \"%s\" in new " 212 "mount namespace: %s\n", mntpoint, 213 strerror(errno)); 214 unlink(mntpoint); 215 return -1; 216 } 217 218 ret = snprintf(ptmx, sizeof(ptmx), "%s/ptmx", devpts); 219 if (ret < 0 || (size_t)ret >= sizeof(ptmx)) { 220 unlink(mntpoint); 221 return -1; 222 } 223 224 ret = do_tiocgptpeer(ptmx, mntpoint); 225 unlink(mntpoint); 226 if (ret < 0) 227 return -1; 228 229 return 0; 230 } 231 232 static int verify_ptmx_bind_mount(void) 233 { 234 int ret; 235 236 ret = mount("/dev/pts/ptmx", "/dev/ptmx", NULL, MS_BIND, NULL); 237 if (ret < 0) { 238 fprintf(stderr, "Failed to bind mount \"/dev/pts/ptmx\" to " 239 "\"/dev/ptmx\" mount namespace\n"); 240 return -1; 241 } 242 243 ret = do_tiocgptpeer("/dev/ptmx", "/dev/pts/"); 244 if (ret < 0) 245 return -1; 246 247 return 0; 248 } 249 250 static int verify_invalid_ptmx_bind_mount(void) 251 { 252 int ret; 253 char mntpoint_fd; 254 char ptmx[] = P_tmpdir "/devpts_ptmx_XXXXXX"; 255 256 mntpoint_fd = mkstemp(ptmx); 257 if (mntpoint_fd < 0) { 258 fprintf(stderr, "Failed to create temporary directory: %s\n", 259 strerror(errno)); 260 return -1; 261 } 262 263 ret = mount("/dev/pts/ptmx", ptmx, NULL, MS_BIND, NULL); 264 close(mntpoint_fd); 265 if (ret < 0) { 266 fprintf(stderr, "Failed to bind mount \"/dev/pts/ptmx\" to " 267 "\"%s\" mount namespace\n", ptmx); 268 return -1; 269 } 270 271 ret = do_tiocgptpeer(ptmx, "/dev/pts/"); 272 if (ret == 0) 273 return -1; 274 275 return 0; 276 } 277 278 int main(int argc, char *argv[]) 279 { 280 int ret; 281 282 if (!isatty(STDIN_FILENO)) { 283 fprintf(stderr, "Standard input file descriptor is not attached " 284 "to a terminal. Skipping test\n"); 285 exit(KSFT_SKIP); 286 } 287 288 ret = unshare(CLONE_NEWNS); 289 if (ret < 0) { 290 fprintf(stderr, "Failed to unshare mount namespace\n"); 291 exit(EXIT_FAILURE); 292 } 293 294 ret = mount("", "/", NULL, MS_PRIVATE | MS_REC, 0); 295 if (ret < 0) { 296 fprintf(stderr, "Failed to make \"/\" MS_PRIVATE in new mount " 297 "namespace\n"); 298 exit(EXIT_FAILURE); 299 } 300 301 ret = verify_ptmx_bind_mount(); 302 if (ret < 0) 303 exit(EXIT_FAILURE); 304 305 ret = verify_invalid_ptmx_bind_mount(); 306 if (ret < 0) 307 exit(EXIT_FAILURE); 308 309 ret = verify_non_standard_devpts_mount(); 310 if (ret < 0) 311 exit(EXIT_FAILURE); 312 313 exit(EXIT_SUCCESS); 314 } 315