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 87397 59237f456cdb05384424942052fbb7f96a24f592
parent 87396 c73a25f9fbd3d674b82c0ae996154c499513bdd1
child 87398 fed61303b55b60385307edda90ff9aaa58c7020b
push id551
push userrcampbell@mozilla.com
push dateWed, 22 Feb 2012 16:49:06 +0000
treeherderfx-team@4f425ab85667 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstglek, sewardj
bugs686805
milestone13.0a1
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
  */