minijail: Check correct executable file

When the chroot and pivot_root options are used the path to the binary
to put in jail is given relative to the new root.  However the checks
for the program existing and how it is linked were still done relative
the original rootfs.  This "worked" as long as there was a similar file
outside of the chroot.  Add the ability to get the full path of the
program from libminijail and use that path to check the file.

This allows chrooting to a system that has init in / instead of /sbin.

Don't try to check the binary if there are bind mounts specified.  This
avoids having to parse the mounts and check if the binary is in a bind
mounted path.

Change-Id: I2e3af14f5e8fd478963bcb56a3a6ae5908e78524
Signed-off-by: Dylan Reid <dgreid@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/300320
Reviewed-by: Jorge Lucangeli Obes <jorgelo@chromium.org>
diff --git a/libminijail.c b/libminijail.c
index b934c34..5ccb9c1 100644
--- a/libminijail.c
+++ b/libminijail.c
@@ -375,6 +375,22 @@
 	return 0;
 }
 
+char *minijail_get_original_path(struct minijail *j, const char *chroot_path)
+{
+	char *external_path;
+	size_t pathlen;
+
+	if (!j->chrootdir)
+		return strdup(chroot_path);
+
+	/* One extra char for '/' and one for '\0', hence + 2. */
+	pathlen = strlen(chroot_path) + strlen(j->chrootdir) + 2;
+	external_path = malloc(pathlen);
+	snprintf(external_path, pathlen, "%s/%s", j->chrootdir, chroot_path);
+
+	return external_path;
+}
+
 void API minijail_mount_tmp(struct minijail *j)
 {
 	j->flags.mount_tmp = 1;
@@ -431,6 +447,11 @@
 	return -ENOMEM;
 }
 
+int API minijail_has_bind_mounts(const struct minijail *j)
+{
+	return j->bindings_head != NULL;
+}
+
 void API minijail_parse_seccomp_filters(struct minijail *j, const char *path)
 {
 	if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, NULL)) {
diff --git a/libminijail.h b/libminijail.h
index 07f0762..d6c3c42 100644
--- a/libminijail.h
+++ b/libminijail.h
@@ -77,6 +77,18 @@
 int minijail_enter_chroot(struct minijail *j, const char *dir);
 int minijail_enter_pivot_root(struct minijail *j, const char *dir);
 
+/* minijail_get_original_path: returns the path of a given file outside of the
+ * chroot.
+ * @j           minijail to obtain the path from.
+ * @chroot_path path inside of the chroot() to.
+ *
+ * When executing a binary in a chroot or pivot_root, return path to the binary
+ * outside of the chroot.
+ *
+ * Returns a string containing the path.  This must be freed by the caller.
+ */
+char *minijail_get_original_path(struct minijail *j, const char *chroot_path);
+
 /* minijail_mount_tmp: enables mounting of a tmpfs filesystem on /tmp.
  * As be rules of bind mounts, /tmp must exist in chroot.
  */
@@ -94,6 +106,11 @@
 int minijail_bind(struct minijail *j, const char *src, const char *dest,
 		  int writeable);
 
+/* minijail_has_bind_mounts: Checks if there are any bind mounts configured.
+ * @j         minijail to check
+ */
+int minijail_has_bind_mounts(const struct minijail *j);
+
 /* Lock this process into the given minijail. Note that this procedure cannot fail,
  * since there is no way to undo privilege-dropping; therefore, if any part of
  * the privilege-drop fails, minijail_enter() will abort the entire process.
diff --git a/minijail0.c b/minijail0.c
index 2ded926..8329080 100644
--- a/minijail0.c
+++ b/minijail0.c
@@ -295,20 +295,33 @@
 	struct minijail *j = minijail_new();
 	char *dl_mesg = NULL;
 	int exit_immediately = 0;
+	char *program_path;
 	int consumed = parse_args(j, argc, argv, &exit_immediately);
 	ElfType elftype = ELFERROR;
 	argc -= consumed;
 	argv += consumed;
 
+	/* Get the path to the program adjusted for changing root. */
+	program_path = minijail_get_original_path(j, argv[0]);
+
 	/* Check that we can access the target program. */
-	if (access(argv[0], X_OK)) {
+	if (!minijail_has_bind_mounts(j) && access(program_path, X_OK)) {
 		fprintf(stderr, "Target program '%s' is not accessible.\n",
 			argv[0]);
 		return 1;
 	}
 
 	/* Check if target is statically or dynamically linked. */
-	elftype = get_elf_linkage(argv[0]);
+	if (minijail_has_bind_mounts(j)) {
+		/* We can't tell what the internal path to the binary is so
+		 * assume it's dynamically linked.
+		 */
+		elftype = ELFDYNAMIC;
+		warn("assuming program '%s' is dynamically linked\n", argv[0]);
+	} else {
+		elftype = get_elf_linkage(program_path);
+	}
+
 	if (elftype == ELFSTATIC) {
 		/*
 		 * Target binary is statically linked so we cannot use
@@ -335,6 +348,8 @@
 		return 1;
 	}
 
+	free(program_path);
+
 	if (exit_immediately) {
 		info("not running init loop, exiting immediately");
 		return 0;