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 134460 1f228b1d95664000454af81fedee37406017d585
parent 134459 122712f43da6e64aeae81bfa1016322d5158da05
child 134461 8ef966de04d5903c9dcb9dbe050d056dc57cf960
push id1723
push userttaubert@mozilla.com
push dateMon, 10 Jun 2013 19:17:22 +0000
treeherderfx-team@a55ab39eda71 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnfroyd
bugs874708
milestone24.0a1
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: