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