Allow minijail to run statically linked targets

minijail will now detect static targets and sandbox them

BUG:chromium:355109
TEST=Tested with autotest security_Minijail0 on arm and x64

Change-Id: I4c38f652207c5c50158449f952b14e9402e17751
Reviewed-on: https://chromium-review.googlesource.com/203013
Reviewed-by: Jorge Lucangeli Obes <jorgelo@chromium.org>
Tested-by: Lee Campbell <leecam@chromium.org>
Commit-Queue: Lee Campbell <leecam@chromium.org>
diff --git a/Makefile b/Makefile
index be4f8e4..204e64b 100644
--- a/Makefile
+++ b/Makefile
@@ -17,7 +17,7 @@
 tests : libminijail_unittest.wrapper syscall_filter_unittest
 
 minijail0 : libsyscalls.gen.o libminijail.o syscall_filter.o \
-		signal.o bpf.o util.o minijail0.c
+		signal.o bpf.o util.o elfparse.o minijail0.c
 	$(CC) $(CFLAGS) -o $@ $^ -lcap -ldl
 
 libminijail.so : libminijail.o syscall_filter.o signal.o bpf.o util.o \
@@ -63,6 +63,8 @@
 
 util.o : util.c util.h
 
+elfparse.o : elfparse.c elfparse.h
+
 # Only regenerate libsyscalls.gen.c if the Makefile or header changes.
 # NOTE! This will not detect if the file is not appropriate for the target.
 libsyscalls.gen.c : Makefile libsyscalls.h
