From d5aa23e662fd9c64bf72df47f74765e5232f0a96 Mon Sep 17 00:00:00 2001 From: Serge Hallyn Date: Fri, 7 Feb 2014 13:00:50 -0600 Subject: [PATCH] add_device_node: act in a chroot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Acked-by: Stéphane Graber --- 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 90f089c7ff..ac92bf10e9 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)