]>
Commit | Line | Data |
---|---|---|
d155b47d SH |
1 | /* |
2 | * (C) Copyright IBM Corp. 2008 | |
3 | * (C) Copyright Canonical, Inc 2010-2013 | |
4 | * | |
5 | * Authors: | |
6 | * Serge Hallyn <serge.hallyn@ubuntu.com> | |
7 | * (Once upon a time, this was based on nsexec from the IBM | |
8 | * container tools) | |
9 | * | |
10 | * This library is free software; you can redistribute it and/or | |
11 | * modify it under the terms of the GNU Lesser General Public | |
12 | * License as published by the Free Software Foundation; either | |
13 | * version 2.1 of the License, or (at your option) any later version. | |
14 | * | |
15 | * This library is distributed in the hope that it will be useful, | |
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
18 | * Lesser General Public License for more details. | |
19 | * | |
20 | * You should have received a copy of the GNU Lesser General Public | |
21 | * License along with this library; if not, write to the Free Software | |
250b1eec | 22 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
d155b47d | 23 | */ |
72a50694 | 24 | |
d38dd64a CB |
25 | #ifndef _GNU_SOURCE |
26 | #define _GNU_SOURCE 1 | |
27 | #endif | |
72a50694 CB |
28 | #include <errno.h> |
29 | #include <fcntl.h> | |
30 | #include <grp.h> | |
31 | #include <libgen.h> | |
32 | #include <pwd.h> | |
d155b47d | 33 | #include <sched.h> |
d155b47d | 34 | #include <signal.h> |
72a50694 CB |
35 | #include <stdio.h> |
36 | #include <stdlib.h> | |
d155b47d | 37 | #include <string.h> |
72a50694 | 38 | #include <sys/mount.h> |
d155b47d | 39 | #include <sys/stat.h> |
72a50694 | 40 | #include <sys/syscall.h> |
d155b47d SH |
41 | #include <sys/types.h> |
42 | #include <sys/wait.h> | |
72a50694 | 43 | #include <unistd.h> |
f2363e38 | 44 | |
0e6e3a41 | 45 | #include "conf.h" |
d38dd64a | 46 | #include "config.h" |
02af8066 CB |
47 | #include "list.h" |
48 | #include "log.h" | |
c881c810 | 49 | #include "macro.h" |
49182c32 CB |
50 | #include "file_utils.h" |
51 | #include "string_utils.h" | |
e8f764b6 | 52 | #include "syscall_wrappers.h" |
7ee37fac | 53 | #include "utils.h" |
d155b47d | 54 | |
2d22b22d TA |
55 | extern int lxc_log_fd; |
56 | ||
d155b47d SH |
57 | static void usage(const char *name) |
58 | { | |
adade80c | 59 | printf("usage: %s [-h] [-m <uid-maps>] -- [command [arg ..]]\n", name); |
d155b47d | 60 | printf("\n"); |
6f94152d | 61 | printf(" -h this message\n"); |
d155b47d SH |
62 | printf("\n"); |
63 | printf(" -m <uid-maps> uid maps to use\n"); | |
64 | printf("\n"); | |
65 | printf(" uid-maps: [u|g|b]:ns_id:host_id:range\n"); | |
66 | printf(" [u|g|b]: map user id, group id, or both\n"); | |
67 | printf(" ns_id: the base id in the new namespace\n"); | |
68 | printf(" host_id: the base id in the parent namespace\n"); | |
69 | printf(" range: how many ids to map\n"); | |
70 | printf(" Note: This program uses newuidmap(2) and newgidmap(2).\n"); | |
71 | printf(" As such, /etc/subuid and /etc/subgid must grant the\n"); | |
72 | printf(" calling user permission to use the mapped ranges\n"); | |
d155b47d SH |
73 | } |
74 | ||
a0ee564f CB |
75 | static void opentty(const char *tty, int which) |
76 | { | |
02af8066 | 77 | int fd, flags, ret; |
b5f4bc78 SH |
78 | |
79 | if (tty[0] == '\0') | |
80 | return; | |
d155b47d SH |
81 | |
82 | fd = open(tty, O_RDWR | O_NONBLOCK); | |
a0ee564f | 83 | if (fd < 0) { |
02af8066 | 84 | CMD_SYSERROR("Failed to open tty"); |
8d4b877a | 85 | return; |
d155b47d SH |
86 | } |
87 | ||
88 | flags = fcntl(fd, F_GETFL); | |
89 | flags &= ~O_NONBLOCK; | |
02af8066 CB |
90 | ret = fcntl(fd, F_SETFL, flags); |
91 | if (ret < 0) { | |
92 | CMD_SYSINFO("Failed to remove O_NONBLOCK from file descriptor %d", fd); | |
22417436 | 93 | close(fd); |
35e3a0cd SG |
94 | return; |
95 | } | |
d155b47d | 96 | |
b5f4bc78 SH |
97 | close(which); |
98 | if (fd != which) { | |
a0ee564f | 99 | (void)dup2(fd, which); |
d155b47d | 100 | close(fd); |
b5f4bc78 | 101 | } |
d155b47d | 102 | } |
1a0e70ac | 103 | /* Code copy end */ |
d155b47d SH |
104 | |
105 | static int do_child(void *vargv) | |
106 | { | |
02af8066 | 107 | int ret; |
d155b47d SH |
108 | char **argv = (char **)vargv; |
109 | ||
1a0e70ac | 110 | /* Assume we want to become root */ |
464c4611 | 111 | if (!lxc_switch_uid_gid(0, 0)) |
d155b47d | 112 | return -1; |
02af8066 | 113 | |
8af07f82 | 114 | if (!lxc_setgroups(0, NULL)) |
d155b47d | 115 | return -1; |
02af8066 CB |
116 | |
117 | ret = unshare(CLONE_NEWNS); | |
118 | if (ret < 0) { | |
119 | CMD_SYSERROR("Failed to unshare mount namespace"); | |
d155b47d SH |
120 | return -1; |
121 | } | |
02af8066 | 122 | |
2c6f3fc9 | 123 | if (detect_shared_rootfs()) { |
02af8066 CB |
124 | ret = mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL); |
125 | if (ret < 0) { | |
126 | CMD_SYSINFO("Failed to make \"/\" rslave"); | |
2c6f3fc9 SH |
127 | return -1; |
128 | } | |
129 | } | |
02af8066 | 130 | |
d155b47d | 131 | execvp(argv[0], argv); |
02af8066 | 132 | CMD_SYSERROR("Failed to execute \"%s\"", argv[0]); |
d155b47d SH |
133 | return -1; |
134 | } | |
135 | ||
0e6e3a41 | 136 | static struct lxc_list active_map; |
d155b47d SH |
137 | |
138 | /* | |
02af8066 CB |
139 | * Given a string like "b:0:100000:10", map both uids and gids 0-10 to 100000 |
140 | * to 100010 | |
d155b47d SH |
141 | */ |
142 | static int parse_map(char *map) | |
143 | { | |
02af8066 CB |
144 | int i, ret; |
145 | long host_id, ns_id, range; | |
146 | char which; | |
d155b47d | 147 | struct id_map *newmap; |
0e6e3a41 | 148 | char types[2] = {'u', 'g'}; |
02af8066 | 149 | struct lxc_list *tmp = NULL; |
d155b47d SH |
150 | |
151 | if (!map) | |
152 | return -1; | |
0e6e3a41 SG |
153 | |
154 | ret = sscanf(map, "%c:%ld:%ld:%ld", &which, &ns_id, &host_id, &range); | |
d155b47d | 155 | if (ret != 4) |
0e6e3a41 | 156 | return -1; |
d155b47d | 157 | |
0e6e3a41 SG |
158 | if (which != 'b' && which != 'u' && which != 'g') |
159 | return -1; | |
160 | ||
161 | for (i = 0; i < 2; i++) { | |
162 | if (which != types[i] && which != 'b') | |
163 | continue; | |
164 | ||
165 | newmap = malloc(sizeof(*newmap)); | |
166 | if (!newmap) | |
167 | return -1; | |
168 | ||
169 | newmap->hostid = host_id; | |
170 | newmap->nsid = ns_id; | |
171 | newmap->range = range; | |
172 | ||
173 | if (types[i] == 'u') | |
174 | newmap->idtype = ID_TYPE_UID; | |
175 | else | |
176 | newmap->idtype = ID_TYPE_GID; | |
177 | ||
178 | tmp = malloc(sizeof(*tmp)); | |
179 | if (!tmp) { | |
180 | free(newmap); | |
181 | return -1; | |
182 | } | |
183 | ||
184 | tmp->elem = newmap; | |
185 | lxc_list_add_tail(&active_map, tmp); | |
186 | } | |
187 | ||
188 | return 0; | |
d155b47d SH |
189 | } |
190 | ||
191 | /* | |
02af8066 CB |
192 | * This is called if the user did not pass any uid ranges in through -m flags. |
193 | * It's called once to get the default uid map, and once for the default gid | |
194 | * map. | |
195 | * Go through /etc/subuids and /etc/subgids to find this user's allowed map. We | |
196 | * only use the first one for each of uid and gid, because otherwise we're not | |
197 | * sure which entries the user wanted. | |
d155b47d | 198 | */ |
5ff02844 | 199 | static int read_default_map(char *fnam, int which, char *user) |
d155b47d | 200 | { |
5ff02844 | 201 | size_t len; |
02af8066 | 202 | char *p1, *p2; |
c14ea11d | 203 | unsigned long ul1, ul2; |
d155b47d | 204 | FILE *fin; |
62a38dff | 205 | int ret = -1; |
02af8066 CB |
206 | size_t sz = 0; |
207 | char *line = NULL; | |
0e6e3a41 | 208 | struct lxc_list *tmp = NULL; |
62a38dff | 209 | struct id_map *newmap = NULL; |
d155b47d SH |
210 | |
211 | fin = fopen(fnam, "r"); | |
212 | if (!fin) | |
213 | return -1; | |
02af8066 | 214 | |
5ff02844 | 215 | len = strlen(user); |
d155b47d | 216 | while (getline(&line, &sz, fin) != -1) { |
5ff02844 | 217 | if (sz <= len || strncmp(line, user, len) != 0 || line[len] != ':') |
d155b47d | 218 | continue; |
02af8066 | 219 | |
46cd2845 | 220 | p1 = strchr(line, ':'); |
d155b47d SH |
221 | if (!p1) |
222 | continue; | |
02af8066 CB |
223 | |
224 | p2 = strchr(p1 + 1, ':'); | |
d155b47d SH |
225 | if (!p2) |
226 | continue; | |
02af8066 | 227 | |
c14ea11d CN |
228 | line[strlen(line) - 1] = '\0'; |
229 | *p2 = '\0'; | |
02af8066 | 230 | |
c14ea11d | 231 | ret = lxc_safe_ulong(p1 + 1, &ul1); |
62a38dff | 232 | if (ret < 0) |
c14ea11d | 233 | break; |
d3b413e7 | 234 | |
c14ea11d | 235 | ret = lxc_safe_ulong(p2 + 1, &ul2); |
62a38dff | 236 | if (ret < 0) |
c14ea11d CN |
237 | break; |
238 | ||
239 | ret = -1; | |
240 | newmap = malloc(sizeof(*newmap)); | |
241 | if (!newmap) | |
242 | break; | |
d3b413e7 | 243 | |
0e6e3a41 SG |
244 | newmap->nsid = 0; |
245 | newmap->idtype = which; | |
c14ea11d CN |
246 | newmap->hostid = ul1; |
247 | newmap->range = ul2; | |
0e6e3a41 SG |
248 | |
249 | tmp = malloc(sizeof(*tmp)); | |
c14ea11d CN |
250 | if (!tmp) { |
251 | free(newmap); | |
252 | break; | |
253 | } | |
0e6e3a41 SG |
254 | |
255 | tmp->elem = newmap; | |
256 | lxc_list_add_tail(&active_map, tmp); | |
c14ea11d CN |
257 | |
258 | ret = 0; | |
d155b47d SH |
259 | break; |
260 | } | |
261 | ||
d155b47d | 262 | fclose(fin); |
62a38dff | 263 | free(line); |
02af8066 | 264 | |
62a38dff | 265 | return ret; |
d155b47d SH |
266 | } |
267 | ||
d155b47d SH |
268 | static int find_default_map(void) |
269 | { | |
02af8066 CB |
270 | size_t bufsize; |
271 | char *buf; | |
cb7aa5e8 | 272 | struct passwd pwent; |
02af8066 | 273 | int ret = -1; |
cb7aa5e8 | 274 | struct passwd *pwentp = NULL; |
cb7aa5e8 DJ |
275 | |
276 | bufsize = sysconf(_SC_GETPW_R_SIZE_MAX); | |
277 | if (bufsize == -1) | |
278 | bufsize = 1024; | |
279 | ||
280 | buf = malloc(bufsize); | |
281 | if (!buf) | |
d155b47d | 282 | return -1; |
cb7aa5e8 DJ |
283 | |
284 | ret = getpwuid_r(getuid(), &pwent, buf, bufsize, &pwentp); | |
285 | if (!pwentp) { | |
286 | if (ret == 0) | |
02af8066 | 287 | CMD_SYSERROR("Failed to find matched password record"); |
cb7aa5e8 | 288 | |
02af8066 CB |
289 | CMD_SYSERROR("Failed to get password record for uid %d", getuid()); |
290 | ret = -1; | |
291 | goto out; | |
cb7aa5e8 DJ |
292 | } |
293 | ||
02af8066 CB |
294 | ret = read_default_map(subuidfile, ID_TYPE_UID, pwent.pw_name); |
295 | if (ret < 0) | |
296 | goto out; | |
cb7aa5e8 | 297 | |
02af8066 CB |
298 | ret = read_default_map(subgidfile, ID_TYPE_GID, pwent.pw_name); |
299 | if (ret < 0) | |
300 | goto out; | |
301 | ||
302 | ret = 0; | |
cb7aa5e8 | 303 | |
02af8066 | 304 | out: |
cb7aa5e8 | 305 | free(buf); |
02af8066 CB |
306 | |
307 | return ret; | |
d155b47d SH |
308 | } |
309 | ||
d155b47d SH |
310 | int main(int argc, char *argv[]) |
311 | { | |
02af8066 CB |
312 | int c, pid, ret, status; |
313 | char buf[1]; | |
314 | int pipe_fds1[2], /* child tells parent it has unshared */ | |
315 | pipe_fds2[2]; /* parent tells child it is mapped and may proceed */ | |
d155b47d | 316 | unsigned long flags = CLONE_NEWUSER | CLONE_NEWNS; |
02af8066 | 317 | char ttyname0[256] = {0}, ttyname1[256] = {0}, ttyname2[256] = {0}; |
d155b47d | 318 | char *default_args[] = {"/bin/sh", NULL}; |
d155b47d | 319 | |
2d22b22d TA |
320 | lxc_log_fd = STDERR_FILENO; |
321 | ||
02af8066 | 322 | if (isatty(STDIN_FILENO)) { |
5d1df05b SH |
323 | ret = readlink("/proc/self/fd/0", ttyname0, sizeof(ttyname0)); |
324 | if (ret < 0) { | |
02af8066 CB |
325 | CMD_SYSERROR("Failed to open stdin"); |
326 | _exit(EXIT_FAILURE); | |
5d1df05b | 327 | } |
02af8066 | 328 | |
5d1df05b SH |
329 | ret = readlink("/proc/self/fd/1", ttyname1, sizeof(ttyname1)); |
330 | if (ret < 0) { | |
02af8066 CB |
331 | CMD_SYSINFO("Failed to open stdout. Continuing"); |
332 | ttyname1[0] = '\0'; | |
5d1df05b | 333 | } |
02af8066 | 334 | |
5d1df05b SH |
335 | ret = readlink("/proc/self/fd/2", ttyname2, sizeof(ttyname2)); |
336 | if (ret < 0) { | |
02af8066 CB |
337 | CMD_SYSINFO("Failed to open stderr. Continuing"); |
338 | ttyname2[0] = '\0'; | |
5d1df05b | 339 | } |
b5f4bc78 | 340 | } |
d155b47d | 341 | |
0e6e3a41 SG |
342 | lxc_list_init(&active_map); |
343 | ||
d155b47d SH |
344 | while ((c = getopt(argc, argv, "m:h")) != EOF) { |
345 | switch (c) { | |
27fdb6be | 346 | case 'm': |
02af8066 CB |
347 | ret = parse_map(optarg); |
348 | if (ret < 0) { | |
27fdb6be | 349 | usage(argv[0]); |
02af8066 | 350 | _exit(EXIT_FAILURE); |
27fdb6be TH |
351 | } |
352 | break; | |
353 | case 'h': | |
354 | usage(argv[0]); | |
02af8066 | 355 | _exit(EXIT_SUCCESS); |
27fdb6be TH |
356 | default: |
357 | usage(argv[0]); | |
02af8066 | 358 | _exit(EXIT_FAILURE); |
d155b47d SH |
359 | } |
360 | }; | |
361 | ||
0e6e3a41 | 362 | if (lxc_list_empty(&active_map)) { |
02af8066 CB |
363 | ret = find_default_map(); |
364 | if (ret < 0) { | |
365 | fprintf(stderr, "Failed to find subuid or subgid allocation\n"); | |
366 | _exit(EXIT_FAILURE); | |
d155b47d SH |
367 | } |
368 | } | |
369 | ||
370 | argv = &argv[optind]; | |
371 | argc = argc - optind; | |
1285f7d5 | 372 | if (argc < 1) |
d155b47d | 373 | argv = default_args; |
d155b47d | 374 | |
02af8066 CB |
375 | ret = pipe2(pipe_fds1, O_CLOEXEC); |
376 | if (ret < 0) { | |
377 | CMD_SYSERROR("Failed to open new pipe"); | |
378 | _exit(EXIT_FAILURE); | |
379 | } | |
380 | ||
381 | ret = pipe2(pipe_fds2, O_CLOEXEC); | |
382 | if (ret < 0) { | |
383 | CMD_SYSERROR("Failed to open new pipe"); | |
384 | close(pipe_fds1[0]); | |
385 | close(pipe_fds1[1]); | |
386 | _exit(EXIT_FAILURE); | |
d155b47d | 387 | } |
02af8066 | 388 | |
f0a86c6d | 389 | pid = fork(); |
02af8066 | 390 | if (pid < 0) { |
04dc1c00 | 391 | close(pipe_fds1[0]); |
02af8066 CB |
392 | close(pipe_fds1[1]); |
393 | close(pipe_fds2[0]); | |
04dc1c00 | 394 | close(pipe_fds2[1]); |
02af8066 CB |
395 | _exit(EXIT_FAILURE); |
396 | } | |
397 | ||
398 | if (pid == 0) { | |
399 | close(pipe_fds1[0]); | |
400 | close(pipe_fds2[1]); | |
401 | ||
402 | opentty(ttyname0, STDIN_FILENO); | |
403 | opentty(ttyname1, STDOUT_FILENO); | |
404 | opentty(ttyname2, STDERR_FILENO); | |
d155b47d SH |
405 | |
406 | ret = unshare(flags); | |
407 | if (ret < 0) { | |
02af8066 CB |
408 | CMD_SYSERROR("Failed to unshare mount and user namespace"); |
409 | close(pipe_fds1[1]); | |
410 | close(pipe_fds2[0]); | |
411 | _exit(EXIT_FAILURE); | |
d155b47d | 412 | } |
02af8066 | 413 | |
cbaed76d | 414 | buf[0] = '1'; |
02af8066 CB |
415 | ret = lxc_write_nointr(pipe_fds1[1], buf, 1); |
416 | if (ret != 1) { | |
417 | CMD_SYSERROR("Failed to write to pipe file descriptor %d", | |
418 | pipe_fds1[1]); | |
419 | close(pipe_fds1[1]); | |
420 | close(pipe_fds2[0]); | |
421 | _exit(EXIT_FAILURE); | |
d155b47d | 422 | } |
02af8066 CB |
423 | |
424 | ret = lxc_read_nointr(pipe_fds2[0], buf, 1); | |
425 | if (ret != 1) { | |
426 | CMD_SYSERROR("Failed to read from pipe file descriptor %d", | |
427 | pipe_fds2[0]); | |
428 | close(pipe_fds1[1]); | |
429 | close(pipe_fds2[0]); | |
430 | _exit(EXIT_FAILURE); | |
d155b47d SH |
431 | } |
432 | ||
04dc1c00 CB |
433 | close(pipe_fds1[1]); |
434 | close(pipe_fds2[0]); | |
02af8066 CB |
435 | |
436 | if (buf[0] != '1') { | |
437 | fprintf(stderr, "Received unexpected value from parent process\n"); | |
438 | _exit(EXIT_FAILURE); | |
439 | } | |
440 | ||
441 | ret = do_child((void *)argv); | |
442 | if (ret < 0) | |
443 | _exit(EXIT_FAILURE); | |
444 | ||
445 | _exit(EXIT_SUCCESS); | |
d155b47d SH |
446 | } |
447 | ||
04dc1c00 CB |
448 | close(pipe_fds1[1]); |
449 | close(pipe_fds2[0]); | |
02af8066 | 450 | |
ede912b4 | 451 | ret = lxc_read_nointr(pipe_fds1[0], buf, 1); |
02af8066 CB |
452 | if (ret <= 0) |
453 | CMD_SYSERROR("Failed to read from pipe file descriptor %d", pipe_fds1[0]); | |
d155b47d | 454 | |
b543ce96 | 455 | buf[0] = '1'; |
0e6e3a41 | 456 | |
02af8066 CB |
457 | ret = lxc_map_ids(&active_map, pid); |
458 | if (ret < 0) | |
459 | fprintf(stderr, "Failed to write id mapping for child process\n"); | |
1285f7d5 | 460 | |
02af8066 CB |
461 | ret = lxc_write_nointr(pipe_fds2[1], buf, 1); |
462 | if (ret < 0) { | |
463 | CMD_SYSERROR("Failed to write to pipe file descriptor %d", pipe_fds2[1]); | |
464 | _exit(EXIT_FAILURE); | |
d155b47d | 465 | } |
02af8066 | 466 | |
f0a86c6d TH |
467 | ret = waitpid(pid, &status, __WALL); |
468 | if (ret < 0) { | |
02af8066 CB |
469 | CMD_SYSERROR("Failed to wait on child process"); |
470 | _exit(EXIT_FAILURE); | |
d155b47d SH |
471 | } |
472 | ||
02af8066 | 473 | _exit(WEXITSTATUS(status)); |
d155b47d | 474 | } |