Skip to content

Commit

Permalink
deps,src: workaround vm.runInContext issue with top level "var"
Browse files Browse the repository at this point in the history
addresses nodejs#420

ChakraCore doesn't handle global objects in the same way as v8, and
in particular ChakraCore doesn't support all proxy interceptors there.
We attempted to work around this via interceptors in the prototype
chain, but that was an imperfect solution, and a top-level "var"
declaration would end up adding a property to the global object in
a context without going through the interceptors.

To work around this in simple cases, we now take a snapshot of
properties on the global before running in a context, and diff the
changes at the end to patch up changes in the sandbox.

This is NOT a complete solution, and in particular will not work if
there is any sort of asynchronous operation that changes the state
of the global object. It also has behavioral differences if the
global object is writable but the sandbox object is not; in node-v8
this will result in the variable silently not being set in the
context and instead the value from the sandbox being used, while
here we discover the mismatch too late and the new value has been
set on the global.

PR-URL: nodejs#542
Reviewed-By: Seth Brenith <sethb@microsoft.com>
Reviewed-By: Kyle Farnung <kfarnung@microsoft.com>
  • Loading branch information
MSLaguana authored and kfarnung committed May 22, 2018
1 parent 7a711e0 commit f8f6b8e
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 1 deletion.
3 changes: 3 additions & 0 deletions deps/chakrashim/include/v8.h
Original file line number Diff line number Diff line change
Expand Up @@ -3005,6 +3005,9 @@ class V8_EXPORT Context {
void SetSecurityToken(Handle<Value> token);
Handle<Value> GetSecurityToken();
void AllowCodeGenerationFromStrings(bool allow);

void CacheGlobalProperties();
void ResolveGlobalChanges(Local<Object> sandbox);
};

class V8_EXPORT Locker {
Expand Down
71 changes: 71 additions & 0 deletions deps/chakrashim/lib/chakra_shim.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
const Function_prototype_toString = Function.prototype.toString;
const Object_defineProperty = Object.defineProperty;
const Object_getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
const Object_getOwnPropertyDescriptors = Object.getOwnPropertyDescriptors;
const Object_getOwnPropertyNames = Object.getOwnPropertyNames;
const Object_keys = Object.keys;
const Object_prototype_toString = Object.prototype.toString;
Expand Down Expand Up @@ -671,6 +672,76 @@

return ownPropertyNames;
};

utils.beforeContext = function (contextGlobal) {
return Object_getOwnPropertyDescriptors(contextGlobal);
}

function descriptorDiff(a, b) {
if (!a || !b) {
return true;
}
if (a.value !== b.value && a.value === a.value && b.value === b.value) {
// allow for NaN in value
return true;
}
if (a.enumerable !== b.enumerable ||
a.configurable !== b.configurable ||
a.writable !== b.writable) {
return true;
}
if (a.get !== b.get || a.set !== b.set) {
return true;
}
return false;
}
utils.afterContext = function (beforeDescriptors, contextGlobal, sandbox) {
try {
const afterDescriptors = Object_getOwnPropertyDescriptors(contextGlobal);
const beforeKeys = Object_keys(beforeDescriptors);
const afterKeys = Object_keys(afterDescriptors);

for (const beforeKey of beforeKeys) {
const beforeDescriptor = beforeDescriptors[beforeKey];
const afterDescriptor = afterDescriptors[beforeKey];
const sandboxDescriptor = Object_getOwnPropertyDescriptor(sandbox, beforeKey);
if (!afterDescriptor) {
// The descriptor was removed
if (sandboxDescriptor) {
if (sandboxDescriptor.configurable) {
delete sandbox[beforeKey];
} else {
// TODO: How to handle this?
}
}
} else if (descriptorDiff(beforeDescriptor, afterDescriptor)) {
// Before and after differ; apply an update if possible
if (!sandboxDescriptor || sandboxDescriptor.configurable) {
Object_defineProperty(sandbox, beforeKey, afterDescriptor);
} else {
// TODO: How can we handle this case? Node tries to avoid it, but we don't always
}
}
}

for (const afterKey of afterKeys) {
if (afterKey in beforeDescriptors) {
continue; // Handled above
}

// This property is freshly added
const afterDescriptor = afterDescriptors[afterKey];
const sandboxDescriptor = Object_getOwnPropertyDescriptor(sandbox, afterKey);
if (!sandboxDescriptor || sandboxDescriptor.configurable) {
Object_defineProperty(sandbox, afterKey, afterDescriptor);
} else {
// TODO: How can we handle this?
}
}
} catch (e) {
// ignored;
}
}
}

