From b380e8b68951776656f286ecd079e2f30981905e Mon Sep 17 00:00:00 2001 From: Ivan Trofimov Date: Fri, 3 Feb 2023 00:29:00 -0800 Subject: [PATCH] [runtimes][asan] Fix swapcontext interception Resetting oucp's stack to zero in swapcontext interception is incorrect, since it breaks ucp cleanup after swapcontext returns in some cases: Say we have two contexts, A and B, and we swapcontext from A to B, do some work on Bs stack and then swapcontext back from B to A. At this point shadow memory of Bs stack is in arbitrary state, but since we can't know whether B will ever swapcontext-ed to again we clean up it's shadow memory, because otherwise it remains poisoned and blows in completely unrelated places when heap-allocated memory of Bs context gets reused later (see https://github.com/llvm/llvm-project/issues/58633 for example). swapcontext prototype is swapcontext(ucontext* oucp, ucontext* ucp), so in this example A is oucp and B is ucp, and i refer to the process of cleaning up Bs shadow memory as ucp cleanup. About how it breaks: Take the same example with A and B: when we swapcontext back from B to A the oucp parameter of swapcontext is actually B, and current trunk resets its stack in a way that it becomes "uncleanupable" later. It works fine if we do A->B->A, but if we do A->B->A->B->A no cleanup is performed for Bs stack after B "returns" to A second time. That's exactly what happens in the test i provided, and it's actually a pretty common real world scenario. Instead of resetting oucp's we make use of uc_stack.ss_flags to mark context as "cleanup-able" by storing stack specific hash. It should be safe since this field is not used in [get|make|swap]context functions and is hopefully never meaningfully used in real-world scenarios (and i haven't seen any). Fixes https://github.com/llvm/llvm-project/issues/58633 Reviewed By: vitalybuka Differential Revision: https://reviews.llvm.org/D137654 --- compiler-rt/lib/asan/asan_interceptors.cpp | 47 ++++++++++---- compiler-rt/lib/asan/asan_internal.h | 2 +- compiler-rt/lib/asan/asan_linux.cpp | 30 ++++++--- .../test/asan/TestCases/Linux/swapcontext_test.cpp | 72 +++++++++++++++++----- 4 files changed, 112 insertions(+), 39 deletions(-) diff --git a/compiler-rt/lib/asan/asan_interceptors.cpp b/compiler-rt/lib/asan/asan_interceptors.cpp index a4084c8..013bdb6 100644 --- a/compiler-rt/lib/asan/asan_interceptors.cpp +++ b/compiler-rt/lib/asan/asan_interceptors.cpp @@ -257,12 +257,36 @@ static void ClearShadowMemoryForContextStack(uptr stack, uptr ssize) { PoisonShadow(bottom, ssize, 0); } -INTERCEPTOR(int, getcontext, struct ucontext_t *ucp) { - // API does not requires to have ucp clean, and sets only part of fields. We - // use ucp->uc_stack to unpoison new stack. We prefer to have zeroes then - // uninitialized bytes. - ResetContextStack(ucp); - return REAL(getcontext)(ucp); +INTERCEPTOR(void, makecontext, struct ucontext_t *ucp, void (*func)(), int argc, + ...) { + va_list ap; + uptr args[64]; + // We don't know a better way to forward ... into REAL function. We can + // increase args size if neccecary. + CHECK_LE(argc, ARRAY_SIZE(args)); + internal_memset(args, 0, sizeof(args)); + va_start(ap, argc); + for (int i = 0; i < argc; ++i) args[i] = va_arg(ap, uptr); + va_end(ap); + +# define ENUMERATE_ARRAY_4(start) \ + args[start], args[start + 1], args[start + 2], args[start + 3] +# define ENUMERATE_ARRAY_16(start) \ + ENUMERATE_ARRAY_4(start), ENUMERATE_ARRAY_4(start + 4), \ + ENUMERATE_ARRAY_4(start + 8), ENUMERATE_ARRAY_4(start + 12) +# define ENUMERATE_ARRAY_64() \ + ENUMERATE_ARRAY_16(0), ENUMERATE_ARRAY_16(16), ENUMERATE_ARRAY_16(32), \ + ENUMERATE_ARRAY_16(48) + + REAL(makecontext) + ((struct ucontext_t *)ucp, func, argc, ENUMERATE_ARRAY_64()); + +# undef ENUMERATE_ARRAY_4 +# undef ENUMERATE_ARRAY_16 +# undef ENUMERATE_ARRAY_64 + + // Sign the stack so we can identify it for unpoisoning. + SignContextStack(ucp); } INTERCEPTOR(int, swapcontext, struct ucontext_t *oucp, @@ -279,9 +303,6 @@ INTERCEPTOR(int, swapcontext, struct ucontext_t *oucp, ReadContextStack(ucp, &stack, &ssize); ClearShadowMemoryForContextStack(stack, ssize); - // See getcontext interceptor. - ResetContextStack(oucp); - # if __has_attribute(__indirect_return__) && \ (defined(__x86_64__) || defined(__i386__)) int (*real_swapcontext)(struct ucontext_t *, struct ucontext_t *) @@ -662,11 +683,11 @@ void InitializeAsanInterceptors() { // Intecept jump-related functions. ASAN_INTERCEPT_FUNC(longjmp); -#if ASAN_INTERCEPT_SWAPCONTEXT - ASAN_INTERCEPT_FUNC(getcontext); +# if ASAN_INTERCEPT_SWAPCONTEXT ASAN_INTERCEPT_FUNC(swapcontext); -#endif -#if ASAN_INTERCEPT__LONGJMP + ASAN_INTERCEPT_FUNC(makecontext); +# endif +# if ASAN_INTERCEPT__LONGJMP ASAN_INTERCEPT_FUNC(_longjmp); #endif #if ASAN_INTERCEPT___LONGJMP_CHK diff --git a/compiler-rt/lib/asan/asan_internal.h b/compiler-rt/lib/asan/asan_internal.h index 959fdec..a5348e3 100644 --- a/compiler-rt/lib/asan/asan_internal.h +++ b/compiler-rt/lib/asan/asan_internal.h @@ -105,8 +105,8 @@ void AsanApplyToGlobals(globals_op_fptr op, const void *needle); void AsanOnDeadlySignal(int, void *siginfo, void *context); +void SignContextStack(void *context); void ReadContextStack(void *context, uptr *stack, uptr *ssize); -void ResetContextStack(void *context); void StopInitOrderChecking(); // Wrapper for TLS/TSD. diff --git a/compiler-rt/lib/asan/asan_linux.cpp b/compiler-rt/lib/asan/asan_linux.cpp index 3feda5b..e19b447 100644 --- a/compiler-rt/lib/asan/asan_linux.cpp +++ b/compiler-rt/lib/asan/asan_linux.cpp @@ -34,6 +34,7 @@ # include "asan_thread.h" # include "sanitizer_common/sanitizer_flags.h" # include "sanitizer_common/sanitizer_freebsd.h" +# include "sanitizer_common/sanitizer_hash.h" # include "sanitizer_common/sanitizer_libc.h" # include "sanitizer_common/sanitizer_procmaps.h" @@ -211,16 +212,29 @@ void AsanCheckIncompatibleRT() { # endif // SANITIZER_ANDROID # if ASAN_INTERCEPT_SWAPCONTEXT -void ReadContextStack(void *context, uptr *stack, uptr *ssize) { - ucontext_t *ucp = (ucontext_t *)context; - *stack = (uptr)ucp->uc_stack.ss_sp; - *ssize = ucp->uc_stack.ss_size; +constexpr u32 kAsanContextStackFlagsMagic = 0x51260eea; + +static int HashContextStack(const ucontext_t &ucp) { + MurMur2Hash64Builder hash(kAsanContextStackFlagsMagic); + hash.add(reinterpret_cast(ucp.uc_stack.ss_sp)); + hash.add(ucp.uc_stack.ss_size); + return static_cast(hash.get()); +} + +void SignContextStack(void *context) { + ucontext_t *ucp = reinterpret_cast(context); + ucp->uc_stack.ss_flags = HashContextStack(*ucp); } -void ResetContextStack(void *context) { - ucontext_t *ucp = (ucontext_t *)context; - ucp->uc_stack.ss_sp = nullptr; - ucp->uc_stack.ss_size = 0; +void ReadContextStack(void *context, uptr *stack, uptr *ssize) { + const ucontext_t *ucp = reinterpret_cast(context); + if (HashContextStack(*ucp) == ucp->uc_stack.ss_flags) { + *stack = reinterpret_cast(ucp->uc_stack.ss_sp); + *ssize = ucp->uc_stack.ss_size; + return; + } + *stack = 0; + *ssize = 0; } # endif // ASAN_INTERCEPT_SWAPCONTEXT diff --git a/compiler-rt/test/asan/TestCases/Linux/swapcontext_test.cpp b/compiler-rt/test/asan/TestCases/Linux/swapcontext_test.cpp index baa18f4..d9381bd 100644 --- a/compiler-rt/test/asan/TestCases/Linux/swapcontext_test.cpp +++ b/compiler-rt/test/asan/TestCases/Linux/swapcontext_test.cpp @@ -20,25 +20,23 @@ ucontext_t child_context; const int kStackSize = 1 << 20; -__attribute__((noinline)) -void Throw() { - throw 1; -} +__attribute__((noinline)) void Throw() { throw 1; } -__attribute__((noinline)) -void ThrowAndCatch() { +__attribute__((noinline)) void ThrowAndCatch() { try { Throw(); - } catch(int a) { + } catch (int a) { printf("ThrowAndCatch: %d\n", a); } } -void Child(int mode) { - assert(orig_context.uc_stack.ss_size == 0); - char x[32] = {0}; // Stack gets poisoned. - printf("Child: %p\n", x); - ThrowAndCatch(); // Simulate __asan_handle_no_return(). +void Child(int mode, int a, int b, int c) { + char x[32] = {0}; // Stack gets poisoned. + printf("Child: %d\n", x); + assert(a == 'a'); + assert(b == 'b'); + assert(c == 'c'); + ThrowAndCatch(); // Simulate __asan_handle_no_return(). // (a) Do nothing, just return to parent function. // (b) Jump into the original function. Stack remains poisoned unless we do // something. @@ -53,16 +51,13 @@ void Child(int mode) { int Run(int arg, int mode, char *child_stack) { printf("Child stack: %p\n", child_stack); // Setup child context. - memset(&child_context, 0xff, sizeof(child_context)); getcontext(&child_context); - assert(child_context.uc_stack.ss_size == 0); child_context.uc_stack.ss_sp = child_stack; child_context.uc_stack.ss_size = kStackSize / 2; if (mode == 0) { child_context.uc_link = &orig_context; } - makecontext(&child_context, (void (*)())Child, 1, mode); - memset(&orig_context, 0xff, sizeof(orig_context)); + makecontext(&child_context, (void (*)())Child, 4, mode, 'a', 'b', 'c'); if (swapcontext(&orig_context, &child_context) < 0) { perror("swapcontext"); return 0; @@ -74,6 +69,47 @@ int Run(int arg, int mode, char *child_stack) { return child_stack[arg]; } +ucontext_t poll_context; +ucontext_t poller_context; + +void Poll() { + swapcontext(&poll_context, &poller_context); + + { + char x = 0; + printf("POLL: %p\n", &x); + } + + swapcontext(&poll_context, &poller_context); +} + +void DoRunPoll(char *poll_stack) { + getcontext(&poll_context); + poll_context.uc_stack.ss_sp = poll_stack; + poll_context.uc_stack.ss_size = kStackSize / 2; + makecontext(&poll_context, Poll, 0); + + getcontext(&poller_context); + + swapcontext(&poller_context, &poll_context); + swapcontext(&poller_context, &poll_context); + + // Touch poll's stack to make sure it's unpoisoned. + for (int i = 0; i < kStackSize; i++) { + poll_stack[i] = i; + } +} + +void RunPoll() { + char *poll_stack = new char[kStackSize]; + + for (size_t i = 0; i < 2; ++i) { + DoRunPoll(poll_stack); + } + + delete[] poll_stack; +} + int main(int argc, char **argv) { char stack[kStackSize + 1]; int ret = 0; @@ -82,6 +118,8 @@ int main(int argc, char **argv) { char *heap = new char[kStackSize + 1]; ret += Run(argc - 1, 0, heap); ret += Run(argc - 1, 1, heap); - delete [] heap; + + RunPoll(); + delete[] heap; return ret; } -- 2.7.4