]> git.proxmox.com Git - rustc.git/blob - src/compiler-rt/lib/cfi/cfi.cc
New upstream version 1.19.0+dfsg3
[rustc.git] / src / compiler-rt / lib / cfi / cfi.cc
1 //===-------- cfi.cc ------------------------------------------------------===//
2 //
3 // The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 //
10 // This file implements the runtime support for the cross-DSO CFI.
11 //
12 //===----------------------------------------------------------------------===//
13
14 #include <assert.h>
15 #include <elf.h>
16 #include <link.h>
17 #include <string.h>
18 #include <sys/mman.h>
19
20 typedef ElfW(Phdr) Elf_Phdr;
21 typedef ElfW(Ehdr) Elf_Ehdr;
22
23 #include "interception/interception.h"
24 #include "sanitizer_common/sanitizer_common.h"
25 #include "sanitizer_common/sanitizer_flag_parser.h"
26 #include "ubsan/ubsan_init.h"
27 #include "ubsan/ubsan_flags.h"
28
29 #ifdef CFI_ENABLE_DIAG
30 #include "ubsan/ubsan_handlers.h"
31 #endif
32
33 using namespace __sanitizer;
34
35 namespace __cfi {
36
37 #define kCfiShadowLimitsStorageSize 4096 // 1 page
38 // Lets hope that the data segment is mapped with 4K pages.
39 // The pointer to the cfi shadow region is stored at the start of this page.
40 // The rest of the page is unused and re-mapped read-only.
41 static union {
42 char space[kCfiShadowLimitsStorageSize];
43 struct {
44 uptr start;
45 uptr size;
46 } limits;
47 } cfi_shadow_limits_storage
48 __attribute__((aligned(kCfiShadowLimitsStorageSize)));
49 static constexpr uptr kShadowGranularity = 12;
50 static constexpr uptr kShadowAlign = 1UL << kShadowGranularity; // 4096
51
52 static constexpr uint16_t kInvalidShadow = 0;
53 static constexpr uint16_t kUncheckedShadow = 0xFFFFU;
54
55 // Get the start address of the CFI shadow region.
56 uptr GetShadow() {
57 return cfi_shadow_limits_storage.limits.start;
58 }
59
60 uptr GetShadowSize() {
61 return cfi_shadow_limits_storage.limits.size;
62 }
63
64 // This will only work while the shadow is not allocated.
65 void SetShadowSize(uptr size) {
66 cfi_shadow_limits_storage.limits.size = size;
67 }
68
69 uptr MemToShadowOffset(uptr x) {
70 return (x >> kShadowGranularity) << 1;
71 }
72
73 uint16_t *MemToShadow(uptr x, uptr shadow_base) {
74 return (uint16_t *)(shadow_base + MemToShadowOffset(x));
75 }
76
77 typedef int (*CFICheckFn)(u64, void *, void *);
78
79 // This class reads and decodes the shadow contents.
80 class ShadowValue {
81 uptr addr;
82 uint16_t v;
83 explicit ShadowValue(uptr addr, uint16_t v) : addr(addr), v(v) {}
84
85 public:
86 bool is_invalid() const { return v == kInvalidShadow; }
87
88 bool is_unchecked() const { return v == kUncheckedShadow; }
89
90 CFICheckFn get_cfi_check() const {
91 assert(!is_invalid() && !is_unchecked());
92 uptr aligned_addr = addr & ~(kShadowAlign - 1);
93 uptr p = aligned_addr - (((uptr)v - 1) << kShadowGranularity);
94 return reinterpret_cast<CFICheckFn>(p);
95 }
96
97 // Load a shadow value for the given application memory address.
98 static const ShadowValue load(uptr addr) {
99 uptr shadow_base = GetShadow();
100 uptr shadow_offset = MemToShadowOffset(addr);
101 if (shadow_offset > GetShadowSize())
102 return ShadowValue(addr, kInvalidShadow);
103 else
104 return ShadowValue(
105 addr, *reinterpret_cast<uint16_t *>(shadow_base + shadow_offset));
106 }
107 };
108
109 class ShadowBuilder {
110 uptr shadow_;
111
112 public:
113 // Allocate a new empty shadow (for the entire address space) on the side.
114 void Start();
115 // Mark the given address range as unchecked.
116 // This is used for uninstrumented libraries like libc.
117 // Any CFI check with a target in that range will pass.
118 void AddUnchecked(uptr begin, uptr end);
119 // Mark the given address range as belonging to a library with the given
120 // cfi_check function.
121 void Add(uptr begin, uptr end, uptr cfi_check);
122 // Finish shadow construction. Atomically switch the current active shadow
123 // region with the newly constructed one and deallocate the former.
124 void Install();
125 };
126
127 void ShadowBuilder::Start() {
128 shadow_ = (uptr)MmapNoReserveOrDie(GetShadowSize(), "CFI shadow");
129 VReport(1, "CFI: shadow at %zx .. %zx\n", shadow_, shadow_ + GetShadowSize());
130 }
131
132 void ShadowBuilder::AddUnchecked(uptr begin, uptr end) {
133 uint16_t *shadow_begin = MemToShadow(begin, shadow_);
134 uint16_t *shadow_end = MemToShadow(end - 1, shadow_) + 1;
135 memset(shadow_begin, kUncheckedShadow,
136 (shadow_end - shadow_begin) * sizeof(*shadow_begin));
137 }
138
139 void ShadowBuilder::Add(uptr begin, uptr end, uptr cfi_check) {
140 assert((cfi_check & (kShadowAlign - 1)) == 0);
141
142 // Don't fill anything below cfi_check. We can not represent those addresses
143 // in the shadow, and must make sure at codegen to place all valid call
144 // targets above cfi_check.
145 begin = Max(begin, cfi_check);
146 uint16_t *s = MemToShadow(begin, shadow_);
147 uint16_t *s_end = MemToShadow(end - 1, shadow_) + 1;
148 uint16_t sv = ((begin - cfi_check) >> kShadowGranularity) + 1;
149 for (; s < s_end; s++, sv++)
150 *s = sv;
151 }
152
153 #if SANITIZER_LINUX
154 void ShadowBuilder::Install() {
155 MprotectReadOnly(shadow_, GetShadowSize());
156 uptr main_shadow = GetShadow();
157 if (main_shadow) {
158 // Update.
159 void *res = mremap((void *)shadow_, GetShadowSize(), GetShadowSize(),
160 MREMAP_MAYMOVE | MREMAP_FIXED, (void *)main_shadow);
161 CHECK(res != MAP_FAILED);
162 } else {
163 // Initial setup.
164 CHECK_EQ(kCfiShadowLimitsStorageSize, GetPageSizeCached());
165 CHECK_EQ(0, GetShadow());
166 cfi_shadow_limits_storage.limits.start = shadow_;
167 MprotectReadOnly((uptr)&cfi_shadow_limits_storage,
168 sizeof(cfi_shadow_limits_storage));
169 CHECK_EQ(shadow_, GetShadow());
170 }
171 }
172 #else
173 #error not implemented
174 #endif
175
176 // This is a workaround for a glibc bug:
177 // https://sourceware.org/bugzilla/show_bug.cgi?id=15199
178 // Other platforms can, hopefully, just do
179 // dlopen(RTLD_NOLOAD | RTLD_LAZY)
180 // dlsym("__cfi_check").
181 uptr find_cfi_check_in_dso(dl_phdr_info *info) {
182 const ElfW(Dyn) *dynamic = nullptr;
183 for (int i = 0; i < info->dlpi_phnum; ++i) {
184 if (info->dlpi_phdr[i].p_type == PT_DYNAMIC) {
185 dynamic =
186 (const ElfW(Dyn) *)(info->dlpi_addr + info->dlpi_phdr[i].p_vaddr);
187 break;
188 }
189 }
190 if (!dynamic) return 0;
191 uptr strtab = 0, symtab = 0;
192 for (const ElfW(Dyn) *p = dynamic; p->d_tag != PT_NULL; ++p) {
193 if (p->d_tag == DT_SYMTAB)
194 symtab = p->d_un.d_ptr;
195 else if (p->d_tag == DT_STRTAB)
196 strtab = p->d_un.d_ptr;
197 }
198
199 if (symtab > strtab) {
200 VReport(1, "Can not handle: symtab > strtab (%p > %zx)\n", symtab, strtab);
201 return 0;
202 }
203
204 // Verify that strtab and symtab are inside of the same LOAD segment.
205 // This excludes VDSO, which has (very high) bogus strtab and symtab pointers.
206 int phdr_idx;
207 for (phdr_idx = 0; phdr_idx < info->dlpi_phnum; phdr_idx++) {
208 const Elf_Phdr *phdr = &info->dlpi_phdr[phdr_idx];
209 if (phdr->p_type == PT_LOAD) {
210 uptr beg = info->dlpi_addr + phdr->p_vaddr;
211 uptr end = beg + phdr->p_memsz;
212 if (strtab >= beg && strtab < end && symtab >= beg && symtab < end)
213 break;
214 }
215 }
216 if (phdr_idx == info->dlpi_phnum) {
217 // Nope, either different segments or just bogus pointers.
218 // Can not handle this.
219 VReport(1, "Can not handle: symtab %p, strtab %zx\n", symtab, strtab);
220 return 0;
221 }
222
223 for (const ElfW(Sym) *p = (const ElfW(Sym) *)symtab; (ElfW(Addr))p < strtab;
224 ++p) {
225 char *name = (char*)(strtab + p->st_name);
226 if (strcmp(name, "__cfi_check") == 0) {
227 assert(p->st_info == ELF32_ST_INFO(STB_GLOBAL, STT_FUNC));
228 uptr addr = info->dlpi_addr + p->st_value;
229 return addr;
230 }
231 }
232 return 0;
233 }
234
235 int dl_iterate_phdr_cb(dl_phdr_info *info, size_t size, void *data) {
236 uptr cfi_check = find_cfi_check_in_dso(info);
237 if (cfi_check)
238 VReport(1, "Module '%s' __cfi_check %zx\n", info->dlpi_name, cfi_check);
239
240 ShadowBuilder *b = reinterpret_cast<ShadowBuilder *>(data);
241
242 for (int i = 0; i < info->dlpi_phnum; i++) {
243 const Elf_Phdr *phdr = &info->dlpi_phdr[i];
244 if (phdr->p_type == PT_LOAD) {
245 // Jump tables are in the executable segment.
246 // VTables are in the non-executable one.
247 // Need to fill shadow for both.
248 // FIXME: reject writable if vtables are in the r/o segment. Depend on
249 // PT_RELRO?
250 uptr cur_beg = info->dlpi_addr + phdr->p_vaddr;
251 uptr cur_end = cur_beg + phdr->p_memsz;
252 if (cfi_check) {
253 VReport(1, " %zx .. %zx\n", cur_beg, cur_end);
254 b->Add(cur_beg, cur_end, cfi_check);
255 } else {
256 b->AddUnchecked(cur_beg, cur_end);
257 }
258 }
259 }
260 return 0;
261 }
262
263 // Init or update shadow for the current set of loaded libraries.
264 void UpdateShadow() {
265 ShadowBuilder b;
266 b.Start();
267 dl_iterate_phdr(dl_iterate_phdr_cb, &b);
268 b.Install();
269 }
270
271 void InitShadow() {
272 CHECK_EQ(0, GetShadow());
273 CHECK_EQ(0, GetShadowSize());
274
275 uptr vma = GetMaxVirtualAddress();
276 // Shadow is 2 -> 2**kShadowGranularity.
277 SetShadowSize((vma >> (kShadowGranularity - 1)) + 1);
278 VReport(1, "CFI: VMA size %zx, shadow size %zx\n", vma, GetShadowSize());
279
280 UpdateShadow();
281 }
282
283 THREADLOCAL int in_loader;
284 BlockingMutex shadow_update_lock(LINKER_INITIALIZED);
285
286 void EnterLoader() {
287 if (in_loader == 0) {
288 shadow_update_lock.Lock();
289 }
290 ++in_loader;
291 }
292
293 void ExitLoader() {
294 CHECK(in_loader > 0);
295 --in_loader;
296 UpdateShadow();
297 if (in_loader == 0) {
298 shadow_update_lock.Unlock();
299 }
300 }
301
302 ALWAYS_INLINE void CfiSlowPathCommon(u64 CallSiteTypeId, void *Ptr,
303 void *DiagData) {
304 uptr Addr = (uptr)Ptr;
305 VReport(3, "__cfi_slowpath: %llx, %p\n", CallSiteTypeId, Ptr);
306 ShadowValue sv = ShadowValue::load(Addr);
307 if (sv.is_invalid()) {
308 VReport(1, "CFI: invalid memory region for a check target: %p\n", Ptr);
309 #ifdef CFI_ENABLE_DIAG
310 if (DiagData) {
311 __ubsan_handle_cfi_check_fail(
312 reinterpret_cast<__ubsan::CFICheckFailData *>(DiagData), Addr, false);
313 return;
314 }
315 #endif
316 Trap();
317 }
318 if (sv.is_unchecked()) {
319 VReport(2, "CFI: unchecked call (shadow=FFFF): %p\n", Ptr);
320 return;
321 }
322 CFICheckFn cfi_check = sv.get_cfi_check();
323 VReport(2, "__cfi_check at %p\n", cfi_check);
324 cfi_check(CallSiteTypeId, Ptr, DiagData);
325 }
326
327 void InitializeFlags() {
328 SetCommonFlagsDefaults();
329 #ifdef CFI_ENABLE_DIAG
330 __ubsan::Flags *uf = __ubsan::flags();
331 uf->SetDefaults();
332 #endif
333
334 FlagParser cfi_parser;
335 RegisterCommonFlags(&cfi_parser);
336 cfi_parser.ParseString(GetEnv("CFI_OPTIONS"));
337
338 #ifdef CFI_ENABLE_DIAG
339 FlagParser ubsan_parser;
340 __ubsan::RegisterUbsanFlags(&ubsan_parser, uf);
341 RegisterCommonFlags(&ubsan_parser);
342
343 const char *ubsan_default_options = __ubsan::MaybeCallUbsanDefaultOptions();
344 ubsan_parser.ParseString(ubsan_default_options);
345 ubsan_parser.ParseString(GetEnv("UBSAN_OPTIONS"));
346 #endif
347
348 InitializeCommonFlags();
349
350 if (Verbosity())
351 ReportUnrecognizedFlags();
352
353 if (common_flags()->help) {
354 cfi_parser.PrintFlagDescriptions();
355 }
356 }
357
358 } // namespace __cfi
359
360 using namespace __cfi;
361
362 extern "C" SANITIZER_INTERFACE_ATTRIBUTE void
363 __cfi_slowpath(u64 CallSiteTypeId, void *Ptr) {
364 CfiSlowPathCommon(CallSiteTypeId, Ptr, nullptr);
365 }
366
367 #ifdef CFI_ENABLE_DIAG
368 extern "C" SANITIZER_INTERFACE_ATTRIBUTE void
369 __cfi_slowpath_diag(u64 CallSiteTypeId, void *Ptr, void *DiagData) {
370 CfiSlowPathCommon(CallSiteTypeId, Ptr, DiagData);
371 }
372 #endif
373
374 // Setup shadow for dlopen()ed libraries.
375 // The actual shadow setup happens after dlopen() returns, which means that
376 // a library can not be a target of any CFI checks while its constructors are
377 // running. It's unclear how to fix this without some extra help from libc.
378 // In glibc, mmap inside dlopen is not interceptable.
379 // Maybe a seccomp-bpf filter?
380 // We could insert a high-priority constructor into the library, but that would
381 // not help with the uninstrumented libraries.
382 INTERCEPTOR(void*, dlopen, const char *filename, int flag) {
383 EnterLoader();
384 void *handle = REAL(dlopen)(filename, flag);
385 ExitLoader();
386 return handle;
387 }
388
389 INTERCEPTOR(int, dlclose, void *handle) {
390 EnterLoader();
391 int res = REAL(dlclose)(handle);
392 ExitLoader();
393 return res;
394 }
395
396 extern "C" SANITIZER_INTERFACE_ATTRIBUTE
397 #if !SANITIZER_CAN_USE_PREINIT_ARRAY
398 // On ELF platforms, the constructor is invoked using .preinit_array (see below)
399 __attribute__((constructor(0)))
400 #endif
401 void __cfi_init() {
402 SanitizerToolName = "CFI";
403 InitializeFlags();
404 InitShadow();
405
406 INTERCEPT_FUNCTION(dlopen);
407 INTERCEPT_FUNCTION(dlclose);
408
409 #ifdef CFI_ENABLE_DIAG
410 __ubsan::InitAsPlugin();
411 #endif
412 }
413
414 #if SANITIZER_CAN_USE_PREINIT_ARRAY
415 // On ELF platforms, run cfi initialization before any other constructors.
416 // On other platforms we use the constructor attribute to arrange to run our
417 // initialization early.
418 extern "C" {
419 __attribute__((section(".preinit_array"),
420 used)) void (*__cfi_preinit)(void) = __cfi_init;
421 }
422 #endif