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 }