Index: elf_loader.cc |
diff --git a/elf_loader.cc b/elf_loader.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..21109e5abf36cb10fa26af9c2d2dc81a4a5f67ee |
--- /dev/null |
+++ b/elf_loader.cc |
@@ -0,0 +1,190 @@ |
+// 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 |
+#elif defined(__i386__) |
+# define ElfW(name) Elf32_##name |
+# define ElfW_ELFCLASS ELFCLASS32 |
+#else |
+# error "Unsupported target platform" |
Markus (顧孟勤)
2011/08/15 22:05:23
I keep making this same mistake all the time. Ther
|
+#endif |
+ |
+ |
+uintptr_t PageSizeRoundDown(uintptr_t val) { |
Markus (顧孟勤)
2011/08/15 22:05:23
Are you deliberately not making these functions "s
|
+ return val & ~(getpagesize() - 1); |
Markus (顧孟勤)
2011/08/15 22:05:23
You can also write:
val & -getpagesize()
Not su
|
+} |
+ |
+uintptr_t PageSizeRoundUp(uintptr_t val) { |
+ return (val + getpagesize() - 1) & ~(getpagesize() - 1); |
Markus (顧孟勤)
2011/08/15 22:05:23
Maybe:
return PageSizeRoundDown(val + getpagesi
|
+} |
+ |
+static ElfW(auxv_t) *FindAuxv(int argc, char **argv) { |
+ char **ptr = argv + argc + 1; |
+ // Skip over envp. |
+ while (*ptr != NULL) |
+ ptr++; |
Markus (顧孟勤)
2011/08/15 22:05:23
I'd prefer "{ }" around this statement. But maybe,
|
+ 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; |
+ } |
+ } |
Markus (顧孟勤)
2011/08/15 22:05:23
Is it OK, if we fail to find the matching type? Sh
|
+} |
+ |
+static void JumpToElfEntryPoint(void *stack, void *entry_point, |
+ void *atexit_func) { |
Markus (顧孟勤)
2011/08/15 22:05:23
You might consider declaring this function as __at
|
+#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"); |
+ } |
+ ssize_t phdrs_size = elf_header.e_phentsize * elf_header.e_phnum; |
Markus (顧孟勤)
2011/08/15 22:05:23
Are we worried about malicious binaries overflowin
|
+ char *phdrs = (char *) alloca(phdrs_size); |
Markus (顧孟勤)
2011/08/15 22:05:23
You should check that the alloca() request succeed
|
+ if (pread(fd, phdrs, phdrs_size, elf_header.e_phoff) != phdrs_size) { |
+ Sandbox::die("Failed to read ELF program headers"); |
+ } |
+ |
+ // Scan program headers to find the overall size of the ELF object. |
+ size_t load_size = 0; |
+ char *required_start = 0; |
+ for (int index = 0; index < elf_header.e_phnum; index++) { |
+ ElfW(Phdr) *segment = |
+ (ElfW(Phdr) *) (phdrs + elf_header.e_phentsize * index); |
+ if (segment->p_type == PT_LOAD) { |
+ size_t segment_end = PageSizeRoundUp(segment->p_vaddr + segment->p_memsz); |
+ if (load_size < segment_end) { |
+ load_size = segment_end; |
+ } |
+ if (elf_header.e_type == ET_EXEC) { |
Markus (顧孟勤)
2011/08/15 22:05:23
I take it, we should see no or exactly one ET_EXEC
|
+ required_start = (char *) PageSizeRoundDown(segment->p_vaddr); |
+ } |
+ } |
+ } |
+ |
Markus (顧孟勤)
2011/08/15 22:05:23
You might want to add a sanity check that "load_si
|
+ // Reserve address space. |
+ // For executables that must be loaded at a fixed address, 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. |
+ char *base_addr = (char *) mmap(required_start, load_size, PROT_NONE, |
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); |
+ if (base_addr == MAP_FAILED) { |
+ Sandbox::die("Failed to reserve address space"); |
+ } |
+ if (required_start != NULL && 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. |
Markus (顧孟勤)
2011/08/15 22:05:23
Any chance that we could actually test this assump
|
+ SetAuxvField(auxv, AT_PHDR, (uintptr_t) base_addr + elf_header.e_phoff); |
+ |
+ for (int index = 0; index < elf_header.e_phnum; index++) { |
+ ElfW(Phdr) *segment = |
+ (ElfW(Phdr) *) (phdrs + elf_header.e_phentsize * index); |
+ if (segment->p_type == PT_LOAD) { |
+ uintptr_t segment_start = PageSizeRoundDown(segment->p_vaddr); |
+ size_t segment_end = PageSizeRoundUp(segment->p_vaddr + segment->p_memsz); |
Markus (顧孟勤)
2011/08/15 22:05:23
Again, do we worry about overflows?
|
+ 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, |
Markus (顧孟勤)
2011/08/15 22:05:23
Don't we have the same problem as for the other ca
|
+ 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. |
Markus (顧孟勤)
2011/08/15 22:05:23
What happens if we encounter something like that?
|
+ if ((segment->p_flags & PF_W) != 0) { |
+ // Zero the BSS to the end of the page. |
+ uintptr_t bss_start = segment->p_vaddr + segment->p_filesz; |
Markus (顧孟勤)
2011/08/15 22:05:23
Do we need sanity checks that we don't zero out so
|
+ 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; |
Markus (顧孟勤)
2011/08/15 22:05:23
We probably want a more fine-grained policy at som
|
+ playground::AddTlsSetupSyscall(); |
+ StartSeccompSandbox(); |
+ |
+ ElfW(auxv_t) *auxv = FindAuxv(argc, argv); |
+ SetAuxvField(auxv, AT_SYSINFO, (uintptr_t) syscallEntryPointNoFrame); |
Markus (顧孟勤)
2011/08/15 22:05:23
Does this work on both x86-32 and x86-64. I vaguel
|
+ char **stack = argv; |
+ *(long *) stack = argc - 1; |
Markus (顧孟勤)
2011/08/15 22:05:23
Are there alignment requirements that we need to w
|
+ void *entry_point = LoadElfObject(executable_fd, auxv); |
+ JumpToElfEntryPoint(stack, entry_point, 0); |
+} |