From 05061544ab7bada371d96f82674f0769af4b1fc3 Mon Sep 17 00:00:00 2001 From: Robo Date: Wed, 18 Feb 2026 12:08:03 +0900 Subject: [PATCH] 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. --- build/fuses/fuses.json5 | 3 +- docs/tutorial/fuses.md | 27 ++++++++++ shell/browser/electron_browser_main_parts.cc | 11 ++++ shell/common/v8_util.cc | 55 ++++++++++++++++++++ shell/common/v8_util.h | 2 + shell/renderer/electron_renderer_client.cc | 55 +++----------------- shell/services/node/node_service.cc | 11 ++++ 7 files changed, 116 insertions(+), 48 deletions(-) diff --git a/build/fuses/fuses.json5 b/build/fuses/fuses.json5 index 3b5916ec7e..1e251db281 100644 --- a/build/fuses/fuses.json5 +++ b/build/fuses/fuses.json5 @@ -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" } diff --git a/docs/tutorial/fuses.md b/docs/tutorial/fuses.md index f5dc6a6ee4..c536c34832 100644 --- a/docs/tutorial/fuses.md +++ b/docs/tutorial/fuses.md @@ -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 diff --git a/shell/browser/electron_browser_main_parts.cc b/shell/browser/electron_browser_main_parts.cc index 834a4ca059..062a1ac703 100644 --- a/shell/browser/electron_browser_main_parts.cc +++ b/shell/browser/electron_browser_main_parts.cc @@ -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( diff --git a/shell/common/v8_util.cc b/shell/common/v8_util.cc index dc01845b39..9574b4085a 100644 --- a/shell/common/v8_util.cc +++ b/shell/common/v8_util.cc @@ -8,6 +8,7 @@ #include #include +#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 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 { /** diff --git a/shell/common/v8_util.h b/shell/common/v8_util.h index 59ba4c633a..eca443eabf 100644 --- a/shell/common/v8_util.h +++ b/shell/common/v8_util.h @@ -30,6 +30,8 @@ v8::Local DeserializeV8Value(v8::Isolate* isolate, v8::Local DeserializeV8Value(v8::Isolate* isolate, base::span data); +void SetUpWebAssemblyTrapHandler(); + namespace util { [[nodiscard]] base::span as_byte_span( diff --git a/shell/renderer/electron_renderer_client.cc b/shell/renderer/electron_renderer_client.cc index bb0be056b4..1866a7347c 100644 --- a/shell/renderer/electron_renderer_client.cc +++ b/shell/renderer/electron_renderer_client.cc @@ -6,10 +6,9 @@ #include -#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( diff --git a/shell/services/node/node_service.cc b/shell/services/node/node_service.cc index ffb7f3c007..c63eea8b38 100644 --- a/shell/services/node/node_service.cc +++ b/shell/services/node/node_service.cc @@ -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(