[lxc-devel] [PATCH] Fix unprivileged containers started by root (v2)

Stéphane Graber stgraber at ubuntu.com
Thu Feb 27 20:05:31 UTC 2014


This change makes it possible to create unprivileged containers as root.
They will be stored in the usual system wide location, use the usual
system wide cache but will be running using a uid/gid map.

This also updates lxc_usernsexec to use the same function as the rest of
LXC, centralizing all the userns switch in a single function.

That function now detects the presence of newuidmap and newgidmap on the
system, if they are present, they will be used for containers created as
either user or root. If they're not and the user isn't root, an error is
shown. If they're not and the user is root, LXC will directly set the
uid_map and gid_map values.

All that should allow for a consistent experience as well as supporting
distributions that don't yet ship newuidmap/newgidmap.

To make things simpler in the future, an helper function "on_path" is
also introduced and used to detect the presence of newuidmap and
newgidmap.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 src/lxc/conf.c           |  15 ++--
 src/lxc/lxc_usernsexec.c | 214 ++++++++++++++---------------------------------
 src/lxc/lxccontainer.c   |   6 +-
 src/lxc/utils.c          |  34 ++++++++
 src/lxc/utils.h          |   1 +
 5 files changed, 113 insertions(+), 157 deletions(-)

diff --git a/src/lxc/conf.c b/src/lxc/conf.c
index b6bc414..d99659a 100644
--- a/src/lxc/conf.c
+++ b/src/lxc/conf.c
@@ -3164,7 +3164,12 @@ 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);
+	int use_shadow = (on_path("newuidmap") && on_path("newuidmap"));
+
+	if (!use_shadow && geteuid()) {
+		ERROR("Missing newuidmap/newgidmap");
+		return -1;
+	}
 
 	for(type = ID_TYPE_UID; type <= ID_TYPE_GID; type++) {
 		int left, fill;
@@ -3175,7 +3180,7 @@ int lxc_map_ids(struct lxc_list *idmap, pid_t pid)
 				return -ENOMEM;
 		}
 		pos = buf;
-		if (!am_root)
+		if (use_shadow)
 			pos += sprintf(buf, "new%cidmap %d",
 				type == ID_TYPE_UID ? 'u' : 'g',
 				pid);
@@ -3189,9 +3194,9 @@ int lxc_map_ids(struct lxc_list *idmap, pid_t pid)
 			had_entry = 1;
 			left = 4096 - (pos - buf);
 			fill = snprintf(pos, left, "%s%lu %lu %lu%s",
-					am_root ? "" : " ",
+					use_shadow ? " " : "",
 					map->nsid, map->hostid, map->range,
-					am_root ? "\n" : "");
+					use_shadow ? "" : "\n");
 			if (fill <= 0 || fill >= left)
 				SYSERROR("snprintf failed, too many mappings");
 			pos += fill;
@@ -3199,7 +3204,7 @@ int lxc_map_ids(struct lxc_list *idmap, pid_t pid)
 		if (!had_entry)
 			continue;
 
