| //===-------- cfi.cpp -----------------------------------------------------===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| // | |
| // This file implements the runtime support for the cross-DSO CFI. | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include <assert.h> | |
| #include <elf.h> | |
| #include "sanitizer_common/sanitizer_common.h" | |
| #if SANITIZER_FREEBSD | |
| #include <sys/link_elf.h> | |
| #endif | |
| #include <link.h> | |
| #include <string.h> | |
| #include <stdlib.h> | |
| #include <sys/mman.h> | |
| #if SANITIZER_LINUX | |
| typedef ElfW(Phdr) Elf_Phdr; | |
| typedef ElfW(Ehdr) Elf_Ehdr; | |
| typedef ElfW(Addr) Elf_Addr; | |
| typedef ElfW(Sym) Elf_Sym; | |
| typedef ElfW(Dyn) Elf_Dyn; | |
| #elif SANITIZER_FREEBSD | |
| #if SANITIZER_WORDSIZE == 64 | |
| #define ElfW64_Dyn Elf_Dyn | |
| #define ElfW64_Sym Elf_Sym | |
| #else | |
| #define ElfW32_Dyn Elf_Dyn | |
| #define ElfW32_Sym Elf_Sym | |
| #endif | |
| #endif | |
| #include "interception/interception.h" | |
| #include "sanitizer_common/sanitizer_flag_parser.h" | |
| #include "ubsan/ubsan_init.h" | |
| #include "ubsan/ubsan_flags.h" | |
| #ifdef CFI_ENABLE_DIAG | |
| #include "ubsan/ubsan_handlers.h" | |
| #endif | |
| using namespace __sanitizer; | |
| namespace __cfi { | |
| #if SANITIZER_LOONGARCH64 | |
| #define kCfiShadowLimitsStorageSize 16384 // 16KiB on loongarch64 per page | |
| #else | |
| #define kCfiShadowLimitsStorageSize 4096 // 1 page | |
| #endif | |
| // Lets hope that the data segment is mapped with 4K pages. | |
| // The pointer to the cfi shadow region is stored at the start of this page. | |
| // The rest of the page is unused and re-mapped read-only. | |
| static union { | |
| char space[kCfiShadowLimitsStorageSize]; | |
| struct { | |
| uptr start; | |
| uptr size; | |
| } limits; | |
| } cfi_shadow_limits_storage | |
| __attribute__((aligned(kCfiShadowLimitsStorageSize))); | |
| static constexpr uptr kShadowGranularity = 12; | |
| static constexpr uptr kShadowAlign = 1UL << kShadowGranularity; // 4096 | |
| static constexpr uint16_t kInvalidShadow = 0; | |
| static constexpr uint16_t kUncheckedShadow = 0xFFFFU; | |
| // Get the start address of the CFI shadow region. | |
| uptr GetShadow() { | |
| return cfi_shadow_limits_storage.limits.start; | |
| } | |
| uptr GetShadowSize() { | |
| return cfi_shadow_limits_storage.limits.size; | |
| } | |
| // This will only work while the shadow is not allocated. | |
| void SetShadowSize(uptr size) { | |
| cfi_shadow_limits_storage.limits.size = size; | |
| } | |
| uptr MemToShadowOffset(uptr x) { | |
| return (x >> kShadowGranularity) << 1; | |
| } | |
| uint16_t *MemToShadow(uptr x, uptr shadow_base) { | |
| return (uint16_t *)(shadow_base + MemToShadowOffset(x)); | |
| } | |
| typedef int (*CFICheckFn)(u64, void *, void *); | |
| // This class reads and decodes the shadow contents. | |
| class ShadowValue { | |
| uptr addr; | |
| uint16_t v; | |
| explicit ShadowValue(uptr addr, uint16_t v) : addr(addr), v(v) {} | |
| public: | |
| bool is_invalid() const { return v == kInvalidShadow; } | |
| bool is_unchecked() const { return v == kUncheckedShadow; } | |
| CFICheckFn get_cfi_check() const { | |
| assert(!is_invalid() && !is_unchecked()); | |
| uptr aligned_addr = addr & ~(kShadowAlign - 1); | |
| uptr p = aligned_addr - (((uptr)v - 1) << kShadowGranularity); | |
| return reinterpret_cast<CFICheckFn>(p); | |
| } | |
| // Load a shadow value for the given application memory address. | |
| static const ShadowValue load(uptr addr) { | |
| uptr shadow_base = GetShadow(); | |
| uptr shadow_offset = MemToShadowOffset(addr); | |
| if (shadow_offset > GetShadowSize()) | |
| return ShadowValue(addr, kInvalidShadow); | |
| else | |
| return ShadowValue( | |
| addr, *reinterpret_cast<uint16_t *>(shadow_base + shadow_offset)); | |
| } | |
| }; | |
| class ShadowBuilder { | |
| uptr shadow_; | |
| public: | |
| // Allocate a new empty shadow (for the entire address space) on the side. | |
| void Start(); | |
| // Mark the given address range as unchecked. | |
| // This is used for uninstrumented libraries like libc. | |
| // Any CFI check with a target in that range will pass. | |
| void AddUnchecked(uptr begin, uptr end); | |
| // Mark the given address range as belonging to a library with the given | |
| // cfi_check function. | |
| void Add(uptr begin, uptr end, uptr cfi_check); | |
| // Finish shadow construction. Atomically switch the current active shadow | |
| // region with the newly constructed one and deallocate the former. | |
| void Install(); | |
| }; | |
| void ShadowBuilder::Start() { | |
| shadow_ = (uptr)MmapNoReserveOrDie(GetShadowSize(), "CFI shadow"); | |
| VReport(1, "CFI: shadow at %zx .. %zx\n", shadow_, shadow_ + GetShadowSize()); | |
| } | |
| void ShadowBuilder::AddUnchecked(uptr begin, uptr end) { | |
| uint16_t *shadow_begin = MemToShadow(begin, shadow_); | |
| uint16_t *shadow_end = MemToShadow(end - 1, shadow_) + 1; | |
| // memset takes a byte, so our unchecked shadow value requires both bytes to | |
| // be the same. Make sure we're ok during compilation. | |
| static_assert((kUncheckedShadow & 0xff) == ((kUncheckedShadow >> 8) & 0xff), | |
| "Both bytes of the 16-bit value must be the same!"); | |
| memset(shadow_begin, kUncheckedShadow & 0xff, | |
| (shadow_end - shadow_begin) * sizeof(*shadow_begin)); | |
| } | |
| void ShadowBuilder::Add(uptr begin, uptr end, uptr cfi_check) { | |
| assert((cfi_check & (kShadowAlign - 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 = Max(begin, cfi_check); | |
| uint16_t *s = MemToShadow(begin, shadow_); | |
| uint16_t *s_end = MemToShadow(end - 1, shadow_) + 1; | |
| uint16_t sv = ((begin - cfi_check) >> kShadowGranularity) + 1; | |
| for (; s < s_end; s++, sv++) | |
| *s = sv; | |
| } | |
| #if SANITIZER_LINUX || SANITIZER_FREEBSD || SANITIZER_NETBSD | |
| void ShadowBuilder::Install() { | |
| MprotectReadOnly(shadow_, GetShadowSize()); | |
| uptr main_shadow = GetShadow(); | |
| if (main_shadow) { | |
| // Update. | |
| #if SANITIZER_LINUX | |
| void *res = mremap((void *)shadow_, GetShadowSize(), GetShadowSize(), | |
| MREMAP_MAYMOVE | MREMAP_FIXED, (void *)main_shadow); | |
| CHECK(res != MAP_FAILED); | |
| #elif SANITIZER_NETBSD | |
| void *res = mremap((void *)shadow_, GetShadowSize(), (void *)main_shadow, | |
| GetShadowSize(), MAP_FIXED); | |
| CHECK(res != MAP_FAILED); | |
| #else | |
| void *res = MmapFixedOrDie(shadow_, GetShadowSize(), "cfi shadow"); | |
| CHECK(res != MAP_FAILED); | |
| ::memcpy(&shadow_, &main_shadow, GetShadowSize()); | |
| #endif | |
| } else { | |
| // Initial setup. | |
| CHECK_EQ(kCfiShadowLimitsStorageSize, GetPageSizeCached()); | |
| CHECK_EQ(0, GetShadow()); | |
| cfi_shadow_limits_storage.limits.start = shadow_; | |
| MprotectReadOnly((uptr)&cfi_shadow_limits_storage, | |
| sizeof(cfi_shadow_limits_storage)); | |
| CHECK_EQ(shadow_, GetShadow()); | |
| } | |
| } | |
| #else | |
| #error not implemented | |
| #endif | |
| // This is a workaround for a glibc bug: | |
| // https://sourceware.org/bugzilla/show_bug.cgi?id=15199 | |
| // Other platforms can, hopefully, just do | |
| // dlopen(RTLD_NOLOAD | RTLD_LAZY) | |
| // dlsym("__cfi_check"). | |
| uptr find_cfi_check_in_dso(dl_phdr_info *info) { | |
| const Elf_Dyn *dynamic = nullptr; | |
| for (int i = 0; i < info->dlpi_phnum; ++i) { | |
| if (info->dlpi_phdr[i].p_type == PT_DYNAMIC) { | |
| dynamic = | |
| (const Elf_Dyn *)(info->dlpi_addr + info->dlpi_phdr[i].p_vaddr); | |
| break; | |
| } | |
| } | |
| if (!dynamic) return 0; | |
| uptr strtab = 0, symtab = 0, strsz = 0; | |
| for (const Elf_Dyn *p = dynamic; p->d_tag != PT_NULL; ++p) { | |
| if (p->d_tag == DT_SYMTAB) | |
| symtab = p->d_un.d_ptr; | |
| else if (p->d_tag == DT_STRTAB) | |
| strtab = p->d_un.d_ptr; | |
| else if (p->d_tag == DT_STRSZ) | |
| strsz = p->d_un.d_ptr; | |
| } | |
| if (symtab > strtab) { | |
| VReport(1, "Can not handle: symtab > strtab (%zx > %zx)\n", symtab, strtab); | |
| return 0; | |
| } | |
| // Verify that strtab and symtab are inside of the same LOAD segment. | |
| // This excludes VDSO, which has (very high) bogus strtab and symtab pointers. | |
| int phdr_idx; | |
| for (phdr_idx = 0; phdr_idx < info->dlpi_phnum; phdr_idx++) { | |
| const Elf_Phdr *phdr = &info->dlpi_phdr[phdr_idx]; | |
| if (phdr->p_type == PT_LOAD) { | |
| uptr beg = info->dlpi_addr + phdr->p_vaddr; | |
| uptr end = beg + phdr->p_memsz; | |
| if (strtab >= beg && strtab + strsz < end && symtab >= beg && | |
| symtab < end) | |
| break; | |
| } | |
| } | |
| if (phdr_idx == info->dlpi_phnum) { | |
| // Nope, either different segments or just bogus pointers. | |
| // Can not handle this. | |
| VReport(1, "Can not handle: symtab %zx, strtab %zx\n", symtab, strtab); | |
| return 0; | |
| } | |
| for (const Elf_Sym *p = (const Elf_Sym *)symtab; (Elf_Addr)p < strtab; | |
| ++p) { | |
| // There is no reliable way to find the end of the symbol table. In | |
| // lld-produces files, there are other sections between symtab and strtab. | |
| // Stop looking when the symbol name is not inside strtab. | |
| if (p->st_name >= strsz) break; | |
| char *name = (char*)(strtab + p->st_name); | |
| if (strcmp(name, "__cfi_check") == 0) { | |
| assert(p->st_info == ELF32_ST_INFO(STB_GLOBAL, STT_FUNC) || | |
| p->st_info == ELF32_ST_INFO(STB_WEAK, STT_FUNC)); | |
| uptr addr = info->dlpi_addr + p->st_value; | |
| return addr; | |
| } | |
| } | |
| return 0; | |
| } | |
| int dl_iterate_phdr_cb(dl_phdr_info *info, size_t size, void *data) { | |
| uptr cfi_check = find_cfi_check_in_dso(info); | |
| if (cfi_check) | |
| VReport(1, "Module '%s' __cfi_check %zx\n", info->dlpi_name, cfi_check); | |
| ShadowBuilder *b = reinterpret_cast<ShadowBuilder *>(data); | |
| for (int i = 0; i < info->dlpi_phnum; i++) { | |
| const Elf_Phdr *phdr = &info->dlpi_phdr[i]; | |
| if (phdr->p_type == PT_LOAD) { | |
| // Jump tables are in the executable segment. | |
| // VTables are in the non-executable one. | |
| // Need to fill shadow for both. | |
| // FIXME: reject writable if vtables are in the r/o segment. Depend on | |
| // PT_RELRO? | |
| uptr cur_beg = info->dlpi_addr + phdr->p_vaddr; | |
| uptr cur_end = cur_beg + phdr->p_memsz; | |
| if (cfi_check) { | |
| VReport(1, " %zx .. %zx\n", cur_beg, cur_end); | |
| b->Add(cur_beg, cur_end, cfi_check); | |
| } else { | |
| b->AddUnchecked(cur_beg, cur_end); | |
| } | |
| } | |
| } | |
| return 0; | |
| } | |
| // Init or update shadow for the current set of loaded libraries. | |
| void UpdateShadow() { | |
| ShadowBuilder b; | |
| b.Start(); | |
| dl_iterate_phdr(dl_iterate_phdr_cb, &b); | |
| b.Install(); | |
| } | |
| void InitShadow() { | |
| CHECK_EQ(0, GetShadow()); | |
| CHECK_EQ(0, GetShadowSize()); | |
| uptr vma = GetMaxUserVirtualAddress(); | |
| // Shadow is 2 -> 2**kShadowGranularity. | |
| SetShadowSize((vma >> (kShadowGranularity - 1)) + 1); | |
| VReport(1, "CFI: VMA size %zx, shadow size %zx\n", vma, GetShadowSize()); | |
| UpdateShadow(); | |
| } | |
| THREADLOCAL int in_loader; | |
| Mutex shadow_update_lock; | |
| void EnterLoader() SANITIZER_NO_THREAD_SAFETY_ANALYSIS { | |
| if (in_loader == 0) { | |
| shadow_update_lock.Lock(); | |
| } | |
| ++in_loader; | |
| } | |
| void ExitLoader() SANITIZER_NO_THREAD_SAFETY_ANALYSIS { | |
| CHECK(in_loader > 0); | |
| --in_loader; | |
| UpdateShadow(); | |
| if (in_loader == 0) { | |
| shadow_update_lock.Unlock(); | |
| } | |
| } | |
| ALWAYS_INLINE void CfiSlowPathCommon(u64 CallSiteTypeId, void *Ptr, | |
| void *DiagData) { | |
| uptr Addr = (uptr)Ptr; | |
| VReport(3, "__cfi_slowpath: %llx, %p\n", CallSiteTypeId, Ptr); | |
| ShadowValue sv = ShadowValue::load(Addr); | |
| if (sv.is_invalid()) { | |
| VReport(1, "CFI: invalid memory region for a check target: %p\n", Ptr); | |
| #ifdef CFI_ENABLE_DIAG | |
| if (DiagData) { | |
| __ubsan_handle_cfi_check_fail( | |
| reinterpret_cast<__ubsan::CFICheckFailData *>(DiagData), Addr, false); | |
| return; | |
| } | |
| #endif | |
| Trap(); | |
| } | |
| if (sv.is_unchecked()) { | |
| VReport(2, "CFI: unchecked call (shadow=FFFF): %p\n", Ptr); | |
| return; | |
| } | |
| CFICheckFn cfi_check = sv.get_cfi_check(); | |
| VReport(2, "__cfi_check at %p\n", (void *)cfi_check); | |
| cfi_check(CallSiteTypeId, Ptr, DiagData); | |
| } | |
| void InitializeFlags() { | |
| SetCommonFlagsDefaults(); | |
| #ifdef CFI_ENABLE_DIAG | |
| __ubsan::Flags *uf = __ubsan::flags(); | |
| uf->SetDefaults(); | |
| #endif | |
| FlagParser cfi_parser; | |
| RegisterCommonFlags(&cfi_parser); | |
| cfi_parser.ParseStringFromEnv("CFI_OPTIONS"); | |
| #ifdef CFI_ENABLE_DIAG | |
| FlagParser ubsan_parser; | |
| __ubsan::RegisterUbsanFlags(&ubsan_parser, uf); | |
| RegisterCommonFlags(&ubsan_parser); | |
| const char *ubsan_default_options = __ubsan_default_options(); | |
| ubsan_parser.ParseString(ubsan_default_options); | |
| ubsan_parser.ParseStringFromEnv("UBSAN_OPTIONS"); | |
| #endif | |
| InitializeCommonFlags(); | |
| if (Verbosity()) | |
| ReportUnrecognizedFlags(); | |
| if (common_flags()->help) { | |
| cfi_parser.PrintFlagDescriptions(); | |
| } | |
| } | |
| } // namespace __cfi | |
| using namespace __cfi; | |
| extern "C" SANITIZER_INTERFACE_ATTRIBUTE void | |
| __cfi_slowpath(u64 CallSiteTypeId, void *Ptr) { | |
| CfiSlowPathCommon(CallSiteTypeId, Ptr, nullptr); | |
| } | |
| #ifdef CFI_ENABLE_DIAG | |
| extern "C" SANITIZER_INTERFACE_ATTRIBUTE void | |
| __cfi_slowpath_diag(u64 CallSiteTypeId, void *Ptr, void *DiagData) { | |
| CfiSlowPathCommon(CallSiteTypeId, Ptr, DiagData); | |
| } | |
| #endif | |
| static void EnsureInterceptorsInitialized(); | |
| // Setup shadow for dlopen()ed libraries. | |
| // The actual shadow setup happens after dlopen() returns, which means that | |
| // a library can not be a target of any CFI checks while its constructors are | |
| // running. It's unclear how to fix this without some extra help from libc. | |
| // In glibc, mmap inside dlopen is not interceptable. | |
| // Maybe a seccomp-bpf filter? | |
| // We could insert a high-priority constructor into the library, but that would | |
| // not help with the uninstrumented libraries. | |
| INTERCEPTOR(void*, dlopen, const char *filename, int flag) { | |
| EnsureInterceptorsInitialized(); | |
| EnterLoader(); | |
| void *handle = REAL(dlopen)(filename, flag); | |
| ExitLoader(); | |
| return handle; | |
| } | |
| INTERCEPTOR(int, dlclose, void *handle) { | |
| EnsureInterceptorsInitialized(); | |
| EnterLoader(); | |
| int res = REAL(dlclose)(handle); | |
| ExitLoader(); | |
| return res; | |
| } | |
| static Mutex interceptor_init_lock; | |
| static bool interceptors_inited = false; | |
| static void EnsureInterceptorsInitialized() { | |
| Lock lock(&interceptor_init_lock); | |
| if (interceptors_inited) | |
| return; | |
| INTERCEPT_FUNCTION(dlopen); | |
| INTERCEPT_FUNCTION(dlclose); | |
| interceptors_inited = true; | |
| } | |
| extern "C" SANITIZER_INTERFACE_ATTRIBUTE | |
| #if !SANITIZER_CAN_USE_PREINIT_ARRAY | |
| // On ELF platforms, the constructor is invoked using .preinit_array (see below) | |
| __attribute__((constructor(0))) | |
| #endif | |
| void __cfi_init() { | |
| SanitizerToolName = "CFI"; | |
| InitializeFlags(); | |
| InitShadow(); | |
| #ifdef CFI_ENABLE_DIAG | |
| __ubsan::InitAsPlugin(); | |
| #endif | |
| } | |
| #if SANITIZER_CAN_USE_PREINIT_ARRAY | |
| // On ELF platforms, run cfi initialization before any other constructors. | |
| // On other platforms we use the constructor attribute to arrange to run our | |
| // initialization early. | |
| extern "C" { | |
| __attribute__((section(".preinit_array"), | |
| used)) void (*__cfi_preinit)(void) = __cfi_init; | |
| } | |
| #endif |