diff --git a/compiler-rt/lib/asan/asan_posix.cpp b/compiler-rt/lib/asan/asan_posix.cpp --- a/compiler-rt/lib/asan/asan_posix.cpp +++ b/compiler-rt/lib/asan/asan_posix.cpp @@ -17,6 +17,7 @@ #include "asan_internal.h" #include "asan_interceptors.h" #include "asan_mapping.h" +#include "asan_poisoning.h" #include "asan_report.h" #include "asan_stack.h" #include "sanitizer_common/sanitizer_libc.h" @@ -24,6 +25,7 @@ #include "sanitizer_common/sanitizer_procmaps.h" #include +#include #include #include #include @@ -38,7 +40,31 @@ } bool PlatformUnpoisonStacks() { - return false; + uptr sigalt_top, sigalt_bottom; + stack_t signal_stack; + CHECK(0 == sigaltstack(nullptr, &signal_stack)); + sigalt_bottom = (uptr)signal_stack.ss_sp; + sigalt_top = (uptr)((char *)signal_stack.ss_sp + signal_stack.ss_size); + // If we're executing on the signal alternate stack AND the Linux flag + // SS_AUTODISARM was used, then we cannot get the signal alternate stack + // bounds from sigaltstack -- sigaltstack's output looks just as if no + // alternate stack has ever been set up. + // We're always unpoisoning the signal alternate stack to support jumping + // between the default stack and signal alternate stack. + if (signal_stack.ss_flags != SS_DISABLE) + UnpoisonStack(sigalt_bottom, sigalt_top, "sigalt"); + + if (signal_stack.ss_flags != SS_ONSTACK) + return false; + + // Since we're on the signal altnerate stack, we cannot find the DEFAULT + // stack bottom using a local variable. + uptr default_top, default_bottom, tls_addr, tls_size, stack_size; + GetThreadStackAndTls(/*main=*/false, &default_bottom, &stack_size, &tls_addr, + &tls_size); + default_top = default_bottom + stack_size; + UnpoisonStack(default_bottom, default_top, "default"); + return true; } // ---------------------- TSD ---------------- {{{1 diff --git a/compiler-rt/test/asan/TestCases/Posix/unpoison-alternate-stack.cpp b/compiler-rt/test/asan/TestCases/Posix/unpoison-alternate-stack.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/test/asan/TestCases/Posix/unpoison-alternate-stack.cpp @@ -0,0 +1,162 @@ +// 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 -std=c++20 -fexceptions -O0 %s -o %t -pthread +// RUN: %run %t + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + + +namespace { + +struct TestContext { + char *LeftRedzone; + char *RightRedzone; + std::jmp_buf JmpBuf; +}; + +TestContext defaultStack; +TestContext signalStack; + +// Create a new stack frame to ensure that logically, the stack frame should be +// unpoisoned when the function exits. Exit is performed via jump, not return, +// such that we trigger __asan_handle_no_return and not ordinary unpoisoning. +template +void __attribute__((noinline)) poisonStackAndJump(TestContext &c, Jump jump) { + char Blob[100]; // This variable must not be optimized out, because we use it + // to create redzones. + + c.LeftRedzone = Blob - 1; + c.RightRedzone = Blob + sizeof(Blob); + + assert(__asan_address_is_poisoned(c.LeftRedzone)); + assert(__asan_address_is_poisoned(c.RightRedzone)); + + // Jump to avoid normal cleanup of redzone markers. Instead, + // __asan_handle_no_return is called which unpoisons the stacks. + jump(); +} + +void testOnCurrentStack() { + TestContext c; + + if (0 == setjmp(c.JmpBuf)) + poisonStackAndJump(c, [&] { longjmp(c.JmpBuf, 1); }); + + assert(0 == __asan_region_is_poisoned(c.LeftRedzone, + c.RightRedzone - c.LeftRedzone)); +} + +void signalHandler(int, siginfo_t *, void *) { + { + stack_t Stack; + sigaltstack(nullptr, &Stack); + assert(Stack.ss_flags == SS_ONSTACK); + } + + // test on signal alternate stack + testOnCurrentStack(); + + // test unpoisoning when jumping between stacks + poisonStackAndJump(signalStack, [] { longjmp(defaultStack.JmpBuf, 1); }); +} + +void setSignalAlternateStack(void *AltStack) { + sigaltstack((stack_t const *)AltStack, nullptr); + + struct sigaction Action = { + .sa_sigaction = signalHandler, + .sa_flags = SA_SIGINFO | SA_NODEFER | SA_ONSTACK, + }; + sigemptyset(&Action.sa_mask); + + sigaction(SIGUSR1, &Action, nullptr); +} + +// 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 reliably that __asan_handle_no_return can properly unpoison +// the signal alternate stack. +void *threadFun(void *AltStack) { + // first test on default stack (sanity check), no signal alternate stack set + testOnCurrentStack(); + + setSignalAlternateStack(AltStack); + + // test on default stack again, but now the signal alternate stack is set + testOnCurrentStack(); + + // set up jump to test unpoisoning when jumping between stacks + if (0 == setjmp(defaultStack.JmpBuf)) + // Test on signal alternate stack, via signalHandler + poisonStackAndJump(defaultStack, [] { raise(SIGUSR1); }); + + assert(0 == __asan_region_is_poisoned( + defaultStack.LeftRedzone, + defaultStack.RightRedzone - defaultStack.LeftRedzone)); + + assert(0 == __asan_region_is_poisoned( + signalStack.LeftRedzone, + signalStack.RightRedzone - signalStack.LeftRedzone)); + + 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 this is not done properly, redzones for +// variables on can remain in shadow memory which might 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, + -1, 0); + + stack_t const AltStack = { + .ss_sp = (char *)Mapping + DefaultStackSize, + .ss_flags = 0, + .ss_size = AltStackSize, + }; + + pthread_t Thread; + pthread_attr_t ThreadAttr; + pthread_attr_init(&ThreadAttr); + pthread_attr_setstack(&ThreadAttr, Mapping, DefaultStackSize); + pthread_create(&Thread, &ThreadAttr, &threadFun, (void *)&AltStack); + + pthread_join(Thread, nullptr); + + munmap(Mapping, MappingSize); +}