[lxc-devel] [PATCH 3/8] container creation: support unpriv container creation in user namespaces
Serge Hallyn
serge at mail.hallyn.com
Fri Jul 19 14:26:50 UTC 2013
From: Serge Hallyn <serge.hallyn at 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.hallyn at 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);
+}
+
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 ",
+ 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};
+
+ // "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";
+ 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
More information about the lxc-devel
mailing list