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(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)) { 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