Index: lib/safestack/CMakeLists.txt =================================================================== --- lib/safestack/CMakeLists.txt +++ lib/safestack/CMakeLists.txt @@ -1,10 +1,14 @@ add_compiler_rt_component(safestack) +add_compiler_rt_component(safestacksepseg) set(SAFESTACK_SOURCES safestack.cc) +set(SAFESTACKMPX_SOURCES safestackmpx.cc) include_directories(..) set(SAFESTACK_CFLAGS ${SANITIZER_COMMON_CFLAGS}) +# CPUID will be used to check for XSAVEC support at runtime +set(SAFESTACKMPX_CFLAGS ${SANITIZER_COMMON_CFLAGS} -mxsavec) if(APPLE) # Build universal binary on APPLE. @@ -31,4 +35,17 @@ CFLAGS ${SAFESTACK_CFLAGS} PARENT_TARGET safestack) endforeach() + add_compiler_rt_runtime(clang_rt.safestacksepseg + STATIC + ARCHS x86_64 + # Build safestack sources into this library instead of linking both + # libraries, because RTSanitizerCommonNoLibc and RTSanitizerCommonLibc define + # some identical symbols. + SOURCES ${SAFESTACK_SOURCES} + ${SAFESTACKMPX_SOURCES} + $ + $ + $ + CFLAGS ${SAFESTACKMPX_CFLAGS} + PARENT_TARGET safestacksepseg) endif() Index: lib/safestack/safestack.cc =================================================================== --- lib/safestack/safestack.cc +++ lib/safestack/safestack.cc @@ -134,6 +134,9 @@ size_t unsafe_stack_guard; }; +__attribute__((weak)) +void __safestack_sepseg_thread_start() {} + /// Wrap the thread function in order to deallocate the unsafe stack when the /// thread terminates by returning from its main function. static void *thread_start(void *arg) { @@ -169,6 +172,13 @@ } } +// The SafeStack separate segment library overrides this to modify the +// attributes. +__attribute__((weak)) +void __safestack_sepseg_pthread_create(const pthread_attr_t **attr, + pthread_attr_t *tmpattr, + size_t size, size_t guard) {} + /// Intercept thread creation operation to allocate and setup the unsafe stack INTERCEPTOR(int, pthread_create, pthread_t *thread, const pthread_attr_t *attr, @@ -202,9 +212,20 @@ tinfo->unsafe_stack_size = size; tinfo->unsafe_stack_guard = guard; - return REAL(pthread_create)(thread, attr, thread_start, tinfo); + pthread_attr_t tmpattr; + __safestack_sepseg_pthread_create(&attr, &tmpattr, size, guard); + + int result = REAL(pthread_create)(thread, attr, thread_start, tinfo); + + if (attr == &tmpattr) + CHECK_EQ(pthread_attr_destroy(&tmpattr), 0); + + return result; } +__attribute__((weak)) +void __safestack_sepseg_init(unsigned pageSize) {} + extern "C" __attribute__((visibility("default"))) #if !SANITIZER_CAN_USE_PREINIT_ARRAY // On ELF platforms, the constructor is invoked using .preinit_array (see below) @@ -230,6 +251,8 @@ // Setup the cleanup handler pthread_key_create(&thread_cleanup_key, thread_cleanup_handler); + + __safestack_sepseg_init(pageSize); } #if SANITIZER_CAN_USE_PREINIT_ARRAY Index: lib/safestack/safestackmpx.cc =================================================================== --- /dev/null +++ lib/safestack/safestackmpx.cc @@ -0,0 +1,192 @@ +//===-- safestackmpx.cc ---------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file implements the runtime support for safe stack hardening based on +// Intel MPX. The runtime initializes MPX and manages allocation of the safe +// stack for each pthread created during program execution. +// +//===----------------------------------------------------------------------===// + + +#include +#include +#include +#include + +#include "sanitizer_common/sanitizer_common.h" +#include "sanitizer_common/sanitizer_libc.h" +#include "sanitizer_common/sanitizer_mutex.h" + +// Cap on amount of virtual address space that can be used beneath initial stack +// to allocate new safe stacks. +#define MAX_SAFE_STACK_CAPACITY 0x10000000 + +using namespace __sanitizer; + +// FIXME: Free safe stack when thread is destroyed. + +#define PAGE_SIZE 4096 + +// Aligned to fill a whole page, since this page will be marked read-only to +// prevent an adversary from modifying this to disable the hardening. +struct ro_settings { + /// Set to true to indicate that MPX-based hardening is enabled. + bool enabled; +} __attribute__((aligned(PAGE_SIZE))); +static ro_settings locked_settings = { false }; + +struct rw_settings { +private: + /// It is necessary to protect this variable to prevent an adversary from + /// modifying it to cause safe stacks to overlap or to be allocated in + /// vulnerable locations. + uptr safe_stack_last_alloc; + /// The associated mutex also needs to be protected. + StaticSpinMutex safe_stack_last_alloc_mutex; + +public: + void init(uptr safe_stack_last_alloc_) { + safe_stack_last_alloc = safe_stack_last_alloc_; + + safe_stack_last_alloc_mutex.Init(); + } + + /// Compute the base address of a new safe stack with the given size. + uptr alloc(size_t size) { + safe_stack_last_alloc_mutex.Lock(); + uptr new_alloc = safe_stack_last_alloc - size; + safe_stack_last_alloc = new_alloc; + safe_stack_last_alloc_mutex.Unlock(); + + return new_alloc; + } +} __attribute__((aligned(PAGE_SIZE))); + +static uptr mpx_get_bnd0_ub() { + uptr bnd0[2]; + __asm__ __volatile__("bndmov %%bnd0, %0" : "=m"(bnd0)); + return ~bnd0[1]; +} + +static void mpx_set_bnd0_ub(uptr ub) { + __asm__ __volatile__("bndmk %0, %%bnd0" :: "m"(*(char *)ub)); +} + +static inline void *safe_stack_alloc(size_t size, size_t guard) { + uptr bnd = mpx_get_bnd0_ub(); + rw_settings *protected_settings = reinterpret_cast(bnd); + size_t alloc_size = size + guard; + CHECK_GE(alloc_size, size); + uptr req_addr = protected_settings->alloc(alloc_size); + // This check can be triggered by a program creating a large number of threads + // or a large stack. An alternative approach to avoid that limitation could + // be to allow the program to keep running even when its safe stacks protrude + // below the bound. Of course, the protruding portions of the safe stacks + // would be vulnerable. Another alternative could be to treat the MPX bounds + // registers as per-program state rather than per-thread state. BND0 could + // then be adjusted downwards as necessary when new safe stacks are allocated. + // Both options would require relocating the rw_settings object. + CHECK_GE(req_addr, protected_settings + 1); + uptr rw_base = req_addr + guard; + CHECK_EQ(MprotectReadWrite(rw_base, size), true); + return (char *)rw_base; +} + +void __safestack_sepseg_pthread_create(const pthread_attr_t **attr, + pthread_attr_t *tmpattr, + size_t size, size_t guard) { + if (!locked_settings.enabled) + return; + + void *safe_stack_addr = safe_stack_alloc(size, guard); + CHECK_EQ(pthread_attr_init(tmpattr), 0); + // FIXME: If attr is non-null, copy other settings into tmpattr. + CHECK_EQ(pthread_attr_setstack(tmpattr, safe_stack_addr, size), 0); + *attr = tmpattr; +} + +void __safestack_sepseg_init(unsigned pageSize) { + // This file implements the runtime support for protecting safe stacks against + // stray writes using MPX. The runtime initializes MPX, intercepts the creation + // of new safe stacks to place them at high addresses, and configures the BND0 + // register to have an upper bound below all safe stacks. + + unsigned cpuid_res; + __asm__ __volatile__("cpuid" : "=b"(cpuid_res) : "a"(7), "c"(0) : "rdx"); + if (((cpuid_res >> 14) & 1) != 1) + // No MPX support + return; + __asm__ __volatile__("cpuid" : "=a"(cpuid_res) : "a"(0xD), "c"(0) : "rbx", "rdx"); + if (((cpuid_res >> 3) & 3) != 3) + // Insufficient MPX support + return; + __asm__ __volatile__("cpuid" : "=a"(cpuid_res) : "a"(0xD), "c"(1) : "rbx", "rdx"); + if (((cpuid_res >> 1) & 1) != 1) + // No XSAVEC support + return; + + struct { + char pad[512]; + uint64_t xstate_bv; + uint64_t xcomp_bv; + char pad1[48]; + struct { + uint64_t en : 1; + uint64_t bprv : 1; + uint64_t : 10; + uint64_t tblbase : 52; + } bndcfgu; + uint64_t bndstatus; + } xsave_area __attribute__((aligned(64))); + + memset(&xsave_area, 0, sizeof(xsave_area)); + xsave_area.bndcfgu.en = 1; + xsave_area.bndcfgu.bprv = 1; + xsave_area.xcomp_bv = 0x8000000000000010ull; + xsave_area.xstate_bv = 0x10; + _xrstor(&xsave_area, 1 << 4); + + pthread_attr_t attr; + void *safe_stack = nullptr; + size_t size = 0; + + CHECK_EQ(pthread_getattr_np(pthread_self(), &attr), 0); + CHECK_EQ(pthread_attr_getstack(&attr, &safe_stack, &size), 0); + CHECK_EQ(pthread_attr_destroy(&attr), 0); + + // This assumes that a single page is used as the guard: + uptr guard_base = (uptr)safe_stack - pageSize; + + uptr safe_stack_cap = MAX_SAFE_STACK_CAPACITY; + + auto below_safe_stacks = [&]() { + return guard_base - (safe_stack_cap + sizeof(rw_settings)); + }; + + while (!MemoryRangeIsAvailable(below_safe_stacks(), guard_base - 1)) { + safe_stack_cap >>= 1; + CHECK_GT(safe_stack_cap, pageSize); + } + + mpx_set_bnd0_ub(below_safe_stacks()); + + uptr future_safe_stacks = below_safe_stacks() + sizeof(rw_settings); + + // Reserve space for additional safe stacks. + CHECK_EQ(MmapFixedNoAccess(future_safe_stacks, safe_stack_cap, nullptr), + future_safe_stacks); + // Reserve space for protected settings just above the bound. + MmapFixedOrDie(below_safe_stacks(), sizeof(rw_settings)); + rw_settings *protected_settings = + reinterpret_cast(below_safe_stacks()); + protected_settings->init(guard_base); + + locked_settings.enabled = true; + CHECK_EQ(MprotectReadOnly((uptr)&locked_settings, PAGE_SIZE), true); +} Index: lib/sanitizer_common/sanitizer_common.h =================================================================== --- lib/sanitizer_common/sanitizer_common.h +++ lib/sanitizer_common/sanitizer_common.h @@ -97,6 +97,7 @@ // unaccessible memory. bool MprotectNoAccess(uptr addr, uptr size); bool MprotectReadOnly(uptr addr, uptr size); +bool MprotectReadWrite(uptr addr, uptr size); // Find an available address space. uptr FindAvailableMemoryRange(uptr size, uptr alignment, uptr left_padding); Index: lib/sanitizer_common/sanitizer_posix.cc =================================================================== --- lib/sanitizer_common/sanitizer_posix.cc +++ lib/sanitizer_common/sanitizer_posix.cc @@ -205,6 +205,10 @@ return 0 == internal_mprotect((void *)addr, size, PROT_READ); } +bool MprotectReadWrite(uptr addr, uptr size) { + return 0 == internal_mprotect((void *)addr, size, PROT_READ | PROT_WRITE); +} + fd_t OpenFile(const char *filename, FileAccessMode mode, error_t *errno_p) { int flags; switch (mode) {