Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,13 @@
#include <unistd.h> // 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 <processthreadsapi.h>
#endif // defined(_WIN32)

#include "absl/synchronization/mutex.h"

// ========== global C++ headers ==========
Expand Down Expand Up @@ -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<IsUserCetAvailableInEnvironment_t>(
::GetProcAddress(kernel32, "IsUserCetAvailableInEnvironment"));
auto fn_get_mitigation_policy =
reinterpret_cast<GetProcessMitigationPolicy_t>(
::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;
Expand Down
8 changes: 8 additions & 0 deletions src/node_options.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions test/parallel/test-cli-node-options.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
43 changes: 43 additions & 0 deletions test/parallel/test-win-cet-maglev-disable.js
Original file line number Diff line number Diff line change
@@ -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}`);
})
);
}