github.com/geofffranks/garden-linux@v0.0.0-20160715111146-26c893169cfa/linux_backend/src/nstar/nstar.c (about)

     1  /*
     2   * This executable passes through to the host's tar, extracting into a
     3   * directory in the container.
     4   *
     5   * It does this with a funky dance involving switching to the container's mount
     6   * namespace, creating the destination and saving off its fd, and then
     7   * switching back to the host's rootfs (but the container's destination) for
     8   * the actual untarring.
     9   */
    10  
    11  #include <stdio.h>
    12  #include <errno.h>
    13  #include <string.h>
    14  #include <sys/param.h>
    15  #include <sys/stat.h>
    16  #include <sys/types.h>
    17  #include <sys/syscall.h>
    18  #include <unistd.h>
    19  #include <linux/sched.h>
    20  #include <linux/fcntl.h>
    21  
    22  #include "pwd.h"
    23  
    24  /* create a directory; chown only if newly created */
    25  int mkdir_as(const char *dir, uid_t uid, gid_t gid) {
    26    int rv;
    27  
    28    rv = mkdir(dir, 0755);
    29    if(rv == 0) {
    30      /* new directory; set ownership */
    31      return chown(dir, uid, gid);
    32    } else {
    33      if(errno == EEXIST) {
    34        /* if directory already exists, leave ownership as-is */
    35        return 0;
    36      } else {
    37        /* if any other error, abort */
    38        return rv;
    39      }
    40    }
    41  
    42    /* unreachable */
    43    return -1;
    44  }
    45  
    46  /* recursively mkdir with directories owned by a given user */
    47  int mkdir_p_as(const char *dir, uid_t uid, gid_t gid) {
    48    char tmp[PATH_MAX];
    49    char *p = NULL;
    50    size_t len;
    51    int rv;
    52  
    53    /* copy the given dir as it'll be mutated */
    54    snprintf(tmp, sizeof(tmp), "%s", dir);
    55    len = strlen(tmp);
    56  
    57    /* strip trailing slash */
    58    if(tmp[len - 1] == '/')
    59      tmp[len - 1] = 0;
    60  
    61    for(p = tmp + 1; *p; p++) {
    62      if(*p == '/') {
    63        /* temporarily null-terminte the string so that mkdir only creates this
    64         * path segment */
    65        *p = 0;
    66  
    67        /* mkdir with truncated path segment */
    68        rv = mkdir_as(tmp, uid, gid);
    69        if(rv == -1) {
    70          return rv;
    71        }
    72  
    73        /* restore path separator */
    74        *p = '/';
    75      }
    76    }
    77  
    78    /* create final destination */
    79    return mkdir_as(tmp, uid, gid);
    80  }
    81  
    82  
    83  #ifndef execveat
    84  /**
    85   * We need to define execveat here since glibc does not provide a wrapper
    86   * for this syscall yet. This code will not run once glibc implements this.
    87   */
    88  #if defined (__PPC64__)
    89  #define EXECVEAT_CODE 362
    90  #else
    91  #define EXECVEAT_CODE 322
    92  #endif
    93  int execveat(int fd, const char *path, char **argv, char **envp, int flags) {
    94      return syscall(EXECVEAT_CODE, fd, path, argv, envp, flags);
    95  }
    96  #endif
    97  
    98  /* nothing seems to define this... */
    99  int setns(int fd, int nstype);
   100  
   101  int main(int argc, char **argv) {
   102    int rv;
   103    int mntnsfd;
   104    int usrnsfd;
   105    char *user = NULL;
   106    char *destination = NULL;
   107    int tpid;
   108    int containerworkdir;
   109    char *tarpath;
   110    int tarfd;
   111    char *compress = NULL;
   112    struct passwd *pw;
   113  
   114    if(argc < 5) {
   115      fprintf(stderr, "Usage: %s <tar path> <wshd pid> <user> <destination> [files to compress]\n", argv[0]);
   116      return 1;
   117    }
   118  
   119    tarpath = argv[1];
   120  
   121    rv = sscanf(argv[2], "%d", &tpid);
   122    if(rv != 1) {
   123      fprintf(stderr, "invalid pid\n");
   124      return 1;
   125    }
   126  
   127    user = argv[3];
   128    destination = argv[4];
   129  
   130    if(argc > 5) {
   131      compress = argv[5];
   132    }
   133  
   134    char mntnspath[PATH_MAX];
   135    rv = snprintf(mntnspath, sizeof(mntnspath), "/proc/%u/ns/mnt", tpid);
   136    if(rv == -1) {
   137      perror("snprintf ns mnt path");
   138      return 1;
   139    }
   140  
   141    mntnsfd = open(mntnspath, O_RDONLY);
   142    if(mntnsfd == -1) {
   143      perror("open mnt namespace");
   144      return 1;
   145    }
   146  
   147    tarfd = open(tarpath, O_RDONLY|O_CLOEXEC);
   148    if(tarfd == -1) {
   149      perror("open host rootfs tar");
   150      return 1;
   151    }
   152  
   153    char usrnspath[PATH_MAX];
   154    rv = snprintf(usrnspath, sizeof(usrnspath), "/proc/%u/ns/user", tpid);
   155    if(rv == -1) {
   156      perror("snprintf ns user path");
   157      return 1;
   158    }
   159  
   160    usrnsfd = open(usrnspath, O_RDONLY);
   161    if(usrnsfd == -1) {
   162      perror("open user namespace");
   163      return 1;
   164    }
   165  
   166    /* switch to container's mount namespace/rootfs */
   167    rv = setns(mntnsfd, CLONE_NEWNS);
   168    if(rv == -1) {
   169      perror("setns");
   170      return 1;
   171    }
   172    close(mntnsfd);
   173  
   174    /* switch to container's user namespace so that user lookup returns correct uids */
   175    /* we allow this to fail if the container isn't user-namespaced */
   176    setns(usrnsfd, CLONE_NEWUSER);
   177  
   178    pw = getpwnam(user);
   179    if(pw == NULL) {
   180      perror("getpwnam");
   181      return 1;
   182    }
   183  
   184    rv = chdir(pw->pw_dir);
   185    if(rv == -1) {
   186      perror("chdir to user home");
   187      return 1;
   188    }
   189  
   190    rv = setgid(0);
   191    if(rv == -1) {
   192      perror("setgid");
   193      return 1;
   194    }
   195  
   196    rv = setuid(0);
   197    if(rv == -1) {
   198      perror("setuid");
   199      return 1;
   200    }
   201  
   202    /* create destination directory */
   203    rv = mkdir_p_as(destination, pw->pw_uid, pw->pw_gid);
   204    if(rv == -1) {
   205      char msg[1024];
   206      sprintf(msg, "mkdir_p_as %d %d", pw->pw_uid, pw->pw_gid);
   207      perror(msg);
   208      return 1;
   209    }
   210  
   211    /* save off destination dir for switching back to it later */
   212    containerworkdir = open(destination, O_RDONLY);
   213    if(containerworkdir == -1) {
   214      perror("open container destination");
   215      return 1;
   216    }
   217  
   218    /* switch to container's destination directory, with host still as rootfs */
   219    rv = fchdir(containerworkdir);
   220    if(rv == -1) {
   221      perror("fchdir to container destination");
   222      return 1;
   223    }
   224  
   225    rv = close(containerworkdir);
   226    if(rv == -1) {
   227      perror("close container destination");
   228      return 1;
   229    }
   230  
   231    rv = setgid(pw->pw_gid);
   232    if(rv == -1) {
   233      perror("setgid");
   234      return 1;
   235    }
   236  
   237    rv = setuid(pw->pw_uid);
   238    if(rv == -1) {
   239      perror("setuid");
   240      return 1;
   241    }
   242  
   243    if(compress != NULL) {
   244      rv = execveat(tarfd, "", (char*[5]){"tar", "cf", "-", compress, NULL}, (char*[0]){}, AT_EMPTY_PATH);
   245      if(rv == -1) {
   246        perror("execveat");
   247        return 1;
   248      }
   249    } else {
   250      rv = execveat(tarfd, "", (char*[4]){"tar", "xf", "-", NULL}, (char*[0]){}, AT_EMPTY_PATH);
   251      if(rv == -1) {
   252        perror("execveat");
   253        return 1;
   254      }
   255    }
   256  
   257    // unreachable
   258    return 2;
   259  }