diff --git a/src/node.cc b/src/node.cc index de21740386cffa..8b08d755744718 100644 --- a/src/node.cc +++ b/src/node.cc @@ -119,6 +119,13 @@ #include // STDIN_FILENO, STDERR_FILENO #endif +#if defined(_WIN32) +// For GetProcessMitigationPolicy(), PROCESS_MITIGATION_USER_SHADOW_STACK_POLICY, +// and IsUserCetAvailableInEnvironment(), used in the CET shadow stack detection +// in InitializeNodeWithArgsInternal(). +#include +#endif // defined(_WIN32) + #include "absl/synchronization/mutex.h" // ========== global C++ headers ========== @@ -867,6 +874,46 @@ static ExitCode InitializeNodeWithArgsInternal( // default value. V8::SetFlagsFromString("--rehash-snapshot"); +#if defined(_WIN32) && !defined(V8_ENABLE_CET_SHADOW_STACK) + // When Windows CET (Control-flow Enforcement Technology) hardware shadow + // stacks are active but Node.js was not compiled with + // V8_ENABLE_CET_SHADOW_STACK support, V8's Maglev deoptimizer does not + // synchronize the hardware shadow stack. This causes a + // STATUS_STACK_BUFFER_OVERRUN (0xC0000409) crash when Maglev JIT-compiled + // code deoptimizes on Windows versions that enforce CET strictly (e.g. + // Windows 11 Insider builds). + // + // Automatically disable the Maglev tier when CET shadow stacks are active. + // TurboFan remains enabled, preserving JIT performance and fetch() + // functionality. The user can explicitly re-enable Maglev with --maglev. + { + using IsUserCetAvailableInEnvironment_t = BOOL(WINAPI*)(DWORD); + using GetProcessMitigationPolicy_t = + BOOL(WINAPI*)(HANDLE, PROCESS_MITIGATION_POLICY, PVOID, SIZE_T); + + HMODULE kernel32 = ::GetModuleHandleW(L"kernel32.dll"); + auto fn_is_cet_available = + reinterpret_cast( + ::GetProcAddress(kernel32, "IsUserCetAvailableInEnvironment")); + auto fn_get_mitigation_policy = + reinterpret_cast( + ::GetProcAddress(kernel32, "GetProcessMitigationPolicy")); + + if (fn_is_cet_available != nullptr && + fn_get_mitigation_policy != nullptr && + fn_is_cet_available(USER_CET_ENVIRONMENT_WIN32_PROCESS)) { + PROCESS_MITIGATION_USER_SHADOW_STACK_POLICY uss_policy{}; + if (fn_get_mitigation_policy(GetCurrentProcess(), + ProcessUserShadowStackPolicy, + &uss_policy, + sizeof(uss_policy)) && + uss_policy.EnableUserShadowStack) { + V8::SetFlagsFromString("--no-maglev"); + } + } + } +#endif // defined(_WIN32) && !defined(V8_ENABLE_CET_SHADOW_STACK) + HandleEnvOptions(per_process::cli_options->per_isolate->per_env); std::string node_options; diff --git a/src/node_options.cc b/src/node_options.cc index d48641ae3ffe07..15665dae221f96 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -1246,6 +1246,14 @@ PerIsolateOptionsParser::PerIsolateOptionsParser( "disable runtime allocation of executable memory", V8Option{}, kAllowedInEnvvar); + AddOption("--maglev", + "enable the Maglev optimizing compiler", + V8Option{}, + kAllowedInEnvvar); + AddOption("--no-maglev", + "disable the Maglev optimizing compiler", + V8Option{}, + kAllowedInEnvvar); AddOption("--report-uncaught-exception", "generate diagnostic report on uncaught exceptions", &PerIsolateOptions::report_uncaught_exception, diff --git a/test/parallel/test-cli-node-options.js b/test/parallel/test-cli-node-options.js index 90c399790f65a4..38ff32e892b885 100644 --- a/test/parallel/test-cli-node-options.js +++ b/test/parallel/test-cli-node-options.js @@ -77,6 +77,8 @@ expect('--abort_on-uncaught_exception', 'B\n'); expect('--disallow-code-generation-from-strings', 'B\n'); expect('--expose-gc', 'B\n'); expect('--jitless', 'B\n'); +expect('--no-maglev', 'B\n'); +expect('--maglev', 'B\n'); expect('--max-old-space-size=0', 'B\n'); expect('--max-semi-space-size=0', 'B\n'); expect('--stack-trace-limit=100', diff --git a/test/parallel/test-win-cet-maglev-disable.js b/test/parallel/test-win-cet-maglev-disable.js new file mode 100644 index 00000000000000..c0f8ff5c3616d8 --- /dev/null +++ b/test/parallel/test-win-cet-maglev-disable.js @@ -0,0 +1,43 @@ +'use strict'; +// Verify that the --no-maglev V8 flag is accepted via NODE_OPTIONS on Windows, +// and that the CET shadow stack auto-detection path compiles correctly. +// +// The actual CET auto-disable behavior (in InitializeNodeWithArgsInternal) can +// only be exercised on hardware that has CET shadow stacks enabled by the OS, +// so that path is covered by manual testing on Windows 11 Insider builds. +// This test covers the observable surface: --no-maglev is accepted via +// NODE_OPTIONS without error, and the process exits cleanly. + +const common = require('../common'); + +if (!common.isWindows) + common.skip('Windows-specific CET / Maglev regression test'); + +if (process.config.variables.node_without_node_options) + common.skip('missing NODE_OPTIONS support'); + +const assert = require('assert'); +const { execFile } = require('child_process'); + +const cases = [ + // --no-maglev must be accepted in NODE_OPTIONS (regression: previously it + // was a pure V8 passthrough not whitelisted for envvar use). + { env: 'NODE_OPTIONS=--no-maglev', desc: '--no-maglev via NODE_OPTIONS' }, + // --maglev must also be accepted so users can explicitly opt back in. + { env: 'NODE_OPTIONS=--maglev', desc: '--maglev via NODE_OPTIONS' }, +]; + +for (const { env: envStr, desc } of cases) { + const [key, value] = envStr.split('='); + const env = { ...process.env, [key]: value }; + + execFile( + process.execPath, + ['-e', 'process.exitCode = 0;'], + { env }, + common.mustCall((err, stdout, stderr) => { + assert.ifError(err); + assert.strictEqual(stderr, '', `${desc}: unexpected stderr: ${stderr}`); + }) + ); +}