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