[lxc-devel] [PATCH] define lxc-usernsexec
Stéphane Graber
stgraber at ubuntu.com
Tue Jul 23 08:28:41 UTC 2013
On Mon, Jul 22, 2013 at 03:23:58PM -0500, Serge Hallyn wrote:
> It uses the newuidmap and newgidmap program to start a shell in
> a mapped user namespace. While newuidmap and newgidmap are
> setuid-root, lxc-usernsexec is not.
>
> If new{ug}idmap are not available, then this program is not
> built or installed. Otherwise, it will be used to support creating,
> starting, destroying, etc containers by unprivileged users using
> their authorized subuids and subgids.
>
> Example:
> usernsexec -m u:0:100000:1 -- /bin/bash
>
> will, if the user is authorized to use subuid 100000, start a
> bash shell in a user namespace where 100000 on the host is
> mapped to root in the namespace, and the shell is running as
> (privileged) root.
>
> Signed-off-by: Serge Hallyn <serge.hallyn at ubuntu.com>
I'm assuming this is a copy of the code you had in the separate package, if so:
Acked-by: Stéphane Graber <stgraber at ubuntu.com>
> ---
> configure.ac | 3 +
> src/lxc/Makefile.am | 9 +
> src/lxc/lxc_usernsexec.c | 417 +++++++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 429 insertions(+)
> create mode 100644 src/lxc/lxc_usernsexec.c
>
> diff --git a/configure.ac b/configure.ac
> index 56638d4..1131f8b 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -52,6 +52,9 @@ esac
> AC_MSG_RESULT([$with_distro])
> AM_CONDITIONAL([HAVE_DEBIAN], [test x"$with_distro" = "xdebian" -o x"$with_distro" = "xubuntu"])
>
> +AC_CHECK_PROG([NEWUIDMAP], [newuidmap], [newuidmap])
> +AM_CONDITIONAL([HAVE_NEWUIDMAP], [test -n "$NEWUIDMAP"])
> +
> # Allow disabling rpath
> AC_ARG_ENABLE([rpath],
> [AC_HELP_STRING([--disable-rpath], [do not set rpath in executables])],
> diff --git a/src/lxc/Makefile.am b/src/lxc/Makefile.am
> index 18469a1..1421251 100644
> --- a/src/lxc/Makefile.am
> +++ b/src/lxc/Makefile.am
> @@ -101,6 +101,10 @@ if ENABLE_APPARMOR
> AM_CFLAGS += -DHAVE_APPARMOR
> endif
>
> +if HAVE_NEWUIDMAP
> +AM_CFLAGS += -DHAVE_NEWUIDMAP
> +endif
> +
> if USE_CONFIGPATH_LOGS
> AM_CFLAGS += -DUSE_CONFIGPATH_LOGS
> endif
> @@ -163,6 +167,10 @@ bin_PROGRAMS = \
> lxc-destroy \
> lxc-create
>
> +if HAVE_NEWUIDMAP
> +bin_PROGRAMS += lxc-usernsexec
> +endif
> +
> pkglibexec_PROGRAMS = \
> lxc-init
>
> @@ -196,6 +204,7 @@ lxc_unshare_SOURCES = lxc_unshare.c
> lxc_wait_SOURCES = lxc_wait.c
> lxc_kill_SOURCES = lxc_kill.c
> lxc_create_SOURCES = lxc_create.c
> +lxc_usernsexec_SOURCES = lxc_usernsexec.c
>
> install-exec-local: install-soPROGRAMS
> mkdir -p $(DESTDIR)$(datadir)/lxc
> diff --git a/src/lxc/lxc_usernsexec.c b/src/lxc/lxc_usernsexec.c
> new file mode 100644
> index 0000000..afc3bd7
> --- /dev/null
> +++ b/src/lxc/lxc_usernsexec.c
> @@ -0,0 +1,417 @@
> +/*
> + * (C) Copyright IBM Corp. 2008
> + * (C) Copyright Canonical, Inc 2010-2013
> + *
> + * Authors:
> + * Serge Hallyn <serge.hallyn at ubuntu.com>
> + * (Once upon a time, this was based on nsexec from the IBM
> + * container tools)
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2.1 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, write to the Free Software
> + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
> + */
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <unistd.h>
> +#include <sched.h>
> +#include <sys/syscall.h>
> +#include <signal.h>
> +#include <string.h>
> +#include <errno.h>
> +#include <libgen.h>
> +#include <fcntl.h>
> +#include <sys/stat.h>
> +#include <sys/types.h>
> +#include <sys/wait.h>
> +#include <sched.h>
> +#include <pwd.h>
> +#include <grp.h>
> +#include "namespace.h"
> +
> +int unshare(int flags);
> +
> +static void usage(const char *name)
> +{
> + printf("usage: %s [-h] [-c] [-mnuUip] [-P <pid-file>]"
> + "[command [arg ..]]\n", name);
> + printf("\n");
> + printf(" -h this message\n");
> + printf("\n");
> + printf(" -m <uid-maps> uid maps to use\n");
> + printf("\n");
> + printf(" uid-maps: [u|g|b]:ns_id:host_id:range\n");
> + printf(" [u|g|b]: map user id, group id, or both\n");
> + printf(" ns_id: the base id in the new namespace\n");
> + printf(" host_id: the base id in the parent namespace\n");
> + printf(" range: how many ids to map\n");
> + printf(" Note: This program uses newuidmap(2) and newgidmap(2).\n");
> + printf(" As such, /etc/subuid and /etc/subgid must grant the\n");
> + printf(" calling user permission to use the mapped ranges\n");
> + exit(1);
> +}
> +
> +static void opentty(const char * tty) {
> + int i, fd, flags;
> +
> + fd = open(tty, O_RDWR | O_NONBLOCK);
> + if (fd == -1) {
> + printf("FATAL: can't reopen tty: %s", strerror(errno));
> + sleep(1);
> + exit(1);
> + }
> +
> + flags = fcntl(fd, F_GETFL);
> + flags &= ~O_NONBLOCK;
> + fcntl(fd, F_SETFL, flags);
> +
> + for (i = 0; i < fd; i++)
> + close(i);
> + for (i = 0; i < 3; i++)
> + if (fd != i)
> + dup2(fd, i);
> + if (fd >= 3)
> + close(fd);
> +}
> +// Code copy end
> +
> +static int do_child(void *vargv)
> +{
> + char **argv = (char **)vargv;
> +
> + // Assume we want to become root
> + if (setgid(0) < 0) {
> + perror("setgid");
> + return -1;
> + }
> + if (setuid(0) < 0) {
> + perror("setuid");
> + return -1;
> + }
> + if (setgroups(0, NULL) < 0) {
> + perror("setgroups");
> + return -1;
> + }
> + if (unshare(CLONE_NEWNS) < 0) {
> + perror("unshare CLONE_NEWNS");
> + return -1;
> + }
> + execvp(argv[0], argv);
> + perror("execvpe");
> + return -1;
> +}
> +
> +struct id_map {
> + char which; // b or u or g
> + long host_id, ns_id, range;
> + struct id_map *next;
> +};
> +
> +struct id_map default_map = {
> + .which = 'b',
> + .host_id = 100000,
> + .ns_id = 0,
> + .range = 10000,
> +};
> +static struct id_map *active_map = &default_map;
> +
> +/*
> + * given a string like "b:0:100000:10", map both uids and gids
> + * 0-10 to 100000 to 100010
> + */
> +static int parse_map(char *map)
> +{
> + struct id_map *newmap;
> + int ret;
> +
> + if (!map)
> + return -1;
> + newmap = malloc(sizeof(*newmap));
> + if (!newmap)
> + return -1;
> + ret = sscanf(map, "%c:%ld:%ld:%ld", &newmap->which, &newmap->ns_id, &newmap->host_id, &newmap->range);
> + if (ret != 4)
> + goto out_free_map;
> + if (newmap->which != 'b' && newmap->which != 'u' && newmap->which != 'g')
> + goto out_free_map;
> + if (active_map != &default_map)
> + newmap->next = active_map;
> + else
> + newmap->next = NULL;
> + active_map = newmap;
> + return 0;
> +
> +out_free_map:
> + free(newmap);
> + return -1;
> +}
> +
> +/*
> + * go through /etc/subuids and /etc/subgids to find this user's
> + * allowed map. We only use the first one (bc otherwise we're
> + * not sure which ns ids he wants to use).
> + */
> +static int read_default_map(char *fnam, char which, char *username)
> +{
> + FILE *fin;
> + char *line = NULL;
> + size_t sz = 0;
> + struct id_map *newmap;
> + char *p1, *p2;
> +
> + fin = fopen(fnam, "r");
> + if (!fin)
> + return -1;
> + while (getline(&line, &sz, fin) != -1) {
> + if (sz <= strlen(username) ||
> + strncmp(line, username, strlen(username)) != 0 ||
> + line[strlen(username)] != ':')
> + continue;
> + p1 = index(line, ':');
> + if (!p1)
> + continue;
> + p2 = index(p1+1, ':');
> + if (!p2)
> + continue;
> + newmap = malloc(sizeof(*newmap));
> + if (!newmap)
> + return -1;
> + newmap->host_id = atol(p1+1);
> + newmap->range = atol(p2+1);
> + newmap->ns_id = 0;
> + newmap->which = which;
> + if (active_map != &default_map)
> + newmap->next = active_map;
> + else
> + newmap->next = NULL;
> + break;
> + }
> +
> + if (line)
> + free(line);
> + fclose(fin);
> + return 0;
> +}
> +
> +#define subuidfile "/etc/subuid"
> +#define subgidfile "/etc/subgid"
> +static int find_default_map(void)
> +{
> + struct passwd *p = getpwuid(getuid());
> + if (!p)
> + return -1;
> + if (read_default_map(subuidfile, 'u', p->pw_name) < 0)
> + return -1;
> + if (read_default_map(subgidfile, 'g', p->pw_name) < 0)
> + return -1;
> + return 0;
> +}
> +
> +static int run_cmd(char **argv)
> +{
> + int status;
> + pid_t pid = fork();
> +
> + if (pid < 0)
> + return pid;
> + if (pid == 0) {
> + execvp(argv[0], argv);
> + perror("exec failed");
> + exit(1);
> + }
> + if (waitpid(pid, &status, __WALL) < 0) {
> + perror("waitpid");
> + return -1;
> + }
> +
> + return WEXITSTATUS(status);
> +}
> +
> +static int map_child_uids(int pid, struct id_map *map)
> +{
> + char **uidargs = NULL, **gidargs = NULL;
> + int i, nuargs = 2, ngargs = 2;
> + struct id_map *m;
> +
> + uidargs = malloc(3 * sizeof(*uidargs));
> + gidargs = malloc(3 * sizeof(*gidargs));
> + if (uidargs == NULL || gidargs == NULL)
> + return -1;
> + uidargs[0] = malloc(10);
> + gidargs[0] = malloc(10);
> + uidargs[1] = malloc(21);
> + gidargs[1] = malloc(21);
> + uidargs[2] = NULL;
> + gidargs[2] = NULL;
> + if (!uidargs[0] || !uidargs[1] || !gidargs[0] || !gidargs[1])
> + return -1;
> + sprintf(uidargs[0], "newuidmap");
> + sprintf(gidargs[0], "newgidmap");
> + sprintf(uidargs[1], "%d", pid);
> + sprintf(gidargs[1], "%d", pid);
> + for (m=map; m; m = m->next) {
> + if (m->which == 'b' || m->which == 'u') {
> + nuargs += 3;
> + uidargs = realloc(uidargs, (nuargs+1) * sizeof(*uidargs));
> + if (!uidargs)
> + return -1;
> + uidargs[nuargs - 3] = malloc(21);
> + uidargs[nuargs - 2] = malloc(21);
> + uidargs[nuargs - 1] = malloc(21);
> + if (!uidargs[nuargs-3] || !uidargs[nuargs-2] || !uidargs[nuargs-1])
> + return -1;
> + sprintf(uidargs[nuargs - 3], "%ld", m->ns_id);
> + sprintf(uidargs[nuargs - 2], "%ld", m->host_id);
> + sprintf(uidargs[nuargs - 1], "%ld", m->range);
> + uidargs[nuargs] = NULL;
> + }
> + if (m->which == 'b' || m->which == 'g') {
> + ngargs += 3;
> + gidargs = realloc(gidargs, (ngargs+1) * sizeof(*gidargs));
> + if (!gidargs)
> + return -1;
> + gidargs[ngargs - 3] = malloc(21);
> + gidargs[ngargs - 2] = malloc(21);
> + gidargs[ngargs - 1] = malloc(21);
> + if (!gidargs[ngargs-3] || !gidargs[ngargs-2] || !gidargs[ngargs-1])
> + return -1;
> + sprintf(gidargs[ngargs - 3], "%ld", m->ns_id);
> + sprintf(gidargs[ngargs - 2], "%ld", m->host_id);
> + sprintf(gidargs[ngargs - 1], "%ld", m->range);
> + gidargs[ngargs] = NULL;
> + }
> + }
> +
> + // exec newuidmap
> + if (nuargs > 2 && run_cmd(uidargs) != 0) {
> + fprintf(stderr, "Error mapping uids\n");
> + return -2;
> + }
> + // exec newgidmap
> + if (ngargs > 2 && run_cmd(gidargs) != 0) {
> + fprintf(stderr, "Error mapping gids\n");
> + return -2;
> + }
> +
> + for (i=0; i<nuargs; i++)
> + free(uidargs[i]);
> + for (i=0; i<ngargs; i++)
> + free(gidargs[i]);
> + free(uidargs);
> + free(gidargs);
> +
> + return 0;
> +}
> +
> +int main(int argc, char *argv[])
> +{
> + int c;
> + unsigned long flags = CLONE_NEWUSER | CLONE_NEWNS;
> + char ttyname[256];
> + int status;
> + int ret;
> + int pid;
> + char *default_args[] = {"/bin/sh", NULL};
> + int pipe1[2], // child tells parent it has unshared
> + pipe2[2]; // parent tells child it is mapped and may proceed
> +
> + memset(ttyname, '\0', sizeof(ttyname));
> + ret = readlink("/proc/self/fd/0", ttyname, sizeof(ttyname));
> + if (ret < 0) {
> + perror("readlink on fd 0");
> + exit(1);
> + }
> +
> + while ((c = getopt(argc, argv, "m:h")) != EOF) {
> + switch (c) {
> + case 'm': if (parse_map(optarg)) usage(argv[0]); break;
> + case 'h':
> + default:
> + usage(argv[0]);
> + }
> + };
> +
> + if (active_map == &default_map) {
> + if (find_default_map()) {
> + fprintf(stderr, "You have no allocated subuids or subgids\n");
> + exit(1);
> + }
> + }
> +
> + argv = &argv[optind];
> + argc = argc - optind;
> + if (argc < 1) {
> + argv = default_args;
> + argc = 1;
> + }
> +
> + if (pipe(pipe1) < 0 || pipe(pipe2) < 0) {
> + perror("pipe");
> + exit(1);
> + }
> + if ((pid = fork()) == 0) {
> + // Child.
> +
> + close(pipe1[0]);
> + close(pipe2[1]);
> + opentty(ttyname);
> +
> + ret = unshare(flags);
> + if (ret < 0) {
> + perror("unshare");
> + return 1;
> + }
> + ret = 1;
> + if (write(pipe1[1], &ret, 1) < 1) {
> + perror("write pipe");
> + exit(1);
> + }
> + if (read(pipe2[0], &ret, 1) < 1) {
> + perror("read pipe");
> + exit(1);
> + }
> + if (ret != 1) {
> + fprintf(stderr, "parent had an error, child exiting\n");
> + exit(1);
> + }
> +
> + close(pipe1[1]);
> + close(pipe2[0]);
> + return do_child((void*)argv);
> + }
> +
> + close(pipe1[1]);
> + close(pipe2[0]);
> + if (read(pipe1[0], &ret, 1) < 1) {
> + perror("read pipe");
> + exit(1);
> + }
> +
> + ret = 1;
> + if (map_child_uids(pid, active_map)) {
> + fprintf(stderr, "error mapping child\n");
> + ret = 0;
> + }
> + if (write(pipe2[1], &ret, 1) < 0) {
> + perror("write to pipe");
> + exit(1);
> + }
> +
> + if ((ret = waitpid(pid, &status, __WALL)) < 0) {
> + printf("waitpid() returns %d, errno %d\n", ret, errno);
> + exit(ret);
> + }
> +
> + exit(WEXITSTATUS(status));
> +}
> --
> 1.8.3.2
>
>
> ------------------------------------------------------------------------------
> See everything from the browser to the database with AppDynamics
> Get end-to-end visibility with application monitoring from AppDynamics
> Isolate bottlenecks and diagnose root cause in seconds.
> Start your free trial of AppDynamics Pro today!
> http://pubads.g.doubleclick.net/gampad/clk?id=48808831&iu=/4140/ostg.clktrk
> _______________________________________________
> Lxc-devel mailing list
> Lxc-devel at lists.sourceforge.net
> https://lists.sourceforge.net/lists/listinfo/lxc-devel
--
Stéphane Graber
Ubuntu developer
http://www.ubuntu.com
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 836 bytes
Desc: Digital signature
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20130723/f4ca7d26/attachment.pgp>
More information about the lxc-devel
mailing list