]> git.proxmox.com Git - mirror_lxc.git/blob - src/lxc/cmd/lxc_usernsexec.c
10557dd5192fb7fd0ac1f48784ac1d92789ce6e2
[mirror_lxc.git] / src / lxc / cmd / lxc_usernsexec.c
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23 */
24
25 #ifndef _GNU_SOURCE
26 #define _GNU_SOURCE 1
27 #endif
28 #include <errno.h>
29 #include <fcntl.h>
30 #include <grp.h>
31 #include <libgen.h>
32 #include <pwd.h>
33 #include <sched.h>
34 #include <signal.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <sys/mount.h>
39 #include <sys/stat.h>
40 #include <sys/syscall.h>
41 #include <sys/types.h>
42 #include <sys/wait.h>
43 #include <unistd.h>
44
45 #include "conf.h"
46 #include "config.h"
47 #include "list.h"
48 #include "log.h"
49 #include "macro.h"
50 #include "file_utils.h"
51 #include "string_utils.h"
52 #include "syscall_wrappers.h"
53 #include "utils.h"
54
55 extern int lxc_log_fd;
56
57 static void usage(const char *name)
58 {
59 printf("usage: %s [-h] [-m <uid-maps>] -- [command [arg ..]]\n", name);
60 printf("\n");
61 printf(" -h this message\n");
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");
73 }
74
75 static void opentty(const char *tty, int which)
76 {
77 int fd, flags, ret;
78
79 if (tty[0] == '\0')
80 return;
81
82 fd = open(tty, O_RDWR | O_NONBLOCK);
83 if (fd < 0) {
84 CMD_SYSERROR("Failed to open tty");
85 return;
86 }
87
88 flags = fcntl(fd, F_GETFL);
89 flags &= ~O_NONBLOCK;
90 ret = fcntl(fd, F_SETFL, flags);
91 if (ret < 0) {
92 CMD_SYSINFO("Failed to remove O_NONBLOCK from file descriptor %d", fd);
93 close(fd);
94 return;
95 }
96
97 close(which);
98 if (fd != which) {
99 (void)dup2(fd, which);
100 close(fd);
101 }
102 }
103 /* Code copy end */
104
105 static int do_child(void *vargv)
106 {
107 int ret;
108 char **argv = (char **)vargv;
109
110 /* Assume we want to become root */
111 if (!lxc_switch_uid_gid(0, 0))
112 return -1;
113
114 if (!lxc_setgroups(0, NULL))
115 return -1;
116
117 ret = unshare(CLONE_NEWNS);
118 if (ret < 0) {
119 CMD_SYSERROR("Failed to unshare mount namespace");
120 return -1;
121 }
122
123 if (detect_shared_rootfs()) {
124 ret = mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL);
125 if (ret < 0) {
126 CMD_SYSINFO("Failed to make \"/\" rslave");
127 return -1;
128 }
129 }
130
131 execvp(argv[0], argv);
132 CMD_SYSERROR("Failed to execute \"%s\"", argv[0]);
133 return -1;
134 }
135
136 static struct lxc_list active_map;
137
138 /*
139 * Given a string like "b:0:100000:10", map both uids and gids 0-10 to 100000
140 * to 100010
141 */
142 static int parse_map(char *map)
143 {
144 int i, ret;
145 long host_id, ns_id, range;
146 char which;
147 struct id_map *newmap;
148 char types[2] = {'u', 'g'};
149 struct lxc_list *tmp = NULL;
150
151 if (!map)
152 return -1;
153
154 ret = sscanf(map, "%c:%ld:%ld:%ld", &which, &ns_id, &host_id, &range);
155 if (ret != 4)
156 return -1;
157
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;
189 }
190
191 /*
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.
198 */
199 static int read_default_map(char *fnam, int which, char *user)
200 {
201 size_t len;
202 char *p1, *p2;
203 FILE *fin;
204 int ret = -1;
205 size_t sz = 0;
206 char *line = NULL;
207 struct lxc_list *tmp = NULL;
208 struct id_map *newmap = NULL;
209
210 fin = fopen(fnam, "r");
211 if (!fin)
212 return -1;
213
214 len = strlen(user);
215 while (getline(&line, &sz, fin) != -1) {
216 if (sz <= len || strncmp(line, user, len) != 0 || line[len] != ':')
217 continue;
218
219 p1 = strchr(line, ':');
220 if (!p1)
221 continue;
222
223 p2 = strchr(p1 + 1, ':');
224 if (!p2)
225 continue;
226
227 newmap = malloc(sizeof(*newmap));
228 if (!newmap)
229 goto on_error;
230
231 ret = lxc_safe_ulong(p1 + 1, &newmap->hostid);
232 if (ret < 0)
233 goto on_error;
234
235 ret = lxc_safe_ulong(p2 + 1, &newmap->range);
236 if (ret < 0)
237 goto on_error;
238
239 newmap->nsid = 0;
240 newmap->idtype = which;
241
242 ret = -1;
243 tmp = malloc(sizeof(*tmp));
244 if (!tmp)
245 goto on_error;
246
247 tmp->elem = newmap;
248 lxc_list_add_tail(&active_map, tmp);
249 break;
250 }
251
252 ret = 0;
253
254 on_error:
255 fclose(fin);
256 free(line);
257 free(newmap);
258
259 return ret;
260 }
261
262 static int find_default_map(void)
263 {
264 size_t bufsize;
265 char *buf;
266 struct passwd pwent;
267 int ret = -1;
268 struct passwd *pwentp = NULL;
269
270 bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
271 if (bufsize == -1)
272 bufsize = 1024;
273
274 buf = malloc(bufsize);
275 if (!buf)
276 return -1;
277
278 ret = getpwuid_r(getuid(), &pwent, buf, bufsize, &pwentp);
279 if (!pwentp) {
280 if (ret == 0)
281 CMD_SYSERROR("Failed to find matched password record");
282
283 CMD_SYSERROR("Failed to get password record for uid %d", getuid());
284 ret = -1;
285 goto out;
286 }
287
288 ret = read_default_map(subuidfile, ID_TYPE_UID, pwent.pw_name);
289 if (ret < 0)
290 goto out;
291
292 ret = read_default_map(subgidfile, ID_TYPE_GID, pwent.pw_name);
293 if (ret < 0)
294 goto out;
295
296 ret = 0;
297
298 out:
299 free(buf);
300
301 return ret;
302 }
303
304 int main(int argc, char *argv[])
305 {
306 int c, pid, ret, status;
307 char buf[1];
308 int pipe_fds1[2], /* child tells parent it has unshared */
309 pipe_fds2[2]; /* parent tells child it is mapped and may proceed */
310 unsigned long flags = CLONE_NEWUSER | CLONE_NEWNS;
311 char ttyname0[256] = {0}, ttyname1[256] = {0}, ttyname2[256] = {0};
312 char *default_args[] = {"/bin/sh", NULL};
313
314 lxc_log_fd = STDERR_FILENO;
315
316 if (isatty(STDIN_FILENO)) {
317 ret = readlink("/proc/self/fd/0", ttyname0, sizeof(ttyname0));
318 if (ret < 0) {
319 CMD_SYSERROR("Failed to open stdin");
320 _exit(EXIT_FAILURE);
321 }
322
323 ret = readlink("/proc/self/fd/1", ttyname1, sizeof(ttyname1));
324 if (ret < 0) {
325 CMD_SYSINFO("Failed to open stdout. Continuing");
326 ttyname1[0] = '\0';
327 }
328
329 ret = readlink("/proc/self/fd/2", ttyname2, sizeof(ttyname2));
330 if (ret < 0) {
331 CMD_SYSINFO("Failed to open stderr. Continuing");
332 ttyname2[0] = '\0';
333 }
334 }
335
336 lxc_list_init(&active_map);
337
338 while ((c = getopt(argc, argv, "m:h")) != EOF) {
339 switch (c) {
340 case 'm':
341 ret = parse_map(optarg);
342 if (ret < 0) {
343 usage(argv[0]);
344 _exit(EXIT_FAILURE);
345 }
346 break;
347 case 'h':
348 usage(argv[0]);
349 _exit(EXIT_SUCCESS);
350 default:
351 usage(argv[0]);
352 _exit(EXIT_FAILURE);
353 }
354 };
355
356 if (lxc_list_empty(&active_map)) {
357 ret = find_default_map();
358 if (ret < 0) {
359 fprintf(stderr, "Failed to find subuid or subgid allocation\n");
360 _exit(EXIT_FAILURE);
361 }
362 }
363
364 argv = &argv[optind];
365 argc = argc - optind;
366 if (argc < 1)
367 argv = default_args;
368
369 ret = pipe2(pipe_fds1, O_CLOEXEC);
370 if (ret < 0) {
371 CMD_SYSERROR("Failed to open new pipe");
372 _exit(EXIT_FAILURE);
373 }
374
375 ret = pipe2(pipe_fds2, O_CLOEXEC);
376 if (ret < 0) {
377 CMD_SYSERROR("Failed to open new pipe");
378 close(pipe_fds1[0]);
379 close(pipe_fds1[1]);
380 _exit(EXIT_FAILURE);
381 }
382
383 pid = fork();
384 if (pid < 0) {
385 close(pipe_fds1[0]);
386 close(pipe_fds1[1]);
387 close(pipe_fds2[0]);
388 close(pipe_fds2[1]);
389 _exit(EXIT_FAILURE);
390 }
391
392 if (pid == 0) {
393 close(pipe_fds1[0]);
394 close(pipe_fds2[1]);
395
396 opentty(ttyname0, STDIN_FILENO);
397 opentty(ttyname1, STDOUT_FILENO);
398 opentty(ttyname2, STDERR_FILENO);
399
400 ret = unshare(flags);
401 if (ret < 0) {
402 CMD_SYSERROR("Failed to unshare mount and user namespace");
403 close(pipe_fds1[1]);
404 close(pipe_fds2[0]);
405 _exit(EXIT_FAILURE);
406 }
407
408 buf[0] = '1';
409 ret = lxc_write_nointr(pipe_fds1[1], buf, 1);
410 if (ret != 1) {
411 CMD_SYSERROR("Failed to write to pipe file descriptor %d",
412 pipe_fds1[1]);
413 close(pipe_fds1[1]);
414 close(pipe_fds2[0]);
415 _exit(EXIT_FAILURE);
416 }
417
418 ret = lxc_read_nointr(pipe_fds2[0], buf, 1);
419 if (ret != 1) {
420 CMD_SYSERROR("Failed to read from pipe file descriptor %d",
421 pipe_fds2[0]);
422 close(pipe_fds1[1]);
423 close(pipe_fds2[0]);
424 _exit(EXIT_FAILURE);
425 }
426
427 close(pipe_fds1[1]);
428 close(pipe_fds2[0]);
429
430 if (buf[0] != '1') {
431 fprintf(stderr, "Received unexpected value from parent process\n");
432 _exit(EXIT_FAILURE);
433 }
434
435 ret = do_child((void *)argv);
436 if (ret < 0)
437 _exit(EXIT_FAILURE);
438
439 _exit(EXIT_SUCCESS);
440 }
441
442 close(pipe_fds1[1]);
443 close(pipe_fds2[0]);
444
445 ret = lxc_read_nointr(pipe_fds1[0], buf, 1);
446 if (ret <= 0)
447 CMD_SYSERROR("Failed to read from pipe file descriptor %d", pipe_fds1[0]);
448
449 buf[0] = '1';
450
451 ret = lxc_map_ids(&active_map, pid);
452 if (ret < 0)
453 fprintf(stderr, "Failed to write id mapping for child process\n");
454
455 ret = lxc_write_nointr(pipe_fds2[1], buf, 1);
456 if (ret < 0) {
457 CMD_SYSERROR("Failed to write to pipe file descriptor %d", pipe_fds2[1]);
458 _exit(EXIT_FAILURE);
459 }
460
461 ret = waitpid(pid, &status, __WALL);
462 if (ret < 0) {
463 CMD_SYSERROR("Failed to wait on child process");
464 _exit(EXIT_FAILURE);
465 }
466
467 _exit(WEXITSTATUS(status));
468 }