aboutsummaryrefslogtreecommitdiffstats
path: root/linker/linker_cfi.cpp
blob: e9cdab64abe1aa7c38ad96688c636b09b61d927a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "linker_cfi.h"

#include "linker_debug.h"
#include "linker_globals.h"
#include "private/bionic_page.h"
#include "private/bionic_prctl.h"

#include <sys/mman.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);
    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) {
    const char* soname = si->get_soname();
    if (soname && strcmp(soname, "libdl.so") == 0) {
      return si;
    }
  }
  return nullptr;
}

static uintptr_t soinfo_find_symbol(soinfo* si, const char* s) {
  SymbolName name(s);
  const ElfW(Sym) * sym;
  if (si->find_symbol_by_name(name, nullptr, &sym) && sym) {
    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_READ, 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);
  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);
}