diff --git a/elfparse.c b/elfparse.c
new file mode 100644
index 0000000..5dd919d
--- /dev/null
+++ b/elfparse.c
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "elfparse.h"
+
+int is_elf_magic (const uint8_t *buf)
+{
+	return (buf[EI_MAG0] == ELFMAG0) &&
+	       (buf[EI_MAG1] == ELFMAG1) &&
+	       (buf[EI_MAG2] == ELFMAG2) &&
+	       (buf[EI_MAG3] == ELFMAG3);
+}
+
+#define parseElftemplate(bit)                                                \
+ElfType parseElf ## bit(FILE *elf_file, uint8_t *pHead, int little_endian)   \
+{                                                                            \
+	ElfType                      ret          = ELFSTATIC;               \
+	Minijail_Elf ## bit ## _Ehdr *pHeader     = NULL;                    \
+	Minijail_Elf ## bit ## _Phdr pheader      = { 0 };                   \
+	uint32_t                     i            = 0;                       \
+	                                                                     \
+	if (!elf_file || !pHead)                                             \
+		return ELFERROR;                                             \
+	                                                                     \
+	pHeader = (Minijail_Elf ## bit ## _Ehdr *)pHead;                     \
+	if (little_endian) {                                                 \
+		pHeader->e_phoff = le ## bit ## toh(pHeader->e_phoff);       \
+		pHeader->e_phentsize = le16toh(pHeader->e_phentsize);        \
+		pHeader->e_phnum = le16toh(pHeader->e_phnum);                \
+	} else {                                                             \
+		pHeader->e_phoff = be ## bit ## toh(pHeader->e_phoff);       \
+		pHeader->e_phentsize = be16toh(pHeader->e_phentsize);        \
+		pHeader->e_phnum = be16toh(pHeader->e_phnum);                \
+	}                                                                    \
+	if (pHeader->e_phentsize != sizeof(Minijail_Elf ## bit ## _Phdr))    \
+		return ELFERROR;                                             \
+	                                                                     \
+	if (fseek(elf_file, pHeader->e_phoff, SEEK_SET) != 0)                \
+		return ELFERROR;                                             \
+	                                                                     \
+	for (i = 0; i < pHeader->e_phnum; i++) {                             \
+		if (fread(&pheader, sizeof(pheader), 1, elf_file) == 1) {    \
+			if (pheader.p_type == PT_INTERP) {                   \
+				ret = ELFDYNAMIC;                            \
+				break;                                       \
+			}                                                    \
+		} else {                                                     \
+			ret = ELFERROR;                                      \
+			break;                                               \
+		}                                                            \
+	}                                                                    \
+	return ret;                                                          \
+}
+parseElftemplate(64)
+parseElftemplate(32)
+
+/* Public function to determine the linkage of an ELF. */
+ElfType get_elf_linkage(const char *path)
+{
+	ElfType ret = ELFERROR;
+	FILE *elf_file = NULL;
+	uint8_t pHeader[HEADERSIZE] = "";
+
+	elf_file = fopen(path, "r");
+	if (elf_file) {
+		if (fread(pHeader, 1, HEADERSIZE, elf_file) == HEADERSIZE) {
+			if (is_elf_magic(pHeader)) {
+				if ((pHeader[EI_DATA] == ELFDATA2LSB) &&
+				    (pHeader[EI_CLASS] == ELFCLASS64)) {
+					/* 64 bit little endian */
+					ret = parseElf64(elf_file, pHeader, 1);
+				} else if ((pHeader[EI_DATA] == ELFDATA2MSB) &&
+					  (pHeader[EI_CLASS] == ELFCLASS64)) {
+					/* 64 bit big endian */
+					ret = parseElf64(elf_file, pHeader, 0);
+				} else if ((pHeader[EI_DATA] == ELFDATA2LSB) &&
+					  (pHeader[EI_CLASS] == ELFCLASS32)) {
+					/* 32 bit little endian */
+					ret = parseElf32(elf_file, pHeader, 1);
+				} else if ((pHeader[EI_DATA] == ELFDATA2MSB) &&
+					  (pHeader[EI_CLASS] == ELFCLASS32)) {
+					/* 32 bit big endian */
+					ret = parseElf32(elf_file, pHeader, 0);
+				}
+			} else {
+				/*
+				 * The binary is not an ELF. We assume it's a
+				 * script. We should parse the #! line and
+				 * check the interpreter to guard against
+				 * static interpreters escaping the sandbox.
+				 * As minijail is only called from rootfs
+				 * it was deemed not necessary to check this.
+				 * So we will just let execve decided if this
+				 * is valid.
+				 */
+				ret = ELFDYNAMIC;
+			}
+		} else {
+			ret = ELFDYNAMIC;
+		}
+		fclose(elf_file);
+	}
+	return ret;
+}
diff --git a/elfparse.h b/elfparse.h
new file mode 100644
index 0000000..e7fe76a
--- /dev/null
+++ b/elfparse.h
@@ -0,0 +1,109 @@
+/* elfparse.h
+ * Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * Elf parsing.
+ */
+
+#ifndef _ELFPARSE_H_
+#define _ELFPARSE_H_
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <elf.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <endian.h>
+#include <string.h>
+
+/*
+ * These structs come from elf.h
+ * The version in elf.h do not pack these structs so
+ * portability could be an issue.
+ * The compiler could mess with aligmment depending on arch
+ * so I'm redefining them here and packing them to 1-byte alignment.
+ */
+#define EI_NIDENT (16)
+#pragma pack(push)
+#pragma pack(1)
+typedef struct
+{
+	unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
+	Elf32_Half    e_type;             /* Object file type */
+	Elf32_Half    e_machine;          /* Architecture */
+	Elf32_Word    e_version;          /* Object file version */
+	Elf32_Addr    e_entry;            /* Entry point virtual address */
+	Elf32_Off     e_phoff;            /* Program header table file offset */
+	Elf32_Off     e_shoff;            /* Section header table file offset */
+	Elf32_Word    e_flags;            /* Processor-specific flags */
+	Elf32_Half    e_ehsize;           /* ELF header size in bytes */
+	Elf32_Half    e_phentsize;        /* Program header table entry size */
+	Elf32_Half    e_phnum;            /* Program header table entry count */
+	Elf32_Half    e_shentsize;        /* Section header table entry size */
+	Elf32_Half    e_shnum;            /* Section header table entry count */
+	Elf32_Half    e_shstrndx;         /* Section header string table index */
+} Minijail_Elf32_Ehdr;
+
+typedef struct
+{
+	unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
+	Elf64_Half    e_type;             /* Object file type */
+	Elf64_Half    e_machine;          /* Architecture */
+	Elf64_Word    e_version;          /* Object file version */
+	Elf64_Addr    e_entry;            /* Entry point virtual address */
+	Elf64_Off     e_phoff;            /* Program header table file offset */
+	Elf64_Off     e_shoff;            /* Section header table file offset */
+	Elf64_Word    e_flags;            /* Processor-specific flags */
+	Elf64_Half    e_ehsize;           /* ELF header size in bytes */
+	Elf64_Half    e_phentsize;        /* Program header table entry size */
+	Elf64_Half    e_phnum;            /* Program header table entry count */
+	Elf64_Half    e_shentsize;        /* Section header table entry size */
+	Elf64_Half    e_shnum;            /* Section header table entry count */
+	Elf64_Half    e_shstrndx;         /* Section header string table index */
+} Minijail_Elf64_Ehdr;
+
+typedef struct
+{
+	Elf32_Word      p_type;           /* Segment type */
+	Elf32_Off       p_offset;         /* Segment file offset */
+	Elf32_Addr      p_vaddr;          /* Segment virtual address */
+	Elf32_Addr      p_paddr;          /* Segment physical address */
+	Elf32_Word      p_filesz;         /* Segment size in file */
+	Elf32_Word      p_memsz;          /* Segment size in memory */
+	Elf32_Word      p_flags;          /* Segment flags */
+	Elf32_Word      p_align;          /* Segment alignment */
+} Minijail_Elf32_Phdr;
+
+typedef struct
+{
+	Elf64_Word      p_type;           /* Segment type */
+	Elf64_Word      p_flags;          /* Segment flags */
+	Elf64_Off       p_offset;         /* Segment file offset */
+	Elf64_Addr      p_vaddr;          /* Segment virtual address */
+	Elf64_Addr      p_paddr;          /* Segment physical address */
+	Elf64_Xword     p_filesz;         /* Segment size in file */
+	Elf64_Xword     p_memsz;          /* Segment size in memory */
+	Elf64_Xword     p_align;          /* Segment alignment */
+} Minijail_Elf64_Phdr;
+#pragma pack(pop)
+/* End of definitions from elf.h */
+
+
+#define ELFERROR    0
+#define ELFSTATIC   1
+#define ELFDYNAMIC  2
+
+/*
+ * This is the inital amout of the elf we try and read.
+ * It is the same value that the kernel uses (BINPRM_BUF_SIZE).
+ */
+#define HEADERSIZE  128
+
+typedef int ElfType;
+
+ElfType get_elf_linkage(const char *path);
+
+
+
+#endif /* _ELFPARSE_H_ */
\ No newline at end of file
diff --git a/libminijail.c b/libminijail.c
index 90fd4a7..d4b9d16 100644
--- a/libminijail.c
+++ b/libminijail.c
@@ -1076,7 +1076,7 @@
 
 	if (child_pid < 0) {
 		free(oldenv_copy);
-		return child_pid;
+		die("failed to fork child");
 	}
 
 	if (child_pid) {
@@ -1195,6 +1195,56 @@
 	_exit(execve(filename, argv, environ));
 }
 
+int API minijail_run_static(struct minijail *j, const char *filename,
+			    char *const argv[])
+{
+	pid_t child_pid;
+	int pid_namespace = j->flags.pids;
+
+	if (j->flags.caps)
+		die("caps not supported with static targets");
+
+	if (pid_namespace)
+		child_pid = syscall(SYS_clone, CLONE_NEWPID | SIGCHLD, NULL);
+	else
+		child_pid = fork();
+
+	if (child_pid < 0) {
+		die("failed to fork child");
+	}
+	if (child_pid > 0 ) {
+		j->initpid = child_pid;
+		return 0;
+	}
+
+	/*
+	 * We can now drop this child into the sandbox
+	 * then execve the target.
+	 */
+
+	j->flags.pids = 0;
+	minijail_enter(j);
+
+	if (pid_namespace) {
+		/*
+		 * pid namespace: this process will become init inside the new
+		 * namespace, so fork off a child to actually run the program
+		 * (we don't want all programs we might exec to have to know
+		 * how to be init).
+		 *
+		 * If we're multithreaded, we'll probably deadlock here. See
+		 * WARNING above.
+		 */
+		child_pid = fork();
+		if (child_pid < 0)
+			_exit(child_pid);
+		else if (child_pid > 0)
+			init(child_pid);	/* never returns */
+	}
+
+	_exit(execve(filename, argv, environ));
+}
+
 int API minijail_kill(struct minijail *j)
 {
 	int st;
diff --git a/libminijail.h b/libminijail.h
index 211b6fc..67442bd 100644
--- a/libminijail.h
+++ b/libminijail.h
@@ -103,6 +103,12 @@
 		 char *const argv[]);
 
 /* Run the specified command in the given minijail, execve(3)-style.
+ * Used with static binaries.
+ */
+int minijail_run_static(struct minijail *j, const char *filename,
+			char *const argv[]);
+
+/* Run the specified command in the given minijail, execve(3)-style.
  * Update |*pchild_pid| with the pid of the child.
  */
 int minijail_run_pid(struct minijail *j, const char *filename,
diff --git a/minijail0.c b/minijail0.c
index 82f67c1..4987001 100644
--- a/minijail0.c
+++ b/minijail0.c
@@ -12,6 +12,7 @@
 #include "libminijail.h"
 #include "libsyscalls.h"
 
+#include "elfparse.h"
 #include "util.h"
 
 static void set_user(struct minijail *j, const char *arg)
@@ -224,6 +225,7 @@
 	char *dl_mesg = NULL;
 	int exit_immediately = 0;
 	int consumed = parse_args(j, argc, argv, &exit_immediately);
+	ElfType elftype = ELFERROR;
 	argc -= consumed;
 	argv += consumed;
 	/* Check that we can access the target program. */
@@ -232,13 +234,30 @@
 		        argv[0]);
 		return 1;
 	}
-	/* Check that we can dlopen() libminijailpreload.so. */
-	if (!dlopen(PRELOADPATH, RTLD_LAZY | RTLD_LOCAL)) {
-		dl_mesg = dlerror();
-		fprintf(stderr, "dlopen(): %s\n", dl_mesg);
+	/* Check if target is statically or dynamically linked. */
+	elftype = get_elf_linkage(argv[0]);
+	if (elftype == ELFSTATIC) {
+		/* Target binary is static. */
+		minijail_run_static(j, argv[0], argv);
+	} else if (elftype == ELFDYNAMIC) {
+		/*
+		 * Target binary is dynamically linked so we can
+		 * inject libminijailpreload.so into it.
+		 */
+
+		/* Check that we can dlopen() libminijailpreload.so. */
+		if (!dlopen(PRELOADPATH, RTLD_LAZY | RTLD_LOCAL)) {
+			    dl_mesg = dlerror();
+			    fprintf(stderr, "dlopen(): %s\n", dl_mesg);
+			    return 1;
+		}
+		minijail_run(j, argv[0], argv);
+	} else {
+		fprintf(stderr, "Target program '%s' is not an ELF executable.\n",
+		        argv[0]);
 		return 1;
 	}
-	minijail_run(j, argv[0], argv);
+
 	if (exit_immediately) {
 		info("not running init loop, exiting immediately");
 		return 0;