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_SANITIZE_SAFE_STACK_FLAG -fno-sanitize=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-sanitize=safe-stack COMPILER_RT_HAS_FNO_SANITIZE_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) @@ -255,6 +256,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") @@ -333,3 +335,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,212 @@ +//===-- safestack.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 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 "interception/interception.h" +#include "sanitizer_common/sanitizer_common.h" + +/// Minimum stack alignment for the unsafe stack. +const unsigned kStackAlign = 16; + +/// Default size of the unsafe stack. This value is only used if the stack +/// size rlimit is set to infinity. +const unsigned kDefaultUnsafeStackSize = 0x2800000; + +// 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 = nullptr; +} + +// 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 = nullptr; +static __thread size_t unsafe_stack_size = 0; +static __thread size_t unsafe_stack_guard = 0; + +static inline void *unsafe_stack_alloc(size_t size, size_t guard) { + CHECK_GE(size + guard, size); + void *addr = MmapOrDie(size + guard, "unsafe_stack_alloc"); + MprotectNoAccess((uptr)addr, (uptr)guard); + return (char*) addr + guard; +} + +static inline void unsafe_stack_setup(void *start, size_t size, size_t guard) { + void* stack_ptr = (char*) start + size; + CHECK_EQ((((size_t)stack_ptr) & (kStackAlign-1)), 0); + + __safestack_unsafe_stack_ptr = stack_ptr; + unsafe_stack_start = start; + unsafe_stack_size = size; + unsafe_stack_guard = guard; +} + +static void unsafe_stack_free() { + if (unsafe_stack_start) { + UnmapOrDie( + (char*) unsafe_stack_start - unsafe_stack_guard, + unsafe_stack_size + unsafe_stack_guard); + } + unsafe_stack_start = nullptr; +} + +/// 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 + // intercepting the pthread_setspecific function itself + pthread_setspecific(thread_cleanup_key, (void*) 1); + + 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. + size_t iter = (size_t) _iter; + if (iter < PTHREAD_DESTRUCTOR_ITERATIONS) { + 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) { + pthread_attr_getstacksize(attr, &size); + pthread_attr_getguardsize(attr, &guard); + } else { + // get pthread default stack size + pthread_attr_t tmpattr; + pthread_attr_init(&tmpattr); + pthread_attr_getstacksize(&tmpattr, &size); + pthread_attr_getguardsize(&tmpattr, &guard); + pthread_attr_destroy(&tmpattr); + } + + CHECK_NE(size, 0); + CHECK_EQ((size & (kStackAlign-1)), 0); + CHECK_EQ((guard & (PAGE_SIZE-1)), 0); + + void *addr = unsafe_stack_alloc(size, guard); + 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"))) +#if !SANITIZER_CAN_USE_PREINIT_ARRAY +// On ELF platforms, the constructor is invoked using .preinit_array (see below) +__attribute__((constructor(0))) +#endif +void __safestack_init() { + // Determine the stack size for the main thread. + size_t size = kDefaultUnsafeStackSize; + 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); + + unsafe_stack_setup(addr, size, guard); + + // Initialize pthread interceptors for thread allocation + INTERCEPT_FUNCTION(pthread_create); + + // Setup the cleanup handler + pthread_key_create(&thread_cleanup_key, thread_cleanup_handler); +} + +#if SANITIZER_CAN_USE_PREINIT_ARRAY +// On ELF platforms, run safestack 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 (*__safestack_preinit)(void) = __safestack_init; +} +#endif + +extern "C" +__attribute__((visibility ("default"))) +void *__get_unsafe_stack_start() { + return unsafe_stack_start; +} + +extern "C" +__attribute__((visibility ("default"))) +void *__get_unsafe_stack_ptr() { + return __safestack_unsafe_stack_ptr; +} 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,29 @@ +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) + + # Some tests require LTO, so add a dependency on the relevant LTO plugin. + if(LLVM_ENABLE_PIC AND LLVM_BINUTILS_INCDIR) + list(APPEND SAFESTACK_TEST_DEPS + LLVMgold + ) + endif() + if(APPLE) + list(APPEND SAFESTACK_TEST_DEPS + LTO + ) + endif() +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/buffer-copy-vla.c =================================================================== --- /dev/null +++ test/safestack/buffer-copy-vla.c @@ -0,0 +1,26 @@ +// RUN: %clang_safestack %s -o %t +// RUN: %run %t + +#include "utils.h" + +// Test that loads/stores work correctly for VLAs on the unsafe stack. + +int main(int argc, char **argv) +{ + int i = 128; + break_optimization(&i); + char buffer[i]; + + // 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'; + + break_optimization(buffer); + + // 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/buffer-copy.c =================================================================== --- /dev/null +++ test/safestack/buffer-copy.c @@ -0,0 +1,25 @@ +// RUN: %clang_safestack %s -o %t +// RUN: %run %t + +#include "utils.h" + +// 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'; + + break_optimization(buffer); + + // 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/init.c =================================================================== --- /dev/null +++ test/safestack/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/lit.cfg =================================================================== --- /dev/null +++ test/safestack/lit.cfg @@ -0,0 +1,24 @@ +# -*- 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 ") ) + +if config.lto_supported: + config.available_features.add('lto') + config.substitutions.append((r"%clang_lto_safestack ", ' '.join(config.lto_launch + [config.clang] + config.lto_flags + ['-flto -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") Index: test/safestack/lto.c =================================================================== --- /dev/null +++ test/safestack/lto.c @@ -0,0 +1,12 @@ +// REQUIRES: lto + +// RUN: %clang_lto_safestack %s -o %t +// RUN: %run %t + +// Test that safe stack works with LTO. + +int main() { + char c[] = "hello world"; + puts(c); + return 0; +} Index: test/safestack/overflow.c =================================================================== --- /dev/null +++ test/safestack/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. + +__attribute__((noinline)) +void fct(volatile int *buffer) +{ + memset(buffer - 1, 0, 7 * sizeof(int)); +} + +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/pthread-cleanup.c =================================================================== --- /dev/null +++ test/safestack/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 + +enum { kBufferSize = (1 << 15) }; + +void *t1_start(void *ptr) +{ + char buffer[kBufferSize]; + 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, kBufferSize); + return 0; +} Index: test/safestack/pthread.c =================================================================== --- /dev/null +++ test/safestack/pthread.c @@ -0,0 +1,40 @@ +// RUN: %clang_safestack %s -pthread -o %t +// RUN: %run %t + +// Test that pthreads receive their own unsafe stack. + +#include +#include +#include +#include "utils.h" + +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)); + break_optimization(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/utils.h =================================================================== --- /dev/null +++ test/safestack/utils.h @@ -0,0 +1,8 @@ +#ifndef UTILS_H +#define UTILS_H + +static inline void break_optimization(void *arg) { + __asm__ __volatile__("" : : "r" (arg) : "memory"); +} + +#endif