Commit a600594d authored by Andreas Haas's avatar Andreas Haas Committed by Commit Bot

[wasm] Implement trap handler on Windows

This is the V8 side of the implementation. You can take a look at a
prototype of the Chrome side changes in https://crrev.com/c/1273043.
Chrome could also use V8's default implementation of the trap handler,
see https://crrev.com/c/1290952.

Bug: v8:6743
Cq-Include-Trybots: luci.chromium.try:linux_chromium_rel_ng
Change-Id: I9bb3e717db17a4f30bbb8acfd80a1f6510d463ff
Reviewed-on: https://chromium-review.googlesource.com/c/1283111
Commit-Queue: Andreas Haas <ahaas@chromium.org>
Reviewed-by: 's avatarUlan Degenbaev <ulan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#57117}
parent e0c6671f
......@@ -1528,6 +1528,10 @@ v8_header_set("v8_headers") {
sources += [ "include/v8-wasm-trap-handler-posix.h" ]
}
if (is_win) {
sources += [ "include/v8-wasm-trap-handler-win.h" ]
}
deps = [
":v8_version",
]
......@@ -2758,7 +2762,11 @@ v8_source_set("v8_base") {
]
}
if (is_win) {
sources += [ "src/trap-handler/handler-outside-win.cc" ]
sources += [
"src/trap-handler/handler-inside-win.cc",
"src/trap-handler/handler-inside-win.h",
"src/trap-handler/handler-outside-win.cc",
]
}
} else if (v8_current_cpu == "arm") {
sources += [ ### gcmole(arch:arm) ###
......
// 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.
#ifndef V8_WASM_TRAP_HANDLER_WIN_H_
#define V8_WASM_TRAP_HANDLER_WIN_H_
#include <windows.h>
namespace v8 {
/**
* This function determines whether a memory access violation has been an
* out-of-bounds memory access in WebAssembly. If so, it will modify the
* exception parameter and add a return address where the execution can continue
* after the exception handling, and return true. Otherwise the return value
* will be false.
*
* The parameter to this function corresponds to the one passed to a Windows
* vectored exception handler. Use this function only on Windows.
*
* \param exception An EXCEPTION_POINTERS* as provided to the exception handler.
*/
bool TryHandleWebAssemblyTrapWindows(EXCEPTION_POINTERS* exception);
} // namespace v8
#endif // V8_WASM_TRAP_HANDLER_WIN_H_
......@@ -28,6 +28,7 @@ include_rules = [
"+src/interpreter/setup-interpreter.h",
"-src/trap-handler",
"+src/trap-handler/handler-inside-posix.h",
"+src/trap-handler/handler-inside-win.h",
"+src/trap-handler/trap-handler.h",
"+testing/gtest/include/gtest/gtest_prod.h",
"-src/libplatform",
......
......@@ -106,6 +106,11 @@
#include "src/trap-handler/handler-inside-posix.h"
#endif
#if V8_OS_WIN
#include <windows.h>
#include "src/trap-handler/handler-inside-win.h"
#endif
namespace v8 {
/*
......@@ -5907,6 +5912,15 @@ bool V8::TryHandleSignal(int signum, void* info, void* context) {
}
#endif
#if V8_OS_WIN
bool TryHandleWebAssemblyTrapWindows(EXCEPTION_POINTERS* exception) {
#if V8_TARGET_ARCH_X64
return i::trap_handler::TryHandleWasmTrap(exception);
#endif
return false;
}
#endif
bool V8::RegisterDefaultSignalHandler() {
return v8::internal::trap_handler::RegisterDefaultTrapHandler();
}
......
......@@ -13,5 +13,8 @@ specific_include_rules = {
"+src/base/build_config.h",
"+src/globals.h",
"+src/flags.h",
],
"handler-inside-win.h": [
"+src/base/macros.h",
]
}
// 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 trap handler for
// WebAssembly. Exception 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
// trap 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 an exception
// handler context. Some additional code is used both inside and outside the
// trap handler. This code can be found in handler-shared.cc.
#include "src/trap-handler/handler-inside-win.h"
#include <windows.h>
#include "src/trap-handler/trap-handler-internal.h"
#include "src/trap-handler/trap-handler.h"
namespace v8 {
namespace internal {
namespace trap_handler {
bool TryHandleWasmTrap(EXCEPTION_POINTERS* exception) {
// 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;
const EXCEPTION_RECORD* record = exception->ExceptionRecord;
if (record->ExceptionCode != EXCEPTION_ACCESS_VIOLATION) {
return false;
}
uintptr_t fault_addr = reinterpret_cast<uintptr_t>(record->ExceptionAddress);
uintptr_t landing_pad = 0;
if (TryFindLandingPad(fault_addr, &landing_pad)) {
exception->ContextRecord->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;
}
// 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;
}
LONG HandleWasmTrap(EXCEPTION_POINTERS* exception) {
if (TryHandleWasmTrap(exception)) {
return EXCEPTION_CONTINUE_EXECUTION;
}
return EXCEPTION_CONTINUE_SEARCH;
}
} // namespace trap_handler
} // namespace internal
} // namespace v8
// 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.
#ifndef V8_TRAP_HANDLER_HANDLER_INSIDE_WIN_H_
#define V8_TRAP_HANDLER_HANDLER_INSIDE_WIN_H_
#include <windows.h>
#include "src/base/macros.h"
namespace v8 {
namespace internal {
namespace trap_handler {
LONG WINAPI HandleWasmTrap(EXCEPTION_POINTERS* exception);
// On Windows, asan installs its own exception handler which maps shadow
// memory. Since our exception handler may be executed before the asan exception
// handler, we have to make sure that asan shadow memory is not accessed here.
DISABLE_ASAN bool TryHandleWasmTrap(EXCEPTION_POINTERS* exception);
} // namespace trap_handler
} // namespace internal
} // namespace v8
#endif // V8_TRAP_HANDLER_HANDLER_INSIDE_WIN_H_
......@@ -4,23 +4,23 @@
// PLEASE READ BEFORE CHANGING THIS FILE!
//
// This file implements the out of bounds signal handler for
// WebAssembly. Signal handlers are notoriously difficult to get
// This file implements the out of bounds trap handler for
// WebAssembly. Trap 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.
// trap 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
// This file contains most of the code that actually runs in a trap handler
// context. Some additional code is used both inside and outside the trap
// handler. This code can be found in handler-shared.cc.
#include "src/trap-handler/trap-handler-internal.h"
......@@ -37,11 +37,11 @@ namespace trap_handler {
bool TryFindLandingPad(uintptr_t fault_addr, uintptr_t* landing_pad) {
// TODO(eholk): broad code range check
// Taking locks in a signal handler is risky because a fault in the signal
// handler could lead to a deadlock when attempting to acquire the lock
// again. We guard against this case with g_thread_in_wasm_code. The lock
// may only be taken when not executing Wasm code (an assert in
// MetadataLock's constructor ensures this). This signal handler will bail
// Taking locks in the trap handler is risky because a fault in the trap
// handler itself could lead to a deadlock when attempting to acquire the
// lock again. We guard against this case with g_thread_in_wasm_code. The
// lock may only be taken when not executing Wasm code (an assert in
// MetadataLock's constructor ensures this). The trap handler will bail
// out before trying to take the lock if g_thread_in_wasm_code is not set.
MetadataLock lock_holder;
......
......@@ -75,17 +75,15 @@ bool RegisterDefaultTrapHandler() {
g_is_default_signal_handler_registered = true;
return true;
}
#endif // V8_TRAP_HANDLER_SUPPORTED
void RemoveTrapHandler() {
#if V8_TRAP_HANDLER_SUPPORTED
if (g_is_default_signal_handler_registered) {
if (sigaction(SIGSEGV, &g_old_handler, nullptr) == 0) {
g_is_default_signal_handler_registered = false;
}
}
#endif
}
#endif // V8_TRAP_HANDLER_SUPPORTED
} // namespace trap_handler
} // namespace internal
......
......@@ -4,9 +4,9 @@
// 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
// This file implements the support code for the out of bounds trap handler.
// Nothing in here actually runs in the trap handler, but the code here
// manipulates data structures used by the trap 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
......@@ -17,18 +17,43 @@
//
// For more information, see https://goo.gl/yMeyUY.
//
// For the code that runs in the signal handler itself, see handler-inside.cc.
// For the code that runs in the trap handler itself, see handler-inside.cc.
#include <windows.h>
#include "src/trap-handler/handler-inside-win.h"
#include "src/trap-handler/trap-handler.h"
namespace v8 {
namespace internal {
namespace trap_handler {
#if V8_TRAP_HANDLER_SUPPORTED
namespace {
// A handle to our registered exception handler, so that we can remove it
// again later.
void* g_registered_handler = nullptr;
} // namespace
bool RegisterDefaultTrapHandler() {
// Not yet implemented
return false;
constexpr ULONG first = TRUE;
CHECK_NULL(g_registered_handler);
g_registered_handler = AddVectoredExceptionHandler(first, HandleWasmTrap);
return nullptr != g_registered_handler;
}
#endif
void RemoveTrapHandler() {
if (!g_registered_handler) return;
RemoveVectoredExceptionHandler(g_registered_handler);
g_registered_handler = nullptr;
}
#endif // V8_TRAP_HANDLER_SUPPORTED
} // namespace trap_handler
} // namespace internal
......
......@@ -4,9 +4,9 @@
// 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
// This file implements the support code for the out of bounds trap handler.
// Nothing in here actually runs in the trap handler, but the code here
// manipulates data structures used by the trap 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
......@@ -17,7 +17,7 @@
//
// For more information, see https://goo.gl/yMeyUY.
//
// For the code that runs in the signal handler itself, see handler-inside.cc.
// For the code that runs in the trap handler itself, see handler-inside.cc.
#include <stddef.h>
#include <stdio.h>
......@@ -243,15 +243,17 @@ size_t GetRecoveredTrapCount() {
// Otherwise, the correct one should be implemented in the appropriate
// platform-specific handler-outside.cc.
bool RegisterDefaultTrapHandler() { return false; }
void RemoveTrapHandler() {}
#endif
bool g_is_trap_handler_enabled;
bool EnableTrapHandler(bool use_v8_signal_handler) {
bool EnableTrapHandler(bool use_v8_handler) {
if (!V8_TRAP_HANDLER_SUPPORTED) {
return false;
}
if (use_v8_signal_handler) {
if (use_v8_handler) {
g_is_trap_handler_enabled = RegisterDefaultTrapHandler();
return g_is_trap_handler_enabled;
}
......
......@@ -5,12 +5,12 @@
// PLEASE READ BEFORE CHANGING THIS FILE!
//
// This file contains code that is used both inside and outside the out of
// bounds signal handler. Because this code runs in a signal handler context,
// bounds trap handler. Because this code runs in a trap handler context,
// use extra care when modifying this file. 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.
// trap handler might do.
//
// 2. Any changes must be reviewed by someone from the crash reporting
// or security team. See OWNERS for suggested reviewers.
......
......@@ -16,7 +16,7 @@ namespace v8 {
namespace internal {
namespace trap_handler {
// This describes a chunk of code that the signal handler will be able to handle
// This describes a chunk of code that the trap handler will be able to handle
// faults in. {base} points to the beginning of the chunk, and {size} is the
// number of bytes in the code chunk. The remainder of the struct is a list of
// protected memory access instructions and an offset to a landing pad to handle
......
......@@ -19,6 +19,8 @@ namespace trap_handler {
// TODO(eholk): Support trap handlers on other platforms.
#if V8_TARGET_ARCH_X64 && V8_OS_LINUX && !V8_OS_ANDROID
#define V8_TRAP_HANDLER_SUPPORTED true
#elif V8_TARGET_ARCH_X64 && V8_OS_WIN
#define V8_TRAP_HANDLER_SUPPORTED true
#else
#define V8_TRAP_HANDLER_SUPPORTED false
#endif
......@@ -36,7 +38,7 @@ struct ProtectedInstructionData {
const int kInvalidIndex = -1;
/// Adds the handler data to the place where the signal handler will find it.
/// Adds the handler data to the place where the trap handler will find it.
///
/// This returns a number that can be used to identify the handler data to
/// ReleaseHandlerData, or -1 on failure.
......@@ -61,9 +63,9 @@ void ReleaseHandlerData(int index);
extern bool g_is_trap_handler_enabled;
// Enables trap handling for WebAssembly bounds checks.
//
// use_v8_signal_handler indicates that V8 should install its own signal handler
// use_v8_handler indicates that V8 should install its own handler
// rather than relying on the embedder to do it.
bool EnableTrapHandler(bool use_v8_signal_handler);
bool EnableTrapHandler(bool use_v8_handler);
inline bool IsTrapHandlerEnabled() {
DCHECK_IMPLIES(g_is_trap_handler_enabled, V8_TRAP_HANDLER_SUPPORTED);
......@@ -79,7 +81,10 @@ inline int* GetThreadInWasmThreadLocalAddress() {
return &g_thread_in_wasm_code;
}
inline bool IsThreadInWasm() { return g_thread_in_wasm_code; }
// On Windows, asan installs its own exception handler which maps shadow
// memory. Since our exception handler may be executed before the asan exception
// handler, we have to make sure that asan shadow memory is not accessed here.
DISABLE_ASAN inline bool IsThreadInWasm() { return g_thread_in_wasm_code; }
inline void SetThreadInWasm() {
if (IsTrapHandlerEnabled()) {
......
......@@ -209,7 +209,6 @@ v8_source_set("unittests_sources") {
"wasm/loop-assignment-analysis-unittest.cc",
"wasm/module-decoder-unittest.cc",
"wasm/streaming-decoder-unittest.cc",
"wasm/trap-handler-unittest.cc",
"wasm/wasm-code-manager-unittest.cc",
"wasm/wasm-macro-gen-unittest.cc",
"wasm/wasm-module-builder-unittest.cc",
......@@ -271,6 +270,14 @@ v8_source_set("unittests_sources") {
]
}
if (is_posix) {
sources += [ "wasm/trap-handler-posix-unittest.cc" ]
}
if (is_win) {
sources += [ "wasm/trap-handler-win-unittest.cc" ]
}
configs = [
"../..:external_config",
"../..:internal_config_base",
......
......@@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/trap-handler/trap-handler.h"
#include "include/v8.h"
#include "src/trap-handler/trap-handler.h"
#include "testing/gtest/include/gtest/gtest.h"
#if V8_OS_POSIX
......
// 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.
#include <windows.h>
#include "include/v8.h"
#include "src/allocation.h"
#include "src/base/page-allocator.h"
#include "src/trap-handler/trap-handler.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
#if V8_TRAP_HANDLER_SUPPORTED
bool g_handler_got_executed = false;
// The start address of the virtual memory we use to cause an exception.
i::Address g_start_address;
// When using V8::EnableWebAssemblyTrapHandler, we save the old one to fall back
// on if V8 doesn't handle the exception. This allows tools like ASan to
// register a handler early on during the process startup and still generate
// stack traces on failures.
class ExceptionHandlerFallbackTest : public ::testing::Test {
protected:
void SetUp() override {
// Register this handler as the last handler.
registered_handler_ = AddVectoredExceptionHandler(/*first=*/0, TestHandler);
CHECK_NOT_NULL(registered_handler_);
v8::PageAllocator* page_allocator = i::GetPlatformPageAllocator();
// We only need a single page.
size_t size = page_allocator->AllocatePageSize();
void* hint = page_allocator->GetRandomMmapAddr();
i::VirtualMemory mem(page_allocator, size, hint, size);
g_start_address = mem.address();
// Set the permissions of the memory to no-access.
CHECK(mem.SetPermissions(g_start_address, size,
v8::PageAllocator::kNoAccess));
mem_ = std::move(mem);
}
void WriteToTestMemory(int value) {
*reinterpret_cast<volatile int*>(g_start_address) = value;
}
int ReadFromTestMemory() {
return *reinterpret_cast<volatile int*>(g_start_address);
}
void TearDown() override {
// be a good citizen and remove the exception handler.
ULONG result = RemoveVectoredExceptionHandler(registered_handler_);
CHECK(result);
}
private:
static LONG WINAPI TestHandler(EXCEPTION_POINTERS* exception) {
g_handler_got_executed = true;
v8::PageAllocator* page_allocator = i::GetPlatformPageAllocator();
// Make the allocated memory accessible so that from now on memory accesses
// do not cause an exception anymore.
CHECK(i::SetPermissions(page_allocator, g_start_address,
page_allocator->AllocatePageSize(),
v8::PageAllocator::kReadWrite));
// The memory access should work now, we can continue execution.
return EXCEPTION_CONTINUE_EXECUTION;
}
i::VirtualMemory mem_;
void* registered_handler_;
};
TEST_F(ExceptionHandlerFallbackTest, DoTest) {
constexpr bool use_default_handler = true;
CHECK(v8::V8::EnableWebAssemblyTrapHandler(use_default_handler));
// In the original test setup the test memory is protected against any kind of
// access. Therefore the access here causes an access violation exception,
// which should be caught by the exception handler we install above. In the
// exception handler we change the permission of the test memory to make it
// accessible, and then return from the exception handler to execute the
// memory access again. This time we expect the memory access to work.
constexpr int test_value = 42;
WriteToTestMemory(test_value);
CHECK_EQ(test_value, ReadFromTestMemory());
CHECK(g_handler_got_executed);
v8::internal::trap_handler::RemoveTrapHandler();
}
#endif
} // namespace
......@@ -34,7 +34,8 @@ AUTO_EXCLUDE_PATTERNS = [
] + [
# platform-specific headers
'\\b{}\\b'.format(p) for p in
('win32', 'ia32', 'x64', 'arm', 'arm64', 'mips', 'mips64', 's390', 'ppc')]
('win', 'win32', 'ia32', 'x64', 'arm', 'arm64', 'mips', 'mips64', 's390',
'ppc')]
args = None
def parse_args():
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment