[lxc-devel] [lxc/master] conf: use minimal {g,u}id map

brauner on Github lxc-bot at linuxcontainers.org
Tue May 30 07:16:08 UTC 2017


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 847 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20170530/dab366a1/attachment.bin>
-------------- next part --------------
From 10cc20e203d3876d909ed1ce19a03688dc46a590 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Mon, 29 May 2017 11:21:29 +0200
Subject: [PATCH] conf: use minimal {g,u}id map

Afaict, userns_exec_1() is only used to operate based on privileges for the
user's own {g,u}id on the host and for the container root's unmapped {g,u}id.
This means we require only to establish a mapping from:
- the container root {g,u}id as seen from the host -> user's host {g,u}id
- the container root -> some sub{g,u}id

The former we add, if the user did not specifiy a mapping. The latter we
retrieve from the ontainer's configured {g,u}id mappings.

Closes #1598.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 src/lxc/conf.c | 274 ++++++++++++++++++++++++++++++++++++++++++---------------
 1 file changed, 201 insertions(+), 73 deletions(-)

diff --git a/src/lxc/conf.c b/src/lxc/conf.c
index 85805f975..3efe4dbd5 100644
--- a/src/lxc/conf.c
+++ b/src/lxc/conf.c
@@ -4625,108 +4625,158 @@ static int run_userns_fn(void *data)
 {
 	struct userns_fn_data *d = data;
 	char c;
-	// we're not sharing with the parent any more, if it was a thread
 
+	/* Close write end of the pipe. */
 	close(d->p[1]);
+
+	/* Wait for parent to finish establishing a new mapping in the user
+	 * namespace we are executing in.
+	 */
 	if (read(d->p[0], &c, 1) != 1)
 		return -1;
+
+	/* Close read end of the pipe. */
 	close(d->p[0]);
+
+	/* Call function to run. */
 	return d->fn(d->arg);
 }
 
+static struct id_map *mapped_hostid_entry(unsigned id, struct lxc_conf *conf,
+					  enum idtype idtype)
+{
+	struct lxc_list *it;
+	struct id_map *map;
+	struct id_map *retmap = NULL;
+
+	lxc_list_for_each(it, &conf->id_map) {
+		map = it->elem;
+		if (map->idtype != idtype)
+			continue;
+
+		if (id >= map->hostid && id < map->hostid + map->range) {
+			retmap = map;
+			break;
+		}
+	}
+
+	if (!retmap)
+		return NULL;
+
+	retmap = malloc(sizeof(*retmap));
+	if (!retmap)
+		return NULL;
+
+	memset(retmap, 0, sizeof(*retmap));
+	memcpy(retmap, map, sizeof(*retmap));
+	return retmap;
+}
+
 /*
- * Add ID_TYPE_UID/ID_TYPE_GID entries to an existing lxc_conf,
- * if they are not already there.
+ * Allocate a new {g,u}id mapping for the given {g,u}id. Re-use an already
+ * existing one or establish a new one.
  */
