Bug 686805 part 2 - Use a SIGSEGV signal handler to handle segmentation faults happening in loaded libraries address space. r=tglek,r=sewardj
authorMike Hommey <mh+mozilla@glandium.org>
Wed, 22 Feb 2012 08:12:15 +0100
changeset 87377 59237f456cdb05384424942052fbb7f96a24f592
parent 87376 c73a25f9fbd3d674b82c0ae996154c499513bdd1
child 87378 fed61303b55b60385307edda90ff9aaa58c7020b
push id22114
push userbmo@edmorley.co.uk
push dateWed, 22 Feb 2012 14:11:35 +0000
treeherdermozilla-central@e722d2ab78da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstglek, sewardj
bugs686805
milestone13.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 686805 part 2 - Use a SIGSEGV signal handler to handle segmentation faults happening in loaded libraries address space. r=tglek,r=sewardj
mozglue/linker/CustomElf.cpp
mozglue/linker/CustomElf.h
mozglue/linker/ElfLoader.cpp
mozglue/linker/ElfLoader.h
mozglue/linker/Mappable.h
--- a/mozglue/linker/CustomElf.cpp
+++ b/mozglue/linker/CustomElf.cpp
@@ -79,17 +79,20 @@ void debug_phdr(const char *type, const 
  * This calls Mappable::munmap instead of system munmap.
  */
 class Mappable1stPagePtr: public GenericMappedPtr<Mappable1stPagePtr> {
 public:
   Mappable1stPagePtr(Mappable *mappable)
   : GenericMappedPtr<Mappable1stPagePtr>(
       mappable->mmap(NULL, PAGE_SIZE, PROT_READ, MAP_PRIVATE, 0), PAGE_SIZE)
   , mappable(mappable)
-  { }
+  {
+    /* Ensure the content of this page */
+    mappable->ensure(*this);
+  }
 
 private:
   friend class GenericMappedPtr<Mappable1stPagePtr>;
   void munmap(void *buf, size_t length) {
     mappable->munmap(buf, length);
   }
 
   Mappable *mappable;
@@ -295,16 +298,21 @@ CustomElf::GetSymbolPtrInDeps(const char
 #else
     if (strcmp(symbol + 2, "cxa_atexit") == 0)
       return FunctionPtr(&ElfLoader::__wrap_cxa_atexit);
 #endif
     if (strcmp(symbol + 2, "cxa_finalize") == 0)
       return FunctionPtr(&ElfLoader::__wrap_cxa_finalize);
     if (strcmp(symbol + 2, "dso_handle") == 0)
       return const_cast<CustomElf *>(this);
+  } else if (symbol[0] == 's' && symbol[1] == 'i') {
+    if (strcmp(symbol + 2, "gnal") == 0)
+      return FunctionPtr(__wrap_signal);
+    if (strcmp(symbol + 2, "gaction") == 0)
+      return FunctionPtr(__wrap_sigaction);
   }
 
   void *sym;
   /* Search the symbol in the main program. Note this also tries all libraries
    * the system linker will have loaded RTLD_GLOBAL. Unfortunately, that doesn't
    * work with bionic, but its linker doesn't normally search the main binary
    * anyways. Moreover, on android, the main binary is dalvik. */
 #ifdef __GLIBC__
--- a/mozglue/linker/CustomElf.h
+++ b/mozglue/linker/CustomElf.h
@@ -206,16 +206,17 @@ class Mappable;
 
 /**
  * Library Handle class for ELF libraries we don't let the system linker
  * handle.
  */
 class CustomElf: public LibHandle, private ElfLoader::link_map
 {
   friend class ElfLoader;
+  friend class SEGVHandler;
 public:
   /**
    * Returns a new CustomElf using the given file descriptor to map ELF
    * content. The file descriptor ownership is stolen, and it will be closed
    * in CustomElf's destructor if an instance is created, or by the Load
    * method otherwise. The path corresponds to the file descriptor, and flags
    * are the same kind of flags that would be given to dlopen(), though
    * currently, none are supported and the behaviour is more or less that of
--- a/mozglue/linker/ElfLoader.cpp
+++ b/mozglue/linker/ElfLoader.cpp
@@ -8,16 +8,29 @@
 #include <unistd.h>
 #include <algorithm>
 #include <fcntl.h>
 #include "ElfLoader.h"
 #include "CustomElf.h"
 #include "Mappable.h"
 #include "Logging.h"
 
+#if defined(ANDROID) && ANDROID_VERSION < 8
+/* Android API < 8 doesn't provide sigaltstack */
+#include <sys/syscall.h>
+
+extern "C" {
+
+inline int sigaltstack(const stack_t *ss, stack_t *oss) {
+  return syscall(__NR_sigaltstack, ss, oss);
+}
+
+} /* extern "C" */
+#endif
+
 using namespace mozilla;
 
 #ifndef PAGE_SIZE
 #define PAGE_SIZE 4096
 #endif
 
 #ifndef PAGE_MASK
 #define PAGE_MASK (~ (PAGE_SIZE - 1))
@@ -560,8 +573,120 @@ ElfLoader::r_debug::Remove(ElfLoader::li
   if (r_map == map)
     r_map = map->l_next;
   else
     map->l_prev->l_next = map->l_next;
   map->l_next->l_prev = map->l_prev;
   r_state = RT_CONSISTENT;
   r_brk();
 }
+
+SEGVHandler::SEGVHandler()
+{
+  /* Setup an alternative stack if the already existing one is not big
+   * enough, or if there is none. */
+  if (sigaltstack(NULL, &oldStack) == -1 || !oldStack.ss_sp ||
+      oldStack.ss_size < stackSize) {
+    stackPtr.Assign(mmap(NULL, stackSize, PROT_READ | PROT_WRITE,
+                         MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), stackSize);
+    stack_t stack;
+    stack.ss_sp = stackPtr;
+    stack.ss_size = stackSize;
+    stack.ss_flags = 0;
+    sigaltstack(&stack, NULL);
+  }
+  /* Register our own handler, and store the already registered one in
+   * SEGVHandler's struct sigaction member */
+  struct sigaction action;
+  action.sa_sigaction = &SEGVHandler::handler;
+  sigemptyset(&action.sa_mask);
+  action.sa_flags = SA_SIGINFO | SA_NODEFER | SA_ONSTACK;
+  action.sa_restorer = NULL;
+  sigaction(SIGSEGV, &action, &this->action);
+}
+
+SEGVHandler::~SEGVHandler()
+{
+  /* Restore alternative stack for signals */
+  sigaltstack(&oldStack, NULL);
+  /* Restore original signal handler */
+  sigaction(SIGSEGV, &this->action, NULL);
+}
+
+/* TODO: "properly" handle signal masks and flags */
+void SEGVHandler::handler(int signum, siginfo_t *info, void *context)
+{
+  //ASSERT(signum == SIGSEGV);
+  debug("Caught segmentation fault @%p", info->si_addr);
+
+  /* Check whether we segfaulted in the address space of a CustomElf. We're
+   * only expecting that to happen as an access error. */
+  if (info->si_code == SEGV_ACCERR) {
+    /* We may segfault when running destructors in CustomElf::~CustomElf, so we
+     * can't hold a RefPtr on the handle. */
+    LibHandle *handle = ElfLoader::Singleton.GetHandleByPtr(info->si_addr).drop();
+    if (handle && !handle->IsSystemElf()) {
+      debug("Within the address space of a CustomElf");
+      CustomElf *elf = static_cast<CustomElf *>(static_cast<LibHandle *>(handle));
+      if (elf->mappable->ensure(info->si_addr))
+        return;
+    }
+  }
+
+  /* Redispatch to the registered handler */
+  SEGVHandler &that = ElfLoader::Singleton;
+  if (that.action.sa_flags & SA_SIGINFO) {
+    debug("Redispatching to registered handler @%p", that.action.sa_sigaction);
+    that.action.sa_sigaction(signum, info, context);
+  } else if (that.action.sa_handler == SIG_DFL) {
+    debug("Redispatching to default handler");
+    /* Reset the handler to the default one, and trigger it. */
+    sigaction(signum, &that.action, NULL);
+    raise(signum);
+  } else if (that.action.sa_handler != SIG_IGN) {
+    debug("Redispatching to registered handler @%p", that.action.sa_handler);
+    that.action.sa_handler(signum);
+  } else {
+    debug("Ignoring");
+  }
+}
+  
+sighandler_t
+__wrap_signal(int signum, sighandler_t handler)
+{
+  /* Use system signal() function for all but SIGSEGV signals. */
+  if (signum != SIGSEGV)
+    return signal(signum, handler);
+
+  SEGVHandler &that = ElfLoader::Singleton;
+  union {
+    sighandler_t signal;
+    void (*sigaction)(int, siginfo_t *, void *);
+  } oldHandler;
+
+  /* Keep the previous handler to return its value */
+  if (that.action.sa_flags & SA_SIGINFO) {
+    oldHandler.sigaction = that.action.sa_sigaction;
+  } else {
+    oldHandler.signal = that.action.sa_handler;
+  }
+  /* Set the new handler */
+  that.action.sa_handler = handler;
+  that.action.sa_flags = 0;
+
+  return oldHandler.signal;
+}
+
+int
+__wrap_sigaction(int signum, const struct sigaction *act,
+                 struct sigaction *oldact)
+{
+  /* Use system sigaction() function for all but SIGSEGV signals. */
+  if (signum != SIGSEGV)
+    return sigaction(signum, act, oldact);
+
+  SEGVHandler &that = ElfLoader::Singleton;
+  if (oldact)
+    *oldact = that.action;
+  if (act)
+    that.action = *act;
+  return 0;
+}
--- a/mozglue/linker/ElfLoader.h
+++ b/mozglue/linker/ElfLoader.h
@@ -2,16 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef ElfLoader_h
 #define ElfLoader_h
 
 #include <vector>
 #include <dlfcn.h>
+#include <signal.h>
 #include "mozilla/RefPtr.h"
 #include "Zip.h"
 
 /**
  * dlfcn.h replacement functions
  */
 extern "C" {
   void *__wrap_dlopen(const char *path, int flags);
@@ -23,16 +24,20 @@ extern "C" {
   typedef struct {
     const char *dli_fname;
     void *dli_fbase;
     const char *dli_sname;
     void *dli_saddr;
   } Dl_info;
 #endif
   int __wrap_dladdr(void *addr, Dl_info *info);
+
+  sighandler_t __wrap_signal(int signum, sighandler_t handler);
+  int __wrap_sigaction(int signum, const struct sigaction *act,
+                       struct sigaction *oldact);
 }
 
 /**
  * Abstract class for loaded libraries. Libraries may be loaded through the
  * system linker or this linker, both cases will be derived from this class.
  */
 class LibHandle: public mozilla::RefCounted<LibHandle>
 {
@@ -114,16 +119,17 @@ public:
 
 protected:
   /**
    * Returns whether the handle is a SystemElf or not. (short of a better way
    * to do this without RTTI)
    */
   friend class ElfLoader;
   friend class CustomElf;
+  friend class SEGVHandler;
   virtual bool IsSystemElf() const { return false; }
 
 private:
   int directRefCnt;
   char *path;
 };
 
 /**
@@ -169,19 +175,66 @@ private:
   SystemElf(const char *path, void *handle)
   : LibHandle(path), dlhandle(handle) { }
 
   /* Handle as returned by system dlopen() */
   void *dlhandle;
 };
 
 /**
+ * The ElfLoader registers its own SIGSEGV handler to handle segmentation
+ * faults within the address space of the loaded libraries. It however
+ * allows a handler to be set for faults in other places, and redispatches
+ * to the handler set through signal() or sigaction(). We assume no system
+ * library loaded with system dlopen is going to call signal or sigaction
+ * for SIGSEGV.
+ */
+class SEGVHandler
+{
+protected:
+  SEGVHandler();
+  ~SEGVHandler();
+
+private:
+  friend sighandler_t __wrap_signal(int signum, sighandler_t handler);
+  friend int __wrap_sigaction(int signum, const struct sigaction *act,
+                              struct sigaction *oldact);
+
+  /**
+   * SIGSEGV handler registered with __wrap_signal or __wrap_sigaction.
+   */
+  struct sigaction action;
+  
+  /**
+   * ElfLoader SIGSEGV handler.
+   */
+  static void handler(int signum, siginfo_t *info, void *context);
+
+  /**
+   * Size of the alternative stack. The printf family requires more than 8KB
+   * of stack, and our signal handler may print a few things.
+   */
+  static const size_t stackSize = 12 * 1024;
+
+  /**
+   * Alternative stack information used before initialization.
+   */
+  stack_t oldStack;
+
+  /**
+   * Pointer to an alternative stack for signals. Only set if oldStack is
+   * not set or not big enough.
+   */
+  MappedPtr stackPtr;
+};
+
+/**
  * Elf Loader class in charge of loading and bookkeeping libraries.
  */
-class ElfLoader
+class ElfLoader: public SEGVHandler
 {
 public:
   /**
    * The Elf Loader instance
    */
   static ElfLoader Singleton;
 
   /**
--- a/mozglue/linker/Mappable.h
+++ b/mozglue/linker/Mappable.h
@@ -32,16 +32,23 @@ private:
     ::munmap(addr, length);
   }
   /* Limit use of Mappable::munmap to classes that keep track of the address
    * and size of the mapping. This allows to ignore ::munmap return value. */
   friend class Mappable1stPagePtr;
 
 public:
   /**
+   * Ensures the availability of the memory pages for the page(s) containing
+   * the given address. Returns whether the pages were successfully made
+   * available.
+   */
+  virtual bool ensure(const void *addr) { return true; }
+
+  /**
    * Indicate to a Mappable instance that no further mmap is going to happen.
    */
   virtual void finalize() = 0;
 };
 
 /**
  * Mappable implementation for plain files
  */