Skip to content

Commit c97322d

Browse files
authored
[compiler-rt][hwasan] Add fiber switch for HwASan (#153822)
Currently HwASan has no fiber switch interface for coroutines. This PR adds fiber switch interfaces similar to ASan which helps to pass sp check correctly on unwinding. The only difference is HwASan does not need a fake stack since tags can do the same thing (e.g., detect UAR). Interfaces are made identical with ASan's. Also adds unit test which is similar to ASan with minor adjustments: 1. change `__asan_handle_no_return` to `__hwasan_handle_vfork` 2. remove huge stack test since `__hwasan_handle_vfork` has no stack size limitation. 3. use uninstrumented globals to simulate allocation since hwasan do not support tagged pointer while using `longjmp` The testcase is tested on both x86 with alias mode enabled and aarch64.
1 parent 1f0af17 commit c97322d

File tree

4 files changed

+301
-3
lines changed

4 files changed

+301
-3
lines changed

compiler-rt/lib/hwasan/hwasan_interface_internal.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,13 @@ void *__hwasan_memmove_match_all(void *dest, const void *src, uptr n, u8);
247247

248248
SANITIZER_INTERFACE_ATTRIBUTE
249249
void __hwasan_set_error_report_callback(void (*callback)(const char *));
250+
251+
// hwasan does not need fake stack, so we leave it empty here.
252+
SANITIZER_INTERFACE_ATTRIBUTE
253+
void __sanitizer_start_switch_fiber(void **, const void *bottom, uptr size);
254+
SANITIZER_INTERFACE_ATTRIBUTE
255+
void __sanitizer_finish_switch_fiber(void *, const void **bottom_old,
256+
uptr *size_old);
250257
} // extern "C"
251258

252259
#endif // HWASAN_INTERFACE_INTERNAL_H

compiler-rt/lib/hwasan/hwasan_thread.cpp

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,59 @@ void Thread::Destroy() {
119119
*GetCurrentThreadLongPtr() = 0;
120120
}
121121

