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,10 @@ CFLAGS ${SAFESTACK_CFLAGS} PARENT_TARGET safestack) endforeach() + add_compiler_rt_runtime(clang_rt.safestacksepseg + STATIC + ARCHS x86_64 + 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,152 @@ +//===-- 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_mutex.h" + +// Cap on amount of virtual address space that can be used beneath initial stack +// to allocate new safe stacks before they protrude beneath the bound that is +// checked by the instrumented code. +#define SAFE_STACK_CAPACITY 0x10000000 + +using namespace __sanitizer; + +// FIXME: Free safe stack when thread is destroyed. + +#define PAGE_SIZE 4096 + +// Set to true to indicate that MPX is available. Aligned to fill a whole page, +// since that page will be marked read-only to prevent an adversary from +// modifying this to disable the hardening. +struct ro_settings { + bool enabled; +} __attribute__((aligned(PAGE_SIZE))); +static ro_settings locked_settings = { false }; + +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 uptr safe_stack_last_alloc; +static StaticSpinMutex safe_stack_last_alloc_mutex; + +static inline void *safe_stack_alloc(size_t size, size_t guard) { + safe_stack_last_alloc_mutex.Lock(); + CHECK_GE(size + guard, size); + uptr req_addr = safe_stack_last_alloc - (size + guard); + // It is necessary to check that the address of the new stack is not below the + // bound that is checked by the instrumented code. Otherwise, an adversary + // may be able to modify safe_stack_last_alloc to cause safe stacks to be + // allocated in vulnerable locations. However, this check can also 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 store + // safe_stack_last_alloc above the bound checked by the instrumented code. + // This could help to prevent adversaries from forcing safe stacks to be + // allocated at vulnerable locations while still allowing 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. + CHECK_GE(req_addr, mpx_get_bnd0_ub()); + safe_stack_last_alloc = req_addr; + safe_stack_last_alloc_mutex.Unlock(); + void *addr = MmapFixedOrDie(req_addr, size + guard); + MprotectNoAccess((uptr)addr, (uptr)guard); + return (char *)addr + guard; +} + +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: + safe_stack_last_alloc = (uptr)safe_stack - pageSize; + + mpx_set_bnd0_ub((uptr)safe_stack - SAFE_STACK_CAPACITY); + + locked_settings.enabled = true; + CHECK_EQ(MprotectReadOnly((uptr)&locked_settings, PAGE_SIZE), true); +}