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 } 314