minijail0: add chroot support.

Support a -C commandline option to chroot(), and a -b commandline option to
bind-mount paths into the chroot from outside.

BUG=chromium-os:21165
TESTED_ON=kaen
TEST=None yet

Change-Id: Ia6a7a4498968a4bc6a12f8274fdb8c4be9d23ca4
Signed-off-by: Elly Jones <ellyjones@chromium.org>
Reviewed-on: http://gerrit.chromium.org/gerrit/8661
Reviewed-by: Kees Cook <keescook@chromium.org>
diff --git a/libminijail.c b/libminijail.c
index 94c6a9d..9600047 100644
--- a/libminijail.c
+++ b/libminijail.c
@@ -58,6 +58,13 @@
 	struct seccomp_filter *next, *prev;
 };
 
+struct binding {
+	char *src;
+	char *dest;
+	int writeable;
+	struct binding *next;
+};
+
 struct minijail {
 	struct {
 		int uid:1;
@@ -70,6 +77,7 @@
 		int usergroups:1;
 		int ptrace:1;
 		int seccomp_filter:1;
+		int chroot:1;
 	} flags;
 	uid_t uid;
 	gid_t gid;
@@ -78,15 +86,16 @@
 	uint64_t caps;
 	pid_t initpid;
 	int filter_count;
+	int binding_count;
+	char *chrootdir;
 	struct seccomp_filter *filters;
+	struct binding *bindings_head;
+	struct binding *bindings_tail;
 };
 
 struct minijail *minijail_new(void)
 {
-	struct minijail *j = malloc(sizeof(*j));
-	if (j)
-		memset(j, 0, sizeof(*j));
-	return j;
+	return calloc(1, sizeof(struct minijail));
 }
 
 void minijail_change_uid(struct minijail *j, uid_t uid)
@@ -197,6 +206,56 @@
 	j->flags.ptrace = 1;
 }
 
+int minijail_enter_chroot(struct minijail *j, const char *dir) {
+	if (j->chrootdir)
+		return -EINVAL;
+	j->chrootdir = strdup(dir);
+	if (!j->chrootdir)
+		return -ENOMEM;
+	j->flags.chroot = 1;
+	return 0;
+}
+
+int minijail_bind(struct minijail *j, const char *src, const char *dest,
+                  int writeable) {
+	struct binding *b;
+
+	if (*dest != '/')
+		return -EINVAL;
+	b = calloc(1, sizeof(*b));
+	if (!b)
+		return -ENOMEM;
+	b->dest = strdup(dest);
+	if (!b->dest)
+		goto error;
+	b->src = strdup(src);
+	if (!b->src)
+		goto error;
+	b->writeable = writeable;
+
+	syslog(LOG_INFO, "libminijail: bind %s -> %s", src, dest);
+
+	/* Force vfs namespacing so the bind mounts don't leak out into the
+	 * containing vfs namespace.
+	 */
+	minijail_namespace_vfs(j);
+
+	if (j->bindings_tail)
+		j->bindings_tail->next = b;
+	else
+		j->bindings_head = b;
+	j->bindings_tail = b;
+	j->binding_count++;
+
+	return 0;
+
+error:
+	free(b->src);
+	free(b->dest);
+	free(b);
+	return -ENOMEM;
+}
+
 int minijail_add_seccomp_filter(struct minijail *j, int nr, const char *filter)
 {
 	struct seccomp_filter *sf;
@@ -332,9 +391,12 @@
 static void minijail_marshal_helper(struct marshal_state *state,
 				    const struct minijail *j)
 {
+	struct binding *b = NULL;
 	marshal_append(state, (char *)j, sizeof(*j));
 	if (j->user)
 		marshal_append(state, j->user, strlen(j->user) + 1);
+	if (j->chrootdir)
+		marshal_append(state, j->chrootdir, strlen(j->chrootdir) + 1);
 	if (j->flags.seccomp_filter && j->filters) {
 		struct seccomp_filter *f = j->filters;
 		do {
@@ -343,6 +405,11 @@
 			f = f->next;
 		} while (f != j->filters);
 	}
+	for (b = j->bindings_head; b; b = b->next) {
+		marshal_append(state, b->src, strlen(b->src) + 1);
+		marshal_append(state, b->dest, strlen(b->dest) + 1);
+		marshal_append(state, (char *)&b->writeable, sizeof(b->writeable));
+	}
 }
 
 size_t minijail_size(const struct minijail *j)
@@ -361,8 +428,40 @@
 	return (state.total > available);
 }
 
