blob: f268b4fdbec84e86f8fcfebd289e9ad88c4b9276 [file] [log] [blame]
/*
* Copyright (c) 2012 The Native Client Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include <errno.h>
#include <signal.h>
#include <stddef.h>
#include <string.h>
#include "native_client/src/include/build_config.h"
#include "native_client/src/include/nacl_macros.h"
#include "native_client/src/include/portability_io.h"
#include "native_client/src/shared/platform/nacl_check.h"
#include "native_client/src/shared/platform/nacl_exit.h"
#include "native_client/src/shared/platform/nacl_log.h"
#include "native_client/src/trusted/service_runtime/arch/sel_ldr_arch.h"
#include "native_client/src/trusted/service_runtime/nacl_app_thread.h"
#include "native_client/src/trusted/service_runtime/nacl_config.h"
#include "native_client/src/trusted/service_runtime/nacl_exception.h"
#include "native_client/src/trusted/service_runtime/nacl_globals.h"
#include "native_client/src/trusted/service_runtime/nacl_signal.h"
#include "native_client/src/trusted/service_runtime/nacl_tls.h"
#include "native_client/src/trusted/service_runtime/sel_ldr.h"
#include "native_client/src/trusted/service_runtime/sel_rt.h"
#include "native_client/src/trusted/service_runtime/thread_suspension.h"
/*
* This module is based on the Posix signal model. See:
* http://www.opengroup.org/onlinepubs/009695399/functions/sigaction.html
*/
/*
* The signals listed here should either be handled by NaCl (or otherwise
* trusted code).
*/
static int s_Signals[] = {
#if NACL_ARCH(NACL_BUILD_ARCH) != NACL_mips
/* This signal does not exist on MIPS. */
SIGSTKFLT,
#endif
SIGSYS, /* Used to support a seccomp-bpf sandbox. */
NACL_THREAD_SUSPEND_SIGNAL,
SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGBUS, SIGFPE, SIGSEGV, SIGTERM,
/* Handle SIGABRT in case someone sends it asynchronously using kill(). */
SIGABRT
};
static struct sigaction s_OldActions[NACL_ARRAY_SIZE_UNSAFE(s_Signals)];
static NaClSignalHandler g_handler_func;
void NaClSignalHandlerSet(NaClSignalHandler func) {
g_handler_func = func;
}
/*
* Returns, via is_untrusted, whether the signal happened while
* executing untrusted code.
*
* Returns, via result_thread, the NaClAppThread that untrusted code
* was running in.
*
* Note that this should only be called from the thread in which the
* signal occurred, because on x86-64 it reads a thread-local variable
* (nacl_current_thread).
*/
static void GetCurrentThread(const struct NaClSignalContext *sig_ctx,
int *is_untrusted,
struct NaClAppThread **result_thread) {
#if NACL_ARCH(NACL_BUILD_ARCH) == NACL_x86 && NACL_BUILD_SUBARCH == 32
/*
* For x86-32, if %cs does not match, it is untrusted code.
*
* Note that this check might not be valid on Mac OS X, because
* thread_get_state() does not return the value of NaClGetGlobalCs()
* for a thread suspended inside a syscall. However, this code is
* not used on Mac OS X.
*/
*is_untrusted = (NaClGetGlobalCs() != sig_ctx->cs);
*result_thread = NaClAppThreadGetFromIndex(sig_ctx->gs >> 3);
#elif (NACL_ARCH(NACL_BUILD_ARCH) == NACL_x86 && NACL_BUILD_SUBARCH == 64) || \
NACL_ARCH(NACL_BUILD_ARCH) == NACL_arm || \
NACL_ARCH(NACL_BUILD_ARCH) == NACL_mips
struct NaClAppThread *natp = NaClTlsGetCurrentThread();
if (natp == NULL) {
*is_untrusted = 0;
*result_thread = NULL;
} else {
/*
* Get the address of an arbitrary local, stack-allocated variable,
* just for the purpose of doing a sanity check.
*/
void *pointer_into_stack = &natp;
/*
* Sanity check: Make sure the stack we are running on is not
* allocated in untrusted memory. This checks that the alternate
* signal stack is correctly set up, because otherwise, if it is
* not set up, the test case would not detect that.
*
* There is little point in doing a CHECK instead of a DCHECK,
* because if we are running off an untrusted stack, we have already
* lost.
*/
DCHECK(!NaClIsUserAddr(natp->nap, (uintptr_t) pointer_into_stack));
*is_untrusted = NaClIsUserAddr(natp->nap, sig_ctx->prog_ctr);
*result_thread = natp;
}
#else
# error Unsupported architecture
#endif
/*
* Trusted code could accidentally jump into sandbox address space,
* so don't rely on prog_ctr on its own for determining whether a
* crash comes from untrusted code. We don't want to restore
* control to an untrusted exception handler if trusted code
* crashes.
*/
if (*is_untrusted &&
((*result_thread)->suspend_state & NACL_APP_THREAD_UNTRUSTED) == 0) {
*is_untrusted = 0;
}
}
static void FindAndRunHandler(int sig, siginfo_t *info, void *uc) {
unsigned int a;
/* If we need to keep searching, try the old signal handler. */
for (a = 0; a < NACL_ARRAY_SIZE(s_Signals); a++) {
/* If we handle this signal */
if (s_Signals[a] == sig) {
/* If this is a real sigaction pointer... */
if ((s_OldActions[a].sa_flags & SA_SIGINFO) != 0) {
/*
* On Mac OS X, sigaction() can return a "struct sigaction"
* with SA_SIGINFO set but with a NULL sa_sigaction if no
* signal handler was previously registered. This is allowed
* by POSIX, which does not require a struct returned by
* sigaction() to be intelligible. We check for NULL here to
* avoid a crash.
*/
if (s_OldActions[a].sa_sigaction != NULL) {
/* then call the old handler. */
s_OldActions[a].sa_sigaction(sig, info, uc);
break;
}
} else {
/* otherwise check if it is a real signal pointer */
if ((s_OldActions[a].sa_handler != SIG_DFL) &&
(s_OldActions[a].sa_handler != SIG_IGN)) {
/* and call the old signal. */
s_OldActions[a].sa_handler(sig);
break;
}
}
/*
* We matched the signal, but didn't handle it, so we emulate
* the default behavior which is to exit the app with the signal
* number as the error code.
*/
NaClExit(-sig);
}
}
}
/*
* This function checks whether we can dispatch the signal to an
* untrusted exception handler. If we can, it modifies the register
* state to call the handler and writes a stack frame into into
* untrusted address space, and returns true. Otherwise, it returns
* false.
*/
static int DispatchToUntrustedHandler(struct NaClAppThread *natp,
struct NaClSignalContext *regs) {
struct NaClApp *nap = natp->nap;
uintptr_t frame_addr;
volatile struct NaClExceptionFrame *frame;
uint32_t new_stack_ptr;
uintptr_t context_user_addr;
if (!NaClSignalCheckSandboxInvariants(regs, natp)) {
return 0;
}
if (nap->exception_handler == 0) {
return 0;
}
if (natp->exception_flag) {
return 0;
}
natp->exception_flag = 1;
if (natp->exception_stack == 0) {
new_stack_ptr = regs->stack_ptr - NACL_STACK_RED_ZONE;
} else {
new_stack_ptr = natp->exception_stack;
}
/* Allocate space for the stack frame, and ensure its alignment. */
new_stack_ptr -=
sizeof(struct NaClExceptionFrame) - NACL_STACK_PAD_BELOW_ALIGN;
new_stack_ptr = new_stack_ptr & ~NACL_STACK_ALIGN_MASK;
new_stack_ptr -= NACL_STACK_ARGS_SIZE;
new_stack_ptr -= NACL_STACK_PAD_BELOW_ALIGN;
frame_addr = NaClUserToSysAddrRange(nap, new_stack_ptr,
sizeof(struct NaClExceptionFrame));
if (frame_addr == kNaClBadAddress) {
/* We cannot write the stack frame. */
return 0;
}
context_user_addr = new_stack_ptr + offsetof(struct NaClExceptionFrame,
context);
frame = (struct NaClExceptionFrame *) frame_addr;
NaClSignalSetUpExceptionFrame(frame, regs, context_user_addr);
#if NACL_ARCH(NACL_BUILD_ARCH) == NACL_x86 && NACL_BUILD_SUBARCH == 32
regs->prog_ctr = nap->exception_handler;
regs->stack_ptr = new_stack_ptr;
#elif NACL_ARCH(NACL_BUILD_ARCH) == NACL_x86 && NACL_BUILD_SUBARCH == 64
regs->rdi = context_user_addr; /* Argument 1 */
regs->prog_ctr = NaClUserToSys(nap, nap->exception_handler);
regs->stack_ptr = NaClUserToSys(nap, new_stack_ptr);
#elif NACL_ARCH(NACL_BUILD_ARCH) == NACL_arm
/*
* Returning from the exception handler is not possible, so to avoid
* any confusion that might arise from jumping to an uninitialised
* address, we set the return address to zero.
*/
regs->lr = 0;
regs->r0 = context_user_addr; /* Argument 1 */
regs->prog_ctr = NaClUserToSys(nap, nap->exception_handler);
regs->stack_ptr = NaClUserToSys(nap, new_stack_ptr);
#elif NACL_ARCH(NACL_BUILD_ARCH) == NACL_mips
regs->return_addr = 0;
regs->a0 = context_user_addr;
regs->prog_ctr = NaClUserToSys(nap, nap->exception_handler);
regs->stack_ptr = NaClUserToSys(nap, new_stack_ptr);
/*
* Per Linux/MIPS convention, PIC functions assume that t9 holds
* the function's address on entry.
*/
regs->t9 = regs->prog_ctr;
#else
# error Unsupported architecture
#endif
#if NACL_ARCH(NACL_BUILD_ARCH) == NACL_x86
regs->flags &= ~NACL_X86_DIRECTION_FLAG;
#endif
return 1;
}
static void SignalCatch(int sig, siginfo_t *info, void *uc) {
struct NaClSignalContext sig_ctx;
int is_untrusted;
struct NaClAppThread *natp;
#if NACL_ARCH(NACL_BUILD_ARCH) == NACL_x86
/*
* Reset the x86 direction flag. New versions of gcc and libc
* assume that the direction flag is clear on entry to a function,
* as the x86 ABI requires. However, untrusted code can set this
* flag, and versions of Linux before 2.6.25 do not clear the flag
* before running the signal handler, so we clear it here for safety.
* See http://code.google.com/p/nativeclient/issues/detail?id=1495
*/
__asm__("cld");
#endif
NaClSignalContextFromHandler(&sig_ctx, uc);
GetCurrentThread(&sig_ctx, &is_untrusted, &natp);
#if NACL_ARCH(NACL_BUILD_ARCH) == NACL_x86 && NACL_BUILD_SUBARCH == 32
/*
* On Linux, the kernel does not restore %gs when entering the
* signal handler, so we must do that here. We need to do this for
* TLS to work and for glibc's syscall wrappers to work, because
* some builds of glibc fetch a syscall function pointer from the
* static TLS area. There is the potential for vulnerabilities if
* we call glibc without restoring %gs (such as
* http://code.google.com/p/nativeclient/issues/detail?id=1607),
* although the risk is reduced because the untrusted %gs segment
* has an extent of only 4 bytes (see
* http://code.google.com/p/nativeclient/issues/detail?id=2176).
*
* Note that, in comparison, Breakpad tries to avoid using libc
* calls at all when a crash occurs.
*
* For comparison, on Mac OS X, the kernel *does* restore the
* original %gs when entering the signal handler. On Mac, our
* assignment to %gs here wouldn't be necessary, but it wouldn't be
* harmful either. However, this code is not currently used on Mac
* OS X.
*
* Both Linux and Mac OS X necessarily restore %cs, %ds, and %ss
* otherwise we would have a hard time handling signals generated by
* untrusted code at all.
*
* Note that we check natp (which is based on %gs) rather than
* is_untrusted (which is based on %cs) because we need to handle
* the case where %gs is set to the untrusted-code value but %cs is
* not.
*
* GCC's stack protector (-fstack-protector) will make use of %gs even before
* we have a chance to restore it. It is important that this function is not
* compiled with -fstack-protector.
*/
if (natp != NULL) {
NaClSetGs(natp->user.trusted_gs);
}
#endif
if (sig != SIGINT && sig != SIGQUIT) {
if (NaClThreadSuspensionSignalHandler(sig, &sig_ctx, is_untrusted, natp)) {
NaClSignalContextToHandler(uc, &sig_ctx);
/* Resume untrusted code using possibly modified register state. */
return;
}
}
if (is_untrusted &&
(sig == SIGSEGV || sig == SIGILL || sig == SIGFPE ||
(NACL_ARCH(NACL_BUILD_ARCH) == NACL_mips && sig == SIGTRAP))) {
if (DispatchToUntrustedHandler(natp, &sig_ctx)) {
NaClSignalContextToHandler(uc, &sig_ctx);
/* Resume untrusted code using the modified register state. */
return;
}
}
if (g_handler_func != NULL) {
g_handler_func(sig, &sig_ctx, is_untrusted);
return;
}
NaClSignalHandleUntrusted(sig, &sig_ctx, is_untrusted);
FindAndRunHandler(sig, info, uc);
}
/*
* Check that the current process has no signal handlers registered
* that we won't override with safe handlers.
*
* We want to discourage Chrome or libraries from registering signal
* handlers themselves, because those signal handlers are often not
* safe when triggered from untrusted code. For background, see:
* http://code.google.com/p/nativeclient/issues/detail?id=1607
*/
static void AssertNoOtherSignalHandlers(void) {
unsigned int index;
int signum;
char handled_by_nacl[NSIG];
/* 0 is not a valid signal number. */
for (signum = 1; signum < NSIG; signum++) {
handled_by_nacl[signum] = 0;
}
for (index = 0; index < NACL_ARRAY_SIZE(s_Signals); index++) {
signum = s_Signals[index];
CHECK(signum > 0);
CHECK(signum < NSIG);
handled_by_nacl[signum] = 1;
}
for (signum = 1; signum < NSIG; signum++) {
struct sigaction sa;
if (handled_by_nacl[signum])
continue;
if (sigaction(signum, NULL, &sa) != 0) {
/*
* Don't complain if the kernel does not consider signum to be a
* valid signal number, which produces EINVAL.
*/
if (errno != EINVAL) {
NaClLog(LOG_FATAL, "AssertNoOtherSignalHandlers: "
"sigaction() call failed for signal %d: errno=%d\n",
signum, errno);
}
} else {
if ((sa.sa_flags & SA_SIGINFO) == 0) {
if (sa.sa_handler == SIG_DFL || sa.sa_handler == SIG_IGN)
continue;
} else {
/*
* It is not strictly legal for sa_sigaction to contain NULL
* or SIG_IGN, but Valgrind reports SIG_IGN for signal 64, so
* we allow it here.
*/
if (sa.sa_sigaction == NULL ||
sa.sa_sigaction == (void (*)(int, siginfo_t *, void *)) SIG_IGN)
continue;
}
NaClLog(LOG_FATAL, "AssertNoOtherSignalHandlers: "
"A signal handler is registered for signal %d\n", signum);
}
}
}
void NaClSignalHandlerInit(void) {
struct sigaction sa;
unsigned int a;
/*
* Android adds a handler for SIGPIPE in the dynamic linker.
*/
if (NACL_ANDROID)
CHECK(signal(SIGPIPE, SIG_IGN) != SIG_ERR);
AssertNoOtherSignalHandlers();
memset(&sa, 0, sizeof(sa));
sigemptyset(&sa.sa_mask);
sa.sa_sigaction = SignalCatch;
sa.sa_flags = SA_ONSTACK | SA_SIGINFO;
/*
* Mask all signals we catch to prevent re-entry.
*
* In particular, NACL_THREAD_SUSPEND_SIGNAL must be masked while we
* are handling a fault from untrusted code, otherwise the
* suspension signal will interrupt the trusted fault handler. That
* would cause NaClAppThreadGetSuspendedRegisters() to report
* trusted-code register state rather than untrusted-code register
* state from the point where the fault occurred.
*/
for (a = 0; a < NACL_ARRAY_SIZE(s_Signals); a++) {
sigaddset(&sa.sa_mask, s_Signals[a]);
}
/* Install all handlers */
for (a = 0; a < NACL_ARRAY_SIZE(s_Signals); a++) {
if (sigaction(s_Signals[a], &sa, &s_OldActions[a]) != 0) {
NaClLog(LOG_FATAL, "Failed to install handler for %d.\n\tERR:%s\n",
s_Signals[a], strerror(errno));
}
}
}
void NaClSignalHandlerFini(void) {
unsigned int a;
/* Remove all handlers */
for (a = 0; a < NACL_ARRAY_SIZE(s_Signals); a++) {
if (sigaction(s_Signals[a], &s_OldActions[a], NULL) != 0) {
NaClLog(LOG_FATAL, "Failed to unregister handler for %d.\n\tERR:%s\n",
s_Signals[a], strerror(errno));
}
}
}