blob: d3783cf2ec78152131470fa8873f0a3a6e977972 [file] [log] [blame]
/*
* Copyright (C) 2012 The Android Open Source Project
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include "linker_phdr.h"
#include <linux/prctl.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <unistd.h>
#include "linker_debug.h"
#include "linker_dlwarning.h"
#include "linker_globals.h"
#include "platform/bionic/macros.h"
#include "platform/bionic/page.h"
#include <string>
static bool g_enable_16kb_app_compat;
static inline bool segment_contains_prefix(const ElfW(Phdr)* segment, const ElfW(Phdr)* prefix) {
return segment && prefix && segment->p_vaddr == prefix->p_vaddr;
}
void set_16kb_appcompat_mode(bool enable_app_compat) {
g_enable_16kb_app_compat = enable_app_compat;
}
bool get_16kb_appcompat_mode() {
return g_enable_16kb_app_compat;
}
/*
* Returns true if the ELF contains at most 1 RELRO segment; and populates @relro_phdr
* with the relro phdr or nullptr if none.
*
* Returns false if more than 1 RELRO segments are found.
*/
bool ElfReader::HasAtMostOneRelroSegment(const ElfW(Phdr)** relro_phdr) {
const ElfW(Phdr)* relro = nullptr;
for (size_t i = 0; i < phdr_num_; ++i) {
const ElfW(Phdr)* phdr = &phdr_table_[i];
if (phdr->p_type != PT_GNU_RELRO) {
continue;
}
if (relro == nullptr) {
relro = phdr;
} else {
return false;
}
}
*relro_phdr = relro;
return true;
}
/*
* In 16KiB compatibility mode ELFs with the following segment layout
* can be loaded successfully:
*
* ┌────────────┬─────────────────────────┬────────────┐
* │ │ │ │
* │ (RO|RX)* │ (RW - RELRO prefix)? │ (RW)* │
* │ │ │ │
* └────────────┴─────────────────────────┴────────────┘
*
* In other words, compatible layouts have:
* - zero or more RO or RX segments;
* - followed by zero or one RELRO prefix;
* - followed by zero or more RW segments (this can include the RW
* suffix from the segment containing the RELRO prefix, if any)
*
* In 16KiB compat mode, after relocation, the ELF is layout in virtual
* memory is as shown below:
* ┌──────────────────────────────────────┬────────────┐
* │ │ │
* │ (RX)? │ (RW)? │
* │ │ │
* └──────────────────────────────────────┴────────────┘
*
* In compat mode:
* - the RO and RX segments along with the RELRO prefix are protected
* as RX;
* - and the RW segments along with RW suffix from the relro segment,
* if any; are RW protected.
*
* This allows for the single RX|RW permission boundary to be aligned with
* a 16KiB page boundary; since a single page cannot share multiple
* permissions.
*
* IsEligibleFor16KiBAppCompat() identifies compatible ELFs and populates @vaddr
* with the boundary between RX|RW portions.
*
* Returns true if the ELF can be loaded in compat mode, else false.
*/
bool ElfReader::IsEligibleFor16KiBAppCompat(ElfW(Addr)* vaddr) {
const ElfW(Phdr)* relro_phdr = nullptr;
if (!HasAtMostOneRelroSegment(&relro_phdr)) {
DL_WARN("\"%s\": Compat loading failed: Multiple RELRO segments found", name_.c_str());
return false;
}
const ElfW(Phdr)* last_rw = nullptr;
const ElfW(Phdr)* first_rw = nullptr;
for (size_t i = 0; i < phdr_num_; ++i) {
const ElfW(Phdr)* curr = &phdr_table_[i];
const ElfW(Phdr)* prev = (i > 0) ? &phdr_table_[i - 1] : nullptr;
if (curr->p_type != PT_LOAD) {
continue;
}
int prot = PFLAGS_TO_PROT(curr->p_flags);
if ((prot & PROT_WRITE) && (prot & PROT_READ)) {
if (!first_rw) {
first_rw = curr;
}
if (last_rw && last_rw != prev) {
DL_WARN("\"%s\": Compat loading failed: ELF contains multiple non-adjacent RW segments",
name_.c_str());
return false;
}
last_rw = curr;
}
}
if (!relro_phdr) {
*vaddr = __builtin_align_down(first_rw->p_vaddr, kCompatPageSize);
return true;
}
// The RELRO segment is present, it must be the prefix of the first RW segment.
if (!segment_contains_prefix(first_rw, relro_phdr)) {
DL_WARN("\"%s\": Compat loading failed: RELRO is not in the first RW segment",
name_.c_str());
return false;
}
uint64_t end;
if (__builtin_add_overflow(relro_phdr->p_vaddr, relro_phdr->p_memsz, &end)) {
DL_WARN("\"%s\": Compat loading failed: relro vaddr + memsz overflowed", name_.c_str());
return false;
}
*vaddr = __builtin_align_up(end, kCompatPageSize);
return true;
}
/*
* Returns the offset/shift needed to align @vaddr to a page boundary.
*/
static inline ElfW(Addr) perm_boundary_offset(const ElfW(Addr) addr) {
ElfW(Addr) offset = page_offset(addr);
return offset ? page_size() - offset : 0;
}
bool ElfReader::Setup16KiBAppCompat() {
if (!should_use_16kib_app_compat_) {
return true;
}
ElfW(Addr) rx_rw_boundary; // Permission bounadry for compat mode
if (!IsEligibleFor16KiBAppCompat(&rx_rw_boundary)) {
return false;
}
// Adjust the load_bias to position the RX|RW boundary on a page boundary
load_bias_ += perm_boundary_offset(rx_rw_boundary);
// RW region (.data, .bss ...)
ElfW(Addr) rw_start = load_bias_ + rx_rw_boundary;
ElfW(Addr) rw_size = load_size_ - (rw_start - reinterpret_cast<ElfW(Addr)>(load_start_));
CHECK(rw_start % getpagesize() == 0);
CHECK(rw_size % getpagesize() == 0);
// Compat RELRO (RX) region (.text, .data.relro, ...)
compat_relro_start_ = reinterpret_cast<ElfW(Addr)>(load_start_);
compat_relro_size_ = load_size_ - rw_size;
// Label the ELF VMA, since compat mode uses anonymous mappings.
std::string compat_name = name_ + " (compat loaded)";
prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, load_start_, load_size_, compat_name.c_str());
return true;
}
bool ElfReader::CompatMapSegment(size_t seg_idx, size_t len) {
const ElfW(Phdr)* phdr = &phdr_table_[seg_idx];
// NOTE: The compat(legacy) page size (4096) must be used when aligning
// the 4KiB segments for loading (reading). The larger 16KiB page size
// will lead to overwriting adjacent segments since the ELF's segment(s)
// are not 16KiB aligned.
void* start = reinterpret_cast<void*>(__builtin_align_down(phdr->p_vaddr + load_bias_, kCompatPageSize));
// The ELF could be being loaded directly from a zipped APK,
// the zip offset must be added to find the segment offset.
const ElfW(Addr) offset = file_offset_ + __builtin_align_down(phdr->p_offset, kCompatPageSize);
CHECK(should_use_16kib_app_compat_);
// Since the 4KiB max-page-size ELF is not properly aligned, loading it by
// directly mmapping the ELF file is not feasible.
// Instead, read the ELF contents into the anonymous RW mapping.
if (TEMP_FAILURE_RETRY(pread64(fd_, start, len, offset)) == -1) {
DL_ERR("Compat loading: \"%s\" failed to read LOAD segment %zu: %m", name_.c_str(), seg_idx);
return false;
}
return true;
}