1// Check that ASan plays well with annotated makecontext/swapcontext.
2
3// RUN: %clangxx_asan -std=c++11 -lpthread -O0 %s -o %t && %run %t 2>&1 | FileCheck %s
4// RUN: %clangxx_asan -std=c++11 -lpthread -O1 %s -o %t && %run %t 2>&1 | FileCheck %s
5// RUN: %clangxx_asan -std=c++11 -lpthread -O2 %s -o %t && %run %t 2>&1 | FileCheck %s
6// RUN: %clangxx_asan -std=c++11 -lpthread -O3 %s -o %t && %run %t 2>&1 | FileCheck %s
7// RUN: seq 60 | xargs -i -- grep LOOPCHECK %s > %t.checks
8// RUN: %clangxx_asan -std=c++11 -lpthread -O0 %s -o %t && %run %t 2>&1 | FileCheck %t.checks --check-prefix LOOPCHECK
9// RUN: %clangxx_asan -std=c++11 -lpthread -O1 %s -o %t && %run %t 2>&1 | FileCheck %t.checks --check-prefix LOOPCHECK
10// RUN: %clangxx_asan -std=c++11 -lpthread -O2 %s -o %t && %run %t 2>&1 | FileCheck %t.checks --check-prefix LOOPCHECK
11// RUN: %clangxx_asan -std=c++11 -lpthread -O3 %s -o %t && %run %t 2>&1 | FileCheck %t.checks --check-prefix LOOPCHECK
12
13//
14// This test is too subtle to try on non-x86 arch for now.
15// Android and musl do not support swapcontext.
16// REQUIRES: x86-target-arch && glibc-2.27
17
18#include <pthread.h>
19#include <setjmp.h>
20#include <signal.h>
21#include <stdio.h>
22#include <sys/time.h>
23#include <ucontext.h>
24#include <unistd.h>
25
26#include <sanitizer/common_interface_defs.h>
27
28ucontext_t orig_context;
29ucontext_t child_context;
30ucontext_t next_child_context;
31
32char *next_child_stack;
33
34const int kStackSize = 1 << 20;
35
36const void *main_thread_stack;
37size_t main_thread_stacksize;
38
39const void *from_stack;
40size_t from_stacksize;
41
42__attribute__((noinline, noreturn)) void LongJump(jmp_buf env) {
43 longjmp(env: env, val: 1);
44 _exit(status: 1);
45}
46
47// Simulate __asan_handle_no_return().
48__attribute__((noinline)) void CallNoReturn() {
49 jmp_buf env;
50 if (setjmp(env) != 0)
51 return;
52
53 LongJump(env);
54 _exit(status: 1);
55}
56
57void NextChild() {
58 CallNoReturn();
59 __sanitizer_finish_switch_fiber(fake_stack_save: nullptr, bottom_old: &from_stack, size_old: &from_stacksize);
60
61 printf(format: "NextChild from: %p %zu\n", from_stack, from_stacksize);
62
63 char x[32] = {0}; // Stack gets poisoned.
64 printf(format: "NextChild: %p\n", x);
65
66 CallNoReturn();
67
68 __sanitizer_start_switch_fiber(fake_stack_save: nullptr, bottom: main_thread_stack,
69 size: main_thread_stacksize);
70 CallNoReturn();
71 if (swapcontext(oucp: &next_child_context, ucp: &orig_context) < 0) {
72 perror(s: "swapcontext");
73 _exit(status: 1);
74 }
75}
76
77void Child(int mode) {
78 CallNoReturn();
79 __sanitizer_finish_switch_fiber(fake_stack_save: nullptr, bottom_old: &main_thread_stack,
80 size_old: &main_thread_stacksize);
81 char x[32] = {0}; // Stack gets poisoned.
82 printf(format: "Child: %p\n", x);
83 CallNoReturn();
84 // (a) Do nothing, just return to parent function.
85 // (b) Jump into the original function. Stack remains poisoned unless we do
86 // something.
87 // (c) Jump to another function which will then jump back to the main function
88 if (mode == 0) {
89 __sanitizer_start_switch_fiber(fake_stack_save: nullptr, bottom: main_thread_stack,
90 size: main_thread_stacksize);
91 CallNoReturn();
92 } else if (mode == 1) {
93 __sanitizer_start_switch_fiber(fake_stack_save: nullptr, bottom: main_thread_stack,
94 size: main_thread_stacksize);
95 CallNoReturn();
96 if (swapcontext(oucp: &child_context, ucp: &orig_context) < 0) {
97 perror(s: "swapcontext");
98 _exit(status: 1);
99 }
100 } else if (mode == 2) {
101 printf(format: "NextChild stack: %p\n", next_child_stack);
102
103 getcontext(ucp: &next_child_context);
104 next_child_context.uc_stack.ss_sp = next_child_stack;
105 next_child_context.uc_stack.ss_size = kStackSize / 2;
106 makecontext(ucp: &next_child_context, func: (void (*)())NextChild, argc: 0);
107 __sanitizer_start_switch_fiber(fake_stack_save: nullptr, bottom: next_child_context.uc_stack.ss_sp,
108 size: next_child_context.uc_stack.ss_size);
109 CallNoReturn();
110 if (swapcontext(oucp: &child_context, ucp: &next_child_context) < 0) {
111 perror(s: "swapcontext");
112 _exit(status: 1);
113 }
114 }
115}
116
117int Run(int arg, int mode, char *child_stack) {
118 printf(format: "Child stack: %p\n", child_stack);
119 // Setup child context.
120 getcontext(ucp: &child_context);
121 child_context.uc_stack.ss_sp = child_stack;
122 child_context.uc_stack.ss_size = kStackSize / 2;
123 if (mode == 0) {
124 child_context.uc_link = &orig_context;
125 }
126 makecontext(ucp: &child_context, func: (void (*)())Child, argc: 1, mode);
127 CallNoReturn();
128 void *fake_stack_save;
129 __sanitizer_start_switch_fiber(fake_stack_save: &fake_stack_save, bottom: child_context.uc_stack.ss_sp,
130 size: child_context.uc_stack.ss_size);
131 CallNoReturn();
132 if (swapcontext(oucp: &orig_context, ucp: &child_context) < 0) {
133 perror(s: "swapcontext");
134 _exit(status: 1);
135 }
136 CallNoReturn();
137 __sanitizer_finish_switch_fiber(fake_stack_save, bottom_old: &from_stack,
138 size_old: &from_stacksize);
139 CallNoReturn();
140 printf(format: "Main context from: %p %zu\n", from_stack, from_stacksize);
141
142 // Touch childs's stack to make sure it's unpoisoned.
143 for (int i = 0; i < kStackSize; i++) {
144 child_stack[i] = i;
145 }
146 return child_stack[arg];
147}
148
149ucontext_t orig_huge_stack_context;
150ucontext_t child_huge_stack_context;
151
152// There used to be a limitation for stack unpoisoning (size <= 4Mb), check that it's gone.
153const int kHugeStackSize = 1 << 23;
154
155void ChildHugeStack() {
156 __sanitizer_finish_switch_fiber(fake_stack_save: nullptr, bottom_old: &main_thread_stack,
157 size_old: &main_thread_stacksize);
158 char x[32] = {0}; // Stack gets poisoned.
159 __sanitizer_start_switch_fiber(fake_stack_save: nullptr, bottom: main_thread_stack,
160 size: main_thread_stacksize);
161 if (swapcontext(oucp: &child_huge_stack_context, ucp: &orig_huge_stack_context) < 0) {
162 perror(s: "swapcontext");
163 _exit(status: 1);
164 }
165}
166
167void DoRunHugeStack(char *child_stack) {
168 getcontext(ucp: &child_huge_stack_context);
169 child_huge_stack_context.uc_stack.ss_sp = child_stack;
170 child_huge_stack_context.uc_stack.ss_size = kHugeStackSize;
171 makecontext(ucp: &child_huge_stack_context, func: (void (*)())ChildHugeStack, argc: 0);
172 void *fake_stack_save;
173 __sanitizer_start_switch_fiber(fake_stack_save: &fake_stack_save,
174 bottom: child_huge_stack_context.uc_stack.ss_sp,
175 size: child_huge_stack_context.uc_stack.ss_size);
176 if (swapcontext(oucp: &orig_huge_stack_context, ucp: &child_huge_stack_context) < 0) {
177 perror(s: "swapcontext");
178 _exit(status: 1);
179 }
180 __sanitizer_finish_switch_fiber(
181 fake_stack_save, bottom_old: (const void **)&child_huge_stack_context.uc_stack.ss_sp,
182 size_old: &child_huge_stack_context.uc_stack.ss_size);
183 for (int i = 0; i < kHugeStackSize; ++i) {
184 child_stack[i] = i;
185 }
186}
187
188void RunHugeStack() {
189 const int run_offset = 1 << 14;
190 char *heap = new char[kHugeStackSize + run_offset + 1];
191 DoRunHugeStack(child_stack: heap);
192 DoRunHugeStack(child_stack: heap + run_offset);
193 DoRunHugeStack(child_stack: heap);
194 delete[] heap;
195}
196
197void handler(int sig) { CallNoReturn(); }
198
199int main(int argc, char **argv) {
200 // CHECK: WARNING: ASan doesn't fully support makecontext/swapcontext
201 // CHECK-NOT: ASan is ignoring requested __asan_handle_no_return
202 RunHugeStack();
203
204 // set up a signal that will spam and trigger __asan_handle_no_return at
205 // tricky moments
206 struct sigaction act = {};
207 act.sa_handler = &handler;
208 if (sigaction(SIGPROF, act: &act, oact: 0)) {
209 perror(s: "sigaction");
210 _exit(status: 1);
211 }
212
213 itimerval t;
214 t.it_interval.tv_sec = 0;
215 t.it_interval.tv_usec = 10;
216 t.it_value = t.it_interval;
217 if (setitimer(ITIMER_PROF, new: &t, old: 0)) {
218 perror(s: "setitimer");
219 _exit(status: 1);
220 }
221
222 char *heap = new char[kStackSize + 1];
223 next_child_stack = new char[kStackSize + 1];
224 char stack[kStackSize + 1];
225 int ret = 0;
226 // CHECK-NOT: ASan is ignoring requested __asan_handle_no_return
227 for (unsigned int i = 0; i < 30; ++i) {
228 ret += Run(arg: argc - 1, mode: 0, child_stack: stack);
229 // LOOPCHECK: Child stack: [[CHILD_STACK:0x[0-9a-f]*]]
230 // LOOPCHECK: Main context from: [[CHILD_STACK]] 524288
231 ret += Run(arg: argc - 1, mode: 1, child_stack: stack);
232 // LOOPCHECK: Child stack: [[CHILD_STACK:0x[0-9a-f]*]]
233 // LOOPCHECK: Main context from: [[CHILD_STACK]] 524288
234 ret += Run(arg: argc - 1, mode: 2, child_stack: stack);
235 // LOOPCHECK: Child stack: [[CHILD_STACK:0x[0-9a-f]*]]
236 // LOOPCHECK: NextChild stack: [[NEXT_CHILD_STACK:0x[0-9a-f]*]]
237 // LOOPCHECK: NextChild from: [[CHILD_STACK]] 524288
238 // LOOPCHECK: Main context from: [[NEXT_CHILD_STACK]] 524288
239 ret += Run(arg: argc - 1, mode: 0, child_stack: heap);
240 ret += Run(arg: argc - 1, mode: 1, child_stack: heap);
241 ret += Run(arg: argc - 1, mode: 2, child_stack: heap);
242 printf(format: "Iteration %d passed\n", i);
243 }
244
245 // CHECK: Test passed
246 printf(format: "Test passed\n");
247
248 delete[] heap;
249 delete[] next_child_stack;
250
251 return ret;
252}
253

source code of compiler-rt/test/asan/TestCases/Linux/swapcontext_annotation.cpp