patchErrorTypes();
Expand Down
2 changes: 2 additions & 0 deletions deps/chakrashim/src/jsrtcachedpropertyidref.inc
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ DEF(ensureDebug)
DEF(saveInHandleScope)
DEF(getPropertyAttributes)
DEF(getOwnPropertyNames)
DEF(beforeContext)
DEF(afterContext)
DEF(getFunctionName)
DEF(getFileName)
DEF(getColumnNumber)
Expand Down
34 changes: 33 additions & 1 deletion deps/chakrashim/src/jsrtcontextshim.cc
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,10 @@ ContextShim::ContextShim(IsolateShim * isolateShim,
getPropertyAttributesFunction(JS_INVALID_REFERENCE),
getOwnPropertyNamesFunction(JS_INVALID_REFERENCE),
jsonParseFunction(JS_INVALID_REFERENCE),
jsonStringifyFunction(JS_INVALID_REFERENCE) {
jsonStringifyFunction(JS_INVALID_REFERENCE),
beforeContextFunction(JS_INVALID_REFERENCE),
afterContextFunction(JS_INVALID_REFERENCE),
cachedDescriptors(JS_INVALID_REFERENCE) {
memset(globalConstructor, 0, sizeof(globalConstructor));
memset(globalPrototypeFunction, 0, sizeof(globalPrototypeFunction));
}
Expand Down Expand Up @@ -482,6 +485,33 @@ void ContextShim::SetAlignedPointerInEmbedderData(int index, void * value) {
}
}

void ContextShim::CacheGlobalProperties() {
JsValueRef beforeContext = this->GetbeforeContextFunction();
JsValueRef result = JS_INVALID_REFERENCE;
if (jsrt::CallFunction(beforeContext, globalObject, &result) != JsNoError) {
return;
}
this->cachedDescriptors = result;
JsAddRef(this->cachedDescriptors, nullptr);
}

void ContextShim::ResolveGlobalChanges(JsValueRef sandbox) {
JsValueRef beforeDescriptors = this->cachedDescriptors;
this->cachedDescriptors = JS_INVALID_REFERENCE;
if (beforeDescriptors == JS_INVALID_REFERENCE) {
return;
}
JsRelease(beforeDescriptors, nullptr);
JsValueRef afterContext = this->GetafterContextFunction();
JsValueRef result = JS_INVALID_REFERENCE;
jsrt::CallFunction(
afterContext,
beforeDescriptors,
globalObject,
sandbox,
&result);
}

// check initialization state first instead of calling
// InitializeCurrentContextShim to save a function call for cases where
// contextshim is already initialized
Expand Down Expand Up @@ -600,6 +630,8 @@ CHAKRASHIM_FUNCTION_GETTER(getPropertyAttributes);
CHAKRASHIM_FUNCTION_GETTER(getOwnPropertyNames);
CHAKRASHIM_FUNCTION_GETTER(jsonParse);
CHAKRASHIM_FUNCTION_GETTER(jsonStringify);
CHAKRASHIM_FUNCTION_GETTER(beforeContext);
CHAKRASHIM_FUNCTION_GETTER(afterContext);

#define DEF_IS_TYPE(F) CHAKRASHIM_FUNCTION_GETTER(F)
#include "jsrtcachedpropertyidref.inc"
Expand Down
7 changes: 7 additions & 0 deletions deps/chakrashim/src/jsrtcontextshim.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ class ContextShim {
void * GetAlignedPointerFromEmbedderData(int index);
void SetAlignedPointerInEmbedderData(int index, void * value);

void CacheGlobalProperties();
void ResolveGlobalChanges(JsValueRef sandbox);

static ContextShim * GetCurrent();

private:
Expand Down Expand Up @@ -138,6 +141,8 @@ class ContextShim {
JsValueRef globalPrototypeFunction[GlobalPrototypeFunction::_FunctionCount];
std::vector<void*> embedderData;

JsValueRef cachedDescriptors;

#define DECLARE_CHAKRASHIM_FUNCTION_GETTER(F) \
public: \
JsValueRef Get##F##Function(); \
Expand Down Expand Up @@ -165,6 +170,8 @@ class ContextShim {
DECLARE_CHAKRASHIM_FUNCTION_GETTER(getOwnPropertyNames);
DECLARE_CHAKRASHIM_FUNCTION_GETTER(jsonParse);
DECLARE_CHAKRASHIM_FUNCTION_GETTER(jsonStringify);
DECLARE_CHAKRASHIM_FUNCTION_GETTER(beforeContext);
DECLARE_CHAKRASHIM_FUNCTION_GETTER(afterContext);
};

} // namespace jsrt
Expand Down
10 changes: 10 additions & 0 deletions deps/chakrashim/src/v8context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -163,4 +163,14 @@ void Context::AllowCodeGenerationFromStrings(bool allow) {
// CHAKRA-TODO
}

void Context::CacheGlobalProperties() {
jsrt::IsolateShim::GetContextShim(
reinterpret_cast<JsContextRef *>(this))->CacheGlobalProperties();
}

void Context::ResolveGlobalChanges(Local<Object> sandbox) {
jsrt::IsolateShim::GetContextShim(
reinterpret_cast<JsContextRef *>(this))
->ResolveGlobalChanges((JsValueRef)*sandbox);
}
} // namespace v8
12 changes: 12 additions & 0 deletions src/node_contextify.cc
Original file line number Diff line number Diff line change
Expand Up @@ -795,11 +795,23 @@ class ContextifyScript : public BaseObject {

// Do the eval within the context
Context::Scope context_scope(contextify_context->context());

#ifdef NODE_ENGINE_CHAKRACORE
// node-chakracore does not handle interceptors on contexts the same
// way that node-v8 does, so we need to patch things up here

contextify_context->context()->CacheGlobalProperties();
#endif

EvalMachine(contextify_context->env(),
timeout,
display_errors,
break_on_sigint,
args);

#ifdef NODE_ENGINE_CHAKRACORE
contextify_context->context()->ResolveGlobalChanges(sandbox);
#endif
}

static void DecorateErrorStack(Environment* env, const TryCatch& try_catch) {
Expand Down
5 changes: 5 additions & 0 deletions test/parallel/parallel.status
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,11 @@ test-v8-untrusted-code-mitigations : SKIP
# https://github.com/Microsoft/ChakraCore/issues/5196
test-repl-top-level-await : SKIP

# This test fails due to the workaround for vm.runInContext not working
# with "var" declarations. Once we have an alternate fix, this can be
# re-enabled
test-vm-proxy-failure-CP : SKIP

# This test seems to fail under system stress, marking it flaky for now
# https://github.com/nodejs/node-chakracore/issues/494
test-async-hooks-destroy-on-gc : PASS,FLAKY
Expand Down

0 comments on commit f8f6b8e

Please sign in to comment.