Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

selftests: add devpts selftests

This adds tests to check:
- bind-mounts from /dev/pts/ptmx to /dev/ptmx work
- non-standard mounts of devpts work
- bind-mounts of /dev/pts/ptmx to locations that do not resolve to a valid
slave pty path under the originating devpts mount fail

Signed-off-by: Christian Brauner <christian.brauner@ubuntu.com>
Acked-by: "Eric W. Biederman" <ebiederm@xmission.com>
Acked-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Christian Brauner and committed by
Greg Kroah-Hartman
ce290a19 4e15f760

+316 -1
+1
tools/testing/selftests/Makefile
··· 7 7 TARGETS += cpu-hotplug 8 8 TARGETS += efivarfs 9 9 TARGETS += exec 10 + TARGETS += filesystems 10 11 TARGETS += firmware 11 12 TARGETS += ftrace 12 13 TARGETS += futex
+1
tools/testing/selftests/filesystems/.gitignore
··· 1 1 dnotify_test 2 + devpts_pts
+1 -1
tools/testing/selftests/filesystems/Makefile
··· 1 1 # SPDX-License-Identifier: GPL-2.0 2 - TEST_PROGS := dnotify_test 2 + TEST_PROGS := dnotify_test devpts_pts 3 3 all: $(TEST_PROGS) 4 4 5 5 include ../lib.mk
+313
tools/testing/selftests/filesystems/devpts_pts.c
··· 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 + }