feat: enable WASM trap handlers in all Node.js processes (#48983)

fix: enable WASM trap handlers in all Node.js processes

```
Original reason for revert:

Some apps started throwing exception on startup
https://github.com/electron/electron/issues/48956
```

We now move the trap handler registeration before
any user script execution. Add a fuse to support
disabling the feature is application needs to run
in memory constrained environments.
This commit is contained in:
Robo
2026-02-18 12:08:03 +09:00
committed by GitHub
parent 10566c2d5f
commit 05061544ab
7 changed files with 116 additions and 48 deletions

View File

@@ -9,5 +9,6 @@
"embedded_asar_integrity_validation": "0",
"only_load_app_from_asar": "0",
"load_browser_process_specific_v8_snapshot": "0",
"grant_file_protocol_extra_privileges": "1"
"grant_file_protocol_extra_privileges": "1",
"wasm_trap_handlers": "1"
}

View File

@@ -137,6 +137,33 @@ The extra privileges granted to the `file://` protocol by this fuse are incomple
* `file://` protocol pages have universal access granted to child frames also running on `file://`
protocols regardless of sandbox settings
### `wasmTrapHandlers`
**Default:** Enabled
**@electron/fuses:** `FuseV1Options.WasmTrapHandlers`
The `wasmTrapHandlers` fuse controls whether V8 will use signal handlers to trap Out of Bounds memory
access from WebAssembly. The feature works by surrounding the WebAssembly memory with large guard regions
and then installing a signal handler that traps attempt to access memory in the guard region. The feature
is only supported on the following 64-bit systems.
Linux. MacOS, Windows - x86_64
Linux, MacOS - aarch64
| Guard Pages | WASM heap | Guard Pages |
|-----8GB-----| |-----8GB-----|
When the fuse is disabled V8 will use explicit bound checks in the generated WebAssembly code to ensure
memory safety. However, this method has some downsides
* The compiler generates extra nodes for each memory reference, leading to longer compile times due to the
additional processing time needed for these nodes.
* In turn, these extra nodes lead to lots of extra code being generated, making WebAssembly modules bigger
than they ideally should be.
* This extra code, particularly the compare and branch before every memory reference,
incurs a significant runtime cost.
## How do I flip fuses?
### The easy way

View File

