168 lines · cpp
1// Tests that __asan_handle_no_return properly unpoisons the signal alternate2// stack.3 4// Don't optimize, otherwise the variables which create redzones might be5// dropped.6// RUN: %clangxx_asan -fexceptions -O0 %s -o %t -pthread7// RUN: %env_asan_opts=detect_stack_use_after_return=0 %run %t8 9#include <algorithm>10#include <cassert>11#include <cerrno>12#include <csetjmp>13#include <cstdint>14#include <cstdio>15#include <cstdlib>16#include <cstring>17 18#include <limits.h>19#include <pthread.h>20#include <signal.h>21#include <sys/mman.h>22#include <unistd.h>23 24#include <sanitizer/asan_interface.h>25 26namespace {27 28struct TestContext {29 char *LeftRedzone;30 char *RightRedzone;31 std::jmp_buf JmpBuf;32};33 34TestContext defaultStack;35TestContext signalStack;36 37// Create a new stack frame to ensure that logically, the stack frame should be38// unpoisoned when the function exits. Exit is performed via jump, not return,39// such that we trigger __asan_handle_no_return and not ordinary unpoisoning.40template <class Jump>41void __attribute__((noinline)) poisonStackAndJump(TestContext &c, Jump jump) {42 char Blob[100]; // This variable must not be optimized out, because we use it43 // to create redzones.44 45 c.LeftRedzone = Blob - 1;46 c.RightRedzone = Blob + sizeof(Blob);47 48 assert(__asan_address_is_poisoned(c.LeftRedzone));49 assert(__asan_address_is_poisoned(c.RightRedzone));50 51 // Jump to avoid normal cleanup of redzone markers. Instead,52 // __asan_handle_no_return is called which unpoisons the stacks.53 jump();54}55 56void testOnCurrentStack() {57 TestContext c;58 59 if (0 == setjmp(c.JmpBuf))60 poisonStackAndJump(c, [&] { longjmp(c.JmpBuf, 1); });61 62 assert(0 == __asan_region_is_poisoned(c.LeftRedzone,63 c.RightRedzone - c.LeftRedzone));64}65 66bool isOnSignalStack() {67 stack_t Stack;68 sigaltstack(nullptr, &Stack);69 return Stack.ss_flags == SS_ONSTACK;70}71 72void signalHandler(int, siginfo_t *, void *) {73 assert(isOnSignalStack());74 75 // test on signal alternate stack76 testOnCurrentStack();77 78 // test unpoisoning when jumping between stacks79 poisonStackAndJump(signalStack, [] { longjmp(defaultStack.JmpBuf, 1); });80}81 82void setSignalAlternateStack(void *AltStack) {83 sigaltstack((stack_t const *)AltStack, nullptr);84 85 struct sigaction Action = {};86 Action.sa_sigaction = signalHandler;87 Action.sa_flags = SA_SIGINFO | SA_NODEFER | SA_ONSTACK;88 sigemptyset(&Action.sa_mask);89 90 sigaction(SIGUSR1, &Action, nullptr);91}92 93// Main test function.94// Must be run on another thread to be able to control memory placement between95// default stack and alternate signal stack.96// If the alternate signal stack is placed in close proximity before the97// default stack, __asan_handle_no_return might unpoison both, even without98// being aware of the signal alternate stack.99// We want to test reliably that __asan_handle_no_return can properly unpoison100// the signal alternate stack.101void *threadFun(void *AltStack) {102 // first test on default stack (sanity check), no signal alternate stack set103 testOnCurrentStack();104 105 setSignalAlternateStack(AltStack);106 107 // test on default stack again, but now the signal alternate stack is set108 testOnCurrentStack();109 110 // set up jump to test unpoisoning when jumping between stacks111 if (0 == setjmp(defaultStack.JmpBuf))112 // Test on signal alternate stack, via signalHandler113 poisonStackAndJump(defaultStack, [] { raise(SIGUSR1); });114 115 assert(!isOnSignalStack());116 117 assert(0 == __asan_region_is_poisoned(118 defaultStack.LeftRedzone,119 defaultStack.RightRedzone - defaultStack.LeftRedzone));120 121 assert(0 == __asan_region_is_poisoned(122 signalStack.LeftRedzone,123 signalStack.RightRedzone - signalStack.LeftRedzone));124 125 return nullptr;126}127 128} // namespace129 130// Check that __asan_handle_no_return properly unpoisons a signal alternate131// stack.132// __asan_handle_no_return tries to determine the stack boundaries and133// unpoisons all memory inside those. If this is not done properly, redzones for134// variables on can remain in shadow memory which might lead to false positive135// reports when the stack is reused.136int main() {137 size_t const PageSize = sysconf(_SC_PAGESIZE);138 // The Solaris defaults of 4k (32-bit) and 8k (64-bit) are too small.139 size_t const MinStackSize = std::max<size_t>(PTHREAD_STACK_MIN, 16 * 1024);140 // To align the alternate stack, we round this up to page_size.141 size_t const DefaultStackSize =142 (MinStackSize - 1 + PageSize) & ~(PageSize - 1);143 // The alternate stack needs a certain size, or the signal handler segfaults.144 size_t const AltStackSize = 10 * PageSize;145 size_t const MappingSize = DefaultStackSize + AltStackSize;146 // Using mmap guarantees proper alignment.147 void *const Mapping = mmap(nullptr, MappingSize,148 PROT_READ | PROT_WRITE,149 MAP_PRIVATE | MAP_ANONYMOUS,150 -1, 0);151 152 stack_t AltStack = {};153 AltStack.ss_sp = (char *)Mapping + DefaultStackSize;154 AltStack.ss_flags = 0;155 AltStack.ss_size = AltStackSize;156 157 pthread_t Thread;158 pthread_attr_t ThreadAttr;159 pthread_attr_init(&ThreadAttr);160 pthread_attr_setstack(&ThreadAttr, Mapping, DefaultStackSize);161 pthread_create(&Thread, &ThreadAttr, &threadFun, (void *)&AltStack);162 pthread_attr_destroy(&ThreadAttr);163 164 pthread_join(Thread, nullptr);165 166 munmap(Mapping, MappingSize);167}168