/* * Copyright (C) 2008 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_cfi.h" #include "linker_debug.h" #include "linker_globals.h" #include "platform/bionic/page.h" #include #include #include #include // Update shadow without making it writable by preparing the data on the side and mremap-ing it in // place. class ShadowWrite { char* shadow_start; char* shadow_end; char* aligned_start; char* aligned_end; char* tmp_start; public: ShadowWrite(uint16_t* s, uint16_t* e) { shadow_start = reinterpret_cast(s); shadow_end = reinterpret_cast(e); aligned_start = reinterpret_cast(PAGE_START(reinterpret_cast(shadow_start))); aligned_end = reinterpret_cast(PAGE_END(reinterpret_cast(shadow_end))); tmp_start = reinterpret_cast(mmap(nullptr, aligned_end - aligned_start, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); CHECK(tmp_start != MAP_FAILED); mprotect(aligned_start, aligned_end - aligned_start, PROT_READ); memcpy(tmp_start, aligned_start, shadow_start - aligned_start); memcpy(tmp_start + (shadow_end - aligned_start), shadow_end, aligned_end - shadow_end); } uint16_t* begin() { return reinterpret_cast(tmp_start + (shadow_start - aligned_start)); } uint16_t* end() { return reinterpret_cast(tmp_start + (shadow_end - aligned_start)); } ~ShadowWrite() { size_t size = aligned_end - aligned_start; mprotect(tmp_start, size, PROT_READ); void* res = mremap(tmp_start, size, size, MREMAP_MAYMOVE | MREMAP_FIXED, reinterpret_cast(aligned_start)); CHECK(res != MAP_FAILED); } }; void CFIShadowWriter::FixupVmaName() { prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, *shadow_start, kShadowSize, "cfi shadow"); } void CFIShadowWriter::AddConstant(uintptr_t begin, uintptr_t end, uint16_t v) { uint16_t* shadow_begin = MemToShadow(begin); uint16_t* shadow_end = MemToShadow(end - 1) + 1; ShadowWrite sw(shadow_begin, shadow_end); std::fill(sw.begin(), sw.end(), v); } void CFIShadowWriter::AddUnchecked(uintptr_t begin, uintptr_t end) { AddConstant(begin, end, kUncheckedShadow); } void CFIShadowWriter::AddInvalid(uintptr_t begin, uintptr_t end) { AddConstant(begin, end, kInvalidShadow); } void CFIShadowWriter::Add(uintptr_t begin, uintptr_t end, uintptr_t cfi_check) { CHECK((cfi_check & (kCfiCheckAlign - 1)) == 0); // Don't fill anything below cfi_check. We can not represent those addresses // in the shadow, and must make sure at codegen to place all valid call // targets above cfi_check. begin = std::max(begin, cfi_check) & ~(kShadowAlign - 1); uint16_t* shadow_begin = MemToShadow(begin); uint16_t* shadow_end = MemToShadow(end - 1) + 1; ShadowWrite sw(shadow_begin, shadow_end); uint16_t sv_begin = ((begin + kShadowAlign - cfi_check) >> kCfiCheckGranularity) + kRegularShadowMin; // With each step of the loop below, __cfi_check address computation base is increased by // 2**ShadowGranularity. // To compensate for that, each next shadow value must be increased by 2**ShadowGranularity / // 2**CfiCheckGranularity. uint16_t sv_step = 1 << (kShadowGranularity - kCfiCheckGranularity); uint16_t sv = sv_begin; for (uint16_t& s : sw) { if (sv < sv_begin) { // If shadow value wraps around, also fall back to unchecked. This means the binary is too // large. FIXME: consider using a (slow) resolution function instead. s = kUncheckedShadow; continue; } // If there is something there already, fall back to unchecked. This may happen in rare cases // with MAP_FIXED libraries. FIXME: consider using a (slow) resolution function instead. s = (s == kInvalidShadow) ? sv : kUncheckedShadow; sv += sv_step; } } static soinfo* find_libdl(soinfo* solist) { for (soinfo* si = solist; si != nullptr; si = si->next) { if (strcmp(si->get_soname(), "libdl.so") == 0) { return si; } } return nullptr; } static uintptr_t soinfo_find_symbol(soinfo* si, const char* s) { SymbolName name(s); if (const ElfW(Sym)* sym = si->find_symbol_by_name(name, nullptr)) { return si->resolve_symbol_address(sym); } return 0; } uintptr_t soinfo_find_cfi_check(soinfo* si) { return soinfo_find_symbol(si, "__cfi_check"); } uintptr_t CFIShadowWriter::MapShadow() { void* p = mmap(nullptr, kShadowSize, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0); CHECK(p != MAP_FAILED); return reinterpret_cast(p); } bool CFIShadowWriter::AddLibrary(soinfo* si) { CHECK(shadow_start != nullptr); if (si->base == 0 || si->size == 0) { return true; } uintptr_t cfi_check = soinfo_find_cfi_check(si); if (cfi_check == 0) { INFO("[ CFI add 0x%zx + 0x%zx %s ]", static_cast(si->base), static_cast(si->size), si->get_soname()); AddUnchecked(si->base, si->base + si->size); return true; } INFO("[ CFI add 0x%zx + 0x%zx %s: 0x%zx ]", static_cast(si->base), static_cast(si->size), si->get_soname(), cfi_check); #ifdef __arm__ // Require Thumb encoding. if ((cfi_check & 1UL) != 1UL) { DL_ERR("__cfi_check in not a Thumb function in the library \"%s\"", si->get_soname()); return false; } cfi_check &= ~1UL; #endif if ((cfi_check & (kCfiCheckAlign - 1)) != 0) { DL_ERR("unaligned __cfi_check in the library \"%s\"", si->get_soname()); return false; } Add(si->base, si->base + si->size, cfi_check); return true; } // Pass the shadow mapping address to libdl.so. In return, we get an pointer to the location // libdl.so uses to store the address. bool CFIShadowWriter::NotifyLibDl(soinfo* solist, uintptr_t p) { soinfo* libdl = find_libdl(solist); if (libdl == nullptr) { DL_ERR("CFI could not find libdl"); return false; } uintptr_t cfi_init = soinfo_find_symbol(libdl, "__cfi_init"); CHECK(cfi_init != 0); shadow_start = reinterpret_cast(cfi_init)(p); CHECK(shadow_start != nullptr); CHECK(*shadow_start == p); mprotect(shadow_start, PAGE_SIZE, PROT_READ); return true; } bool CFIShadowWriter::MaybeInit(soinfo* new_si, soinfo* solist) { CHECK(initial_link_done); CHECK(shadow_start == nullptr); // Check if CFI shadow must be initialized at this time. bool found = false; if (new_si == nullptr) { // This is the case when we've just completed the initial link. There may have been earlier // calls to MaybeInit that were skipped. Look though the entire solist. for (soinfo* si = solist; si != nullptr; si = si->next) { if (soinfo_find_cfi_check(si)) { found = true; break; } } } else { // See if the new library uses CFI. found = soinfo_find_cfi_check(new_si); } // Nothing found. if (!found) { return true; } // Init shadow and add all currently loaded libraries (not just the new ones). if (!NotifyLibDl(solist, MapShadow())) return false; for (soinfo* si = solist; si != nullptr; si = si->next) { if (!AddLibrary(si)) return false; } FixupVmaName(); return true; } bool CFIShadowWriter::AfterLoad(soinfo* si, soinfo* solist) { if (!initial_link_done) { // Too early. return true; } if (shadow_start == nullptr) { return MaybeInit(si, solist); } // Add the new library to the CFI shadow. if (!AddLibrary(si)) return false; FixupVmaName(); return true; } void CFIShadowWriter::BeforeUnload(soinfo* si) { if (shadow_start == nullptr) return; if (si->base == 0 || si->size == 0) return; INFO("[ CFI remove 0x%zx + 0x%zx: %s ]", static_cast(si->base), static_cast(si->size), si->get_soname()); AddInvalid(si->base, si->base + si->size); FixupVmaName(); } bool CFIShadowWriter::InitialLinkDone(soinfo* solist) { CHECK(!initial_link_done); initial_link_done = true; return MaybeInit(nullptr, solist); } // Find __cfi_check in the caller and let it handle the problem. Since caller_pc is likely not a // valid CFI target, we can not use CFI shadow for lookup. This does not need to be fast, do the // regular symbol lookup. void CFIShadowWriter::CfiFail(uint64_t CallSiteTypeId, void* Ptr, void* DiagData, void* CallerPc) { soinfo* si = find_containing_library(CallerPc); if (!si) { __builtin_trap(); } uintptr_t cfi_check = soinfo_find_cfi_check(si); if (!cfi_check) { __builtin_trap(); } reinterpret_cast(cfi_check)(CallSiteTypeId, Ptr, DiagData); }