Index: src/trusted/service_runtime/elf_util.c |
=================================================================== |
--- src/trusted/service_runtime/elf_util.c (revision 0) |
+++ src/trusted/service_runtime/elf_util.c (revision 0) |
@@ -0,0 +1,466 @@ |
+/* |
+ * Copyright 2009 The Native Client Authors. All rights reserved. |
+ * Use of this source code is governed by a BSD-style license that can |
+ * be found in the LICENSE file. |
+ */ |
+ |
+/* |
+ * NaCl helper functions to deal with elf images |
+ */ |
+ |
+#include "native_client/src/include/portability.h" |
+ |
+#include <stdio.h> |
+ |
+#include <stdlib.h> |
+#include <string.h> |
+ |
+#include "native_client/src/include/elf_constants.h" |
+#include "native_client/src/include/nacl_elf.h" |
+#include "native_client/src/include/nacl_macros.h" |
+ |
+#include "native_client/src/shared/platform/nacl_log.h" |
+ |
+#include "native_client/src/trusted/service_runtime/elf_util.h" |
+#include "native_client/src/trusted/service_runtime/nacl_config.h" |
+ |
+/* private */ |
+struct NaClElfImage { |
+ Elf32_Ehdr ehdr; |
+ Elf32_Phdr phdrs[NACL_MAX_PROGRAM_HEADERS]; |
+ int loadable[NACL_MAX_PROGRAM_HEADERS]; |
+}; |
+ |
+ |
+enum NaClPhdrCheckAction { |
+ PCA_NONE, |
+ PCA_TEXT_CHECK, |
+ PCA_IGNORE /* ignore this segment. currently used only for PT_PHDR. */ |
+}; |
+ |
+ |
+struct NaClPhdrChecks { |
+ Elf32_Word p_type; |
+ Elf32_Word p_flags; /* rwx */ |
+ enum NaClPhdrCheckAction action; |
+ int required; /* only for text for now */ |
+ Elf32_Word p_vaddr; /* if non-zero, vaddr must be this */ |
+}; |
+ |
+/* |
+ * Other than empty segments, these are the only ones that are allowed. |
+ */ |
+static const struct NaClPhdrChecks nacl_phdr_check_data[] = { |
+ /* phdr */ |
+ { PT_PHDR, PF_R, PCA_IGNORE, 0, 0, }, |
+ /* text */ |
+ { PT_LOAD, PF_R|PF_X, PCA_TEXT_CHECK, 1, NACL_TRAMPOLINE_END, }, |
+ /* rodata */ |
+ { PT_LOAD, PF_R, PCA_NONE, 0, 0, }, |
+ /* data/bss */ |
+ { PT_LOAD, PF_R|PF_W, PCA_NONE, 0, 0, }, |
+#if NACL_ARCH(NACL_BUILD_ARCH) == NACL_arm |
+ /* arm exception handling unwind info (for c++)*/ |
+ /* TODO(robertm): for some reason this does NOT end up in ro maybe because |
+ * it is relocatable. Try hacking the linker script to move it. |
+ */ |
+ { PT_ARM_EXIDX, PF_R, PCA_IGNORE, 0, 0, }, |
+#endif |
+ /* |
+ * allow optional GNU stack permission marker, but require that the |
+ * stack is non-executable. |
+ */ |
+ { PT_GNU_STACK, PF_R|PF_W, PCA_NONE, 0, 0, }, |
+}; |
+ |
+ |
+ |
+static void NaClDumpElfHeader(Elf32_Ehdr *elf_hdr) { |
+#define DUMP(m,f) do { NaClLog(2, \ |
+ #m " = %" f "\n", \ |
+ elf_hdr->m); } while (0) |
+ DUMP(e_ident+1, ".3s"); |
+ DUMP(e_type, "#x"); |
+ DUMP(e_machine, "#x"); |
+ DUMP(e_version, "#x"); |
+ DUMP(e_entry, "#x"); |
+ DUMP(e_phoff, "#x"); |
+ DUMP(e_shoff, "#x"); |
+ DUMP(e_flags, "#x"); |
+ DUMP(e_ehsize, "#x"); |
+ DUMP(e_phentsize, "#x"); |
+ DUMP(e_phnum, "#x"); |
+ DUMP(e_shentsize, "#x"); |
+ DUMP(e_shnum, "#x"); |
+ DUMP(e_shstrndx, "#x"); |
+#undef DUMP |
+ NaClLog(2, "sizeof(Elf32_Ehdr) = %x\n", (int) sizeof *elf_hdr); |
+} |
+ |
+ |
+static void NaClDumpElfProgramHeader(Elf32_Phdr *phdr) { |
+ #define DUMP(mem) do { \ |
+ NaClLog(2, "%s: %x\n", #mem, phdr->mem); \ |
+ } while (0) |
+ |
+ DUMP(p_type); |
+ DUMP(p_offset); |
+ DUMP(p_vaddr); |
+ DUMP(p_paddr); |
+ DUMP(p_filesz); |
+ DUMP(p_memsz); |
+ DUMP(p_flags); |
+ NaClLog(2, " (%s %s %s)\n", |
+ (phdr->p_flags & PF_R) ? "PF_R" : "", |
+ (phdr->p_flags & PF_W) ? "PF_W" : "", |
+ (phdr->p_flags & PF_X) ? "PF_X" : ""); |
+ DUMP(p_align); |
+ #undef DUMP |
+ NaClLog(2, "\n"); |
+ } |
+ |
+ |
+NaClErrorCode NaClElfImageValidateAbi(struct NaClElfImage *image) { |
+ const Elf32_Ehdr *hdr = &image->ehdr; |
+ |
+ if (ELFOSABI_NACL != hdr->e_ident[EI_OSABI]) { |
+ NaClLog(LOG_ERROR, "Expected OSABI %d, got %d\n", |
+ ELFOSABI_NACL, |
+ hdr->e_ident[EI_OSABI]); |
+ return LOAD_BAD_ABI; |
+ } |
+ |
+ if (EF_NACL_ABIVERSION != hdr->e_ident[EI_ABIVERSION]) { |
+ NaClLog(LOG_ERROR, "Expected ABIVERSION %d, got %d\n", |
+ EF_NACL_ABIVERSION, |
+ hdr->e_ident[EI_ABIVERSION]); |
+ return LOAD_BAD_ABI; |
+ } |
+ |
+ return LOAD_OK; |
+} |
+ |
+ |
+NaClErrorCode NaClElfImageValidateElfHeader(struct NaClElfImage *image) { |
+ const Elf32_Ehdr *hdr = &image->ehdr; |
+ |
+ if (memcmp(hdr->e_ident, ELFMAG, SELFMAG)) { |
+ NaClLog(LOG_ERROR, "bad elf magic\n"); |
+ return LOAD_BAD_ELF_MAGIC; |
+ } |
+ |
+ if (ELFCLASS32 != hdr->e_ident[EI_CLASS]) { |
+ NaClLog(LOG_ERROR, "bad elf class\n"); |
+ return LOAD_NOT_32_BIT; |
+ } |
+ |
+ if (ET_EXEC != hdr->e_type) { |
+ NaClLog(LOG_ERROR, "non executable\n"); |
+ return LOAD_NOT_EXEC; |
+ } |
+ |
+ if (EM_EXPECTED_BY_NACL != hdr->e_machine) { |
+ NaClLog(LOG_ERROR, "bad machine\n"); |
+ return LOAD_BAD_MACHINE; |
+ } |
+ |
+ if (EV_CURRENT != hdr->e_version) { |
+ NaClLog(LOG_ERROR, "bad elf version\n"); |
+ return LOAD_BAD_ELF_VERS; |
+ } |
+ |
+ return LOAD_OK; |
+} |
+ |
+/* TODO(robertm): decouple validation from computation of |
+ text_region_bytes and max_vaddr */ |
+NaClErrorCode NaClElfImageValidateProgramHeaders( |
+ struct NaClElfImage *image, |
+ uint32_t addr_bits, |
+ uint32_t *text_region_bytes, |
+ uintptr_t *max_vaddr) { |
+ /* |
+ * Scan phdrs and do sanity checks in-line. Verify that the load |
+ * address is NACL_TRAMPOLINE_END, that we have a single text |
+ * segment. Data and TLS segments are not required, though it is |
+ * hard to avoid with standard tools, but in any case there should |
+ * be at most one each. Ensure that no segment's vaddr is outside |
+ * of the address space. Ensure that PT_GNU_STACK is present, and |
+ * that x is off. |
+ */ |
+ const Elf32_Ehdr *hdr = &image->ehdr; |
+ int seen_seg[NACL_ARRAY_SIZE(nacl_phdr_check_data)]; |
+ |
+ int segnum; |
+ const Elf32_Phdr *php; |
+ size_t j; |
+ |
+ *max_vaddr = NACL_TRAMPOLINE_END; |
+ |
+ /* |
+ * nacl_phdr_check_data is small, so O(|check_data| * nap->elf_hdr.e_phum) |
+ * is okay. |
+ */ |
+ memset(seen_seg, 0, sizeof seen_seg); |
+ for (segnum = 0; segnum < hdr->e_phnum; ++segnum) { |
+ php = &image->phdrs[segnum]; |
+ NaClLog(3, "Looking at segment %d, type 0x%x, p_flags 0x%x\n", |
+ segnum, php->p_type, php->p_flags); |
+ for (j = 0; j < NACL_ARRAY_SIZE(nacl_phdr_check_data); ++j) { |
+ if (php->p_type == nacl_phdr_check_data[j].p_type |
+ && php->p_flags == nacl_phdr_check_data[j].p_flags) { |
+ NaClLog(2, "Matched nacl_phdr_check_data[%"PRIdS"]\n", j); |
+ if (seen_seg[j] > 0) { |
+ NaClLog(2, "Segment %d is a type that has been seen\n", segnum); |
+ return LOAD_DUP_SEGMENT; |
+ } |
+ ++seen_seg[j]; |
+ |
+ if (PCA_IGNORE == nacl_phdr_check_data[j].action) { |
+ NaClLog(3, "Ignoring\n"); |
+ goto next_seg; |
+ } |
+ |
+ if (0 != php->p_memsz) { |
+ /* |
+ * We will load this segment later. Do the sanity checks. |
+ */ |
+ if (0 != nacl_phdr_check_data[j].p_vaddr |
+ && (nacl_phdr_check_data[j].p_vaddr != php->p_vaddr)) { |
+ NaClLog(2, |
+ ("Segment %d: bad virtual address: 0x%08x," |
+ " expected 0x%08x\n"), |
+ segnum, |
+ php->p_vaddr, |
+ nacl_phdr_check_data[j].p_vaddr); |
+ return LOAD_SEGMENT_BAD_LOC; |
+ } |
+ if (php->p_vaddr < NACL_TRAMPOLINE_END) { |
+ NaClLog(2, "Segment %d: virtual address (0x%08x) too low\n", |
+ segnum, |
+ php->p_vaddr); |
+ return LOAD_SEGMENT_OUTSIDE_ADDRSPACE; |
+ } |
+ /* |
+ * integer overflow? Elf32_Addr and Elf32_Word are uint32_t, |
+ * so the addition/comparison is well defined. |
+ */ |
+ if (php->p_vaddr + php->p_memsz < php->p_vaddr) { |
+ NaClLog(2, |
+ "Segment %d: p_memsz caused integer overflow\n", |
+ segnum); |
+ return LOAD_SEGMENT_OUTSIDE_ADDRSPACE; |
+ } |
+ if (php->p_vaddr + php->p_memsz >= (1U << addr_bits)) { |
+ NaClLog(2, |
+ "Segment %d: too large, ends at 0x%08x\n", |
+ segnum, |
+ php->p_vaddr + php->p_memsz); |
+ return LOAD_SEGMENT_OUTSIDE_ADDRSPACE; |
+ } |
+ if (php->p_filesz > php->p_memsz) { |
+ NaClLog(2, |
+ ("Segment %d: file size 0x%08x larger" |
+ " than memory size 0x%08x\n"), |
+ segnum, |
+ php->p_filesz, |
+ php->p_memsz); |
+ return LOAD_SEGMENT_BAD_PARAM; |
+ } |
+ |
+ image->loadable[segnum] = 1; |
+ /* record our decision that we will load this segment */ |
+ |
+ /* |
+ * NACL_TRAMPOLINE_END <= p_vaddr |
+ * <= p_vaddr + p_memsz |
+ * < (1U << nap->addr_bits) |
+ */ |
+ if (*max_vaddr < php->p_vaddr + php->p_memsz) { |
+ *max_vaddr = php->p_vaddr + php->p_memsz; |
+ } |
+ } |
+ |
+ switch (nacl_phdr_check_data[j].action) { |
+ case PCA_NONE: |
+ break; |
+ case PCA_TEXT_CHECK: |
+ if (0 == php->p_memsz) { |
+ return LOAD_BAD_ELF_TEXT; |
+ } |
+ *text_region_bytes = php->p_filesz; |
+ break; |
+ case PCA_IGNORE: |
+ break; |
+ } |
+ goto next_seg; |
+ } |
+ } |
+ /* segment not in nacl_phdr_check_data */ |
+ if (0 == php->p_memsz) { |
+ NaClLog(3, "Segment %d zero size: ignored\n", segnum); |
+ continue; |
+ } |
+ NaClLog(2, |
+ "Segment %d is of unexpected type 0x%x, flag 0x%x\n", |
+ segnum, |
+ php->p_type, |
+ php->p_flags); |
+ return LOAD_BAD_SEGMENT; |
+ next_seg: |
+ {} |
+ } |
+ for (j = 0; j < NACL_ARRAY_SIZE(nacl_phdr_check_data); ++j) { |
+ if (nacl_phdr_check_data[j].required && !seen_seg[j]) { |
+ return LOAD_REQUIRED_SEG_MISSING; |
+ } |
+ } |
+ |
+ /* |
+ * Memory allocation will use NaClRoundPage(nap->break_addr), but |
+ * the system notion of break is always an exact address. Even |
+ * though we must allocate and make accessible multiples of pages, |
+ * the linux-style brk system call (which returns current break on |
+ * failure) permits an arbitrarily aligned address as argument. |
+ */ |
+ |
+ return LOAD_OK; |
+} |
+ |
+ |
+struct NaClElfImage *NaClElfImageNew(struct Gio *gp) { |
+ struct NaClElfImage *result; |
+ struct NaClElfImage image; |
+ int cur_ph; |
+ |
+ memset(image.loadable, 0, sizeof image.loadable); |
+ if ((*gp->vtbl->Read)(gp, |
+ &image.ehdr, |
+ sizeof image.ehdr) |
+ != sizeof image.ehdr) { |
+ /* Consider making this fatal */ |
+ NaClLog(2, "could not load elf headers\n"); |
+ return 0; |
+ } |
+ |
+ NaClDumpElfHeader(&image.ehdr); |
+ |
+ /* read program headers */ |
+ if (image.ehdr.e_phnum > NACL_MAX_PROGRAM_HEADERS) { |
+ /* Consider making this fatal */ |
+ NaClLog(2, "too many prog headers\n"); |
+ return 0; |
+ } |
+ |
+ if (image.ehdr.e_phentsize < sizeof image.phdrs[0]) { |
+ NaClLog(2, "bad prog headers size\n"); |
+ return 0; |
+ } |
+ |
+ if ((*gp->vtbl->Seek)(gp, |
+ image.ehdr.e_phoff, |
+ SEEK_SET) == -1) { |
+ NaClLog(2, "cannot seek tp prog headers\n"); |
+ return 0; |
+ } |
+ |
+ if ((*gp->vtbl->Read)(gp, |
+ &image.phdrs[0], |
+ image.ehdr.e_phnum * sizeof image.phdrs[0]) |
+ != (int32_t) (image.ehdr.e_phnum * sizeof image.phdrs[0])) { |
+ NaClLog(2, "cannot load tp prog headers\n"); |
+ return 0; |
+ } |
+ |
+ for (cur_ph = 0; cur_ph < image.ehdr.e_phnum; ++cur_ph) { |
+ NaClDumpElfProgramHeader(&image.phdrs[cur_ph]); |
+ } |
+ |
+ /* we delay allocating till the end to avoid cleanup code */ |
+ result = malloc(sizeof image); |
+ if (result == 0) { |
+ NaClLog(LOG_FATAL, "no enough memory for image meta data\n"); |
+ return 0; |
+ } |
+ memcpy(result, &image, sizeof image); |
+ return result; |
+} |
+ |
+ |
+NaClErrorCode NaClElfImageLoad(struct NaClElfImage *image, |
+ struct Gio *gp, |
+ uint32_t addr_bits, |
+ uintptr_t mem_start) { |
+ int segnum; |
+ uintptr_t paddr; |
+ uintptr_t end_vaddr; |
+ |
+ for (segnum = 0; segnum < image->ehdr.e_phnum; ++segnum) { |
+ const Elf32_Phdr *php = &image->phdrs[segnum]; |
+ |
+ /* did we decide that we will load this segment earlier? */ |
+ if (!image->loadable[segnum]) { |
+ continue; |
+ } |
+ |
+ NaClLog(2, "loading segment %d", segnum); |
+ end_vaddr = php->p_vaddr + php->p_filesz; |
+ /* integer overflow? */ |
+ if (end_vaddr < php->p_vaddr) { |
+ NaClLog(LOG_FATAL, "parameter error should have been detected already\n"); |
+ } |
+ /* |
+ * is the end virtual address within the NaCl application's |
+ * address space? if it is, it implies that the start virtual |
+ * address is also. |
+ */ |
+ if (end_vaddr >= (1U << addr_bits)) { |
+ NaClLog(LOG_FATAL, "parameter error should have been detected already\n"); |
+ } |
+ |
+ paddr = mem_start + php->p_vaddr; |
+ |
+ if ((*gp->vtbl->Seek)(gp, php->p_offset, SEEK_SET) == -1) { |
+ NaClLog(LOG_ERROR, "seek failure segment %d", segnum); |
+ return LOAD_SEGMENT_BAD_PARAM; |
+ } |
+ if ((Elf32_Word) (*gp->vtbl->Read)(gp, (void *) paddr, php->p_filesz) |
+ != php->p_filesz) { |
+ NaClLog(LOG_ERROR, "load failure segment %d", segnum); |
+ return LOAD_SEGMENT_BAD_PARAM; |
+ } |
+ /* region from p_filesz to p_memsz should already be zero filled */ |
+ } |
+ |
+ return LOAD_OK; |
+} |
+ |
+ |
+void NaClElfImageDelete(struct NaClElfImage *image) { |
+ free(image); |
+} |
+ |
+ |
+uint32_t NaClElfImageGetEntryPoint(struct NaClElfImage *image) { |
+ return image->ehdr.e_entry; |
+} |
+ |
+ |
+/* TODO(robertm): this code should enforce that either 16 or 32 bit alignment is |
+ is set - there are currently some problems with ARM, though |
+*/ |
+int NaClElfImageGetAlignBoundary(struct NaClElfImage *image) { |
+ unsigned long eflags = image->ehdr.e_flags & EF_NACL_ALIGN_MASK; |
+ if (eflags) { |
+ if (eflags == EF_NACL_ALIGN_16) { |
+ return 16; |
+ } else if (eflags == EF_NACL_ALIGN_32) { |
+ return 32; |
+ } else { |
+ NaClLog(LOG_ERROR, "strange alignment"); |
+ return 0; |
+ } |
+ } else { |
+ return 32; |
+ } |
+} |