+/* consumebytes: consumes @length bytes from a buffer @buf of length @buflength
+ * @length    Number of bytes to consume
+ * @buf       Buffer to consume from
+ * @buflength Size of @buf
+ *
+ * Returns a pointer to the base of the bytes, or NULL for errors.
+ */
+static void *consumebytes(size_t length, char **buf, size_t *buflength) {
+	char *p = *buf;
+	if (length > *buflength)
+		return NULL;
+	*buf += length;
+	*buflength -= length;
+	return p;
+}
+
+/* consumestr: consumes a C string from a buffer @buf of length @length
+ * @buf    Buffer to consume
+ * @length Length of buffer
+ *
+ * Returns a pointer to the base of the string, or NULL for errors.
+ */
+static char *consumestr(char **buf, size_t *buflength) {
+	size_t len = strnlen(*buf, *buflength);
+	if (len == *buflength)
+		/* There's no null-terminator */
+		return NULL;
+	return consumebytes(len + 1, buf, buflength);
+}
+
 int minijail_unmarshal(struct minijail *j, char *serialized, size_t length)
 {
+	int i;
+	int count;
 	if (length < sizeof(*j))
 		return -EINVAL;
 	memcpy((void *)j, serialized, sizeof(*j));
@@ -370,34 +469,51 @@
 	length -= sizeof(*j);
 
 	if (j->user) {		/* stale pointer */
-		if (!length)
+		char *user = consumestr(&serialized, &length);
+		if (!user)
 			return -EINVAL;
-		j->user = strndup(serialized, length);
-		length -= strlen(j->user) + 1;
-		serialized += strlen(j->user) + 1;
+		j->user = strdup(user);
 	}
 
 	if (j->flags.seccomp_filter && j->filter_count) {
-		int count = j->filter_count;
+		count = j->filter_count;
 		/* Let add_seccomp_filter recompute the value. */
 		j->filter_count = 0;
 		j->filters = NULL;	/* Don't follow the stale pointer. */
 		for (; count > 0; --count) {
-			int *nr = (int *)serialized;
+			int *nr = (int *)consumebytes(sizeof(*nr), &serialized,
+			                              &length);
 			char *filter;
-			if (length < sizeof(*nr))
+			if (!nr)
 				return -EINVAL;
-			length -= sizeof(*nr);
-			serialized += sizeof(*nr);
-			if (!length)
+			filter = consumestr(&serialized, &length);
+			if (!filter)
 				return -EINVAL;
-			filter = serialized;
 			if (minijail_add_seccomp_filter(j, *nr, filter))
 				return -EINVAL;
-			length -= strlen(filter) + 1;
-			serialized += strlen(filter) + 1;
 		}
 	}
+
+	count = j->binding_count;
+	j->bindings_head = NULL;
+	j->bindings_tail = NULL;
+	j->binding_count = 0;
+	for (i = 0; i < count; ++i) {
+		int *writeable;
+		const char *dest;
+		const char *src = consumestr(&serialized, &length);
+		if (!src)
+			return -EINVAL;
+		dest = consumestr(&serialized, &length);
+		if (!dest)
+			return -EINVAL;
+		writeable = consumebytes(sizeof(*writeable), &serialized, &length);
+		if (!writeable)
+			return -EINVAL;
+		if (minijail_bind(j, src, dest, *writeable))
+			return -EINVAL;
+	}
+
 	return 0;
 }
 
@@ -423,6 +539,46 @@
 	/* Note, pidns will already have been used before this call. */
 }
 
+/* bind_one: Applies bindings from @b for @j, recursing as needed.
+ * @j Minijail these bindings are for
+ * @b Head of list of bindings
+ *
+ * Returns 0 for success.
+ */
+static int bind_one(const struct minijail *j, struct binding *b) {
+	int ret = 0;
+	char *dest = NULL;
+	int mflags = MS_BIND | (b->writeable ? 0 : MS_RDONLY);
+	if (ret)
+		return ret;
+	/* dest has a leading "/" */
+	if (asprintf(&dest, "%s%s", j->chrootdir, b->dest) < 0)
+		return -ENOMEM;
+	ret = mount(b->src, dest, NULL, mflags, NULL);
+	if (ret)
+		pdie("bind: %s -> %s", b->src, dest);
+	free(dest);
+	if (b->next)
+		return bind_one(j, b->next);
+	return ret;
+}
+
+static int enter_chroot(const struct minijail *j) {
+	int ret;
+	if (j->bindings_head && (ret = bind_one(j, j->bindings_head)))
+		return ret;
+
+	if (chroot(j->chrootdir))
+		return -errno;
+
+	if (chdir("/"))
+		return -errno;
+
+	return 0;
+}
+
+
+
 static int remount_readonly(void)
 {
 	const char *kProcPath = "/proc";
@@ -541,6 +697,9 @@
 	if (j->flags.vfs && unshare(CLONE_NEWNS))
 		pdie("unshare");
 
+	if (j->flags.chroot && enter_chroot(j))
+		pdie("chroot");
+
 	if (j->flags.readonly && remount_readonly())
 		pdie("remount");
 
@@ -807,6 +966,14 @@
 		free(f);
 		f = next;
 	}
+	while (j->bindings_head) {
+		struct binding *b = j->bindings_head;
+		j->bindings_head = j->bindings_head->next;
+		free(b->dest);
+		free(b->src);
+		free(b);
+	}
+	j->bindings_tail = NULL;
 	if (j->user)
 		free(j->user);
 	free(j);