Bug 874708 - Hook libc's sigaction to avoid system libraries replacing our segfault handler temporarily and restoring it wrongly. r=nfroyd
authorMike Hommey <mh+mozilla@glandium.org>
Sun, 09 Jun 2013 09:23:03 +0200
changeset 145950 1f228b1d95664000454af81fedee37406017d585
parent 145949 122712f43da6e64aeae81bfa1016322d5158da05
child 145951 8ef966de04d5903c9dcb9dbe050d056dc57cf960
push id2697
push userbbajaj@mozilla.com
push dateMon, 05 Aug 2013 18:49:53 +0000
treeherdermozilla-beta@dfec938c7b63 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnfroyd
bugs874708
milestone24.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 874708 - Hook libc's sigaction to avoid system libraries replacing our segfault handler temporarily and restoring it wrongly. r=nfroyd
mozglue/linker/CustomElf.cpp
mozglue/linker/ElfLoader.cpp
mozglue/linker/ElfLoader.h
--- a/mozglue/linker/CustomElf.cpp
+++ b/mozglue/linker/CustomElf.cpp
@@ -303,21 +303,16 @@ CustomElf::GetSymbolPtrInDeps(const char
       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);
     if (strcmp(symbol + 2, "moz_linker_stats") == 0)
       return FunctionPtr(&ElfLoader::stats);
-  } 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__
@@ -424,19 +419,21 @@ CustomElf::LoadSegment(const Phdr *pt_lo
     } else {
       log("%s: Didn't map at the expected location (wanted: %p, got: %p)",
           GetPath(), where, mapped);
     }
     return false;
   }
 
   /* Ensure the availability of all pages within the mapping if on-demand
-   * decompression is disabled (MOZ_LINKER_ONDEMAND=0). */
+   * decompression is disabled (MOZ_LINKER_ONDEMAND=0 or signal handler not
+   * registered). */
   const char *ondemand = getenv("MOZ_LINKER_ONDEMAND");
-  if (ondemand && !strncmp(ondemand, "0", 2 /* Including '\0' */)) {
+  if (!ElfLoader::Singleton.hasRegisteredHandler() ||
+      (ondemand && !strncmp(ondemand, "0", 2 /* Including '\0' */))) {
     for (Addr off = 0; off < pt_load->p_filesz; off += PAGE_SIZE) {
       mappable->ensure(reinterpret_cast<char *>(mapped) + off);
     }
   }
   /* When p_memsz is greater than p_filesz, we need to have nulled out memory
    * after p_filesz and before p_memsz.
    * Mappable::mmap already guarantees that after p_filesz and up to the end
    * of the page p_filesz is in, memory is nulled out.
--- a/mozglue/linker/ElfLoader.cpp
+++ b/mozglue/linker/ElfLoader.cpp
@@ -11,20 +11,21 @@
 #include <algorithm>
 #include <fcntl.h>
 #include "ElfLoader.h"
 #include "CustomElf.h"
 #include "Mappable.h"
 #include "Logging.h"
 
 #if defined(ANDROID)
+#include <sys/syscall.h>
+
 #include <android/api-level.h>
 #if __ANDROID_API__ < 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" */
@@ -669,36 +670,48 @@ ElfLoader::DebuggerHelper::DebuggerHelpe
  * Helper class to ensure the given pointer is writable within the scope of
  * an instance. Permissions to the memory page where the pointer lies are
  * restored to their original value when the instance is destroyed.
  */
 class EnsureWritable
 {
 public:
   template <typename T>
-  EnsureWritable(T *&ptr)
+  EnsureWritable(T *ptr, size_t length_ = sizeof(T))
   {
-    prot = getProt((uintptr_t) &ptr);
-    if (prot == -1)
+    MOZ_ASSERT(length_ < PAGE_SIZE);
+    prot = -1;
+    page = MAP_FAILED;
+
+    uintptr_t firstPage = reinterpret_cast<uintptr_t>(ptr) & PAGE_MASK;
+    length = (reinterpret_cast<uintptr_t>(ptr) + length_ - firstPage
+              + PAGE_SIZE - 1) & PAGE_MASK;
+
+    uintptr_t end;
+
+    prot = getProt(firstPage, &end);
+    if (prot == -1 || (firstPage + length) > end)
       MOZ_CRASH();
-    /* Pointers are aligned such that their value can't be spanning across
-     * 2 pages. */
-    page = (void*)((uintptr_t) &ptr & PAGE_MASK);
-    if (!(prot & PROT_WRITE))
-      mprotect(page, PAGE_SIZE, prot | PROT_WRITE);
+
+    if (prot & PROT_WRITE)
+      return;
+
+    page = reinterpret_cast<void *>(firstPage);
+    mprotect(page, length, prot | PROT_WRITE);
   }
 
   ~EnsureWritable()
   {
-    if (!(prot & PROT_WRITE))
-      mprotect(page, PAGE_SIZE, prot);
+    if (page != MAP_FAILED) {
+      mprotect(page, length, prot);
+}
   }
 
 private:
-  int getProt(uintptr_t addr)
+  int getProt(uintptr_t addr, uintptr_t *end)
   {
     /* The interesting part of the /proc/self/maps format looks like:
      * startAddr-endAddr rwxp */
     int result = 0;
     AutoCloseFILE f(fopen("/proc/self/maps", "r"));
     while (f) {
       unsigned long long startAddr, endAddr;
       char perms[5];
@@ -713,23 +726,25 @@ private:
       if (perms[1] == 'w')
         result |= PROT_WRITE;
       else if (perms[1] != '-')
         return -1;
       if (perms[2] == 'x')
         result |= PROT_EXEC;
       else if (perms[2] != '-')
         return -1;
+      *end = endAddr;
       return result;
     }
     return -1;
   }
 
   int prot;
   void *page;
+  size_t length;
 };
 
 /**
  * The system linker maintains a doubly linked list of library it loads
  * for use by the debugger. Unfortunately, it also uses the list pointers
  * in a lot of operations and adding our data in the list is likely to
  * trigger crashes when the linker tries to use data we don't provide or
  * that fall off the amount data we allocated. Fortunately, the linker only
@@ -751,17 +766,17 @@ ElfLoader::DebuggerHelper::Add(ElfLoader
   dbg->r_state = r_debug::RT_ADD;
   dbg->r_brk();
   map->l_prev = NULL;
   map->l_next = dbg->r_map;
   if (!firstAdded) {
     firstAdded = map;
     /* When adding a library for the first time, r_map points to data
      * handled by the system linker, and that data may be read-only */
-    EnsureWritable w(dbg->r_map->l_prev);
+    EnsureWritable w(&dbg->r_map->l_prev);
     dbg->r_map->l_prev = map;
   } else
     dbg->r_map->l_prev = map;
   dbg->r_map = map;
   dbg->r_state = r_debug::RT_CONSISTENT;
   dbg->r_brk();
 }
 
