[compiler-rt] 6b4aeec - [asan] Unpoison signal alternate stack.
Vitaly Buka via llvm-commits
llvm-commits at lists.llvm.org
Tue Jun 16 02:28:49 PDT 2020
Author: Vitaly Buka
Date: 2020-06-16T02:28:38-07:00
New Revision: 6b4aeec94afc3626c19ed93dea5d158fbea29732
URL: https://github.com/llvm/llvm-project/commit/6b4aeec94afc3626c19ed93dea5d158fbea29732
DIFF: https://github.com/llvm/llvm-project/commit/6b4aeec94afc3626c19ed93dea5d158fbea29732.diff
LOG: [asan] Unpoison signal alternate stack.
Summary:
Before unwinding the stack, `__asan_handle_no_return` is supposed to
unpoison the entire stack - that is, remove the entries in the shadow
memory corresponding to stack (e.g. redzone markers around variables).
This does not work correctly if `__asan_handle_no_return` is called from
the alternate stack used in signal handlers, because the stack top is
read from a cache, which yields the default stack top instead of the
signal alternate stack top.
It is also possible to jump between the default stack and the signal
alternate stack. Therefore, __asan_handle_no_return needs to unpoison
both.
Reviewers: vitalybuka, kubamracek, kcc, eugenis
Reviewed By: vitalybuka
Subscribers: phosek, #sanitizers
Tags: #sanitizers
Differential Revision: https://reviews.llvm.org/D76986
Added:
compiler-rt/test/asan/TestCases/Posix/unpoison-alternate-stack.cpp
Modified:
compiler-rt/lib/asan/asan_posix.cpp
Removed:
################################################################################
diff --git a/compiler-rt/lib/asan/asan_posix.cpp b/compiler-rt/lib/asan/asan_posix.cpp
index 30559798c5c5..d7f19d846544 100644
--- a/compiler-rt/lib/asan/asan_posix.cpp
+++ b/compiler-rt/lib/asan/asan_posix.cpp
@@ -17,6 +17,7 @@
#include "asan_internal.h"
#include "asan_interceptors.h"
#include "asan_mapping.h"
+#include "asan_poisoning.h"
#include "asan_report.h"
#include "asan_stack.h"
#include "sanitizer_common/sanitizer_libc.h"
@@ -24,6 +25,7 @@
#include "sanitizer_common/sanitizer_procmaps.h"
#include <pthread.h>
+#include <signal.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/resource.h>
@@ -37,7 +39,31 @@ void AsanOnDeadlySignal(int signo, void *siginfo, void *context) {
ReportDeadlySignal(sig);
}
-bool PlatformUnpoisonStacks() { return false; }
+bool PlatformUnpoisonStacks() {
+ stack_t signal_stack;
+ CHECK_EQ(0, sigaltstack(nullptr, &signal_stack));
+ uptr sigalt_bottom = (uptr)signal_stack.ss_sp;
+ uptr sigalt_top = (uptr)((char *)signal_stack.ss_sp + signal_stack.ss_size);
+ // If we're executing on the signal alternate stack AND the Linux flag
+ // SS_AUTODISARM was used, then we cannot get the signal alternate stack
+ // bounds from sigaltstack -- sigaltstack's output looks just as if no
+ // alternate stack has ever been set up.
+ // We're always unpoisoning the signal alternate stack to support jumping
+ // between the default stack and signal alternate stack.
+ if (signal_stack.ss_flags != SS_DISABLE)
+ UnpoisonStack(sigalt_bottom, sigalt_top, "sigalt");
+
+ if (signal_stack.ss_flags != SS_ONSTACK)
+ return false;
+
+ // Since we're on the signal altnerate stack, we cannot find the DEFAULT
+ // stack bottom using a local variable.
+ uptr default_bottom, tls_addr, tls_size, stack_size;
+ GetThreadStackAndTls(/*main=*/false, &default_bottom, &stack_size, &tls_addr,
+ &tls_size);
+ UnpoisonStack(default_bottom, default_bottom + stack_size, "default");
+ return true;
+}
// ---------------------- TSD ---------------- {{{1
diff --git a/compiler-rt/test/asan/TestCases/Posix/unpoison-alternate-stack.cpp b/compiler-rt/test/asan/TestCases/Posix/unpoison-alternate-stack.cpp
new file mode 100644
index 000000000000..60a561279a15
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/Posix/unpoison-alternate-stack.cpp
@@ -0,0 +1,161 @@
+// Tests that __asan_handle_no_return properly unpoisons the signal alternate
+// stack.
+
+// Don't optimize, otherwise the variables which create redzones might be
+// dropped.
+// RUN: %clangxx_asan -std=c++20 -fexceptions -O0 %s -o %t -pthread
+// RUN: %run %t
+
+#include <cassert>
+#include <cerrno>
+#include <csetjmp>
+#include <cstdint>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+
+#include <limits.h>
+#include <pthread.h>
+#include <signal.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include <sanitizer/asan_interface.h>
+
+namespace {
+
+struct TestContext {
+ char *LeftRedzone;
+ char *RightRedzone;
+ std::jmp_buf JmpBuf;
+};
+
+TestContext defaultStack;
+TestContext signalStack;
+
+// Create a new stack frame to ensure that logically, the stack frame should be
+// unpoisoned when the function exits. Exit is performed via jump, not return,
+// such that we trigger __asan_handle_no_return and not ordinary unpoisoning.
+template <class Jump>
+void __attribute__((noinline)) poisonStackAndJump(TestContext &c, Jump jump) {
+ char Blob[100]; // This variable must not be optimized out, because we use it
+ // to create redzones.
+
+ c.LeftRedzone = Blob - 1;
+ c.RightRedzone = Blob + sizeof(Blob);
+
+ assert(__asan_address_is_poisoned(c.LeftRedzone));
+ assert(__asan_address_is_poisoned(c.RightRedzone));
+
+ // Jump to avoid normal cleanup of redzone markers. Instead,
+ // __asan_handle_no_return is called which unpoisons the stacks.
+ jump();
+}
+
+void testOnCurrentStack() {
+ TestContext c;
+
+ if (0 == setjmp(c.JmpBuf))
+ poisonStackAndJump(c, [&] { longjmp(c.JmpBuf, 1); });
+
+ assert(0 == __asan_region_is_poisoned(c.LeftRedzone,
+ c.RightRedzone - c.LeftRedzone));
+}
+
+void signalHandler(int, siginfo_t *, void *) {
+ {
+ stack_t Stack;
+ sigaltstack(nullptr, &Stack);
+ assert(Stack.ss_flags == SS_ONSTACK);
+ }
+
+ // test on signal alternate stack
+ testOnCurrentStack();
+
+ // test unpoisoning when jumping between stacks
+ poisonStackAndJump(signalStack, [] { longjmp(defaultStack.JmpBuf, 1); });
+}
+
+void setSignalAlternateStack(void *AltStack) {
+ sigaltstack((stack_t const *)AltStack, nullptr);
+
+ struct sigaction Action = {
+ .sa_sigaction = signalHandler,
+ .sa_flags = SA_SIGINFO | SA_NODEFER | SA_ONSTACK,
+ };
+ sigemptyset(&Action.sa_mask);
+
+ sigaction(SIGUSR1, &Action, nullptr);
+}
+
+// Main test function.
+// Must be run on another thread to be able to control memory placement between
+// default stack and alternate signal stack.
+// If the alternate signal stack is placed in close proximity before the
+// default stack, __asan_handle_no_return might unpoison both, even without
+// being aware of the signal alternate stack.
+// We want to test reliably that __asan_handle_no_return can properly unpoison
+// the signal alternate stack.
+void *threadFun(void *AltStack) {
+ // first test on default stack (sanity check), no signal alternate stack set
+ testOnCurrentStack();
+
+ setSignalAlternateStack(AltStack);
+
+ // test on default stack again, but now the signal alternate stack is set
+ testOnCurrentStack();
+
+ // set up jump to test unpoisoning when jumping between stacks
+ if (0 == setjmp(defaultStack.JmpBuf))
+ // Test on signal alternate stack, via signalHandler
+ poisonStackAndJump(defaultStack, [] { raise(SIGUSR1); });
+
+ assert(0 == __asan_region_is_poisoned(
+ defaultStack.LeftRedzone,
+ defaultStack.RightRedzone - defaultStack.LeftRedzone));
+
+ assert(0 == __asan_region_is_poisoned(
+ signalStack.LeftRedzone,
+ signalStack.RightRedzone - signalStack.LeftRedzone));
+
+ return nullptr;
+}
+
+} // namespace
+
+// Check that __asan_handle_no_return properly unpoisons a signal alternate
+// stack.
+// __asan_handle_no_return tries to determine the stack boundaries and
+// unpoisons all memory inside those. If this is not done properly, redzones for
+// variables on can remain in shadow memory which might lead to false positive
+// reports when the stack is reused.
+int main() {
+ size_t const PageSize = sysconf(_SC_PAGESIZE);
+ // To align the alternate stack, we round this up to page_size.
+ size_t const DefaultStackSize =
+ (PTHREAD_STACK_MIN - 1 + PageSize) & ~(PageSize - 1);
+ // The alternate stack needs a certain size, or the signal handler segfaults.
+ size_t const AltStackSize = 10 * PageSize;
+ size_t const MappingSize = DefaultStackSize + AltStackSize;
+ // Using mmap guarantees proper alignment.
+ void *const Mapping = mmap(nullptr, MappingSize,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS,
+ -1, 0);
+
+ stack_t const AltStack = {
+ .ss_sp = (char *)Mapping + DefaultStackSize,
+ .ss_flags = 0,
+ .ss_size = AltStackSize,
+ };
+
+ pthread_t Thread;
+ pthread_attr_t ThreadAttr;
+ pthread_attr_init(&ThreadAttr);
+ pthread_attr_setstack(&ThreadAttr, Mapping, DefaultStackSize);
+ pthread_create(&Thread, &ThreadAttr, &threadFun, (void *)&AltStack);
+
+ pthread_join(Thread, nullptr);
+
+ munmap(Mapping, MappingSize);
+}
More information about the llvm-commits
mailing list