OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include <elf.h> |
| 6 #include <fcntl.h> |
| 7 #include <stdio.h> |
| 8 #include <string.h> |
| 9 #include <sys/mman.h> |
| 10 #include <unistd.h> |
| 11 |
| 12 #include "sandbox_impl.h" |
| 13 #include "syscall_entrypoint.h" |
| 14 #include "tls_setup.h" |
| 15 |
| 16 |
| 17 #if defined(__x86_64__) |
| 18 # define ElfW(name) Elf64_##name |
| 19 # define ElfW_ELFCLASS ELFCLASS64 |
| 20 # define ElfW_EXPECTED_MACHINE EM_X86_64 |
| 21 #elif defined(__i386__) |
| 22 # define ElfW(name) Elf32_##name |
| 23 # define ElfW_ELFCLASS ELFCLASS32 |
| 24 # define ElfW_EXPECTED_MACHINE EM_386 |
| 25 #else |
| 26 # error Unsupported target platform |
| 27 #endif |
| 28 |
| 29 |
| 30 static uintptr_t PageSizeRoundDown(uintptr_t val) { |
| 31 return val & ~(getpagesize() - 1); |
| 32 } |
| 33 |
| 34 static uintptr_t PageSizeRoundUp(uintptr_t val) { |
| 35 return PageSizeRoundDown(val + getpagesize() - 1); |
| 36 } |
| 37 |
| 38 static ElfW(auxv_t) *FindAuxv(int argc, char **argv) { |
| 39 char **ptr = argv + argc + 1; |
| 40 // Skip over envp. |
| 41 while (*ptr != NULL) { |
| 42 ptr++; |
| 43 } |
| 44 ptr++; |
| 45 return (ElfW(auxv_t) *) ptr; |
| 46 } |
| 47 |
| 48 static void SetAuxvField(ElfW(auxv_t) *auxv, unsigned type, uintptr_t value) { |
| 49 for (; auxv->a_type != AT_NULL; auxv++) { |
| 50 if (auxv->a_type == type) { |
| 51 auxv->a_un.a_val = value; |
| 52 return; |
| 53 } |
| 54 } |
| 55 } |
| 56 |
| 57 static void JumpToElfEntryPoint(void *stack, void *entry_point, |
| 58 void *atexit_func) { |
| 59 #if defined(__x86_64__) |
| 60 asm("mov %0, %%rsp\n" |
| 61 "jmp *%1\n" |
| 62 // %edx is registered as an atexit handler if non-zero. |
| 63 : : "r"(stack), "r"(entry_point), "d"(atexit_func)); |
| 64 #elif defined(__i386__) |
| 65 asm("mov %0, %%esp\n" |
| 66 "jmp *%1\n" |
| 67 // %rdx is registered as an atexit handler if non-zero. |
| 68 : : "r"(stack), "r"(entry_point), "d"(atexit_func)); |
| 69 #else |
| 70 # error Unsupported target platform |
| 71 #endif |
| 72 } |
| 73 |
| 74 static void *LoadElfObject(int fd, ElfW(auxv_t) *auxv) { |
| 75 // Load and check headers. |
| 76 ElfW(Ehdr) elf_header; |
| 77 if (pread(fd, &elf_header, sizeof(elf_header), 0) != sizeof(elf_header)) { |
| 78 Sandbox::die("Failed to read ELF header"); |
| 79 } |
| 80 if (memcmp(elf_header.e_ident, ELFMAG, SELFMAG) != 0) { |
| 81 Sandbox::die("Not an ELF file"); |
| 82 } |
| 83 if (elf_header.e_ident[EI_CLASS] != ElfW_ELFCLASS) { |
| 84 Sandbox::die("Unexpected ELFCLASS"); |
| 85 } |
| 86 if (elf_header.e_machine != ElfW_EXPECTED_MACHINE) { |
| 87 Sandbox::die("Unexpected ELF machine type"); |
| 88 } |
| 89 if (elf_header.e_phentsize != sizeof(ElfW(Phdr))) { |
| 90 Sandbox::die("Unexpected ELF program header entry size"); |
| 91 } |
| 92 if (elf_header.e_phnum >= 20) { |
| 93 // We impose an arbitrary limit as a sanity check and to avoid |
| 94 // overflowing the stack. |
| 95 Sandbox::die("Too many ELF program headers"); |
| 96 } |
| 97 ElfW(Phdr) phdrs[elf_header.e_phnum]; |
| 98 if (pread(fd, phdrs, sizeof(phdrs), elf_header.e_phoff) |
| 99 != (ssize_t) sizeof(phdrs)) { |
| 100 Sandbox::die("Failed to read ELF program headers"); |
| 101 } |
| 102 |
| 103 // Scan program headers to find the overall size of the ELF object. |
| 104 // Find the first and last PT_LOAD segments. ELF requires that |
| 105 // PT_LOAD segments be in ascending order of p_vaddr, so we can use |
| 106 // the last one to calculate the whole address span of the image. |
| 107 size_t index = 0; |
| 108 while (index < elf_header.e_phnum && phdrs[index].p_type != PT_LOAD) { |
| 109 index++; |
| 110 } |
| 111 if (index == elf_header.e_phnum) { |
| 112 Sandbox::die("ELF object contains no PT_LOAD headers"); |
| 113 } |
| 114 ElfW(Phdr) *first_segment = &phdrs[index]; |
| 115 ElfW(Phdr) *last_segment = &phdrs[elf_header.e_phnum - 1]; |
| 116 while (last_segment > first_segment && last_segment->p_type != PT_LOAD) { |
| 117 last_segment--; |
| 118 } |
| 119 uintptr_t overall_start = PageSizeRoundDown(first_segment->p_vaddr); |
| 120 uintptr_t overall_end = PageSizeRoundUp(last_segment->p_vaddr |
| 121 + last_segment->p_memsz); |
| 122 uintptr_t overall_size = overall_end - overall_start; |
| 123 |
| 124 // Reserve address space. |
| 125 // Executables that must be loaded at a fixed address have an e_type |
| 126 // of ET_EXEC. For these, we could use MAP_FIXED, but if the |
| 127 // address range is already occupied then that will clobber the |
| 128 // existing mappings without warning, which is bad. Instead, use an |
| 129 // address hint and check that we got the expected address. |
| 130 // Executables that can be loaded at any address have an e_type of |
| 131 // ET_DYN. |
| 132 char *required_start = |
| 133 elf_header.e_type == ET_EXEC ? (char *) overall_start : NULL; |
| 134 char *base_addr = (char *) mmap(required_start, overall_size, PROT_NONE, |
| 135 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); |
| 136 if (base_addr == MAP_FAILED) { |
| 137 Sandbox::die("Failed to reserve address space"); |
| 138 } |
| 139 if (elf_header.e_type == ET_EXEC && base_addr != required_start) { |
| 140 Sandbox::die("Failed to reserve address space at fixed address"); |
| 141 } |
| 142 |
| 143 char *load_offset = (char *) (base_addr - required_start); |
| 144 char *entry_point = load_offset + elf_header.e_entry; |
| 145 SetAuxvField(auxv, AT_ENTRY, (uintptr_t) entry_point); |
| 146 SetAuxvField(auxv, AT_BASE, (uintptr_t) load_offset); |
| 147 SetAuxvField(auxv, AT_PHNUM, elf_header.e_phnum); |
| 148 SetAuxvField(auxv, AT_PHENT, elf_header.e_phentsize); |
| 149 // Note that this assumes that the program headers are included in a |
| 150 // PT_LOAD segment for which the file offsets matches the mapping |
| 151 // offset, but Linux assumes this too when setting AT_PHDR. |
| 152 SetAuxvField(auxv, AT_PHDR, (uintptr_t) base_addr + elf_header.e_phoff); |
| 153 |
| 154 for (ElfW(Phdr) *segment = first_segment; |
| 155 segment <= last_segment; |
| 156 segment++) { |
| 157 if (segment->p_type == PT_LOAD) { |
| 158 uintptr_t segment_start = PageSizeRoundDown(segment->p_vaddr); |
| 159 uintptr_t segment_end = PageSizeRoundUp(segment->p_vaddr |
| 160 + segment->p_memsz); |
| 161 int prot = 0; |
| 162 if ((segment->p_flags & PF_R) != 0) |
| 163 prot |= PROT_READ; |
| 164 if ((segment->p_flags & PF_W) != 0) |
| 165 prot |= PROT_WRITE; |
| 166 if ((segment->p_flags & PF_X) != 0) |
| 167 prot |= PROT_EXEC; |
| 168 void *result = mmap(load_offset + segment_start, |
| 169 segment_end - segment_start, |
| 170 prot, MAP_PRIVATE | MAP_FIXED, fd, |
| 171 PageSizeRoundDown(segment->p_offset)); |
| 172 if (result == MAP_FAILED) { |
| 173 Sandbox::die("Failed to map ELF segment"); |
| 174 } |
| 175 // TODO(mseaborn): Support a BSS that goes beyond the file's extent. |
| 176 if ((segment->p_flags & PF_W) != 0) { |
| 177 // Zero the BSS to the end of the page. ld.so and other |
| 178 // programs use the rest of this page as part of the brk() |
| 179 // heap and assume that it has been zeroed. |
| 180 uintptr_t bss_start = segment->p_vaddr + segment->p_filesz; |
| 181 memset(load_offset + bss_start, 0, segment_end - bss_start); |
| 182 } |
| 183 } |
| 184 } |
| 185 if (close(fd) != 0) { |
| 186 Sandbox::die("close() failed"); |
| 187 } |
| 188 return entry_point; |
| 189 } |
| 190 |
| 191 int main(int argc, char **argv) { |
| 192 if (argc < 2) { |
| 193 fprintf(stderr, "Usage: %s executable args...\n", argv[0]); |
| 194 return 1; |
| 195 } |
| 196 |
| 197 const char *executable_filename = argv[1]; |
| 198 int executable_fd = open(executable_filename, O_RDONLY); |
| 199 if (executable_fd < 0) { |
| 200 fprintf(stderr, "Failed to open executable %s: %s\n", |
| 201 executable_filename, strerror(errno)); |
| 202 return 1; |
| 203 } |
| 204 |
| 205 playground::g_policy.allow_file_namespace = true; |
| 206 playground::AddTlsSetupSyscall(); |
| 207 StartSeccompSandbox(); |
| 208 |
| 209 ElfW(auxv_t) *auxv = FindAuxv(argc, argv); |
| 210 SetAuxvField(auxv, AT_SYSINFO, (uintptr_t) syscallEntryPointNoFrame); |
| 211 char **stack = argv; |
| 212 *(long *) stack = argc - 1; |
| 213 void *entry_point = LoadElfObject(executable_fd, auxv); |
| 214 JumpToElfEntryPoint(stack, entry_point, 0); |
| 215 } |
OLD | NEW |