Add a SIGSEGV signal handler which detects stack overflow
authorAndrii Grynenko <andrii@fb.com>
Fri, 3 Jun 2016 22:01:28 +0000 (15:01 -0700)
committerFacebook Github Bot 2 <facebook-github-bot-2-bot@fb.com>
Fri, 3 Jun 2016 22:08:24 +0000 (15:08 -0700)
Summary: GuardPageAllocator now keeps a track of all protected pages. If SIGSEGV occurs, signal handler can check address at fault against protected pages and if it matches - we got a fiber stack overflow.

Reviewed By: yfeldblum

Differential Revision: D3386960

fbshipit-source-id: 775b36ee08fecbbd87da0025f794717c222f5cce

folly/fibers/GuardPageAllocator.cpp

index 80bdbd755c7628edbe5ddaaafe950d71c51c73cc..87dc8f91ffe75760699b1717741e31aefccb81d5 100644 (file)
  */
 #include "GuardPageAllocator.h"
 
+#include <signal.h>
+
 #include <mutex>
 
 #include <folly/Singleton.h>
 #include <folly/SpinLock.h>
+#include <folly/Synchronized.h>
 #include <folly/portability/SysMman.h>
 #include <folly/portability/Unistd.h>
 
@@ -83,6 +86,9 @@ class StackCache {
     auto p = freeList_.back().first;
     if (!freeList_.back().second) {
       PCHECK(0 == ::mprotect(p, pagesize(), PROT_NONE));
+      SYNCHRONIZED(pages, protectedPages()) {
+        pages.insert(reinterpret_cast<intptr_t>(p));
+      }
     }
     freeList_.pop_back();
 
@@ -122,9 +128,27 @@ class StackCache {
 
   ~StackCache() {
     assert(storage_);
+    SYNCHRONIZED(pages, protectedPages()) {
+      for (const auto& item : freeList_) {
+        pages.erase(reinterpret_cast<intptr_t>(item.first));
+      }
+    }
     PCHECK(0 == ::munmap(storage_, allocSize_ * kNumGuarded));
   }
 
+  static bool isProtected(intptr_t addr) {
+    // Use a read lock for reading.
+    SYNCHRONIZED_CONST(pages, protectedPages()) {
+      for (const auto& page : pages) {
+        intptr_t pageEnd = page + pagesize();
+        if (page <= addr && addr < pageEnd) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
  private:
   folly::SpinLock lock_;
   unsigned char* storage_{nullptr};
@@ -144,8 +168,57 @@ class StackCache {
   static size_t allocSize(size_t size) {
     return pagesize() * ((size + pagesize() - 1) / pagesize() + 1);
   }
+
+  static folly::Synchronized<std::unordered_set<intptr_t>>& protectedPages() {
+    static auto instance =
+        new folly::Synchronized<std::unordered_set<intptr_t>>();
+    return *instance;
+  }
 };
 
+#ifndef _WIN32
+
+namespace {
+
+struct sigaction oldSigsegvAction;
+
+void sigsegvSignalHandler(int signum, siginfo_t* info, void*) {
+  if (signum != SIGSEGV) {
+    std::cerr << "GuardPageAllocator signal handler called for signal: "
+              << signum;
+    return;
+  }
+
+  if (info &&
+      StackCache::isProtected(reinterpret_cast<intptr_t>(info->si_addr))) {
+    std::cerr << "folly::fibers Fiber stack overflow detected." << std::endl;
+  }
+
+  // Restore old signal handler and let it handle the signal.
+  sigaction(signum, &oldSigsegvAction, nullptr);
+  raise(signum);
+}
+
+void installSignalHandler() {
+  static std::once_flag onceFlag;
+  std::call_once(onceFlag, []() {
+    struct sigaction sa;
+    memset(&sa, 0, sizeof(sa));
+    sigemptyset(&sa.sa_mask);
+    // By default signal handlers are run on the signaled thread's stack.
+    // In case of stack overflow running the SIGSEGV signal handler on
+    // the same stack leads to another SIGSEGV and crashes the program.
+    // Use SA_ONSTACK, so alternate stack is used (only if configured via
+    // sigaltstack).
+    sa.sa_flags |= SA_SIGINFO | SA_ONSTACK;
+    sa.sa_sigaction = &sigsegvSignalHandler;
+    sigaction(SIGSEGV, &sa, &oldSigsegvAction);
+  });
+}
+}
+
+#endif
+
 class CacheManager {
  public:
   static CacheManager& instance() {
@@ -204,7 +277,11 @@ class StackCacheEntry {
 };
 
 GuardPageAllocator::GuardPageAllocator(bool useGuardPages)
-    : useGuardPages_(useGuardPages) {}
+    : useGuardPages_(useGuardPages) {
+#ifndef _WIN32
+  installSignalHandler();
+#endif
+}
 
 GuardPageAllocator::~GuardPageAllocator() = default;