[wasm] Refactor trap handlers to make way for Windows support

In preparing for adding trap-based bounds checking to Windows, this
change refactors the code to separate the platform-specific portions
from that which can be shared between platforms.

Internally, we've renamed `RegisterDefaultSignalHandler` to
`RegisterDefaultTrapHandler` to more accurately represent the
difference in terminology between Linux (signals) and Windows
(exceptions). The external API is left the same so as not to break
downstream clients.

This CL is primarily to make room for Windows support. Future CLs
will begin adding support for Windows.

This is a reincarnation of https://crrev.com/c/626558.

Bug: v8:6743
Cq-Include-Trybots: luci.chromium.try:linux_chromium_rel_ng
Change-Id: Iaa8bfd68c14cd1d17933b12c24cb8dd5ee8a21d6
Reviewed-on: https://chromium-review.googlesource.com/998829
Commit-Queue: Eric Holk <eholk@chromium.org>
Reviewed-by: Brad Nelson <bradnelson@chromium.org>
Cr-Commit-Position: refs/heads/master@{#53006}
This commit is contained in:
Eric Holk (eholk) 2018-05-04 14:44:07 -07:00 committed by Commit Bot
parent a31320f59c
commit bb60967e36
9 changed files with 263 additions and 153 deletions

View File

@ -2289,6 +2289,7 @@ v8_source_set("v8_base") {
"src/transitions-inl.h",
"src/transitions.cc",
"src/transitions.h",
"src/trap-handler/handler-inside.cc",
"src/trap-handler/handler-outside.cc",
"src/trap-handler/handler-shared.cc",
"src/trap-handler/trap-handler-internal.h",
@ -2475,7 +2476,13 @@ v8_source_set("v8_base") {
"src/x64/sse-instr.h",
]
if (is_linux) {
sources += [ "src/trap-handler/handler-inside.cc" ]
sources += [
"src/trap-handler/handler-inside-linux.cc",
"src/trap-handler/handler-outside-linux.cc",
]
}
if (is_win) {
sources += [ "src/trap-handler/handler-outside-win.cc" ]
}
} else if (v8_current_cpu == "arm") {
sources += [ ### gcmole(arch:arm) ###

View File

@ -6017,7 +6017,7 @@ bool V8::TryHandleSignal(int signum, void* info, void* context) {
#endif
bool V8::RegisterDefaultSignalHandler() {
return v8::internal::trap_handler::RegisterDefaultSignalHandler();
return v8::internal::trap_handler::RegisterDefaultTrapHandler();
}
bool V8::EnableWebAssemblyTrapHandler(bool use_v8_signal_handler) {

View File

@ -3318,9 +3318,9 @@ int Shell::Main(int argc, char* argv[]) {
}
if (V8_TRAP_HANDLER_SUPPORTED && i::FLAG_wasm_trap_handler) {
constexpr bool use_default_signal_handler = true;
if (!v8::V8::EnableWebAssemblyTrapHandler(use_default_signal_handler)) {
fprintf(stderr, "Could not register signal handler");
constexpr bool use_default_trap_handler = true;
if (!v8::V8::EnableWebAssemblyTrapHandler(use_default_trap_handler)) {
fprintf(stderr, "Could not register trap handler");
exit(1);
}
}

View File

@ -0,0 +1,133 @@
// 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 <signal.h>
#include <stddef.h>
#include <stdlib.h>
#include "src/trap-handler/trap-handler-internal.h"
#include "src/trap-handler/trap-handler.h"
namespace v8 {
namespace internal {
namespace trap_handler {
bool IsKernelGeneratedSignal(siginfo_t* info) {
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 SigUnmaskStack {
public:
explicit SigUnmaskStack(sigset_t sigs) {
// TODO(eholk): consider using linux-syscall-support for calling this
// syscall.
pthread_sigmask(SIG_UNBLOCK, &sigs, &old_mask_);
}
~SigUnmaskStack() { pthread_sigmask(SIG_SETMASK, &old_mask_, nullptr); }
private:
sigset_t old_mask_;
// We'd normally use DISALLOW_COPY_AND_ASSIGN, but we're avoiding a dependency
// on base/macros.h
SigUnmaskStack(const SigUnmaskStack&) = delete;
void operator=(const SigUnmaskStack&) = delete;
};
bool TryHandleSignal(int signum, siginfo_t* info, ucontext_t* context) {
// Bail out early in case we got called for the wrong kind of signal.
if (signum != SIGSEGV) {
return false;
}
// Make sure the signal was generated by the kernel and not some other source.
if (!IsKernelGeneratedSignal(info)) {
return false;
}
// Ensure the faulting thread was actually running Wasm code.
if (!IsThreadInWasm()) {
return false;
}
// Clear g_thread_in_wasm_code, primarily to protect against nested faults.
g_thread_in_wasm_code = false;
// Begin signal mask scope. We need to be sure to restore the signal mask
// before we restore the g_thread_in_wasm_code flag.
{
// Unmask the signal so that if this signal handler crashes, the crash will
// be handled by the crash reporter. Otherwise, the process might be killed
// with the crash going unreported.
sigset_t sigs;
// Fortunately, sigemptyset and sigaddset are async-signal-safe according to
// the POSIX standard.
sigemptyset(&sigs);
sigaddset(&sigs, SIGSEGV);
SigUnmaskStack unmask(sigs);
uintptr_t fault_addr = context->uc_mcontext.gregs[REG_RIP];
uintptr_t landing_pad = 0;
if (TryFindLandingPad(fault_addr, &landing_pad)) {
// Tell the caller to return to the landing pad.
context->uc_mcontext.gregs[REG_RIP] = landing_pad;
// We will return to wasm code, so restore the g_thread_in_wasm_code flag.
g_thread_in_wasm_code = true;
return true;
}
} // end signal mask scope
// If we get here, it's not a recoverable wasm fault, so we go to the next
// handler. Leave the g_thread_in_wasm_code flag unset since we do not return
// to wasm code.
return false;
}
void HandleSignal(int signum, siginfo_t* info, void* context) {
ucontext_t* uc = reinterpret_cast<ucontext_t*>(context);
if (!TryHandleSignal(signum, info, uc)) {
// Since V8 didn't handle this signal, we want to re-raise the same signal.
// For kernel-generated SEGV signals, we do this by restoring the original
// SEGV handler and then returning. The fault will happen again and the
// usual SEGV 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.
RestoreOriginalSignalHandler();
if (!IsKernelGeneratedSignal(info)) {
raise(signum);
}
}
// TryHandleSignal modifies context to change where we return to.
}
} // namespace trap_handler
} // namespace internal
} // namespace v8

View File

@ -23,10 +23,6 @@
// context. Some additional code is used both inside and outside the signal
// handler. This code can be found in handler-shared.cc.
#include <signal.h>
#include <stddef.h>
#include <stdlib.h>
#include "src/trap-handler/trap-handler-internal.h"
#include "src/trap-handler/trap-handler.h"
@ -34,85 +30,7 @@ namespace v8 {
namespace internal {
namespace trap_handler {
namespace {
bool IsKernelGeneratedSignal(siginfo_t* info) {
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;
}
#if V8_TRAP_HANDLER_SUPPORTED
class SigUnmaskStack {
public:
explicit SigUnmaskStack(sigset_t sigs) {
// TODO(eholk): consider using linux-syscall-support for calling this
// syscall.
pthread_sigmask(SIG_UNBLOCK, &sigs, &old_mask_);
}
~SigUnmaskStack() { pthread_sigmask(SIG_SETMASK, &old_mask_, nullptr); }
private:
sigset_t old_mask_;
// We'd normally use DISALLOW_COPY_AND_ASSIGN, but we're avoiding a dependency
// on base/macros.h
SigUnmaskStack(const SigUnmaskStack&) = delete;
void operator=(const SigUnmaskStack&) = delete;
};
#endif
} // namespace
#if V8_TRAP_HANDLER_SUPPORTED && V8_OS_LINUX
bool TryHandleSignal(int signum, siginfo_t* info, ucontext_t* context) {
// Bail out early in case we got called for the wrong kind of signal.
if (signum != SIGSEGV) {
return false;
}
// Make sure the signal was generated by the kernel and not some other source.
if (!IsKernelGeneratedSignal(info)) {
return false;
}
// Ensure the faulting thread was actually running Wasm code.
if (!IsThreadInWasm()) {
return false;
}
// Clear g_thread_in_wasm_code, primarily to protect against nested faults.
g_thread_in_wasm_code = false;
// Begin signal mask scope. We need to be sure to restore the signal mask
// before we restore the g_thread_in_wasm_code flag.
{
// Unmask the signal so that if this signal handler crashes, the crash will
// be handled by the crash reporter. Otherwise, the process might be killed
// with the crash going unreported.
sigset_t sigs;
// Fortunately, sigemptyset and sigaddset are async-signal-safe according to
// the POSIX standard.
sigemptyset(&sigs);
sigaddset(&sigs, SIGSEGV);
SigUnmaskStack unmask(sigs);
uintptr_t fault_addr = context->uc_mcontext.gregs[REG_RIP];
uintptr_t landing_pad = 0;
if (TryFindLandingPad(fault_addr, &landing_pad)) {
// Tell the caller to return to the landing pad.
context->uc_mcontext.gregs[REG_RIP] = landing_pad;
// We will return to wasm code, so restore the g_thread_in_wasm_code flag.
g_thread_in_wasm_code = true;
return true;
}
} // end signal mask scope
// If we get here, it's not a recoverable wasm fault, so we go to the next
// handler. Leave the g_thread_in_wasm_code flag unset since we do not return
// to wasm code.
return false;
}
// This function contains the platform independent portions of fault
// classification.
@ -154,29 +72,8 @@ bool TryFindLandingPad(uintptr_t fault_addr, uintptr_t* landing_pad) {
}
return false;
}
#endif // V8_TRAP_HANDLER_SUPPORTED && V8_OS_LINUX
#endif // V8_TRAP_HANDLER_SUPPORTED
#if V8_TRAP_HANDLER_SUPPORTED
void HandleSignal(int signum, siginfo_t* info, void* context) {
ucontext_t* uc = reinterpret_cast<ucontext_t*>(context);
if (!TryHandleSignal(signum, info, uc)) {
// Since V8 didn't handle this signal, we want to re-raise the same signal.
// For kernel-generated SEGV signals, we do this by restoring the original
// SEGV handler and then returning. The fault will happen again and the
// usual SEGV 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.
RestoreOriginalSignalHandler();
if (!IsKernelGeneratedSignal(info)) {
raise(signum);
}
}
// TryHandleSignal modifies context to change where we return to.
}
#endif
} // namespace trap_handler
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,73 @@
// 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 support code for the out of bounds signal handler.
// Nothing in here actually runs in the signal handler, but the code here
// manipulates data structures used by the signal handler so we still need to be
// careful. In order to minimize this risk, here are some rules to follow.
//
// 1. Avoid introducing new external dependencies. The files in src/trap-handler
// should be as self-contained as possible to make it easy to audit the code.
//
// 2. Any changes must be reviewed by someone from the crash reporting
// or security team. Se OWNERS for suggested reviewers.
//
// For more information, see https://goo.gl/yMeyUY.
//
// For the code that runs in the signal handler itself, see handler-inside.cc.
#include <signal.h>
#include "src/trap-handler/trap-handler-internal.h"
#include "src/trap-handler/trap-handler.h"
namespace v8 {
namespace internal {
namespace trap_handler {
#if V8_TRAP_HANDLER_SUPPORTED
bool RegisterDefaultTrapHandler() {
CHECK(!g_is_default_signal_handler_registered);
struct sigaction action;
action.sa_sigaction = HandleSignal;
action.sa_flags = SA_SIGINFO;
sigemptyset(&action.sa_mask);
// {sigaction} installs a new custom segfault handler. On success, it returns
// 0. If we get a nonzero value, we report an error to the caller by returning
// false.
if (sigaction(SIGSEGV, &action, &g_old_handler) != 0) {
return false;
}
// Sanitizers often prevent us from installing our own signal handler. Attempt
// to detect this and if so, refuse to enable trap handling.
//
// TODO(chromium:830894): Remove this once all bots support custom signal
// handlers.
#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \
defined(THREAD_SANITIZER) || defined(LEAK_SANITIZER) || \
defined(UNDEFINED_SANITIZER)
struct sigaction installed_handler;
CHECK_EQ(sigaction(SIGSEGV, NULL, &installed_handler), 0);
// If the installed handler does not point to HandleSignal, then
// allow_user_segv_handler is 0.
if (installed_handler.sa_sigaction != HandleSignal) {
printf(
"WARNING: sanitizers are preventing signal handler installation. "
"Trap handlers are disabled.");
return false;
}
#endif
g_is_default_signal_handler_registered = true;
return true;
}
#endif
} // namespace trap_handler
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,35 @@
// 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 support code for the out of bounds signal handler.
// Nothing in here actually runs in the signal handler, but the code here
// manipulates data structures used by the signal handler so we still need to be
// careful. In order to minimize this risk, here are some rules to follow.
//
// 1. Avoid introducing new external dependencies. The files in src/trap-handler
// should be as self-contained as possible to make it easy to audit the code.
//
// 2. Any changes must be reviewed by someone from the crash reporting
// or security team. Se OWNERS for suggested reviewers.
//
// For more information, see https://goo.gl/yMeyUY.
//
// For the code that runs in the signal handler itself, see handler-inside.cc.
namespace v8 {
namespace internal {
namespace trap_handler {
#if V8_TRAP_HANDLER_SUPPORTED
bool RegisterDefaultTrapHandler() {
// Not yet implemented
return false;
}
#endif
} // namespace trap_handler
} // namespace internal
} // namespace v8

View File

@ -236,52 +236,17 @@ void ReleaseHandlerData(int index) {
free(data);
}
bool RegisterDefaultSignalHandler() {
#if V8_TRAP_HANDLER_SUPPORTED
CHECK(!g_is_default_signal_handler_registered);
struct sigaction action;
action.sa_sigaction = HandleSignal;
action.sa_flags = SA_SIGINFO;
sigemptyset(&action.sa_mask);
// {sigaction} installs a new custom segfault handler. On success, it returns
// 0. If we get a nonzero value, we report an error to the caller by returning
// false.
if (sigaction(SIGSEGV, &action, &g_old_handler) != 0) {
return false;
}
// Sanitizers often prevent us from installing our own signal handler. Attempt
// to detect this and if so, refuse to enable trap handling.
//
// TODO(chromium:830894): Remove this once all bots support custom signal
// handlers.
#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \
defined(THREAD_SANITIZER) || defined(LEAK_SANITIZER) || \
defined(UNDEFINED_SANITIZER)
struct sigaction installed_handler;
CHECK_EQ(sigaction(SIGSEGV, NULL, &installed_handler), 0);
// If the installed handler does not point to HandleSignal, then
// allow_user_segv_handler is 0.
if (installed_handler.sa_sigaction != HandleSignal) {
printf(
"WARNING: sanitizers are preventing signal handler installation. "
"Trap handlers are disabled.");
return false;
}
#endif
g_is_default_signal_handler_registered = true;
return true;
#else
return false;
#endif
}
size_t GetRecoveredTrapCount() {
return gRecoveredTrapCount.load(std::memory_order_relaxed);
}
#if !V8_TRAP_HANDLER_SUPPORTED
// This version is provided for systems that do not support trap handlers.
// Otherwise, the correct one should be implemented in the appropriate
// platform-specific handler-outside.cc.
bool RegisterDefaultTrapHandler() { return false; }
#endif
bool g_is_trap_handler_enabled;
bool EnableTrapHandler(bool use_v8_signal_handler) {
@ -289,7 +254,7 @@ bool EnableTrapHandler(bool use_v8_signal_handler) {
return false;
}
if (use_v8_signal_handler) {
g_is_trap_handler_enabled = RegisterDefaultSignalHandler();
g_is_trap_handler_enabled = RegisterDefaultTrapHandler();
return g_is_trap_handler_enabled;
}
g_is_trap_handler_enabled = true;

View File

@ -99,7 +99,7 @@ class ThreadInWasmScope {
~ThreadInWasmScope() { ClearThreadInWasm(); }
};
bool RegisterDefaultSignalHandler();
bool RegisterDefaultTrapHandler();
V8_EXPORT_PRIVATE void RestoreOriginalSignalHandler();
#if V8_OS_LINUX