@@ -775,26 +790,120 @@ ElfLoader::DebuggerHelper::Remove(ElfLoa
   if (dbg->r_map == map)
     dbg->r_map = map->l_next;
   else
     map->l_prev->l_next = map->l_next;
   if (map == firstAdded) {
     firstAdded = map->l_prev;
     /* When removing the first added library, its l_next is going to be
      * data handled by the system linker, and that data may be read-only */
-    EnsureWritable w(map->l_next->l_prev);
+    EnsureWritable w(&map->l_next->l_prev);
     map->l_next->l_prev = map->l_prev;
   } else
     map->l_next->l_prev = map->l_prev;
   dbg->r_state = r_debug::RT_CONSISTENT;
   dbg->r_brk();
 }
 
+#if defined(ANDROID)
+/* As some system libraries may be calling signal() or sigaction() to
+ * set a SIGSEGV handler, effectively breaking MappableSeekableZStream,
+ * or worse, restore our SIGSEGV handler with wrong flags (which using
+ * signal() will do), we want to hook into the system's sigaction() to
+ * replace it with our own wrapper instead, so that our handler is never
+ * replaced. We used to only do that with libraries this linker loads,
+ * but it turns out at least one system library does call signal() and
+ * breaks us (libsc-a3xx.so on the Samsung Galaxy S4).
+ * As libc's signal (bsd_signal/sysv_signal, really) calls sigaction
+ * under the hood, instead of calling the signal system call directly,
+ * we only need to hook sigaction. This is true for both bionic and
+ * glibc.
+ */
+
+/* libc's sigaction */
+extern "C" int
+sigaction(int signum, const struct sigaction *act,
+          struct sigaction *oldact);
+
+/* Simple reimplementation of sigaction. This is roughly equivalent
+ * to the assembly that comes in bionic, but not quite equivalent to
+ * glibc's implementation, so we only use this on Android. */
+int
+sys_sigaction(int signum, const struct sigaction *act,
+              struct sigaction *oldact)
+{
+  return syscall(__NR_sigaction, signum, act, oldact);
+}
+
+/* Replace the first instructions of the given function with a jump
+ * to the given new function. */
+template <typename T>
+static bool
+Divert(T func, T new_func)
+{
+  void *ptr = FunctionPtr(func);
+  uintptr_t addr = reinterpret_cast<uintptr_t>(ptr);
+
+#if defined(__i386__)
+  // A 32-bit jump is a 5 bytes instruction.
+  EnsureWritable w(ptr, 5);
+  *reinterpret_cast<unsigned char *>(addr) = 0xe9; // jmp
+  *reinterpret_cast<intptr_t *>(addr + 1) =
+    reinterpret_cast<uintptr_t>(new_func) - addr - 5; // target displacement
+  return true;
+#elif defined(__arm__)
+  const unsigned char trampoline[] = {
+                            // .thumb
+    0x46, 0x04,             // nop
+    0x78, 0x47,             // bx pc
+    0x46, 0x04,             // nop
+                            // .arm
+    0x04, 0xf0, 0x1f, 0xe5, // ldr pc, [pc, #-4]
+                            // .word <new_func>
+  };
+  const unsigned char *start;
+  if (addr & 0x01) {
+    /* Function is thumb, the actual address of the code is without the
+     * least significant bit. */
+    addr--;
+    /* The arm part of the trampoline needs to be 32-bit aligned */
+    if (addr & 0x02)
+      start = trampoline;
+    else
+      start = trampoline + 2;
+  } else {
+    /* Function is arm, we only need the arm part of the trampoline */
+    start = trampoline + 6;
+  }
+
+  size_t len = sizeof(trampoline) - (start - trampoline);
+  EnsureWritable w(reinterpret_cast<void *>(addr), len + sizeof(void *));
+  memcpy(reinterpret_cast<void *>(addr), start, len);
+  *reinterpret_cast<void **>(addr + len) = FunctionPtr(new_func);
+  cacheflush(addr, addr + len + sizeof(void *), 0);
+  return true;
+#else
+  return false;
+#endif
+}
+#else
+#define sys_sigaction sigaction
+template <typename T>
+static bool
+Divert(T func, T new_func)
+{
+  return false;
+}
+#endif
+
 SEGVHandler::SEGVHandler()
