diff --git a/src/env-inl.h b/src/env-inl.h index a194751e8b3..1423767e398 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -697,6 +697,10 @@ inline bool Environment::no_browser_globals() const { #endif } +inline bool Environment::no_addon_permission_for_linked_bindings() const { + return flags_ & EnvironmentFlags::kNoAddonPermissionForLinkedBindings; +} + void Environment::set_source_maps_enabled(bool on) { source_maps_enabled_ = on; } diff --git a/src/env.h b/src/env.h index cb701b197df..751e3acec59 100644 --- a/src/env.h +++ b/src/env.h @@ -916,6 +916,7 @@ class Environment final : public MemoryRetainer { inline bool no_global_search_paths() const; inline bool should_start_debug_signal_handler() const; inline bool no_browser_globals() const; + inline bool no_addon_permission_for_linked_bindings() const; inline uint64_t thread_id() const; inline std::string_view thread_name() const; inline worker::Worker* worker_context() const; diff --git a/src/node.h b/src/node.h index ab5a542270c..ca81c4818b4 100644 --- a/src/node.h +++ b/src/node.h @@ -651,7 +651,12 @@ enum Flags : uint64_t { // Controls whether the InspectorAgent created for this Environment waits for // Inspector frontend events during the Environment creation. It's used to // call node::Stop(env) on a Worker thread that is waiting for the events. - kNoWaitForInspectorFrontend = 1 << 11 + kNoWaitForInspectorFrontend = 1 << 11, + // Set this flag to exempt process._linkedBinding() from the permission + // model's addon scope (--allow-addons): linked bindings are compiled into + // the executable by the embedder, unlike addons loaded from the file system + // through process.dlopen(), which stays gated. Inherited by worker threads. + kNoAddonPermissionForLinkedBindings = 1 << 12 }; } // namespace EnvironmentFlags diff --git a/src/node_binding.cc b/src/node_binding.cc index b93e4200f29..568325e8496 100644 --- a/src/node_binding.cc +++ b/src/node_binding.cc @@ -1024,8 +1024,10 @@ void GetLinkedBinding(const FunctionCallbackInfo& args) { node::Utf8Value module_name_v(env->isolate(), module_name); const char* name = *module_name_v; - THROW_IF_INSUFFICIENT_PERMISSIONS( - env, permission::PermissionScope::kAddon, module_name_v.ToStringView()); + if (!env->no_addon_permission_for_linked_bindings()) { + THROW_IF_INSUFFICIENT_PERMISSIONS( + env, permission::PermissionScope::kAddon, module_name_v.ToStringView()); + } node_module* mod = nullptr; diff --git a/src/node_worker.cc b/src/node_worker.cc index 9de1a9e2576..728671331d1 100644 --- a/src/node_worker.cc +++ b/src/node_worker.cc @@ -743,6 +743,10 @@ void Worker::New(const FunctionCallbackInfo& args) { worker->environment_flags_ |= EnvironmentFlags::kNoGlobalSearchPaths; if (env->no_browser_globals()) worker->environment_flags_ |= EnvironmentFlags::kNoBrowserGlobals; + if (env->no_addon_permission_for_linked_bindings()) { + worker->environment_flags_ |= + EnvironmentFlags::kNoAddonPermissionForLinkedBindings; + } } void Worker::StartThread(const FunctionCallbackInfo& args) { diff --git a/test/cctest/test_linked_binding.cc b/test/cctest/test_linked_binding.cc index be097abd90b..d36b5874e74 100644 --- a/test/cctest/test_linked_binding.cc +++ b/test/cctest/test_linked_binding.cc @@ -5,6 +5,8 @@ #include "node_api.h" #include "node_test_fixture.h" +#include + void InitializeBinding(v8::Local exports, v8::Local module, v8::Local context, @@ -46,6 +48,52 @@ TEST_F(LinkedBindingTest, SimpleTest) { CHECK_EQ(strcmp(*utf8val, "value"), 0); } +static std::string RunScript(v8::Isolate* isolate, const char* source) { + v8::Local context = isolate->GetCurrentContext(); + v8::Local script = + v8::Script::Compile(context, + v8::String::NewFromOneByte( + isolate, reinterpret_cast(source)) + .ToLocalChecked()) + .ToLocalChecked(); + v8::Local completion_value = script->Run(context).ToLocalChecked(); + v8::String::Utf8Value utf8val(isolate, completion_value); + CHECK_NOT_NULL(*utf8val); + return *utf8val; +} + +TEST_F(LinkedBindingTest, PermissionModelDeniesLinkedBindingTest) { + const v8::HandleScope handle_scope(isolate_); + const Argv argv; + isolate_data_->options()->per_env->permission = true; + Env test_env{handle_scope, argv}; + + const char* run_script = + "try { process._linkedBinding('cctest_linkedbinding').key; }" + " catch (err) { err.code; }"; + CHECK_EQ(RunScript(isolate_, run_script), "ERR_ACCESS_DENIED"); +} + +TEST_F(LinkedBindingTest, NoAddonPermissionForLinkedBindingsTest) { + const v8::HandleScope handle_scope(isolate_); + const Argv argv; + isolate_data_->options()->per_env->permission = true; + Env test_env{ + handle_scope, + argv, + static_cast( + node::EnvironmentFlags::kDefaultFlags | + node::EnvironmentFlags::kNoAddonPermissionForLinkedBindings)}; + + const char* linked_script = + "process._linkedBinding('cctest_linkedbinding').key"; + CHECK_EQ(RunScript(isolate_, linked_script), "value"); + const char* dlopen_script = + "try { process.dlopen({ exports: {} }, 'addon.node'); }" + " catch (err) { err.code; }"; + CHECK_EQ(RunScript(isolate_, dlopen_script), "ERR_DLOPEN_DISABLED"); +} + void InitializeLocalBinding(v8::Local exports, v8::Local module, v8::Local context,