Bug 874708 - Hook libc's sigaction to avoid system libraries replacing our segfault handler temporarily and restoring it wrongly. r=nfroyd

This commit is contained in:
Mike Hommey 2013-06-09 09:23:03 +02:00
Родитель 800328cc02
Коммит 7a3c231b71
3 изменённых файлов: 143 добавлений и 63 удалений

Просмотреть файл

@ -308,11 +308,6 @@ CustomElf::GetSymbolPtrInDeps(const char *symbol) const
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;
@ -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<char *>(mapped) + off);
}

Просмотреть файл

@ -16,10 +16,11 @@
#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" {
@ -674,26 +675,38 @@ 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 */
@ -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 <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 ||
@ -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)

Просмотреть файл

@ -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;
};
/**