// 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.h" #include "node_internals.h" #include "node_watchdog.h" #include "base-object.h" #include "base-object-inl.h" #include "env.h" #include "env-inl.h" #include "util.h" #include "util-inl.h" #include "v8-debug.h" namespace node { 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::Integer; using v8::Local; using v8::Maybe; using v8::MaybeLocal; using v8::Name; using v8::NamedPropertyHandlerConfiguration; using v8::Object; using v8::ObjectTemplate; using v8::Persistent; using v8::PropertyAttribute; using v8::PropertyCallbackInfo; using v8::PropertyDescriptor; using v8::Script; using v8::ScriptCompiler; using v8::ScriptOrigin; using v8::String; using v8::TryCatch; using v8::Uint8Array; using v8::UnboundScript; using v8::Value; using v8::WeakCallbackInfo; class ContextifyContext { protected: // V8 reserves the first field in context objects for the debugger. We use the // second field to hold a reference to the sandbox object. enum { kSandboxObjectIndex = 1 }; Environment* const env_; Persistent context_; public: ContextifyContext(Environment* env, Local sandbox_obj) : env_(env) { Local v8_context = CreateV8Context(env, sandbox_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(); } ~ContextifyContext() { context_.Reset(); } inline Environment* env() const { return env_; } inline Local context() const { return PersistentToLocal(env()->isolate(), context_); } inline Local global_proxy() const { return context()->Global(); } inline Local sandbox() const { return Local::Cast(context()->GetEmbedderData(kSandboxObjectIndex)); } // XXX(isaacs): This function only exists because of a shortcoming of // the V8 SetNamedPropertyHandler function. // // It does not provide a way to intercept Object.defineProperty(..) // calls. As a result, these properties are not copied onto the // contextified sandbox when a new global property is added via either // a function declaration or a Object.defineProperty(global, ...) call. // // Note that any function declarations or Object.defineProperty() // globals that are created asynchronously (in a setTimeout, callback, // etc.) will happen AFTER the call to copy properties, and thus not be // caught. // // The way to properly fix this is to add some sort of a // Object::SetNamedDefinePropertyHandler() function that takes a callback, // which receives the property name and property descriptor as arguments. // // Luckily, such situations are rare, and asynchronously-added globals // weren't supported by Node's VM module until 0.12 anyway. But, this // should be fixed properly in V8, and this copy function should be // removed once there is a better way. void CopyProperties() { HandleScope scope(env()->isolate()); Local context = PersistentToLocal(env()->isolate(), context_); Local global = context->Global()->GetPrototype()->ToObject(env()->isolate()); Local sandbox_obj = sandbox(); Local clone_property_method; Local names = global->GetOwnPropertyNames(); int length = names->Length(); for (int i = 0; i < length; i++) { Local key = names->Get(i)->ToString(env()->isolate()); Maybe has = sandbox_obj->HasOwnProperty(context, key); // Check for pending exceptions if (has.IsNothing()) return; if (!has.FromJust()) { Local desc_vm_context = global->GetOwnPropertyDescriptor(context, key) .ToLocalChecked().As(); bool is_accessor = desc_vm_context->Has(context, env()->get_string()).FromJust() || desc_vm_context->Has(context, env()->set_string()).FromJust(); auto define_property_on_sandbox = [&] (PropertyDescriptor* desc) { desc->set_configurable(desc_vm_context ->Get(context, env()->configurable_string()).ToLocalChecked() ->BooleanValue(context).FromJust()); desc->set_enumerable(desc_vm_context ->Get(context, env()->enumerable_string()).ToLocalChecked() ->BooleanValue(context).FromJust()); CHECK(sandbox_obj->DefineProperty(context, key, *desc).FromJust()); }; if (is_accessor) { Local get = desc_vm_context->Get(context, env()->get_string()) .ToLocalChecked().As(); Local set = desc_vm_context->Get(context, env()->set_string()) .ToLocalChecked().As(); PropertyDescriptor desc(get, set); define_property_on_sandbox(&desc); } else { Local value = desc_vm_context->Get(context, env()->value_string()) .ToLocalChecked(); bool writable = desc_vm_context->Get(context, env()->writable_string()) .ToLocalChecked()->BooleanValue(context).FromJust(); PropertyDescriptor desc(value, writable); define_property_on_sandbox(&desc); } } } } // 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 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 CreateV8Context(Environment* env, Local sandbox_obj) { EscapableHandleScope scope(env->isolate()); Local function_template = FunctionTemplate::New(env->isolate()); function_template->SetHiddenPrototype(true); function_template->SetClassName(sandbox_obj->GetConstructorName()); Local object_template = function_template->InstanceTemplate(); NamedPropertyHandlerConfiguration config(GlobalPropertyGetterCallback, GlobalPropertySetterCallback, GlobalPropertyQueryCallback, GlobalPropertyDeleterCallback, GlobalPropertyEnumeratorCallback, CreateDataWrapper(env)); object_template->SetHandler(config); Local ctx = Context::New(env->isolate(), nullptr, 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()); env->AssignToContext(ctx); return scope.Escape(ctx); } static void 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); } static void 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