-static struct lxc_list *idmap_add_id(struct lxc_conf *conf,
-		uid_t uid, gid_t gid)
+static struct lxc_list *idmap_add_id(struct lxc_conf *conf, uid_t uid,
+				     gid_t gid)
 {
-	int hostuid_mapped = mapped_hostid(uid, conf, ID_TYPE_UID);
-	int hostgid_mapped = mapped_hostid(gid, conf, ID_TYPE_GID);
-	struct lxc_list *new = NULL, *tmp, *it, *next;
-	struct id_map *entry;
+	int hostuid_mapped, hostgid_mapped;
+	struct id_map *hostuid_idmap, *hostgid_idmap;
+	struct id_map *entry = NULL;
+	struct lxc_list *new = NULL;
+	struct lxc_list *tmp = NULL;
+
+	hostuid_idmap = mapped_hostid_entry(uid, conf, ID_TYPE_UID);
+	hostgid_idmap = mapped_hostid_entry(gid, conf, ID_TYPE_GID);
 
+	/* Allocate new {g,u}id map list. */
 	new = malloc(sizeof(*new));
-	if (!new) {
-		ERROR("Out of memory building id map");
-		return NULL;
-	}
+	if (!new)
+		goto on_error;
 	lxc_list_init(new);
 
-	if (hostuid_mapped < 0) {
+	tmp = malloc(sizeof(*tmp));
+	if (!tmp)
+		goto on_error;
+	entry = hostuid_idmap;
+	if (!hostuid_idmap) {
 		hostuid_mapped = find_unmapped_nsuid(conf, ID_TYPE_UID);
 		if (hostuid_mapped < 0)
-			goto err;
-		tmp = malloc(sizeof(*tmp));
-		if (!tmp)
-			goto err;
+			goto on_error;
+
 		entry = malloc(sizeof(*entry));
-		if (!entry) {
-			free(tmp);
-			goto err;
-		}
+		if (!entry)
+			goto on_error;
+
 		tmp->elem = entry;
 		entry->idtype = ID_TYPE_UID;
 		entry->nsid = hostuid_mapped;
-		entry->hostid = (unsigned long) uid;
+		entry->hostid = (unsigned long)uid;
 		entry->range = 1;
-		lxc_list_add_tail(new, tmp);
+		DEBUG("adding uid mapping: nsid %lu hostid %lu range %lu",
+		      entry->nsid, entry->hostid, entry->range);
 	}
-	if (hostgid_mapped < 0) {
+	lxc_list_add_tail(new, tmp);
+	entry = NULL;
+
+	tmp = malloc(sizeof(*tmp));
+	if (!tmp)
+		goto on_error;
+	entry = hostgid_idmap;
+	if (!hostgid_idmap) {
 		hostgid_mapped = find_unmapped_nsuid(conf, ID_TYPE_GID);
 		if (hostgid_mapped < 0)
-			goto err;
-		tmp = malloc(sizeof(*tmp));
-		if (!tmp)
-			goto err;
+			goto on_error;
+
 		entry = malloc(sizeof(*entry));
-		if (!entry) {
-			free(tmp);
-			goto err;
-		}
+		if (!entry)
+			goto on_error;
+
 		tmp->elem = entry;
 		entry->idtype = ID_TYPE_GID;
 		entry->nsid = hostgid_mapped;
-		entry->hostid = (unsigned long) gid;
+		entry->hostid = (unsigned long)gid;
 		entry->range = 1;
 		lxc_list_add_tail(new, tmp);
+		DEBUG("adding gid mapping: nsid %lu hostid %lu range %lu",
+		      entry->nsid, entry->hostid, entry->range);
 	}
-	lxc_list_for_each_safe(it, &conf->id_map, next) {
-		tmp = malloc(sizeof(*tmp));
-		if (!tmp)
-			goto err;
-		entry = malloc(sizeof(*entry));
-		if (!entry) {
-			free(tmp);
-			goto err;
-		}
-		memset(entry, 0, sizeof(*entry));
-		memcpy(entry, it->elem, sizeof(*entry));
-		tmp->elem = entry;
-		lxc_list_add_tail(new, tmp);
-	}
+	lxc_list_add_tail(new, tmp);
 
 	return new;
 
-err:
-	ERROR("Out of memory building a new uid/gid map");
+on_error:
+	ERROR("failed to allocate memory for new id map");
 	if (new)
 		lxc_free_idmap(new);
 	free(new);
+	if (entry)
+		free(entry);
 	return NULL;
 }
 
-/*
- * Run a function in a new user namespace.
- * The caller's euid/egid will be mapped in if it is not already.
+/* Run a function in a new user namespace.
+ * The caller's euid/egid will be mapped if it is not already.
+ * Afaict, userns_exec_1() is only used to operate based on privileges for the
+ * user's own {g,u}id on the host and for the container root's unmapped {g,u}id.
+ * This means we require only to establish a mapping from:
+ * - the container root {g,u}id as seen from the host > user's host {g,u}id
+ * - the container root -> some sub{g,u}id
+ * The former we add, if the user did not specifiy a mapping. The latter we
+ * retrieve from the ontainer's configured {g,u}id mappings as it must have been
+ * there to start the container in the first place.
  */
 int userns_exec_1(struct lxc_conf *conf, int (*fn)(void *), void *data)
 {
-	int ret, pid;
+	pid_t pid;
+	uid_t euid, egid;
 	struct userns_fn_data d;
-	char c = '1';
 	int p[2];
-	struct lxc_list *idmap;
+	struct lxc_list *it;
+	struct id_map *map;
+	char c = '1';
+	int ret = -1;
+	struct lxc_list *idmap = NULL, *tmplist = NULL;
+	struct id_map *container_root_uid = NULL, *container_root_gid = NULL;
 
 	ret = pipe(p);
 	if (ret < 0) {
@@ -4737,41 +4787,119 @@ int userns_exec_1(struct lxc_conf *conf, int (*fn)(void *), void *data)
 	d.arg = data;
 	d.p[0] = p[0];
 	d.p[1] = p[1];
+
+	/* Clone child in new user namespace. */
 	pid = lxc_clone(run_userns_fn, &d, CLONE_NEWUSER);
-	if (pid < 0)
-		goto err;
+	if (pid < 0) {
+		ERROR("failed to clone child process in new user namespace");
+		goto on_error;
+	}
+
 	close(p[0]);
 	p[0] = -1;
 
-	if ((idmap = idmap_add_id(conf, geteuid(), getegid())) == NULL) {
-		ERROR("Error adding self to container uid/gid map");
-		goto err;
+	/* Find container root. */
+	lxc_list_for_each(it, &conf->id_map) {
+		map = it->elem;
+
+		if (map->nsid != 0)
+			continue;
+
+		if (map->idtype == ID_TYPE_UID && container_root_uid == NULL) {
+			container_root_uid = malloc(sizeof(*container_root_uid));
+			if (!container_root_uid)
+				goto on_error;
+			container_root_uid->idtype = map->idtype;
+			container_root_uid->hostid = map->hostid;
+			container_root_uid->nsid = 0;
+			container_root_uid->range = map->range;
+		} else if (map->idtype == ID_TYPE_GID && container_root_gid == NULL) {
+			container_root_gid = malloc(sizeof(*container_root_gid));
+			if (!container_root_gid)
+				goto on_error;
+			container_root_gid->idtype = map->idtype;
+			container_root_gid->hostid = map->hostid;
+			container_root_gid->nsid = 0;
+			container_root_gid->range = map->range;
+		}
+
+		/* Found container root. */
+		if (container_root_uid && container_root_gid)
+			break;
+	}
+
+	/* This is actually checked earlier but it can't hurt. */
+	if (!container_root_uid || !container_root_gid) {
+		ERROR("no mapping for container root found");
+		goto on_error;
+	}
+
+	/* Check whether the {g,u}id of the user has a mapping. */
+	euid = geteuid();
+	egid = getegid();
+	idmap = idmap_add_id(conf, euid, egid);
+	if (!idmap) {
+		ERROR("failed to prepare id mapping for uid %d and gid %d",
+		      euid, egid);
+		goto on_error;
+	}
+
+	/* Add container root to the map. */
+	tmplist = malloc(sizeof(*tmplist));
+	if (!tmplist)
+		goto on_error;
+	lxc_list_add_elem(tmplist, container_root_uid);
+	lxc_list_add_tail(idmap, tmplist);
+	/* idmap will now keep track of that memory. */
+	container_root_uid = NULL;
+
+	tmplist = malloc(sizeof(*tmplist));
+	if (!tmplist)
+		goto on_error;
+	lxc_list_add_elem(tmplist, container_root_gid);
+	lxc_list_add_tail(idmap, tmplist);
+	/* idmap will now keep track of that memory. */
+	container_root_gid = NULL;
+
+	if (lxc_log_get_level() == LXC_LOG_PRIORITY_TRACE) {
+		lxc_list_for_each(it, idmap) {
+			map = it->elem;
+			TRACE("establishing %cid mapping for \"%d\" in new "
+			      "user namespace: nsuid %lu - hostid %lu - range "
+			      "%lu",
+			      (map->idtype == ID_TYPE_UID) ? 'u' : 'g', pid,
+			      map->nsid, map->hostid, map->range);
+		}
 	}
 
+	/* Set up {g,u}id mapping for user namespace of child process. */
 	ret = lxc_map_ids(idmap, pid);
-	lxc_free_idmap(idmap);
-	free(idmap);
-	if (ret) {
-		ERROR("Error setting up child mappings");
-		goto err;
+	if (ret < 0) {
+		ERROR("error setting up {g,u}id mappings for child process "
+		      "\"%d\"",
+		      pid);
+		goto on_error;
 	}
 
-	// kick the child
+	/* Tell child to proceed. */
 	if (write(p[1], &c, 1) != 1) {
-		SYSERROR("writing to pipe to child");
-		goto err;
+		SYSERROR("failed telling child process \"%d\" to proceed", pid);
+		goto on_error;
 	}
 
+	/* Wait for child to finish. */
 	ret = wait_for_pid(pid);
 
-	close(p[1]);
-	return ret;
+on_error:
+	lxc_free_idmap(idmap);
+	free(container_root_uid);
+	free(container_root_gid);
 
-err:
 	if (p[0] != -1)
 		close(p[0]);
 	close(p[1]);
-	return -1;
+
+	return ret;
 }
 
 /* not thread-safe, do not use from api without first forking */


More information about the lxc-devel mailing list