Skip to content

Commit

Permalink
[WIP] src: introduce Policy permissions
Browse files Browse the repository at this point in the history
  • Loading branch information
jasnell committed Dec 11, 2020
1 parent 7faeddf commit 09443c3
Show file tree
Hide file tree
Showing 18 changed files with 693 additions and 13 deletions.
11 changes: 9 additions & 2 deletions lib/internal/bootstrap/loaders.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down
5 changes: 3 additions & 2 deletions lib/internal/bootstrap/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
12 changes: 10 additions & 2 deletions lib/internal/bootstrap/pre_execution.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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() {
Expand Down
29 changes: 29 additions & 0 deletions lib/internal/process/policy.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 (typeof ret === 'number')
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 (typeof ret === 'number')
throw new ERR_INVALID_ARG_VALUE('permissions', permissions);
return ret;
}
});
3 changes: 3 additions & 0 deletions node.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,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',
Expand Down Expand Up @@ -735,6 +736,8 @@
'src/node_perf.h',
'src/node_perf_common.h',
'src/node_platform.h',
'src/policy/policy.h',
'src/policy/policy-inl.h',
'src/node_process.h',
'src/node_report.h',
'src/node_revert.h',
Expand Down
5 changes: 5 additions & 0 deletions src/env-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "env.h"
#include "node.h"
#include "util-inl.h"
#include "policy/policy-inl.h"
#include "uv.h"
#include "v8.h"
#include "node_perf_common.h"
Expand Down Expand Up @@ -246,6 +247,10 @@ inline size_t Environment::async_callback_scope_depth() const {
return async_callback_scope_depth_;
}

policy::PrivilegedAccessContext* Environment::privileged_access_context() {
return &privileged_access_context_;
}

inline void Environment::PushAsyncCallbackScope() {
async_callback_scope_depth_++;
}
Expand Down
1 change: 1 addition & 0 deletions src/env.cc
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@ Environment::Environment(IsolateData* isolate_data,
ThreadId thread_id)
: isolate_(isolate),
isolate_data_(isolate_data),
privileged_access_context_(isolate_data->options()->per_env.get()),
async_hooks_(isolate, MAYBE_FIELD_PTR(env_info, async_hooks)),
immediate_info_(isolate, MAYBE_FIELD_PTR(env_info, immediate_info)),
tick_info_(isolate, MAYBE_FIELD_PTR(env_info, tick_info)),
Expand Down
7 changes: 7 additions & 0 deletions src/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
#include "node_main_instance.h"
#include "node_options.h"
#include "node_perf_common.h"
#include "policy/policy.h"
#include "req_wrap.h"
#include "util.h"
#include "uv.h"
Expand Down Expand Up @@ -375,6 +376,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") \
Expand Down Expand Up @@ -547,6 +549,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) \
Expand Down Expand Up @@ -977,6 +980,7 @@ class Environment : public MemoryRetainer {
v8::MaybeLocal<v8::Value> BootstrapInternalLoaders();
v8::MaybeLocal<v8::Value> BootstrapNode();
v8::MaybeLocal<v8::Value> RunBootstrapping();
bool BootstrapPrivilegedAccessContext();

inline size_t async_callback_scope_depth() const;
inline void PushAsyncCallbackScope();
Expand Down Expand Up @@ -1036,6 +1040,8 @@ class Environment : public MemoryRetainer {
inline const std::vector<std::string>& argv();
const std::string& exec_path() const;

inline policy::PrivilegedAccessContext* privileged_access_context();

typedef void (*HandleCleanupCb)(Environment* env,
uv_handle_t* handle,
void* arg);
Expand Down Expand Up @@ -1397,6 +1403,7 @@ class Environment : public MemoryRetainer {
std::list<binding::DLib> loaded_addons_;
v8::Isolate* const isolate_;
IsolateData* const isolate_data_;
policy::PrivilegedAccessContext privileged_access_context_;
uv_timer_t timer_handle_;
uv_check_t immediate_check_handle_;
uv_idle_t immediate_idle_handle_;
Expand Down
35 changes: 29 additions & 6 deletions src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -294,6 +295,18 @@ void Environment::InitializeDiagnostics() {
#endif
}

bool Environment::BootstrapPrivilegedAccessContext() {
Local<Function> run_in_privileged_scope;
MaybeLocal<Function> maybe_run_in_privileged_scope =
Function::New(
context(),
policy::PrivilegedAccessContext::Run);
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<Value> Environment::BootstrapInternalLoaders() {
EscapableHandleScope scope(isolate_);

Expand All @@ -302,7 +315,8 @@ MaybeLocal<Value> 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<Local<Value>> loaders_args = {
process_object(),
NewFunctionTemplate(binding::GetLinkedBinding)
Expand All @@ -311,7 +325,8 @@ MaybeLocal<Value> Environment::BootstrapInternalLoaders() {
NewFunctionTemplate(binding::GetInternalBinding)
->GetFunction(context())
.ToLocalChecked(),
primordials()};
primordials(),
run_in_privileged_scope()};

// Bootstrap internal loaders
Local<Value> loader_exports;
Expand Down Expand Up @@ -348,12 +363,14 @@ MaybeLocal<Value> Environment::BootstrapNode() {
process_string(),
require_string(),
internal_binding_string(),
primordials_string()};
primordials_string(),
run_in_privileged_scope_string()};
std::vector<Local<Value>> node_args = {
process_object(),
native_module_require(),
internal_binding_loader(),
primordials()};
primordials(),
run_in_privileged_scope()};

MaybeLocal<Value> result = ExecuteBootstrapper(
this, "internal/bootstrap/node", &node_params, &node_args);
Expand Down Expand Up @@ -399,6 +416,10 @@ MaybeLocal<Value> Environment::RunBootstrapping() {

CHECK(!has_run_bootstrapping_code());

if (!BootstrapPrivilegedAccessContext()) {
return MaybeLocal<Value>();
}

if (BootstrapInternalLoaders().IsEmpty()) {
return MaybeLocal<Value>();
}
Expand Down Expand Up @@ -436,7 +457,8 @@ MaybeLocal<Value> 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<Local<Value>> arguments = {
env->process_object(),
Expand All @@ -445,7 +467,8 @@ MaybeLocal<Value> 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, &parameters, &arguments));
Expand Down
1 change: 1 addition & 0 deletions src/node_binding.cc
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
V(os) \
V(performance) \
V(pipe_wrap) \
V(policy) \
V(process_wrap) \
V(process_methods) \
V(report) \
Expand Down
2 changes: 2 additions & 0 deletions src/node_errors.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ void OnFatalError(const char* location, const char* message);
// a `Local<Value>` 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) \
Expand Down Expand Up @@ -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") \
Expand Down
1 change: 1 addition & 0 deletions src/node_external_reference.h
Original file line number Diff line number Diff line change
Expand Up @@ -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) \
Expand Down
3 changes: 2 additions & 1 deletion src/node_native_module.cc
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,8 @@ MaybeLocal<Function> 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, &parameters, result);
}

Expand Down
8 changes: 8 additions & 0 deletions src/node_options.cc
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,14 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
&EnvironmentOptions::experimental_policy_integrity,
kAllowedInEnvironment);
Implies("--policy-integrity", "[has_policy_integrity_string]");
AddOption("--policy-deny",
"denied permissions",
&EnvironmentOptions::policy_deny,
kAllowedInEnvironment);
AddOption("--policy-grant",
"granted permissions",
&EnvironmentOptions::policy_grant,
kAllowedInEnvironment);
AddOption("--experimental-repl-await",
"experimental await keyword support in REPL",
&EnvironmentOptions::experimental_repl_await,
Expand Down
3 changes: 3 additions & 0 deletions src/node_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,9 @@ class EnvironmentOptions : public Options {

std::vector<std::string> preload_modules;

std::string policy_deny;
std::string policy_grant;

std::vector<std::string> user_argv;

inline DebugOptions* get_debug_options() { return &debug_options_; }
Expand Down
Loading

0 comments on commit 09443c3

Please sign in to comment.