diff --git a/src/api/environment.cc b/src/api/environment.cc index b50325f0923a69..bb9b6cca9c26eb 100644 --- a/src/api/environment.cc +++ b/src/api/environment.cc @@ -488,7 +488,7 @@ Environment* CreateEnvironment( CHECK(!context.IsEmpty()); Context::Scope context_scope(context); - if (InitializeContextRuntime(context).IsNothing()) { + if (InitializeContextRuntime(context, isolate_data).IsNothing()) { FreeEnvironment(env); return nullptr; } @@ -670,10 +670,16 @@ MaybeLocal GetPerContextExports(Local context, // call NewContext and so they will experience breakages. Local NewContext(Isolate* isolate, Local object_template) { + return NewContext(isolate, object_template, nullptr); +} + +Local NewContext(Isolate* isolate, + Local object_template, + IsolateData* isolate_data) { auto context = Context::New(isolate, nullptr, object_template); if (context.IsEmpty()) return context; - if (InitializeContext(context).IsNothing()) { + if (InitializeContext(context, isolate_data).IsNothing()) { return Local(); } @@ -686,7 +692,8 @@ void ProtoThrower(const FunctionCallbackInfo& info) { // This runs at runtime, regardless of whether the context // is created from a snapshot. -Maybe InitializeContextRuntime(Local context) { +Maybe InitializeContextRuntime(Local context, + IsolateData* isolate_data) { Isolate* isolate = Isolate::GetCurrent(); HandleScope handle_scope(isolate); @@ -698,6 +705,18 @@ Maybe InitializeContextRuntime(Local context) { // to the runtime flags, propagate the value to the embedder data. bool is_code_generation_from_strings_allowed = context->IsCodeGenerationFromStringsAllowed(); + + // Check if the Node.js option --disallow-code-generation-from-strings is set + // Use isolate_data if provided, otherwise fall back to per_process options + if (isolate_data != nullptr && + isolate_data->options()->disallow_code_generation_from_strings) { + is_code_generation_from_strings_allowed = false; + } else if (isolate_data == nullptr && + per_process::cli_options->per_isolate + ->disallow_code_generation_from_strings) { + is_code_generation_from_strings_allowed = false; + } + context->AllowCodeGenerationFromStrings(false); context->SetEmbedderData( ContextEmbedderIndex::kAllowCodeGenerationFromStrings, @@ -922,11 +941,16 @@ Maybe InitializePrimordials(Local context, // This initializes the main context (i.e. vm contexts are not included). Maybe InitializeContext(Local context) { + return InitializeContext(context, nullptr); +} + +Maybe InitializeContext(Local context, + IsolateData* isolate_data) { if (InitializeMainContextForSnapshot(context).IsNothing()) { return Nothing(); } - if (InitializeContextRuntime(context).IsNothing()) { + if (InitializeContextRuntime(context, isolate_data).IsNothing()) { return Nothing(); } return Just(true); diff --git a/src/node.h b/src/node.h index 70b60e79ac2969..e243bf575ffa01 100644 --- a/src/node.h +++ b/src/node.h @@ -567,10 +567,20 @@ NODE_EXTERN v8::Local NewContext( v8::Local object_template = v8::Local()); +// Overload that accepts IsolateData for per-isolate options +v8::Local NewContext( + v8::Isolate* isolate, + v8::Local object_template, + IsolateData* isolate_data); + // Runs Node.js-specific tweaks on an already constructed context // Return value indicates success of operation NODE_EXTERN v8::Maybe InitializeContext(v8::Local context); +// Overload that accepts IsolateData for per-isolate options +v8::Maybe InitializeContext(v8::Local context, + IsolateData* isolate_data); + // If `platform` is passed, it will be used to register new Worker instances. // It can be `nullptr`, in which case creating new Workers inside of // Environments that use this `IsolateData` will not work. diff --git a/src/node_contextify.cc b/src/node_contextify.cc index e66d4fcb0c064f..5dc21f5f4e1593 100644 --- a/src/node_contextify.cc +++ b/src/node_contextify.cc @@ -253,7 +253,7 @@ ContextifyContext* ContextifyContext::New(Local v8_context, // only initialized when needed because even deserializing them slows // things down significantly and they are only needed in rare occasions // in the vm contexts. - if (InitializeContextRuntime(v8_context).IsNothing()) { + if (InitializeContextRuntime(v8_context, env->isolate_data()).IsNothing()) { return {}; } diff --git a/src/node_internals.h b/src/node_internals.h index 6ec17b35cae6c6..26048c1bc6b171 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -116,7 +116,8 @@ std::string GetHumanReadableProcessName(); v8::Maybe InitializeBaseContextForSnapshot( v8::Local context); -v8::Maybe InitializeContextRuntime(v8::Local context); +v8::Maybe InitializeContextRuntime(v8::Local context, + IsolateData* isolate_data); v8::Maybe InitializePrimordials(v8::Local context, IsolateData* isolate_data); v8::MaybeLocal InitializePrivateSymbols( diff --git a/src/node_options.cc b/src/node_options.cc index 687cbd5398535f..ad7b2709551b73 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -1172,7 +1172,7 @@ PerIsolateOptionsParser::PerIsolateOptionsParser( kAllowedInEnvvar); AddOption("--disallow-code-generation-from-strings", "disallow eval and friends", - V8Option{}, + &PerIsolateOptions::disallow_code_generation_from_strings, kAllowedInEnvvar); AddOption("--jitless", "disable runtime allocation of executable memory", diff --git a/src/node_options.h b/src/node_options.h index c9c41ae81b1897..31b3446e27add9 100644 --- a/src/node_options.h +++ b/src/node_options.h @@ -293,6 +293,7 @@ class PerIsolateOptions : public Options { std::string max_old_space_size; int64_t stack_trace_limit = 10; std::string report_signal = "SIGUSR2"; + bool disallow_code_generation_from_strings = false; bool build_snapshot = false; std::string build_snapshot_config; inline EnvironmentOptions* get_per_env_options(); diff --git a/src/node_worker.cc b/src/node_worker.cc index 8d878855706ac1..65330e747f9bb7 100644 --- a/src/node_worker.cc +++ b/src/node_worker.cc @@ -352,13 +352,14 @@ void Worker::Run() { SnapshotData::kNodeBaseContextIndex) .ToLocalChecked(); if (!context.IsEmpty() && - !InitializeContextRuntime(context).IsJust()) { + !InitializeContextRuntime(context, data.isolate_data_.get()).IsJust()) { context = Local(); } } else { Debug( this, "Worker %llu builds context from scratch\n", thread_id_.id); - context = NewContext(isolate_); + context = NewContext(isolate_, Local(), + data.isolate_data_.get()); } if (context.IsEmpty()) { // TODO(joyeecheung): maybe this should be kBootstrapFailure instead? diff --git a/test/parallel/test-worker-disallow-code-generation-from-strings.js b/test/parallel/test-worker-disallow-code-generation-from-strings.js new file mode 100644 index 00000000000000..52605b20674097 --- /dev/null +++ b/test/parallel/test-worker-disallow-code-generation-from-strings.js @@ -0,0 +1,76 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const { Worker } = require('worker_threads'); + +// Test that --disallow-code-generation-from-strings can be passed to workers +// and properly blocks eval() and related code generation functions. + +// Test 1: Worker with --disallow-code-generation-from-strings should block eval +{ + const worker = new Worker(` + const { parentPort } = require('worker_threads'); + try { + eval('"test"'); + parentPort.postMessage({ evalBlocked: false }); + } catch (err) { + parentPort.postMessage({ evalBlocked: true, errorName: err.name }); + } + `, { + eval: true, + execArgv: ['--disallow-code-generation-from-strings'] + }); + + worker.on('message', common.mustCall((msg) => { + assert.strictEqual(msg.evalBlocked, true); + assert.strictEqual(msg.errorName, 'EvalError'); + })); + + worker.on('error', common.mustNotCall()); +} + +// Test 2: Worker without the flag should allow eval +{ + const worker = new Worker(` + const { parentPort } = require('worker_threads'); + try { + const result = eval('"test"'); + parentPort.postMessage({ evalBlocked: false, result }); + } catch (err) { + parentPort.postMessage({ evalBlocked: true }); + } + `, { + eval: true, + execArgv: [] + }); + + worker.on('message', common.mustCall((msg) => { + assert.strictEqual(msg.evalBlocked, false); + assert.strictEqual(msg.result, 'test'); + })); + + worker.on('error', common.mustNotCall()); +} + +// Test 3: Verify the flag also blocks Function constructor +{ + const worker = new Worker(` + const { parentPort } = require('worker_threads'); + try { + new Function('return 42')(); + parentPort.postMessage({ functionBlocked: false }); + } catch (err) { + parentPort.postMessage({ functionBlocked: true, errorName: err.name }); + } + `, { + eval: true, + execArgv: ['--disallow-code-generation-from-strings'] + }); + + worker.on('message', common.mustCall((msg) => { + assert.strictEqual(msg.functionBlocked, true); + assert.strictEqual(msg.errorName, 'EvalError'); + })); + + worker.on('error', common.mustNotCall()); +}