| 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, jump: [&] { longjmp(env: c.JmpBuf, val: 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(ss: nullptr, oss: &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(c&: signalStack, jump: [] { longjmp(env: defaultStack.JmpBuf, val: 1); }); |
| 80 | } |
| 81 | |
| 82 | void setSignalAlternateStack(void *AltStack) { |
| 83 | sigaltstack(ss: (stack_t const *)AltStack, oss: nullptr); |
| 84 | |
| 85 | struct sigaction Action = {}; |
| 86 | Action.sa_sigaction = signalHandler; |
| 87 | Action.sa_flags = SA_SIGINFO | SA_NODEFER | SA_ONSTACK; |
| 88 | sigemptyset(set: &Action.sa_mask); |
| 89 | |
| 90 | sigaction(SIGUSR1, act: &Action, oact: 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(c&: defaultStack, jump: [] { 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, b: 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(addr: nullptr, len: MappingSize, |
| 148 | PROT_READ | PROT_WRITE, |
| 149 | MAP_PRIVATE | MAP_ANONYMOUS, |
| 150 | fd: -1, offset: 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(attr: &ThreadAttr); |
| 160 | pthread_attr_setstack(attr: &ThreadAttr, stackaddr: Mapping, stacksize: DefaultStackSize); |
| 161 | pthread_create(newthread: &Thread, attr: &ThreadAttr, start_routine: &threadFun, arg: (void *)&AltStack); |
| 162 | pthread_attr_destroy(attr: &ThreadAttr); |
| 163 | |
| 164 | pthread_join(th: Thread, thread_return: nullptr); |
| 165 | |
| 166 | munmap(addr: Mapping, len: MappingSize); |
| 167 | } |
| 168 | |