1 // Tests that __asan_handle_no_return properly unpoisons the signal alternate 2 // stack. 3 4 // Don't optimize, otherwise the variables which create redzones might be 5 // dropped. 6 // RUN: %clangxx_asan -fexceptions -O0 %s -o %t -pthread 7 // RUN: %env_asan_opts=detect_stack_use_after_return=0 %run %t 8 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 26 namespace { 27 28 struct TestContext { 29 char *LeftRedzone; 30 char *RightRedzone; 31 std::jmp_buf JmpBuf; 32 }; 33 34 TestContext defaultStack; 35 TestContext signalStack; 36 37 // Create a new stack frame to ensure that logically, the stack frame should be 38 // 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. 40 template <class Jump> 41 void __attribute__((noinline)) poisonStackAndJump(TestContext &c, Jump jump) { 42 char Blob[100]; // This variable must not be optimized out, because we use it 43 // 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 56 void 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 66 bool isOnSignalStack() { 67 stack_t Stack; 68 sigaltstack(nullptr, &Stack); 69 return Stack.ss_flags == SS_ONSTACK; 70 } 71 72 void signalHandler(int, siginfo_t *, void *) { 73 assert(isOnSignalStack()); 74 75 // test on signal alternate stack 76 testOnCurrentStack(); 77 78 // test unpoisoning when jumping between stacks 79 poisonStackAndJump(signalStack, [] { longjmp(defaultStack.JmpBuf, 1); }); 80 } 81 82 void 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 between 95 // default stack and alternate signal stack. 96 // If the alternate signal stack is placed in close proximity before the 97 // default stack, __asan_handle_no_return might unpoison both, even without 98 // being aware of the signal alternate stack. 99 // We want to test reliably that __asan_handle_no_return can properly unpoison 100 // the signal alternate stack. 101 void *threadFun(void *AltStack) { 102 // first test on default stack (sanity check), no signal alternate stack set 103 testOnCurrentStack(); 104 105 setSignalAlternateStack(AltStack); 106 107 // test on default stack again, but now the signal alternate stack is set 108 testOnCurrentStack(); 109 110 // set up jump to test unpoisoning when jumping between stacks 111 if (0 == setjmp(defaultStack.JmpBuf)) 112 // Test on signal alternate stack, via signalHandler 113 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 } // namespace 129 130 // Check that __asan_handle_no_return properly unpoisons a signal alternate 131 // stack. 132 // __asan_handle_no_return tries to determine the stack boundaries and 133 // unpoisons all memory inside those. If this is not done properly, redzones for 134 // variables on can remain in shadow memory which might lead to false positive 135 // reports when the stack is reused. 136 int 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