if (relro_phdr) { // 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\": RX|RW compat loading failed: RELRO is not in the first RW segment",
name_.c_str()); returnfalse;
}
// The extra offset applied to the compat-loaded binary must respect its maximum required // alignment, otherwise we should use RWX compat mode, which doesn't apply any extra offsets.
uint64_t max_section_align = get_max_section_alignment(shdr_table_, shdr_num_);
uint64_t offset = perm_boundary_offset(*vaddr); if (offset % max_section_align != 0) {
DL_WARN("\"%s\": RX|RW compat loading failed: maximum section alignment requirement of %" PRIu64 " is too strict to apply an offset of %" PRIu64 " bytes",
name_.c_str(), max_section_align, offset); returnfalse;
}
returntrue;
}
enum relro_pos_t {
NONE, // No RELRO in the LOAD segment
PREFIX, // RELRO is a prefix of the LOAD segment
MIDDLE, // RELRO is contained in the middle of the LOAD segment
SUFFIX, // RELRO is a suffix of the LOAD segment
ENTIRE, // RELRO is the entire LOAD segment
ERROR, // The relro size invalid (spans multiple segments?)
};
switch (relro_pos) { case NONE: return""; case PREFIX: return"(PREFIX)"; case MIDDLE: return"(MIDDLE)"; case SUFFIX: return"(SUFFIX)"; case ENTIRE: return"(ENTIRE)"; case ERROR: return"(ERROR)";
}
for (struct segment& segment : segments) {
layout.emplace_back(segment_format(segment));
}
if (layout.empty()) return"";
return std::accumulate(std::next(layout.begin()), layout.end(), layout[0],
[](std::string a, std::string b) { return std::move(a) + "," + b; });
}
void ElfReader::LabelCompatVma() { // Label the ELF VMA, since compat mode uses anonymous mappings, and some applications may rely on // them having their name set to the ELF's path. // Since kernel 5.10 it is safe to use non-global storage for the VMA name because it will be // copied into the kernel. 16KiB pages require a minimum kernel version of 6.1 so we can safely // use a stack-allocated buffer here. char vma_name_buffer[kVmaNameLimit] = {};
format_left_truncated_vma_anon_name(vma_name_buffer, sizeof(vma_name_buffer), "16k:", name_.c_str(), ""); if (prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, load_start_, load_size_, vma_name_buffer) != 0) {
DL_WARN("\"%s\": Failed to rename 16KiB compat segment: %m", name_.c_str());
}
}
void ElfReader::SetupRXRWAppCompat(ElfW(Addr) rx_rw_boundary) { // Adjust the load_bias to position the RX|RW boundary on a page boundary
load_bias_ += perm_boundary_offset(rx_rw_boundary);
// 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.
// 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); returnfalse;
}
returntrue;
}
static size_t phdr_table_get_relro_min_align(const ElfW(Phdr)* relro_phdr, const ElfW(Phdr)* phdr_table, size_t phdr_count) { for (size_t index = 0; index < phdr_count; ++index) { const ElfW(Phdr)* phdr = &phdr_table[index];
if (phdr->p_type != PT_LOAD) { continue;
}
// Only check for the case, where the relro segment is a prefix of a load segment. Conventional // linkers will only generate binaries where the relro segment is either the prefix of the first // RW load segment, or is entirely contained in the first RW segment. if (phdr->p_vaddr == relro_phdr->p_vaddr) { // No extra alignment checks needed if the whole load segment is relro. if (phdr->p_memsz <= relro_phdr->p_memsz) { return0;
}
ElfW(Addr) relro_end = relro_phdr->p_vaddr + relro_phdr->p_memsz; // Alignments must be powers of two, so the RELRO segment's alignment can be determined by // calculating its lowest set bit with (n & -n).
size_t relro_align = static_cast<size_t>(relro_end & -relro_end); // We only care about relro segments that are aligned to at least 4KiB. This is always // expected for outputs of a conventional linker. return relro_align >= kCompatPageSize ? relro_align : 0;
}
} return0;
}
/* *Inthebasepagesizeis16KiBandtheRELRO'sendalignmentislessthanmin_align_; *overridemin_align_withtherelro'sendalignment.ThisensuresthattheELFis *loadedincompatmodeeveniftheLOADsegmentsare16KBaligned. *Linkerbug:https://sourceware.org/bugzilla/show_bug.cgi?id=28824
*/ void ElfReader::FixMinAlignFor16KiB() { // A binary with LOAD segment alignments of at least 16KiB can still be incompatible with 16KiB // page sizes if the first RW segment has a RELRO prefix ending at a non-16KiB-aligned address. We // need to check for this possibility here and adjust min_align_ accordingly. // We only check if the ELF file contains a single RELRO segment, because that's what the 16KiB // compatibility loader can handle. const ElfW(Phdr)* relro_phdr = nullptr; if (HasAtMostOneRelroSegment(&relro_phdr) && relro_phdr != nullptr) {
size_t relro_min_align = phdr_table_get_relro_min_align(relro_phdr, phdr_table_, phdr_num_); if (relro_min_align) {
min_align_ = std::min(min_align_, relro_min_align);
}
}
if (min_align_ < 16 * 1024) { return;
}
// In some apps we also observe binaries where the LOAD segments overlap when loaded with 16KiB // pages, even though they have p_align >= 16KiB. Strictly speaking this is not a violation of the // ELF specification, but it causes parts of the earlier LOAD segment to be discarded, which can // cause issues. Therefore we check for this case as well, and set min_align_ to 4KiB if we detect // it, ensuring that the binary is loaded in 16KiB compatibility mode.
ElfW(Addr) prev_load_end = 0; for (size_t i = 0; i < phdr_num_; ++i) { const ElfW(Phdr)* phdr = &phdr_table_[i]; if (phdr->p_type != PT_LOAD) { continue;
}
if (note_gnu_property_->IsBTICompatible()) {
prot |= PROT_BTI;
} #endif
if (mprotect(reinterpret_cast<void*>(compat_code_start_), compat_code_size_, prot)) {
DL_ERR("failed to set execute permission for compat loaded binary \"%s\": %m",
get_realpath()); returnfalse;
}
if (should_16kib_app_compat_use_rwx_) { return protect_16kib_app_compat_middle_pages();
}
if (phdr_table == nullptr || phdr_count == 0) returntrue;
// We have to ensure that the RELRO protection is applied after // the LOAD segment's because it could be overwritten by them. // First pass: Handle all LOAD segments to restore original permissions. for (size_t i = 0; i < phdr_count; ++i) { const ElfW(Phdr)* phdr_ptr = &phdr_table[i]; if (phdr_ptr->p_type != PT_LOAD) continue;
if (!protect_segment_middle_pages(this, phdr_ptr)) returnfalse;
}
// Second pass: Handle RELRO segments. for (size_t i = 0; i < phdr_count; ++i) { const ElfW(Phdr)* phdr_ptr = &phdr_table[i]; if (phdr_ptr->p_type == PT_GNU_RELRO) { if (!protect_segment_middle_pages(this, phdr_ptr)) returnfalse;
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.