Commit 1354955b by Serge Hallyn Committed by Stéphane Graber

lxc-clone: support unprivileged use

This also fixes unprivileged use of lxc-snapshot and lxc-rename. Signed-off-by: 's avatarSerge Hallyn <serge.hallyn@ubuntu.com> Acked-by: 's avatarStéphane Graber <stgraber@ubuntu.com>
parent f6639e3b
......@@ -2003,20 +2003,70 @@ struct bdev *bdev_init(const char *src, const char *dst, const char *data)
return bdev;
}
struct rsync_data {
struct bdev *orig;
struct bdev *new;
};
static int rsync_rootfs(struct rsync_data *data)
{
struct bdev *orig = data->orig,
*new = data->new;
if (unshare(CLONE_NEWNS) < 0) {
SYSERROR("unshare CLONE_NEWNS");
return -1;
}
// If not a snapshot, copy the fs.
if (orig->ops->mount(orig) < 0) {
ERROR("failed mounting %s onto %s\n", orig->src, orig->dest);
return -1;
}
if (new->ops->mount(new) < 0) {
ERROR("failed mounting %s onto %s\n", new->src, new->dest);
return -1;
}
if (setgid(0) < 0) {
ERROR("Failed to setgid to 0");
return -1;
}
if (setuid(0) < 0) {
ERROR("Failed to setuid to 0");
return -1;
}
if (do_rsync(orig->dest, new->dest) < 0) {
ERROR("rsyncing %s to %s\n", orig->src, new->src);
return -1;
}
return 0;
}
static int rsync_rootfs_wrapper(void *data)
{
struct rsync_data *arg = data;
return rsync_rootfs(arg);
}
/*
* If we're not snaphotting, then bdev_copy becomes a simple case of mount
* the original, mount the new, and rsync the contents.
*/
struct bdev *bdev_copy(const char *src, const char *oldname, const char *cname,
const char *oldpath, const char *lxcpath, const char *bdevtype,
struct bdev *bdev_copy(struct lxc_container *c0, const char *cname,
const char *lxcpath, const char *bdevtype,
int flags, const char *bdevdata, uint64_t newsize,
int *needs_rdep)
{
struct bdev *orig, *new;
pid_t pid;
int ret;
bool snap = flags & LXC_CLONE_SNAPSHOT;
bool maybe_snap = flags & LXC_CLONE_MAYBE_SNAPSHOT;
bool keepbdevtype = flags & LXC_CLONE_KEEPBDEVTYPE;
const char *src = c0->lxc_conf->rootfs.path;
const char *oldname = c0->name;
const char *oldpath = c0->config_path;
struct rsync_data data;
/* if the container name doesn't show up in the rootfs path, then
* we don't know how to come up with a new name
......@@ -2049,6 +2099,21 @@ struct bdev *bdev_copy(const char *src, const char *oldname, const char *cname,
}
}
/* check for privilege */
if (am_unpriv()) {
if (bdevtype && strcmp(bdevtype, "dir") != 0) {
ERROR("Unprivileged users can only make dir copy-clones");
bdev_put(orig);
return NULL;
}
if (strcmp(orig->type, "dir") != 0) {
ERROR("Unprivileged users can only make dir copy-clones");
bdev_put(orig);
return NULL;
}
}
/*
* special case for snapshot - if caller requested maybe_snapshot and
* keepbdevtype and backing store is directory, then proceed with a copy
......@@ -2081,6 +2146,8 @@ struct bdev *bdev_copy(const char *src, const char *oldname, const char *cname,
bdev_put(new);
return NULL;
}
if (snap)
return new;
pid = fork();
if (pid < 0) {
......@@ -2100,29 +2167,14 @@ struct bdev *bdev_copy(const char *src, const char *oldname, const char *cname,
return new;
}
if (unshare(CLONE_NEWNS) < 0) {
SYSERROR("unshare CLONE_NEWNS");
exit(1);
}
if (snap)
exit(0);
// If not a snapshot, copy the fs.
if (orig->ops->mount(orig) < 0) {
ERROR("failed mounting %s onto %s\n", src, orig->dest);
exit(1);
}
if (new->ops->mount(new) < 0) {
ERROR("failed mounting %s onto %s\n", new->src, new->dest);
exit(1);
}
if (do_rsync(orig->dest, new->dest) < 0) {
ERROR("rsyncing %s to %s\n", orig->src, new->src);
exit(1);
}
// don't bother umounting, ns exit will do that
data.orig = orig;
data.new = new;
if (am_unpriv())
ret = userns_exec_1(c0->lxc_conf, rsync_rootfs_wrapper, &data);
else
ret = rsync_rootfs(&data);
exit(0);
exit(ret == 0 ? 0 : 1);
}
static struct bdev * do_bdev_create(const char *dest, const char *type,
......
......@@ -99,8 +99,8 @@ char *overlayfs_getlower(char *p);
*/
struct bdev *bdev_init(const char *src, const char *dst, const char *data);
struct bdev *bdev_copy(const char *src, const char *oldname, const char *cname,
const char *oldpath, const char *lxcpath, const char *bdevtype,
struct bdev *bdev_copy(struct lxc_container *c0, const char *cname,
const char *lxcpath, const char *bdevtype,
int flags, const char *bdevdata, uint64_t newsize,
int *needs_rdep);
struct bdev *bdev_create(const char *dest, const char *type,
......
......@@ -66,10 +66,6 @@
lxc_log_define(lxc_container, lxc);
inline static bool am_unpriv() {
return geteuid() != 0;
}
static bool file_exists(const char *f)
{
struct stat statbuf;
......@@ -2381,8 +2377,7 @@ static int copy_storage(struct lxc_container *c0, struct lxc_container *c,
struct bdev *bdev;
int need_rdep;
bdev = bdev_copy(c0->lxc_conf->rootfs.path, c0->name, c->name,
c0->config_path, c->config_path, newtype, flags,
bdev = bdev_copy(c0, c->name, c->config_path, newtype, flags,
bdevdata, newsize, &need_rdep);
if (!bdev) {
ERROR("Error copying storage");
......@@ -2408,36 +2403,49 @@ static int copy_storage(struct lxc_container *c0, struct lxc_container *c,
return 0;
}
static int clone_update_rootfs(struct lxc_container *c0,
struct lxc_container *c, int flags,
char **hookargs)
struct clone_update_data {
struct lxc_container *c0;
struct lxc_container *c1;
int flags;
char **hookargs;
};
static int clone_update_rootfs(struct clone_update_data *data)
{
struct lxc_container *c0 = data->c0;
struct lxc_container *c = data->c1;
int flags = data->flags;
char **hookargs = data->hookargs;
int ret = -1;
char path[MAXPATHLEN];
struct bdev *bdev;
FILE *fout;
pid_t pid;
struct lxc_conf *conf = c->lxc_conf;
/* update hostname in rootfs */
/* we're going to mount, so run in a clean namespace to simplify cleanup */
pid = fork();
if (pid < 0)
if (setgid(0) < 0) {
ERROR("Failed to setgid to 0");
return -1;
if (pid > 0)
return wait_for_pid(pid);
}
if (setuid(0) < 0) {
ERROR("Failed to setuid to 0");
return -1;
}
if (unshare(CLONE_NEWNS) < 0)
return -1;
bdev = bdev_init(c->lxc_conf->rootfs.path, c->lxc_conf->rootfs.mount, NULL);
if (!bdev)
exit(1);
return -1;
if (strcmp(bdev->type, "dir") != 0) {
if (unshare(CLONE_NEWNS) < 0) {
ERROR("error unsharing mounts");
exit(1);
return -1;
}
if (bdev->ops->mount(bdev) < 0)
exit(1);
return -1;
} else { // TODO come up with a better way
if (bdev->dest)
free(bdev->dest);
......@@ -2464,26 +2472,32 @@ static int clone_update_rootfs(struct lxc_container *c0,
if (run_lxc_hooks(c->name, "clone", conf, c->get_config_path(c), hookargs)) {
ERROR("Error executing clone hook for %s", c->name);
exit(1);
return -1;
}
}
if (!(flags & LXC_CLONE_KEEPNAME)) {
ret = snprintf(path, MAXPATHLEN, "%s/etc/hostname", bdev->dest);
if (ret < 0 || ret >= MAXPATHLEN)
exit(1);
return -1;
if (!file_exists(path))
exit(0);
return 0;
if (!(fout = fopen(path, "w"))) {
SYSERROR("unable to open %s: ignoring\n", path);
exit(0);
return 0;
}
if (fprintf(fout, "%s", c->name) < 0)
exit(1);
return -1;
if (fclose(fout) < 0)
exit(1);
return -1;
}
exit(0);
return 0;
}
static int clone_update_rootfs_wrapper(void *data)
{
struct clone_update_data *arg = (struct clone_update_data *) data;
return clone_update_rootfs(arg);
}
/*
......@@ -2522,16 +2536,13 @@ static struct lxc_container *lxcapi_clone(struct lxc_container *c, const char *n
char newpath[MAXPATHLEN];
int ret, storage_copied = 0;
const char *n, *l;
struct clone_update_data data;
FILE *fout;
pid_t pid;
if (!c || !c->is_defined(c))
return NULL;
if (am_unpriv()) {
ERROR(NOT_SUPPORTED_ERROR, __FUNCTION__);
return NULL;
}
if (container_mem_lock(c))
return NULL;
......@@ -2574,6 +2585,13 @@ static struct lxc_container *lxcapi_clone(struct lxc_container *c, const char *n
goto out;
}
if (am_unpriv()) {
if (chown_mapped_root(newpath, c->lxc_conf) < 0) {
ERROR("Error chowning %s to container root\n", newpath);
goto out;
}
}
c2 = lxc_container_new(n, l);
if (!c2) {
ERROR("clone: failed to create new container (%s %s)", n, l);
......@@ -2614,12 +2632,31 @@ static struct lxc_container *lxcapi_clone(struct lxc_container *c, const char *n
if (!c2->save_config(c2, NULL))
goto out;
if (clone_update_rootfs(c, c2, flags, hookargs) < 0)
if ((pid = fork()) < 0) {
SYSERROR("fork");
goto out;
}
if (pid > 0) {
ret = wait_for_pid(pid);
if (ret)
goto out;
container_mem_unlock(c);
return c2;
}
data.c0 = c;
data.c1 = c2;
data.flags = flags;
data.hookargs = hookargs;
if (am_unpriv())
ret = userns_exec_1(c->lxc_conf, clone_update_rootfs_wrapper,
&data);
else
ret = clone_update_rootfs(&data);
if (ret < 0)
exit(1);
// TODO: update c's lxc.snapshot = count
container_mem_unlock(c);
return c2;
exit(0);
out:
container_mem_unlock(c);
......@@ -2641,11 +2678,6 @@ static bool lxcapi_rename(struct lxc_container *c, const char *newname)
if (!c || !c->name || !c->config_path)
return false;
if (am_unpriv()) {
ERROR(NOT_SUPPORTED_ERROR, __FUNCTION__);
return false;
}
bdev = bdev_init(c->lxc_conf->rootfs.path, c->lxc_conf->rootfs.mount, NULL);
if (!bdev) {
ERROR("Failed to find original backing store type");
......@@ -2718,11 +2750,6 @@ static int lxcapi_snapshot(struct lxc_container *c, const char *commentfile)
struct lxc_container *c2;
char snappath[MAXPATHLEN], newname[20];
if (am_unpriv()) {
ERROR(NOT_SUPPORTED_ERROR, __FUNCTION__);
return -1;
}
// /var/lib/lxc -> /var/lib/lxcsnaps \0
ret = snprintf(snappath, MAXPATHLEN, "%ssnaps/%s", c->config_path, c->name);
if (ret < 0 || ret >= MAXPATHLEN)
......@@ -2861,11 +2888,6 @@ static int lxcapi_snapshot_list(struct lxc_container *c, struct lxc_snapshot **r
if (!c || !lxcapi_is_defined(c))
return -1;
if (am_unpriv()) {
ERROR(NOT_SUPPORTED_ERROR, __FUNCTION__);
return -1;
}
// snappath is ${lxcpath}snaps/${lxcname}/
dirlen = snprintf(snappath, MAXPATHLEN, "%ssnaps/%s", c->config_path, c->name);
if (dirlen < 0 || dirlen >= MAXPATHLEN) {
......@@ -2944,11 +2966,6 @@ static bool lxcapi_snapshot_restore(struct lxc_container *c, const char *snapnam
if (!c || !c->name || !c->config_path)
return false;
if (am_unpriv()) {
ERROR(NOT_SUPPORTED_ERROR, __FUNCTION__);
return false;
}
bdev = bdev_init(c->lxc_conf->rootfs.path, c->lxc_conf->rootfs.mount, NULL);
if (!bdev) {
ERROR("Failed to find original backing store type");
......@@ -2998,11 +3015,6 @@ static bool lxcapi_snapshot_destroy(struct lxc_container *c, const char *snapnam
if (!c || !c->name || !c->config_path)
return false;
if (am_unpriv()) {
ERROR(NOT_SUPPORTED_ERROR, __FUNCTION__);
return false;
}
ret = snprintf(clonelxcpath, MAXPATHLEN, "%ssnaps/%s", c->config_path, c->name);
if (ret < 0 || ret >= MAXPATHLEN)
goto err;
......
......@@ -260,4 +260,7 @@ extern void **lxc_append_null_to_array(void **array, size_t count);
//initialize rand with urandom
extern int randseed(bool);
inline static bool am_unpriv(void) {
return geteuid() != 0;
}
#endif
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment