Allow mount data to be specified

Add an API, minijail_mount_with_data, that allows the mount data string
to be set.  This is needed for some mounts when entering a user
namespace and specifying uid=, gid=, or similar mount options.

BUG=b/27273730
TEST=mount proc with hidepid=2 check mount output to confirm
     security_Minijail0 test case added.

Signed-off-by: Dylan Reid <dgreid@chromium.org>
(cherry picked from commit 81e2397c51787ed8682b08e9c732f53cc668401f)

Change-Id: Iee10ead22c7d043afc3955496f7d4c470e31dfa0
Reviewed-on: https://chromium-review.googlesource.com/360259
Commit-Ready: Dylan Reid <dgreid@chromium.org>
Tested-by: Dylan Reid <dgreid@chromium.org>
Reviewed-by: Stephen Barber <smbarber@chromium.org>
diff --git a/libminijail.c b/libminijail.c
index 3a51942..b300daf 100644
--- a/libminijail.c
+++ b/libminijail.c
@@ -99,6 +99,8 @@
 	char *src;
 	char *dest;
 	char *type;
+	char *data;
+	int has_data;
 	unsigned long flags;
 	struct mountpoint *next;
 };
@@ -618,8 +620,9 @@
 	return 0;
 }
 
-int API minijail_mount(struct minijail *j, const char *src, const char *dest,
-		       const char *type, unsigned long flags)
+int API minijail_mount_with_data(struct minijail *j, const char *src,
+				 const char *dest, const char *type,
+				 unsigned long flags, const char *data)
 {
 	struct mountpoint *m;
 
@@ -637,6 +640,12 @@
 	m->type = strdup(type);
 	if (!m->type)
 		goto error;
+	if (data) {
+		m->data = strdup(data);
+		if (!m->data)
+			goto error;
+		m->has_data = 1;
+	}
 	m->flags = flags;
 
 	info("mount %s -> %s type '%s'", src, dest, type);
@@ -657,12 +666,19 @@
 	return 0;
 
 error:
+	free(m->type);
 	free(m->src);
 	free(m->dest);
 	free(m);
 	return -ENOMEM;
 }
 
+int API minijail_mount(struct minijail *j, const char *src, const char *dest,
+		       const char *type, unsigned long flags)
+{
+	return minijail_mount_with_data(j, src, dest, type, flags, NULL);
+}
+
 int API minijail_bind(struct minijail *j, const char *src, const char *dest,
 		      int writeable)
 {
@@ -741,6 +757,18 @@
 	state->total += length;
 }
 
+static void minijail_marshal_mount(struct marshal_state *state,
+				   const struct mountpoint *m)
+{
+	marshal_append(state, m->src, strlen(m->src) + 1);
+	marshal_append(state, m->dest, strlen(m->dest) + 1);
+	marshal_append(state, m->type, strlen(m->type) + 1);
+	marshal_append(state, (char *)&m->has_data, sizeof(m->has_data));
+	if (m->has_data)
+		marshal_append(state, m->data, strlen(m->data) + 1);
+	marshal_append(state, (char *)&m->flags, sizeof(m->flags));
+}
+
 void minijail_marshal_helper(struct marshal_state *state,
 			     const struct minijail *j)
 {
@@ -766,10 +794,7 @@
 			       fp->len * sizeof(struct sock_filter));
 	}
 	for (m = j->mounts_head; m; m = m->next) {
-		marshal_append(state, m->src, strlen(m->src) + 1);
-		marshal_append(state, m->dest, strlen(m->dest) + 1);
-		marshal_append(state, m->type, strlen(m->type) + 1);
-		marshal_append(state, (char *)&m->flags, sizeof(m->flags));
+		minijail_marshal_mount(state, m);
 	}
 	for (i = 0; i < j->cgroup_count; ++i)
 		marshal_append(state, j->cgroups[i], strlen(j->cgroups[i]) + 1);
@@ -913,8 +938,10 @@
 	j->mounts_count = 0;
 	for (i = 0; i < count; ++i) {
 		unsigned long *flags;
+		int *has_data;
 		const char *dest;
 		const char *type;
+		const char *data = NULL;
 		const char *src = consumestr(&serialized, &length);
 		if (!src)
 			goto bad_mounts;
@@ -924,10 +951,19 @@
 		type = consumestr(&serialized, &length);
 		if (!type)
 			goto bad_mounts;
+		has_data = consumebytes(sizeof(*has_data), &serialized,
+					&length);
+		if (!has_data)
+			goto bad_mounts;
+		if (*has_data) {
+			data = consumestr(&serialized, &length);
+			if (!data)
+				goto bad_mounts;
+		}
 		flags = consumebytes(sizeof(*flags), &serialized, &length);
 		if (!flags)
 			goto bad_mounts;
-		if (minijail_mount(j, src, dest, type, *flags))
+		if (minijail_mount_with_data(j, src, dest, type, *flags, data))
 			goto bad_mounts;
 	}
 
