On Fri, Jul 19, 2013 at 02:26:50PM +0000, Serge Hallyn wrote:
> From: Serge Hallyn <serge.hal...@ubuntu.com>
> 
> 1. lxcapi_create: don't try to unshare and mount for dir backed containers
> 
> It's unnecessary, and breaks unprivileged lxc-create (since unpriv users
> cannot yet unshare(CLONE_NEWNS)).
> 
> 2. api_create: chown rootfs
> 
> chown rootfs to the host uid to which container root will be mapped
> 
> 3. create: run template in a mapped user ns
> 
> 4. use (setuid-root) newxidmap to set id_map if we are not root
> 
> This is needed to be able to set userns mappings as an unprivileged
> user, for unprivileged lxc-start.
> 
> Signed-off-by: Serge Hallyn <serge.hal...@ubuntu.com>
> ---
>  src/lxc/conf.c         | 107 +++++++++++++++++++++++++++-----
>  src/lxc/conf.h         |   4 ++
>  src/lxc/lxccontainer.c | 162 
> +++++++++++++++++++++++++++++++++++++++++++++----
>  3 files changed, 244 insertions(+), 29 deletions(-)
> 
> diff --git a/src/lxc/conf.c b/src/lxc/conf.c
> index 46320dd..2e202ec 100644
> --- a/src/lxc/conf.c
> +++ b/src/lxc/conf.c
> @@ -2547,6 +2547,11 @@ static int write_id_mapping(enum idtype idtype, pid_t 
> pid, const char *buf,
>       return ret < 0 ? ret : closeret;
>  }
>  
> +int do_call_newxid(char *str)
> +{
> +     return system(str);
> +}
> +

Hmm, what? :)

Is that meant to evolve into something more than an alias of system()?

