// Copyright Joyent, Inc. and other Node contributors. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to permit // persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. #include "node_internals.h" #include "node_watchdog.h" #include "base_object-inl.h" #include "v8-debug.h" #include "node_contextify.h" namespace node { namespace contextify { using v8::Array; using v8::ArrayBuffer; using v8::Boolean; using v8::Context; using v8::Debug; using v8::EscapableHandleScope; using v8::External; using v8::Function; using v8::FunctionCallbackInfo; using v8::FunctionTemplate; using v8::HandleScope; using v8::IndexedPropertyHandlerConfiguration; using v8::Integer; using v8::Just; using v8::Local; using v8::Maybe; using v8::MaybeLocal; using v8::Name; using v8::NamedPropertyHandlerConfiguration; using v8::Nothing; using v8::Object; using v8::ObjectTemplate; using v8::PropertyAttribute; using v8::PropertyCallbackInfo; using v8::PropertyDescriptor; using v8::Script; using v8::ScriptCompiler; using v8::ScriptOrigin; using v8::String; using v8::Symbol; using v8::TryCatch; using v8::Uint32; using v8::Uint8Array; using v8::UnboundScript; using v8::Value; using v8::WeakCallbackInfo; // The vm module executes code in a sandboxed environment with a different // global object than the rest of the code. This is achieved by applying // every call that changes or queries a property on the global `this` in the // sandboxed code, to the sandbox object. // // The implementation uses V8's interceptors for methods like `set`, `get`, // `delete`, `defineProperty`, and for any query of the property attributes. // Property handlers with interceptors are set on the object template for // the sandboxed code. Handlers for both named properties and for indexed // properties are used. Their functionality is almost identical, the indexed // interceptors mostly just call the named interceptors. // // For every `get` of a global property in the sandboxed context, the // interceptor callback checks the sandbox object for the property. // If the property is defined on the sandbox, that result is returned to // the original call instead of finishing the query on the global object. // // For every `set` of a global property, the interceptor callback defines or // changes the property both on the sandbox and the global proxy. namespace { // Convert an int to a V8 Name (String or Symbol). Local Uint32ToName(Local context, uint32_t index) { return Uint32::New(context->GetIsolate(), index)->ToString(context) .ToLocalChecked(); } } // anonymous namespace ContextifyContext::ContextifyContext( Environment* env, Local sandbox_obj, Local options_obj) : env_(env) { Local v8_context = CreateV8Context(env, sandbox_obj, options_obj); context_.Reset(env->isolate(), v8_context); // Allocation failure or maximum call stack size reached if (context_.IsEmpty()) return; context_.SetWeak(this, WeakCallback, v8::WeakCallbackType::kParameter); context_.MarkIndependent(); } // This is an object that just keeps an internal pointer to this // ContextifyContext. It's passed to the NamedPropertyHandler. If we // pass the main JavaScript context object we're embedded in, then the // NamedPropertyHandler will store a reference to it forever and keep it // from getting gc'd. Local ContextifyContext::CreateDataWrapper(Environment* env) { EscapableHandleScope scope(env->isolate()); Local wrapper = env->script_data_constructor_function() ->NewInstance(env->context()).FromMaybe(Local()); if (wrapper.IsEmpty()) return scope.Escape(Local::New(env->isolate(), Local())); Wrap(wrapper, this); return scope.Escape(wrapper); } Local ContextifyContext::CreateV8Context( Environment* env, Local sandbox_obj, Local options_obj) { EscapableHandleScope scope(env->isolate()); Local function_template = FunctionTemplate::New(env->isolate()); function_template->SetClassName(sandbox_obj->GetConstructorName()); Local object_template = function_template->InstanceTemplate(); NamedPropertyHandlerConfiguration config(PropertyGetterCallback, PropertySetterCallback, PropertyDescriptorCallback, PropertyDeleterCallback, PropertyEnumeratorCallback, PropertyDefinerCallback, CreateDataWrapper(env)); IndexedPropertyHandlerConfiguration indexed_config( IndexedPropertyGetterCallback, IndexedPropertySetterCallback, IndexedPropertyDescriptorCallback, IndexedPropertyDeleterCallback, PropertyEnumeratorCallback, IndexedPropertyDefinerCallback, CreateDataWrapper(env)); object_template->SetHandler(config); object_template->SetHandler(indexed_config); Local ctx = NewContext(env->isolate(), object_template); if (ctx.IsEmpty()) { env->ThrowError("Could not instantiate context"); return Local(); } ctx->SetSecurityToken(env->context()->GetSecurityToken()); // We need to tie the lifetime of the sandbox object with the lifetime of // newly created context. We do this by making them hold references to each // other. The context can directly hold a reference to the sandbox as an // embedder data field. However, we cannot hold a reference to a v8::Context // directly in an Object, we instead hold onto the new context's global // object instead (which then has a reference to the context). ctx->SetEmbedderData(kSandboxObjectIndex, sandbox_obj); sandbox_obj->SetPrivate(env->context(), env->contextify_global_private_symbol(), ctx->Global()); Local name = options_obj->Get(env->context(), env->name_string()) .ToLocalChecked(); CHECK(name->IsString()); Utf8Value name_val(env->isolate(), name); ContextInfo info(*name_val); Local origin = options_obj->Get(env->context(), FIXED_ONE_BYTE_STRING(env->isolate(), "origin")) .ToLocalChecked(); if (!origin->IsUndefined()) { CHECK(origin->IsString()); Utf8Value origin_val(env->isolate(), origin); info.origin = *origin_val; } env->AssignToContext(ctx, info); return scope.Escape(ctx); } void ContextifyContext::Init(Environment* env, Local target) { Local function_template = FunctionTemplate::New(env->isolate()); function_template->InstanceTemplate()->SetInternalFieldCount(1); env->set_script_data_constructor_function(function_template->GetFunction()); env->SetMethod(target, "runInDebugContext", RunInDebugContext); env->SetMethod(target, "makeContext", MakeContext); env->SetMethod(target, "isContext", IsContext); } void ContextifyContext::RunInDebugContext( const FunctionCallbackInfo& args) { Local script_source(args[0]->ToString(args.GetIsolate())); if (script_source.IsEmpty()) return; // Exception pending. Local debug_context = Debug::GetDebugContext(args.GetIsolate()); Environment* env = Environment::GetCurrent(args); if (debug_context.IsEmpty()) { // Force-load the debug context. auto dummy_event_listener = [] (const Debug::EventDetails&) {}; Debug::SetDebugEventListener(args.GetIsolate(), dummy_event_listener); debug_context = Debug::GetDebugContext(args.GetIsolate()); CHECK(!debug_context.IsEmpty()); // Ensure that the debug context has an Environment assigned in case // a fatal error is raised. The fatal exception handler in node.cc // is not equipped to deal with contexts that don't have one and // can't easily be taught that due to a deficiency in the V8 API: // there is no way for the embedder to tell if the data index is // in use. const int index = Environment::kContextEmbedderDataIndex; debug_context->SetAlignedPointerInEmbedderData(index, env); } Context::Scope context_scope(debug_context); MaybeLocal