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