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