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) ...@@ -2003,20 +2003,70 @@ struct bdev *bdev_init(const char *src, const char *dst, const char *data)
return bdev; 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 * If we're not snaphotting, then bdev_copy becomes a simple case of mount
* the original, mount the new, and rsync the contents. * the original, mount the new, and rsync the contents.
*/ */
struct bdev *bdev_copy(const char *src, const char *oldname, const char *cname, struct bdev *bdev_copy(struct lxc_container *c0, const char *cname,
const char *oldpath, const char *lxcpath, const char *bdevtype, const char *lxcpath, const char *bdevtype,
int flags, const char *bdevdata, uint64_t newsize, int flags, const char *bdevdata, uint64_t newsize,
int *needs_rdep) int *needs_rdep)
{ {
struct bdev *orig, *new; struct bdev *orig, *new;
pid_t pid; pid_t pid;
int ret;
bool snap = flags & LXC_CLONE_SNAPSHOT; bool snap = flags & LXC_CLONE_SNAPSHOT;
bool maybe_snap = flags & LXC_CLONE_MAYBE_SNAPSHOT; bool maybe_snap = flags & LXC_CLONE_MAYBE_SNAPSHOT;
bool keepbdevtype = flags & LXC_CLONE_KEEPBDEVTYPE; 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 /* 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 * 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, ...@@ -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 * special case for snapshot - if caller requested maybe_snapshot and
* keepbdevtype and backing store is directory, then proceed with a copy * 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, ...@@ -2081,6 +2146,8 @@ struct bdev *bdev_copy(const char *src, const char *oldname, const char *cname,
bdev_put(new); bdev_put(new);
return NULL; return NULL;
} }
if (snap)
return new;
pid = fork(); pid = fork();
if (pid < 0) { if (pid < 0) {
...@@ -2100,29 +2167,14 @@ struct bdev *bdev_copy(const char *src, const char *oldname, const char *cname, ...@@ -2100,29 +2167,14 @@ struct bdev *bdev_copy(const char *src, const char *oldname, const char *cname,
return new; return new;
} }
if (unshare(CLONE_NEWNS) < 0) { data.orig = orig;
SYSERROR("unshare CLONE_NEWNS"); data.new = new;
exit(1); if (am_unpriv())
} ret = userns_exec_1(c0->lxc_conf, rsync_rootfs_wrapper, &data);
if (snap) else
exit(0); ret = rsync_rootfs(&data);
// 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
exit(0); exit(ret == 0 ? 0 : 1);
} }
static struct bdev * do_bdev_create(const char *dest, const char *type, static struct bdev * do_bdev_create(const char *dest, const char *type,
......
...@@ -99,8 +99,8 @@ char *overlayfs_getlower(char *p); ...@@ -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_init(const char *src, const char *dst, const char *data);
struct bdev *bdev_copy(const char *src, const char *oldname, const char *cname, struct bdev *bdev_copy(struct lxc_container *c0, const char *cname,
const char *oldpath, const char *lxcpath, const char *bdevtype, const char *lxcpath, const char *bdevtype,
int flags, const char *bdevdata, uint64_t newsize, int flags, const char *bdevdata, uint64_t newsize,
int *needs_rdep); int *needs_rdep);
struct bdev *bdev_create(const char *dest, const char *type, struct bdev *bdev_create(const char *dest, const char *type,
......
...@@ -66,10 +66,6 @@ ...@@ -66,10 +66,6 @@
lxc_log_define(lxc_container, lxc); lxc_log_define(lxc_container, lxc);
inline static bool am_unpriv() {
return geteuid() != 0;
}
static bool file_exists(const char *f) static bool file_exists(const char *f)
{ {
struct stat statbuf; struct stat statbuf;
...@@ -2381,8 +2377,7 @@ static int copy_storage(struct lxc_container *c0, struct lxc_container *c, ...@@ -2381,8 +2377,7 @@ static int copy_storage(struct lxc_container *c0, struct lxc_container *c,
struct bdev *bdev; struct bdev *bdev;
int need_rdep; int need_rdep;
bdev = bdev_copy(c0->lxc_conf->rootfs.path, c0->name, c->name, bdev = bdev_copy(c0, c->name, c->config_path, newtype, flags,
c0->config_path, c->config_path, newtype, flags,
bdevdata, newsize, &need_rdep); bdevdata, newsize, &need_rdep);
if (!bdev) { if (!bdev) {
ERROR("Error copying storage"); ERROR("Error copying storage");
...@@ -2408,36 +2403,49 @@ static int copy_storage(struct lxc_container *c0, struct lxc_container *c, ...@@ -2408,36 +2403,49 @@ static int copy_storage(struct lxc_container *c0, struct lxc_container *c,
return 0; return 0;
} }
static int clone_update_rootfs(struct lxc_container *c0, struct clone_update_data {
struct lxc_container *c, int flags, struct lxc_container *c0;
char **hookargs) 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; int ret = -1;
char path[MAXPATHLEN]; char path[MAXPATHLEN];
struct bdev *bdev; struct bdev *bdev;
FILE *fout; FILE *fout;
pid_t pid;
struct lxc_conf *conf = c->lxc_conf; struct lxc_conf *conf = c->lxc_conf;
/* update hostname in rootfs */ /* update hostname in rootfs */
/* we're going to mount, so run in a clean namespace to simplify cleanup */ /* we're going to mount, so run in a clean namespace to simplify cleanup */
pid = fork(); if (setgid(0) < 0) {
if (pid < 0) ERROR("Failed to setgid to 0");
return -1; 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); bdev = bdev_init(c->lxc_conf->rootfs.path, c->lxc_conf->rootfs.mount, NULL);
if (!bdev) if (!bdev)
exit(1); return -1;
if (strcmp(bdev->type, "dir") != 0) { if (strcmp(bdev->type, "dir") != 0) {
if (unshare(CLONE_NEWNS) < 0) { if (unshare(CLONE_NEWNS) < 0) {
ERROR("error unsharing mounts"); ERROR("error unsharing mounts");
exit(1); return -1;
} }
if (bdev->ops->mount(bdev) < 0) if (bdev->ops->mount(bdev) < 0)
exit(1); return -1;
} else { // TODO come up with a better way } else { // TODO come up with a better way
if (bdev->dest) if (bdev->dest)
free(bdev->dest); free(bdev->dest);
...@@ -2464,26 +2472,32 @@ static int clone_update_rootfs(struct lxc_container *c0, ...@@ -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)) { if (run_lxc_hooks(c->name, "clone", conf, c->get_config_path(c), hookargs)) {
ERROR("Error executing clone hook for %s", c->name); ERROR("Error executing clone hook for %s", c->name);
exit(1); return -1;
} }
} }
if (!(flags & LXC_CLONE_KEEPNAME)) { if (!(flags & LXC_CLONE_KEEPNAME)) {
ret = snprintf(path, MAXPATHLEN, "%s/etc/hostname", bdev->dest); ret = snprintf(path, MAXPATHLEN, "%s/etc/hostname", bdev->dest);
if (ret < 0 || ret >= MAXPATHLEN) if (ret < 0 || ret >= MAXPATHLEN)
exit(1); return -1;
if (!file_exists(path)) if (!file_exists(path))
exit(0); return 0;
if (!(fout = fopen(path, "w"))) { if (!(fout = fopen(path, "w"))) {
SYSERROR("unable to open %s: ignoring\n", path); SYSERROR("unable to open %s: ignoring\n", path);
exit(0); return 0;
} }
if (fprintf(fout, "%s", c->name) < 0) if (fprintf(fout, "%s", c->name) < 0)
exit(1); return -1;
if (fclose(fout) < 0) 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 ...@@ -2522,16 +2536,13 @@ static struct lxc_container *lxcapi_clone(struct lxc_container *c, const char *n
char newpath[MAXPATHLEN]; char newpath[MAXPATHLEN];
int ret, storage_copied = 0; int ret, storage_copied = 0;
const char *n, *l; const char *n, *l;
struct clone_update_data data;
FILE *fout; FILE *fout;
pid_t pid;
if (!c || !c->is_defined(c)) if (!c || !c->is_defined(c))
return NULL; return NULL;
if (am_unpriv()) {
ERROR(NOT_SUPPORTED_ERROR, __FUNCTION__);
return NULL;
}
if (container_mem_lock(c)) if (container_mem_lock(c))
return NULL; return NULL;
...@@ -2574,6 +2585,13 @@ static struct lxc_container *lxcapi_clone(struct lxc_container *c, const char *n ...@@ -2574,6 +2585,13 @@ static struct lxc_container *lxcapi_clone(struct lxc_container *c, const char *n
goto out; 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); c2 = lxc_container_new(n, l);
if (!c2) { if (!c2) {
ERROR("clone: failed to create new container (%s %s)", n, l); 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 ...@@ -2614,12 +2632,31 @@ static struct lxc_container *lxcapi_clone(struct lxc_container *c, const char *n
if (!c2->save_config(c2, NULL)) if (!c2->save_config(c2, NULL))
goto out; goto out;
if (clone_update_rootfs(c, c2, flags, hookargs) < 0) if ((pid = fork()) < 0) {
SYSERROR("fork");
goto out; 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); container_mem_unlock(c);
return c2; exit(0);
out: out:
container_mem_unlock(c); container_mem_unlock(c);
...@@ -2641,11 +2678,6 @@ static bool lxcapi_rename(struct lxc_container *c, const char *newname) ...@@ -2641,11 +2678,6 @@ static bool lxcapi_rename(struct lxc_container *c, const char *newname)
if (!c || !c->name || !c->config_path) if (!c || !c->name || !c->config_path)
return false; 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); bdev = bdev_init(c->lxc_conf->rootfs.path, c->lxc_conf->rootfs.mount, NULL);
if (!bdev) { if (!bdev) {
ERROR("Failed to find original backing store type"); ERROR("Failed to find original backing store type");
...@@ -2718,11 +2750,6 @@ static int lxcapi_snapshot(struct lxc_container *c, const char *commentfile) ...@@ -2718,11 +2750,6 @@ static int lxcapi_snapshot(struct lxc_container *c, const char *commentfile)
struct lxc_container *c2; struct lxc_container *c2;
char snappath[MAXPATHLEN], newname[20]; char snappath[MAXPATHLEN], newname[20];
if (am_unpriv()) {
ERROR(NOT_SUPPORTED_ERROR, __FUNCTION__);
return -1;
}
// /var/lib/lxc -> /var/lib/lxcsnaps \0 // /var/lib/lxc -> /var/lib/lxcsnaps \0
ret = snprintf(snappath, MAXPATHLEN, "%ssnaps/%s", c->config_path, c->name); ret = snprintf(snappath, MAXPATHLEN, "%ssnaps/%s", c->config_path, c->name);
if (ret < 0 || ret >= MAXPATHLEN) if (ret < 0 || ret >= MAXPATHLEN)
...@@ -2861,11 +2888,6 @@ static int lxcapi_snapshot_list(struct lxc_container *c, struct lxc_snapshot **r ...@@ -2861,11 +2888,6 @@ static int lxcapi_snapshot_list(struct lxc_container *c, struct lxc_snapshot **r
if (!c || !lxcapi_is_defined(c)) if (!c || !lxcapi_is_defined(c))
return -1; return -1;
if (am_unpriv()) {
ERROR(NOT_SUPPORTED_ERROR, __FUNCTION__);
return -1;
}
// snappath is ${lxcpath}snaps/${lxcname}/ // snappath is ${lxcpath}snaps/${lxcname}/
dirlen = snprintf(snappath, MAXPATHLEN, "%ssnaps/%s", c->config_path, c->name); dirlen = snprintf(snappath, MAXPATHLEN, "%ssnaps/%s", c->config_path, c->name);
if (dirlen < 0 || dirlen >= MAXPATHLEN) { if (dirlen < 0 || dirlen >= MAXPATHLEN) {
...@@ -2944,11 +2966,6 @@ static bool lxcapi_snapshot_restore(struct lxc_container *c, const char *snapnam ...@@ -2944,11 +2966,6 @@ static bool lxcapi_snapshot_restore(struct lxc_container *c, const char *snapnam
if (!c || !c->name || !c->config_path) if (!c || !c->name || !c->config_path)
return false; 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); bdev = bdev_init(c->lxc_conf->rootfs.path, c->lxc_conf->rootfs.mount, NULL);
if (!bdev) { if (!bdev) {
ERROR("Failed to find original backing store type"); ERROR("Failed to find original backing store type");
...@@ -2998,11 +3015,6 @@ static bool lxcapi_snapshot_destroy(struct lxc_container *c, const char *snapnam ...@@ -2998,11 +3015,6 @@ static bool lxcapi_snapshot_destroy(struct lxc_container *c, const char *snapnam
if (!c || !c->name || !c->config_path) if (!c || !c->name || !c->config_path)
return false; return false;
if (am_unpriv()) {
ERROR(NOT_SUPPORTED_ERROR, __FUNCTION__);
return false;
}
ret = snprintf(clonelxcpath, MAXPATHLEN, "%ssnaps/%s", c->config_path, c->name); ret = snprintf(clonelxcpath, MAXPATHLEN, "%ssnaps/%s", c->config_path, c->name);
if (ret < 0 || ret >= MAXPATHLEN) if (ret < 0 || ret >= MAXPATHLEN)
goto err; goto err;
......
...@@ -260,4 +260,7 @@ extern void **lxc_append_null_to_array(void **array, size_t count); ...@@ -260,4 +260,7 @@ extern void **lxc_append_null_to_array(void **array, size_t count);
//initialize rand with urandom //initialize rand with urandom
extern int randseed(bool); extern int randseed(bool);
inline static bool am_unpriv(void) {
return geteuid() != 0;
}
#endif #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