]> git.proxmox.com Git - mirror_lxc.git/blame - src/lxc/lxc_usernsexec.c
always check whether rootfs is shared
[mirror_lxc.git] / src / lxc / lxc_usernsexec.c
CommitLineData
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
47int unshare(int flags);
48
49static 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
68static 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
94static 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
126struct 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 132static struct id_map default_map = {
d155b47d
SH
133 .which = 'b',
134 .host_id = 100000,
135 .ns_id = 0,
136 .range = 10000,
137};
138static 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 */
144static 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
166out_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 */
180static 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"
228static 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
240static 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
260static 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 335out:
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
346int 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}