@@ -949,6 +985,7 @@
 	while (j->mounts_head) {
 		struct mountpoint *m = j->mounts_head;
 		j->mounts_head = j->mounts_head->next;
+		free(m->data);
 		free(m->type);
 		free(m->dest);
 		free(m->src);
@@ -1076,14 +1113,14 @@
 		m->flags &= ~MS_RDONLY;
 	}
 
-	ret = mount(m->src, dest, m->type, m->flags, NULL);
+	ret = mount(m->src, dest, m->type, m->flags, m->data);
 	if (ret)
 		pdie("mount: %s -> %s", m->src, dest);
 
 	if (remount_ro) {
 		m->flags |= MS_RDONLY;
 		ret = mount(m->src, dest, NULL,
-			    m->flags | MS_REMOUNT, NULL);
+			    m->flags | MS_REMOUNT, m->data);
 		if (ret)
 			pdie("bind ro: %s -> %s", m->src, dest);
 	}
@@ -2116,6 +2153,7 @@
 	while (j->mounts_head) {
 		struct mountpoint *m = j->mounts_head;
 		j->mounts_head = j->mounts_head->next;
+		free(m->data);
 		free(m->type);
 		free(m->dest);
 		free(m->src);
diff --git a/libminijail.h b/libminijail.h
index c2532db..f7a2f1f 100644
--- a/libminijail.h
+++ b/libminijail.h
@@ -127,6 +127,23 @@
 void minijail_mount_tmp(struct minijail *j);
 
 /*
+ * minijail_mount_with_data: when entering minijail @j,
+ *   mounts @src at @dst with @flags and @data.
+ * @j         minijail to bind inside
+ * @src       source to bind
+ * @dest      location to bind (inside chroot)
+ * @type      type of filesystem
+ * @flags     flags passed to mount
+ * @data      data arguments passed to mount(2), e.g. "mode=755"
+ *
+ * This may be called multiple times; all mounts will be applied in the order
+ * of minijail_mount() calls.
+ */
+int minijail_mount_with_data(struct minijail *j, const char *src,
+			     const char *dest, const char *type,
+			     unsigned long flags, const char *data);
+
+/*
  * minijail_mount: when entering minijail @j, mounts @src at @dst with @flags
  * @j         minijail to bind inside
  * @src       source to bind
@@ -134,7 +151,7 @@
  * @type      type of filesystem
  * @flags     flags passed to mount
  *
- * This may be called multiple times; all bindings will be applied in the order
+ * This may be called multiple times; all mounts will be applied in the order
  * of minijail_mount() calls.
  */
 int minijail_mount(struct minijail *j, const char *src, const char *dest,
diff --git a/minijail0.c b/minijail0.c
index 50fd6f9..449cbe2 100644
--- a/minijail0.c
+++ b/minijail0.c
@@ -78,12 +78,13 @@
 	char *dest = strtok(NULL, ",");
 	char *type = strtok(NULL, ",");
 	char *flags = strtok(NULL, ",");
+	char *data = strtok(NULL, ",");
 	if (!src || !dest || !type) {
 		fprintf(stderr, "Bad mount: %s %s %s\n", src, dest, type);
 		exit(1);
 	}
-	if (minijail_mount(j, src, dest, type,
-			   flags ? strtoul(flags, NULL, 16) : 0)) {
+	if (minijail_mount_with_data(j, src, dest, type,
+			   flags ? strtoul(flags, NULL, 16) : 0, data)) {
 		fprintf(stderr, "minijail_mount failed.\n");
 		exit(1);
 	}
@@ -96,7 +97,7 @@
 	printf("Usage: %s [-GhiIlnprstUv]\n"
 	       "  [-b <src>,<dest>[,<writeable>]] [-f <file>]"
 	       " [-c <caps>] [-C <dir>] [-g <group>] [-u <user>]\n"
-	       "  [-S <file>] [-k <src>,<dest>,<type>[,<flags>]] [-T <type>]\n"
+	       "  [-S <file>] [-k <src>,<dest>,<type>[,<flags>][,<data>]] [-T <type>]\n"
 	       "  [-m \"<uid> <loweruid> <count>[,<uid> <loweruid> <count>]\"]\n"
 	       "  [-M \"<gid> <lowergid> <count>[,<uid> <loweruid> <count>]\"]\n"
 	       "  <program> [args...]\n"
@@ -104,7 +105,8 @@
 	       "  -b:         Bind <src> to <dest> in chroot.\n"
 	       "              Multiple instances allowed.\n"
 	       "  -k:         Mount <src> at <dest> in chroot.\n"
-	       "              Multiple instances allowed, flags are passed to mount(2).\n"
+	       "              <flags> and <data> can be specified as in mount(2).\n"
+	       "              Multiple instances allowed.\n"
 	       "  -c <caps>:  Restrict caps to <caps>.\n"
 	       "  -C <dir>:   chroot(2) to <dir>.\n"
 	       "              Not compatible with -P.\n"