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;