xref: /llvm-project/compiler-rt/test/asan/TestCases/Posix/unpoison-alternate-stack.cpp (revision a79098bc726e8de85d3ed0050de5395015bca031)
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