diff --git a/compiler-rt/lib/asan/asan_rtl.cpp b/compiler-rt/lib/asan/asan_rtl.cpp --- a/compiler-rt/lib/asan/asan_rtl.cpp +++ b/compiler-rt/lib/asan/asan_rtl.cpp @@ -31,6 +31,10 @@ #include "ubsan/ubsan_init.h" #include "ubsan/ubsan_platform.h" +#if SANITIZER_POSIX +#include "sanitizer_common/sanitizer_posix.h" +#endif + uptr __asan_shadow_memory_dynamic_address; // Global interface symbol. int __asan_option_detect_stack_use_after_return; // Global interface symbol. uptr *__asan_test_only_reported_buggy_pointer; // Used only for testing asan. @@ -551,7 +555,159 @@ static AsanInitializer asan_initializer; #endif // ASAN_DYNAMIC -} // namespace __asan +/// Returns the cached stack bounds for the default stack. +/// +/// \param[in] CurrThread May be null. If non-null, read stack top from cache. +/// \param[in] LocalStack Address of a variable on the stack, used to find stack +/// bottom. +/// \param[out] Top Top of the default stack. Only guaranteed to be written if +/// return value is true. +/// \param[out] Bottom Bottom of the default stack. Only guaranteed to be +/// written if return value is true. +/// +/// \returns true IFF CurrThread was non-null +static bool getCachedDefaultStackBounds(AsanThread *CurrThread, uptr LocalStack, + /*out*/ uptr *Top, + /*out*/ uptr *Bottom) { + if (CurrThread) { + uptr PageSize = GetPageSizeCached(); + *Top = CurrThread->stack_top(); + *Bottom = (LocalStack - PageSize) & ~(PageSize - 1); + return true; + } else { + return false; + } +} + +#if !SANITIZER_FUCHSIA && !SANITIZER_RTEMS +/// Tries to find stack bounds of default stack. +/// +/// \param[in] LocalStack Address of a variable on the stack, used to find stack +/// bottom. +/// \param[in] CurrThread Current thread, used to look up cached stack top. Can +/// be null, in which case we ask the OS for stack +/// boundaries. +/// \param[out] Top Top of the default stack. Only guaranteed to be written if +/// return value is true. +/// \param[out] Bottom Bottom of the default stack. Only guaranteed to be +/// written if return value is true. +/// +/// \returns true on success (= stack boundaries could be determined). +static bool getDefaultStackBounds(AsanThread *CurrThread, uptr LocalStack, + /*out*/ uptr *Top, /*out*/ uptr *Bottom) { + if (getCachedDefaultStackBounds(CurrThread, LocalStack, Top, Bottom)) { + return true; + } else { + // If we haven't seen this thread, try asking the OS for stack bounds. + uptr TlsAddr, TlsSize, StackSize; + GetThreadStackAndTls(/*main=*/false, Bottom, &StackSize, &TlsAddr, + &TlsSize); + *Top = *Bottom + StackSize; + return true; + } +} +#endif + +#ifdef SANITIZER_POSIX +/// Tries to find stack bounds of active stack. +/// +/// This function determines if the signal alternate stack is the current stack, +/// and if so, returns the stack bounds of the signal alternate stack. +/// Otherwise, and in case of failure to determine if the sig alt stack is +/// active, returns the bounds of the default stack. +/// +/// \param[in] LocalStack address of a variable on the stack, used to find stack +/// bottom. +/// \param[in] CurrThread Used to look up cached stack top. Can be null, in +/// which case we ask the OS for stack boundaries. +/// \param[out] Top Top of the default stack. Only guaranteed to be written if +/// return value is true. +/// \param[out] Bottom Bottom of the default stack. Only guaranteed to be +/// written if return value is true. +/// +/// \returns true on success (= stack boundaries could be determined). +static bool getSignalAltOrDefaultStackBounds(AsanThread *CurrThread, + uptr LocalStack, + /*out*/ uptr *Top, + /*out*/ uptr *Bottom) { + AlternateStackInfo const SignalStack = GetSignalAlternateStack(); + if (0 == SignalStack.error) { + if (SignalStack.on_stack) { + *Bottom = SignalStack.bottom; + *Top = SignalStack.top; + return true; + } else { + return getDefaultStackBounds(CurrThread, LocalStack, Top, Bottom); + } + } else { + // We don't know if we're on the alternate stack, but we can't get any + // information on it anyway. So we assume we're on the default stack. + // For __asan_handle_no_return, this means: + // - if we read the stack top from cache, our range to unpoison is + // [sigalt stack bottom, default stack top). + // . If sigalt stack is shortly before default stack, we'll unpoison both + // stacks => false negatives + // . otherwise, clean-up size heuristic triggers and we don't unpoison + // anything => false positives + // - if we read the stack top from OS + // . if we search for the address of a local variable in the memory + // mappings, we get a correct sigalt stack bottom but the found sigalt + // stack top can be too high (stack is smaller than mapping) or too low + // (stack consists of multiple consecutive mappings, pathological). + // _ if top is too high, we unpoison too much => false negatives + // _ if top is too low, we unpoison too little => false positives + // . using pthread_attr_getstack, we get the default stack bounds. The + // sigalt stack is not unpoisoned => false positives + + bool const Ret = getDefaultStackBounds(CurrThread, LocalStack, Top, Bottom); + + static bool ReportedWarning = false; + if (!ReportedWarning) { + ReportedWarning = true; + Report( + "WARNING: ASan might fail to perform requested " + "__asan_handle_no_return correctly: " + "error code %d from sigaltstack\n" + "stack top: %p; bottom %p; size: %p (%zd)\n" + "False positive error reports may follow\n", + SignalStack.error, Top, Bottom, Top - Bottom, Top - Bottom); + } + + return Ret; + } +} +#endif + +/// Tries to get stack bounds of active stack of current thread. +/// +/// \note There can be multiple stacks associated to a thread, e.g. in case of +/// the signal alternate stack on POSIX. +/// +/// \param[in] CurrThread Can be null. If non-null, we use its cached values for +/// stack top, if applicable. +/// \param[in] LocalStack Address of a variable on the stack, used to find stack +/// bottom. +/// \param[out] Top Top of the current stack. Only guaranteed to be written if +/// return value is true. +/// \param[out] Bottom Bottom of the current stack. Only guaranteed to be +/// written if return value is true. +/// +/// \returns true IFF the stack bounds could be determined. +static bool getCurrentStackBounds(AsanThread *CurrThread, uptr LocalStack, + /*out*/ uptr *Top, /*out*/ uptr *Bottom) { +#if SANITIZER_POSIX + return getSignalAltOrDefaultStackBounds(CurrThread, LocalStack, Top, Bottom); +#elif SANITIZER_RTEMS + return getCachedDefaultStackBounds(CurrThread, LocalStack, Top, Bottom); +#elif SANITIZER_FUCHSIA + CHECK(getCachedDefaultStackBounds(CurrThread, LocalStack, Top, Bottom)); + return true; +#else + return getDefaultStackBounds(CurrThread, LocalStack, Top, Bottom); +#endif +} + +} // namespace __asan // ---------------------- Interface ---------------- {{{1 using namespace __asan; @@ -561,40 +717,31 @@ return; int local_stack; - AsanThread *curr_thread = GetCurrentThread(); - uptr PageSize = GetPageSizeCached(); uptr top, bottom; - if (curr_thread) { - top = curr_thread->stack_top(); - bottom = ((uptr)&local_stack - PageSize) & ~(PageSize - 1); - } else if (SANITIZER_RTEMS) { - // Give up On RTEMS. + AsanThread *CurrThread = GetCurrentThread(); + + if (!getCurrentStackBounds(CurrThread, (uptr)&local_stack, &top, &bottom)) return; - } else { - CHECK(!SANITIZER_FUCHSIA); - // If we haven't seen this thread, try asking the OS for stack bounds. - uptr tls_addr, tls_size, stack_size; - GetThreadStackAndTls(/*main=*/false, &bottom, &stack_size, &tls_addr, - &tls_size); - top = bottom + stack_size; - } + static const uptr kMaxExpectedCleanupSize = 64 << 20; // 64M + // top > bottom is possible, but handled gracefully (see above) if (top - bottom > kMaxExpectedCleanupSize) { static bool reported_warning = false; if (reported_warning) return; reported_warning = true; - Report("WARNING: ASan is ignoring requested __asan_handle_no_return: " - "stack top: %p; bottom %p; size: %p (%zd)\n" - "False positive error reports may follow\n" - "For details see " - "https://github.com/google/sanitizers/issues/189\n", - top, bottom, top - bottom, top - bottom); + Report( + "WARNING: ASan is ignoring requested __asan_handle_no_return: " + "stack top: %p; bottom %p; size: %p (%zd)\n" + "False positive error reports may follow\n" + "For details see " + "https://github.com/google/sanitizers/issues/189\n", + top, bottom, top - bottom, top - bottom); return; } PoisonShadow(bottom, top - bottom, 0); - if (curr_thread && curr_thread->has_fake_stack()) - curr_thread->fake_stack()->HandleNoReturn(); + if (CurrThread && CurrThread->has_fake_stack()) + CurrThread->fake_stack()->HandleNoReturn(); } extern "C" void *__asan_extra_spill_area() { diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_posix.h b/compiler-rt/lib/sanitizer_common/sanitizer_posix.h --- a/compiler-rt/lib/sanitizer_common/sanitizer_posix.h +++ b/compiler-rt/lib/sanitizer_common/sanitizer_posix.h @@ -119,6 +119,22 @@ // alive at least as long as the mapping exists. void DecorateMapping(uptr addr, uptr size, const char *name); +struct AlternateStackInfo { + uptr bottom; //!< Bottom of the signal alternate stack, only meaningful if + //!< error == 0 and on_stack == true + uptr top; //!< Top of the signal alternate stack, like + //!< AlternateStackInfo::bottom + int error; //!< 0 == success, other codes are platform-dependent + bool on_stack; //! true IFF this thread is on the signal alternate stack, + //! only meaningful if error == 0 +}; +/// Gets info about the signal alternate stack. +/// +/// \returns true IFF this thread is running on the signal alternate stack. +// Writes the signal alternate stack top and bottom into *stack_top and +// *stack_bottom. These values are only meaningful if a signal alternate stack +// has been installed. +AlternateStackInfo GetSignalAlternateStack(); } // namespace __sanitizer diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_posix_libcdep.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_posix_libcdep.cpp --- a/compiler-rt/lib/sanitizer_common/sanitizer_posix_libcdep.cpp +++ b/compiler-rt/lib/sanitizer_common/sanitizer_posix_libcdep.cpp @@ -196,9 +196,25 @@ UnmapOrDie(oldstack.ss_sp, oldstack.ss_size); } -static void MaybeInstallSigaction(int signum, - SignalHandlerType handler) { - if (GetHandleSignalMode(signum) == kHandleSignalNo) return; +AlternateStackInfo GetSignalAlternateStack() { + stack_t signal_stack; + if (0 == sigaltstack(nullptr, &signal_stack)) { + if (signal_stack.ss_flags == SS_ONSTACK) { + uptr const bottom = (uptr)signal_stack.ss_sp; + uptr const top = + (uptr)((char *)signal_stack.ss_sp + signal_stack.ss_size); + return {bottom, top, 0, true}; + } else { + return {0, 0, 0, false}; + } + } else { + // This should not happen according to the error list of sigaltstack. + return {0, 0, errno, false}; + } +} +static void MaybeInstallSigaction(int signum, SignalHandlerType handler) { + if (GetHandleSignalMode(signum) == kHandleSignalNo) + return; struct sigaction sigact; internal_memset(&sigact, 0, sizeof(sigact)); diff --git a/compiler-rt/test/asan/TestCases/unpoison-alternate-stack.cpp b/compiler-rt/test/asan/TestCases/unpoison-alternate-stack.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/test/asan/TestCases/unpoison-alternate-stack.cpp @@ -0,0 +1,175 @@ +// Tests that __asan_handle_no_return properly unpoisons the signal alternate +// stack. + +// Don't optimize, otherwise the variables which create redzones might be +// dropped. +// RUN: %clangxx_asan -fexceptions -O0 %s -o %t -pthread +// RUN: %run %t + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#define HANDLE_ERROR_FROM_RETVAL(EXPRESSION) \ + handleErrorFromRetval((EXPRESSION), #EXPRESSION, __FUNCTION__, __FILE__, __LINE__) + +// If EXPRESSION == 0, no error happened. +#define HANDLE_ERROR_FROM_ERRNO(EXPRESSION) \ + handleErrorFromErrno((EXPRESSION) != 0, #EXPRESSION, __FUNCTION__, __FILE__, __LINE__) + +#define ERROR(STR, ERRNO) \ + error(ERRNO, STR, __FUNCTION__, __FILE__, __LINE__) + +namespace { + +void error(int Err, char const *Expression, + char const *Function, char const *File, int Line) { + std::fprintf(stderr, + "Error %d: %s\nIn expression %s\nIn function %s\nIn file %s:%d\nAborting.\n\n", + Err, std::strerror(Err), Expression, Function, File, Line); + std::abort(); +} + +void handleErrorFromRetval(int Err, char const *Expression, + char const *Function, char const *File, int Line) { + if (Err) + error(Err, Expression, Function, File, Line); +} + +void handleErrorFromErrno(bool Failed, char const *Expression, + char const *Function, char const *File, int Line) { + if (Failed) + error(errno, Expression, Function, File, Line); +} + +char *LeftRedzone; +char *RightRedzone; + +// Create redzones for stack variables in shadow memory and throw, +// which should unpoison the entire stack. +void __attribute__((noinline)) prepareStackShadow() { + char Blob[100]; // This variable must not be optimized out, because we use it + // to create redzones. + + LeftRedzone = Blob - 1; + RightRedzone = Blob + sizeof(Blob); + + assert(__asan_address_is_poisoned(LeftRedzone)); + assert(__asan_address_is_poisoned(RightRedzone)); + + // Trigger stack unwinding, which avoids normal cleanup of redzone markers. + // Instead, __asan_handle_no_return is called which unpoisons the entire + // stack. + throw "up"; +} + +// Ensure that the redzones created by prepareStackShadow have been removed. +void __attribute__((noinline)) assertNoRedzones() { + assert(0 == __asan_region_is_poisoned(LeftRedzone, + RightRedzone - LeftRedzone)); +} + +// - Create redzones for a stack variable +// - Unwind the stack +// - Ensure that the redzones have been removed +void testHandleNoReturn() { + try { + prepareStackShadow(); + } catch (...) { + assertNoRedzones(); + } +} + +// Signal handler which must be run on signal alternate stack, +// and tests if stack unwinding properly triggers stack unpoisoning. +void signalHandler(int, siginfo_t *, void *) { + stack_t Stack; + HANDLE_ERROR_FROM_ERRNO(sigaltstack(nullptr, &Stack)); + assert(Stack.ss_flags == SS_ONSTACK); + + // second test on alt stack (actual check) + testHandleNoReturn(); +} + +// Main test function. +// Must be run on another thread to be able to control memory placement between +// default stack and alternate signal stack. +// If the alternate signal stack is placed in close proximity before the +// default stack, __asan_handle_no_return might unpoison both, even without +// being aware of the signal alternate stack. +// We want to test reliable that __asan_handle_no_return can properly unpoison +// the signal alternate stack. +void *threadFun(void *AltStack) { + // first test on default stack (sanity check) + testHandleNoReturn(); + + HANDLE_ERROR_FROM_ERRNO(sigaltstack((stack_t const *)AltStack, nullptr)); + + struct sigaction const Action = { + .sa_sigaction = signalHandler, + .sa_mask = {0}, + .sa_flags = SA_SIGINFO | SA_NODEFER | SA_ONSTACK, + .sa_restorer = nullptr}; + HANDLE_ERROR_FROM_ERRNO(sigaction(SIGSEGV, &Action, nullptr)); + + HANDLE_ERROR_FROM_ERRNO(raise(SIGSEGV)); + + return nullptr; +} + +} // namespace + +// Check that __asan_handle_no_return properly unpoisons a signal alternate +// stack. +// __asan_handle_no_return tries to determine the stack boundaries and +// unpoisons all memory inside those. +// If the determination of stack boundaries does not work properly on the +// signal alternate stack, then the unpoisoning might be omitted. +// This can leave redzones for variables on the signal alternate stack, which +// can lead to false positive reports when the stack is reused. +int main() { + size_t const PageSize = sysconf(_SC_PAGESIZE); + // To align the alternate stack, we round this up to page_size. + size_t const DefaultStackSize = + (PTHREAD_STACK_MIN - 1 + PageSize) & ~(PageSize - 1); + // The alternate stack needs a certain size, or the signal handler segfaults. + size_t const AltStackSize = 10 * PageSize; + size_t const MappingSize = DefaultStackSize + AltStackSize; + // Using mmap guarantees proper alignment. + void *const Mapping = mmap(nullptr, MappingSize, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, + -1, 0); + if ((void *)-1 == Mapping) + ERROR("mmap", errno); + + stack_t const AltStack = { + .ss_sp = (char *)Mapping + DefaultStackSize, + .ss_flags = 0, + .ss_size = AltStackSize}; + + pthread_t Thread; + pthread_attr_t ThreadAttr; + HANDLE_ERROR_FROM_RETVAL(pthread_attr_init(&ThreadAttr)); + HANDLE_ERROR_FROM_RETVAL(pthread_attr_setstack(&ThreadAttr, Mapping, + DefaultStackSize)); + HANDLE_ERROR_FROM_RETVAL(pthread_create(&Thread, &ThreadAttr, &threadFun, + (void *)&AltStack)); + + if (int const Err = pthread_join(Thread, nullptr)) + if (EINVAL != Err) // EINVAL means the other thread exited first + ERROR("pthread_join", Err); + + HANDLE_ERROR_FROM_RETVAL(munmap(Mapping, MappingSize)); +}