>  int lxc_map_ids(struct lxc_list *idmap, pid_t pid)
>  {
>       struct lxc_list *iterator;
> @@ -2554,31 +2559,49 @@ int lxc_map_ids(struct lxc_list *idmap, pid_t pid)
>       int ret = 0;
>       enum idtype type;
>       char *buf = NULL, *pos;
> +     int am_root = (getuid() == 0);
>  
>       for(type = ID_TYPE_UID; type <= ID_TYPE_GID; type++) {
>               int left, fill;
> -
> -             pos = buf;
> -             lxc_list_for_each(iterator, idmap) {
> -                     /* The kernel only takes <= 4k for writes to 
> /proc/<nr>/[ug]id_map */
> -                     if (!buf)
> -                             buf = pos = malloc(4096);
> +             int had_entry = 0;
> +             if (!buf) {
> +                     buf = pos = malloc(4096);
>                       if (!buf)
>                               return -ENOMEM;
> +             }
> +             pos = buf;
> +             if (!am_root)
> +                     pos += sprintf(buf, "/usr/bin/new%cidmap %d ",

May be worth having autoconf figure out the paths for those as they very
well may be moved to /bin.

> +                             type == ID_TYPE_UID ? 'u' : 'g',
> +                             pid);
>  
> +             lxc_list_for_each(iterator, idmap) {
> +                     /* The kernel only takes <= 4k for writes to 
> /proc/<nr>/[ug]id_map */
>                       map = iterator->elem;
> -                     if (map->idtype == type) {
> -                             left = 4096 - (pos - buf);
> -                             fill = snprintf(pos, left, "%lu %lu %lu\n",
> -                                     map->nsid, map->hostid, map->range);
> -                             if (fill <= 0 || fill >= left)
> -                                     SYSERROR("snprintf failed, too many 
> mappings");
> -                             pos += fill;
> -                     }
> +                     if (map->idtype != type)
> +                             continue;
> +
> +                     had_entry = 1;
> +                     left = 4096 - (pos - buf);
> +                     fill = snprintf(pos, left, " %lu %lu %lu", map->nsid,
> +                                     map->hostid, map->range);
> +                     if (fill <= 0 || fill >= left)
> +                             SYSERROR("snprintf failed, too many mappings");
> +                     pos += fill;
>               }
> -             if (pos == buf) // no mappings were found
> +             if (!had_entry)
>                       continue;
> -             ret = write_id_mapping(type, pid, buf, pos-buf);
> +             left = 4096 - (pos - buf);
> +             fill = snprintf(pos, left, "\n");
> +             if (fill <= 0 || fill >= left)
> +                     SYSERROR("snprintf failed, too many mappings");
> +             pos += fill;
> +
> +             if (am_root)
> +                     ret = write_id_mapping(type, pid, buf, pos-buf);
> +             else
> +                     ret = do_call_newxid(buf);
> +
>               if (ret)
>                       break;
>       }
> @@ -2588,6 +2611,58 @@ int lxc_map_ids(struct lxc_list *idmap, pid_t pid)
>       return ret;
>  }
>  
> +/*
> + * return the host uid to which the container root is mapped, or -1 on
> + * error
> + */
> +int get_mapped_rootid(struct lxc_conf *conf)
> +{
> +     struct lxc_list *it;
> +     struct id_map *map;
> +
> +     lxc_list_for_each(it, &conf->id_map) {
> +             map = it->elem;
> +             if (map->idtype != ID_TYPE_UID)
> +                     continue;
> +             if (map->nsid != 0)
> +                     continue;
> +             return map->hostid;
> +     }
> +     return -1;
> +}
> +
> +bool hostid_is_mapped(int id, struct lxc_conf *conf)
> +{
> +     struct lxc_list *it;
> +     struct id_map *map;
> +     lxc_list_for_each(it, &conf->id_map) {
> +             map = it->elem;
> +             if (map->idtype != ID_TYPE_UID)
> +                     continue;
> +             if (id >= map->hostid && id < map->hostid + map->range)
> +                     return true;
> +     }
> +     return false;
> +}
> +
> +int find_unmapped_nsuid(struct lxc_conf *conf)
> +{
> +     struct lxc_list *it;
> +     struct id_map *map;
> +     uid_t freeid = 0;
> +again:
> +     lxc_list_for_each(it, &conf->id_map) {
> +             map = it->elem;
> +             if (map->idtype != ID_TYPE_UID)
> +                     continue;
> +             if (freeid >= map->nsid && freeid < map->nsid + map->range) {
> +                     freeid = map->nsid + map->range;
> +                     goto again;
> +             }
> +     }
> +     return freeid;
> +}
> +
>  int lxc_find_gateway_addresses(struct lxc_handler *handler)
>  {
>       struct lxc_list *network = &handler->conf->network;
> diff --git a/src/lxc/conf.h b/src/lxc/conf.h
> index ed3240d..065b1df 100644
> --- a/src/lxc/conf.h
> +++ b/src/lxc/conf.h
> @@ -331,4 +331,8 @@ extern int lxc_setup(const char *name, struct lxc_conf 
> *lxc_conf,
>                       const char *lxcpath);
>  
>  extern void lxc_rename_phys_nics_on_shutdown(struct lxc_conf *conf);
> +
> +extern int get_mapped_rootid(struct lxc_conf *conf);
> +extern int find_unmapped_nsuid(struct lxc_conf *conf);
> +extern bool hostid_is_mapped(int id, struct lxc_conf *conf);
>  #endif
> diff --git a/src/lxc/lxccontainer.c b/src/lxc/lxccontainer.c
> index b0695bc..f9ce7d3 100644
> --- a/src/lxc/lxccontainer.c
> +++ b/src/lxc/lxccontainer.c
> @@ -678,6 +678,49 @@ static const char *lxcapi_get_config_path(struct 
> lxc_container *c);
>  static bool lxcapi_set_config_item(struct lxc_container *c, const char *key, 
> const char *v);
>  
>  /*
> + * chown_mapped: for an unprivileged user with uid X to chown a dir
> + * to subuid Y, he needs to run chown as root in a userns where
> + * nsid 0 is mapped to hostuid Y, and nsid Y is mapped to hostuid
> + * X.  That way, the container root is privileged with respect to
> + * hostuid X, allowing him to do the chown.
> + */
> +static int chown_mapped(int nsrootid, char *path)
> +{
> +     if (nsrootid < 0)
> +             return nsrootid;
> +     pid_t pid = fork();
> +     if (pid < 0) {
> +             SYSERROR("Failed forking");
> +             return -1;
> +     }
> +     if (!pid) {
> +             int hostuid = geteuid(), ret;
> +             char map1[100], map2[100];
> +             char *args[] = {"usernsexec", "-m", map1, "-m", map2, "--", 
> "/bin/chown",
> +                              "0", path, NULL};

Same thing as above, we should avoid hardcoding paths to binaries that
we aren't shipping ourselves, though admitedly this one should be
safe...

> +
> +             // "b:0:nsrootid:1"
> +             ret = snprintf(map1, 100, "b:0:%d:1", nsrootid);
> +             if (ret < 0 || ret >= 100) {
> +                     ERROR("Error uid printing map string");
> +                     return -1;
> +             }
> +
> +             // "b:hostuid:hostuid:1"
> +             ret = snprintf(map2, 100, "b:%d:%d:1", hostuid, hostuid);
> +             if (ret < 0 || ret >= 100) {
> +                     ERROR("Error uid printing map string");
> +                     return -1;
> +             }
> +
> +             ret = execvp("usernsexec", args);
> +             SYSERROR("Failed executing usernsexec");
> +             exit(1);
> +     }
> +     return wait_for_pid(pid);
> +}
> +
> +/*
>   * do_bdev_create: thin wrapper around bdev_create().  Like bdev_create(),
>   * it returns a mounted bdev on success, NULL on error.
>   */
> @@ -701,6 +744,25 @@ static struct bdev *do_bdev_create(struct lxc_container 
> *c, const char *type,
>       if (!bdev)
>               return NULL;
>       lxcapi_set_config_item(c, "lxc.rootfs", bdev->src);
> +
> +     /* if we are not root, chown the rootfs dir to root in the
> +      * target uidmap */
> +
> +     if (geteuid() != 0) {
> +             int rootid;
> +             if ((rootid = get_mapped_rootid(c->lxc_conf)) <= 0) {
> +                     ERROR("No mapping for container root");
> +                     bdev_put(bdev);
> +                     return NULL;
> +             }
> +             ret = chown_mapped(rootid, bdev->dest);
> +             if (ret < 0) {
> +                     ERROR("Error chowning %s to %d\n", bdev->dest, rootid);
> +                     bdev_put(bdev);
> +                     return NULL;
> +             }
> +     }
> +
>       return bdev;
>  }
>  
> @@ -772,6 +834,7 @@ static bool create_run_template(struct lxc_container *c, 
> char *tpath, bool quiet
>               int i;
>               int ret, len, nargs = 0;
>               char **newargv;
> +             struct lxc_conf *conf = c->lxc_conf;
>  
>               if (quiet) {
>                       close(0);
> @@ -781,10 +844,6 @@ static bool create_run_template(struct lxc_container *c, 
> char *tpath, bool quiet
>                       open("/dev/null", O_RDWR);
>                       open("/dev/null", O_RDWR);
>               }
> -             if (unshare(CLONE_NEWNS) < 0) {
> -                     ERROR("error unsharing mounts");
> -                     exit(1);
> -             }
>  
>               src = c->lxc_conf->rootfs.path;
>               /*
> @@ -801,9 +860,19 @@ static bool create_run_template(struct lxc_container *c, 
> char *tpath, bool quiet
>                       exit(1);
>               }
>  
> -             if (bdev->ops->mount(bdev) < 0) {
> -                     ERROR("Error mounting rootfs");
> -                     exit(1);
> +             if (strcmp(bdev->type, "dir") != 0) {
> +                     if (unshare(CLONE_NEWNS) < 0) {
> +                             ERROR("error unsharing mounts");
> +                             exit(1);
> +                     }
> +                     if (bdev->ops->mount(bdev) < 0) {
> +                             ERROR("Error mounting rootfs");
> +                             exit(1);
> +                     }
> +             } else { // TODO come up with a better way here!
> +                     if (bdev->dest)
> +                             free(bdev->dest);
> +                     bdev->dest = strdup(bdev->src);
>               }
>  
>               /*
> @@ -813,6 +882,7 @@ static bool create_run_template(struct lxc_container *c, 
> char *tpath, bool quiet
>               if (argv)
>                       for (nargs = 0; argv[nargs]; nargs++) ;
>               nargs += 4;  // template, path, rootfs and name args
> +
>               newargv = malloc(nargs * sizeof(*newargv));
>               if (!newargv)
>                       exit(1);
> @@ -856,6 +926,66 @@ static bool create_run_template(struct lxc_container *c, 
> char *tpath, bool quiet
>                       exit(1);
>               newargv[nargs - 1] = NULL;
>  
> +             /*
> +              * If we're running the template in a mapped userns, then
> +              * we prepend the template command with:
> +              * usernsexec <-m map1> ... <-m mapn> --
> +              */
> +             if (geteuid() != 0 && !lxc_list_empty(&conf->id_map)) {
> +                     int n2args = 1;
> +                     char **n2 = malloc(n2args * sizeof(*n2));
> +                     struct lxc_list *it;
> +                     struct id_map *map;
> +
> +                     newargv[0] = tpath;
> +                     tpath = "/usr/bin/usernsexec";

Same thing.

> +                     n2[0] = "usernsexec";
> +                     lxc_list_for_each(it, &conf->id_map) {
> +                             map = it->elem;
> +                             n2args += 2;
> +                             n2 = realloc(n2, n2args * sizeof(*n2));
> +                             if (!n2)
> +                                     exit(1);
> +                             n2[n2args-2] = "-m";
> +                             n2[n2args-1] = malloc(200);
> +                             if (!n2[n2args-1])
> +                                     exit(1);
> +                             ret = snprintf(n2[n2args-1], 200, 
> "%c:%lu:%lu:%lu",
> +                                     map->idtype == ID_TYPE_UID ? 'u' : 'g',
> +                                     map->nsid, map->hostid, map->range);
> +                             if (ret < 0 || ret >= 200)
> +                                     exit(1);
> +                     }
> +                     bool hostid_mapped = hostid_is_mapped(geteuid(), conf);
> +                     int extraargs = hostid_mapped ?  1 : 3;
> +                     n2 = realloc(n2, (nargs + n2args + extraargs) * 
> sizeof(*n2));
> +                     if (!n2)
> +                             exit(1);
> +                     if (!hostid_mapped) {
> +                             int free_id = find_unmapped_nsuid(conf);
> +                             n2[n2args++] = "-m";
> +                             if (free_id < 0) {
> +                                     ERROR("Could not find free uid to map");
> +                                     exit(1);
> +                             }
> +                             n2[n2args++] = malloc(200);
> +                             if (!n2[n2args-1]) {
> +                                     SYSERROR("out of memory");
> +                                     exit(1);
> +                             }
> +                             ret = snprintf(n2[n2args-1], 200, "u:%d:%d:1",
> +                                     free_id, geteuid());
> +                             if (ret < 0 || ret >= 200) {
> +                                     ERROR("string too long");
> +                                     exit(1);
> +                             }
> +                     }
> +                     n2[n2args++] = "--";
> +                     for (i = 0; i < nargs; i++)
> +                             n2[i + n2args] = newargv[i];
> +                     free(newargv);
> +                     newargv = n2;
> +             }
>               /* execute */
>               execv(tpath, newargv);
>               SYSERROR("failed to execute template %s", tpath);
> @@ -1949,15 +2079,21 @@ static int clone_update_rootfs(struct lxc_container 
> *c0,
>       if (pid > 0)
>               return wait_for_pid(pid);
>  
> -     if (unshare(CLONE_NEWNS) < 0) {
> -             ERROR("error unsharing mounts");
> -             exit(1);
> -     }
>       bdev = bdev_init(c->lxc_conf->rootfs.path, c->lxc_conf->rootfs.mount, 
> NULL);
>       if (!bdev)
>               exit(1);
> -     if (bdev->ops->mount(bdev) < 0)
> -             exit(1);
> +     if (strcmp(bdev->type, "dir") != 0) {
> +             if (unshare(CLONE_NEWNS) < 0) {
> +                     ERROR("error unsharing mounts");
> +                     exit(1);
> +             }
> +             if (bdev->ops->mount(bdev) < 0)
> +                     exit(1);
> +     } else { // TODO come up with a better way
> +             if (bdev->dest)
> +                     free(bdev->dest);
> +             bdev->dest = strdup(bdev->src);
> +     }
>  
>       if (!lxc_list_empty(&conf->hooks[LXCHOOK_CLONE])) {
>               /* Start of environment variable setup for hooks */
> -- 
> 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@lists.sourceforge.net
> https://lists.sourceforge.net/lists/listinfo/lxc-devel

-- 
Stéphane Graber
Ubuntu developer
http://www.ubuntu.com

Attachment: signature.asc
Description: Digital signature

------------------------------------------------------------------------------
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@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/lxc-devel

Reply via email to