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