src: add internalBindings for binding isolation

This commit adds a method to internal/process that allows access to
bindings that are not intended to be used by user code. It has a
separate cache object and modlist in order to avoid collisions.

You can use NODE_MODULE_CONTEXT_AWARE_INTERNAL to register a C++
module as an internal.

PR-URL: https://github.com/nodejs/node/pull/15759
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Brian White <mscdex@mscdex.net>
Reviewed-By: Refael Ackermann <refack@gmail.com>
This commit is contained in:
Bradley Farias 2017-10-03 10:07:48 -05:00
parent 75d41cf531
commit a36aa049c8
10 changed files with 135 additions and 49 deletions

View File

@ -1,6 +1,7 @@
'use strict';
const { ModuleWrap } = process.binding('module_wrap');
const { ModuleWrap } =
require('internal/process').internalBinding('module_wrap');
const debug = require('util').debuglog('esm');
const ArrayJoin = Function.call.bind(Array.prototype.join);
const ArrayMap = Function.call.bind(Array.prototype.map);

View File

@ -3,7 +3,7 @@
const { URL } = require('url');
const CJSmodule = require('module');
const errors = require('internal/errors');
const { resolve } = process.binding('module_wrap');
const { resolve } = require('internal/process').internalBinding('module_wrap');
module.exports = (target, base) => {
if (base === undefined) {

View File

@ -4,6 +4,9 @@ const errors = require('internal/errors');
const util = require('util');
const constants = process.binding('constants').os.signals;
const internalBinding = process._internalBinding;
delete process._internalBinding;
const assert = process.assert = function(x, msg) {
if (!x) throw new errors.Error('ERR_ASSERTION', msg || 'assertion error');
};
@ -256,5 +259,6 @@ module.exports = {
setupKillAndExit,
setupSignalHandlers,
setupChannel,
setupRawDebug
setupRawDebug,
internalBinding
};

View File

@ -303,7 +303,17 @@ inline Environment::Environment(IsolateData* isolate_data,
v8::HandleScope handle_scope(isolate());
v8::Context::Scope context_scope(context);
set_as_external(v8::External::New(isolate(), this));
set_binding_cache_object(v8::Object::New(isolate()));
v8::Local<v8::Primitive> null = v8::Null(isolate());
v8::Local<v8::Object> binding_cache_object = v8::Object::New(isolate());
CHECK(binding_cache_object->SetPrototype(context, null).FromJust());
set_binding_cache_object(binding_cache_object);
v8::Local<v8::Object> internal_binding_cache_object =
v8::Object::New(isolate());
CHECK(internal_binding_cache_object->SetPrototype(context, null).FromJust());
set_internal_binding_cache_object(internal_binding_cache_object);
set_module_load_list_array(v8::Array::New(isolate()));
AssignToContext(context);

View File

@ -302,6 +302,7 @@ class ModuleWrap;
V(async_hooks_after_function, v8::Function) \
V(async_hooks_promise_resolve_function, v8::Function) \
V(binding_cache_object, v8::Object) \
V(internal_binding_cache_object, v8::Object) \
V(buffer_prototype_object, v8::Object) \
V(context, v8::Context) \
V(domain_array, v8::Array) \

View File

@ -7,6 +7,7 @@
#include "node_url.h"
#include "util.h"
#include "util-inl.h"
#include "node_internals.h"
namespace node {
namespace loader {
@ -523,5 +524,5 @@ void ModuleWrap::Initialize(Local<Object> target,
} // namespace loader
} // namespace node
NODE_MODULE_CONTEXT_AWARE_BUILTIN(module_wrap,
NODE_MODULE_CONTEXT_AWARE_INTERNAL(module_wrap,
node::loader::ModuleWrap::Initialize)

View File

@ -183,6 +183,7 @@ static bool v8_is_profiling = false;
static bool node_is_initialized = false;
static node_module* modpending;
static node_module* modlist_builtin;
static node_module* modlist_internal;
static node_module* modlist_linked;
static node_module* modlist_addon;
static bool trace_enabled = false;
@ -2574,6 +2575,9 @@ extern "C" void node_module_register(void* m) {
if (mp->nm_flags & NM_F_BUILTIN) {
mp->nm_link = modlist_builtin;
modlist_builtin = mp;
} else if (mp->nm_flags & NM_F_INTERNAL) {
mp->nm_link = modlist_internal;
modlist_internal = mp;
} else if (!node_is_initialized) {
// "Linked" modules are included as part of the node project.
// Like builtins they are registered *before* node::Init runs.
@ -2585,30 +2589,30 @@ extern "C" void node_module_register(void* m) {
}
}
struct node_module* get_builtin_module(const char* name) {
inline struct node_module* FindModule(struct node_module* list,
const char* name,
int flag) {
struct node_module* mp;
for (mp = modlist_builtin; mp != nullptr; mp = mp->nm_link) {
for (mp = list; mp != nullptr; mp = mp->nm_link) {
if (strcmp(mp->nm_modname, name) == 0)
break;
}
CHECK(mp == nullptr || (mp->nm_flags & NM_F_BUILTIN) != 0);
return (mp);
}
struct node_module* get_linked_module(const char* name) {
struct node_module* mp;
for (mp = modlist_linked; mp != nullptr; mp = mp->nm_link) {
if (strcmp(mp->nm_modname, name) == 0)
break;
}
CHECK(mp == nullptr || (mp->nm_flags & NM_F_LINKED) != 0);
CHECK(mp == nullptr || (mp->nm_flags & flag) != 0);
return mp;
}
node_module* get_builtin_module(const char* name) {
return FindModule(modlist_builtin, name, NM_F_BUILTIN);
}
node_module* get_internal_module(const char* name) {
return FindModule(modlist_internal, name, NM_F_INTERNAL);
}
node_module* get_linked_module(const char* name) {
return FindModule(modlist_linked, name, NM_F_LINKED);
}
struct DLib {
std::string filename_;
std::string errmsg_;
@ -2880,24 +2884,60 @@ void ProcessEmitWarning(Environment* env, const char* fmt, ...) {
f.As<v8::Function>()->Call(process, 1, &arg);
}
static bool PullFromCache(Environment* env,
const FunctionCallbackInfo<Value>& args,
Local<String> module,
Local<Object> cache) {
Local<Context> context = env->context();
Local<Value> exports_v;
Local<Object> exports;
if (cache->Get(context, module).ToLocal(&exports_v) &&
exports_v->IsObject() &&
exports_v->ToObject(context).ToLocal(&exports)) {
args.GetReturnValue().Set(exports);
return true;
}
return false;
}
static Local<Object> InitModule(Environment* env,
node_module* mod,
Local<String> module) {
Local<Object> exports = Object::New(env->isolate());
// Internal bindings don't have a "module" object, only exports.
CHECK_EQ(mod->nm_register_func, nullptr);
CHECK_NE(mod->nm_context_register_func, nullptr);
Local<Value> unused = Undefined(env->isolate());
mod->nm_context_register_func(exports,
unused,
env->context(),
mod->nm_priv);
return exports;
}
static void ThrowIfNoSuchModule(Environment* env, const char* module_v) {
char errmsg[1024];
snprintf(errmsg,
sizeof(errmsg),
"No such module: %s",
module_v);
env->ThrowError(errmsg);
}
static void Binding(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Local<String> module = args[0]->ToString(env->isolate());
node::Utf8Value module_v(env->isolate(), module);
Local<String> module;
if (!args[0]->ToString(env->context()).ToLocal(&module)) return;
Local<Object> cache = env->binding_cache_object();
Local<Object> exports;
if (cache->Has(env->context(), module).FromJust()) {
exports = cache->Get(module)->ToObject(env->isolate());
args.GetReturnValue().Set(exports);
if (PullFromCache(env, args, module, cache))
return;
}
// Append a string to process.moduleLoadList
char buf[1024];
node::Utf8Value module_v(env->isolate(), module);
snprintf(buf, sizeof(buf), "Binding %s", *module_v);
Local<Array> modules = env->module_load_list_array();
@ -2905,33 +2945,49 @@ static void Binding(const FunctionCallbackInfo<Value>& args) {
modules->Set(l, OneByteString(env->isolate(), buf));
node_module* mod = get_builtin_module(*module_v);
Local<Object> exports;
if (mod != nullptr) {
exports = Object::New(env->isolate());
// Internal bindings don't have a "module" object, only exports.
CHECK_EQ(mod->nm_register_func, nullptr);
CHECK_NE(mod->nm_context_register_func, nullptr);
Local<Value> unused = Undefined(env->isolate());
mod->nm_context_register_func(exports, unused,
env->context(), mod->nm_priv);
cache->Set(module, exports);
exports = InitModule(env, mod, module);
} else if (!strcmp(*module_v, "constants")) {
exports = Object::New(env->isolate());
CHECK(exports->SetPrototype(env->context(),
Null(env->isolate())).FromJust());
DefineConstants(env->isolate(), exports);
cache->Set(module, exports);
} else if (!strcmp(*module_v, "natives")) {
exports = Object::New(env->isolate());
DefineJavaScript(env, exports);
cache->Set(module, exports);
} else {
char errmsg[1024];
snprintf(errmsg,
sizeof(errmsg),
"No such module: %s",
*module_v);
return env->ThrowError(errmsg);
return ThrowIfNoSuchModule(env, *module_v);
}
cache->Set(module, exports);
args.GetReturnValue().Set(exports);
}
static void InternalBinding(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Local<String> module;
if (!args[0]->ToString(env->context()).ToLocal(&module)) return;
Local<Object> cache = env->internal_binding_cache_object();
if (PullFromCache(env, args, module, cache))
return;
// Append a string to process.moduleLoadList
char buf[1024];
node::Utf8Value module_v(env->isolate(), module);
snprintf(buf, sizeof(buf), "Internal Binding %s", *module_v);
Local<Array> modules = env->module_load_list_array();
uint32_t l = modules->Length();
modules->Set(l, OneByteString(env->isolate(), buf));
node_module* mod = get_internal_module(*module_v);
if (mod == nullptr) return ThrowIfNoSuchModule(env, *module_v);
Local<Object> exports = InitModule(env, mod, module);
cache->Set(module, exports);
args.GetReturnValue().Set(exports);
}
@ -2939,7 +2995,8 @@ static void Binding(const FunctionCallbackInfo<Value>& args) {
static void LinkedBinding(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args.GetIsolate());
Local<String> module_name = args[0]->ToString(env->isolate());
Local<String> module_name;
if (!args[0]->ToString(env->context()).ToLocal(&module_name)) return;
Local<Object> cache = env->binding_cache_object();
Local<Value> exports_v = cache->Get(module_name);
@ -3674,6 +3731,7 @@ void SetupProcessObject(Environment* env,
env->SetMethod(process, "binding", Binding);
env->SetMethod(process, "_linkedBinding", LinkedBinding);
env->SetMethod(process, "_internalBinding", InternalBinding);
env->SetMethod(process, "_setupProcessObject", SetupProcessObject);
env->SetMethod(process, "_setupNextTick", SetupNextTick);

View File

@ -423,6 +423,7 @@ typedef void (*addon_context_register_func)(
#define NM_F_BUILTIN 0x01
#define NM_F_LINKED 0x02
#define NM_F_INTERNAL 0x04
struct node_module {
int nm_version;
@ -436,9 +437,6 @@ struct node_module {
struct node_module* nm_link;
};
node_module* get_builtin_module(const char *name);
node_module* get_linked_module(const char *name);
extern "C" NODE_EXTERN void node_module_register(void* mod);
#ifdef _WIN32

View File

@ -324,6 +324,9 @@ class InternalCallbackScope {
bool closed_ = false;
};
#define NODE_MODULE_CONTEXT_AWARE_INTERNAL(modname, regfunc) \
NODE_MODULE_CONTEXT_AWARE_X(modname, regfunc, NULL, NM_F_INTERNAL) \
} // namespace node

View File

@ -0,0 +1,10 @@
'use strict';
require('../common');
const assert = require('assert');
assert.strictEqual(undefined, process._internalBinding);
assert.strictEqual(undefined, process.internalBinding);
assert.throws(() => {
process.binding('module_wrap');
}, /No such module/);