[lxc-devel] [PATCH] add_device_node: act in a chroot

Serge Hallyn serge.hallyn at ubuntu.com
Fri Feb 7 19:00:50 UTC 2014


The goal is to avoid an absolute symlink in the guest redirecting
us to the host's /dev.  Thanks to the libvirt team for considering
that possibility!

We want to work on kernels which do not support setns, so we simply
chroot into the container before doing any rm/mknod.  If /dev/vda5
is a symlink to /XXX, or /dev is a symlink to /etc, this is now
correctly resolved locally in the chroot.

We would have preferred to use realpath() to check that the resolved
path is not changed, but realpath across /proc/pid/root does not
work as expected.

Signed-off-by: Serge Hallyn <serge.hallyn at ubuntu.com>
---
 src/lxc/lxccontainer.c | 110 +++++++++++++++++++++++++++++--------------------
 1 file changed, 65 insertions(+), 45 deletions(-)

diff --git a/src/lxc/lxccontainer.c b/src/lxc/lxccontainer.c
index 90f089c..ac92bf1 100644
--- a/src/lxc/lxccontainer.c
+++ b/src/lxc/lxccontainer.c
@@ -3084,34 +3084,80 @@ static bool lxcapi_may_control(struct lxc_container *c)
 	return lxc_try_cmd(c->name, c->config_path) == 0;
 }
 
+static bool do_add_remove_node(pid_t init_pid, const char *path, bool add,
+		struct stat *st)
+{
+	char chrootpath[MAXPATHLEN];
+	char *directory_path = NULL;
+	pid_t pid;
+	int ret;
+
+	if ((pid = fork()) < 0) {
+		SYSERROR("failed to fork a child helper");
+		return false;
+	}
+	if (pid) {
+		if (wait_for_pid(pid) != 0) {
+			ERROR("Failed to create note in guest");
+			return false;
+		}
+		return true;
+	}
+
+	/* prepare the path */
+	ret = snprintf(chrootpath, MAXPATHLEN, "/proc/%d/root", init_pid);
+	if (ret < 0 || ret >= MAXPATHLEN)
+		return false;
+
+	if (chdir(chrootpath) < 0)
+		exit(1);
+	if (chroot(".") < 0)
+		exit(1);
+	/* remove path if it exists */
+	if(faccessat(AT_FDCWD, path, F_OK, AT_SYMLINK_NOFOLLOW) == 0) {
+		if (unlink(path) < 0) {
+			ERROR("unlink failed");
+			exit(1);
+		}
+	}
+	if (!add)
+		exit(0);
+
+	/* create any missing directories */
+	directory_path = dirname(strdup(path));
+	if (mkdir_p(directory_path, 0755) < 0 && errno != EEXIST) {
+		ERROR("failed to create directory");
+		exit(1);
+	}
+
+	/* create the device node */
+	if (mknod(path, st->st_mode, st->st_rdev) < 0) {
+		ERROR("mknod failed");
+		exit(1);
+	}
+
+	exit(0);
+}
+
 static bool add_remove_device_node(struct lxc_container *c, const char *src_path, const char *dest_path, bool add)
 {
 	int ret;
 	struct stat st;
-	char path[MAXPATHLEN];
 	char value[MAX_BUFFER];
-	char *directory_path = NULL;
 	const char *p;
 
 	/* make sure container is running */
 	if (!c->is_running(c)) {
 		ERROR("container is not running");
-		goto out;
+		return false;
 	}
 
 	/* use src_path if dest_path is NULL otherwise use dest_path */
 	p = dest_path ? dest_path : src_path;
 
-	/* prepare the path */
-	ret = snprintf(path, MAXPATHLEN, "/proc/%d/root/%s", c->init_pid(c), p);
-	if (ret < 0 || ret >= MAXPATHLEN)
-		goto out;
-	remove_trailing_slashes(path);
-
-	p = add ? src_path : path;
 	/* make sure we can access p */
 	if(access(p, F_OK) < 0 || stat(p, &st) < 0)
-		goto out;
+		return false;
 
 	/* continue if path is character device or block device */
 	if (S_ISCHR(st.st_mode))
@@ -3119,55 +3165,29 @@ static bool add_remove_device_node(struct lxc_container *c, const char *src_path
 	else if (S_ISBLK(st.st_mode))
 		ret = snprintf(value, MAX_BUFFER, "b %d:%d rwm", major(st.st_rdev), minor(st.st_rdev));
 	else
-		goto out;
+		return false;
 
 	/* check snprintf return code */
 	if (ret < 0 || ret >= MAX_BUFFER)
-		goto out;
+		return false;
 
-	directory_path = dirname(strdup(path));
-	/* remove path and directory_path (if empty) */
-	if(access(path, F_OK) == 0) {
-		if (unlink(path) < 0) {
-			ERROR("unlink failed");
-			goto out;
-		}
-		if (rmdir(directory_path) < 0 && errno != ENOTEMPTY) {
-			ERROR("rmdir failed");
-			goto out;
-		}
-	}
+	if (!do_add_remove_node(c->init_pid(c), p, add, &st))
+		return false;
 
+	/* add or remove device to/from cgroup access list */
 	if (add) {
-		/* create the missing directories */
-		if (mkdir_p(directory_path, 0755) < 0) {
-			ERROR("failed to create directory");
-			goto out;
-		}
-
-		/* create the device node */
-		if (mknod(path, st.st_mode, st.st_rdev) < 0) {
-			ERROR("mknod failed");
-			goto out;
-		}
-
-		/* add device node to device list */
 		if (!c->set_cgroup_item(c, "devices.allow", value)) {
 			ERROR("set_cgroup_item failed while adding the device node");
-			goto out;
+			return false;
 		}
 	} else {
-		/* remove device node from device list */
 		if (!c->set_cgroup_item(c, "devices.deny", value)) {
 			ERROR("set_cgroup_item failed while removing the device node");
-			goto out;
+			return false;
 		}
 	}
+
 	return true;
-out:
-	if (directory_path)
-		free(directory_path);
-	return false;
 }
 
 static bool lxcapi_add_device_node(struct lxc_container *c, const char *src_path, const char *dest_path)
-- 
1.8.3.2



More information about the lxc-devel mailing list