-		if (am_root) {
+		if (!use_shadow) {
 			ret = write_id_mapping(type, pid, buf, pos-buf);
 		} else {
 			left = 4096 - (pos - buf);
diff --git a/src/lxc/lxc_usernsexec.c b/src/lxc/lxc_usernsexec.c
index 2efc687..f5b7fe0 100644
--- a/src/lxc/lxc_usernsexec.c
+++ b/src/lxc/lxc_usernsexec.c
@@ -41,6 +41,7 @@
 #include <pwd.h>
 #include <grp.h>
 
+#include "conf.h"
 #include "namespace.h"
 #include "utils.h"
 
@@ -131,19 +132,7 @@ static int do_child(void *vargv)
 	return -1;
 }
 
-struct id_map {
-	char which; // b or u or g
-	long host_id, ns_id, range;
-	struct id_map *next;
-};
-
-static struct id_map default_map = {
-	.which = 'b',
-	.host_id = 100000,
-	.ns_id = 0,
-	.range = 10000,
-};
-static struct id_map *active_map = &default_map;
+static struct lxc_list active_map;
 
 /*
  * given a string like "b:0:100000:10", map both uids and gids
@@ -152,28 +141,51 @@ static struct id_map *active_map = &default_map;
 static int parse_map(char *map)
 {
 	struct id_map *newmap;
-    int ret;
+	struct lxc_list *tmp = NULL;
+	int ret;
+	int i;
+	char types[2] = {'u', 'g'};
+	char which;
+	long host_id, ns_id, range;
 
 	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);
+
+	ret = sscanf(map, "%c:%ld:%ld:%ld", &which, &ns_id, &host_id, &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;
+		return -1;
 
-out_free_map:
-	free(newmap);
-	return -1;
+	if (which != 'b' && which != 'u' && which != 'g')
+		return -1;
+
+	for (i = 0; i < 2; i++) {
+		if (which != types[i] && which != 'b')
+			continue;
+
+		newmap = malloc(sizeof(*newmap));
+		if (!newmap)
+			return -1;
+
+		newmap->hostid = host_id;
+		newmap->nsid = ns_id;
+		newmap->range = range;
+
+		if (types[i] == 'u')
+			newmap->idtype = ID_TYPE_UID;
+		else
+			newmap->idtype = ID_TYPE_GID;
+
+		tmp = malloc(sizeof(*tmp));
+		if (!tmp) {
+			free(newmap);
+			return -1;
+		}
+
+		tmp->elem = newmap;
+		lxc_list_add_tail(&active_map, tmp);
+	}
+
+	return 0;
 }
 
 /*
@@ -185,12 +197,13 @@ out_free_map:
  * gid, because otherwise we're not sure which entries the user
  * wanted.
  */
-static int read_default_map(char *fnam, char which, char *username)
+static int read_default_map(char *fnam, int which, char *username)
 {
 	FILE *fin;
 	char *line = NULL;
 	size_t sz = 0;
 	struct id_map *newmap;
+	struct lxc_list *tmp = NULL;
 	char *p1, *p2;
 
 	fin = fopen(fnam, "r");
@@ -213,15 +226,21 @@ static int read_default_map(char *fnam, char which, char *username)
 			free(line);
 			return -1;
 		}
-		newmap->host_id = atol(p1+1);
+		newmap->hostid = 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;
-		active_map = newmap;
+		newmap->nsid = 0;
+		newmap->idtype = which;
+
+		tmp = malloc(sizeof(*tmp));
+		if (!tmp) {
+			fclose(fin);
+			free(line);
+			free(newmap);
+			return -1;
+		}
+
+		tmp->elem = newmap;
+		lxc_list_add_tail(&active_map, tmp);
 		break;
 	}
 
@@ -238,119 +257,13 @@ 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)
+	if (read_default_map(subuidfile, ID_TYPE_UID, p->pw_name) < 0)
 		return -1;
-	if (read_default_map(subgidfile, 'g', p->pw_name) < 0)
+	if (read_default_map(subgidfile, ID_TYPE_GID, 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;
-	char **newuidargs = NULL, **newgidargs = NULL;
-	int i, nuargs = 2, ngargs = 2, ret = -1;
-	struct id_map *m;
-
-	uidargs = malloc(3 * sizeof(*uidargs));
-	if (uidargs == NULL)
-		return -1;
-	gidargs = malloc(3 * sizeof(*gidargs));
-	if (gidargs == NULL) {
-		free(uidargs);
-		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])
-		goto out;
-	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;
-			newuidargs = realloc(uidargs, (nuargs+1) * sizeof(*uidargs));
-			if (!newuidargs)
-				goto out;
-			uidargs = newuidargs;
-			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])
-				goto out;
-			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;
-			newgidargs = realloc(gidargs, (ngargs+1) * sizeof(*gidargs));
-			if (!newgidargs)
-				goto out;
-			gidargs = newgidargs;
-			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])
-				goto out;
-			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;
-		}
-	}
-
-	ret = -2;
-	// exec newuidmap
-	if (nuargs > 2 && run_cmd(uidargs) != 0) {
-		fprintf(stderr, "Error mapping uids\n");
-		goto out;
-	}
-	// exec newgidmap
-	if (ngargs > 2 && run_cmd(gidargs) != 0) {
-		fprintf(stderr, "Error mapping gids\n");
-		goto out;
-	}
-	ret = 0;
-
-out:
-	for (i=0; i<nuargs; i++)
-		free(uidargs[i]);
-	for (i=0; i<ngargs; i++)
-		free(gidargs[i]);
-	free(uidargs);
-	free(gidargs);
-
-	return ret;
-}
-
 int main(int argc, char *argv[])
 {
 	int c;
@@ -371,6 +284,8 @@ int main(int argc, char *argv[])
 		exit(1);
 	}
 
