diff --git a/compiler-rt/lib/asan/asan_fake_stack.cpp b/compiler-rt/lib/asan/asan_fake_stack.cpp --- a/compiler-rt/lib/asan/asan_fake_stack.cpp +++ b/compiler-rt/lib/asan/asan_fake_stack.cpp @@ -133,6 +133,12 @@ needs_gc_ = true; } +// Hack: The statement below is not true if we take into account sigaltstack or +// makecontext. It should be possible to make GC to discard wrong stack frame if +// we use these tools. For now, let's support the simplest case and allow GC to +// discard only frames from the default stack, assuming there is no buffer on +// the stack which is used for makecontext or sigaltstack. +// // When throw, longjmp or some such happens we don't call OnFree() and // as the result may leak one or more fake frames, but the good news is that // we are notified about all such events by HandleNoReturn(). @@ -140,6 +146,14 @@ // We do it based on their 'real_stack' values -- everything that is lower // than the current real_stack is garbage. NOINLINE void FakeStack::GC(uptr real_stack) { + AsanThread *curr_thread = GetCurrentThread(); + if (!curr_thread) + return; // Try again when we have a thread. + auto top = curr_thread->stack_top(); + auto bottom = curr_thread->stack_bottom(); + if (real_stack < top || real_stack > bottom) + return; // Not the default stack. + for (uptr class_id = 0; class_id < kNumberOfSizeClasses; class_id++) { u8 *flags = GetFlags(stack_size_log(), class_id); for (uptr i = 0, n = NumberOfFrames(stack_size_log(), class_id); i < n; @@ -147,8 +161,12 @@ if (flags[i] == 0) continue; // not allocated. FakeFrame *ff = reinterpret_cast( GetFrame(stack_size_log(), class_id, i)); - if (ff->real_stack < real_stack) { + // GC only on the default stack. + if (ff->real_stack < real_stack && ff->real_stack >= top) { flags[i] = 0; + // Poison the frame, so the any access will be reported as UAR. + SetShadow(reinterpret_cast(ff), BytesInSizeClass(class_id), + class_id, kMagic8); } } } diff --git a/compiler-rt/test/asan/TestCases/Posix/fake_stack_gc.cpp b/compiler-rt/test/asan/TestCases/Posix/fake_stack_gc.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/test/asan/TestCases/Posix/fake_stack_gc.cpp @@ -0,0 +1,92 @@ +// RUN: %clangxx_asan -O0 -pthread %s -o %t && %env_asan_opts=use_sigaltstack=0 %run not --crash %t 2>&1 | FileCheck %s + +// Check that fake stack does not discard frames on the main stack, when GC is +// triggered from high alt stack. + +#include +#include +#include +#include +#include +#include +#include +#include + +const size_t kStackSize = 0x100000; + +int *on_thread; +int *p; + +template void Fn() { + int t[N]; + p = t; + if constexpr (N > 1) + Fn(); +} + +static void Handler(int signo) { + fprintf(stderr, "Handler Frame:%p\n", __builtin_frame_address(0)); + + // Trigger GC and create a lot of frame to reuse "Thread" frame if it was + // discarded. + for (int i = 0; i < 1000; ++i) + Fn<1000>(); + // If we discarder and reused "Thread" frame, the next line will crash with + // false report. + *on_thread = 10; + fprintf(stderr, "SUCCESS\n"); + // CHECK: SUCCESS +} + +void *Thread(void *arg) { + fprintf(stderr, "Thread Frame:%p\n", __builtin_frame_address(0)); + stack_t stack = { + .ss_sp = arg, + .ss_flags = 0, + .ss_size = kStackSize, + }; + assert(sigaltstack(&stack, nullptr) == 0); + + struct sigaction sa = {}; + sa.sa_handler = Handler; + sa.sa_flags = SA_ONSTACK; + sigaction(SIGABRT, &sa, nullptr); + + // Store pointer to the local var, so we can access this frame from the signal + // handler when the frame is still alive. + int n; + on_thread = &n; + + // Abort should schedule FakeStack GC and call handler on alt stack. + abort(); +} + +int main(void) { + // Allocate main and alt stack for future thread. + void *main_stack = malloc(kStackSize); + void *alt_stack = malloc(kStackSize); + + // Pick the lower stack as the main stack, as we want to trigger GC in + // FakeStack from alt stack in a such way that main stack is allocated below. + if ((uintptr_t)main_stack > (uintptr_t)alt_stack) + std::swap(alt_stack, main_stack); + + pthread_attr_t attr; + assert(pthread_attr_init(&attr) == 0); + assert(pthread_attr_setstack(&attr, main_stack, kStackSize) == 0); + + fprintf(stderr, "main_stack: %p-%p\n", main_stack, + (char *)main_stack + kStackSize); + fprintf(stderr, "alt_stack: %p-%p\n", alt_stack, + (char *)alt_stack + kStackSize); + + pthread_t tid; + assert(pthread_create(&tid, &attr, Thread, alt_stack) == 0); + + pthread_join(tid, nullptr); + + free(main_stack); + free(alt_stack); + + return 0; +}