Index: include/sanitizer/common_interface_defs.h =================================================================== --- include/sanitizer/common_interface_defs.h +++ include/sanitizer/common_interface_defs.h @@ -139,6 +139,11 @@ // `top_percent` should be between 1 and 100. // Experimental feature currently available only with asan on Linux/x86_64. void __sanitizer_print_memory_profile(size_t top_percent); + + void __sanitizer_start_enter_fiber(const void *bottom, size_t size); + void __sanitizer_finish_enter_fiber(); + void __sanitizer_start_exit_fiber(); + void __sanitizer_finish_exit_fiber(); #ifdef __cplusplus } // extern "C" #endif Index: lib/asan/asan_thread.h =================================================================== --- lib/asan/asan_thread.h +++ lib/asan/asan_thread.h @@ -66,9 +66,9 @@ thread_return_t ThreadStart(uptr os_id, atomic_uintptr_t *signal_thread_is_registered); - uptr stack_top() { return stack_top_; } - uptr stack_bottom() { return stack_bottom_; } - uptr stack_size() { return stack_size_; } + uptr stack_top(); + uptr stack_bottom(); + uptr stack_size(); uptr tls_begin() { return tls_begin_; } uptr tls_end() { return tls_end_; } DTLS *dtls() { return dtls_; } @@ -83,9 +83,7 @@ }; bool GetStackFrameAccessByAddr(uptr addr, StackFrameAccess *access); - bool AddrIsInStack(uptr addr) { - return addr >= stack_bottom_ && addr < stack_top_; - } + bool AddrIsInStack(uptr addr); void DeleteFakeStack(int tid) { if (!fake_stack_) return; @@ -95,6 +93,11 @@ t->Destroy(tid); } + void StartEnterFiber(uptr bottom, uptr size); + void FinishEnterFiber(); + void StartExitFiber(); + void FinishExitFiber(); + bool has_fake_stack() { return (reinterpret_cast(fake_stack_) > 1); } @@ -127,14 +130,28 @@ void ClearShadowForThreadStackAndTLS(); FakeStack *AsyncSignalSafeLazyInitFakeStack(); + struct StackBounds { + uptr bottom; + uptr top; + }; + StackBounds GetStackBounds() const; + AsanThreadContext *context_; thread_callback_t start_routine_; void *arg_; uptr stack_top_; uptr stack_bottom_; - // stack_size_ == stack_top_ - stack_bottom_; - // It needs to be set in a async-signal-safe manner. - uptr stack_size_; + + // if the thread is running on an alternate stack, these variables are used + uptr fiber_stack_top_; + uptr fiber_stack_bottom_; + // when the thread is switching from an alternate stack to another alternate + // stack, we need to keep both of them during the switch + uptr next_fiber_stack_top_; + uptr next_fiber_stack_bottom_; + // true if switching is in progress + bool fiber_switching_; + uptr tls_begin_; uptr tls_end_; DTLS *dtls_; Index: lib/asan/asan_thread.cc =================================================================== --- lib/asan/asan_thread.cc +++ lib/asan/asan_thread.cc @@ -11,6 +11,8 @@ // // Thread-related code. //===----------------------------------------------------------------------===// +#include + #include "asan_allocator.h" #include "asan_interceptors.h" #include "asan_poisoning.h" @@ -120,6 +122,76 @@ DTLS_Destroy(); } +void AsanThread::StartEnterFiber(uptr bottom, uptr size) { + if (fiber_switching_) + Report("WARNING: starting fiber switch twice\n"); + if (!fiber_stack_bottom_) { + fiber_stack_bottom_ = bottom; + fiber_stack_top_ = bottom + size; + } else { + next_fiber_stack_bottom_ = bottom; + next_fiber_stack_top_ = bottom + size; + } + fiber_switching_ = true; +} + +void AsanThread::FinishEnterFiber() { + if (!fiber_switching_) + Report("WARNING: finishing a fiber enter that has not started\n"); + if (next_fiber_stack_bottom_) { + fiber_stack_bottom_ = next_fiber_stack_bottom_; + fiber_stack_top_ = next_fiber_stack_top_; + next_fiber_stack_bottom_ = next_fiber_stack_top_ = 0; + } + fiber_switching_ = false; +} + +void AsanThread::StartExitFiber() { + if (fiber_switching_) + Report("WARNING: starting fiber switch twice\n"); + fiber_switching_ = true; +} + +void AsanThread::FinishExitFiber() { + if (!fiber_switching_) + Report("WARNING: finishing a fiber exit that has not started\n"); + fiber_stack_top_ = fiber_stack_bottom_ = 0; + fiber_switching_ = false; +} + +inline AsanThread::StackBounds AsanThread::GetStackBounds() const { + if (!fiber_switching_) { + if (fiber_stack_bottom_) + return StackBounds{fiber_stack_bottom_, fiber_stack_top_}; // NOLINT + else + return StackBounds{stack_bottom_, stack_top_}; // NOLINT + } else { + char local; + const uptr cur_stack = (uptr)&local; + if (cur_stack >= fiber_stack_bottom_ && cur_stack < fiber_stack_top_) + return StackBounds{fiber_stack_bottom_, fiber_stack_top_}; // NOLINT + else if (cur_stack >= next_fiber_stack_bottom_ && + cur_stack < next_fiber_stack_top_) + return StackBounds{next_fiber_stack_bottom_, // NOLINT + next_fiber_stack_top_}; // NOLINT + else + return StackBounds{stack_bottom_, stack_top_}; // NOLINT + } +} + +uptr AsanThread::stack_top() { + return GetStackBounds().top; +} + +uptr AsanThread::stack_bottom() { + return GetStackBounds().bottom; +} + +uptr AsanThread::stack_size() { + const auto bounds = GetStackBounds(); + return bounds.top - bounds.bottom; +} + // We want to create the FakeStack lazyly on the first use, but not eralier // than the stack size is known and the procedure has to be async-signal safe. FakeStack *AsanThread::AsyncSignalSafeLazyInitFakeStack() { @@ -195,9 +267,13 @@ void AsanThread::SetThreadStackAndTls() { uptr tls_size = 0; - GetThreadStackAndTls(tid() == 0, &stack_bottom_, &stack_size_, &tls_begin_, + uptr stack_size = 0; + GetThreadStackAndTls(tid() == 0, &stack_bottom_, &stack_size, &tls_begin_, &tls_size); - stack_top_ = stack_bottom_ + stack_size_; + stack_top_ = stack_bottom_ + stack_size; + fiber_stack_top_ = fiber_stack_bottom_ = 0; + next_fiber_stack_top_ = next_fiber_stack_bottom_ = 0; + fiber_switching_ = false; tls_end_ = tls_begin_ + tls_size; dtls_ = DTLS_Get(); @@ -250,6 +326,11 @@ return true; } +bool AsanThread::AddrIsInStack(uptr addr) { + const auto bounds = GetStackBounds(); + return addr >= bounds.bottom && addr < bounds.top; +} + static bool ThreadStackContainsAddress(ThreadContextBase *tctx_base, void *addr) { AsanThreadContext *tctx = static_cast(tctx_base); @@ -357,3 +438,48 @@ __asan::EnsureMainThreadIDIsCorrect(); } } // namespace __lsan + +// ---------------------- Interface ---------------- {{{1 +using namespace __asan; // NOLINT + +extern "C" { +SANITIZER_INTERFACE_ATTRIBUTE +void __sanitizer_start_enter_fiber(const void *bottom, size_t size) { + AsanThread *t = GetCurrentThread(); + if (!t) { + Report("WARNING: __asan_enter_fiber called from unknown thread\n"); + return; + } + t->StartEnterFiber((uptr)bottom, size); +} + +SANITIZER_INTERFACE_ATTRIBUTE +void __sanitizer_finish_enter_fiber() { + AsanThread *t = GetCurrentThread(); + if (!t) { + Report("WARNING: __asan_exit_fiber called from unknown thread\n"); + return; + } + t->FinishEnterFiber(); +} + +SANITIZER_INTERFACE_ATTRIBUTE +void __sanitizer_start_exit_fiber() { + AsanThread *t = GetCurrentThread(); + if (!t) { + Report("WARNING: __asan_enter_fiber called from unknown thread\n"); + return; + } + t->StartExitFiber(); +} + +SANITIZER_INTERFACE_ATTRIBUTE +void __sanitizer_finish_exit_fiber() { + AsanThread *t = GetCurrentThread(); + if (!t) { + Report("WARNING: __asan_exit_fiber called from unknown thread\n"); + return; + } + t->FinishExitFiber(); +} +} Index: test/asan/TestCases/Linux/swapcontext_test.cc =================================================================== --- test/asan/TestCases/Linux/swapcontext_test.cc +++ test/asan/TestCases/Linux/swapcontext_test.cc @@ -12,8 +12,13 @@ #include #include +#include + ucontext_t orig_context; ucontext_t child_context; +ucontext_t next_child_context; + +char* next_child_stack; const int kStackSize = 1 << 20; @@ -22,6 +27,7 @@ throw 1; } +// Simulate __asan_handle_no_return(). __attribute__((noinline)) void ThrowAndCatch() { try { @@ -31,18 +37,55 @@ } } +void NextChild() { + ThrowAndCatch(); + __sanitizer_finish_enter_fiber(); + + char x[32] = {0}; // Stack gets poisoned. + printf("NextChild: %p\n", x); + + ThrowAndCatch(); + + __sanitizer_start_exit_fiber(); + ThrowAndCatch(); + if (swapcontext(&next_child_context, &orig_context) < 0) { + perror("swapcontext"); + _exit(0); + } +} + void Child(int mode) { + ThrowAndCatch(); + __sanitizer_finish_enter_fiber(); char x[32] = {0}; // Stack gets poisoned. printf("Child: %p\n", x); - ThrowAndCatch(); // Simulate __asan_handle_no_return(). + ThrowAndCatch(); // (a) Do nothing, just return to parent function. // (b) Jump into the original function. Stack remains poisoned unless we do // something. - if (mode == 1) { + // (c) Jump to another function which will then jump back to the main function + if (mode == 0) { + __sanitizer_start_exit_fiber(); + ThrowAndCatch(); + } else if (mode == 1) { + __sanitizer_start_exit_fiber(); + ThrowAndCatch(); if (swapcontext(&child_context, &orig_context) < 0) { perror("swapcontext"); _exit(0); } + } else if (mode == 2) { + getcontext(&next_child_context); + next_child_context.uc_stack.ss_sp = next_child_stack; + next_child_context.uc_stack.ss_size = kStackSize / 2; + makecontext(&next_child_context, (void (*)())NextChild, 0); + __sanitizer_start_enter_fiber(next_child_context.uc_stack.ss_sp, + next_child_context.uc_stack.ss_size); + ThrowAndCatch(); + if (swapcontext(&child_context, &next_child_context) < 0) { + perror("swapcontext"); + _exit(0); + } } } @@ -56,10 +99,18 @@ child_context.uc_link = &orig_context; } makecontext(&child_context, (void (*)())Child, 1, mode); + ThrowAndCatch(); + __sanitizer_start_enter_fiber(child_context.uc_stack.ss_sp, + child_context.uc_stack.ss_size); + ThrowAndCatch(); if (swapcontext(&orig_context, &child_context) < 0) { perror("swapcontext"); return 0; } + ThrowAndCatch(); + __sanitizer_finish_exit_fiber(); + ThrowAndCatch(); + // Touch childs's stack to make sure it's unpoisoned. for (int i = 0; i < kStackSize; i++) { child_stack[i] = i; @@ -68,23 +119,37 @@ } int main(int argc, char **argv) { + next_child_stack = new char[kStackSize + 1]; char stack[kStackSize + 1]; // CHECK: WARNING: ASan doesn't fully support makecontext/swapcontext int ret = 0; ret += Run(argc - 1, 0, stack); printf("Test1 passed\n"); + // CHECK-NOT: ASan is ignoring requested __asan_handle_no_return // CHECK: Test1 passed ret += Run(argc - 1, 1, stack); printf("Test2 passed\n"); + // CHECK-NOT: ASan is ignoring requested __asan_handle_no_return // CHECK: Test2 passed - char *heap = new char[kStackSize + 1]; - ret += Run(argc - 1, 0, heap); + ret += Run(argc - 1, 2, stack); printf("Test3 passed\n"); + // CHECK-NOT: ASan is ignoring requested __asan_handle_no_return // CHECK: Test3 passed - ret += Run(argc - 1, 1, heap); + char *heap = new char[kStackSize + 1]; + ret += Run(argc - 1, 0, heap); printf("Test4 passed\n"); + // CHECK-NOT: ASan is ignoring requested __asan_handle_no_return // CHECK: Test4 passed + ret += Run(argc - 1, 1, heap); + printf("Test5 passed\n"); + // CHECK-NOT: ASan is ignoring requested __asan_handle_no_return + // CHECK: Test5 passed + ret += Run(argc - 1, 2, heap); + printf("Test6 passed\n"); + // CHECK-NOT: ASan is ignoring requested __asan_handle_no_return + // CHECK: Test6 passed delete [] heap; + delete [] next_child_stack; return ret; }