Commit 3c9f4651 authored by Mike Hommey's avatar Mike Hommey
Browse files

Bug 686805 part 2 - Use a SIGSEGV signal handler to handle segmentation faults...

Bug 686805 part 2 - Use a SIGSEGV signal handler to handle segmentation faults happening in loaded libraries address space. r=tglek,r=sewardj
parent 524ce829
Loading
Loading
Loading
Loading
+9 −1
Original line number Diff line number Diff line
@@ -84,7 +84,10 @@ public:
  : 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>;
@@ -300,6 +303,11 @@ CustomElf::GetSymbolPtrInDeps(const char *symbol) const
      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;
+1 −0
Original line number Diff line number Diff line
@@ -211,6 +211,7 @@ class Mappable;
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
+125 −0
Original line number Diff line number Diff line
@@ -13,6 +13,19 @@
#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
@@ -565,3 +578,115 @@ ElfLoader::r_debug::Remove(ElfLoader::link_map *map)
  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;
}
+54 −1
Original line number Diff line number Diff line
@@ -7,6 +7,7 @@

#include <vector>
#include <dlfcn.h>
#include <signal.h>
#include "mozilla/RefPtr.h"
#include "Zip.h"

@@ -28,6 +29,10 @@ extern "C" {
  } 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);
}

/**
@@ -119,6 +124,7 @@ protected:
   */
  friend class ElfLoader;
  friend class CustomElf;
  friend class SEGVHandler;
  virtual bool IsSystemElf() const { return false; }

private:
@@ -173,10 +179,57 @@ private:
  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:
  /**
+7 −0
Original line number Diff line number Diff line
@@ -36,6 +36,13 @@ private:
  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.
   */