[lxc-devel] [lxc/master] conf: introduce userns_exec_mapped_root()

brauner on Github lxc-bot at linuxcontainers.org
Mon May 4 08:57:12 UTC 2020


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 435 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20200504/2f3da02c/attachment.bin>
-------------- next part --------------
From 234998b4f2a42623565f58b0ad20fa8254cbedde Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Mon, 4 May 2020 10:56:05 +0200
Subject: [PATCH] conf: introduce userns_exec_mapped_root()

to avoid the overhead of calling to lxc-usernsexec whenever we can.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 src/lxc/conf.c     | 246 ++++++++++++++++++++++++++++++++++++++++++---
 src/lxc/conf.h     |   2 +
 src/lxc/terminal.c |  11 +-
 src/lxc/utils.h    |  10 ++
 4 files changed, 251 insertions(+), 18 deletions(-)

diff --git a/src/lxc/conf.c b/src/lxc/conf.c
index 20d1583fc4..90d464f686 100644
--- a/src/lxc/conf.c
+++ b/src/lxc/conf.c
@@ -2791,11 +2791,11 @@ int lxc_map_ids(struct lxc_list *idmap, pid_t pid)
 	return 0;
 }
 
-/* Return the host uid/gid to which the container root is mapped in val.
+/*
+ * Return the host uid/gid to which the container root is mapped in val.
  * Return true if id was found, false otherwise.
  */
-static bool get_mapped_rootid(const struct lxc_conf *conf, enum idtype idtype,
-			      unsigned long *val)
+static id_t get_mapped_rootid(const struct lxc_conf *conf, enum idtype idtype)
 {
 	unsigned nsid;
 	struct id_map *map;
@@ -2812,11 +2812,13 @@ static bool get_mapped_rootid(const struct lxc_conf *conf, enum idtype idtype,
 			continue;
 		if (map->nsid != nsid)
 			continue;
-		*val = map->hostid;
-		return true;
+		return map->hostid;
 	}
 
-	return false;
+	if (idtype == ID_TYPE_UID)
+		return LXC_INVALID_UID;
+
+	return LXC_INVALID_GID;
 }
 
 int mapped_hostid(unsigned id, const struct lxc_conf *conf, enum idtype idtype)
@@ -2873,7 +2875,6 @@ int chown_mapped_root_exec_wrapper(void *args)
 int chown_mapped_root(const char *path, const struct lxc_conf *conf)
 {
 	uid_t rootuid, rootgid;
-	unsigned long val;
 	int hostuid, hostgid, ret;
 	struct stat sb;
 	char map1[100], map2[100], map3[100], map4[100], map5[100];
@@ -2895,17 +2896,15 @@ int chown_mapped_root(const char *path, const struct lxc_conf *conf)
 			 NULL};
 	char cmd_output[PATH_MAX];
 
-	hostuid = geteuid();
-	hostgid = getegid();
-
-	if (!get_mapped_rootid(conf, ID_TYPE_UID, &val))
+	rootuid = get_mapped_rootid(conf, ID_TYPE_UID);
+	if (!uid_valid(rootuid))
 		return log_error(-1, "No uid mapping for container root");
-	rootuid = (uid_t)val;
 
-	if (!get_mapped_rootid(conf, ID_TYPE_GID, &val))
+	rootgid = get_mapped_rootid(conf, ID_TYPE_GID);
+	if (!gid_valid(rootgid))
 		return log_error(-1, "No gid mapping for container root");
-	rootgid = (gid_t)val;
 
+	hostuid = geteuid();
 	if (hostuid == 0) {
 		if (chown(path, rootuid, rootgid) < 0)
 			return log_error(-1, "Error chowning %s", path);
@@ -2929,6 +2928,7 @@ int chown_mapped_root(const char *path, const struct lxc_conf *conf)
 	 * A file has to be group-owned by a gid mapped into the
 	 * container, or the container won't be privileged over it.
 	 */
+	hostgid = getegid();
 	DEBUG("trying to chown \"%s\" to %d", path, hostgid);
 	if (sb.st_uid == hostuid &&
 	    mapped_hostid(sb.st_gid, conf, ID_TYPE_GID) < 0 &&
@@ -4420,6 +4420,224 @@ int userns_exec_full(struct lxc_conf *conf, int (*fn)(void *), void *data,
 	return ret;
 }
 
+static int add_idmap_entry(struct lxc_list *idmap, enum idtype idtype,
+			   unsigned long nsid, unsigned long hostid,
+			   unsigned long range)
+{
+	__do_free struct id_map *new_idmap = NULL;
+	__do_free struct lxc_list *new_list = NULL;
+
+	new_idmap = zalloc(sizeof(*new_idmap));
+	if (!new_idmap)
+		return ret_errno(ENOMEM);
+
+	new_idmap->idtype = idtype;
+	new_idmap->hostid = hostid;
+	new_idmap->nsid = nsid;
+	new_idmap->range = range;
+
+	new_list = zalloc(sizeof(*new_list));
+	if (!new_list)
+		return ret_errno(ENOMEM);
+
+	new_list->elem = move_ptr(new_idmap);
+	lxc_list_add_tail(idmap, move_ptr(new_list));
+
+	INFO("Adding id map: type %c nsid %lu hostid %lu range %lu",
+	     idtype == ID_TYPE_UID ? 'u' : 'g', nsid, hostid, range);
+	return 0;
+}
+
+int userns_exec_mapped_root(const char *path, int path_fd,
+			    const struct lxc_conf *conf)
+{
+	call_cleaner(lxc_free_idmap) struct lxc_list *idmap = NULL;
+	__do_close int fd = -EBADF;
+	int target_fd = -EBADF;
+	char c = '1';
+	ssize_t ret;
+	pid_t pid;
+	int sock_fds[2];
+	uid_t container_host_uid, hostuid;
+	gid_t container_host_gid, hostgid;
+	struct stat st;
+
+	if (!conf || (!path && path_fd < 0))
+		return ret_errno(EINVAL);
+
+	if (!path)
+		path = "(null)";
+
+	container_host_uid = get_mapped_rootid(conf, ID_TYPE_UID);
+	if (!uid_valid(container_host_uid))
+		return log_error(-1, "No uid mapping for container root");
+
+	container_host_gid = get_mapped_rootid(conf, ID_TYPE_GID);
+	if (!gid_valid(container_host_gid))
+		return log_error(-1, "No gid mapping for container root");
+
+	if (path) {
+		fd = open(path, O_RDWR | O_CLOEXEC | O_NOCTTY);
+		if (fd < 0)
+			return log_error_errno(-errno, errno, "Failed to open \"%s\"", path);
+		target_fd = fd;
+	} else {
+		target_fd = path_fd;
+	}
+
+	hostuid = geteuid();
+	/* We are root so chown directly. */
+	if (hostuid == 0) {
+		ret = fchown(target_fd, container_host_uid, container_host_gid);
+		if (ret)
+			return log_error_errno(-errno, errno,
+					       "Failed to fchown(%d(%s), %d, %d)",
+					       target_fd, path, container_host_uid,
+					       container_host_gid);
+		return log_trace(0, "Chowned %d(%s) to uid %d and %d", target_fd, path,
+				 container_host_uid, container_host_gid);
+	}
+
+	/* The container's root host id matches  */
+	if (container_host_uid == hostuid)
+		return log_info(0, "Container root id is mapped to our uid");
+
+	/* Get the current ids of our target. */
+	ret = fstat(target_fd, &st);
+	if (ret)
+		return log_error_errno(-errno, errno, "Failed to stat \"%s\"", path);
+
+	hostgid = getegid();
+	if (st.st_uid == hostuid && mapped_hostid(st.st_gid, conf, ID_TYPE_GID) < 0) {
+		ret = fchown(target_fd, -1, hostgid);
+		if (ret)
+			return log_error_errno(-errno, errno,
+					       "Failed to fchown(%d(%s), -1, %d)",
+					       target_fd, path, hostgid);
+	}
+
+	idmap = malloc(sizeof(*idmap));
+	if (!idmap)
+		return -ENOMEM;
+	lxc_list_init(idmap);
+
+	/* "u:0:rootuid:1" */
+	ret = add_idmap_entry(idmap, ID_TYPE_UID, 0, container_host_uid, 1);
+	if (ret < 0)
+		return log_error_errno(ret, -ret, "Failed to add idmap entry");
+
+	/* "u:hostuid:hostuid:1" */
+	ret = add_idmap_entry(idmap, ID_TYPE_UID, hostuid, hostuid, 1);
+	if (ret < 0)
+		return log_error_errno(ret, -ret, "Failed to add idmap entry");
+
+	/* "g:0:rootgid:1" */
+	ret = add_idmap_entry(idmap, ID_TYPE_GID, 0, container_host_gid, 1);
+	if (ret < 0)
+		return log_error_errno(ret, -ret, "Failed to add idmap entry");
+
+	/* "g:hostgid:hostgid:1" */
+	ret = add_idmap_entry(idmap, ID_TYPE_GID, hostgid, hostgid, 1);
+	if (ret < 0)
+		return log_error_errno(ret, -ret, "Failed to add idmap entry");
+
+	if (hostgid != st.st_gid) {
+		/* "g:pathgid:rootgid+pathgid:1" */
+		ret = add_idmap_entry(idmap, ID_TYPE_GID, st.st_gid,
+				      container_host_gid + (gid_t)st.st_gid, 1);
+		if (ret < 0)
+			return log_error_errno(ret, -ret, "Failed to add idmap entry");
+	}
+
+	ret = socketpair(PF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, sock_fds);
+	if (ret < 0)
+		return -errno;
+
+	pid = fork();
+	if (pid < 0) {
+		SYSERROR("Failed to create new process");
+		goto on_error;
+	}
+
+	if (pid == 0) {
+		close_prot_errno_disarm(sock_fds[1]);
+
+		ret = unshare(CLONE_NEWUSER);
+		if (ret < 0) {
+			SYSERROR("Failed to unshare new user namespace");
+			_exit(EXIT_FAILURE);
+		}
+
+		ret = lxc_write_nointr(sock_fds[0], &c, 1);
+		if (ret != 1)
+			_exit(EXIT_FAILURE);
+
+		ret = lxc_read_nointr(sock_fds[0], &c, 1);
+		if (ret != 1)
+			_exit(EXIT_FAILURE);
+
+		close_prot_errno_disarm(sock_fds[0]);
+
+		if (!lxc_switch_uid_gid(0, 0))
+			_exit(EXIT_FAILURE);
+
+		if (!lxc_setgroups(0, NULL))
+			_exit(EXIT_FAILURE);
+
+		ret = chown(path, 0, st.st_gid);
+		if (ret) {
+			SYSERROR("Failed to chown \"%s\"", path);
+			_exit(EXIT_FAILURE);
+		}
+
+		_exit(EXIT_SUCCESS);
+	}
+
+	close_prot_errno_disarm(sock_fds[0]);
+
+	if (lxc_log_get_level() == LXC_LOG_LEVEL_TRACE ||
+	    conf->loglevel == LXC_LOG_LEVEL_TRACE) {
+		struct id_map *map;
+		struct lxc_list *it;
+
+		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);
+		}
+	}
+
+	ret = lxc_read_nointr(sock_fds[1], &c, 1);
+	if (ret != 1) {
+		SYSERROR("Failed waiting for child process %d\" to tell us to proceed", pid);
+		goto on_error;
+	}
+
+	/* Set up {g,u}id mapping for user namespace of child process. */
+	ret = lxc_map_ids(idmap, pid);
+	if (ret < 0) {
+		ERROR("Error setting up {g,u}id mappings for child process \"%d\"", pid);
+		goto on_error;
+	}
+
+	/* Tell child to proceed. */
+	ret = lxc_write_nointr(sock_fds[1], &c, 1);
+	if (ret != 1) {
+		SYSERROR("Failed telling child process \"%d\" to proceed", pid);
+		goto on_error;
+	}
+
+on_error:
+	close_prot_errno_disarm(sock_fds[0]);
+	close_prot_errno_disarm(sock_fds[1]);
+
+	/* Wait for child to finish. */
+	if (pid < 0)
+		return -1;
+
+	return wait_for_pid(pid);
+}
+
 /* not thread-safe, do not use from api without first forking */
 static char *getuname(void)
 {
diff --git a/src/lxc/conf.h b/src/lxc/conf.h
index 3ff226b729..346b736e17 100644
--- a/src/lxc/conf.h
+++ b/src/lxc/conf.h
@@ -473,5 +473,7 @@ extern int lxc_clear_namespace(struct lxc_conf *c);
 extern int userns_exec_minimal(const struct lxc_conf *conf,
 			       int (*fn_parent)(void *), void *fn_parent_data,
 			       int (*fn_child)(void *), void *fn_child_data);
+extern int userns_exec_mapped_root(const char *path, int path_fd,
+				   const struct lxc_conf *conf);
 
 #endif /* __LXC_CONF_H */
diff --git a/src/lxc/terminal.c b/src/lxc/terminal.c
index 1b170cabe8..c2516b205a 100644
--- a/src/lxc/terminal.c
+++ b/src/lxc/terminal.c
@@ -1167,13 +1167,16 @@ int lxc_terminal_map_ids(struct lxc_conf *c, struct lxc_terminal *terminal)
 	if (strcmp(terminal->name, "") == 0)
 		return 0;
 
-	ret = chown_mapped_root(terminal->name, c);
+	if (terminal->slave >= 0)
+		ret = userns_exec_mapped_root(terminal->name, terminal->slave, c);
+	else
+		ret = userns_exec_mapped_root(terminal->name, terminal->slave, c);
 	if (ret < 0) {
-		ERROR("Failed to chown terminal \"%s\"", terminal->name);
-		return -1;
+		return log_error(-1, "Failed to chown terminal %d(%s)",
+				 terminal->slave, terminal->name);
 	}
 
-	TRACE("Chowned terminal \"%s\"", terminal->name);
+	TRACE("Chowned terminal %d(%s)", terminal->slave, terminal->name);
 
 	return 0;
 }
diff --git a/src/lxc/utils.h b/src/lxc/utils.h
index 339217c506..45ca5270de 100644
--- a/src/lxc/utils.h
+++ b/src/lxc/utils.h
@@ -241,4 +241,14 @@ extern bool lxc_can_use_pidfd(int pidfd);
 
 extern int fix_stdio_permissions(uid_t uid);
 
+static inline bool uid_valid(uid_t uid)
+{
+	return uid != LXC_INVALID_UID;
+}
+
+static inline bool gid_valid(gid_t gid)
+{
+	return gid != LXC_INVALID_GID;
+}
+
 #endif /* __LXC_UTILS_H */


More information about the lxc-devel mailing list