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