122+
void Thread::StartSwitchFiber(uptr bottom, uptr size) {
123+
if (atomic_load(&stack_switching_, memory_order_acquire)) {
124+
Report("ERROR: starting fiber switch while in fiber switch\n");
125+
Die();
126+
}
127+
128+
next_stack_bottom_ = bottom;
129+
next_stack_top_ = bottom + size;
130+
atomic_store(&stack_switching_, 1, memory_order_release);
131+
}
132+
133+
void Thread::FinishSwitchFiber(uptr *bottom_old, uptr *size_old) {
134+
if (!atomic_load(&stack_switching_, memory_order_acquire)) {
135+
Report("ERROR: finishing a fiber switch that has not started\n");
136+
Die();
137+
}
138+
139+
if (bottom_old)
140+
*bottom_old = stack_bottom_;
141+
if (size_old)
142+
*size_old = stack_top_ - stack_bottom_;
143+
stack_bottom_ = next_stack_bottom_;
144+
stack_top_ = next_stack_top_;
145+
atomic_store(&stack_switching_, 0, memory_order_release);
146+
next_stack_top_ = 0;
147+
next_stack_bottom_ = 0;
148+
}
149+
150+
inline Thread::StackBounds Thread::GetStackBounds() const {
151+
if (!atomic_load(&stack_switching_, memory_order_acquire)) {
152+
// Make sure the stack bounds are fully initialized.
153+
if (stack_bottom_ >= stack_top_)
154+
return {0, 0};
155+
return {stack_bottom_, stack_top_};
156+
}
157+
const uptr cur_stack = (uptr)__builtin_frame_address(0);
158+
// Note: need to check next stack first, because FinishSwitchFiber
159+
// may be in process of overwriting stack_top_/bottom_. But in such case
160+
// we are already on the next stack.
161+
if (cur_stack >= next_stack_bottom_ && cur_stack < next_stack_top_)
162+
return {next_stack_bottom_, next_stack_top_};
163+
return {stack_bottom_, stack_top_};
164+
}
165+
166+
uptr Thread::stack_top() { return GetStackBounds().top; }
167+
168+
uptr Thread::stack_bottom() { return GetStackBounds().bottom; }
169+
170+
uptr Thread::stack_size() {
171+
const auto bounds = GetStackBounds();
172+
return bounds.top - bounds.bottom;
173+
}
174+
122175
void Thread::Print(const char *Prefix) {
123176
Printf("%sT%zd %p stack: [%p,%p) sz: %zd tls: [%p,%p)\n", Prefix, unique_id_,
124177
(void *)this, stack_bottom(), stack_top(),
@@ -226,3 +279,25 @@ void PrintThreads() {
226279
}
227280

228281
} // namespace __lsan
282+
283+
// ---------------------- Interface ---------------- {{{1
284+
using namespace __hwasan;
285+
286+
extern "C" {
287+
SANITIZER_INTERFACE_ATTRIBUTE
288+
void __sanitizer_start_switch_fiber(void **, const void *bottom, uptr size) {
289+
if (auto *t = GetCurrentThread())
290+
t->StartSwitchFiber((uptr)bottom, size);
291+
else
292+
VReport(1, "__hwasan_start_switch_fiber called from unknown thread\n");
293+
}
294+
295+
SANITIZER_INTERFACE_ATTRIBUTE
296+
void __sanitizer_finish_switch_fiber(void *, const void **bottom_old,
297+
uptr *size_old) {
298+
if (auto *t = GetCurrentThread())
299+
t->FinishSwitchFiber((uptr *)bottom_old, size_old);
300+
else
301+
VReport(1, "__hwasan_finish_switch_fiber called from unknown thread\n");
302+
}
303+
}

compiler-rt/lib/hwasan/hwasan_thread.h

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@ class Thread {
4141

4242
void Destroy();
4343

44-
uptr stack_top() { return stack_top_; }
45-
uptr stack_bottom() { return stack_bottom_; }
46-
uptr stack_size() { return stack_top() - stack_bottom(); }
44+
uptr stack_top();
45+
uptr stack_bottom();
46+
uptr stack_size();
4747
uptr tls_begin() { return tls_begin_; }
4848
uptr tls_end() { return tls_end_; }
4949
DTLS *dtls() { return dtls_; }
@@ -53,6 +53,9 @@ class Thread {
5353
return addr >= stack_bottom_ && addr < stack_top_;
5454
}
5555

56+
void StartSwitchFiber(uptr bottom, uptr size);
57+
void FinishSwitchFiber(uptr *bottom_old, uptr *size_old);
58+
5659
AllocatorCache *allocator_cache() { return &allocator_cache_; }
5760
HeapAllocationsRingBuffer *heap_allocations() { return heap_allocations_; }
5861
StackAllocationsRingBuffer *stack_allocations() { return stack_allocations_; }
@@ -80,9 +83,22 @@ class Thread {
8083
void ClearShadowForThreadStackAndTLS();
8184
void Print(const char *prefix);
8285
void InitRandomState();
86+
87+
struct StackBounds {
88+
uptr bottom;
89+
uptr top;
90+
};
91+
StackBounds GetStackBounds() const;
92+
8393
uptr vfork_spill_;
8494
uptr stack_top_;
8595
uptr stack_bottom_;
96+
// these variables are used when the thread is about to switch stack
97+
uptr next_stack_top_;
98+
uptr next_stack_bottom_;
99+
// true if switching is in progress
100+
atomic_uint8_t stack_switching_;
101+
86102
uptr tls_begin_;
87103
uptr tls_end_;
88104
DTLS *dtls_;
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
// Test hwasan __sanitizer_start_switch_fiber and
2+
// __sanitizer_finish_switch_fiber interface.
3+
4+
// RUN: %clangxx_hwasan -std=c++11 -lpthread -O0 %s -o %t && %run %t 2>&1 | FileCheck %s
5+
// RUN: %clangxx_hwasan -std=c++11 -lpthread -O1 %s -o %t && %run %t 2>&1 | FileCheck %s
6+
// RUN: %clangxx_hwasan -std=c++11 -lpthread -O2 %s -o %t && %run %t 2>&1 | FileCheck %s
7+
// RUN: %clangxx_hwasan -std=c++11 -lpthread -O3 %s -o %t && %run %t 2>&1 | FileCheck %s
8+
// RUN: seq 30 | xargs -i -- grep LOOPCHECK %s > %t.checks
9+
// RUN: %clangxx_hwasan -std=c++11 -lpthread -O0 %s -o %t && %run %t 2>&1 | FileCheck %t.checks --check-prefix LOOPCHECK
10+
// RUN: %clangxx_hwasan -std=c++11 -lpthread -O1 %s -o %t && %run %t 2>&1 | FileCheck %t.checks --check-prefix LOOPCHECK
11+
// RUN: %clangxx_hwasan -std=c++11 -lpthread -O2 %s -o %t && %run %t 2>&1 | FileCheck %t.checks --check-prefix LOOPCHECK
12+
// RUN: %clangxx_hwasan -std=c++11 -lpthread -O3 %s -o %t && %run %t 2>&1 | FileCheck %t.checks --check-prefix LOOPCHECK
13+
14+
//
15+
// Android and musl do not support swapcontext.
16+
// REQUIRES: 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+
42+
// hwasan does not support longjmp with tagged stack pointer, make sure it is
43+
// not tagged.
44+
char __attribute__((no_sanitize("hwaddress"))) allocated_stack[kStackSize + 1];
45+
char __attribute__((
46+
no_sanitize("hwaddress"))) allocated_child_stack[kStackSize + 1];
47+
48+
__attribute__((noinline, noreturn)) void LongJump(jmp_buf env) {
49+
longjmp(env, 1);
50+
_exit(1);
51+
}
52+
53+
// Simulate __asan_handle_no_return().
54+
__attribute__((noinline)) void CallNoReturn() {
55+
jmp_buf env;
56+
if (setjmp(env) != 0)
57+
return;
58+
59+
LongJump(env);
60+
_exit(1);
61+
}
62+
63+
void NextChild() {
64+
CallNoReturn();
65+
__sanitizer_finish_switch_fiber(nullptr, &from_stack, &from_stacksize);
66+
67+
printf("NextChild from: %p %zu\n", from_stack, from_stacksize);
68+
69+
char x[32] = {0}; // Stack gets poisoned.
70+
printf("NextChild: %p\n", x);
71+
72+
CallNoReturn();
73+
74+
__sanitizer_start_switch_fiber(nullptr, main_thread_stack,
75+
main_thread_stacksize);
76+
CallNoReturn();
77+
if (swapcontext(&next_child_context, &orig_context) < 0) {
78+
perror("swapcontext");
79+
_exit(1);
80+
}
81+
}
82+
83+
void Child(int mode) {
84+
CallNoReturn();
85+
__sanitizer_finish_switch_fiber(nullptr, &main_thread_stack,
86+
&main_thread_stacksize);
87+
char x[32] = {0}; // Stack gets poisoned.
88+
printf("Child: %p\n", x);
89+
CallNoReturn();
90+
// (a) Do nothing, just return to parent function.
91+
// (b) Jump into the original function. Stack remains poisoned unless we do
92+
// something.
93+
// (c) Jump to another function which will then jump back to the main function
94+
if (mode == 0) {
95+
__sanitizer_start_switch_fiber(nullptr, main_thread_stack,
96+
main_thread_stacksize);
97+
CallNoReturn();
98+
} else if (mode == 1) {
99+
__sanitizer_start_switch_fiber(nullptr, main_thread_stack,
100+
main_thread_stacksize);
101+
CallNoReturn();
102+
if (swapcontext(&child_context, &orig_context) < 0) {
103+
perror("swapcontext");
104+
_exit(1);
105+
}
106+
} else if (mode == 2) {
107+
printf("NextChild stack: %p\n", next_child_stack);
108+
109+
getcontext(&next_child_context);
110+
next_child_context.uc_stack.ss_sp = next_child_stack;
111+
next_child_context.uc_stack.ss_size = kStackSize / 2;
112+
makecontext(&next_child_context, (void (*)())NextChild, 0);
113+
__sanitizer_start_switch_fiber(nullptr, next_child_context.uc_stack.ss_sp,
114+
next_child_context.uc_stack.ss_size);
115+
CallNoReturn();
116+
if (swapcontext(&child_context, &next_child_context) < 0) {
117+
perror("swapcontext");
118+
_exit(1);
119+
}
120+
}
121+
}
122+
123+
int Run(int arg, int mode, char *child_stack) {
124+
printf("Child stack: %p\n", child_stack);
125+
// Setup child context.
126+
getcontext(&child_context);
127+
child_context.uc_stack.ss_sp = child_stack;
128+
child_context.uc_stack.ss_size = kStackSize / 2;
129+
if (mode == 0) {
130+
child_context.uc_link = &orig_context;
131+
}
132+
makecontext(&child_context, (void (*)())Child, 1, mode);
133+
CallNoReturn();
134+
__sanitizer_start_switch_fiber(nullptr, child_context.uc_stack.ss_sp,
135+
child_context.uc_stack.ss_size);
136+
CallNoReturn();
137+
if (swapcontext(&orig_context, &child_context) < 0) {
138+
perror("swapcontext");
139+
_exit(1);
140+
}
141+
CallNoReturn();
142+
__sanitizer_finish_switch_fiber(nullptr, &from_stack, &from_stacksize);
143+
CallNoReturn();
144+
printf("Main context from: %p %zu\n", from_stack, from_stacksize);
145+
146+
return child_stack[arg];
147+
}
148+
149+
void handler(int sig) { CallNoReturn(); }
150+
151+
int main(int argc, char **argv) {
152+
// This testcase is copied from ASan's swapcontext_annotation.cpp testcase
153+
// and adapted to HWASan:
154+
// 1. removed huge stack test since hwasan has no huge stack limitations
155+
// 2. stack allocations are now done with original malloc/free instead of
156+
// hwasan interceptor, since HWASan does not support tagged stack pointer
157+
// in longjmp (see __hwasan_handle_longjmp)
158+
159+
// set up a signal that will spam and trigger __hwasan_handle_vfork at
160+
// tricky moments
161+
struct sigaction act = {};
162+
act.sa_handler = &handler;
163+
if (sigaction(SIGPROF, &act, 0)) {
164+
perror("sigaction");
165+
_exit(1);
166+
}
167+
168+
itimerval t;
169+
t.it_interval.tv_sec = 0;
170+
t.it_interval.tv_usec = 10;
171+
t.it_value = t.it_interval;
172+
if (setitimer(ITIMER_PROF, &t, 0)) {
173+
perror("setitimer");
174+
_exit(1);
175+
}
176+
177+
char *heap = allocated_stack;
178+
next_child_stack = allocated_child_stack;
179+
int ret = 0;
180+
// CHECK-NOT: WARNING: HWASan is ignoring requested __hwasan_handle_vfork
181+
for (unsigned int i = 0; i < 30; ++i) {
182+
// LOOPCHECK: Child stack: [[CHILD_STACK:0x[0-9a-f]*]]
183+
// LOOPCHECK: Main context from: [[CHILD_STACK]] 524288
184+
ret += Run(argc - 1, 0, heap);
185+
// LOOPCHECK: Child stack: [[CHILD_STACK:0x[0-9a-f]*]]
186+
// LOOPCHECK: Main context from: [[CHILD_STACK]] 524288
187+
ret += Run(argc - 1, 1, heap);
188+
// LOOPCHECK: Child stack: [[CHILD_STACK:0x[0-9a-f]*]]
189+
// LOOPCHECK: NextChild stack: [[NEXT_CHILD_STACK:0x[0-9a-f]*]]
190+
// LOOPCHECK: NextChild from: [[CHILD_STACK]] 524288
191+
// LOOPCHECK: Main context from: [[NEXT_CHILD_STACK]] 524288
192+
ret += Run(argc - 1, 2, heap);
193+
printf("Iteration %d passed\n", i);
194+
}
195+
196+
// CHECK: Test passed
197+
printf("Test passed\n");
198+
199+
return ret;
200+
}

0 commit comments

Comments
 (0)