Path: blob/main/contrib/llvm-project/compiler-rt/lib/cfi/cfi.cpp
35233 views
//===-------- cfi.cpp -----------------------------------------------------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//7//8// This file implements the runtime support for the cross-DSO CFI.9//10//===----------------------------------------------------------------------===//1112#include <assert.h>13#include <elf.h>1415#include "sanitizer_common/sanitizer_common.h"16#if SANITIZER_FREEBSD17#include <sys/link_elf.h>18#endif19#include <link.h>20#include <string.h>21#include <stdlib.h>22#include <sys/mman.h>2324#if SANITIZER_LINUX25typedef ElfW(Phdr) Elf_Phdr;26typedef ElfW(Ehdr) Elf_Ehdr;27typedef ElfW(Addr) Elf_Addr;28typedef ElfW(Sym) Elf_Sym;29typedef ElfW(Dyn) Elf_Dyn;30#elif SANITIZER_FREEBSD31#if SANITIZER_WORDSIZE == 6432#define ElfW64_Dyn Elf_Dyn33#define ElfW64_Sym Elf_Sym34#else35#define ElfW32_Dyn Elf_Dyn36#define ElfW32_Sym Elf_Sym37#endif38#endif3940#include "interception/interception.h"41#include "sanitizer_common/sanitizer_flag_parser.h"42#include "ubsan/ubsan_init.h"43#include "ubsan/ubsan_flags.h"4445#ifdef CFI_ENABLE_DIAG46#include "ubsan/ubsan_handlers.h"47#endif4849using namespace __sanitizer;5051namespace __cfi {5253#if SANITIZER_LOONGARCH6454#define kCfiShadowLimitsStorageSize 16384 // 16KiB on loongarch64 per page55#else56#define kCfiShadowLimitsStorageSize 4096 // 1 page57#endif58// Lets hope that the data segment is mapped with 4K pages.59// The pointer to the cfi shadow region is stored at the start of this page.60// The rest of the page is unused and re-mapped read-only.61static union {62char space[kCfiShadowLimitsStorageSize];63struct {64uptr start;65uptr size;66} limits;67} cfi_shadow_limits_storage68__attribute__((aligned(kCfiShadowLimitsStorageSize)));69static constexpr uptr kShadowGranularity = 12;70static constexpr uptr kShadowAlign = 1UL << kShadowGranularity; // 40967172static constexpr uint16_t kInvalidShadow = 0;73static constexpr uint16_t kUncheckedShadow = 0xFFFFU;7475// Get the start address of the CFI shadow region.76uptr GetShadow() {77return cfi_shadow_limits_storage.limits.start;78}7980uptr GetShadowSize() {81return cfi_shadow_limits_storage.limits.size;82}8384// This will only work while the shadow is not allocated.85void SetShadowSize(uptr size) {86cfi_shadow_limits_storage.limits.size = size;87}8889uptr MemToShadowOffset(uptr x) {90return (x >> kShadowGranularity) << 1;91}9293uint16_t *MemToShadow(uptr x, uptr shadow_base) {94return (uint16_t *)(shadow_base + MemToShadowOffset(x));95}9697typedef int (*CFICheckFn)(u64, void *, void *);9899// This class reads and decodes the shadow contents.100class ShadowValue {101uptr addr;102uint16_t v;103explicit ShadowValue(uptr addr, uint16_t v) : addr(addr), v(v) {}104105public:106bool is_invalid() const { return v == kInvalidShadow; }107108bool is_unchecked() const { return v == kUncheckedShadow; }109110CFICheckFn get_cfi_check() const {111assert(!is_invalid() && !is_unchecked());112uptr aligned_addr = addr & ~(kShadowAlign - 1);113uptr p = aligned_addr - (((uptr)v - 1) << kShadowGranularity);114return reinterpret_cast<CFICheckFn>(p);115}116117// Load a shadow value for the given application memory address.118static const ShadowValue load(uptr addr) {119uptr shadow_base = GetShadow();120uptr shadow_offset = MemToShadowOffset(addr);121if (shadow_offset > GetShadowSize())122return ShadowValue(addr, kInvalidShadow);123else124return ShadowValue(125addr, *reinterpret_cast<uint16_t *>(shadow_base + shadow_offset));126}127};128129class ShadowBuilder {130uptr shadow_;131132public:133// Allocate a new empty shadow (for the entire address space) on the side.134void Start();135// Mark the given address range as unchecked.136// This is used for uninstrumented libraries like libc.137// Any CFI check with a target in that range will pass.138void AddUnchecked(uptr begin, uptr end);139// Mark the given address range as belonging to a library with the given140// cfi_check function.141void Add(uptr begin, uptr end, uptr cfi_check);142// Finish shadow construction. Atomically switch the current active shadow143// region with the newly constructed one and deallocate the former.144void Install();145};146147void ShadowBuilder::Start() {148shadow_ = (uptr)MmapNoReserveOrDie(GetShadowSize(), "CFI shadow");149VReport(1, "CFI: shadow at %zx .. %zx\n", shadow_, shadow_ + GetShadowSize());150}151152void ShadowBuilder::AddUnchecked(uptr begin, uptr end) {153uint16_t *shadow_begin = MemToShadow(begin, shadow_);154uint16_t *shadow_end = MemToShadow(end - 1, shadow_) + 1;155// memset takes a byte, so our unchecked shadow value requires both bytes to156// be the same. Make sure we're ok during compilation.157static_assert((kUncheckedShadow & 0xff) == ((kUncheckedShadow >> 8) & 0xff),158"Both bytes of the 16-bit value must be the same!");159memset(shadow_begin, kUncheckedShadow & 0xff,160(shadow_end - shadow_begin) * sizeof(*shadow_begin));161}162163void ShadowBuilder::Add(uptr begin, uptr end, uptr cfi_check) {164assert((cfi_check & (kShadowAlign - 1)) == 0);165166// Don't fill anything below cfi_check. We can not represent those addresses167// in the shadow, and must make sure at codegen to place all valid call168// targets above cfi_check.169begin = Max(begin, cfi_check);170uint16_t *s = MemToShadow(begin, shadow_);171uint16_t *s_end = MemToShadow(end - 1, shadow_) + 1;172uint16_t sv = ((begin - cfi_check) >> kShadowGranularity) + 1;173for (; s < s_end; s++, sv++)174*s = sv;175}176177#if SANITIZER_LINUX || SANITIZER_FREEBSD || SANITIZER_NETBSD178void ShadowBuilder::Install() {179MprotectReadOnly(shadow_, GetShadowSize());180uptr main_shadow = GetShadow();181if (main_shadow) {182// Update.183#if SANITIZER_LINUX184void *res = mremap((void *)shadow_, GetShadowSize(), GetShadowSize(),185MREMAP_MAYMOVE | MREMAP_FIXED, (void *)main_shadow);186CHECK(res != MAP_FAILED);187#elif SANITIZER_NETBSD188void *res = mremap((void *)shadow_, GetShadowSize(), (void *)main_shadow,189GetShadowSize(), MAP_FIXED);190CHECK(res != MAP_FAILED);191#else192void *res = MmapFixedOrDie(shadow_, GetShadowSize(), "cfi shadow");193CHECK(res != MAP_FAILED);194::memcpy(&shadow_, &main_shadow, GetShadowSize());195#endif196} else {197// Initial setup.198CHECK_EQ(kCfiShadowLimitsStorageSize, GetPageSizeCached());199CHECK_EQ(0, GetShadow());200cfi_shadow_limits_storage.limits.start = shadow_;201MprotectReadOnly((uptr)&cfi_shadow_limits_storage,202sizeof(cfi_shadow_limits_storage));203CHECK_EQ(shadow_, GetShadow());204}205}206#else207#error not implemented208#endif209210// This is a workaround for a glibc bug:211// https://sourceware.org/bugzilla/show_bug.cgi?id=15199212// Other platforms can, hopefully, just do213// dlopen(RTLD_NOLOAD | RTLD_LAZY)214// dlsym("__cfi_check").215uptr find_cfi_check_in_dso(dl_phdr_info *info) {216const Elf_Dyn *dynamic = nullptr;217for (int i = 0; i < info->dlpi_phnum; ++i) {218if (info->dlpi_phdr[i].p_type == PT_DYNAMIC) {219dynamic =220(const Elf_Dyn *)(info->dlpi_addr + info->dlpi_phdr[i].p_vaddr);221break;222}223}224if (!dynamic) return 0;225uptr strtab = 0, symtab = 0, strsz = 0;226for (const Elf_Dyn *p = dynamic; p->d_tag != PT_NULL; ++p) {227if (p->d_tag == DT_SYMTAB)228symtab = p->d_un.d_ptr;229else if (p->d_tag == DT_STRTAB)230strtab = p->d_un.d_ptr;231else if (p->d_tag == DT_STRSZ)232strsz = p->d_un.d_ptr;233}234235if (symtab > strtab) {236VReport(1, "Can not handle: symtab > strtab (%zx > %zx)\n", symtab, strtab);237return 0;238}239240// Verify that strtab and symtab are inside of the same LOAD segment.241// This excludes VDSO, which has (very high) bogus strtab and symtab pointers.242int phdr_idx;243for (phdr_idx = 0; phdr_idx < info->dlpi_phnum; phdr_idx++) {244const Elf_Phdr *phdr = &info->dlpi_phdr[phdr_idx];245if (phdr->p_type == PT_LOAD) {246uptr beg = info->dlpi_addr + phdr->p_vaddr;247uptr end = beg + phdr->p_memsz;248if (strtab >= beg && strtab + strsz < end && symtab >= beg &&249symtab < end)250break;251}252}253if (phdr_idx == info->dlpi_phnum) {254// Nope, either different segments or just bogus pointers.255// Can not handle this.256VReport(1, "Can not handle: symtab %zx, strtab %zx\n", symtab, strtab);257return 0;258}259260for (const Elf_Sym *p = (const Elf_Sym *)symtab; (Elf_Addr)p < strtab;261++p) {262// There is no reliable way to find the end of the symbol table. In263// lld-produces files, there are other sections between symtab and strtab.264// Stop looking when the symbol name is not inside strtab.265if (p->st_name >= strsz) break;266char *name = (char*)(strtab + p->st_name);267if (strcmp(name, "__cfi_check") == 0) {268assert(p->st_info == ELF32_ST_INFO(STB_GLOBAL, STT_FUNC) ||269p->st_info == ELF32_ST_INFO(STB_WEAK, STT_FUNC));270uptr addr = info->dlpi_addr + p->st_value;271return addr;272}273}274return 0;275}276277int dl_iterate_phdr_cb(dl_phdr_info *info, size_t size, void *data) {278uptr cfi_check = find_cfi_check_in_dso(info);279if (cfi_check)280VReport(1, "Module '%s' __cfi_check %zx\n", info->dlpi_name, cfi_check);281282ShadowBuilder *b = reinterpret_cast<ShadowBuilder *>(data);283284for (int i = 0; i < info->dlpi_phnum; i++) {285const Elf_Phdr *phdr = &info->dlpi_phdr[i];286if (phdr->p_type == PT_LOAD) {287// Jump tables are in the executable segment.288// VTables are in the non-executable one.289// Need to fill shadow for both.290// FIXME: reject writable if vtables are in the r/o segment. Depend on291// PT_RELRO?292uptr cur_beg = info->dlpi_addr + phdr->p_vaddr;293uptr cur_end = cur_beg + phdr->p_memsz;294if (cfi_check) {295VReport(1, " %zx .. %zx\n", cur_beg, cur_end);296b->Add(cur_beg, cur_end, cfi_check);297} else {298b->AddUnchecked(cur_beg, cur_end);299}300}301}302return 0;303}304305// Init or update shadow for the current set of loaded libraries.306void UpdateShadow() {307ShadowBuilder b;308b.Start();309dl_iterate_phdr(dl_iterate_phdr_cb, &b);310b.Install();311}312313void InitShadow() {314CHECK_EQ(0, GetShadow());315CHECK_EQ(0, GetShadowSize());316317uptr vma = GetMaxUserVirtualAddress();318// Shadow is 2 -> 2**kShadowGranularity.319SetShadowSize((vma >> (kShadowGranularity - 1)) + 1);320VReport(1, "CFI: VMA size %zx, shadow size %zx\n", vma, GetShadowSize());321322UpdateShadow();323}324325THREADLOCAL int in_loader;326Mutex shadow_update_lock;327328void EnterLoader() SANITIZER_NO_THREAD_SAFETY_ANALYSIS {329if (in_loader == 0) {330shadow_update_lock.Lock();331}332++in_loader;333}334335void ExitLoader() SANITIZER_NO_THREAD_SAFETY_ANALYSIS {336CHECK(in_loader > 0);337--in_loader;338UpdateShadow();339if (in_loader == 0) {340shadow_update_lock.Unlock();341}342}343344ALWAYS_INLINE void CfiSlowPathCommon(u64 CallSiteTypeId, void *Ptr,345void *DiagData) {346uptr Addr = (uptr)Ptr;347VReport(3, "__cfi_slowpath: %llx, %p\n", CallSiteTypeId, Ptr);348ShadowValue sv = ShadowValue::load(Addr);349if (sv.is_invalid()) {350VReport(1, "CFI: invalid memory region for a check target: %p\n", Ptr);351#ifdef CFI_ENABLE_DIAG352if (DiagData) {353__ubsan_handle_cfi_check_fail(354reinterpret_cast<__ubsan::CFICheckFailData *>(DiagData), Addr, false);355return;356}357#endif358Trap();359}360if (sv.is_unchecked()) {361VReport(2, "CFI: unchecked call (shadow=FFFF): %p\n", Ptr);362return;363}364CFICheckFn cfi_check = sv.get_cfi_check();365VReport(2, "__cfi_check at %p\n", (void *)cfi_check);366cfi_check(CallSiteTypeId, Ptr, DiagData);367}368369void InitializeFlags() {370SetCommonFlagsDefaults();371#ifdef CFI_ENABLE_DIAG372__ubsan::Flags *uf = __ubsan::flags();373uf->SetDefaults();374#endif375376FlagParser cfi_parser;377RegisterCommonFlags(&cfi_parser);378cfi_parser.ParseStringFromEnv("CFI_OPTIONS");379380#ifdef CFI_ENABLE_DIAG381FlagParser ubsan_parser;382__ubsan::RegisterUbsanFlags(&ubsan_parser, uf);383RegisterCommonFlags(&ubsan_parser);384385const char *ubsan_default_options = __ubsan_default_options();386ubsan_parser.ParseString(ubsan_default_options);387ubsan_parser.ParseStringFromEnv("UBSAN_OPTIONS");388#endif389390InitializeCommonFlags();391392if (Verbosity())393ReportUnrecognizedFlags();394395if (common_flags()->help) {396cfi_parser.PrintFlagDescriptions();397}398}399400} // namespace __cfi401402using namespace __cfi;403404extern "C" SANITIZER_INTERFACE_ATTRIBUTE void405__cfi_slowpath(u64 CallSiteTypeId, void *Ptr) {406CfiSlowPathCommon(CallSiteTypeId, Ptr, nullptr);407}408409#ifdef CFI_ENABLE_DIAG410extern "C" SANITIZER_INTERFACE_ATTRIBUTE void411__cfi_slowpath_diag(u64 CallSiteTypeId, void *Ptr, void *DiagData) {412CfiSlowPathCommon(CallSiteTypeId, Ptr, DiagData);413}414#endif415416static void EnsureInterceptorsInitialized();417418// Setup shadow for dlopen()ed libraries.419// The actual shadow setup happens after dlopen() returns, which means that420// a library can not be a target of any CFI checks while its constructors are421// running. It's unclear how to fix this without some extra help from libc.422// In glibc, mmap inside dlopen is not interceptable.423// Maybe a seccomp-bpf filter?424// We could insert a high-priority constructor into the library, but that would425// not help with the uninstrumented libraries.426INTERCEPTOR(void*, dlopen, const char *filename, int flag) {427EnsureInterceptorsInitialized();428EnterLoader();429void *handle = REAL(dlopen)(filename, flag);430ExitLoader();431return handle;432}433434INTERCEPTOR(int, dlclose, void *handle) {435EnsureInterceptorsInitialized();436EnterLoader();437int res = REAL(dlclose)(handle);438ExitLoader();439return res;440}441442static Mutex interceptor_init_lock;443static bool interceptors_inited = false;444445static void EnsureInterceptorsInitialized() {446Lock lock(&interceptor_init_lock);447if (interceptors_inited)448return;449450INTERCEPT_FUNCTION(dlopen);451INTERCEPT_FUNCTION(dlclose);452453interceptors_inited = true;454}455456extern "C" SANITIZER_INTERFACE_ATTRIBUTE457#if !SANITIZER_CAN_USE_PREINIT_ARRAY458// On ELF platforms, the constructor is invoked using .preinit_array (see below)459__attribute__((constructor(0)))460#endif461void __cfi_init() {462SanitizerToolName = "CFI";463InitializeFlags();464InitShadow();465466#ifdef CFI_ENABLE_DIAG467__ubsan::InitAsPlugin();468#endif469}470471#if SANITIZER_CAN_USE_PREINIT_ARRAY472// On ELF platforms, run cfi initialization before any other constructors.473// On other platforms we use the constructor attribute to arrange to run our474// initialization early.475extern "C" {476__attribute__((section(".preinit_array"),477used)) void (*__cfi_preinit)(void) = __cfi_init;478}479#endif480481482