@@ -41,6 +41,7 @@
#include "content/public/common/process_type.h"
#include "content/public/common/result_codes.h"
#include "electron/buildflags/buildflags.h"
#include "electron/fuses.h"
#include "media/base/localized_strings.h"
#include "services/network/public/cpp/features.h"
#include "services/tracing/public/cpp/stack_sampling/tracing_sampler_profiler.h"
@@ -62,6 +63,7 @@
#include "shell/common/logging.h"
#include "shell/common/node_bindings.h"
#include "shell/common/node_includes.h"
#include "shell/common/v8_util.h"
#include "ui/base/idle/idle.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/ui_base_switches.h"
@@ -238,6 +240,15 @@ void ElectronBrowserMainParts::PostEarlyInitialization() {
v8::Isolate* const isolate = js_env_->isolate();
v8::HandleScope scope(isolate);
// Enable trap handlers before user script execution.
#if ((BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)) && \
defined(ARCH_CPU_X86_64)) || \
((BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC)) && defined(ARCH_CPU_ARM64))
if (electron::fuses::IsWasmTrapHandlersEnabled()) {
electron::SetUpWebAssemblyTrapHandler();
}
#endif
node_bindings_->Initialize(isolate, isolate->GetCurrentContext());
// Create the global environment.
node_env_ = node_bindings_->CreateEnvironment(

View File

@@ -8,6 +8,7 @@
#include <utility>
#include <vector>
#include "base/base_switches.h"
#include "base/memory/raw_ptr.h"
#include "gin/converter.h"
#include "shell/common/api/electron_api_native_image.h"
@@ -17,6 +18,15 @@
#include "ui/gfx/image/image_skia.h"
#include "v8/include/v8.h"
#if BUILDFLAG(IS_LINUX) && (defined(ARCH_CPU_X86_64) || defined(ARCH_CPU_ARM64))
#define ENABLE_WEB_ASSEMBLY_TRAP_HANDLER_LINUX
#include "base/command_line.h"
#include "base/debug/stack_trace.h"
#include "components/crash/core/app/crashpad.h" // nogncheck
#include "content/public/common/content_switches.h"
#include "v8/include/v8-wasm-trap-handler-posix.h"
#endif
namespace electron {
namespace {
@@ -240,6 +250,51 @@ v8::Local<v8::Value> DeserializeV8Value(v8::Isolate* isolate,
return V8Deserializer(isolate, data).Deserialize();
}
void SetUpWebAssemblyTrapHandler() {
#if BUILDFLAG(IS_WIN)
// On Windows we use the default trap handler provided by V8.
v8::V8::EnableWebAssemblyTrapHandler(true);
#elif BUILDFLAG(IS_MAC)
// On macOS, Crashpad uses exception ports to handle signals in a
// different process. As we cannot just pass a callback to this other
// process, we ask V8 to install its own signal handler to deal with
// WebAssembly traps.
v8::V8::EnableWebAssemblyTrapHandler(true);
#elif defined(ENABLE_WEB_ASSEMBLY_TRAP_HANDLER_LINUX)
const bool crash_reporter_enabled =
crash_reporter::GetHandlerSocket(nullptr, nullptr);
if (crash_reporter_enabled) {
// If either --enable-crash-reporter or --enable-crash-reporter-for-testing
// is enabled it should take care of signal handling for us, use the default
// implementation which doesn't register an additional handler.
v8::V8::EnableWebAssemblyTrapHandler(false);
return;
}
const bool use_v8_default_handler =
base::CommandLine::ForCurrentProcess()->HasSwitch(
::switches::kDisableInProcessStackTraces);
if (use_v8_default_handler) {
// There is no signal handler yet, but it's okay if v8 registers one.
v8::V8::EnableWebAssemblyTrapHandler(/*use_v8_signal_handler=*/true);
return;
}
if (base::debug::SetStackDumpFirstChanceCallback(
v8::TryHandleWebAssemblyTrapPosix)) {
// Crashpad and Breakpad are disabled, but the in-process stack dump
// handlers are enabled, so set the callback on the stack dump handlers.
v8::V8::EnableWebAssemblyTrapHandler(/*use_v8_signal_handler=*/false);
return;
}
// As the registration of the callback failed, we don't enable trap
// handlers.
#endif
}
namespace util {
/**

View File

@@ -30,6 +30,8 @@ v8::Local<v8::Value> DeserializeV8Value(v8::Isolate* isolate,
v8::Local<v8::Value> DeserializeV8Value(v8::Isolate* isolate,
base::span<const uint8_t> data);
void SetUpWebAssemblyTrapHandler();
namespace util {
[[nodiscard]] base::span<uint8_t> as_byte_span(

View File

@@ -6,10 +6,9 @@
#include <algorithm>
#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/debug/stack_trace.h"
#include "content/public/renderer/render_frame.h"
#include "electron/fuses.h"
#include "net/http/http_request_headers.h"
#include "shell/common/api/electron_bindings.h"
#include "shell/common/gin_helper/dictionary.h"
@@ -18,6 +17,7 @@
#include "shell/common/node_includes.h"
#include "shell/common/node_util.h"
#include "shell/common/options_switches.h"
#include "shell/common/v8_util.h"
#include "shell/renderer/electron_render_frame_observer.h"
#include "shell/renderer/web_worker_observer.h"
#include "third_party/blink/public/common/web_preferences/web_preferences.h"
@@ -26,13 +26,6 @@
#include "third_party/blink/renderer/core/execution_context/execution_context.h" // nogncheck
#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h" // nogncheck
#if BUILDFLAG(IS_LINUX) && (defined(ARCH_CPU_X86_64) || defined(ARCH_CPU_ARM64))
#define ENABLE_WEB_ASSEMBLY_TRAP_HANDLER_LINUX
#include "components/crash/core/app/crashpad.h" // nogncheck
#include "content/public/common/content_switches.h"
#include "v8/include/v8-wasm-trap-handler-posix.h"
#endif
namespace electron {
ElectronRendererClient::ElectronRendererClient()
@@ -247,45 +240,13 @@ void ElectronRendererClient::WillDestroyWorkerContextOnWorkerThread(
}
void ElectronRendererClient::SetUpWebAssemblyTrapHandler() {
// See CL:5372409 - copied from ShellContentRendererClient.
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
// Mac and Windows use the default implementation (where the default v8 trap
// handler gets set up).
ContentRendererClient::SetUpWebAssemblyTrapHandler();
return;
#elif defined(ENABLE_WEB_ASSEMBLY_TRAP_HANDLER_LINUX)
const bool crash_reporter_enabled =
crash_reporter::GetHandlerSocket(nullptr, nullptr);
if (crash_reporter_enabled) {
// If either --enable-crash-reporter or --enable-crash-reporter-for-testing
// is enabled it should take care of signal handling for us, use the default
// implementation which doesn't register an additional handler.
ContentRendererClient::SetUpWebAssemblyTrapHandler();
return;
#if ((BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)) && \
defined(ARCH_CPU_X86_64)) || \
((BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC)) && defined(ARCH_CPU_ARM64))
if (electron::fuses::IsWasmTrapHandlersEnabled()) {
electron::SetUpWebAssemblyTrapHandler();
}
const bool use_v8_default_handler =
base::CommandLine::ForCurrentProcess()->HasSwitch(
::switches::kDisableInProcessStackTraces);
if (use_v8_default_handler) {
// There is no signal handler yet, but it's okay if v8 registers one.
v8::V8::EnableWebAssemblyTrapHandler(/*use_v8_signal_handler=*/true);
return;
}
if (base::debug::SetStackDumpFirstChanceCallback(
v8::TryHandleWebAssemblyTrapPosix)) {
// Crashpad and Breakpad are disabled, but the in-process stack dump
// handlers are enabled, so set the callback on the stack dump handlers.
v8::V8::EnableWebAssemblyTrapHandler(/*use_v8_signal_handler=*/false);
return;
}
// As the registration of the callback failed, we don't enable trap
// handlers.
#endif // defined(ENABLE_WEB_ASSEMBLY_TRAP_HANDLER_LINUX)
#endif
}
node::Environment* ElectronRendererClient::GetEnvironment(

View File

@@ -11,6 +11,7 @@
#include "base/no_destructor.h"
#include "base/process/process.h"
#include "base/strings/utf_string_conversions.h"
#include "electron/fuses.h"
#include "electron/mas.h"
#include "net/base/network_change_notifier.h"
#include "services/network/public/cpp/wrapper_shared_url_loader_factory.h"
@@ -22,6 +23,7 @@
#include "shell/common/gin_helper/dictionary.h"
#include "shell/common/node_bindings.h"
#include "shell/common/node_includes.h"
#include "shell/common/v8_util.h"
#include "shell/services/node/parent_port.h"
#if !IS_MAS_BUILD()
@@ -130,6 +132,15 @@ void NodeService::Initialize(
v8::Isolate* const isolate = js_env_->isolate();
v8::HandleScope scope{isolate};
// Enable trap handlers before user script execution.
#if ((BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)) && \
defined(ARCH_CPU_X86_64)) || \
((BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC)) && defined(ARCH_CPU_ARM64))
if (electron::fuses::IsWasmTrapHandlersEnabled()) {
electron::SetUpWebAssemblyTrapHandler();
}
#endif
node_bindings_->Initialize(isolate, isolate->GetCurrentContext());
network_change_notifier_ = net::NetworkChangeNotifier::CreateIfNeeded(