xref: /llvm-project/compiler-rt/test/asan/TestCases/Linux/swapcontext_annotation.cpp (revision 15e9b1d0c0bea607fc0aad4a851ddd32b5736c80)
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 
28 ucontext_t orig_context;
29 ucontext_t child_context;
30 ucontext_t next_child_context;
31 
32 char *next_child_stack;
33 
34 const int kStackSize = 1 << 20;
35 
36 const void *main_thread_stack;
37 size_t main_thread_stacksize;
38 
39 const void *from_stack;
40 size_t from_stacksize;
41 
LongJump(jmp_buf env)42 __attribute__((noinline, noreturn)) void LongJump(jmp_buf env) {
43   longjmp(env, 1);
44   _exit(1);
45 }
46 
47 // Simulate __asan_handle_no_return().
CallNoReturn()48 __attribute__((noinline)) void CallNoReturn() {
49   jmp_buf env;
50   if (setjmp(env) != 0)
51     return;
52 
53   LongJump(env);
54   _exit(1);
55 }
56 
NextChild()57 void NextChild() {
58   CallNoReturn();
59   __sanitizer_finish_switch_fiber(nullptr, &from_stack, &from_stacksize);
60 
61   printf("NextChild from: %p %zu\n", from_stack, from_stacksize);
62 
63   char x[32] = {0}; // Stack gets poisoned.
64   printf("NextChild: %p\n", x);
65 
66   CallNoReturn();
67 
68   __sanitizer_start_switch_fiber(nullptr, main_thread_stack,
69                                  main_thread_stacksize);
70   CallNoReturn();
71   if (swapcontext(&next_child_context, &orig_context) < 0) {
72     perror("swapcontext");
73     _exit(1);
74   }
75 }
76 
Child(int mode)77 void Child(int mode) {
78   CallNoReturn();
79   __sanitizer_finish_switch_fiber(nullptr, &main_thread_stack,
80                                   &main_thread_stacksize);
81   char x[32] = {0}; // Stack gets poisoned.
82   printf("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(nullptr, main_thread_stack,
90                                    main_thread_stacksize);
91     CallNoReturn();
92   } else if (mode == 1) {
93     __sanitizer_start_switch_fiber(nullptr, main_thread_stack,
94                                    main_thread_stacksize);
95     CallNoReturn();
96     if (swapcontext(&child_context, &orig_context) < 0) {
97       perror("swapcontext");
98       _exit(1);
99     }
100   } else if (mode == 2) {
101     printf("NextChild stack: %p\n", next_child_stack);
102 
103     getcontext(&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(&next_child_context, (void (*)())NextChild, 0);
107     __sanitizer_start_switch_fiber(nullptr, next_child_context.uc_stack.ss_sp,
108                                    next_child_context.uc_stack.ss_size);
109     CallNoReturn();
110     if (swapcontext(&child_context, &next_child_context) < 0) {
111       perror("swapcontext");
112       _exit(1);
113     }
114   }
115 }
116 
Run(int arg,int mode,char * child_stack)117 int Run(int arg, int mode, char *child_stack) {
118   printf("Child stack: %p\n", child_stack);
119   // Setup child context.
120   getcontext(&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(&child_context, (void (*)())Child, 1, mode);
127   CallNoReturn();
128   void *fake_stack_save;
129   __sanitizer_start_switch_fiber(&fake_stack_save, child_context.uc_stack.ss_sp,
130                                  child_context.uc_stack.ss_size);
131   CallNoReturn();
132   if (swapcontext(&orig_context, &child_context) < 0) {
133     perror("swapcontext");
134     _exit(1);
135   }
136   CallNoReturn();
137   __sanitizer_finish_switch_fiber(fake_stack_save, &from_stack,
138                                   &from_stacksize);
139   CallNoReturn();
140   printf("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 
149 ucontext_t orig_huge_stack_context;
150 ucontext_t child_huge_stack_context;
151 
152 // There used to be a limitation for stack unpoisoning (size <= 4Mb), check that it's gone.
153 const int kHugeStackSize = 1 << 23;
154 
ChildHugeStack()155 void ChildHugeStack() {
156   __sanitizer_finish_switch_fiber(nullptr, &main_thread_stack,
157                                   &main_thread_stacksize);
158   char x[32] = {0}; // Stack gets poisoned.
159   __sanitizer_start_switch_fiber(nullptr, main_thread_stack,
160                                  main_thread_stacksize);
161   if (swapcontext(&child_huge_stack_context, &orig_huge_stack_context) < 0) {
162     perror("swapcontext");
163     _exit(1);
164   }
165 }
166 
DoRunHugeStack(char * child_stack)167 void DoRunHugeStack(char *child_stack) {
168   getcontext(&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(&child_huge_stack_context, (void (*)())ChildHugeStack, 0);
172   void *fake_stack_save;
173   __sanitizer_start_switch_fiber(&fake_stack_save,
174                                  child_huge_stack_context.uc_stack.ss_sp,
175                                  child_huge_stack_context.uc_stack.ss_size);
176   if (swapcontext(&orig_huge_stack_context, &child_huge_stack_context) < 0) {
177     perror("swapcontext");
178     _exit(1);
179   }
180   __sanitizer_finish_switch_fiber(
181       fake_stack_save, (const void **)&child_huge_stack_context.uc_stack.ss_sp,
182       &child_huge_stack_context.uc_stack.ss_size);
183   for (int i = 0; i < kHugeStackSize; ++i) {
184     child_stack[i] = i;
185   }
186 }
187 
RunHugeStack()188 void RunHugeStack() {
189   const int run_offset = 1 << 14;
190   char *heap = new char[kHugeStackSize + run_offset + 1];
191   DoRunHugeStack(heap);
192   DoRunHugeStack(heap + run_offset);
193   DoRunHugeStack(heap);
194   delete[] heap;
195 }
196 
handler(int sig)197 void handler(int sig) { CallNoReturn(); }
198 
main(int argc,char ** argv)199 int 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, 0)) {
209     perror("sigaction");
210     _exit(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, &t, 0)) {
218     perror("setitimer");
219     _exit(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(argc - 1, 0, stack);
229     // LOOPCHECK: Child stack: [[CHILD_STACK:0x[0-9a-f]*]]
230     // LOOPCHECK: Main context from: [[CHILD_STACK]] 524288
231     ret += Run(argc - 1, 1, stack);
232     // LOOPCHECK: Child stack: [[CHILD_STACK:0x[0-9a-f]*]]
233     // LOOPCHECK: Main context from: [[CHILD_STACK]] 524288
234     ret += Run(argc - 1, 2, 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(argc - 1, 0, heap);
240     ret += Run(argc - 1, 1, heap);
241     ret += Run(argc - 1, 2, heap);
242     printf("Iteration %d passed\n", i);
243   }
244 
245   // CHECK: Test passed
246   printf("Test passed\n");
247 
248   delete[] heap;
249   delete[] next_child_stack;
250 
251   return ret;
252 }
253