Index: CMakeLists.txt =================================================================== --- CMakeLists.txt +++ CMakeLists.txt @@ -207,6 +207,7 @@ append_list_if(COMPILER_RT_HAS_FOMIT_FRAME_POINTER_FLAG -fomit-frame-pointer SANITIZER_COMMON_CFLAGS) append_list_if(COMPILER_RT_HAS_FUNWIND_TABLES_FLAG -funwind-tables SANITIZER_COMMON_CFLAGS) append_list_if(COMPILER_RT_HAS_FNO_STACK_PROTECTOR_FLAG -fno-stack-protector SANITIZER_COMMON_CFLAGS) +append_list_if(COMPILER_RT_HAS_FNO_SAFE_STACK_FLAG -fno-safe-stack SANITIZER_COMMON_CFLAGS) append_list_if(COMPILER_RT_HAS_FVISIBILITY_HIDDEN_FLAG -fvisibility=hidden SANITIZER_COMMON_CFLAGS) append_list_if(COMPILER_RT_HAS_FNO_FUNCTION_SECTIONS_FLAG -fno-function-sections SANITIZER_COMMON_CFLAGS) append_list_if(COMPILER_RT_HAS_FNO_LTO_FLAG -fno-lto SANITIZER_COMMON_CFLAGS) Index: cmake/config-ix.cmake =================================================================== --- cmake/config-ix.cmake +++ cmake/config-ix.cmake @@ -19,6 +19,7 @@ check_cxx_compiler_flag(-fomit-frame-pointer COMPILER_RT_HAS_FOMIT_FRAME_POINTER_FLAG) check_cxx_compiler_flag(-funwind-tables COMPILER_RT_HAS_FUNWIND_TABLES_FLAG) check_cxx_compiler_flag(-fno-stack-protector COMPILER_RT_HAS_FNO_STACK_PROTECTOR_FLAG) +check_cxx_compiler_flag(-fno-safe-stack COMPILER_RT_HAS_FNO_SAFE_STACK_FLAG) check_cxx_compiler_flag(-fvisibility=hidden COMPILER_RT_HAS_FVISIBILITY_HIDDEN_FLAG) check_cxx_compiler_flag(-fno-rtti COMPILER_RT_HAS_FNO_RTTI_FLAG) check_cxx_compiler_flag(-ffreestanding COMPILER_RT_HAS_FFREESTANDING_FLAG) @@ -254,6 +255,7 @@ filter_available_targets(TSAN_SUPPORTED_ARCH x86_64 mips64 mips64el) filter_available_targets(UBSAN_SUPPORTED_ARCH x86_64 i386 i686 arm aarch64 mips mipsel mips64 mips64el powerpc64 powerpc64le) +filter_available_targets(SAFESTACK_SUPPORTED_ARCH x86_64 i386 i686) if(ANDROID) set(OS_NAME "Android") @@ -332,3 +334,10 @@ if("${LLVM_NATIVE_ARCH}" STREQUAL "Mips") set(COMPILER_RT_HAS_MSSE3_FLAG FALSE) endif() + +if (SAFESTACK_SUPPORTED_ARCH AND + OS_NAME MATCHES "Darwin|Linux|FreeBSD") + set(COMPILER_RT_HAS_SAFESTACK TRUE) +else() + set(COMPILER_RT_HAS_SAFESTACK FALSE) +endif() Index: lib/CMakeLists.txt =================================================================== --- lib/CMakeLists.txt +++ lib/CMakeLists.txt @@ -34,3 +34,6 @@ add_subdirectory(tsan/dd) endif() +if(COMPILER_RT_HAS_SAFESTACK) + add_subdirectory(safestack) +endif() Index: lib/safestack/CMakeLists.txt =================================================================== --- /dev/null +++ lib/safestack/CMakeLists.txt @@ -0,0 +1,28 @@ +add_custom_target(safestack) + +set(SAFESTACK_SOURCES safestack.cc) + +include_directories(..) + +set(SAFESTACK_CFLAGS ${SANITIZER_COMMON_CFLAGS}) + +if(APPLE) + # Build universal binary on APPLE. + add_compiler_rt_osx_static_runtime(clang_rt.safestack_osx + ARCH ${SAFESTACK_SUPPORTED_ARCH} + SOURCES ${SAFESTACK_SOURCES} + $ + $ + CFLAGS ${SAFESTACK_CFLAGS}) + add_dependencies(safestack clang_rt.safestack_osx) +else() + # Otherwise, build separate libraries for each target. + foreach(arch ${SAFESTACK_SUPPORTED_ARCH}) + add_compiler_rt_runtime(clang_rt.safestack-${arch} ${arch} STATIC + SOURCES ${SAFESTACK_SOURCES} + $ + $ + CFLAGS ${SAFESTACK_CFLAGS}) + add_dependencies(safestack clang_rt.safestack-${arch}) + endforeach() +endif() Index: lib/safestack/safestack.cc =================================================================== --- /dev/null +++ lib/safestack/safestack.cc @@ -0,0 +1,302 @@ +//===-- safestack.cc --------------------------------------------*- C++ -*-===// +// +// 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 the safe stack protection +// mechanism. The runtime manages allocation/deallocation of the unsafe stack +// for the main thread, as well as all pthreads that are created/destroyed +// during program execution. +// +//===----------------------------------------------------------------------===// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__linux__) +#include +#include +#endif + +#include "interception/interception.h" +#include "sanitizer_common/sanitizer_posix.h" + +/// Minimum stack alignment for the unsafe stack. +/// FIXME: is this in some header? +#define STACK_ALIGN 16 + +/// Default size of the unsafe stack. This value is only used if the stack +/// size rlimit is set to infinity. +#define DEFAULT_UNSAFE_STACK_SIZE 0x2800000 + + +// define MAP_STACK if undefined, this flag is used as a hint +#ifndef MAP_STACK +# define MAP_STACK 0 +#endif + +// define MAP_GROWSDOWN if undefined, this flag is used as a hint +#ifndef MAP_GROWSDOWN +# define MAP_GROWSDOWN 0 +#endif + +// We don't know whether pthread is linked in or not, so we resolve +// all symbols from pthread that we use dynamically +#define __DECLARE_WRAPPER(fn) static __typeof__(fn)* __d_ ## fn = NULL; + +__DECLARE_WRAPPER(pthread_attr_init) +__DECLARE_WRAPPER(pthread_attr_destroy) +__DECLARE_WRAPPER(pthread_attr_getstacksize) +__DECLARE_WRAPPER(pthread_attr_getguardsize) +__DECLARE_WRAPPER(pthread_key_create) +__DECLARE_WRAPPER(pthread_setspecific) + +// TODO: To make accessing the unsafe stack pointer faster, we plan to +// eventually store it directly in the thread control block data structure on +// platforms where this structure is pointed to by %fs or %gs. This is exactly +// the same mechanism as currently being used by the traditional stack +// protector pass to store the stack guard (see getStackCookieLocation() +// function above). Doing so requires changing the tcbhead_t struct in glibc +// on Linux and tcb struct in libc on FreeBSD. +// +// For now, store it in a thread-local variable. +extern "C" { + __attribute__((visibility ("default"))) + __thread void *__safestack_unsafe_stack_ptr = 0; +} + +#define __GET_UNSAFE_STACK_PTR() __safestack_unsafe_stack_ptr +#define __SET_UNSAFE_STACK_PTR(value) __safestack_unsafe_stack_ptr = (value) + +// Per-thread unsafe stack information. It's not frequently accessed, so there +// it can be kept out of the tcb in normal thread-local variables. +static __thread void *unsafe_stack_start = 0; +static __thread size_t unsafe_stack_size = 0; +static __thread size_t unsafe_stack_guard = 0; + +# define __GET_UNSAFE_STACK_START() unsafe_stack_start +# define __SET_UNSAFE_STACK_START(value) unsafe_stack_start = (value) + +# define __GET_UNSAFE_STACK_SIZE() unsafe_stack_size +# define __SET_UNSAFE_STACK_SIZE(value) unsafe_stack_size = (value) + +# define __GET_UNSAFE_STACK_GUARD() unsafe_stack_guard +# define __SET_UNSAFE_STACK_GUARD(value) unsafe_stack_guard = (value) + +static inline void *unsafe_stack_alloc(size_t size, size_t guard) { + // We need the mmap system call without any LD_PRELOAD overrides + // (such overrides might crash is they use the unsafe stack themselves) + void *addr = (void *)internal_mmap( + NULL, size + guard, PROT_WRITE | PROT_READ, + MAP_PRIVATE | MAP_ANON | MAP_STACK | MAP_GROWSDOWN, -1, 0); + + if (addr == MAP_FAILED) + return nullptr; + + internal_mprotect(addr, guard, PROT_NONE); + return (char*) addr + guard; +} + +static inline void unsafe_stack_setup(void *start, size_t size, size_t guard) { + void* stack_ptr = (char*) start + size; + assert((((size_t)stack_ptr) & (STACK_ALIGN-1)) == 0); + + __SET_UNSAFE_STACK_PTR(stack_ptr); + __SET_UNSAFE_STACK_START(start); + __SET_UNSAFE_STACK_SIZE(size); + __SET_UNSAFE_STACK_GUARD(guard); +} + +static void unsafe_stack_free() { + if (__GET_UNSAFE_STACK_START()) { + // We need the munmap system call without any LD_PRELOAD overrides + // (such overrides might crash is they use the unsafe stack themselves) + (void)internal_munmap( + (char*) __GET_UNSAFE_STACK_START() - __GET_UNSAFE_STACK_GUARD(), + __GET_UNSAFE_STACK_SIZE() + __GET_UNSAFE_STACK_GUARD()); + } + __SET_UNSAFE_STACK_START(0); +} + +/// Thread data for the cleanup handler +static pthread_key_t thread_cleanup_key; + +/// Safe stack per-thread information passed to the thread_start function +struct tinfo { + void *(*start_routine)(void*); + void *start_routine_arg; + + void *unsafe_stack_start; + size_t unsafe_stack_size; + size_t unsafe_stack_guard; +}; + +/// 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) { + struct tinfo *tinfo = (struct tinfo*) arg; + + void *(*start_routine)(void*) = tinfo->start_routine; + void *start_routine_arg = tinfo->start_routine_arg; + + // Setup the unsafe stack; this will destroy tinfo content + unsafe_stack_setup(tinfo->unsafe_stack_start, + tinfo->unsafe_stack_size, + tinfo->unsafe_stack_guard); + + // Make sure out thread-specific destructor will be called + // FIXME: we can do this only any other specific key is set by + // intersepting the pthread_setspecific function itself + __d_pthread_setspecific(thread_cleanup_key, (void*) 1); + + // Start the original thread rutine + return start_routine(start_routine_arg); +} + +/// Thread-specific data destructor +static void thread_cleanup_handler(void* _iter) { + // We want to free the unsafe stack only after all other destructors + // have already run. We force this function to be called multiple times. + // User destructors that might run more then PTHREAD_DESTRUCTOR_ITERATIONS-1 + // times might still end up executing after the unsafe stack is deallocated, + // so such descructors must have __attribute__((no_safe_stack)). + size_t iter = (size_t) _iter; + if (iter < PTHREAD_DESTRUCTOR_ITERATIONS) { + __d_pthread_setspecific(thread_cleanup_key, (void*) (iter + 1)); + } else { + // This is the last iteration + unsafe_stack_free(); + } +} + +/// Intercept thread creation operation to allocate and setup the unsafe stack +INTERCEPTOR(int, pthread_create, pthread_t *thread, + const pthread_attr_t *attr, + void *(*start_routine)(void*), void *arg) { + + size_t size = 0; + size_t guard = 0; + + if (attr != NULL) { + __d_pthread_attr_getstacksize(attr, &size); + __d_pthread_attr_getguardsize(attr, &guard); + } else { + // get pthread default stack size + pthread_attr_t tmpattr; + __d_pthread_attr_init(&tmpattr); + __d_pthread_attr_getstacksize(&tmpattr, &size); + __d_pthread_attr_getguardsize(&tmpattr, &guard); + __d_pthread_attr_destroy(&tmpattr); + } + + assert(size != 0); + assert((size & (STACK_ALIGN-1)) == 0); + assert((guard & (PAGE_SIZE-1)) == 0); + + void *addr = unsafe_stack_alloc(size, guard); + if (!addr) { + // failed to allocate the unsafe stack + errno = EAGAIN; + return -1; + } + struct tinfo *tinfo = (struct tinfo*) ( + ((char*)addr) + size - sizeof(struct tinfo)); + tinfo->start_routine = start_routine; + tinfo->start_routine_arg = arg; + tinfo->unsafe_stack_start = addr; + tinfo->unsafe_stack_size = size; + tinfo->unsafe_stack_guard = guard; + + return REAL(pthread_create)(thread, attr, thread_start, tinfo); +} + +extern "C" +__attribute__((visibility ("default"))) +#ifndef __ELF__ +// On ELF platforms, the constructor is invoked using .preinit_array (see below) +__attribute__((constructor(0))) +#endif +void __safestack_init() { + static int initialized = 0; + + if (initialized) + return; + + initialized = 1; + + // Determine the stack size for the main thread. + size_t size = DEFAULT_UNSAFE_STACK_SIZE; + size_t guard = 4096; + + struct rlimit limit; + if (getrlimit(RLIMIT_STACK, &limit) == 0 + && limit.rlim_cur != RLIM_INFINITY) + size = limit.rlim_cur; + + // Allocate unsafe stack for main thread + void *addr = unsafe_stack_alloc(size, guard); + if (!addr) + // Failed to allocate the unsafe stack. + abort(); + + unsafe_stack_setup(addr, size, guard); + + // Initialize pthread interceptors for thread allocation + INTERCEPT_FUNCTION(pthread_create); + + #define __FIND_FUNCTION(fn) \ + __d_ ## fn = __extension__ (__typeof__(__d_ ## fn)) dlsym(RTLD_DEFAULT, #fn); + + // Find pthread functions that we need + __FIND_FUNCTION(pthread_attr_init) + __FIND_FUNCTION(pthread_attr_destroy) + __FIND_FUNCTION(pthread_attr_getstacksize) + __FIND_FUNCTION(pthread_attr_getguardsize) + __FIND_FUNCTION(pthread_key_create) + __FIND_FUNCTION(pthread_setspecific) + + if (__d_pthread_key_create != NULL) { + // We're using pthreads, setup the cleanup handler + __d_pthread_key_create(&thread_cleanup_key, thread_cleanup_handler); + } +} + +#ifdef __ELF__ +// Run safestack initialization before any other constructors. +// FIXME: can we do something similar on non-ELF platforms, e.g., on Mac? +extern "C" { +__attribute__((section(".preinit_array"), used)) +void (*__safestack_preinit)(void) = __safestack_init; +} +#endif + +extern "C" +__attribute__((visibility ("default"))) +void *__get_unsafe_stack_start() { + return __GET_UNSAFE_STACK_START(); +} + +extern "C" +__attribute__((visibility ("default"))) +void *__get_unsafe_stack_ptr() { + return __GET_UNSAFE_STACK_PTR(); +} + +extern "C" +__attribute__((visibility ("default"))) +__attribute__((noinline)) // required for __builtin_frame_address(0) to work +void *__get_safe_stack_ptr() { + return (char*) __builtin_frame_address(0) + 2*sizeof(void*); +} Index: test/CMakeLists.txt =================================================================== --- test/CMakeLists.txt +++ test/CMakeLists.txt @@ -58,6 +58,9 @@ add_subdirectory(ubsan) endif() add_subdirectory(cfi) + if(COMPILER_RT_HAS_SAFESTACK) + add_subdirectory(safestack) + endif() endif() if(COMPILER_RT_STANDALONE_BUILD) Index: test/safestack/CMakeLists.txt =================================================================== --- /dev/null +++ test/safestack/CMakeLists.txt @@ -0,0 +1,17 @@ +set(SAFESTACK_LIT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) +set(SAFESTACK_LIT_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}) + +set(SAFESTACK_TEST_DEPS ${SANITIZER_COMMON_LIT_TEST_DEPS}) +if(NOT COMPILER_RT_STANDALONE_BUILD) + list(APPEND SAFESTACK_TEST_DEPS safestack clang) +endif() + +configure_lit_site_cfg( + ${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.in + ${CMAKE_CURRENT_BINARY_DIR}/lit.site.cfg + ) + +add_lit_testsuite(check-safestack "Running the SafeStack tests" + ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS ${SAFESTACK_TEST_DEPS}) +set_target_properties(check-safestack PROPERTIES FOLDER "SafeStack tests") Index: test/safestack/check-buffer-copy.c =================================================================== --- /dev/null +++ test/safestack/check-buffer-copy.c @@ -0,0 +1,21 @@ +// RUN: %clang_safestack %s -o %t +// RUN: %run %t + +// Test that loads/stores work correctly for variables on the unsafe stack. + +int main(int argc, char **argv) +{ + int i; + char buffer[128]; + + // check that we can write to a buffer + for (i = 0; argv[0][i] && i < sizeof (buffer) - 1; ++i) + buffer[i] = argv[0][i]; + buffer[i] = '\0'; + + // check that we can read from a buffer + for (i = 0; argv[0][i] && i < sizeof (buffer) - 1; ++i) + if (buffer[i] != argv[0][i]) + return 1; + return 0; +} Index: test/safestack/check-init.c =================================================================== --- /dev/null +++ test/safestack/check-init.c @@ -0,0 +1,9 @@ +// RUN: %clang_safestack %s -o %t +// RUN: %run %t + +// Basic smoke test for the runtime library. + +int main(int argc, char **argv) +{ + return 0; +} Index: test/safestack/check-overflow.c =================================================================== --- /dev/null +++ test/safestack/check-overflow.c @@ -0,0 +1,23 @@ +// RUN: %clang_safestack %s -o %t +// RUN: %run %t + +// RUN: %clang_nosafestack -fno-stack-protector %s -o %t +// RUN: not %run %t + +// Test that buffer overflows on the unsafe stack do not affect variables on the +// safe stack. + +void fct(int *buffer) +{ + buffer[-1] = 36; + buffer[6] = 36; +} + +int main(int argc, char **argv) +{ + int value1 = 42; + int buffer[5]; + int value2 = 42; + fct(buffer); + return value1 != 42 || value2 != 42; +} Index: test/safestack/check-pthread-cleanup.c =================================================================== --- /dev/null +++ test/safestack/check-pthread-cleanup.c @@ -0,0 +1,31 @@ +// RUN: %clang_safestack %s -pthread -o %t +// RUN: not --crash %run %t + +// Test that unsafe stacks are deallocated correctly on thread exit. + +#include +#include +#include + +#define BUFFER_SIZE (1 << 15) + +void *t1_start(void *ptr) +{ + char buffer[BUFFER_SIZE]; + return buffer; +} + +int main(int argc, char **argv) +{ + pthread_t t1; + char *buffer = NULL; + + if (pthread_create(&t1, NULL, t1_start, NULL)) + abort(); + if (pthread_join(t1, &buffer)) + abort(); + + // should segfault here + memset(buffer, 0, BUFFER_SIZE); + return 0; +} Index: test/safestack/check-pthread.c =================================================================== --- /dev/null +++ test/safestack/check-pthread.c @@ -0,0 +1,38 @@ +// RUN: %clang_safestack %s -pthread -o %t +// RUN: %run %t + +// Test that pthreads receive their own unsafe stack. + +#include +#include +#include + +static int ptr_test = 42; + +void *t1_start(void *ptr) +{ + if (ptr != &ptr_test) + abort(); + + // safe stack + int val = ptr_test * 5; + + // unsafe stack + char buffer[8096]; // two pages + memset(buffer, val, sizeof (buffer)); + + return ptr; +} + +int main(int argc, char **argv) +{ + pthread_t t1; + void *ptr = NULL; + if (pthread_create(&t1, NULL, t1_start, &ptr_test)) + abort(); + if (pthread_join(t1, &ptr)) + abort(); + if (ptr != &ptr_test) + abort(); + return 0; +} Index: test/safestack/lit.cfg =================================================================== --- /dev/null +++ test/safestack/lit.cfg @@ -0,0 +1,20 @@ +# -*- Python -*- + +import os + +# Setup config name. +config.name = 'SafeStack' + +# Setup source root. +config.test_source_root = os.path.dirname(__file__) + +# Test suffixes. +config.suffixes = ['.c', '.cc', '.cpp', '.m', '.mm', '.ll', '.test'] + +# Add clang substitutions. +config.substitutions.append( ("%clang_nosafestack ", config.clang + " -O0 -fno-sanitize=safe-stack ") ) +config.substitutions.append( ("%clang_safestack ", config.clang + " -O0 -fsanitize=safe-stack ") ) + +# SafeStack tests are currently supported on Linux, FreeBSD and Darwin only. +if config.host_os not in ['Linux', 'FreeBSD', 'Darwin']: + config.unsupported = True Index: test/safestack/lit.site.cfg.in =================================================================== --- /dev/null +++ test/safestack/lit.site.cfg.in @@ -0,0 +1,8 @@ +## Autogenerated by LLVM/Clang configuration. +# Do not edit! + +# Load common config for all compiler-rt lit tests. +lit_config.load_config(config, "@COMPILER_RT_BINARY_DIR@/test/lit.common.configured") + +# Load tool-specific config that would do the real work. +lit_config.load_config(config, "@SAFESTACK_LIT_SOURCE_DIR@/lit.cfg")