// Copyright (c) 2013 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_relro.h" #include #include #include #include "crazy_linker_elf_relocations.h" #include "crazy_linker_elf_view.h" #include "crazy_linker_memory_mapping.h" #include "crazy_linker_util.h" namespace crazy { namespace { inline bool PageEquals(const char* p1, const char* p2) { return ::memcmp(p1, p2, PAGE_SIZE) == 0; } // Swap pages between |addr| and |addr + size| with the bytes // from the ashmem region identified by |fd|, starting from // a given |offset|. On failure return false and set |error| message. bool SwapPagesFromFd(void* addr, size_t size, int fd, size_t offset, Error* error) { // Unmap current pages. if (::munmap(addr, size) < 0) { error->Format("%s: Could not unmap %p-%p: %s", __FUNCTION__, addr, (char*)addr + size, strerror(errno)); return false; } // Remap the fd pages at the same location now. void* new_map = ::mmap(addr, size, PROT_READ, MAP_FIXED | MAP_SHARED, fd, static_cast(offset)); if (new_map == MAP_FAILED) { char* p = reinterpret_cast(addr); error->Format("%s: Could not map %p-%p: %s", __FUNCTION__, p, p + size, strerror(errno)); return false; } // TODO(digit): Is this necessary? #ifdef __arm__ __clear_cache(addr, (char*)addr + size); #endif // Done. return true; } } // namespace bool SharedRelro::Allocate(size_t relro_size, const char* library_name, Error* error) { // Allocate a new ashmem region. String name("RELRO:"); name += library_name; if (!ashmem_.Allocate(relro_size, name.c_str())) { error->Format("Could not allocate RELRO ashmem region for %s: %s", library_name, strerror(errno)); return false; } start_ = 0; size_ = relro_size; return true; } bool SharedRelro::CopyFrom(size_t relro_start, size_t relro_size, Error* error) { // Map it in the process. ScopedMemoryMapping map; if (!map.Allocate(NULL, relro_size, MemoryMapping::CAN_WRITE, ashmem_.fd())) { error->Format("Could not allocate RELRO mapping: %s", strerror(errno)); return false; } // Copy process' RELRO into it. ::memcpy(map.Get(), reinterpret_cast(relro_start), relro_size); // Unmap it. map.Deallocate(); // Everything's good. start_ = relro_start; size_ = relro_size; return true; } bool SharedRelro::CopyFromRelocated(const ElfView* view, size_t load_address, size_t relro_start, size_t relro_size, Error* error) { // Offset of RELRO section in current library. size_t relro_offset = relro_start - view->load_address(); ElfRelocations relocations; if (!relocations.Init(view, error)) return false; // Map the region in memory (any address). ScopedMemoryMapping map; if (!map.Allocate( NULL, relro_size, MemoryMapping::CAN_READ_WRITE, ashmem_.fd())) { error->Format("Could not allocate RELRO mapping for: %s", strerror(errno)); return false; } // Copy and relocate. relocations.CopyAndRelocate(relro_start, reinterpret_cast(map.Get()), load_address + relro_offset, relro_size); // Unmap it. map.Deallocate(); start_ = load_address + relro_offset; size_ = relro_size; return true; } bool SharedRelro::ForceReadOnly(Error* error) { // Ensure the ashmem region content isn't writable anymore. if (!ashmem_.SetProtectionFlags(PROT_READ)) { error->Format("Could not make RELRO ashmem region read-only: %s", strerror(errno)); return false; } return true; } bool SharedRelro::InitFrom(size_t relro_start, size_t relro_size, int ashmem_fd, Error* error) { // Create temporary mapping of the ashmem region. ScopedMemoryMapping fd_map; LOG("%s: Entering addr=%p size=%p fd=%d\n", __FUNCTION__, (void*)relro_start, (void*)relro_size, ashmem_fd); // Sanity check: Ashmem file descriptor must be read-only. if (!AshmemRegion::CheckFileDescriptorIsReadOnly(ashmem_fd)) { error->Format("Ashmem file descriptor is not read-only: %s\n", strerror(errno)); return false; } if (!fd_map.Allocate(NULL, relro_size, MemoryMapping::CAN_READ, ashmem_fd)) { error->Format("Cannot map RELRO ashmem region as read-only: %s\n", strerror(errno)); return false; } LOG("%s: mapping allocated at %p\n", __FUNCTION__, fd_map.Get()); char* cur_page = reinterpret_cast(relro_start); char* fd_page = static_cast(fd_map.Get()); size_t p = 0; size_t size = relro_size; size_t similar_size = 0; do { // Skip over dissimilar pages. while (p < size && !PageEquals(cur_page + p, fd_page + p)) { p += PAGE_SIZE; } // Count similar pages. size_t p2 = p; while (p2 < size && PageEquals(cur_page + p2, fd_page + p2)) { p2 += PAGE_SIZE; } if (p2 > p) { // Swap pages between |pos| and |pos2|. LOG("%s: Swap pages at %p-%p\n", __FUNCTION__, cur_page + p, cur_page + p2); if (!SwapPagesFromFd(cur_page + p, p2 - p, ashmem_fd, p, error)) return false; similar_size += (p2 - p); } p = p2; } while (p < size); LOG("%s: Swapped %d pages over %d (%d %%, %d KB not shared)\n", __FUNCTION__, similar_size / PAGE_SIZE, size / PAGE_SIZE, similar_size * 100 / size, (size - similar_size) / 4096); if (similar_size == 0) return false; start_ = relro_start; size_ = relro_size; return true; } } // namespace crazy