| Index: elf_loader.cc
|
| diff --git a/elf_loader.cc b/elf_loader.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..24f7db47ccd578a195a7abfa1bd131d6786c18cd
|
| --- /dev/null
|
| +++ b/elf_loader.cc
|
| @@ -0,0 +1,215 @@
|
| +// Copyright (c) 2011 The Chromium 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 <elf.h>
|
| +#include <fcntl.h>
|
| +#include <stdio.h>
|
| +#include <string.h>
|
| +#include <sys/mman.h>
|
| +#include <unistd.h>
|
| +
|
| +#include "sandbox_impl.h"
|
| +#include "syscall_entrypoint.h"
|
| +#include "tls_setup.h"
|
| +
|
| +
|
| +#if defined(__x86_64__)
|
| +# define ElfW(name) Elf64_##name
|
| +# define ElfW_ELFCLASS ELFCLASS64
|
| +# define ElfW_EXPECTED_MACHINE EM_X86_64
|
| +#elif defined(__i386__)
|
| +# define ElfW(name) Elf32_##name
|
| +# define ElfW_ELFCLASS ELFCLASS32
|
| +# define ElfW_EXPECTED_MACHINE EM_386
|
| +#else
|
| +# error Unsupported target platform
|
| +#endif
|
| +
|
| +
|
| +static uintptr_t PageSizeRoundDown(uintptr_t val) {
|
| + return val & ~(getpagesize() - 1);
|
| +}
|
| +
|
| +static uintptr_t PageSizeRoundUp(uintptr_t val) {
|
| + return PageSizeRoundDown(val + getpagesize() - 1);
|
| +}
|
| +
|
| +static ElfW(auxv_t) *FindAuxv(int argc, char **argv) {
|
| + char **ptr = argv + argc + 1;
|
| + // Skip over envp.
|
| + while (*ptr != NULL) {
|
| + ptr++;
|
| + }
|
| + ptr++;
|
| + return (ElfW(auxv_t) *) ptr;
|
| +}
|
| +
|
| +static void SetAuxvField(ElfW(auxv_t) *auxv, unsigned type, uintptr_t value) {
|
| + for (; auxv->a_type != AT_NULL; auxv++) {
|
| + if (auxv->a_type == type) {
|
| + auxv->a_un.a_val = value;
|
| + return;
|
| + }
|
| + }
|
| +}
|
| +
|
| +static void JumpToElfEntryPoint(void *stack, void *entry_point,
|
| + void *atexit_func) {
|
| +#if defined(__x86_64__)
|
| + asm("mov %0, %%rsp\n"
|
| + "jmp *%1\n"
|
| + // %edx is registered as an atexit handler if non-zero.
|
| + : : "r"(stack), "r"(entry_point), "d"(atexit_func));
|
| +#elif defined(__i386__)
|
| + asm("mov %0, %%esp\n"
|
| + "jmp *%1\n"
|
| + // %rdx is registered as an atexit handler if non-zero.
|
| + : : "r"(stack), "r"(entry_point), "d"(atexit_func));
|
| +#else
|
| +# error Unsupported target platform
|
| +#endif
|
| +}
|
| +
|
| +static void *LoadElfObject(int fd, ElfW(auxv_t) *auxv) {
|
| + // Load and check headers.
|
| + ElfW(Ehdr) elf_header;
|
| + if (pread(fd, &elf_header, sizeof(elf_header), 0) != sizeof(elf_header)) {
|
| + Sandbox::die("Failed to read ELF header");
|
| + }
|
| + if (memcmp(elf_header.e_ident, ELFMAG, SELFMAG) != 0) {
|
| + Sandbox::die("Not an ELF file");
|
| + }
|
| + if (elf_header.e_ident[EI_CLASS] != ElfW_ELFCLASS) {
|
| + Sandbox::die("Unexpected ELFCLASS");
|
| + }
|
| + if (elf_header.e_machine != ElfW_EXPECTED_MACHINE) {
|
| + Sandbox::die("Unexpected ELF machine type");
|
| + }
|
| + if (elf_header.e_phentsize != sizeof(ElfW(Phdr))) {
|
| + Sandbox::die("Unexpected ELF program header entry size");
|
| + }
|
| + if (elf_header.e_phnum >= 20) {
|
| + // We impose an arbitrary limit as a sanity check and to avoid
|
| + // overflowing the stack.
|
| + Sandbox::die("Too many ELF program headers");
|
| + }
|
| + ElfW(Phdr) phdrs[elf_header.e_phnum];
|
| + if (pread(fd, phdrs, sizeof(phdrs), elf_header.e_phoff)
|
| + != (ssize_t) sizeof(phdrs)) {
|
| + Sandbox::die("Failed to read ELF program headers");
|
| + }
|
| +
|
| + // Scan program headers to find the overall size of the ELF object.
|
| + // Find the first and last PT_LOAD segments. ELF requires that
|
| + // PT_LOAD segments be in ascending order of p_vaddr, so we can use
|
| + // the last one to calculate the whole address span of the image.
|
| + size_t index = 0;
|
| + while (index < elf_header.e_phnum && phdrs[index].p_type != PT_LOAD) {
|
| + index++;
|
| + }
|
| + if (index == elf_header.e_phnum) {
|
| + Sandbox::die("ELF object contains no PT_LOAD headers");
|
| + }
|
| + ElfW(Phdr) *first_segment = &phdrs[index];
|
| + ElfW(Phdr) *last_segment = &phdrs[elf_header.e_phnum - 1];
|
| + while (last_segment > first_segment && last_segment->p_type != PT_LOAD) {
|
| + last_segment--;
|
| + }
|
| + uintptr_t overall_start = PageSizeRoundDown(first_segment->p_vaddr);
|
| + uintptr_t overall_end = PageSizeRoundUp(last_segment->p_vaddr
|
| + + last_segment->p_memsz);
|
| + uintptr_t overall_size = overall_end - overall_start;
|
| +
|
| + // Reserve address space.
|
| + // Executables that must be loaded at a fixed address have an e_type
|
| + // of ET_EXEC. For these, we could use MAP_FIXED, but if the
|
| + // address range is already occupied then that will clobber the
|
| + // existing mappings without warning, which is bad. Instead, use an
|
| + // address hint and check that we got the expected address.
|
| + // Executables that can be loaded at any address have an e_type of
|
| + // ET_DYN.
|
| + char *required_start =
|
| + elf_header.e_type == ET_EXEC ? (char *) overall_start : NULL;
|
| + char *base_addr = (char *) mmap(required_start, overall_size, PROT_NONE,
|
| + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
| + if (base_addr == MAP_FAILED) {
|
| + Sandbox::die("Failed to reserve address space");
|
| + }
|
| + if (elf_header.e_type == ET_EXEC && base_addr != required_start) {
|
| + Sandbox::die("Failed to reserve address space at fixed address");
|
| + }
|
| +
|
| + char *load_offset = (char *) (base_addr - required_start);
|
| + char *entry_point = load_offset + elf_header.e_entry;
|
| + SetAuxvField(auxv, AT_ENTRY, (uintptr_t) entry_point);
|
| + SetAuxvField(auxv, AT_BASE, (uintptr_t) load_offset);
|
| + SetAuxvField(auxv, AT_PHNUM, elf_header.e_phnum);
|
| + SetAuxvField(auxv, AT_PHENT, elf_header.e_phentsize);
|
| + // Note that this assumes that the program headers are included in a
|
| + // PT_LOAD segment for which the file offsets matches the mapping
|
| + // offset, but Linux assumes this too when setting AT_PHDR.
|
| + SetAuxvField(auxv, AT_PHDR, (uintptr_t) base_addr + elf_header.e_phoff);
|
| +
|
| + for (ElfW(Phdr) *segment = first_segment;
|
| + segment <= last_segment;
|
| + segment++) {
|
| + if (segment->p_type == PT_LOAD) {
|
| + uintptr_t segment_start = PageSizeRoundDown(segment->p_vaddr);
|
| + uintptr_t segment_end = PageSizeRoundUp(segment->p_vaddr
|
| + + segment->p_memsz);
|
| + int prot = 0;
|
| + if ((segment->p_flags & PF_R) != 0)
|
| + prot |= PROT_READ;
|
| + if ((segment->p_flags & PF_W) != 0)
|
| + prot |= PROT_WRITE;
|
| + if ((segment->p_flags & PF_X) != 0)
|
| + prot |= PROT_EXEC;
|
| + void *result = mmap(load_offset + segment_start,
|
| + segment_end - segment_start,
|
| + prot, MAP_PRIVATE | MAP_FIXED, fd,
|
| + PageSizeRoundDown(segment->p_offset));
|
| + if (result == MAP_FAILED) {
|
| + Sandbox::die("Failed to map ELF segment");
|
| + }
|
| + // TODO(mseaborn): Support a BSS that goes beyond the file's extent.
|
| + if ((segment->p_flags & PF_W) != 0) {
|
| + // Zero the BSS to the end of the page. ld.so and other
|
| + // programs use the rest of this page as part of the brk()
|
| + // heap and assume that it has been zeroed.
|
| + uintptr_t bss_start = segment->p_vaddr + segment->p_filesz;
|
| + memset(load_offset + bss_start, 0, segment_end - bss_start);
|
| + }
|
| + }
|
| + }
|
| + if (close(fd) != 0) {
|
| + Sandbox::die("close() failed");
|
| + }
|
| + return entry_point;
|
| +}
|
| +
|
| +int main(int argc, char **argv) {
|
| + if (argc < 2) {
|
| + fprintf(stderr, "Usage: %s executable args...\n", argv[0]);
|
| + return 1;
|
| + }
|
| +
|
| + const char *executable_filename = argv[1];
|
| + int executable_fd = open(executable_filename, O_RDONLY);
|
| + if (executable_fd < 0) {
|
| + fprintf(stderr, "Failed to open executable %s: %s\n",
|
| + executable_filename, strerror(errno));
|
| + return 1;
|
| + }
|
| +
|
| + playground::g_policy.allow_file_namespace = true;
|
| + playground::AddTlsSetupSyscall();
|
| + StartSeccompSandbox();
|
| +
|
| + ElfW(auxv_t) *auxv = FindAuxv(argc, argv);
|
| + SetAuxvField(auxv, AT_SYSINFO, (uintptr_t) syscallEntryPointNoFrame);
|
| + char **stack = argv;
|
| + *(long *) stack = argc - 1;
|
| + void *entry_point = LoadElfObject(executable_fd, auxv);
|
| + JumpToElfEntryPoint(stack, entry_point, 0);
|
| +}
|
|
|