From 86ae8d183c3a4b45cdaf5b732fd5040dd6b4a0f0 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Sun, 10 Sep 2017 01:43:55 +0200 Subject: [PATCH] worker: implement vm.moveMessagePortToContext() This should help a lot with actual sandboxing of JS code. --- lib/vm.js | 3 ++ node.gyp | 3 ++ src/node.cc | 3 -- src/node_contextify.cc | 12 ++++++ src/node_contextify.h | 21 ++++++++++ src/node_messaging.cc | 34 ++++++++++++++++ src/node_messaging.h | 5 +++ test/parallel/test-message-channel-move.js | 45 ++++++++++++++++++++++ 8 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 src/node_contextify.h create mode 100644 test/parallel/test-message-channel-move.js diff --git a/lib/vm.js b/lib/vm.js index e7fccc9749..f8e34d0d5b 100644 --- a/lib/vm.js +++ b/lib/vm.js @@ -30,6 +30,8 @@ const { runInDebugContext } = process.binding('contextify'); +const { moveMessagePortToContext } = process.binding('messaging'); + // The binding provides a few useful primitives: // - Script(code, { filename = "evalmachine.anonymous", // displayErrors = true } = {}) @@ -143,6 +145,7 @@ module.exports = { Script, createContext, createScript, + moveMessagePortToContext, runInDebugContext, runInContext, runInNewContext, diff --git a/node.gyp b/node.gyp index ffc5e91588..dd9fffb21d 100644 --- a/node.gyp +++ b/node.gyp @@ -249,6 +249,7 @@ 'src/node_http2_core-inl.h', 'src/node_buffer.h', 'src/node_constants.h', + 'src/node_contextify.h', 'src/node_debug_options.h', 'src/node_http2.h', 'src/node_http2_state.h', @@ -680,10 +681,12 @@ '<(OBJ_PATH)<(OBJ_SEPARATOR)handle_wrap.<(OBJ_SUFFIX)', '<(OBJ_PATH)<(OBJ_SEPARATOR)node.<(OBJ_SUFFIX)', '<(OBJ_PATH)<(OBJ_SEPARATOR)node_buffer.<(OBJ_SUFFIX)', + '<(OBJ_PATH)<(OBJ_SEPARATOR)node_contextify.<(OBJ_SUFFIX)', '<(OBJ_PATH)<(OBJ_SEPARATOR)node_i18n.<(OBJ_SUFFIX)', '<(OBJ_PATH)<(OBJ_SEPARATOR)node_messaging.<(OBJ_SUFFIX)', '<(OBJ_PATH)<(OBJ_SEPARATOR)node_perf.<(OBJ_SUFFIX)', '<(OBJ_PATH)<(OBJ_SEPARATOR)node_url.<(OBJ_SUFFIX)', + '<(OBJ_PATH)<(OBJ_SEPARATOR)node_watchdog.<(OBJ_SUFFIX)', '<(OBJ_PATH)<(OBJ_SEPARATOR)node_worker.<(OBJ_SUFFIX)', '<(OBJ_PATH)<(OBJ_SEPARATOR)util.<(OBJ_SUFFIX)', '<(OBJ_PATH)<(OBJ_SEPARATOR)string_bytes.<(OBJ_SUFFIX)', diff --git a/src/node.cc b/src/node.cc index 51d9d19427..ecfc775fa1 100644 --- a/src/node.cc +++ b/src/node.cc @@ -1426,9 +1426,6 @@ InternalCallbackScope::InternalCallbackScope(Environment* env, return; } - // If you hit this assertion, you forgot to enter the v8::Context first. - CHECK_EQ(env->context(), env->isolate()->GetCurrentContext()); - if (env->using_domains()) { failed_ = DomainEnter(env, object_); if (failed_) diff --git a/src/node_contextify.cc b/src/node_contextify.cc index c8830b45f3..5b05104c0c 100644 --- a/src/node_contextify.cc +++ b/src/node_contextify.cc @@ -1071,6 +1071,18 @@ void InitContextify(Local target, } } // anonymous namespace + +MaybeLocal ContextFromContextifiedSandbox( + Environment* env, + Local sandbox) { + auto contextify_context = + ContextifyContext::ContextFromContextifiedSandbox(env, sandbox); + if (contextify_context == nullptr) + return MaybeLocal(); + else + return contextify_context->context(); +} + } // namespace node NODE_MODULE_CONTEXT_AWARE_BUILTIN(contextify, node::InitContextify) diff --git a/src/node_contextify.h b/src/node_contextify.h new file mode 100644 index 0000000000..86ecdf55a7 --- /dev/null +++ b/src/node_contextify.h @@ -0,0 +1,21 @@ +#ifndef SRC_NODE_CONTEXTIFY_H_ +#define SRC_NODE_CONTEXTIFY_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "v8.h" + +namespace node { + +class Environment; + +v8::MaybeLocal ContextFromContextifiedSandbox( + Environment* env, + v8::Local sandbox); + +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + + +#endif // SRC_NODE_CONTEXTIFY_H_ diff --git a/src/node_messaging.cc b/src/node_messaging.cc index acf4aa802d..10ed6cd59a 100644 --- a/src/node_messaging.cc +++ b/src/node_messaging.cc @@ -1,4 +1,5 @@ #include "node_messaging.h" +#include "node_contextify.h" #include "node_internals.h" #include "node_buffer.h" #include "util.h" @@ -698,6 +699,37 @@ void MessagePort::StartBinding(const FunctionCallbackInfo& args) { port->Start(); } +void MessagePort::MoveToContext(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + MessagePort* port; + if (!args[0]->IsObject() || + (port = Unwrap(args[0].As())) == nullptr) { + env->ThrowTypeError("First argument needs to be a MessagePort instance"); + } + if (!port->data_) { + env->ThrowError("Cannot transfer a closed MessagePort"); + return; + } + if (port->is_privileged_ || port->fm_listener_) { + env->ThrowError("Cannot transfer MessagePort with special semantics"); + return; + } + Local context_arg = args[1]; + Local context; + if (!context_arg->IsObject() || + !ContextFromContextifiedSandbox(env, context_arg.As()) + .ToLocal(&context)) { + env->ThrowError("Invalid context argument"); + return; + } + Context::Scope context_scope(context); + MessagePort* target = + MessagePort::New(env, context, nullptr, std::move(port->data_)); + if (target) { + args.GetReturnValue().Set(target->object()); + } +} + size_t MessagePort::self_size() const { Mutex::ScopedLock lock(data_->mutex_); size_t sz = sizeof(*this) + sizeof(*data_); @@ -781,6 +813,8 @@ static void InitMessaging(Local target, templ->GetFunction(context).ToLocalChecked()).FromJust(); } + env->SetMethod(target, "moveMessagePortToContext", + MessagePort::MoveToContext); target->Set(context, env->message_port_constructor_string(), GetMessagePortConstructor(env, context).ToLocalChecked()) diff --git a/src/node_messaging.h b/src/node_messaging.h index 02fe223c29..40f7a93225 100644 --- a/src/node_messaging.h +++ b/src/node_messaging.h @@ -193,10 +193,15 @@ class MessagePort : public HandleWrap { // Start processing messages on this port as a receiving end. void Start(); + /* constructor */ static void New(const v8::FunctionCallbackInfo& args); + /* prototype methods */ static void PostMessage(const v8::FunctionCallbackInfo& args); static void StartBinding(const v8::FunctionCallbackInfo& args); + /* static */ + static void MoveToContext(const v8::FunctionCallbackInfo& args); + static void Entangle(MessagePort* a, MessagePort* b); static void Entangle(MessagePort* a, MessagePortData* b); diff --git a/test/parallel/test-message-channel-move.js b/test/parallel/test-message-channel-move.js new file mode 100644 index 0000000000..ba140dc064 --- /dev/null +++ b/test/parallel/test-message-channel-move.js @@ -0,0 +1,45 @@ +/* eslint-disable prefer-assert-methods */ +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const vm = require('vm'); +const { MessageChannel } = require('worker'); + +{ + const context = vm.createContext(); + const channel = new MessageChannel(); + context.port = vm.moveMessagePortToContext(channel.port1, context); + context.global = context; + const port = channel.port2; + vm.runInContext('(' + function() { + function assert(condition) { if (!condition) throw new Error(); } + + { + assert(port instanceof Object); + assert(port.onmessage === undefined); + assert(port.postMessage instanceof Function); + port.onmessage = function(msg) { + assert(msg instanceof Object); + port.postMessage(msg); + }; + port.start(); + } + + { + let threw = false; + try { + port.postMessage(global); + } catch (e) { + assert(e instanceof Object); + assert(e instanceof Error); + threw = true; + } + assert(threw); + } + } + ')()', context); + port.on('message', common.mustCall((msg) => { + assert(msg instanceof Object); + port.close(); + })); + port.postMessage({}); +}