Evgenii Stepanov | 0a3637d | 2016-07-06 13:20:59 -0700 | [diff] [blame] | 1 | /* |
Dimitry Ivanov | bcc4da9 | 2017-02-15 15:31:13 -0800 | [diff] [blame^] | 2 | * Copyright (C) 2008 The Android Open Source Project |
| 3 | * All rights reserved. |
Evgenii Stepanov | 0a3637d | 2016-07-06 13:20:59 -0700 | [diff] [blame] | 4 | * |
Dimitry Ivanov | bcc4da9 | 2017-02-15 15:31:13 -0800 | [diff] [blame^] | 5 | * Redistribution and use in source and binary forms, with or without |
| 6 | * modification, are permitted provided that the following conditions |
| 7 | * are met: |
| 8 | * * Redistributions of source code must retain the above copyright |
| 9 | * notice, this list of conditions and the following disclaimer. |
| 10 | * * Redistributions in binary form must reproduce the above copyright |
| 11 | * notice, this list of conditions and the following disclaimer in |
| 12 | * the documentation and/or other materials provided with the |
| 13 | * distribution. |
Evgenii Stepanov | 0a3637d | 2016-07-06 13:20:59 -0700 | [diff] [blame] | 14 | * |
Dimitry Ivanov | bcc4da9 | 2017-02-15 15:31:13 -0800 | [diff] [blame^] | 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| 16 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| 17 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
| 18 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
| 19 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
| 20 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
| 21 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS |
| 22 | * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
| 23 | * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| 24 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT |
| 25 | * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| 26 | * SUCH DAMAGE. |
Evgenii Stepanov | 0a3637d | 2016-07-06 13:20:59 -0700 | [diff] [blame] | 27 | */ |
| 28 | |
| 29 | #include "linker_cfi.h" |
| 30 | |
| 31 | #include "linker_debug.h" |
| 32 | #include "linker_globals.h" |
| 33 | #include "private/bionic_page.h" |
| 34 | #include "private/bionic_prctl.h" |
| 35 | |
| 36 | #include <sys/mman.h> |
| 37 | #include <sys/types.h> |
| 38 | #include <cstdint> |
| 39 | |
| 40 | // Update shadow without making it writable by preparing the data on the side and mremap-ing it in |
| 41 | // place. |
| 42 | class ShadowWrite { |
| 43 | char* shadow_start; |
| 44 | char* shadow_end; |
| 45 | char* aligned_start; |
| 46 | char* aligned_end; |
| 47 | char* tmp_start; |
| 48 | |
| 49 | public: |
| 50 | ShadowWrite(uint16_t* s, uint16_t* e) { |
| 51 | shadow_start = reinterpret_cast<char*>(s); |
| 52 | shadow_end = reinterpret_cast<char*>(e); |
| 53 | aligned_start = reinterpret_cast<char*>(PAGE_START(reinterpret_cast<uintptr_t>(shadow_start))); |
| 54 | aligned_end = reinterpret_cast<char*>(PAGE_END(reinterpret_cast<uintptr_t>(shadow_end))); |
| 55 | tmp_start = |
| 56 | reinterpret_cast<char*>(mmap(nullptr, aligned_end - aligned_start, PROT_READ | PROT_WRITE, |
| 57 | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); |
| 58 | CHECK(tmp_start != MAP_FAILED); |
| 59 | memcpy(tmp_start, aligned_start, shadow_start - aligned_start); |
| 60 | memcpy(tmp_start + (shadow_end - aligned_start), shadow_end, aligned_end - shadow_end); |
| 61 | } |
| 62 | |
| 63 | uint16_t* begin() { |
| 64 | return reinterpret_cast<uint16_t*>(tmp_start + (shadow_start - aligned_start)); |
| 65 | } |
| 66 | |
| 67 | uint16_t* end() { |
| 68 | return reinterpret_cast<uint16_t*>(tmp_start + (shadow_end - aligned_start)); |
| 69 | } |
| 70 | |
| 71 | ~ShadowWrite() { |
| 72 | size_t size = aligned_end - aligned_start; |
| 73 | mprotect(tmp_start, size, PROT_READ); |
| 74 | void* res = mremap(tmp_start, size, size, MREMAP_MAYMOVE | MREMAP_FIXED, |
| 75 | reinterpret_cast<void*>(aligned_start)); |
| 76 | CHECK(res != MAP_FAILED); |
| 77 | } |
| 78 | }; |
| 79 | |
| 80 | void CFIShadowWriter::FixupVmaName() { |
| 81 | prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, *shadow_start, kShadowSize, "cfi shadow"); |
| 82 | } |
| 83 | |
| 84 | void CFIShadowWriter::AddConstant(uintptr_t begin, uintptr_t end, uint16_t v) { |
| 85 | uint16_t* shadow_begin = MemToShadow(begin); |
| 86 | uint16_t* shadow_end = MemToShadow(end - 1) + 1; |
| 87 | |
| 88 | ShadowWrite sw(shadow_begin, shadow_end); |
| 89 | std::fill(sw.begin(), sw.end(), v); |
| 90 | } |
| 91 | |
| 92 | void CFIShadowWriter::AddUnchecked(uintptr_t begin, uintptr_t end) { |
| 93 | AddConstant(begin, end, kUncheckedShadow); |
| 94 | } |
| 95 | |
| 96 | void CFIShadowWriter::AddInvalid(uintptr_t begin, uintptr_t end) { |
| 97 | AddConstant(begin, end, kInvalidShadow); |
| 98 | } |
| 99 | |
| 100 | void CFIShadowWriter::Add(uintptr_t begin, uintptr_t end, uintptr_t cfi_check) { |
| 101 | CHECK((cfi_check & (kCfiCheckAlign - 1)) == 0); |
| 102 | |
| 103 | // Don't fill anything below cfi_check. We can not represent those addresses |
| 104 | // in the shadow, and must make sure at codegen to place all valid call |
| 105 | // targets above cfi_check. |
| 106 | begin = std::max(begin, cfi_check) & ~(kShadowAlign - 1); |
| 107 | uint16_t* shadow_begin = MemToShadow(begin); |
| 108 | uint16_t* shadow_end = MemToShadow(end - 1) + 1; |
| 109 | |
| 110 | ShadowWrite sw(shadow_begin, shadow_end); |
Evgenii Stepanov | 636a2ec | 2017-01-20 13:47:04 -0800 | [diff] [blame] | 111 | uint16_t sv_begin = ((begin + kShadowAlign - cfi_check) >> kCfiCheckGranularity) + kRegularShadowMin; |
Evgenii Stepanov | 0a3637d | 2016-07-06 13:20:59 -0700 | [diff] [blame] | 112 | |
| 113 | // With each step of the loop below, __cfi_check address computation base is increased by |
| 114 | // 2**ShadowGranularity. |
| 115 | // To compensate for that, each next shadow value must be increased by 2**ShadowGranularity / |
| 116 | // 2**CfiCheckGranularity. |
| 117 | uint16_t sv_step = 1 << (kShadowGranularity - kCfiCheckGranularity); |
Evgenii Stepanov | 636a2ec | 2017-01-20 13:47:04 -0800 | [diff] [blame] | 118 | uint16_t sv = sv_begin; |
Evgenii Stepanov | 0a3637d | 2016-07-06 13:20:59 -0700 | [diff] [blame] | 119 | for (uint16_t& s : sw) { |
Evgenii Stepanov | 636a2ec | 2017-01-20 13:47:04 -0800 | [diff] [blame] | 120 | if (sv < sv_begin) { |
| 121 | // If shadow value wraps around, also fall back to unchecked. This means the binary is too |
| 122 | // large. FIXME: consider using a (slow) resolution function instead. |
| 123 | s = kUncheckedShadow; |
| 124 | continue; |
| 125 | } |
Evgenii Stepanov | 0a3637d | 2016-07-06 13:20:59 -0700 | [diff] [blame] | 126 | // If there is something there already, fall back to unchecked. This may happen in rare cases |
| 127 | // with MAP_FIXED libraries. FIXME: consider using a (slow) resolution function instead. |
| 128 | s = (s == kInvalidShadow) ? sv : kUncheckedShadow; |
| 129 | sv += sv_step; |
| 130 | } |
| 131 | } |
| 132 | |
| 133 | static soinfo* find_libdl(soinfo* solist) { |
| 134 | for (soinfo* si = solist; si != nullptr; si = si->next) { |
| 135 | const char* soname = si->get_soname(); |
| 136 | if (soname && strcmp(soname, "libdl.so") == 0) { |
| 137 | return si; |
| 138 | } |
| 139 | } |
| 140 | return nullptr; |
| 141 | } |
| 142 | |
| 143 | static uintptr_t soinfo_find_symbol(soinfo* si, const char* s) { |
| 144 | SymbolName name(s); |
| 145 | const ElfW(Sym) * sym; |
| 146 | if (si->find_symbol_by_name(name, nullptr, &sym) && sym) { |
| 147 | return si->resolve_symbol_address(sym); |
| 148 | } |
| 149 | return 0; |
| 150 | } |
| 151 | |
| 152 | uintptr_t soinfo_find_cfi_check(soinfo* si) { |
| 153 | return soinfo_find_symbol(si, "__cfi_check"); |
| 154 | } |
| 155 | |
| 156 | uintptr_t CFIShadowWriter::MapShadow() { |
| 157 | void* p = |
| 158 | mmap(nullptr, kShadowSize, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0); |
| 159 | CHECK(p != MAP_FAILED); |
| 160 | return reinterpret_cast<uintptr_t>(p); |
| 161 | } |
| 162 | |
| 163 | bool CFIShadowWriter::AddLibrary(soinfo* si) { |
| 164 | CHECK(shadow_start != nullptr); |
| 165 | if (si->base == 0 || si->size == 0) { |
| 166 | return true; |
| 167 | } |
| 168 | uintptr_t cfi_check = soinfo_find_cfi_check(si); |
| 169 | if (cfi_check == 0) { |
| 170 | INFO("[ CFI add 0x%zx + 0x%zx %s ]", static_cast<uintptr_t>(si->base), |
| 171 | static_cast<uintptr_t>(si->size), si->get_soname()); |
| 172 | AddUnchecked(si->base, si->base + si->size); |
| 173 | return true; |
| 174 | } |
| 175 | |
| 176 | INFO("[ CFI add 0x%zx + 0x%zx %s: 0x%zx ]", static_cast<uintptr_t>(si->base), |
| 177 | static_cast<uintptr_t>(si->size), si->get_soname(), cfi_check); |
| 178 | #ifdef __arm__ |
| 179 | // Require Thumb encoding. |
| 180 | if ((cfi_check & 1UL) != 1UL) { |
| 181 | DL_ERR("__cfi_check in not a Thumb function in the library \"%s\"", si->get_soname()); |
| 182 | return false; |
| 183 | } |
| 184 | cfi_check &= ~1UL; |
| 185 | #endif |
| 186 | if ((cfi_check & (kCfiCheckAlign - 1)) != 0) { |
| 187 | DL_ERR("unaligned __cfi_check in the library \"%s\"", si->get_soname()); |
| 188 | return false; |
| 189 | } |
| 190 | Add(si->base, si->base + si->size, cfi_check); |
| 191 | return true; |
| 192 | } |
| 193 | |
| 194 | // Pass the shadow mapping address to libdl.so. In return, we get an pointer to the location |
| 195 | // libdl.so uses to store the address. |
| 196 | bool CFIShadowWriter::NotifyLibDl(soinfo* solist, uintptr_t p) { |
| 197 | soinfo* libdl = find_libdl(solist); |
| 198 | if (libdl == nullptr) { |
| 199 | DL_ERR("CFI could not find libdl"); |
| 200 | return false; |
| 201 | } |
| 202 | |
| 203 | uintptr_t cfi_init = soinfo_find_symbol(libdl, "__cfi_init"); |
| 204 | CHECK(cfi_init != 0); |
| 205 | shadow_start = reinterpret_cast<uintptr_t* (*)(uintptr_t)>(cfi_init)(p); |
| 206 | CHECK(shadow_start != nullptr); |
| 207 | CHECK(*shadow_start == p); |
Evgenii Stepanov | 68ecec1 | 2017-01-31 13:19:30 -0800 | [diff] [blame] | 208 | mprotect(shadow_start, PAGE_SIZE, PROT_READ); |
Evgenii Stepanov | 0a3637d | 2016-07-06 13:20:59 -0700 | [diff] [blame] | 209 | return true; |
| 210 | } |
| 211 | |
| 212 | bool CFIShadowWriter::MaybeInit(soinfo* new_si, soinfo* solist) { |
| 213 | CHECK(initial_link_done); |
Evgenii Stepanov | 636a2ec | 2017-01-20 13:47:04 -0800 | [diff] [blame] | 214 | CHECK(shadow_start == nullptr); |
Evgenii Stepanov | 0a3637d | 2016-07-06 13:20:59 -0700 | [diff] [blame] | 215 | // Check if CFI shadow must be initialized at this time. |
| 216 | bool found = false; |
| 217 | if (new_si == nullptr) { |
| 218 | // This is the case when we've just completed the initial link. There may have been earlier |
| 219 | // calls to MaybeInit that were skipped. Look though the entire solist. |
| 220 | for (soinfo* si = solist; si != nullptr; si = si->next) { |
| 221 | if (soinfo_find_cfi_check(si)) { |
| 222 | found = true; |
| 223 | break; |
| 224 | } |
| 225 | } |
| 226 | } else { |
| 227 | // See if the new library uses CFI. |
| 228 | found = soinfo_find_cfi_check(new_si); |
| 229 | } |
| 230 | |
| 231 | // Nothing found. |
| 232 | if (!found) { |
| 233 | return true; |
| 234 | } |
| 235 | |
| 236 | // Init shadow and add all currently loaded libraries (not just the new ones). |
| 237 | if (!NotifyLibDl(solist, MapShadow())) |
| 238 | return false; |
| 239 | for (soinfo* si = solist; si != nullptr; si = si->next) { |
| 240 | if (!AddLibrary(si)) |
| 241 | return false; |
| 242 | } |
| 243 | FixupVmaName(); |
| 244 | return true; |
| 245 | } |
| 246 | |
| 247 | bool CFIShadowWriter::AfterLoad(soinfo* si, soinfo* solist) { |
| 248 | if (!initial_link_done) { |
| 249 | // Too early. |
| 250 | return true; |
| 251 | } |
| 252 | |
| 253 | if (shadow_start == nullptr) { |
| 254 | return MaybeInit(si, solist); |
| 255 | } |
| 256 | |
| 257 | // Add the new library to the CFI shadow. |
| 258 | if (!AddLibrary(si)) |
| 259 | return false; |
| 260 | FixupVmaName(); |
| 261 | return true; |
| 262 | } |
| 263 | |
| 264 | void CFIShadowWriter::BeforeUnload(soinfo* si) { |
| 265 | if (shadow_start == nullptr) return; |
| 266 | if (si->base == 0 || si->size == 0) return; |
| 267 | INFO("[ CFI remove 0x%zx + 0x%zx: %s ]", static_cast<uintptr_t>(si->base), |
| 268 | static_cast<uintptr_t>(si->size), si->get_soname()); |
| 269 | AddInvalid(si->base, si->base + si->size); |
| 270 | FixupVmaName(); |
| 271 | } |
| 272 | |
| 273 | bool CFIShadowWriter::InitialLinkDone(soinfo* solist) { |
Evgenii Stepanov | 636a2ec | 2017-01-20 13:47:04 -0800 | [diff] [blame] | 274 | CHECK(!initial_link_done); |
Evgenii Stepanov | 0a3637d | 2016-07-06 13:20:59 -0700 | [diff] [blame] | 275 | initial_link_done = true; |
| 276 | return MaybeInit(nullptr, solist); |
| 277 | } |
| 278 | |
| 279 | // Find __cfi_check in the caller and let it handle the problem. Since caller_pc is likely not a |
| 280 | // valid CFI target, we can not use CFI shadow for lookup. This does not need to be fast, do the |
| 281 | // regular symbol lookup. |
| 282 | void CFIShadowWriter::CfiFail(uint64_t CallSiteTypeId, void* Ptr, void* DiagData, void* CallerPc) { |
| 283 | soinfo* si = find_containing_library(CallerPc); |
| 284 | if (!si) { |
| 285 | __builtin_trap(); |
| 286 | } |
| 287 | |
| 288 | uintptr_t cfi_check = soinfo_find_cfi_check(si); |
| 289 | if (!cfi_check) { |
| 290 | __builtin_trap(); |
| 291 | } |
| 292 | |
| 293 | reinterpret_cast<CFICheckFn>(cfi_check)(CallSiteTypeId, Ptr, DiagData); |
| 294 | } |