github.com/cloudfoundry-attic/garden-linux@v0.333.2-candidate/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  #define EXECVEAT_CODE 322
    89  int execveat(int fd, const char *path, char **argv, char **envp, int flags) {
    90      return syscall(EXECVEAT_CODE, fd, path, argv, envp, flags);
    91  }
    92  #endif
    93  
    94  /* nothing seems to define this... */
    95  int setns(int fd, int nstype);
    96  
    97  int main(int argc, char **argv) {
    98    int rv;
    99    int mntnsfd;
   100    int usrnsfd;
   101    char *user = NULL;
   102    char *destination = NULL;
   103    int tpid;
   104    int containerworkdir;
   105    char *tarpath;
   106    int tarfd;
   107    char *compress = NULL;
   108    struct passwd *pw;
   109  
   110    if(argc < 5) {
   111      fprintf(stderr, "Usage: %s <tar path> <wshd pid> <user> <destination> [files to compress]\n", argv[0]);
   112      return 1;
   113    }
   114  
   115    tarpath = argv[1];
   116  
   117    rv = sscanf(argv[2], "%d", &tpid);
   118    if(rv != 1) {
   119      fprintf(stderr, "invalid pid\n");
   120      return 1;
   121    }
   122  
   123    user = argv[3];
   124    destination = argv[4];
   125  
   126    if(argc > 5) {
   127      compress = argv[5];
   128    }
   129  
   130    char mntnspath[PATH_MAX];
   131    rv = snprintf(mntnspath, sizeof(mntnspath), "/proc/%u/ns/mnt", tpid);
   132    if(rv == -1) {
   133      perror("snprintf ns mnt path");
   134      return 1;
   135    }
   136  
   137    mntnsfd = open(mntnspath, O_RDONLY);
   138    if(mntnsfd == -1) {
   139      perror("open mnt namespace");
   140      return 1;
   141    }
   142  
   143    tarfd = open(tarpath, O_RDONLY|O_CLOEXEC);
   144    if(tarfd == -1) {
   145      perror("open host rootfs tar");
   146      return 1;
   147    }
   148  
   149    char usrnspath[PATH_MAX];
   150    rv = snprintf(usrnspath, sizeof(usrnspath), "/proc/%u/ns/user", tpid);
   151    if(rv == -1) {
   152      perror("snprintf ns user path");
   153      return 1;
   154    }
   155  
   156    usrnsfd = open(usrnspath, O_RDONLY);
   157    if(usrnsfd == -1) {
   158      perror("open user namespace");
   159      return 1;
   160    }
   161  
   162    /* switch to container's mount namespace/rootfs */
   163    rv = setns(mntnsfd, CLONE_NEWNS);
   164    if(rv == -1) {
   165      perror("setns");
   166      return 1;
   167    }
   168    close(mntnsfd);
   169  
   170    /* switch to container's user namespace so that user lookup returns correct uids */
   171    /* we allow this to fail if the container isn't user-namespaced */
   172    setns(usrnsfd, CLONE_NEWUSER);
   173  
   174    pw = getpwnam(user);
   175    if(pw == NULL) {
   176      perror("getpwnam");
   177      return 1;
   178    }
   179  
   180    rv = chdir(pw->pw_dir);
   181    if(rv == -1) {
   182      perror("chdir to user home");
   183      return 1;
   184    }
   185  
   186    rv = setgid(0);
   187    if(rv == -1) {
   188      perror("setgid");
   189      return 1;
   190    }
   191  
   192    rv = setuid(0);
   193    if(rv == -1) {
   194      perror("setuid");
   195      return 1;
   196    }
   197  
   198    /* create destination directory */
   199    rv = mkdir_p_as(destination, pw->pw_uid, pw->pw_gid);
   200    if(rv == -1) {
   201      char msg[1024];
   202      sprintf(msg, "mkdir_p_as %d %d", pw->pw_uid, pw->pw_gid);
   203      perror(msg);
   204      return 1;
   205    }
   206  
   207    /* save off destination dir for switching back to it later */
   208    containerworkdir = open(destination, O_RDONLY);
   209    if(containerworkdir == -1) {
   210      perror("open container destination");
   211      return 1;
   212    }
   213  
   214    /* switch to container's destination directory, with host still as rootfs */
   215    rv = fchdir(containerworkdir);
   216    if(rv == -1) {
   217      perror("fchdir to container destination");
   218      return 1;
   219    }
   220  
   221    rv = close(containerworkdir);
   222    if(rv == -1) {
   223      perror("close container destination");
   224      return 1;
   225    }
   226  
   227    rv = setgid(pw->pw_gid);
   228    if(rv == -1) {
   229      perror("setgid");
   230      return 1;
   231    }
   232  
   233    rv = setuid(pw->pw_uid);
   234    if(rv == -1) {
   235      perror("setuid");
   236      return 1;
   237    }
   238  
   239    if(compress != NULL) {
   240      rv = execveat(tarfd, "", (char*[5]){"tar", "cf", "-", compress, NULL}, (char*[0]){}, AT_EMPTY_PATH);
   241      if(rv == -1) {
   242        perror("execveat");
   243        return 1;
   244      }
   245    } else {
   246      rv = execveat(tarfd, "", (char*[4]){"tar", "xf", "-", NULL}, (char*[0]){}, AT_EMPTY_PATH);
   247      if(rv == -1) {
   248        perror("execveat");
   249        return 1;
   250      }
   251    }
   252  
   253    // unreachable
   254    return 2;
   255  }