Skip to content

Commit

Permalink
add_device_node: act in a chroot
Browse files Browse the repository at this point in the history
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@ubuntu.com>
Acked-by: Stéphane Graber <stgraber@ubuntu.com>
  • Loading branch information
hallyn authored and stgraber committed Feb 7, 2014
1 parent b8ac275 commit d5aa23e
Showing 1 changed file with 65 additions and 45 deletions.
110 changes: 65 additions & 45 deletions src/lxc/lxccontainer.c
Expand Up @@ -3084,90 +3084,110 @@ 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))
ret = snprintf(value, MAX_BUFFER, "c %d:%d rwm", major(st.st_rdev), minor(st.st_rdev));
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)
Expand Down

0 comments on commit d5aa23e

Please sign in to comment.