+: registeredHandler(false)
 {
+  if (!Divert(sigaction, __wrap_sigaction))
+    return;
   /* 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;
@@ -804,25 +913,25 @@ SEGVHandler::SEGVHandler()
   }
   /* 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);
+  registeredHandler = !sys_sigaction(SIGSEGV, &action, &this->action);
 }
 
 SEGVHandler::~SEGVHandler()
 {
   /* Restore alternative stack for signals */
   sigaltstack(&oldStack, NULL);
   /* Restore original signal handler */
-  sigaction(SIGSEGV, &this->action, NULL);
+  sys_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);
 
@@ -843,60 +952,34 @@ void SEGVHandler::handler(int signum, si
   SEGVHandler &that = ElfLoader::Singleton;
   if (that.action.sa_flags & SA_SIGINFO) {
     debug("Redispatching to registered handler @%p",
           FunctionPtr(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);
+    sys_sigaction(signum, &that.action, NULL);
     raise(signum);
   } else if (that.action.sa_handler != SIG_IGN) {
     debug("Redispatching to registered handler @%p",
           FunctionPtr(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)
+SEGVHandler::__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);
+    return sys_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
@@ -26,20 +26,16 @@ extern "C" {
     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);
-
   struct dl_phdr_info {
     Elf::Addr dlpi_addr;
     const char *dlpi_name;
     const Elf::Phdr *dlpi_phdr;
     Elf::Half dlpi_phnum;
   };
 
   typedef int (*dl_phdr_cb)(struct dl_phdr_info *, size_t, void *);
@@ -287,29 +283,31 @@ private:
   /* 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.
+ * to the handler set through signal() or sigaction().
  */
 class SEGVHandler
 {
+public:
+  bool hasRegisteredHandler() {
+    return registeredHandler;
+  }
+
 protected:
   SEGVHandler();
   ~SEGVHandler();
 
 private:
-  friend sighandler_t __wrap_signal(int signum, sighandler_t handler);
-  friend int __wrap_sigaction(int signum, const struct sigaction *act,
+  static int __wrap_sigaction(int signum, const struct sigaction *act,
                               struct sigaction *oldact);
 
   /**
    * SIGSEGV handler registered with __wrap_signal or __wrap_sigaction.
    */
   struct sigaction action;
   
   /**
@@ -328,16 +326,18 @@ private:
    */
   stack_t oldStack;
 
   /**
    * Pointer to an alternative stack for signals. Only set if oldStack is
    * not set or not big enough.
    */
   MappedPtr stackPtr;
+
+  bool registeredHandler;
 };
 
 /**
  * Elf Loader class in charge of loading and bookkeeping libraries.
  */
 class ElfLoader: public SEGVHandler
 {
 public: