Skip to content
This repository has been archived by the owner on Aug 31, 2018. It is now read-only.

Commit

Permalink
worker: implement vm.moveMessagePortToContext()
Browse files Browse the repository at this point in the history
This should help a lot with actual sandboxing of JS code.
  • Loading branch information
addaleax committed Sep 14, 2017
1 parent 31ee1a1 commit 09926cd
Show file tree
Hide file tree
Showing 9 changed files with 123 additions and 2 deletions.
3 changes: 3 additions & 0 deletions lib/vm.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 } = {})
Expand Down Expand Up @@ -143,6 +145,7 @@ module.exports = {
Script,
createContext,
createScript,
moveMessagePortToContext,
runInDebugContext,
runInContext,
runInNewContext,
Expand Down
3 changes: 3 additions & 0 deletions node.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,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_internals.h',
Expand Down Expand Up @@ -675,10 +676,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)',
Expand Down
1 change: 0 additions & 1 deletion src/async-wrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -673,7 +673,6 @@ void AsyncWrap::EmitAsyncInit(Environment* env,
MaybeLocal<Value> AsyncWrap::MakeCallback(const Local<Function> cb,
int argc,
Local<Value>* argv) {
CHECK(env()->context() == env()->isolate()->GetCurrentContext());
if (!env()->can_call_into_js()) return Undefined(env()->isolate());

Environment::AsyncCallbackScope callback_scope(env());
Expand Down
1 change: 0 additions & 1 deletion src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1343,7 +1343,6 @@ MaybeLocal<Value> MakeCallback(Environment* env,
Local<Value> argv[],
async_context asyncContext) {
// If you hit this assertion, you forgot to enter the v8::Context first.
CHECK_EQ(env->context(), env->isolate()->GetCurrentContext());
if (!env->can_call_into_js()) return Undefined(env->isolate());

Local<Object> object;
Expand Down
12 changes: 12 additions & 0 deletions src/node_contextify.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1076,6 +1076,18 @@ void InitContextify(Local<Object> target,
}

} // anonymous namespace

MaybeLocal<Context> ContextFromContextifiedSandbox(
Environment* env,
Local<Object> sandbox) {
auto contextify_context =
ContextifyContext::ContextFromContextifiedSandbox(env, sandbox);
if (contextify_context == nullptr)
return MaybeLocal<Context>();
else
return contextify_context->context();
}

} // namespace node

NODE_MODULE_CONTEXT_AWARE_BUILTIN(contextify, node::InitContextify)
21 changes: 21 additions & 0 deletions src/node_contextify.h
Original file line number Diff line number Diff line change
@@ -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<v8::Context> ContextFromContextifiedSandbox(
Environment* env,
v8::Local<v8::Object> sandbox);

} // namespace node

#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS


#endif // SRC_NODE_CONTEXTIFY_H_
34 changes: 34 additions & 0 deletions src/node_messaging.cc
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "node_messaging.h"
#include "node_contextify.h"
#include "node_internals.h"
#include "node_buffer.h"
#include "util.h"
Expand Down Expand Up @@ -698,6 +699,37 @@ void MessagePort::StartBinding(const FunctionCallbackInfo<Value>& args) {
port->Start();
}

void MessagePort::MoveToContext(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
MessagePort* port;
if (!args[0]->IsObject() ||
(port = Unwrap<MessagePort>(args[0].As<Object>())) == 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<Value> context_arg = args[1];
Local<Context> context;
if (!context_arg->IsObject() ||
!ContextFromContextifiedSandbox(env, context_arg.As<Object>())
.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_);
Expand Down Expand Up @@ -781,6 +813,8 @@ static void InitMessaging(Local<Object> target,
templ->GetFunction(context).ToLocalChecked()).FromJust();
}

env->SetMethod(target, "moveMessagePortToContext",
MessagePort::MoveToContext);
target->Set(context,
env->message_port_constructor_string(),
GetMessagePortConstructor(env, context).ToLocalChecked())
Expand Down
5 changes: 5 additions & 0 deletions src/node_messaging.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<v8::Value>& args);
/* prototype methods */
static void PostMessage(const v8::FunctionCallbackInfo<v8::Value>& args);
static void StartBinding(const v8::FunctionCallbackInfo<v8::Value>& args);

/* static */
static void MoveToContext(const v8::FunctionCallbackInfo<v8::Value>& args);

static void Entangle(MessagePort* a, MessagePort* b);
static void Entangle(MessagePort* a, MessagePortData* b);

Expand Down
45 changes: 45 additions & 0 deletions test/parallel/test-message-channel-move.js
Original file line number Diff line number Diff line change
@@ -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({});
}

0 comments on commit 09926cd

Please sign in to comment.