root/tools/testing/selftests/filesystems/devpts_pts.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. terminal_dup2
  2. terminal_set_stdfds
  3. login_pty
  4. wait_for_pid
  5. resolve_procfd_symlink
  6. do_tiocgptpeer
  7. verify_non_standard_devpts_mount
  8. verify_ptmx_bind_mount
  9. verify_invalid_ptmx_bind_mount
  10. main

   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 }

/* [<][>][^][v][top][bottom][index][help] */