[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