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