From 7a3c231b71ee659015a2b9ed5c56700a36aa324b Mon Sep 17 00:00:00 2001 From: Mike Hommey Date: Sun, 9 Jun 2013 09:23:03 +0200 Subject: [PATCH] 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 | 11 +-- mozglue/linker/ElfLoader.cpp | 177 +++++++++++++++++++++++++---------- mozglue/linker/ElfLoader.h | 18 ++-- 3 files changed, 143 insertions(+), 63 deletions(-) diff --git a/mozglue/linker/CustomElf.cpp b/mozglue/linker/CustomElf.cpp index 763739dce52c..1833d47b38bd 100644 --- a/mozglue/linker/CustomElf.cpp +++ b/mozglue/linker/CustomElf.cpp @@ -308,11 +308,6 @@ CustomElf::GetSymbolPtrInDeps(const char *symbol) const return const_cast(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; @@ -429,9 +424,11 @@ CustomElf::LoadSegment(const Phdr *pt_load) const } /* 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(mapped) + off); } diff --git a/mozglue/linker/ElfLoader.cpp b/mozglue/linker/ElfLoader.cpp index 41600bdc0544..a9a6c6a125b9 100644 --- a/mozglue/linker/ElfLoader.cpp +++ b/mozglue/linker/ElfLoader.cpp @@ -16,10 +16,11 @@ #include "Logging.h" #if defined(ANDROID) +#include + #include #if __ANDROID_API__ < 8 /* Android API < 8 doesn't provide sigaltstack */ -#include extern "C" { @@ -674,26 +675,38 @@ class EnsureWritable { public: template - 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(ptr) & PAGE_MASK; + length = (reinterpret_cast(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(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 */ @@ -718,6 +731,7 @@ private: result |= PROT_EXEC; else if (perms[2] != '-') return -1; + *end = endAddr; return result; } return -1; @@ -725,6 +739,7 @@ private: int prot; void *page; + size_t length; }; /** @@ -756,7 +771,7 @@ ElfLoader::DebuggerHelper::Add(ElfLoader::link_map *map) 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; @@ -780,7 +795,7 @@ ElfLoader::DebuggerHelper::Remove(ElfLoader::link_map *map) 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; @@ -788,8 +803,102 @@ ElfLoader::DebuggerHelper::Remove(ElfLoader::link_map *map) dbg->r_brk(); } -SEGVHandler::SEGVHandler() +#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 +static bool +Divert(T func, T new_func) +{ + void *ptr = FunctionPtr(func); + uintptr_t addr = reinterpret_cast(ptr); + +#if defined(__i386__) + // A 32-bit jump is a 5 bytes instruction. + EnsureWritable w(ptr, 5); + *reinterpret_cast(addr) = 0xe9; // jmp + *reinterpret_cast(addr + 1) = + reinterpret_cast(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 + }; + 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(addr), len + sizeof(void *)); + memcpy(reinterpret_cast(addr), start, len); + *reinterpret_cast(addr + len) = FunctionPtr(new_func); + cacheflush(addr, addr + len + sizeof(void *), 0); + return true; +#else + return false; +#endif +} +#else +#define sys_sigaction sigaction +template +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 || @@ -809,7 +918,7 @@ SEGVHandler::SEGVHandler() 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() @@ -817,7 +926,7 @@ 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 */ @@ -848,7 +957,7 @@ void SEGVHandler::handler(int signum, siginfo_t *info, void *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", @@ -858,40 +967,14 @@ void SEGVHandler::handler(int signum, siginfo_t *info, void *context) 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) diff --git a/mozglue/linker/ElfLoader.h b/mozglue/linker/ElfLoader.h index 8b76a8baffbb..ae44bfe54344 100644 --- a/mozglue/linker/ElfLoader.h +++ b/mozglue/linker/ElfLoader.h @@ -31,10 +31,6 @@ extern "C" { #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; @@ -292,19 +288,21 @@ private: * 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); /** @@ -333,6 +331,8 @@ private: * not set or not big enough. */ MappedPtr stackPtr; + + bool registeredHandler; }; /**