github.com/tiagovtristao/plz@v13.4.0+incompatible/tools/sandbox/main.c (about)

     1  // please_sandbox is a very small binary to implement sandboxing
     2  // of tests (and possibly other build actions) via cgroups.
     3  // Essentially this is a very lightweight replacement for Docker
     4  // where we would use it for tests to avoid port clashes etc.
     5  //
     6  // Note that this is a no-op on non-Linux OSs because they will not
     7  // support namespaces / cgroups. We still behave similarly otherwise
     8  // in order for it to be transparent to the rest of the system.
     9  
    10  #define _GNU_SOURCE
    11  #include <stdio.h>
    12  #include <stdlib.h>
    13  #include <unistd.h>
    14  
    15  #ifdef __linux__
    16  #include <sched.h>
    17  #include <string.h>
    18  #include <net/if.h>
    19  #include <sys/ioctl.h>
    20  #include <sys/mount.h>
    21  #include <sys/stat.h>
    22  #include <sys/types.h>
    23  
    24  // lo_up brings up the loopback interface in the new network namespace.
    25  // By default the namespace is created with lo but it is down.
    26  // Note that this can't be done with system() because it loses the
    27  // required capabilities.
    28  int lo_up() {
    29      const int sock = socket(AF_INET, SOCK_DGRAM, 0);
    30      if (sock < 0) {
    31          perror("socket");
    32          return 1;
    33      }
    34  
    35      struct ifreq req;
    36      memset(&req, 0, sizeof(req));
    37      strncpy(req.ifr_name, "lo", IFNAMSIZ);
    38      if (ioctl(sock, SIOCGIFFLAGS, &req) < 0) {
    39          perror("SIOCGIFFLAGS");
    40          return 1;
    41      }
    42  
    43      req.ifr_flags |= IFF_UP;
    44      if (ioctl(sock, SIOCSIFFLAGS, &req) < 0) {
    45          perror("SIOCSIFFLAGS");
    46          return 1;
    47      }
    48      close(sock);
    49      return 0;
    50  }
    51  
    52  // deny_groups disables the ability to call setgroups(2). This is required
    53  // before we can successfully write to gid_map in map_ids.
    54  int deny_groups() {
    55      FILE* f = fopen("/proc/self/setgroups", "w");
    56      if (!f) {
    57          perror("fopen /proc/self/setgroups");
    58          return 1;
    59      }
    60      if (fputs("deny\n", f) < 0) {
    61          perror("fputs");
    62          return 1;
    63      }
    64      return fclose(f);
    65  }
    66  
    67  // map_ids maps the user id or group id inside the namespace to those outside.
    68  // Without this we fail to create directories in the tmpfs with an EOVERFLOW.
    69  int map_ids(int out_id, const char* path) {
    70      FILE* f = fopen(path, "w");
    71      if (!f) {
    72          perror("fopen");
    73          return 1;
    74      }
    75      if (fprintf(f, "%d %d 1\n", out_id, out_id) < 0) {
    76          perror("fprintf");
    77          return 1;
    78      }
    79      if (fclose(f) != 0) {
    80          perror("fclose");
    81          return 1;
    82      }
    83      return 0;
    84  }
    85  
    86  // mount_tmp mounts a tmpfs on /tmp for the tests to muck about in.
    87  int mount_tmp() {
    88      // Remounting / as private is necessary so that the tmpfs mount isn't visible to anyone else.
    89      if (mount("none", "/", NULL, MS_REC | MS_PRIVATE, NULL) != 0) {
    90          perror("remount");
    91          return 1;
    92      }
    93      const int flags = MS_LAZYTIME | MS_NOATIME | MS_NODEV | MS_NOSUID;
    94      if (mount("tmpfs", "/tmp", "tmpfs", flags, NULL) != 0) {
    95          perror("mount");
    96          return 1;
    97      }
    98      return setenv("TMPDIR", "/tmp", 1);
    99  }
   100  
   101  // mount_test bind mounts the test directory to
   102  int mount_test() {
   103      const char* d = "/tmp/plz_sandbox";
   104      const char* dir = getenv("TEST_DIR");
   105      if (!dir) {
   106          fputs("TEST_DIR not set, will not bind-mount to /tmp/test\n", stderr);
   107          return 0;
   108      }
   109      if (mkdir(d, S_IRWXU) != 0) {
   110          perror("mkdir /tmp/test");
   111          return 1;
   112      }
   113      if (mount(dir, d, "", MS_BIND, NULL) != 0) {
   114          perror("bind mount");
   115          return 1;
   116      }
   117      if (setenv("TEST_DIR", d, 1) != 0 ||
   118          setenv("TMP_DIR", d, 1) != 0 ||
   119          setenv("HOME", d, 1) != 0) {
   120          perror("setenv");
   121          return 1;
   122      }
   123      return chdir(d);
   124  }
   125  
   126  // contain separates the process into new namespaces to sandbox it.
   127  int contain(char* argv[]) {
   128      const uid_t uid = getuid();
   129      const uid_t gid = getgid();
   130      if (unshare(CLONE_NEWUSER | CLONE_NEWNET | CLONE_NEWUTS | CLONE_NEWIPC | CLONE_NEWNS) != 0) {
   131          perror("unshare");
   132          fputs("Your user doesn't seem to have enough permissions to call unshare(2).\n", stderr);
   133          fputs("please_sandbox requires support for user namespaces (usually >= Linux 3.10)\n", stderr);
   134          return 1;
   135      }
   136      if (deny_groups() != 0) {
   137        return 1;
   138      }
   139      if (map_ids(uid, "/proc/self/uid_map") != 0 ||
   140          map_ids(gid, "/proc/self/gid_map") != 0) {
   141          return 1;
   142      }
   143      if (mount_tmp() != 0) {
   144          return 1;
   145      }
   146      if (mount_test() != 0) {
   147          return 1;
   148      }
   149      if (lo_up() != 0) {
   150          return 1;
   151      }
   152      return execvp(argv[0], argv);
   153  }
   154  
   155  #else
   156  
   157  // On non-Linux systems contain simply execs a subprocess.
   158  // It's not really expected to be used there, this is simply to make it compile.
   159  int contain(char* argv[]) {
   160      return execvp(argv[0], argv);
   161  }
   162  
   163  #endif
   164  
   165  
   166  int main(int argc, char* argv[]) {
   167      if (argc < 2) {
   168          fputs("please_sandbox implements sandboxing for Please.\n", stderr);
   169          fputs("It takes no flags, it simply executes the command given as arguments.\n", stderr);
   170          fputs("Usage: plz_sandbox command args...\n", stderr);
   171          exit(1);
   172      }
   173      return contain(&argv[1]);
   174  }