Skip to content

Commit 0b00d5e

Browse files
codebytereaduh95
authored andcommitted
src: let embedders exempt linked bindings from the addon permission
process._linkedBinding() is subject to the permission model's addon scope since the check was added to GetLinkedBinding(). For an embedder that implements part of its runtime as linked bindings this means its own bootstrap cannot reach them under --permission unless the user also passes --allow-addons, which allows loading addons from the file system as well. Add EnvironmentFlags::kNoAddonPermissionForLinkedBindings. When set, GetLinkedBinding() skips the addon permission check for that Environment and the worker threads it creates; process.dlopen() stays gated and the default behavior is unchanged. Refs: #65432 Signed-off-by: Shelley Vohr <shelley.vohr@gmail.com> PR-URL: #66067 Reviewed-By: Chengzhong Wu <legendecas@gmail.com> Reviewed-By: Xuguang Mei <meixuguang@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 27f20dc commit 0b00d5e

6 files changed

Lines changed: 67 additions & 3 deletions

File tree

‎src/env-inl.h‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,10 @@ inline bool Environment::no_browser_globals() const {
697697
#endif
698698
}
699699

700+
inline bool Environment::no_addon_permission_for_linked_bindings() const {
701+
return flags_ & EnvironmentFlags::kNoAddonPermissionForLinkedBindings;
702+
}
703+
700704
void Environment::set_source_maps_enabled(bool on) {
701705
source_maps_enabled_ = on;
702706
}

‎src/env.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -916,6 +916,7 @@ class Environment final : public MemoryRetainer {
916916
inline bool no_global_search_paths() const;
917917
inline bool should_start_debug_signal_handler() const;
918918
inline bool no_browser_globals() const;
919+
inline bool no_addon_permission_for_linked_bindings() const;
919920
inline uint64_t thread_id() const;
920921
inline std::string_view thread_name() const;
921922
inline worker::Worker* worker_context() const;

‎src/node.h‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -651,7 +651,12 @@ enum Flags : uint64_t {
651651
// Controls whether the InspectorAgent created for this Environment waits for
652652
// Inspector frontend events during the Environment creation. It's used to
653653
// call node::Stop(env) on a Worker thread that is waiting for the events.
654-
kNoWaitForInspectorFrontend = 1 << 11
654+
kNoWaitForInspectorFrontend = 1 << 11,
655+
// Set this flag to exempt process._linkedBinding() from the permission
656+
// model's addon scope (--allow-addons): linked bindings are compiled into
657+
// the executable by the embedder, unlike addons loaded from the file system
658+
// through process.dlopen(), which stays gated. Inherited by worker threads.
659+
kNoAddonPermissionForLinkedBindings = 1 << 12
655660
};
656661
} // namespace EnvironmentFlags
657662

‎src/node_binding.cc‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1023,8 +1023,10 @@ void GetLinkedBinding(const FunctionCallbackInfo<Value>& args) {
10231023

10241024
node::Utf8Value module_name_v(env->isolate(), module_name);
10251025
const char* name = *module_name_v;
1026-
THROW_IF_INSUFFICIENT_PERMISSIONS(
1027-
env, permission::PermissionScope::kAddon, module_name_v.ToStringView());
1026+
if (!env->no_addon_permission_for_linked_bindings()) {
1027+
THROW_IF_INSUFFICIENT_PERMISSIONS(
1028+
env, permission::PermissionScope::kAddon, module_name_v.ToStringView());
1029+
}
10281030

10291031
node_module* mod = nullptr;
10301032

‎src/node_worker.cc‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,10 @@ void Worker::New(const FunctionCallbackInfo<Value>& args) {
748748
worker->environment_flags_ |= EnvironmentFlags::kNoGlobalSearchPaths;
749749
if (env->no_browser_globals())
750750
worker->environment_flags_ |= EnvironmentFlags::kNoBrowserGlobals;
751+
if (env->no_addon_permission_for_linked_bindings()) {
752+
worker->environment_flags_ |=
753+
EnvironmentFlags::kNoAddonPermissionForLinkedBindings;
754+
}
751755
}
752756

753757
void Worker::StartThread(const FunctionCallbackInfo<Value>& args) {

‎test/cctest/test_linked_binding.cc‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
#include "node_api.h"
66
#include "node_test_fixture.h"
77

8+
#include <string>
9+
810
void InitializeBinding(v8::Local<v8::Object> exports,
911
v8::Local<v8::Value> module,
1012
v8::Local<v8::Context> context,
@@ -46,6 +48,52 @@ TEST_F(LinkedBindingTest, SimpleTest) {
4648
CHECK_EQ(strcmp(*utf8val, "value"), 0);
4749
}
4850

51+
static std::string RunScript(v8::Isolate* isolate, const char* source) {
52+
v8::Local<v8::Context> context = isolate->GetCurrentContext();
53+
v8::Local<v8::Script> script =
54+
v8::Script::Compile(context,
55+
v8::String::NewFromOneByte(
56+
isolate, reinterpret_cast<const uint8_t*>(source))
57+
.ToLocalChecked())
58+
.ToLocalChecked();
59+
v8::Local<v8::Value> completion_value = script->Run(context).ToLocalChecked();
60+
v8::String::Utf8Value utf8val(isolate, completion_value);
61+
CHECK_NOT_NULL(*utf8val);
62+
return *utf8val;
63+
}
64+
65+
TEST_F(LinkedBindingTest, PermissionModelDeniesLinkedBindingTest) {
66+
const v8::HandleScope handle_scope(isolate_);
67+
const Argv argv;
68+
isolate_data_->options()->per_env->permission = true;
69+
Env test_env{handle_scope, argv};
70+
71+
const char* run_script =
72+
"try { process._linkedBinding('cctest_linkedbinding').key; }"
73+
" catch (err) { err.code; }";
74+
CHECK_EQ(RunScript(isolate_, run_script), "ERR_ACCESS_DENIED");
75+
}
76+
77+
TEST_F(LinkedBindingTest, NoAddonPermissionForLinkedBindingsTest) {
78+
const v8::HandleScope handle_scope(isolate_);
79+
const Argv argv;
80+
isolate_data_->options()->per_env->permission = true;
81+
Env test_env{
82+
handle_scope,
83+
argv,
84+
static_cast<node::EnvironmentFlags::Flags>(
85+
node::EnvironmentFlags::kDefaultFlags |
86+
node::EnvironmentFlags::kNoAddonPermissionForLinkedBindings)};
87+
88+
const char* linked_script =
89+
"process._linkedBinding('cctest_linkedbinding').key";
90+
CHECK_EQ(RunScript(isolate_, linked_script), "value");
91+
const char* dlopen_script =
92+
"try { process.dlopen({ exports: {} }, 'addon.node'); }"
93+
" catch (err) { err.code; }";
94+
CHECK_EQ(RunScript(isolate_, dlopen_script), "ERR_DLOPEN_DISABLED");
95+
}
96+
4997
void InitializeLocalBinding(v8::Local<v8::Object> exports,
5098
v8::Local<v8::Value> module,
5199
v8::Local<v8::Context> context,

0 commit comments

Comments
 (0)