minijail: Support pivot_root

Add an option that allows user to use pivot_root(2) when one want to
jail process in a chrooted environment. This implies entering a new
mount namespace since pivot_root(2) will really move the root
filesystem.

BUG=chromium:517844
TEST=security_Minijail0 passes

Change-Id: Ie990670703b00e333fa4abc3804d6384d36fa7c9
Reviewed-on: https://chromium-review.googlesource.com/293128
Commit-Ready: Yu-hsi Chiang <yuhsi@google.com>
Tested-by: Yu-hsi Chiang <yuhsi@google.com>
Reviewed-by: Jorge Lucangeli Obes <jorgelo@chromium.org>
diff --git a/libminijail.c b/libminijail.c
index 5378d84..8e05094 100644
--- a/libminijail.c
+++ b/libminijail.c
@@ -95,6 +95,7 @@
 		int seccomp_filter:1;
 		int log_seccomp_filter:1;
 		int chroot:1;
+		int pivot_root:1;
 		int mount_tmp:1;
 		int do_init:1;
 		int pid_file:1;
@@ -362,6 +363,17 @@
 	return 0;
 }
 
+int API minijail_enter_pivot_root(struct minijail *j, const char *dir)
+{
+	if (j->chrootdir)
+		return -EINVAL;
+	j->chrootdir = strdup(dir);
+	if (!j->chrootdir)
+		return -ENOMEM;
+	j->flags.pivot_root = 1;
+	return 0;
+}
+
 void API minijail_mount_tmp(struct minijail *j)
 {
 	j->flags.mount_tmp = 1;
@@ -730,6 +742,36 @@
 	return 0;
 }
 
+int enter_pivot_root(const struct minijail *j)
+{
+	int ret;
+	if (j->bindings_head && (ret = bind_one(j, j->bindings_head)))
+		return ret;
+
+	/* To ensure chrootdir is the root of a file system, do a self bind mount. */
+	if (mount(j->chrootdir, j->chrootdir, "bind", MS_BIND | MS_REC, ""))
+		pdie("failed to bind mount '%s'", j->chrootdir);
+	if (chdir(j->chrootdir))
+		return -errno;
+	if (mkdir(".minijail_pivot", 0755))
+		pdie("mkdir(.minijail_pivot)");
+	if (syscall(SYS_pivot_root, ".", ".minijail_pivot")) {
+		remove(".minijail_pivot");
+		pdie("pivot_root");
+	}
+	/* The old root might be busy, so use lazy unmount. */
+	if (umount2(".minijail_pivot", MNT_DETACH))
+		pdie("umount(.minijail_pivot");
+	if (chdir("/"))
+		return -errno;
+	if (chroot("/"))
+		return -errno;
+	if (remove(".minijail_pivot"))
+		return -errno;
+
+	return 0;
+}
+
 int mount_tmp(void)
 {
 	return mount("none", "/tmp", "tmpfs", 0, "size=64M,mode=777");
@@ -927,6 +969,9 @@
 	if (j->flags.chroot && enter_chroot(j))
 		pdie("chroot");
 
+	if (j->flags.pivot_root && enter_pivot_root(j))
+		pdie("pivot_root");
+
 	if (j->flags.mount_tmp && mount_tmp())
 		pdie("mount_tmp");
 
diff --git a/libminijail.h b/libminijail.h
index 89abf6a..d3d1d37 100644
--- a/libminijail.h
+++ b/libminijail.h
@@ -75,6 +75,7 @@
  * Returns 0 on success.
  */
 int minijail_enter_chroot(struct minijail *j, const char *dir);
+int minijail_enter_pivot_root(struct minijail *j, const char *dir);
 
 /* minijail_mount_tmp: enables mounting of a tmpfs filesystem on /tmp.
  * As be rules of bind mounts, /tmp must exist in chroot.
diff --git a/minijail0.c b/minijail0.c
index d199fab..ce8e058 100644
--- a/minijail0.c
+++ b/minijail0.c
@@ -85,6 +85,7 @@
 	       "instances allowed\n"
 	       "  -c <caps>:  restrict caps to <caps>\n"
 	       "  -C <dir>:   chroot to <dir>\n"
+	       "              Not compatible with -P\n"
 	       "  -e:         enter new network namespace\n"
 	       "  -f <file>:  write the pid of the jailed process to <file>\n"
 	       "  -G:         inherit secondary groups from uid\n"
@@ -109,6 +110,8 @@
 	       "              Not compatible with -b without writable\n"
 	       "  -n:         set no_new_privs\n"
 	       "  -p:         enter new pid namespace (implies -vr)\n"
+	       "  -P <dir>:   pivot_root to <dir> (implies -v)\n"
+	       "              Not compatible with -C\n"
 	       "  -r:         remount /proc read-only (implies -v)\n"
 	       "  -s:         use seccomp\n"
 	       "  -S <file>:  set seccomp filter using <file>\n"
@@ -136,11 +139,12 @@
 {
 	int opt;
 	int use_seccomp_filter = 0;
+	int pivot_root = 0, chroot = 0;
 	const size_t path_max = 4096;
 	const char *filter_path;
 	if (argc > 1 && argv[1][0] != '-')
 		return 1;
-	while ((opt = getopt(argc, argv, "u:g:sS:c:C:b:V:f:m:M:vrGhHinpLetIU")) != -1) {
+	while ((opt = getopt(argc, argv, "u:g:sS:c:C:P:b:V:f:m:M:vrGhHinpLetIU")) != -1) {
 		switch (opt) {
 		case 'u':
 			set_user(j, optarg);
@@ -179,10 +183,29 @@
 			use_caps(j, optarg);
 			break;
 		case 'C':
+			if (pivot_root) {
+				fprintf(stderr, "Could not set chroot because "
+				                "'-P' was specified.\n");
+				exit(1);
+			}
 			if (0 != minijail_enter_chroot(j, optarg)) {
 				fprintf(stderr, "Could not set chroot.\n");
 				exit(1);
 			}
+			chroot = 1;
+			break;
+		case 'P':
+			if (chroot) {
+				fprintf(stderr, "Could not set pivot_root because "
+				                "'-C' was specified.\n");
+				exit(1);
+			}
+			if (0 != minijail_enter_pivot_root(j, optarg)) {
+				fprintf(stderr, "Could not set pivot_root.\n");
+				exit(1);
+			}
+			minijail_namespace_vfs(j);
+			pivot_root = 1;
 			break;
 		case 'f':
 			if (0 != minijail_write_pid_file(j, optarg)) {