Chromium Code Reviews| 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); |
| +} |