+	lxc_list_init(&active_map);
+
 	while ((c = getopt(argc, argv, "m:h")) != EOF) {
 		switch (c) {
 			case 'm': if (parse_map(optarg)) usage(argv[0]); break;
@@ -380,7 +295,7 @@ int main(int argc, char *argv[])
 		}
 	};
 
-	if (active_map == &default_map) {
+	if (lxc_list_empty(&active_map)) {
 		if (find_default_map()) {
 			fprintf(stderr, "You have no allocated subuids or subgids\n");
 			exit(1);
@@ -437,7 +352,8 @@ int main(int argc, char *argv[])
 	}
 
 	buf[0] = '1';
-	if (map_child_uids(pid, active_map)) {
+
+	if (lxc_map_ids(&active_map, pid)) {
 		fprintf(stderr, "error mapping child\n");
 		ret = 0;
 	}
diff --git a/src/lxc/lxccontainer.c b/src/lxc/lxccontainer.c
index 0427791..d9aa973 100644
--- a/src/lxc/lxccontainer.c
+++ b/src/lxc/lxccontainer.c
@@ -806,7 +806,7 @@ static struct bdev *do_bdev_create(struct lxc_container *c, const char *type,
 	/* if we are not root, chown the rootfs dir to root in the
 	 * target uidmap */
 
-	if (geteuid() != 0) {
+	if (geteuid() != 0 || (c->lxc_conf && !lxc_list_empty(&c->lxc_conf->id_map))) {
 		if (chown_mapped_root(bdev->dest, c->lxc_conf) < 0) {
 			ERROR("Error chowning %s to container root", bdev->dest);
 			bdev_put(bdev);
@@ -992,7 +992,7 @@ static bool create_run_template(struct lxc_container *c, char *tpath, bool quiet
 		 * and we append "--mapped-uid x", where x is the mapped uid
 		 * for our geteuid()
 		 */
-		if (geteuid() != 0 && !lxc_list_empty(&conf->id_map)) {
+		if (!lxc_list_empty(&conf->id_map)) {
 			int n2args = 1;
 			char txtuid[20];
 			char txtgid[20];
@@ -1450,7 +1450,7 @@ static inline bool enter_to_ns(struct lxc_container *c) {
 	init_pid = c->init_pid(c);
 
 	/* Switch to new userns */
-	if (geteuid() && access("/proc/self/ns/user", F_OK) == 0) {
+	if ((geteuid() != 0 || (c->lxc_conf && !lxc_list_empty(&c->lxc_conf->id_map))) && access("/proc/self/ns/user", F_OK) == 0) {
 		ret = snprintf(new_userns_path, MAXPATHLEN, "/proc/%d/ns/user", init_pid);
 		if (ret < 0 || ret >= MAXPATHLEN)
 			goto out;
diff --git a/src/lxc/utils.c b/src/lxc/utils.c
index da7c3b4..90bdddb 100644
--- a/src/lxc/utils.c
+++ b/src/lxc/utils.c
@@ -1234,3 +1234,37 @@ int detect_shared_rootfs(void)
 	fclose(f);
 	return 0;
 }
+
+bool on_path(char *cmd) {
+	char *path = NULL;
+	char *entry = NULL;
+	char cmdpath[MAXPATHLEN];
+	int ret;
+
+	path = getenv("PATH");
+	if (!path)
+		return false;
+
+	path = strdup(path);
+	if (!path)
+		return false;
+
+	entry = strtok(path, ":");
+	while (entry) {
+		ret = snprintf(cmdpath, MAXPATHLEN, "%s/%s", entry, cmd);
+
+		if (ret < 0 || ret >= MAXPATHLEN)
+			goto next_loop;
+
+		if (access(cmdpath, X_OK) == 0) {
+			free(path);
+			return true;
+		}
+
+next_loop:
+		entry = strtok(NULL, ":");
+	}
+
+	free(path);
+	return false;
+}
diff --git a/src/lxc/utils.h b/src/lxc/utils.h
index dcf0e34..978f586 100644
--- a/src/lxc/utils.h
+++ b/src/lxc/utils.h
@@ -278,3 +278,4 @@ uint64_t fnv_64a_buf(void *buf, size_t len, uint64_t hval);
 #endif
 
 int detect_shared_rootfs(void);
+bool on_path(char *cmd);
-- 
1.9.0



More information about the lxc-devel mailing list