[lxc-devel] [PATCH 1/1] lxc-clone: support unprivileged use

Serge Hallyn serge.hallyn at ubuntu.com
Thu Jan 23 00:18:04 UTC 2014


This also fixes unprivileged use of lxc-snapshot and lxc-rename.

Signed-off-by: Serge Hallyn <serge.hallyn at ubuntu.com>
---
 src/lxc/bdev.c         | 100 +++++++++++++++++++++++++++++----------
 src/lxc/bdev.h         |   4 +-
 src/lxc/lxccontainer.c | 126 +++++++++++++++++++++++++++----------------------
 src/lxc/utils.h        |   3 ++
 4 files changed, 150 insertions(+), 83 deletions(-)

diff --git a/src/lxc/bdev.c b/src/lxc/bdev.c
index 5cd7340..2c3388f 100644
--- a/src/lxc/bdev.c
+++ b/src/lxc/bdev.c
@@ -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,
diff --git a/src/lxc/bdev.h b/src/lxc/bdev.h
index c45f086..cc7b59e 100644
--- a/src/lxc/bdev.h
+++ b/src/lxc/bdev.h
@@ -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,
diff --git a/src/lxc/lxccontainer.c b/src/lxc/lxccontainer.c
index 3723997..5a3802a 100644
--- a/src/lxc/lxccontainer.c
+++ b/src/lxc/lxccontainer.c
@@ -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;
@@ -2348,8 +2344,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");
@@ -2375,36 +2370,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);
@@ -2431,26 +2439,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);
 }
 
 /*
@@ -2489,16 +2503,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;
 
@@ -2541,6 +2552,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);
@@ -2581,12 +2599,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);
@@ -2608,11 +2645,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");
@@ -2685,11 +2717,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)
@@ -2828,11 +2855,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) {
@@ -2911,11 +2933,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");
@@ -2965,11 +2982,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;
diff --git a/src/lxc/utils.h b/src/lxc/utils.h
index ab2bd84..dd81c62 100644
--- a/src/lxc/utils.h
+++ b/src/lxc/utils.h
@@ -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
-- 
1.8.5.3



More information about the lxc-devel mailing list