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 }