| // Copyright 2014 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 "crazy_linker_elf_loader.h" |
| |
| #include <limits.h> // For PAGE_SIZE and PAGE_MASK |
| |
| #include "crazy_linker_debug.h" |
| #include "linker_phdr.h" |
| |
| #define PAGE_START(x) ((x) & PAGE_MASK) |
| #define PAGE_OFFSET(x) ((x) & ~PAGE_MASK) |
| #define PAGE_END(x) PAGE_START((x) + (PAGE_SIZE - 1)) |
| |
| namespace crazy { |
| |
| #define MAYBE_MAP_FLAG(x, from, to) (((x) & (from)) ? (to) : 0) |
| #define PFLAGS_TO_PROT(x) \ |
| (MAYBE_MAP_FLAG((x), PF_X, PROT_EXEC) | \ |
| MAYBE_MAP_FLAG((x), PF_R, PROT_READ) | \ |
| MAYBE_MAP_FLAG((x), PF_W, PROT_WRITE)) |
| |
| namespace { |
| |
| class InternalElfLoader { |
| public: |
| ~InternalElfLoader(); |
| |
| bool LoadAt(const char* lib_path, |
| off_t file_offset, |
| uintptr_t wanted_address, |
| Error* error); |
| |
| // Only call the following functions after a successful LoadAt() call. |
| |
| size_t phdr_count() { return phdr_num_; } |
| ELF::Addr load_start() { return reinterpret_cast<ELF::Addr>(load_start_); } |
| ELF::Addr load_size() { return load_size_; } |
| ELF::Addr load_bias() { return load_bias_; } |
| const ELF::Phdr* loaded_phdr() { return loaded_phdr_; } |
| |
| // Return the mapping object covering the reserved address space for this |
| // ELF object. Caller takes ownership. |
| MemoryMapping ReleaseMapping() { return std::move(reserved_map_); } |
| |
| private: |
| FileDescriptor fd_; |
| const char* path_ = nullptr; |
| |
| ELF::Ehdr header_ = {}; |
| size_t phdr_num_ = 0; |
| |
| void* phdr_mmap_ = nullptr; // temporary copy of the program header. |
| ELF::Phdr* phdr_table_ = nullptr; |
| ELF::Addr phdr_size_ = 0; // and its size. |
| |
| off_t file_offset_ = 0; |
| void* wanted_load_address_ = nullptr; |
| void* load_start_ = nullptr; // First page of reserved address space. |
| ELF::Addr load_size_ = 0; // Size in bytes of reserved address space. |
| ELF::Addr load_bias_ = 0; // load_bias, add this value to all "vaddr" |
| // values in the library to get the corresponding |
| // memory address. |
| |
| const ELF::Phdr* loaded_phdr_ = |
| nullptr; // points to the loaded program header. |
| |
| MemoryMapping reserved_map_; |
| |
| // Individual steps used by ::LoadAt() |
| bool ReadElfHeader(Error* error); |
| bool ReadProgramHeader(Error* error); |
| bool ReserveAddressSpace(Error* error); |
| bool LoadSegments(Error* error); |
| bool FindPhdr(Error* error); |
| bool CheckPhdr(ELF::Addr, Error* error); |
| }; |
| |
| InternalElfLoader::~InternalElfLoader() { |
| if (phdr_mmap_) { |
| // Deallocate the temporary program header copy. |
| munmap(phdr_mmap_, phdr_size_); |
| } |
| } |
| |
| bool InternalElfLoader::LoadAt(const char* lib_path, |
| off_t file_offset, |
| uintptr_t wanted_address, |
| Error* error) { |
| LOG("lib_path='%s', file_offset=%p, load_address=%p", lib_path, file_offset, |
| wanted_address); |
| |
| // Check that the load address is properly page-aligned. |
| if (wanted_address != PAGE_START(wanted_address)) { |
| error->Format("Load address is not page aligned (%08x)", wanted_address); |
| return false; |
| } |
| wanted_load_address_ = reinterpret_cast<void*>(wanted_address); |
| |
| // Check that the file offset is also properly page-aligned. |
| // PAGE_START() can't be used here due to the compiler complaining about |
| // comparing signed (off_t) and unsigned (size_t) values. |
| if ((file_offset & static_cast<off_t>(PAGE_SIZE - 1)) != 0) { |
| error->Format("File offset is not page aligned (%08x)", file_offset); |
| return false; |
| } |
| file_offset_ = file_offset; |
| |
| // Open the file. |
| if (!fd_.OpenReadOnly(lib_path)) { |
| error->Format("Can't open file: %s", strerror(errno)); |
| return false; |
| } |
| |
| if (file_offset && fd_.SeekTo(file_offset) < 0) { |
| error->Format( |
| "Can't seek to file offset %08x: %s", file_offset, strerror(errno)); |
| return false; |
| } |
| |
| path_ = lib_path; |
| |
| if (!ReadElfHeader(error) || !ReadProgramHeader(error) || |
| !ReserveAddressSpace(error)) { |
| return false; |
| } |
| |
| if (!LoadSegments(error) || !FindPhdr(error)) { |
| // An error occured, cleanup the address space by un-mapping the |
| // range that was reserved by ReserveAddressSpace(). |
| reserved_map_.Deallocate(); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool InternalElfLoader::ReadElfHeader(Error* error) { |
| int ret = fd_.Read(&header_, sizeof(header_)); |
| if (ret < 0) { |
| error->Format("Can't read file: %s", strerror(errno)); |
| return false; |
| } |
| if (ret != static_cast<int>(sizeof(header_))) { |
| error->Set("File too small to be ELF"); |
| return false; |
| } |
| |
| if (memcmp(header_.e_ident, ELFMAG, SELFMAG) != 0) { |
| error->Set("Bad ELF magic"); |
| return false; |
| } |
| |
| if (header_.e_ident[EI_CLASS] != ELF::kElfClass) { |
| error->Format("Not a %d-bit class: %d", |
| ELF::kElfBits, |
| header_.e_ident[EI_CLASS]); |
| return false; |
| } |
| |
| if (header_.e_ident[EI_DATA] != ELFDATA2LSB) { |
| error->Format("Not little-endian class: %d", header_.e_ident[EI_DATA]); |
| return false; |
| } |
| |
| if (header_.e_type != ET_DYN) { |
| error->Format("Not a shared library type: %d", header_.e_type); |
| return false; |
| } |
| |
| if (header_.e_version != EV_CURRENT) { |
| error->Format("Unexpected ELF version: %d", header_.e_version); |
| return false; |
| } |
| |
| if (header_.e_machine != ELF_MACHINE) { |
| error->Format("Unexpected ELF machine type: %d", header_.e_machine); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| // Loads the program header table from an ELF file into a read-only private |
| // anonymous mmap-ed block. |
| bool InternalElfLoader::ReadProgramHeader(Error* error) { |
| phdr_num_ = header_.e_phnum; |
| |
| // Like the kernel, only accept program header tables smaller than 64 KB. |
| if (phdr_num_ < 1 || phdr_num_ > 65536 / sizeof(ELF::Phdr)) { |
| error->Format("Invalid program header count: %d", phdr_num_); |
| return false; |
| } |
| |
| ELF::Addr page_min = PAGE_START(header_.e_phoff); |
| ELF::Addr page_max = |
| PAGE_END(header_.e_phoff + (phdr_num_ * sizeof(ELF::Phdr))); |
| ELF::Addr page_offset = PAGE_OFFSET(header_.e_phoff); |
| |
| phdr_size_ = page_max - page_min; |
| |
| void* mmap_result = fd_.Map( |
| NULL, phdr_size_, PROT_READ, MAP_PRIVATE, page_min + file_offset_); |
| if (!mmap_result) { |
| error->Format("Phdr mmap failed: %s", strerror(errno)); |
| return false; |
| } |
| |
| phdr_mmap_ = mmap_result; |
| phdr_table_ = reinterpret_cast<ELF::Phdr*>( |
| reinterpret_cast<char*>(mmap_result) + page_offset); |
| return true; |
| } |
| |
| // Reserve a virtual address range big enough to hold all loadable |
| // segments of a program header table. This is done by creating a |
| // private anonymous mmap() with PROT_NONE. |
| // |
| // This will use the wanted_load_address_ value. Fails if the requested |
| // address range cannot be reserved. Typically this would be because |
| // it overlaps an existing, possibly system, mapping. |
| bool InternalElfLoader::ReserveAddressSpace(Error* error) { |
| ELF::Addr min_vaddr; |
| load_size_ = |
| phdr_table_get_load_size(phdr_table_, phdr_num_, &min_vaddr, NULL); |
| if (load_size_ == 0) { |
| error->Set("No loadable segments"); |
| return false; |
| } |
| |
| uint8_t* addr = NULL; |
| int mmap_flags = MAP_PRIVATE | MAP_ANONYMOUS; |
| |
| // Support loading at a fixed address. |
| if (wanted_load_address_) { |
| addr = static_cast<uint8_t*>(wanted_load_address_); |
| } |
| |
| size_t reserved_size = load_size_; |
| |
| LOG("address=%p size=%p", addr, reserved_size); |
| void* start = mmap(addr, reserved_size, PROT_NONE, mmap_flags, -1, 0); |
| if (start == MAP_FAILED) { |
| error->Format("Could not reserve %d bytes of address space", reserved_size); |
| return false; |
| } |
| if (addr && start != addr) { |
| error->Format("Could not map at %p requested, backing out", addr); |
| munmap(start, reserved_size); |
| return false; |
| } |
| |
| // Take ownership of the mapping here. |
| reserved_map_ = MemoryMapping(start, reserved_size); |
| LOG("reserved start=%p", reserved_map_.address()); |
| |
| load_start_ = start; |
| load_bias_ = reinterpret_cast<ELF::Addr>(start) - min_vaddr; |
| |
| LOG("load start=%p, bias=%p", load_start_, load_bias_); |
| return true; |
| } |
| |
| // Returns the address of the program header table as it appears in the loaded |
| // segments in memory. This is in contrast with 'phdr_table_' which |
| // is temporary and will be released before the library is relocated. |
| bool InternalElfLoader::FindPhdr(Error* error) { |
| const ELF::Phdr* phdr_limit = phdr_table_ + phdr_num_; |
| |
| // If there is a PT_PHDR, use it directly. |
| for (const ELF::Phdr* phdr = phdr_table_; phdr < phdr_limit; ++phdr) { |
| if (phdr->p_type == PT_PHDR) { |
| return CheckPhdr(load_bias_ + phdr->p_vaddr, error); |
| } |
| } |
| |
| // Otherwise, check the first loadable segment. If its file offset |
| // is 0, it starts with the ELF header, and we can trivially find the |
| // loaded program header from it. |
| for (const ELF::Phdr* phdr = phdr_table_; phdr < phdr_limit; ++phdr) { |
| if (phdr->p_type == PT_LOAD) { |
| if (phdr->p_offset == 0) { |
| ELF::Addr elf_addr = load_bias_ + phdr->p_vaddr; |
| const ELF::Ehdr* ehdr = (const ELF::Ehdr*)(void*)elf_addr; |
| ELF::Addr offset = ehdr->e_phoff; |
| return CheckPhdr((ELF::Addr)ehdr + offset, error); |
| } |
| break; |
| } |
| } |
| |
| error->Set("Can't find loaded program header"); |
| return false; |
| } |
| |
| // Ensures that our program header is actually within a loadable |
| // segment. This should help catch badly-formed ELF files that |
| // would cause the linker to crash later when trying to access it. |
| bool InternalElfLoader::CheckPhdr(ELF::Addr loaded, Error* error) { |
| const ELF::Phdr* phdr_limit = phdr_table_ + phdr_num_; |
| ELF::Addr loaded_end = loaded + (phdr_num_ * sizeof(ELF::Phdr)); |
| for (ELF::Phdr* phdr = phdr_table_; phdr < phdr_limit; ++phdr) { |
| if (phdr->p_type != PT_LOAD) { |
| continue; |
| } |
| ELF::Addr seg_start = phdr->p_vaddr + load_bias_; |
| ELF::Addr seg_end = phdr->p_filesz + seg_start; |
| if (seg_start <= loaded && loaded_end <= seg_end) { |
| loaded_phdr_ = reinterpret_cast<const ELF::Phdr*>(loaded); |
| return true; |
| } |
| } |
| error->Format("Loaded program header %x not in loadable segment", loaded); |
| return false; |
| } |
| |
| // Map all loadable segments in process' address space. |
| // This assumes you already called phdr_table_reserve_memory to |
| // reserve the address space range for the library. |
| bool InternalElfLoader::LoadSegments(Error* error) { |
| for (size_t i = 0; i < phdr_num_; ++i) { |
| const ELF::Phdr* phdr = &phdr_table_[i]; |
| |
| if (phdr->p_type != PT_LOAD) { |
| continue; |
| } |
| |
| // Segment addresses in memory. |
| ELF::Addr seg_start = phdr->p_vaddr + load_bias_; |
| ELF::Addr seg_end = seg_start + phdr->p_memsz; |
| |
| ELF::Addr seg_page_start = PAGE_START(seg_start); |
| ELF::Addr seg_page_end = PAGE_END(seg_end); |
| |
| ELF::Addr seg_file_end = seg_start + phdr->p_filesz; |
| |
| // File offsets. |
| ELF::Addr file_start = phdr->p_offset; |
| ELF::Addr file_end = file_start + phdr->p_filesz; |
| |
| ELF::Addr file_page_start = PAGE_START(file_start); |
| ELF::Addr file_length = file_end - file_page_start; |
| |
| LOG("file_offset=%p file_length=%p start_address=%p end_address=%p", |
| file_offset_ + file_page_start, file_length, seg_page_start, |
| seg_page_start + PAGE_END(file_length)); |
| |
| if (file_length != 0) { |
| const int prot_flags = PFLAGS_TO_PROT(phdr->p_flags); |
| void* seg_addr = fd_.Map((void*)seg_page_start, |
| file_length, |
| prot_flags, |
| MAP_FIXED | MAP_PRIVATE, |
| file_page_start + file_offset_); |
| if (!seg_addr) { |
| error->Format("Could not map segment %d: %s", i, strerror(errno)); |
| return false; |
| } |
| } |
| |
| // if the segment is writable, and does not end on a page boundary, |
| // zero-fill it until the page limit. |
| if ((phdr->p_flags & PF_W) != 0 && PAGE_OFFSET(seg_file_end) > 0) { |
| memset((void*)seg_file_end, 0, PAGE_SIZE - PAGE_OFFSET(seg_file_end)); |
| } |
| |
| seg_file_end = PAGE_END(seg_file_end); |
| |
| // seg_file_end is now the first page address after the file |
| // content. If seg_end is larger, we need to zero anything |
| // between them. This is done by using a private anonymous |
| // map for all extra pages. |
| if (seg_page_end > seg_file_end) { |
| void* zeromap = mmap((void*)seg_file_end, |
| seg_page_end - seg_file_end, |
| PFLAGS_TO_PROT(phdr->p_flags), |
| MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE, |
| -1, |
| 0); |
| if (zeromap == MAP_FAILED) { |
| error->Format("Could not zero-fill gap: %s", strerror(errno)); |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| } // namespace |
| |
| // static |
| ElfLoader::Result ElfLoader::LoadAt(const char* lib_path, |
| off_t file_offset, |
| uintptr_t wanted_address, |
| Error* error) { |
| InternalElfLoader loader; |
| Result result; |
| if (loader.LoadAt(lib_path, file_offset, wanted_address, error)) { |
| result.load_start = reinterpret_cast<ELF::Addr>(loader.load_start()); |
| result.load_size = loader.load_size(); |
| result.load_bias = loader.load_bias(); |
| result.phdr = loader.loaded_phdr(); |
| result.phdr_count = loader.phdr_count(); |
| result.reserved_mapping = loader.ReleaseMapping(); |
| } |
| return result; |
| } |
| |
| } // namespace crazy |