blob: adfb2e9a7aa5de98409e89745f9bf70abd3885af [file] [log] [blame]
// Copyright 2018 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// PLEASE READ BEFORE CHANGING THIS FILE!
//
// This file implements the out of bounds signal handler for
// WebAssembly. Signal handlers are notoriously difficult to get
// right, and getting it wrong can lead to security
// vulnerabilities. In order to minimize this risk, here are some
// rules to follow.
//
// 1. Do not introduce any new external dependencies. This file needs
// to be self contained so it is easy to audit everything that a
// signal handler might do.
//
// 2. Any changes must be reviewed by someone from the crash reporting
// or security team. See OWNERS for suggested reviewers.
//
// For more information, see https://goo.gl/yMeyUY.
//
// This file contains most of the code that actually runs in a signal handler
// context. Some additional code is used both inside and outside the signal
// handler. This code can be found in handler-shared.cc.
#include "src/trap-handler/handler-inside-posix.h"
#include <signal.h>
#if defined(V8_OS_LINUX) || defined(V8_OS_FREEBSD)
#include <ucontext.h>
#elif V8_OS_DARWIN
#include <sys/ucontext.h>
#endif
#include <stddef.h>
#include <stdlib.h>
#include "src/trap-handler/trap-handler-internal.h"
#include "src/trap-handler/trap-handler.h"
#ifdef V8_TRAP_HANDLER_VIA_SIMULATOR
#include "src/trap-handler/trap-handler-simulator.h"
#endif
namespace v8 {
namespace internal {
namespace trap_handler {
#if V8_TRAP_HANDLER_SUPPORTED
#if V8_OS_LINUX && V8_HOST_ARCH_ARM64
#define CONTEXT_REG(reg, REG) &uc->uc_mcontext.regs[REG]
#elif V8_OS_LINUX && (V8_HOST_ARCH_LOONG64 || V8_HOST_ARCH_RISCV64)
#define CONTEXT_REG(reg, REG) &uc->uc_mcontext.__gregs[REG]
#elif V8_OS_LINUX
#define CONTEXT_REG(reg, REG) &uc->uc_mcontext.gregs[REG_##REG]
#elif V8_OS_DARWIN && V8_HOST_ARCH_ARM64
#define CONTEXT_REG(reg, REG) &uc->uc_mcontext->__ss.__x[REG]
#elif V8_OS_DARWIN
#define CONTEXT_REG(reg, REG) &uc->uc_mcontext->__ss.__##reg
#elif V8_OS_FREEBSD
#define CONTEXT_REG(reg, REG) &uc->uc_mcontext.mc_##reg
#else
#error "Unsupported platform."
#endif
#if V8_OS_LINUX && V8_HOST_ARCH_ARM64
#define CONTEXT_PC() &uc->uc_mcontext.pc
#elif V8_OS_DARWIN && V8_HOST_ARCH_ARM64
#define CONTEXT_PC() &uc->uc_mcontext->__ss.__pc
#elif V8_OS_LINUX && V8_HOST_ARCH_LOONG64
#define CONTEXT_PC() &uc->uc_mcontext.__pc
#elif V8_OS_LINUX && V8_HOST_ARCH_RISCV64
#define CONTEXT_PC() &uc->uc_mcontext.__gregs[REG_PC]
#endif
bool IsKernelGeneratedSignal(siginfo_t* info) {
// On macOS, only `info->si_code > 0` is relevant, because macOS leaves
// si_code at its default of 0 for signals that don’t originate in hardware.
// The other conditions are only relevant for Linux.
return info->si_code > 0 && info->si_code != SI_USER &&
info->si_code != SI_QUEUE && info->si_code != SI_TIMER &&
info->si_code != SI_ASYNCIO && info->si_code != SI_MESGQ;
}
class UnmaskOobSignalScope {
public:
UnmaskOobSignalScope() {
sigset_t sigs;
// Fortunately, sigemptyset and sigaddset are async-signal-safe according to
// the POSIX standard.
sigemptyset(&sigs);
sigaddset(&sigs, kOobSignal);
pthread_sigmask(SIG_UNBLOCK, &sigs, &old_mask_);
}
UnmaskOobSignalScope(const UnmaskOobSignalScope&) = delete;
void operator=(const UnmaskOobSignalScope&) = delete;
~UnmaskOobSignalScope() { pthread_sigmask(SIG_SETMASK, &old_mask_, nullptr); }
private:
sigset_t old_mask_;
};
#ifdef V8_TRAP_HANDLER_VIA_SIMULATOR
// This is the address where we continue on a failed "ProbeMemory". It's defined
// in "handler-outside-simulator.cc".
extern char probe_memory_continuation[]
#if V8_OS_DARWIN
asm("_v8_simulator_probe_memory_continuation");
#else
asm("v8_simulator_probe_memory_continuation");
#endif
#endif // V8_TRAP_HANDLER_VIA_SIMULATOR
bool TryHandleSignal(int signum, siginfo_t* info, void* context) {
// Ensure the faulting thread was actually running Wasm code. This should be
// the first check in the trap handler to guarantee that the
// g_thread_in_wasm_code flag is only set in wasm code. Otherwise a later
// signal handler is executed with the flag set.
if (!g_thread_in_wasm_code) return false;
// Clear g_thread_in_wasm_code, primarily to protect against nested faults.
// The only path that resets the flag to true is if we find a landing pad (in
// which case this function returns true). Otherwise we leave the flag unset
// since we do not return to wasm code.
g_thread_in_wasm_code = false;
// Bail out early in case we got called for the wrong kind of signal.
if (signum != kOobSignal) return false;
// Make sure the signal was generated by the kernel and not some other source.
if (!IsKernelGeneratedSignal(info)) return false;
// Check whether the fault should be handled based on the accessed address.
// A fault caused by an access to an address that cannot belong to a Wasm
// memory object should not be handled.
uintptr_t access_addr = reinterpret_cast<uintptr_t>(info->si_addr);
if (!IsAccessedMemoryCovered(access_addr)) return false;
// Unmask the oob signal, which is automatically masked during the execution
// of this handler. This ensures that crashes generated in this function will
// be handled by the crash reporter. Otherwise, the process might be killed
// with the crash going unreported. The scope object makes sure to restore the
// signal mask on return from this function. We put the scope object in a
// separate block to ensure that we restore the signal mask before we restore
// the g_thread_in_wasm_code flag.
{
UnmaskOobSignalScope unmask_oob_signal;
ucontext_t* uc = reinterpret_cast<ucontext_t*>(context);
#if V8_HOST_ARCH_X64
auto* context_ip = CONTEXT_REG(rip, RIP);
#elif V8_HOST_ARCH_ARM64
auto* context_ip = CONTEXT_PC();
#elif V8_HOST_ARCH_LOONG64
auto* context_ip = CONTEXT_PC();
#elif V8_HOST_ARCH_RISCV64
auto* context_ip = CONTEXT_PC();
#else
#error "Unsupported architecture."
#endif
uintptr_t fault_addr = *context_ip;
#ifdef V8_TRAP_HANDLER_VIA_SIMULATOR
// Only handle signals triggered by the load in {ProbeMemory}.
if (fault_addr != reinterpret_cast<uintptr_t>(&ProbeMemory)) {
return false;
}
// The simulated ip will be in the second parameter register (%rsi).
auto* simulated_ip_reg = CONTEXT_REG(rsi, RSI);
if (!IsFaultAddressCovered(*simulated_ip_reg)) return false;
TH_DCHECK(gLandingPad != 0);
auto* return_reg = CONTEXT_REG(rax, RAX);
*return_reg = gLandingPad;
// The fault_address that is set in non-simulator builds here is set in the
// simulator directly.
// Continue at the memory probing continuation.
*context_ip = reinterpret_cast<uintptr_t>(&probe_memory_continuation);
#else
if (!IsFaultAddressCovered(fault_addr)) return false;
TH_DCHECK(gLandingPad != 0);
// Tell the caller to return to the landing pad.
*context_ip = gLandingPad;
#if V8_HOST_ARCH_X64
auto* fault_address_reg = CONTEXT_REG(r10, R10);
#elif V8_HOST_ARCH_ARM64
auto* fault_address_reg = CONTEXT_REG(x16, 16);
#elif V8_HOST_ARCH_LOONG64
auto* fault_address_reg = CONTEXT_REG(t6, 18);
#elif V8_HOST_ARCH_RISCV64
auto* fault_address_reg = CONTEXT_REG(t6, 18);
#else
#error "Unsupported architecture."
#endif
*fault_address_reg = fault_addr;
#endif
}
// We will return to wasm code, so restore the g_thread_in_wasm_code flag.
// This should only be done once the signal is blocked again (outside the
// {UnmaskOobSignalScope}) to ensure that we do not catch a signal we raise
// inside of the handler.
g_thread_in_wasm_code = true;
return true;
}
void HandleSignal(int signum, siginfo_t* info, void* context) {
if (!TryHandleSignal(signum, info, context)) {
// Since V8 didn't handle this signal, we want to re-raise the same signal.
// For kernel-generated signals, we do this by restoring the original
// handler and then returning. The fault will happen again and the usual
// signal handling will happen.
//
// We handle user-generated signals by calling raise() instead. This is for
// completeness. We should never actually see one of these, but just in
// case, we do the right thing.
RemoveTrapHandler();
if (!IsKernelGeneratedSignal(info)) {
raise(signum);
}
}
// TryHandleSignal modifies context to change where we return to.
}
#endif
} // namespace trap_handler
} // namespace internal
} // namespace v8