diff --git a/lib/.eslintrc.yaml b/lib/.eslintrc.yaml index deea6bf4fb05ae..2b62bfb0eca38f 100644 --- a/lib/.eslintrc.yaml +++ b/lib/.eslintrc.yaml @@ -93,3 +93,4 @@ globals: module: false internalBinding: false primordials: false + runInPrivilegedScope: false diff --git a/lib/internal/bootstrap/loaders.js b/lib/internal/bootstrap/loaders.js index cae3200e677926..b995d6acd58df8 100644 --- a/lib/internal/bootstrap/loaders.js +++ b/lib/internal/bootstrap/loaders.js @@ -41,7 +41,8 @@ // This file is compiled as if it's wrapped in a function with arguments // passed by node::RunBootstrapping() -/* global process, getLinkedBinding, getInternalBinding, primordials */ +/* global process, getLinkedBinding, getInternalBinding, primordials, + runInPrivilegedScope */ const { ArrayPrototypeMap, @@ -280,7 +281,13 @@ class NativeModule { requireWithFallbackInDeps : nativeModuleRequire; const fn = compileFunction(id); - fn(this.exports, requireFn, this, process, internalBinding, primordials); + fn(this.exports, + requireFn, + this, + process, + internalBinding, + primordials, + runInPrivilegedScope); this.loaded = true; } finally { diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js index 8077c462983154..23efd1c9fabc47 100644 --- a/lib/internal/bootstrap/node.js +++ b/lib/internal/bootstrap/node.js @@ -33,8 +33,9 @@ 'use strict'; // This file is compiled as if it's wrapped in a function with arguments -// passed by node::RunBootstrapping() -/* global process, require, internalBinding, primordials */ +// passed by node::RunBootstrapping(): +// global process, require, internalBinding, primordials, +// runInPriviledgedScope setupPrepareStackTrace(); diff --git a/lib/internal/bootstrap/pre_execution.js b/lib/internal/bootstrap/pre_execution.js index 381a54d489fb0f..b9bd4ebe0ce645 100644 --- a/lib/internal/bootstrap/pre_execution.js +++ b/lib/internal/bootstrap/pre_execution.js @@ -356,6 +356,7 @@ function initializeClusterIPC() { function initializePolicy() { const experimentalPolicy = getOptionValue('--experimental-policy'); + const { setup, check, deny } = require('internal/process/policy'); if (experimentalPolicy) { process.emitWarning('Policies are experimental.', 'ExperimentalWarning'); @@ -398,9 +399,16 @@ function initializePolicy() { throw new ERR_MANIFEST_ASSERT_INTEGRITY(manifestURL, realIntegrities); } } - require('internal/process/policy') - .setup(src, manifestURL.href); + setup(src, manifestURL.href); } + ObjectDefineProperty(process, 'policy', { + enumerable: true, + configurable: false, + value: { + deny, + check, + } + }); } function initializeWASI() { diff --git a/lib/internal/process/policy.js b/lib/internal/process/policy.js index ea283a449742fc..14acd7a69479f6 100644 --- a/lib/internal/process/policy.js +++ b/lib/internal/process/policy.js @@ -8,7 +8,12 @@ const { const { ERR_MANIFEST_TDZ, + ERR_INVALID_ARG_TYPE, + ERR_INVALID_ARG_VALUE, } = require('internal/errors').codes; + +const policy = internalBinding('policy'); + const { Manifest } = require('internal/policy/manifest'); let manifest; let manifestSrc; @@ -57,5 +62,29 @@ module.exports = ObjectFreeze({ assertIntegrity(moduleURL, content) { this.manifest.assertIntegrity(moduleURL, content); + }, + + deny(permissions) { + if (typeof permissions !== 'string') + throw new ERR_INVALID_ARG_TYPE('permissions', 'string', permissions); + const ret = policy.deny(permissions); + if (ret === undefined) + throw new ERR_INVALID_ARG_VALUE('permissions', permissions); + }, + + fastCheck(permission) { + // This should only be used by internal code. Skips explicit + // type checking to improve performance. The permission + // argument must be a Int32 + return policy.fastCheck(permission); + }, + + check(permissions) { + if (typeof permissions !== 'string') + throw new ERR_INVALID_ARG_TYPE('permission', 'string', permissions); + const ret = policy.check(permissions); + if (ret === undefined) + throw new ERR_INVALID_ARG_VALUE('permissions', permissions); + return ret; } }); diff --git a/lib/internal/test/policy.js b/lib/internal/test/policy.js new file mode 100644 index 00000000000000..786c21c9a8baaf --- /dev/null +++ b/lib/internal/test/policy.js @@ -0,0 +1,7 @@ +'use strict'; + +process.emitWarning( + 'These APIs are for internal testing only. Do not use them.', + 'internal/test/policy'); + +module.exports = { runInPrivilegedScope }; diff --git a/node.gyp b/node.gyp index 865a7de93176e5..441b42c62c292f 100644 --- a/node.gyp +++ b/node.gyp @@ -220,6 +220,7 @@ 'lib/internal/source_map/source_map.js', 'lib/internal/source_map/source_map_cache.js', 'lib/internal/test/binding.js', + 'lib/internal/test/policy.js', 'lib/internal/timers.js', 'lib/internal/tls.js', 'lib/internal/trace_events_async_hooks.js', @@ -660,6 +661,7 @@ 'src/stream_wrap.cc', 'src/string_bytes.cc', 'src/string_decoder.cc', + 'src/policy/policy.cc', 'src/tcp_wrap.cc', 'src/timers.cc', 'src/timer_wrap.cc', @@ -735,6 +737,7 @@ 'src/node_perf.h', 'src/node_perf_common.h', 'src/node_platform.h', + 'src/policy/policy.h', 'src/node_process.h', 'src/node_report.h', 'src/node_revert.h', diff --git a/src/env-inl.h b/src/env-inl.h index cd9f6daaaff758..ab1a74e8d012da 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -246,6 +246,19 @@ inline size_t Environment::async_callback_scope_depth() const { return async_callback_scope_depth_; } +inline void Environment::set_in_privileged_scope(bool on) { + if (on) + in_privileged_scope_++; + else { + CHECK_GT(in_privileged_scope_, 0); + in_privileged_scope_--; + } +} + +inline bool Environment::in_privileged_scope() const { + return in_privileged_scope_ > 0; +} + inline void Environment::PushAsyncCallbackScope() { async_callback_scope_depth_++; } diff --git a/src/env.h b/src/env.h index a0d59ff8728deb..1b3e52209d2f7e 100644 --- a/src/env.h +++ b/src/env.h @@ -375,6 +375,7 @@ constexpr size_t kFsStatsBufferLength = V(replacement_string, "replacement") \ V(require_string, "require") \ V(retry_string, "retry") \ + V(run_in_privileged_scope_string, "runInPrivilegedScope") \ V(scheme_string, "scheme") \ V(scopeid_string, "scopeid") \ V(serial_number_string, "serialNumber") \ @@ -547,6 +548,7 @@ constexpr size_t kFsStatsBufferLength = V(primordials, v8::Object) \ V(promise_hook_handler, v8::Function) \ V(promise_reject_callback, v8::Function) \ + V(run_in_privileged_scope, v8::Function) \ V(script_data_constructor_function, v8::Function) \ V(source_map_cache_getter, v8::Function) \ V(tick_callback_function, v8::Function) \ @@ -977,6 +979,7 @@ class Environment : public MemoryRetainer { v8::MaybeLocal BootstrapInternalLoaders(); v8::MaybeLocal BootstrapNode(); v8::MaybeLocal RunBootstrapping(); + bool BootstrapPrivilegedAccessContext(); inline size_t async_callback_scope_depth() const; inline void PushAsyncCallbackScope(); @@ -1183,6 +1186,9 @@ class Environment : public MemoryRetainer { inline node_module* extra_linked_bindings_head(); inline const Mutex& extra_linked_bindings_mutex() const; + inline void set_in_privileged_scope(bool on = true); + inline bool in_privileged_scope() const; + inline bool filehandle_close_warning() const; inline void set_filehandle_close_warning(bool on); @@ -1418,6 +1424,8 @@ class Environment : public MemoryRetainer { size_t async_callback_scope_depth_ = 0; std::vector destroy_async_id_list_; + size_t in_privileged_scope_ = 0; + #if HAVE_INSPECTOR std::unique_ptr coverage_connection_; std::unique_ptr cpu_profiler_connection_; diff --git a/src/node.cc b/src/node.cc index c3f423cb579479..2fae33be3cd62e 100644 --- a/src/node.cc +++ b/src/node.cc @@ -39,6 +39,7 @@ #include "node_revert.h" #include "node_v8_platform-inl.h" #include "node_version.h" +#include "policy/policy.h" #if HAVE_OPENSSL #include "allocated_buffer-inl.h" // Inlined functions needed by node_crypto.h @@ -294,6 +295,16 @@ void Environment::InitializeDiagnostics() { #endif } +bool Environment::BootstrapPrivilegedAccessContext() { + Local run_in_privileged_scope; + MaybeLocal maybe_run_in_privileged_scope = + Function::New(context(), policy::RunInPrivilegedScope); + if (!maybe_run_in_privileged_scope.ToLocal(&run_in_privileged_scope)) + return false; + set_run_in_privileged_scope(run_in_privileged_scope); + return true; +} + MaybeLocal Environment::BootstrapInternalLoaders() { EscapableHandleScope scope(isolate_); @@ -302,7 +313,8 @@ MaybeLocal Environment::BootstrapInternalLoaders() { process_string(), FIXED_ONE_BYTE_STRING(isolate_, "getLinkedBinding"), FIXED_ONE_BYTE_STRING(isolate_, "getInternalBinding"), - primordials_string()}; + primordials_string(), + run_in_privileged_scope_string()}; std::vector> loaders_args = { process_object(), NewFunctionTemplate(binding::GetLinkedBinding) @@ -311,7 +323,8 @@ MaybeLocal Environment::BootstrapInternalLoaders() { NewFunctionTemplate(binding::GetInternalBinding) ->GetFunction(context()) .ToLocalChecked(), - primordials()}; + primordials(), + run_in_privileged_scope()}; // Bootstrap internal loaders Local loader_exports; @@ -348,12 +361,14 @@ MaybeLocal Environment::BootstrapNode() { process_string(), require_string(), internal_binding_string(), - primordials_string()}; + primordials_string(), + run_in_privileged_scope_string()}; std::vector> node_args = { process_object(), native_module_require(), internal_binding_loader(), - primordials()}; + primordials(), + run_in_privileged_scope()}; MaybeLocal result = ExecuteBootstrapper( this, "internal/bootstrap/node", &node_params, &node_args); @@ -399,6 +414,10 @@ MaybeLocal Environment::RunBootstrapping() { CHECK(!has_run_bootstrapping_code()); + if (!BootstrapPrivilegedAccessContext()) { + return MaybeLocal(); + } + if (BootstrapInternalLoaders().IsEmpty()) { return MaybeLocal(); } @@ -436,7 +455,8 @@ MaybeLocal StartExecution(Environment* env, const char* main_script_id) { env->require_string(), env->internal_binding_string(), env->primordials_string(), - FIXED_ONE_BYTE_STRING(env->isolate(), "markBootstrapComplete")}; + FIXED_ONE_BYTE_STRING(env->isolate(), "markBootstrapComplete"), + env->run_in_privileged_scope_string()}; std::vector> arguments = { env->process_object(), @@ -445,7 +465,8 @@ MaybeLocal StartExecution(Environment* env, const char* main_script_id) { env->primordials(), env->NewFunctionTemplate(MarkBootstrapComplete) ->GetFunction(env->context()) - .ToLocalChecked()}; + .ToLocalChecked(), + env->run_in_privileged_scope()}; return scope.EscapeMaybe( ExecuteBootstrapper(env, main_script_id, ¶meters, &arguments)); @@ -795,6 +816,14 @@ int ProcessGlobalArgs(std::vector* args, } } + if (per_process::root_policy.Apply( + per_process::cli_options->policy_deny, + per_process::cli_options->policy_grant).IsNothing()) { + errors->emplace_back( + "invalid permissions passed to --policy-deny or --policy-grant"); + return 12; + } + if (per_process::cli_options->disable_proto != "delete" && per_process::cli_options->disable_proto != "throw" && per_process::cli_options->disable_proto != "") { diff --git a/src/node_binding.cc b/src/node_binding.cc index 0db930adff1a9f..3bf7fb76393335 100644 --- a/src/node_binding.cc +++ b/src/node_binding.cc @@ -69,6 +69,7 @@ V(os) \ V(performance) \ V(pipe_wrap) \ + V(policy) \ V(process_wrap) \ V(process_methods) \ V(report) \ diff --git a/src/node_errors.h b/src/node_errors.h index 6158a968d27a9a..208fb2ca579609 100644 --- a/src/node_errors.h +++ b/src/node_errors.h @@ -28,6 +28,7 @@ void OnFatalError(const char* location, const char* message); // a `Local` containing the TypeError with proper code and message #define ERRORS_WITH_CODE(V) \ + V(ERR_ACCESS_DENIED, Error) \ V(ERR_BUFFER_CONTEXT_NOT_AVAILABLE, Error) \ V(ERR_BUFFER_OUT_OF_BOUNDS, RangeError) \ V(ERR_BUFFER_TOO_LARGE, Error) \ @@ -105,6 +106,7 @@ void OnFatalError(const char* location, const char* message); // Errors with predefined static messages #define PREDEFINED_ERROR_MESSAGES(V) \ + V(ERR_ACCESS_DENIED, "Access is denied") \ V(ERR_BUFFER_CONTEXT_NOT_AVAILABLE, \ "Buffer is not available for the current Context") \ V(ERR_CONSTRUCT_CALL_INVALID, "Constructor cannot be called") \ diff --git a/src/node_external_reference.h b/src/node_external_reference.h index 0544979dd9a6f1..5477bc89f5e694 100644 --- a/src/node_external_reference.h +++ b/src/node_external_reference.h @@ -56,6 +56,7 @@ class ExternalReferenceRegistry { V(handle_wrap) \ V(messaging) \ V(native_module) \ + V(policy) \ V(process_methods) \ V(process_object) \ V(task_queue) \ diff --git a/src/node_native_module.cc b/src/node_native_module.cc index f7d73544d2dbf1..8a9ecfc87871d1 100644 --- a/src/node_native_module.cc +++ b/src/node_native_module.cc @@ -183,7 +183,8 @@ MaybeLocal NativeModuleLoader::CompileAsModule( FIXED_ONE_BYTE_STRING(isolate, "module"), FIXED_ONE_BYTE_STRING(isolate, "process"), FIXED_ONE_BYTE_STRING(isolate, "internalBinding"), - FIXED_ONE_BYTE_STRING(isolate, "primordials")}; + FIXED_ONE_BYTE_STRING(isolate, "primordials"), + FIXED_ONE_BYTE_STRING(isolate, "runInPrivilegedScope")}; return LookupAndCompile(context, id, ¶meters, result); } diff --git a/src/node_options.cc b/src/node_options.cc index e90dcd93231fca..afd3f792dc4925 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -704,6 +704,14 @@ PerProcessOptionsParser::PerProcessOptionsParser( "generate diagnostic report on fatal (internal) errors", &PerProcessOptions::report_on_fatalerror, kAllowedInEnvironment); + AddOption("--policy-deny", + "denied permissions", + &PerProcessOptions::policy_deny, + kAllowedInEnvironment); + AddOption("--policy-grant", + "granted permissions", + &PerProcessOptions::policy_grant, + kAllowedInEnvironment); #ifdef NODE_HAVE_I18N_SUPPORT AddOption("--icu-data-dir", diff --git a/src/node_options.h b/src/node_options.h index 84ee8e34bcafcf..a37328d225bd93 100644 --- a/src/node_options.h +++ b/src/node_options.h @@ -260,6 +260,9 @@ class PerProcessOptions : public Options { bool trace_sigint = false; std::vector cmdline; + std::string policy_grant; + std::string policy_deny; + inline PerIsolateOptions* get_per_isolate_options(); void CheckOptions(std::vector* errors) override; }; diff --git a/src/policy/policy.cc b/src/policy/policy.cc new file mode 100644 index 00000000000000..e31a824c134bcb --- /dev/null +++ b/src/policy/policy.cc @@ -0,0 +1,222 @@ +#include "policy.h" +#include "aliased_struct-inl.h" +#include "base_object-inl.h" +#include "env-inl.h" +#include "memory_tracker-inl.h" +#include "node.h" +#include "node_external_reference.h" + +#include "v8.h" + +#include +#include + +namespace node { + +using v8::Context; +using v8::Function; +using v8::FunctionCallbackInfo; +using v8::Int32; +using v8::Just; +using v8::Local; +using v8::Maybe; +using v8::Nothing; +using v8::Object; +using v8::Value; + +namespace per_process { +// The root policy is establish at process start using +// the --policy-grant and --policy-deny command line +// arguments. Every node::Environment has it's own +// Policy that derives from the root. +policy::Policy root_policy; +} // namespace per_process + +namespace policy { + +PrivilegedScope::PrivilegedScope(Environment* env_) : env(env_) { + env->set_in_privileged_scope(true); +} + +PrivilegedScope::~PrivilegedScope() { + env->set_in_privileged_scope(false); +} + +namespace { +Mutex apply_mutex_; + +Permission GetPermission(Local arg) { + if (!arg->IsInt32()) + return Permission::kPermissionsCount; + int32_t permission = arg.As()->Value(); + if (permission < static_cast(Permission::kPermissionsRoot) || + permission > static_cast(Permission::kPermissionsCount)) { + return Permission::kPermissionsCount; + } + return static_cast(permission); +} + +static void Deny(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + CHECK(args[0]->IsString()); + Utf8Value list(env->isolate(), args[0]); + // If Apply returns Nothing, there was an error + // parsing the list, in which case we'll return undefined. + if (per_process::root_policy.Apply(*list).IsJust()) + return args.GetReturnValue().Set(true); +} + +static void FastCheck(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + Permission permission = GetPermission(args[0]); + CHECK_LT(permission, Permission::kPermissionsCount); + CHECK_GT(permission, Permission::kPermissionsRoot); + args.GetReturnValue().Set(Policy::is_granted(env, GetPermission(args[0]))); +} + +static void Check(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + CHECK(args[0]->IsString()); + Utf8Value list(env->isolate(), args[0]); + Maybe permissions = Policy::Parse(*list); + // If permissions is empty, there was an error parsing. + // return undefined to indicate check failure. + if (permissions.IsNothing()) return; + args.GetReturnValue().Set(Policy::is_granted(env, permissions.FromJust())); +} + +#define V(name, _, parent) \ + if (permission == Permission::k##parent) \ + SetRecursively(set, Permission::k##name); +void SetRecursively(PermissionSet* set, Permission permission) { + if (permission != Permission::kPermissionsRoot) + set->set(static_cast(permission)); + PERMISSIONS(V) +} +#undef V + +} // namespace + +void RunInPrivilegedScope(const FunctionCallbackInfo& args) { + CHECK(args[0]->IsFunction()); // The function to execute + CHECK(!args.IsConstructCall()); + Local fn = args[0].As(); + + Environment* env = Environment::GetCurrent(args); + PrivilegedScope privileged_scope(env); + + SlicedArguments call_args(args, 1); + + Local ret; + if (fn->Call( + env->context(), + args.This(), + call_args.length(), + call_args.out()).ToLocal(&ret)) { + args.GetReturnValue().Set(ret); + } +} + +bool Policy::is_granted(Environment* env, Permission permission) { + return env->in_privileged_scope() + ? true + : per_process::root_policy.is_granted(permission); +} + +bool Policy::is_granted(Environment* env, std::string permission) { + return env->in_privileged_scope() + ? true + : per_process::root_policy.is_granted(permission); +} + +bool Policy::is_granted(Environment* env, const PermissionSet& permissions) { + return env->in_privileged_scope() + ? true + : per_process::root_policy.is_granted(permissions); +} + +Maybe Policy::Parse(const std::string& list) { + PermissionSet set; + for (const auto& name : SplitString(list, ',')) { + Permission permission = PermissionFromName(name); + if (permission == Permission::kPermissionsCount) + return Nothing(); + SetRecursively(&set, permission); + } + return Just(set); +} + +#define V(Name, label, _) \ + if (strcmp(name.c_str(), label) == 0) return Permission::k##Name; +Permission Policy::PermissionFromName(const std::string& name) { + if (strcmp(name.c_str(), "*") == 0) return Permission::kPermissionsRoot; + PERMISSIONS(V) + return Permission::kPermissionsCount; +} +#undef V + +Maybe Policy::Apply(const std::string& deny, const std::string& grant) { + Maybe deny_set = Parse(deny); + if (deny_set.IsNothing()) return Nothing(); + Maybe grant_set = Parse(grant); + if (grant_set.IsNothing()) return Nothing(); + Apply(deny_set.FromJust(), grant_set.FromJust()); + return Just(true);; +} + +void Policy::Apply(const PermissionSet& deny, const PermissionSet& grant) { + // permissions_ is an inverted set. If a bit is *set* in + // permissions_, then the permission is *denied*, otherwise + // it is granted. + + // Just in case Deny is called from multiple Worker threads. + // TODO(@jasnell): Do we want to allow workers to call deny? + Mutex::ScopedLock lock(apply_mutex_); + + if (deny.count() > 0) { +#define V(name, _, __) \ + permissions_.set(static_cast(Permission::k##name)); + SPECIAL_PERMISSIONS(V) +#undef V + } + + permissions_ |= deny; + + if (!locked_) + permissions_ &= ~grant; + + locked_ = true; +} + +void Initialize(Local target, + Local unused, + Local context, + void* priv) { + Environment* env = Environment::GetCurrent(context); + env->SetMethod(target, "deny", Deny); + env->SetMethodNoSideEffect(target, "fastCheck", FastCheck); + env->SetMethodNoSideEffect(target, "check", Check); + + #define V(name, _, __) \ + constexpr int kPermission##name = static_cast(Permission::k##name); \ + NODE_DEFINE_CONSTANT(target, kPermission##name); + PERMISSIONS(V) + #undef V + + // internalBinding('policy') should be frozen + target->SetIntegrityLevel(context, v8::IntegrityLevel::kFrozen).FromJust(); +} + +void RegisterExternalReferences( + ExternalReferenceRegistry* registry) { + registry->Register(Deny); + registry->Register(FastCheck); + registry->Register(Check); + registry->Register(RunInPrivilegedScope); +} + +} // namespace policy +} // namespace node + +NODE_MODULE_CONTEXT_AWARE_INTERNAL(policy, node::policy::Initialize) +NODE_MODULE_EXTERNAL_REFERENCE(policy, node::policy::RegisterExternalReferences) diff --git a/src/policy/policy.h b/src/policy/policy.h new file mode 100644 index 00000000000000..959eac14050353 --- /dev/null +++ b/src/policy/policy.h @@ -0,0 +1,136 @@ +#ifndef SRC_POLICY_POLICY_H_ +#define SRC_POLICY_POLICY_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "node_options.h" +#include "v8.h" + +#include +#include +#include + +namespace node { + +class Environment; + +namespace policy { + +// Special Permissions are denied by default if any other permission is denied. +#define SPECIAL_PERMISSIONS(V) \ + V(SpecialInspector, "inspector", PermissionsRoot) \ + V(SpecialAddons, "addons", PermissionsRoot) \ + V(SpecialChildProcess, "child_process", PermissionsRoot) + +#define FILESYSTEM_PERMISSIONS(V) \ + V(FileSystem, "fs", PermissionsRoot) \ + V(FileSystemIn, "fs.in", FileSystem) \ + V(FileSystemOut, "fs.out", FileSystem) + +#define NETWORKING_PERMISSIONS(V) \ + V(Net, "net", PermissionsRoot) \ + V(NetIn, "net.in", Net) \ + V(NetOut, "net.out", Net) + +#define EXPERIMENTAL_PERMISSIONS(V) \ + V(Experimental, "wasi", PermissionsRoot) \ + +#define PERMISSIONS(V) \ + EXPERIMENTAL_PERMISSIONS(V) \ + FILESYSTEM_PERMISSIONS(V) \ + NETWORKING_PERMISSIONS(V) \ + SPECIAL_PERMISSIONS(V) \ + V(Process, "process", PermissionsRoot) \ + V(Signal, "signal", PermissionsRoot) \ + V(Timing, "timing", PermissionsRoot) \ + V(Env, "env", PermissionsRoot) \ + V(Workers, "workers", PermissionsRoot) \ + V(Policy, "policy", PermissionsRoot) + +#define V(name, _, __) k##name, +enum class Permission { + kPermissionsRoot = -1, + PERMISSIONS(V) + kPermissionsCount +}; +#undef V + +using PermissionSet = + std::bitset(Permission::kPermissionsCount)>; + +void RunInPrivilegedScope( + const v8::FunctionCallbackInfo& args); + +class Policy final { + public: + static bool is_granted(Environment* env, Permission permission); + static bool is_granted(Environment* env, std::string permission); + static bool is_granted(Environment* env, const PermissionSet& permissions); + + static Permission PermissionFromName(const std::string& name); + + static v8::Maybe Parse(const std::string& list); + + Policy() = default; + + Policy(Policy&& other) = delete; + Policy& operator=(Policy&& other) = delete; + Policy(const Policy& other) = delete; + Policy& operator=(const Policy& other) = delete; + + // Returns true after setting the permissions. If Nothing + // is returned, the permissions could not be parsed successfully. + v8::Maybe Apply( + const std::string& deny, + const std::string& grant = std::string()); + + inline bool is_granted(Permission permission) const { + return LIKELY(permission != Permission::kPermissionsCount) && + LIKELY(permission != Permission::kPermissionsRoot) && + !test(permission); + } + + inline bool is_granted(std::string permission) const { + return is_granted(PermissionFromName(permission)); + } + + inline bool is_granted(const PermissionSet& set) const { + PermissionSet check = permissions_; + check &= set; + return check.none(); + } + + // Once a policy is locked, no additional grants will be permitted. + inline bool is_locked() const { return locked_; } + + private: + // Returns true after setting the permissions. If Nothing + // is returned, the permissions could not be + void Apply(const PermissionSet& deny, const PermissionSet& grant); + + inline bool test(Permission permission) const { + return UNLIKELY(permissions_.test(static_cast(permission))); + } + + bool locked_ = false; + PermissionSet permissions_; +}; + +// When code is running with the privileged scope, policy +// permission checking should be disabled. +struct PrivilegedScope { + Environment* env; + explicit PrivilegedScope(Environment* env_); + ~PrivilegedScope(); +}; + +} // namespace policy + +namespace per_process { +extern policy::Policy root_policy; +} // namespace per_process + +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS +#endif // SRC_POLICY_POLICY_H_