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
26namespace {
27
28struct TestContext {
29 char *LeftRedzone;
30 char *RightRedzone;
31 std::jmp_buf JmpBuf;
32};
33
34TestContext defaultStack;
35TestContext 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.
40template <class Jump>
41void __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
56void 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
66bool isOnSignalStack() {
67 stack_t Stack;
68 sigaltstack(ss: nullptr, oss: &Stack);
69 return Stack.ss_flags == SS_ONSTACK;
70}
71
72void 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
82void 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.
101void *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.
136int 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
163 pthread_join(th: Thread, thread_return: nullptr);
164
165 munmap(addr: Mapping, len: MappingSize);
166}
167

source code of compiler-rt/test/asan/TestCases/Posix/unpoison-alternate-stack.cpp