linux/tools/testing/selftests/filesystems/devpts_pts.c
<<
>>
Prefs
   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
  16static 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
  27static 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
  42static 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
  62static int wait_for_pid(pid_t pid)
  63{
  64        int status, ret;
  65
  66again:
  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
  82static 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
 100static 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
 178do_cleanup:
 179        if (master >= 0)
 180                close(master);
 181        if (slave >= 0)
 182                close(slave);
 183
 184        return fret;
 185}
 186
 187static 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
 234static 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
 252static 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
 280int 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