|  | /* | 
|  | * 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); | 
|  | } |