| /* | |
| * 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 <sys/mman.h> | |
| #include <sys/prctl.h> | |
| #include <sys/types.h> | |
| #include <cstdint> | |
| // 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<char*>(s); | |
| shadow_end = reinterpret_cast<char*>(e); | |
| aligned_start = reinterpret_cast<char*>(page_start(reinterpret_cast<uintptr_t>(shadow_start))); | |
| aligned_end = reinterpret_cast<char*>(page_end(reinterpret_cast<uintptr_t>(shadow_end))); | |
| tmp_start = | |
| reinterpret_cast<char*>(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<uint16_t*>(tmp_start + (shadow_start - aligned_start)); | |
| } | |
| uint16_t* end() { | |
| return reinterpret_cast<uint16_t*>(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<void*>(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<uintptr_t>(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<uintptr_t>(si->base), | |
| static_cast<uintptr_t>(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<uintptr_t>(si->base), | |
| static_cast<uintptr_t>(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<uintptr_t* (*)(uintptr_t)>(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<uintptr_t>(si->base), | |
| static_cast<uintptr_t>(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<CFICheckFn>(cfi_check)(CallSiteTypeId, Ptr, DiagData); | |
| } |