From 9df5adf4ffb41ae2032a6dccaac346bf48df852c Mon Sep 17 00:00:00 2001 From: Gabor Krizsanits Date: Thu, 22 Aug 2013 08:26:37 +0200 Subject: [PATCH] Bug 886237 - Splitting up XPCComponents. r=bholley --- js/xpconnect/src/Sandbox.cpp | 1573 ++++++++++++++++++++++++++++ js/xpconnect/src/XPCComponents.cpp | 1545 +-------------------------- js/xpconnect/src/moz.build | 1 + js/xpconnect/src/xpcprivate.h | 15 + 4 files changed, 1595 insertions(+), 1539 deletions(-) create mode 100644 js/xpconnect/src/Sandbox.cpp diff --git a/js/xpconnect/src/Sandbox.cpp b/js/xpconnect/src/Sandbox.cpp new file mode 100644 index 000000000000..93b05c797ac7 --- /dev/null +++ b/js/xpconnect/src/Sandbox.cpp @@ -0,0 +1,1573 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * The Components.Sandbox object. + */ + +#include "AccessCheck.h" +#include "jsdbgapi.h" +#include "jsfriendapi.h" +#include "jsproxy.h" +#include "nsContentUtils.h" +#include "nsCxPusher.h" +#include "nsGlobalWindow.h" +#include "nsIDOMWindow.h" +#include "nsIScriptContext.h" +#include "nsIScriptObjectPrincipal.h" +#include "nsIScriptSecurityManager.h" +#include "nsIURI.h" +#include "nsJSEnvironment.h" +#include "nsJSUtils.h" +#include "nsNetUtil.h" +#include "nsNullPrincipal.h" +#include "nsPrincipal.h" +#include "nsXMLHttpRequest.h" +#include "WrapperFactory.h" +#include "XPCJSWeakReference.h" +#include "xpcprivate.h" +#include "XPCQuickStubs.h" +#include "XPCWrapper.h" +#include "XrayWrapper.h" +#include "mozilla/dom/BindingUtils.h" + +using namespace mozilla; +using namespace js; +using namespace xpc; + +using mozilla::dom::DestroyProtoAndIfaceCache; + +NS_IMPL_ISUPPORTS3(SandboxPrivate, + nsIScriptObjectPrincipal, + nsIGlobalObject, + nsISupportsWeakReference) + +const char kScriptSecurityManagerContractID[] = NS_SCRIPTSECURITYMANAGER_CONTRACTID; + +class nsXPCComponents_utils_Sandbox : public nsIXPCComponents_utils_Sandbox, + public nsIXPCScriptable +{ +public: + // Aren't macros nice? + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIXPCCOMPONENTS_UTILS_SANDBOX + NS_DECL_NSIXPCSCRIPTABLE + +public: + nsXPCComponents_utils_Sandbox(); + virtual ~nsXPCComponents_utils_Sandbox(); + +private: + static nsresult CallOrConstruct(nsIXPConnectWrappedNative *wrapper, + JSContext *cx, HandleObject obj, + const CallArgs &args, bool *_retval); +}; + +already_AddRefed +NewSandboxConstructor() +{ + nsCOMPtr sbConstructor = + new nsXPCComponents_utils_Sandbox(); + return sbConstructor.forget(); +} + +static bool +SandboxDump(JSContext *cx, unsigned argc, jsval *vp) +{ + JSString *str; + if (!argc) + return true; + + str = JS_ValueToString(cx, JS_ARGV(cx, vp)[0]); + if (!str) + return false; + + size_t length; + const jschar *chars = JS_GetStringCharsZAndLength(cx, str, &length); + if (!chars) + return false; + + nsDependentString wstr(chars, length); + char *cstr = ToNewUTF8String(wstr); + if (!cstr) + return false; + +#if defined(XP_MACOSX) + // Be nice and convert all \r to \n. + char *c = cstr, *cEnd = cstr + strlen(cstr); + while (c < cEnd) { + if (*c == '\r') + *c = '\n'; + c++; + } +#endif + + fputs(cstr, stdout); + fflush(stdout); + NS_Free(cstr); + JS_SET_RVAL(cx, vp, JSVAL_TRUE); + return true; +} + +static bool +SandboxDebug(JSContext *cx, unsigned argc, jsval *vp) +{ +#ifdef DEBUG + return SandboxDump(cx, argc, vp); +#else + return true; +#endif +} + +static bool +SandboxImport(JSContext *cx, unsigned argc, Value *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + if (args.length() < 1 || args[0].isPrimitive()) { + XPCThrower::Throw(NS_ERROR_INVALID_ARG, cx); + return false; + } + + RootedString funname(cx); + if (args.length() > 1) { + // Use the second parameter as the function name. + funname = JS_ValueToString(cx, args[1]); + if (!funname) + return false; + } else { + // NB: funobj must only be used to get the JSFunction out. + RootedObject funobj(cx, &args[0].toObject()); + if (js::IsProxy(funobj)) { + funobj = XPCWrapper::UnsafeUnwrapSecurityWrapper(funobj); + } + + JSAutoCompartment ac(cx, funobj); + + JSFunction *fun = JS_ValueToFunction(cx, ObjectValue(*funobj)); + if (!fun) { + XPCThrower::Throw(NS_ERROR_INVALID_ARG, cx); + return false; + } + + // Use the actual function name as the name. + funname = JS_GetFunctionId(fun); + if (!funname) { + XPCThrower::Throw(NS_ERROR_INVALID_ARG, cx); + return false; + } + } + + RootedId id(cx); + if (!JS_ValueToId(cx, StringValue(funname), id.address())) + return false; + + // We need to resolve the this object, because this function is used + // unbound and should still work and act on the original sandbox. + RootedObject thisObject(cx, JS_THIS_OBJECT(cx, vp)); + if (!thisObject) { + XPCThrower::Throw(NS_ERROR_UNEXPECTED, cx); + return false; + } + if (!JS_SetPropertyById(cx, thisObject, id, args[0])) + return false; + + args.rval().setUndefined(); + return true; +} + +static bool +CreateXMLHttpRequest(JSContext *cx, unsigned argc, jsval *vp) +{ + nsIScriptSecurityManager *ssm = XPCWrapper::GetSecurityManager(); + if (!ssm) + return false; + + nsIPrincipal *subjectPrincipal = ssm->GetCxSubjectPrincipal(cx); + if (!subjectPrincipal) + return false; + + RootedObject global(cx, JS::CurrentGlobalOrNull(cx)); + MOZ_ASSERT(global); + + nsIScriptObjectPrincipal *sop = + static_cast(xpc_GetJSPrivate(global)); + nsCOMPtr iglobal = do_QueryInterface(sop); + + nsCOMPtr xhr = new nsXMLHttpRequest(); + nsresult rv = xhr->Init(subjectPrincipal, nullptr, iglobal, nullptr); + if (NS_FAILED(rv)) + return false; + + rv = nsContentUtils::WrapNative(cx, global, xhr, vp); + if (NS_FAILED(rv)) + return false; + + return true; +} + +/* + * Instead of simply wrapping a function into another compartment, + * this helper function creates a native function in the target + * compartment and forwards the call to the original function. + * That call will be different than a regular JS function call in + * that, the |this| is left unbound, and all the non-native JS + * object arguments will be cloned using the structured clone + * algorithm. + * The return value is the new forwarder function, wrapped into + * the caller's compartment. + * The 3rd argument is the name of the property that will + * be set on the target scope, with the forwarder function as + * the value. + * The principal of the caller must subsume that of the target. + * + * Expected type of the arguments and the return value: + * function exportFunction(function funToExport, + * object targetScope, + * string name) + */ +static bool +ExportFunction(JSContext *cx, unsigned argc, jsval *vp) +{ + MOZ_ASSERT(cx); + if (argc < 3) { + JS_ReportError(cx, "Function requires at least 3 arguments"); + return false; + } + + CallArgs args = CallArgsFromVp(argc, vp); + if (!args[0].isObject() || !args[1].isObject() || !args[2].isString()) { + JS_ReportError(cx, "Invalid argument"); + return false; + } + + RootedObject funObj(cx, &args[0].toObject()); + RootedObject targetScope(cx, &args[1].toObject()); + RootedString funName(cx, args[2].toString()); + + // We can only export functions to scopes those are transparent for us, + // so if there is a security wrapper around targetScope we must throw. + targetScope = CheckedUnwrap(targetScope); + if (!targetScope) { + JS_ReportError(cx, "Permission denied to export function into scope"); + return false; + } + + if (JS_GetStringLength(funName) == 0) { + JS_ReportError(cx, "3rd argument should be a non-empty string"); + return false; + } + + { + // We need to operate in the target scope from here on, let's enter + // its compartment. + JSAutoCompartment ac(cx, targetScope); + + // Unwrapping to see if we have a callable. + funObj = UncheckedUnwrap(funObj); + if (!JS_ObjectIsCallable(cx, funObj)) { + JS_ReportError(cx, "First argument must be a function"); + return false; + } + + // The function forwarder will live in the target compartment. Since + // this function will be referenced from its private slot, to avoid a + // GC hazard, we must wrap it to the same compartment. + if (!JS_WrapObject(cx, funObj.address())) + return false; + + RootedId id(cx); + if (!JS_ValueToId(cx, args[2], id.address())) + return false; + + // And now, let's create the forwarder function in the target compartment + // for the function the be exported. + if (!NewFunctionForwarder(cx, id, funObj, /* doclone = */ true, args.rval())) { + JS_ReportError(cx, "Exporting function failed"); + return false; + } + + // We have the forwarder function in the target compartment, now + // we have to add it to the target scope as a property. + if (!JS_DefinePropertyById(cx, targetScope, id, args.rval(), + JS_PropertyStub, JS_StrictPropertyStub, + JSPROP_ENUMERATE)) + return false; + } + + // Finally we have to re-wrap the exported function back to the caller compartment. + if (!JS_WrapValue(cx, args.rval().address())) + return false; + + return true; +} + +static bool +GetFilenameAndLineNumber(JSContext *cx, nsACString &filename, unsigned &lineno) +{ + JSScript *script; + if (JS_DescribeScriptedCaller(cx, &script, &lineno)) { + if (const char *cfilename = JS_GetScriptFilename(cx, script)) { + filename.Assign(nsDependentCString(cfilename)); + return true; + } + } + return false; +} + +namespace xpc { +bool +IsReflector(JSObject *obj) +{ + return IS_WN_REFLECTOR(obj) || dom::IsDOMObject(obj); +} +} /* namespace xpc */ + +enum ForwarderCloneTags { + SCTAG_BASE = JS_SCTAG_USER_MIN, + SCTAG_REFLECTOR +}; + +static JSObject * +CloneNonReflectorsRead(JSContext *cx, JSStructuredCloneReader *reader, uint32_t tag, + uint32_t data, void *closure) +{ + MOZ_ASSERT(closure, "Null pointer!"); + AutoObjectVector *reflectors = static_cast(closure); + if (tag == SCTAG_REFLECTOR) { + MOZ_ASSERT(!data); + + size_t idx; + if (JS_ReadBytes(reader, &idx, sizeof(size_t))) { + RootedObject reflector(cx, reflectors->handleAt(idx)); + MOZ_ASSERT(reflector, "No object pointer?"); + MOZ_ASSERT(IsReflector(reflector), "Object pointer must be a reflector!"); + + JS_WrapObject(cx, reflector.address()); + JS_ASSERT(WrapperFactory::IsXrayWrapper(reflector) || + IsReflector(reflector)); + + return reflector; + } + } + + JS_ReportError(cx, "CloneNonReflectorsRead error"); + return nullptr; +} + +static bool +CloneNonReflectorsWrite(JSContext *cx, JSStructuredCloneWriter *writer, + Handle obj, void *closure) +{ + MOZ_ASSERT(closure, "Null pointer!"); + + // We need to maintain a list of reflectors to make sure all these objects + // are properly rooter. Only their indices will be serialized. + AutoObjectVector *reflectors = static_cast(closure); + if (IsReflector(obj)) { + if (!reflectors->append(obj)) + return false; + + size_t idx = reflectors->length()-1; + if (JS_WriteUint32Pair(writer, SCTAG_REFLECTOR, 0) && + JS_WriteBytes(writer, &idx, sizeof(size_t))) { + return true; + } + } + + JS_ReportError(cx, "CloneNonReflectorsWrite error"); + return false; +} + +JSStructuredCloneCallbacks gForwarderStructuredCloneCallbacks = { + CloneNonReflectorsRead, + CloneNonReflectorsWrite, + nullptr +}; + +/* + * This is a special structured cloning, that clones only non-reflectors. + * The function assumes the cx is already entered the compartment we want + * to clone to, and that if val is an object is from the compartment we + * clone from. + */ +bool +CloneNonReflectors(JSContext *cx, MutableHandleValue val) +{ + JSAutoStructuredCloneBuffer buffer; + AutoObjectVector rootedReflectors(cx); + { + // For parsing val we have to enter its compartment. + // (unless it's a primitive) + Maybe ac; + if (val.isObject()) { + ac.construct(cx, &val.toObject()); + } + + if (!buffer.write(cx, val, + &gForwarderStructuredCloneCallbacks, + &rootedReflectors)) + { + return false; + } + } + + // Now recreate the clones in the target compartment. + RootedValue rval(cx); + if (!buffer.read(cx, val.address(), + &gForwarderStructuredCloneCallbacks, + &rootedReflectors)) + { + return false; + } + + return true; +} + +/* + * Similar to evalInSandbox except this one is used to eval a script in the + * scope of a window. Also note, that the return value and the possible exceptions + * in the script are structured cloned, unless they are natives (then they are just + * wrapped). + * Principal of the caller must subsume the target's. + * + * Expected type of the arguments: + * value evalInWindow(string script, + * object window) + */ +static bool +EvalInWindow(JSContext *cx, unsigned argc, jsval *vp) +{ + MOZ_ASSERT(cx); + if (argc < 2) { + JS_ReportError(cx, "Function requires two arguments"); + return false; + } + + CallArgs args = CallArgsFromVp(argc, vp); + if (!args[0].isString() || !args[1].isObject()) { + JS_ReportError(cx, "Invalid arguments"); + return false; + } + + RootedString srcString(cx, args[0].toString()); + RootedObject targetScope(cx, &args[1].toObject()); + + // If we cannot unwrap we must not eval in it. + targetScope = CheckedUnwrap(targetScope); + if (!targetScope) { + JS_ReportError(cx, "Permission denied to eval in target scope"); + return false; + } + + // Make sure that we have a window object. + RootedObject inner(cx, CheckedUnwrap(targetScope, /* stopAtOuter = */ false)); + nsCOMPtr global; + nsCOMPtr window; + if (!JS_IsGlobalObject(inner) || + !(global = GetNativeForGlobal(inner)) || + !(window = do_QueryInterface(global))) + { + JS_ReportError(cx, "Second argument must be a window"); + return false; + } + + nsCOMPtr context = + (static_cast(window.get()))->GetScriptContext(); + if (!context) { + JS_ReportError(cx, "Script context needed"); + return false; + } + + if (!context->GetScriptsEnabled()) { + JS_ReportError(cx, "Scripts are disabled in this window"); + return false; + } + + nsCString filename; + unsigned lineNo; + if (!GetFilenameAndLineNumber(cx, filename, lineNo)) { + // Default values for non-scripted callers. + filename.Assign("Unknown"); + lineNo = 0; + } + + nsDependentJSString srcDepString; + srcDepString.init(cx, srcString); + + { + // CompileOptions must be created from the context + // we will execute this script in. + JSContext *wndCx = context->GetNativeContext(); + AutoCxPusher pusher(wndCx); + JS::CompileOptions compileOptions(wndCx); + compileOptions.setFileAndLine(filename.get(), lineNo); + + // We don't want the JS engine to automatically report + // uncaught exceptions. + nsJSUtils::EvaluateOptions evaluateOptions; + evaluateOptions.setReportUncaught(false); + + nsresult rv = nsJSUtils::EvaluateString(wndCx, + srcDepString, + targetScope, + compileOptions, + evaluateOptions, + args.rval().address()); + + if (NS_FAILED(rv)) { + // If there was an exception we get it as a return value, if + // the evaluation failed for some other reason, then a default + // exception is raised. + MOZ_ASSERT(!JS_IsExceptionPending(wndCx), + "Exception should be delivered as return value."); + if (args.rval().isUndefined()) { + MOZ_ASSERT(rv == NS_ERROR_OUT_OF_MEMORY); + return false; + } + + // If there was an exception thrown we should set it + // on the calling context. + RootedValue exn(wndCx, args.rval()); + // First we should reset the return value. + args.rval().set(UndefinedValue()); + + // Then clone the exception. + if (CloneNonReflectors(cx, &exn)) + JS_SetPendingException(cx, exn); + + return false; + } + } + + // Let's clone the return value back to the callers compartment. + if (!CloneNonReflectors(cx, args.rval())) { + args.rval().set(UndefinedValue()); + return false; + } + + return true; +} + +static bool +sandbox_enumerate(JSContext *cx, HandleObject obj) +{ + return JS_EnumerateStandardClasses(cx, obj); +} + +static bool +sandbox_resolve(JSContext *cx, HandleObject obj, HandleId id) +{ + bool resolved; + return JS_ResolveStandardClass(cx, obj, id, &resolved); +} + +static void +sandbox_finalize(JSFreeOp *fop, JSObject *obj) +{ + nsIScriptObjectPrincipal *sop = + static_cast(xpc_GetJSPrivate(obj)); + MOZ_ASSERT(sop); + static_cast(sop)->ForgetGlobalObject(); + NS_IF_RELEASE(sop); + DestroyProtoAndIfaceCache(obj); +} + +static bool +sandbox_convert(JSContext *cx, HandleObject obj, JSType type, MutableHandleValue vp) +{ + if (type == JSTYPE_OBJECT) { + vp.set(OBJECT_TO_JSVAL(obj)); + return true; + } + + return JS_ConvertStub(cx, obj, type, vp); +} + +static JSClass SandboxClass = { + "Sandbox", + XPCONNECT_GLOBAL_FLAGS, + JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, + sandbox_enumerate, sandbox_resolve, sandbox_convert, sandbox_finalize, + NULL, NULL, NULL, NULL, TraceXPCGlobal +}; + +static const JSFunctionSpec SandboxFunctions[] = { + JS_FS("dump", SandboxDump, 1,0), + JS_FS("debug", SandboxDebug, 1,0), + JS_FS("importFunction", SandboxImport, 1,0), + JS_FS_END +}; + +bool +IsSandbox(JSObject *obj) +{ + return GetObjectJSClass(obj) == &SandboxClass; +} + +/***************************************************************************/ +nsXPCComponents_utils_Sandbox::nsXPCComponents_utils_Sandbox() +{ +} + +nsXPCComponents_utils_Sandbox::~nsXPCComponents_utils_Sandbox() +{ +} + +NS_INTERFACE_MAP_BEGIN(nsXPCComponents_utils_Sandbox) + NS_INTERFACE_MAP_ENTRY(nsIXPCComponents_utils_Sandbox) + NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCComponents_utils_Sandbox) +NS_INTERFACE_MAP_END_THREADSAFE + +NS_IMPL_ADDREF(nsXPCComponents_utils_Sandbox) +NS_IMPL_RELEASE(nsXPCComponents_utils_Sandbox) + +// We use the nsIXPScriptable macros to generate lots of stuff for us. +#define XPC_MAP_CLASSNAME nsXPCComponents_utils_Sandbox +#define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_utils_Sandbox" +#define XPC_MAP_WANT_CALL +#define XPC_MAP_WANT_CONSTRUCT +#define XPC_MAP_FLAGS 0 +#include "xpc_map_end.h" /* This #undef's the above. */ + +xpc::SandboxProxyHandler xpc::sandboxProxyHandler; + +bool +xpc::IsSandboxPrototypeProxy(JSObject *obj) +{ + return js::IsProxy(obj) && + js::GetProxyHandler(obj) == &xpc::sandboxProxyHandler; +} + +bool +xpc::SandboxCallableProxyHandler::call(JSContext *cx, JS::Handle proxy, + const JS::CallArgs &args) +{ + // We forward the call to our underlying callable. + + // The parent of our proxy is the SandboxProxyHandler proxy + RootedObject sandboxProxy(cx, JS_GetParent(proxy)); + MOZ_ASSERT(js::IsProxy(sandboxProxy) && + js::GetProxyHandler(sandboxProxy) == &xpc::sandboxProxyHandler); + + // The parent of the sandboxProxy is the sandbox global, and the + // target object is the original proto. + RootedObject sandboxGlobal(cx, JS_GetParent(sandboxProxy)); + MOZ_ASSERT(js::GetObjectJSClass(sandboxGlobal) == &SandboxClass); + + // If our this object is the sandbox global, we call with this set to the + // original proto instead. + // + // There are two different ways we can compute |this|. If we use + // JS_THIS_VALUE, we'll get the bonafide |this| value as passed by the + // caller, which may be undefined if a global function was invoked without + // an explicit invocant. If we use JS_THIS or JS_THIS_OBJECT, the |this| + // in |vp| will be coerced to the global, which is not the correct + // behavior in ES5 strict mode. And we have no way to compute strictness + // here. + // + // The naive approach is simply to use JS_THIS_VALUE here. If |this| was + // explicit, we can remap it appropriately. If it was implicit, then we + // leave it as undefined, and let the callee sort it out. Since the callee + // is generally in the same compartment as its global (eg the Window's + // compartment, not the Sandbox's), the callee will generally compute the + // correct |this|. + // + // However, this breaks down in the Xray case. If the sandboxPrototype + // is an Xray wrapper, then we'll end up reifying the native methods in + // the Sandbox's scope, which means that they'll compute |this| to be the + // Sandbox, breaking old-style XPC_WN_CallMethod methods. + // + // Luckily, the intent of Xrays is to provide a vanilla view of a foreign + // DOM interface, which means that we don't care about script-enacted + // strictness in the prototype's home compartment. Indeed, since DOM + // methods are always non-strict, we can just assume non-strict semantics + // if the sandboxPrototype is an Xray Wrapper, which lets us appropriately + // remap |this|. + JS::Value thisVal = + WrapperFactory::IsXrayWrapper(sandboxProxy) ? args.computeThis(cx) : args.thisv(); + if (thisVal == ObjectValue(*sandboxGlobal)) { + thisVal = ObjectValue(*js::GetProxyTargetObject(sandboxProxy)); + } + + return JS::Call(cx, thisVal, js::GetProxyPrivate(proxy), args.length(), args.array(), + args.rval()); +} + +xpc::SandboxCallableProxyHandler xpc::sandboxCallableProxyHandler; + +// Wrap a callable such that if we're called with oldThisObj as the +// "this" we will instead call it with newThisObj as the this. +static JSObject* +WrapCallable(JSContext *cx, JSObject *callable, JSObject *sandboxProtoProxy) +{ + MOZ_ASSERT(JS_ObjectIsCallable(cx, callable)); + // Our proxy is wrapping the callable. So we need to use the + // callable as the private. We use the given sandboxProtoProxy as + // the parent, and our call() hook depends on that. + MOZ_ASSERT(js::IsProxy(sandboxProtoProxy) && + js::GetProxyHandler(sandboxProtoProxy) == + &xpc::sandboxProxyHandler); + + RootedValue priv(cx, ObjectValue(*callable)); + return js::NewProxyObject(cx, &xpc::sandboxCallableProxyHandler, + priv, nullptr, + sandboxProtoProxy, js::ProxyIsCallable); +} + +template +bool BindPropertyOp(JSContext *cx, Op &op, JSPropertyDescriptor *desc, HandleId id, + unsigned attrFlag, HandleObject sandboxProtoProxy) +{ + if (!op) { + return true; + } + + RootedObject func(cx); + if (desc->attrs & attrFlag) { + // Already an object + func = JS_FUNC_TO_DATA_PTR(JSObject *, op); + } else { + // We have an actual property op. For getters, we use 0 + // args, for setters we use 1 arg. + uint32_t args = (attrFlag == JSPROP_GETTER) ? 0 : 1; + RootedObject obj(cx, desc->obj); + func = GeneratePropertyOp(cx, obj, id, args, op); + if (!func) + return false; + } + func = WrapCallable(cx, func, sandboxProtoProxy); + if (!func) + return false; + op = JS_DATA_TO_FUNC_PTR(Op, func.get()); + desc->attrs |= attrFlag; + return true; +} + +extern bool +XPC_WN_Helper_GetProperty(JSContext *cx, HandleObject obj, HandleId id, MutableHandleValue vp); +extern bool +XPC_WN_Helper_SetProperty(JSContext *cx, HandleObject obj, HandleId id, bool strict, MutableHandleValue vp); + +bool +xpc::SandboxProxyHandler::getPropertyDescriptor(JSContext *cx, + JS::Handle proxy, + JS::Handle id, + JS::MutableHandle desc, + unsigned flags) +{ + JS::RootedObject obj(cx, wrappedObject(proxy)); + + MOZ_ASSERT(js::GetObjectCompartment(obj) == js::GetObjectCompartment(proxy)); + if (!JS_GetPropertyDescriptorById(cx, obj, id, + flags, desc)) + return false; + + if (!desc.object()) + return true; // No property, nothing to do + + // Now fix up the getter/setter/value as needed to be bound to desc->obj + // Don't mess with holder_get and holder_set, though, because those rely on + // the "vp is prefilled with the value in the slot" behavior that property + // ops can in theory rely on, but our property op forwarder doesn't know how + // to make that happen. Since we really only need to rebind the DOM methods + // here, not rebindings holder_get and holder_set is OK. + // + // Similarly, don't mess with XPC_WN_Helper_GetProperty and + // XPC_WN_Helper_SetProperty, for the same reasons: that could confuse our + // access to expandos when we're not doing Xrays. + if (desc.getter() != xpc::holder_get && + desc.getter() != XPC_WN_Helper_GetProperty && + !BindPropertyOp(cx, desc.getter(), desc.address(), id, JSPROP_GETTER, proxy)) + return false; + if (desc.setter() != xpc::holder_set && + desc.setter() != XPC_WN_Helper_SetProperty && + !BindPropertyOp(cx, desc.setter(), desc.address(), id, JSPROP_SETTER, proxy)) + return false; + if (desc.value().isObject()) { + JSObject* val = &desc.value().toObject(); + if (JS_ObjectIsCallable(cx, val)) { + val = WrapCallable(cx, val, proxy); + if (!val) + return false; + desc.value().setObject(*val); + } + } + + return true; +} + +bool +xpc::SandboxProxyHandler::getOwnPropertyDescriptor(JSContext *cx, + JS::Handle proxy, + JS::Handle id, + JS::MutableHandle desc, + unsigned flags) +{ + if (!getPropertyDescriptor(cx, proxy, id, desc, flags)) + return false; + + if (desc.object() != wrappedObject(proxy)) + desc.object().set(nullptr); + + return true; +} + +/* + * Reuse the BaseProxyHandler versions of the derived traps that are implemented + * in terms of the fundamental traps. + */ + +bool +xpc::SandboxProxyHandler::has(JSContext *cx, JS::Handle proxy, + JS::Handle id, bool *bp) +{ + return BaseProxyHandler::has(cx, proxy, id, bp); +} +bool +xpc::SandboxProxyHandler::hasOwn(JSContext *cx, JS::Handle proxy, + JS::Handle id, bool *bp) +{ + return BaseProxyHandler::hasOwn(cx, proxy, id, bp); +} + +bool +xpc::SandboxProxyHandler::get(JSContext *cx, JS::Handle proxy, + JS::Handle receiver, + JS::Handle id, + JS::MutableHandle vp) +{ + return BaseProxyHandler::get(cx, proxy, receiver, id, vp); +} + +bool +xpc::SandboxProxyHandler::set(JSContext *cx, JS::Handle proxy, + JS::Handle receiver, + JS::Handle id, + bool strict, + JS::MutableHandle vp) +{ + return BaseProxyHandler::set(cx, proxy, receiver, id, strict, vp); +} + +bool +xpc::SandboxProxyHandler::keys(JSContext *cx, JS::Handle proxy, + AutoIdVector &props) +{ + return BaseProxyHandler::keys(cx, proxy, props); +} + +bool +xpc::SandboxProxyHandler::iterate(JSContext *cx, JS::Handle proxy, + unsigned flags, JS::MutableHandle vp) +{ + return BaseProxyHandler::iterate(cx, proxy, flags, vp); +} + +nsresult +xpc_CreateSandboxObject(JSContext *cx, jsval *vp, nsISupports *prinOrSop, SandboxOptions& options) +{ + // Create the sandbox global object + nsresult rv; + nsCOMPtr xpc(do_GetService(nsIXPConnect::GetCID(), &rv)); + if (NS_FAILED(rv)) + return NS_ERROR_XPC_UNEXPECTED; + + nsCOMPtr principal = do_QueryInterface(prinOrSop); + if (!principal) { + nsCOMPtr sop = do_QueryInterface(prinOrSop); + if (sop) { + principal = sop->GetPrincipal(); + } else { + principal = do_CreateInstance("@mozilla.org/nullprincipal;1", &rv); + MOZ_ASSERT(NS_FAILED(rv) || principal, "Bad return from do_CreateInstance"); + + if (!principal || NS_FAILED(rv)) { + if (NS_SUCCEEDED(rv)) { + rv = NS_ERROR_FAILURE; + } + + return rv; + } + } + MOZ_ASSERT(principal); + } + + JS::CompartmentOptions compartmentOptions; + compartmentOptions.setZone(options.sameZoneAs + ? JS::SameZoneAs(js::UncheckedUnwrap(options.sameZoneAs)) + : JS::SystemZone); + RootedObject sandbox(cx, xpc::CreateGlobalObject(cx, &SandboxClass, + principal, compartmentOptions)); + if (!sandbox) + return NS_ERROR_FAILURE; + + // Set up the wantXrays flag, which indicates whether xrays are desired even + // for same-origin access. + // + // This flag has historically been ignored for chrome sandboxes due to + // quirks in the wrapping implementation that have now been removed. Indeed, + // same-origin Xrays for chrome->chrome access seems a bit superfluous. + // Arguably we should just flip the default for chrome and still honor the + // flag, but such a change would break code in subtle ways for minimal + // benefit. So we just switch it off here. + xpc::GetCompartmentPrivate(sandbox)->wantXrays = + AccessCheck::isChrome(sandbox) ? false : options.wantXrays; + + { + JSAutoCompartment ac(cx, sandbox); + + if (options.proto) { + bool ok = JS_WrapObject(cx, options.proto.address()); + if (!ok) + return NS_ERROR_XPC_UNEXPECTED; + + if (xpc::WrapperFactory::IsXrayWrapper(options.proto) && !options.wantXrays) { + RootedValue v(cx, ObjectValue(*options.proto)); + if (!xpc::WrapperFactory::WaiveXrayAndWrap(cx, v.address())) + return NS_ERROR_FAILURE; + options.proto = &v.toObject(); + } + + // Now check what sort of thing we've got in |proto| + JSObject *unwrappedProto = js::UncheckedUnwrap(options.proto, false); + js::Class *unwrappedClass = js::GetObjectClass(unwrappedProto); + if (IS_WN_CLASS(unwrappedClass) || + mozilla::dom::IsDOMClass(Jsvalify(unwrappedClass))) { + // Wrap it up in a proxy that will do the right thing in terms + // of this-binding for methods. + RootedValue priv(cx, ObjectValue(*options.proto)); + options.proto = js::NewProxyObject(cx, &xpc::sandboxProxyHandler, + priv, nullptr, sandbox); + if (!options.proto) + return NS_ERROR_OUT_OF_MEMORY; + } + + ok = JS_SetPrototype(cx, sandbox, options.proto); + if (!ok) + return NS_ERROR_XPC_UNEXPECTED; + } + + nsCOMPtr sbp = + new SandboxPrivate(principal, sandbox); + + // Pass on ownership of sbp to |sandbox|. + JS_SetPrivate(sandbox, sbp.forget().get()); + + bool allowComponents = nsContentUtils::IsSystemPrincipal(principal) || + nsContentUtils::IsExpandedPrincipal(principal); + if (options.wantComponents && allowComponents && + !nsXPCComponents::AttachComponentsObject(cx, GetObjectScope(sandbox))) + return NS_ERROR_XPC_UNEXPECTED; + + if (!XPCNativeWrapper::AttachNewConstructorObject(cx, sandbox)) + return NS_ERROR_XPC_UNEXPECTED; + + if (!JS_DefineFunctions(cx, sandbox, SandboxFunctions)) + return NS_ERROR_XPC_UNEXPECTED; + + if (options.wantXHRConstructor && + !JS_DefineFunction(cx, sandbox, "XMLHttpRequest", CreateXMLHttpRequest, 0, JSFUN_CONSTRUCTOR)) + return NS_ERROR_XPC_UNEXPECTED; + + if (options.wantExportHelpers && + (!JS_DefineFunction(cx, sandbox, "exportFunction", ExportFunction, 3, 0) || + !JS_DefineFunction(cx, sandbox, "evalInWindow", EvalInWindow, 2, 0))) + return NS_ERROR_XPC_UNEXPECTED; + + } + + if (vp) { + // We have this crazy behavior where wantXrays=false also implies that the + // returned sandbox is implicitly waived. We've stopped advertising it, but + // keep supporting it for now. + *vp = OBJECT_TO_JSVAL(sandbox); + if (options.wantXrays && !JS_WrapValue(cx, vp)) + return NS_ERROR_UNEXPECTED; + if (!options.wantXrays && !xpc::WrapperFactory::WaiveXrayAndWrap(cx, vp)) + return NS_ERROR_UNEXPECTED; + } + + // Set the location information for the new global, so that tools like + // about:memory may use that information + xpc::SetLocationForGlobal(sandbox, options.sandboxName); + + JS_FireOnNewGlobalObject(cx, sandbox); + + return NS_OK; +} + +/* bool call(in nsIXPConnectWrappedNative wrapper, + in JSContextPtr cx, + in JSObjectPtr obj, + in uint32_t argc, + in JSValPtr argv, + in JSValPtr vp); +*/ +NS_IMETHODIMP +nsXPCComponents_utils_Sandbox::Call(nsIXPConnectWrappedNative *wrapper, JSContext *cx, + JSObject *objArg, const CallArgs &args, bool *_retval) +{ + RootedObject obj(cx, objArg); + return CallOrConstruct(wrapper, cx, obj, args, _retval); +} + +/* bool construct(in nsIXPConnectWrappedNative wrapper, + in JSContextPtr cx, + in JSObjectPtr obj, + in uint32_t argc, + in JSValPtr argv, + in JSValPtr vp); +*/ +NS_IMETHODIMP +nsXPCComponents_utils_Sandbox::Construct(nsIXPConnectWrappedNative *wrapper, JSContext *cx, + JSObject *objArg, const CallArgs &args, bool *_retval) +{ + RootedObject obj(cx, objArg); + return CallOrConstruct(wrapper, cx, obj, args, _retval); +} + +// for sandbox constructor the first argument can be a URI string in which case +// we use the related Codebase Principal for the sandbox +nsresult +GetPrincipalFromString(JSContext *cx, HandleString codebase, nsIPrincipal **principal) +{ + MOZ_ASSERT(principal); + MOZ_ASSERT(codebase); + nsCOMPtr uri; + nsDependentJSString codebaseStr; + NS_ENSURE_TRUE(codebaseStr.init(cx, codebase), NS_ERROR_FAILURE); + nsresult rv = NS_NewURI(getter_AddRefs(uri), codebaseStr); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr secman = + do_GetService(kScriptSecurityManagerContractID); + NS_ENSURE_TRUE(secman, NS_ERROR_FAILURE); + + // We could allow passing in the app-id and browser-element info to the + // sandbox constructor. But creating a sandbox based on a string is a + // deprecated API so no need to add features to it. + rv = secman->GetNoAppCodebasePrincipal(uri, principal); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(*principal, NS_ERROR_FAILURE); + + return NS_OK; +} + +// for sandbox constructor the first argument can be a principal object or +// a script object principal (Document, Window) +nsresult +GetPrincipalOrSOP(JSContext *cx, HandleObject from, nsISupports **out) +{ + MOZ_ASSERT(out); + *out = NULL; + + nsXPConnect* xpc = nsXPConnect::XPConnect(); + nsCOMPtr wrapper; + xpc->GetWrappedNativeOfJSObject(cx, from, + getter_AddRefs(wrapper)); + + NS_ENSURE_TRUE(wrapper, NS_ERROR_INVALID_ARG); + + if (nsCOMPtr sop = do_QueryWrappedNative(wrapper)) { + sop.forget(out); + return NS_OK; + } + + nsCOMPtr principal = do_QueryWrappedNative(wrapper); + principal.forget(out); + NS_ENSURE_TRUE(*out, NS_ERROR_INVALID_ARG); + + return NS_OK; +} + +// the first parameter of the sandbox constructor might be an array of principals, either in string +// format or actual objects (see GetPrincipalOrSOP) +nsresult +GetExpandedPrincipal(JSContext *cx, HandleObject arrayObj, nsIExpandedPrincipal **out) +{ + MOZ_ASSERT(out); + uint32_t length; + + if (!JS_IsArrayObject(cx, arrayObj) || + !JS_GetArrayLength(cx, arrayObj, &length) || + !length) + { + // we need a white list of principals or uri strings to create an + // expanded principal, if we got an empty array or something else + // report error + return NS_ERROR_INVALID_ARG; + } + + nsTArray< nsCOMPtr > allowedDomains(length); + allowedDomains.SetLength(length); + nsIScriptSecurityManager *ssm = XPCWrapper::GetSecurityManager(); + NS_ENSURE_TRUE(ssm, NS_ERROR_XPC_UNEXPECTED); + + for (uint32_t i = 0; i < length; ++i) { + RootedValue allowed(cx); + if (!JS_GetElement(cx, arrayObj, i, &allowed)) + return NS_ERROR_INVALID_ARG; + + nsresult rv; + nsCOMPtr principal; + if (allowed.isString()) { + // in case of string let's try to fetch a codebase principal from it + RootedString str(cx, allowed.toString()); + rv = GetPrincipalFromString(cx, str, getter_AddRefs(principal)); + NS_ENSURE_SUCCESS(rv, rv); + } else if (allowed.isObject()) { + // in case of object let's see if it's a Principal or a ScriptObjectPrincipal + nsCOMPtr prinOrSop; + RootedObject obj(cx, &allowed.toObject()); + rv = GetPrincipalOrSOP(cx, obj, getter_AddRefs(prinOrSop)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr sop(do_QueryInterface(prinOrSop)); + principal = do_QueryInterface(prinOrSop); + if (sop) { + principal = sop->GetPrincipal(); + } + } + NS_ENSURE_TRUE(principal, NS_ERROR_INVALID_ARG); + + // We do not allow ExpandedPrincipals to contain any system principals + bool isSystem; + rv = ssm->IsSystemPrincipal(principal, &isSystem); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_FALSE(isSystem, NS_ERROR_INVALID_ARG); + allowedDomains[i] = principal; + } + + nsCOMPtr result = new nsExpandedPrincipal(allowedDomains); + result.forget(out); + return NS_OK; +} + +// helper that tries to get a property form the options object +nsresult +GetPropFromOptions(JSContext *cx, HandleObject from, const char *name, MutableHandleValue prop, + bool *found) +{ + if (!JS_HasProperty(cx, from, name, found)) + return NS_ERROR_INVALID_ARG; + + if (found && !JS_GetProperty(cx, from, name, prop)) + return NS_ERROR_INVALID_ARG; + + return NS_OK; +} + +// helper that tries to get a boolean property form the options object +nsresult +GetBoolPropFromOptions(JSContext *cx, HandleObject from, const char *name, bool *prop) +{ + MOZ_ASSERT(prop); + + + RootedValue value(cx); + bool found; + if (NS_FAILED(GetPropFromOptions(cx, from, name, &value, &found))) + return NS_ERROR_INVALID_ARG; + + if (!found) + return NS_OK; + + if (!value.isBoolean()) + return NS_ERROR_INVALID_ARG; + + *prop = value.toBoolean(); + return NS_OK; +} + +// helper that tries to get an object property form the options object +nsresult +GetObjPropFromOptions(JSContext *cx, HandleObject from, const char *name, JSObject **prop) +{ + MOZ_ASSERT(prop); + + RootedValue value(cx); + bool found; + if (NS_FAILED(GetPropFromOptions(cx, from, name, &value, &found))) + return NS_ERROR_INVALID_ARG; + + if (!found) { + *prop = NULL; + return NS_OK; + } + + if (!value.isObject()) + return NS_ERROR_INVALID_ARG; + + *prop = &value.toObject(); + return NS_OK; +} + +// helper that tries to get a string property form the options object +nsresult +GetStringPropFromOptions(JSContext *cx, HandleObject from, const char *name, nsCString &prop) +{ + RootedValue value(cx); + bool found; + nsresult rv = GetPropFromOptions(cx, from, name, &value, &found); + NS_ENSURE_SUCCESS(rv, rv); + + if (!found) + return NS_OK; + + NS_ENSURE_TRUE(value.isString(), NS_ERROR_INVALID_ARG); + + char *tmp = JS_EncodeString(cx, value.toString()); + NS_ENSURE_TRUE(tmp, NS_ERROR_INVALID_ARG); + prop.Adopt(tmp, strlen(tmp)); + return NS_OK; +} + +// helper that parsing the sandbox options object (from) and sets the fields of the incoming options struct (options) +nsresult +ParseOptionsObject(JSContext *cx, jsval from, SandboxOptions &options) +{ + NS_ENSURE_TRUE(from.isObject(), NS_ERROR_INVALID_ARG); + RootedObject optionsObject(cx, &from.toObject()); + nsresult rv = GetObjPropFromOptions(cx, optionsObject, + "sandboxPrototype", options.proto.address()); + NS_ENSURE_SUCCESS(rv, rv); + + rv = GetBoolPropFromOptions(cx, optionsObject, + "wantXrays", &options.wantXrays); + NS_ENSURE_SUCCESS(rv, rv); + + rv = GetBoolPropFromOptions(cx, optionsObject, + "wantComponents", &options.wantComponents); + NS_ENSURE_SUCCESS(rv, rv); + + rv = GetBoolPropFromOptions(cx, optionsObject, + "wantXHRConstructor", &options.wantXHRConstructor); + NS_ENSURE_SUCCESS(rv, rv); + + rv = GetBoolPropFromOptions(cx, optionsObject, + "wantExportHelpers", &options.wantExportHelpers); + NS_ENSURE_SUCCESS(rv, rv); + + rv = GetStringPropFromOptions(cx, optionsObject, + "sandboxName", options.sandboxName); + NS_ENSURE_SUCCESS(rv, rv); + + rv = GetObjPropFromOptions(cx, optionsObject, + "sameZoneAs", options.sameZoneAs.address()); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +static nsresult +AssembleSandboxMemoryReporterName(JSContext *cx, nsCString &sandboxName) +{ + // Use a default name when the caller did not provide a sandboxName. + if (sandboxName.IsEmpty()) + sandboxName = NS_LITERAL_CSTRING("[anonymous sandbox]"); + + nsXPConnect* xpc = nsXPConnect::XPConnect(); + // Get the xpconnect native call context. + nsAXPCNativeCallContext *cc = nullptr; + xpc->GetCurrentNativeCallContext(&cc); + NS_ENSURE_TRUE(cc, NS_ERROR_INVALID_ARG); + + // Get the current source info from xpc. + nsCOMPtr frame; + xpc->GetCurrentJSStack(getter_AddRefs(frame)); + + // Append the caller's location information. + if (frame) { + nsCString location; + int32_t lineNumber = 0; + frame->GetFilename(getter_Copies(location)); + frame->GetLineNumber(&lineNumber); + + sandboxName.AppendLiteral(" (from: "); + sandboxName.Append(location); + sandboxName.AppendLiteral(":"); + sandboxName.AppendInt(lineNumber); + sandboxName.AppendLiteral(")"); + } + + return NS_OK; +} + +// static +nsresult +nsXPCComponents_utils_Sandbox::CallOrConstruct(nsIXPConnectWrappedNative *wrapper, + JSContext *cx, HandleObject obj, + const CallArgs &args, bool *_retval) +{ + if (args.length() < 1) + return ThrowAndFail(NS_ERROR_XPC_NOT_ENOUGH_ARGS, cx, _retval); + + nsresult rv; + + // Make sure to set up principals on the sandbox before initing classes + nsCOMPtr principal; + nsCOMPtr expanded; + nsCOMPtr prinOrSop; + + if (args[0].isString()) { + RootedString str(cx, args[0].toString()); + rv = GetPrincipalFromString(cx, str, getter_AddRefs(principal)); + prinOrSop = principal; + } else if (args[0].isObject()) { + RootedObject obj(cx, &args[0].toObject()); + if (JS_IsArrayObject(cx, obj)) { + rv = GetExpandedPrincipal(cx, obj, getter_AddRefs(expanded)); + prinOrSop = expanded; + } else { + rv = GetPrincipalOrSOP(cx, obj, getter_AddRefs(prinOrSop)); + } + } else { + return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval); + } + + if (NS_FAILED(rv)) + return ThrowAndFail(rv, cx, _retval); + + SandboxOptions options(cx); + + if (args.length() > 1 && args[1].isObject()) { + if (NS_FAILED(ParseOptionsObject(cx, args[1], options))) + return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval); + } + + if (NS_FAILED(AssembleSandboxMemoryReporterName(cx, options.sandboxName))) + return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval); + + rv = xpc_CreateSandboxObject(cx, args.rval().address(), prinOrSop, options); + + if (NS_FAILED(rv)) + return ThrowAndFail(rv, cx, _retval); + + *_retval = true; + + return rv; +} + +class ContextHolder : public nsIScriptObjectPrincipal +{ +public: + ContextHolder(JSContext *aOuterCx, HandleObject aSandbox, nsIPrincipal *aPrincipal); + virtual ~ContextHolder(); + + JSContext * GetJSContext() + { + return mJSContext; + } + + nsIPrincipal * GetPrincipal() { return mPrincipal; } + + NS_DECL_ISUPPORTS + +private: + JSContext* mJSContext; + nsCOMPtr mPrincipal; +}; + +NS_IMPL_ISUPPORTS1(ContextHolder, nsIScriptObjectPrincipal) + +ContextHolder::ContextHolder(JSContext *aOuterCx, + HandleObject aSandbox, + nsIPrincipal *aPrincipal) + : mJSContext(JS_NewContext(JS_GetRuntime(aOuterCx), 1024)), + mPrincipal(aPrincipal) +{ + if (mJSContext) { + bool isChrome; + DebugOnly rv = XPCWrapper::GetSecurityManager()-> + IsSystemPrincipal(mPrincipal, &isChrome); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + JS_SetOptions(mJSContext, + JS_GetOptions(mJSContext) | + JSOPTION_DONT_REPORT_UNCAUGHT | + JSOPTION_PRIVATE_IS_NSISUPPORTS); + js::SetDefaultObjectForContext(mJSContext, aSandbox); + JS_SetContextPrivate(mJSContext, this); + } +} + +ContextHolder::~ContextHolder() +{ + if (mJSContext) + JS_DestroyContextNoGC(mJSContext); +} + +nsresult +xpc_EvalInSandbox(JSContext *cx, HandleObject sandboxArg, const nsAString& source, + const char *filename, int32_t lineNo, + JSVersion jsVersion, bool returnStringOnly, MutableHandleValue rval) +{ + JS_AbortIfWrongThread(JS_GetRuntime(cx)); + rval.set(UndefinedValue()); + + bool waiveXray = xpc::WrapperFactory::HasWaiveXrayFlag(sandboxArg); + RootedObject sandbox(cx, js::CheckedUnwrap(sandboxArg)); + if (!sandbox || js::GetObjectJSClass(sandbox) != &SandboxClass) { + return NS_ERROR_INVALID_ARG; + } + + nsIScriptObjectPrincipal *sop = + (nsIScriptObjectPrincipal*)xpc_GetJSPrivate(sandbox); + MOZ_ASSERT(sop, "Invalid sandbox passed"); + nsCOMPtr prin = sop->GetPrincipal(); + NS_ENSURE_TRUE(prin, NS_ERROR_FAILURE); + + nsAutoCString filenameBuf; + if (!filename) { + // Default to the spec of the principal. + nsJSPrincipals::get(prin)->GetScriptLocation(filenameBuf); + filename = filenameBuf.get(); + lineNo = 1; + } + + // We create a separate cx to do the sandbox evaluation. Scope it. + RootedValue v(cx, UndefinedValue()); + RootedValue exn(cx, UndefinedValue()); + bool ok = true; + { + // Make a special cx for the sandbox and push it. + // NB: As soon as the RefPtr goes away, the cx goes away. So declare + // it first so that it disappears last. + nsRefPtr sandcxHolder = new ContextHolder(cx, sandbox, prin); + JSContext *sandcx = sandcxHolder->GetJSContext(); + if (!sandcx) { + JS_ReportError(cx, "Can't prepare context for evalInSandbox"); + return NS_ERROR_OUT_OF_MEMORY; + } + nsCxPusher pusher; + pusher.Push(sandcx); + JSAutoCompartment ac(sandcx, sandbox); + + JS::CompileOptions options(sandcx); + options.setPrincipals(nsJSPrincipals::get(prin)) + .setFileAndLine(filename, lineNo); + if (jsVersion != JSVERSION_DEFAULT) + options.setVersion(jsVersion); + JS::RootedObject rootedSandbox(sandcx, sandbox); + ok = JS::Evaluate(sandcx, rootedSandbox, options, + PromiseFlatString(source).get(), source.Length(), + v.address()); + if (ok && returnStringOnly && !v.isUndefined()) { + JSString *str = JS_ValueToString(sandcx, v); + ok = !!str; + v = ok ? JS::StringValue(str) : JS::UndefinedValue(); + } + + // If the sandbox threw an exception, grab it off the context. + if (JS_GetPendingException(sandcx, exn.address())) { + MOZ_ASSERT(!ok); + JS_ClearPendingException(sandcx); + if (returnStringOnly) { + // The caller asked for strings only, convert the + // exception into a string. + JSString *str = JS_ValueToString(sandcx, exn); + exn = str ? JS::StringValue(str) : JS::UndefinedValue(); + } + } + } + + // + // Alright, we're back on the caller's cx. If an error occured, try to + // wrap and set the exception. Otherwise, wrap the return value. + // + + if (!ok) { + // If we end up without an exception, it was probably due to OOM along + // the way, in which case we thow. Otherwise, wrap it. + if (exn.isUndefined() || !JS_WrapValue(cx, exn.address())) + return NS_ERROR_OUT_OF_MEMORY; + + // Set the exception on our caller's cx. + JS_SetPendingException(cx, exn); + return NS_ERROR_FAILURE; + } + + // Transitively apply Xray waivers if |sb| was waived. + if (waiveXray) { + ok = xpc::WrapperFactory::WaiveXrayAndWrap(cx, v.address()); + } else { + ok = JS_WrapValue(cx, v.address()); + } + NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE); + + // Whew! + rval.set(v); + return NS_OK; +} + +bool +NonCloningFunctionForwarder(JSContext *cx, unsigned argc, Value *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + RootedValue v(cx, js::GetFunctionNativeReserved(&args.callee(), 0)); + MOZ_ASSERT(v.isObject(), "weird function"); + + JSObject *obj = JS_THIS_OBJECT(cx, vp); + if (!obj) { + return false; + } + return JS_CallFunctionValue(cx, obj, v, args.length(), args.array(), vp); +} + +/* + * Forwards the call to the exported function. Clones all the non reflectors, ignores + * the |this| argument. + */ +bool +CloningFunctionForwarder(JSContext *cx, unsigned argc, Value *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + RootedValue v(cx, js::GetFunctionNativeReserved(&args.callee(), 0)); + NS_ASSERTION(v.isObject(), "weird function"); + RootedObject origFunObj(cx, UncheckedUnwrap(&v.toObject())); + { + JSAutoCompartment ac(cx, origFunObj); + // Note: only the arguments are cloned not the |this| or the |callee|. + // Function forwarder does not use those. + for (unsigned i = 0; i < args.length(); i++) { + if (!CloneNonReflectors(cx, args[i])) { + return false; + } + } + + // JS API does not support any JSObject to JSFunction conversion, + // so let's use JS_CallFunctionValue instead. + RootedValue functionVal(cx); + functionVal.setObject(*origFunObj); + + if (!JS_CallFunctionValue(cx, nullptr, functionVal, args.length(), args.array(), vp)) + return false; + } + + // Return value must be wrapped. + return JS_WrapValue(cx, vp); +} + +bool +NewFunctionForwarder(JSContext *cx, HandleId id, HandleObject callable, bool doclone, + MutableHandleValue vp) +{ + JSFunction *fun = js::NewFunctionByIdWithReserved(cx, doclone ? CloningFunctionForwarder : + NonCloningFunctionForwarder, + 0,0, JS::CurrentGlobalOrNull(cx), id); + + if (!fun) + return false; + + JSObject *funobj = JS_GetFunctionObject(fun); + js::SetFunctionNativeReserved(funobj, 0, ObjectValue(*callable)); + vp.setObject(*funobj); + return true; +} diff --git a/js/xpconnect/src/XPCComponents.cpp b/js/xpconnect/src/XPCComponents.cpp index 1ae811156239..c385e73a336b 100644 --- a/js/xpconnect/src/XPCComponents.cpp +++ b/js/xpconnect/src/XPCComponents.cpp @@ -40,17 +40,15 @@ #include "nsDOMClassInfoID.h" #include "nsGlobalWindow.h" - using namespace mozilla; using namespace js; using namespace xpc; -using mozilla::dom::DestroyProtoAndIfaceCache; - /***************************************************************************/ // stuff used by all -static nsresult ThrowAndFail(nsresult errNum, JSContext* cx, bool* retval) +nsresult +ThrowAndFail(nsresult errNum, JSContext* cx, bool* retval) { XPCThrower::Throw(errNum, cx); *retval = false; @@ -102,7 +100,6 @@ char * xpc_CheckAccessList(const PRUnichar* wideName, const char* const list[]) /***************************************************************************/ - class nsXPCComponents_Interfaces : public nsIXPCComponents_Interfaces, public nsIXPCScriptable, @@ -2588,27 +2585,6 @@ nsXPCComponents_Constructor::HasInstance(nsIXPConnectWrappedNative *wrapper, return NS_OK; } -/***************************************************************************/ -// Javascript constructor for the sandbox object -class nsXPCComponents_utils_Sandbox : public nsIXPCComponents_utils_Sandbox, - public nsIXPCScriptable -{ -public: - // Aren't macros nice? - NS_DECL_THREADSAFE_ISUPPORTS - NS_DECL_NSIXPCCOMPONENTS_UTILS_SANDBOX - NS_DECL_NSIXPCSCRIPTABLE - -public: - nsXPCComponents_utils_Sandbox(); - virtual ~nsXPCComponents_utils_Sandbox(); - -private: - static nsresult CallOrConstruct(nsIXPConnectWrappedNative *wrapper, - JSContext *cx, HandleObject obj, - const CallArgs &args, bool *_retval); -}; - class nsXPCComponents_Utils : public nsIXPCComponents_Utils, public nsIXPCScriptable, @@ -2649,10 +2625,9 @@ NS_IMETHODIMP nsXPCComponents_Utils::GetSandbox(nsIXPCComponents_utils_Sandbox **aSandbox) { NS_ENSURE_ARG_POINTER(aSandbox); - if (!mSandbox && !(mSandbox = new nsXPCComponents_utils_Sandbox())) { - *aSandbox = nullptr; - return NS_ERROR_OUT_OF_MEMORY; - } + if (!mSandbox) + mSandbox = NewSandboxConstructor(); + NS_ADDREF(*aSandbox = mSandbox); return NS_OK; } @@ -2793,1344 +2768,6 @@ nsXPCComponents_Utils::ReportError(const JS::Value &errorArg, JSContext *cx) return NS_OK; } -#include "nsIScriptSecurityManager.h" -#include "nsIURI.h" -#include "nsNetUtil.h" -const char kScriptSecurityManagerContractID[] = NS_SCRIPTSECURITYMANAGER_CONTRACTID; - -NS_IMPL_ISUPPORTS3(SandboxPrivate, - nsIScriptObjectPrincipal, - nsIGlobalObject, - nsISupportsWeakReference) - -static bool -SandboxDump(JSContext *cx, unsigned argc, jsval *vp) -{ - JSString *str; - if (!argc) - return true; - - str = JS_ValueToString(cx, JS_ARGV(cx, vp)[0]); - if (!str) - return false; - - size_t length; - const jschar *chars = JS_GetStringCharsZAndLength(cx, str, &length); - if (!chars) - return false; - - nsDependentString wstr(chars, length); - char *cstr = ToNewUTF8String(wstr); - if (!cstr) - return false; - -#if defined(XP_MACOSX) - // Be nice and convert all \r to \n. - char *c = cstr, *cEnd = cstr + strlen(cstr); - while (c < cEnd) { - if (*c == '\r') - *c = '\n'; - c++; - } -#endif - - fputs(cstr, stdout); - fflush(stdout); - NS_Free(cstr); - JS_SET_RVAL(cx, vp, JSVAL_TRUE); - return true; -} - -static bool -SandboxDebug(JSContext *cx, unsigned argc, jsval *vp) -{ -#ifdef DEBUG - return SandboxDump(cx, argc, vp); -#else - return true; -#endif -} - -static bool -SandboxImport(JSContext *cx, unsigned argc, Value *vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - - if (args.length() < 1 || args[0].isPrimitive()) { - XPCThrower::Throw(NS_ERROR_INVALID_ARG, cx); - return false; - } - - RootedString funname(cx); - if (args.length() > 1) { - // Use the second parameter as the function name. - funname = JS_ValueToString(cx, args[1]); - if (!funname) - return false; - } else { - // NB: funobj must only be used to get the JSFunction out. - RootedObject funobj(cx, &args[0].toObject()); - if (js::IsProxy(funobj)) { - funobj = XPCWrapper::UnsafeUnwrapSecurityWrapper(funobj); - } - - JSAutoCompartment ac(cx, funobj); - - JSFunction *fun = JS_ValueToFunction(cx, ObjectValue(*funobj)); - if (!fun) { - XPCThrower::Throw(NS_ERROR_INVALID_ARG, cx); - return false; - } - - // Use the actual function name as the name. - funname = JS_GetFunctionId(fun); - if (!funname) { - XPCThrower::Throw(NS_ERROR_INVALID_ARG, cx); - return false; - } - } - - RootedId id(cx); - if (!JS_ValueToId(cx, StringValue(funname), id.address())) - return false; - - // We need to resolve the this object, because this function is used - // unbound and should still work and act on the original sandbox. - RootedObject thisObject(cx, JS_THIS_OBJECT(cx, vp)); - if (!thisObject) { - XPCThrower::Throw(NS_ERROR_UNEXPECTED, cx); - return false; - } - if (!JS_SetPropertyById(cx, thisObject, id, args[0])) - return false; - - args.rval().setUndefined(); - return true; -} - -static bool -CreateXMLHttpRequest(JSContext *cx, unsigned argc, jsval *vp) -{ - nsIScriptSecurityManager *ssm = XPCWrapper::GetSecurityManager(); - if (!ssm) - return false; - - nsIPrincipal *subjectPrincipal = ssm->GetCxSubjectPrincipal(cx); - if (!subjectPrincipal) - return false; - - RootedObject global(cx, JS::CurrentGlobalOrNull(cx)); - MOZ_ASSERT(global); - - nsIScriptObjectPrincipal *sop = - static_cast(xpc_GetJSPrivate(global)); - nsCOMPtr iglobal = do_QueryInterface(sop); - - nsCOMPtr xhr = new nsXMLHttpRequest(); - nsresult rv = xhr->Init(subjectPrincipal, nullptr, iglobal, nullptr); - if (NS_FAILED(rv)) - return false; - - rv = nsContentUtils::WrapNative(cx, global, xhr, vp); - if (NS_FAILED(rv)) - return false; - - return true; -} - -bool -NewFunctionForwarder(JSContext *cx, HandleId id, HandleObject callable, - bool doclone, MutableHandleValue vp); - -/* - * Instead of simply wrapping a function into another compartment, - * this helper function creates a native function in the target - * compartment and forwards the call to the original function. - * That call will be different than a regular JS function call in - * that, the |this| is left unbound, and all the non-native JS - * object arguments will be cloned using the structured clone - * algorithm. - * The return value is the new forwarder function, wrapped into - * the caller's compartment. - * The 3rd argument is the name of the property that will - * be set on the target scope, with the forwarder function as - * the value. - * The principal of the caller must subsume that of the target. - * - * Expected type of the arguments and the return value: - * function exportFunction(function funToExport, - * object targetScope, - * string name) - */ -static bool -ExportFunction(JSContext *cx, unsigned argc, jsval *vp) -{ - MOZ_ASSERT(cx); - if (argc < 3) { - JS_ReportError(cx, "Function requires at least 3 arguments"); - return false; - } - - CallArgs args = CallArgsFromVp(argc, vp); - if (!args[0].isObject() || !args[1].isObject() || !args[2].isString()) { - JS_ReportError(cx, "Invalid argument"); - return false; - } - - RootedObject funObj(cx, &args[0].toObject()); - RootedObject targetScope(cx, &args[1].toObject()); - RootedString funName(cx, args[2].toString()); - - // We can only export functions to scopes those are transparent for us, - // so if there is a security wrapper around targetScope we must throw. - targetScope = CheckedUnwrap(targetScope); - if (!targetScope) { - JS_ReportError(cx, "Permission denied to export function into scope"); - return false; - } - - if (JS_GetStringLength(funName) == 0) { - JS_ReportError(cx, "3rd argument should be a non-empty string"); - return false; - } - - { - // We need to operate in the target scope from here on, let's enter - // its compartment. - JSAutoCompartment ac(cx, targetScope); - - // Unwrapping to see if we have a callable. - funObj = UncheckedUnwrap(funObj); - if (!JS_ObjectIsCallable(cx, funObj)) { - JS_ReportError(cx, "First argument must be a function"); - return false; - } - - // The function forwarder will live in the target compartment. Since - // this function will be referenced from its private slot, to avoid a - // GC hazard, we must wrap it to the same compartment. - if (!JS_WrapObject(cx, funObj.address())) - return false; - - RootedId id(cx); - if (!JS_ValueToId(cx, args[2], id.address())) - return false; - - // And now, let's create the forwarder function in the target compartment - // for the function the be exported. - if (!NewFunctionForwarder(cx, id, funObj, /* doclone = */ true, args.rval())) { - JS_ReportError(cx, "Exporting function failed"); - return false; - } - - // We have the forwarder function in the target compartment, now - // we have to add it to the target scope as a property. - if (!JS_DefinePropertyById(cx, targetScope, id, args.rval(), - JS_PropertyStub, JS_StrictPropertyStub, - JSPROP_ENUMERATE)) - return false; - } - - // Finally we have to re-wrap the exported function back to the caller compartment. - if (!JS_WrapValue(cx, args.rval().address())) - return false; - - return true; -} - -static bool -GetFilenameAndLineNumber(JSContext *cx, nsACString &filename, unsigned &lineno) -{ - JSScript *script; - if (JS_DescribeScriptedCaller(cx, &script, &lineno)) { - if (const char *cfilename = JS_GetScriptFilename(cx, script)) { - filename.Assign(nsDependentCString(cfilename)); - return true; - } - } - return false; -} - -namespace xpc { -bool -IsReflector(JSObject *obj) -{ - return IS_WN_REFLECTOR(obj) || dom::IsDOMObject(obj); -} -} /* namespace xpc */ - -enum ForwarderCloneTags { - SCTAG_BASE = JS_SCTAG_USER_MIN, - SCTAG_REFLECTOR -}; - -static JSObject * -CloneNonReflectorsRead(JSContext *cx, JSStructuredCloneReader *reader, uint32_t tag, - uint32_t data, void *closure) -{ - MOZ_ASSERT(closure, "Null pointer!"); - AutoObjectVector *reflectors = static_cast(closure); - if (tag == SCTAG_REFLECTOR) { - MOZ_ASSERT(!data); - - size_t idx; - if (JS_ReadBytes(reader, &idx, sizeof(size_t))) { - RootedObject reflector(cx, reflectors->handleAt(idx)); - MOZ_ASSERT(reflector, "No object pointer?"); - MOZ_ASSERT(IsReflector(reflector), "Object pointer must be a reflector!"); - - JS_WrapObject(cx, reflector.address()); - JS_ASSERT(WrapperFactory::IsXrayWrapper(reflector) || - IsReflector(reflector)); - - return reflector; - } - } - - JS_ReportError(cx, "CloneNonReflectorsRead error"); - return nullptr; -} - -static bool -CloneNonReflectorsWrite(JSContext *cx, JSStructuredCloneWriter *writer, - Handle obj, void *closure) -{ - MOZ_ASSERT(closure, "Null pointer!"); - - // We need to maintain a list of reflectors to make sure all these objects - // are properly rooter. Only their indices will be serialized. - AutoObjectVector *reflectors = static_cast(closure); - if (IsReflector(obj)) { - if (!reflectors->append(obj)) - return false; - - size_t idx = reflectors->length()-1; - if (JS_WriteUint32Pair(writer, SCTAG_REFLECTOR, 0) && - JS_WriteBytes(writer, &idx, sizeof(size_t))) { - return true; - } - } - - JS_ReportError(cx, "CloneNonReflectorsWrite error"); - return false; -} - -JSStructuredCloneCallbacks gForwarderStructuredCloneCallbacks = { - CloneNonReflectorsRead, - CloneNonReflectorsWrite, - nullptr -}; - -/* - * This is a special structured cloning, that clones only non-reflectors. - * The function assumes the cx is already entered the compartment we want - * to clone to, and that if val is an object is from the compartment we - * clone from. - */ -bool -CloneNonReflectors(JSContext *cx, MutableHandleValue val) -{ - JSAutoStructuredCloneBuffer buffer; - AutoObjectVector rootedReflectors(cx); - { - // For parsing val we have to enter its compartment. - // (unless it's a primitive) - Maybe ac; - if (val.isObject()) { - ac.construct(cx, &val.toObject()); - } - - if (!buffer.write(cx, val, - &gForwarderStructuredCloneCallbacks, - &rootedReflectors)) - { - return false; - } - } - - // Now recreate the clones in the target compartment. - RootedValue rval(cx); - if (!buffer.read(cx, val.address(), - &gForwarderStructuredCloneCallbacks, - &rootedReflectors)) - { - return false; - } - - return true; -} - -/* - * Similar to evalInSandbox except this one is used to eval a script in the - * scope of a window. Also note, that the return value and the possible exceptions - * in the script are structured cloned, unless they are natives (then they are just - * wrapped). - * Principal of the caller must subsume the target's. - * - * Expected type of the arguments: - * value evalInWindow(string script, - * object window) - */ -static bool -EvalInWindow(JSContext *cx, unsigned argc, jsval *vp) -{ - MOZ_ASSERT(cx); - if (argc < 2) { - JS_ReportError(cx, "Function requires two arguments"); - return false; - } - - CallArgs args = CallArgsFromVp(argc, vp); - if (!args[0].isString() || !args[1].isObject()) { - JS_ReportError(cx, "Invalid arguments"); - return false; - } - - RootedString srcString(cx, args[0].toString()); - RootedObject targetScope(cx, &args[1].toObject()); - - // If we cannot unwrap we must not eval in it. - targetScope = CheckedUnwrap(targetScope); - if (!targetScope) { - JS_ReportError(cx, "Permission denied to eval in target scope"); - return false; - } - - // Make sure that we have a window object. - RootedObject inner(cx, CheckedUnwrap(targetScope, /* stopAtOuter = */ false)); - nsCOMPtr global; - nsCOMPtr window; - if (!JS_IsGlobalObject(inner) || - !(global = GetNativeForGlobal(inner)) || - !(window = do_QueryInterface(global))) - { - JS_ReportError(cx, "Second argument must be a window"); - return false; - } - - nsCOMPtr context = - (static_cast(window.get()))->GetScriptContext(); - if (!context) { - JS_ReportError(cx, "Script context needed"); - return false; - } - - if (!context->GetScriptsEnabled()) { - JS_ReportError(cx, "Scripts are disabled in this window"); - return false; - } - - nsCString filename; - unsigned lineNo; - if (!GetFilenameAndLineNumber(cx, filename, lineNo)) { - // Default values for non-scripted callers. - filename.Assign("Unknown"); - lineNo = 0; - } - - nsDependentJSString srcDepString; - srcDepString.init(cx, srcString); - - { - // CompileOptions must be created from the context - // we will execute this script in. - JSContext *wndCx = context->GetNativeContext(); - AutoCxPusher pusher(wndCx); - JS::CompileOptions compileOptions(wndCx); - compileOptions.setFileAndLine(filename.get(), lineNo); - - // We don't want the JS engine to automatically report - // uncaught exceptions. - nsJSUtils::EvaluateOptions evaluateOptions; - evaluateOptions.setReportUncaught(false); - - nsresult rv = nsJSUtils::EvaluateString(wndCx, - srcDepString, - targetScope, - compileOptions, - evaluateOptions, - args.rval().address()); - - if (NS_FAILED(rv)) { - // If there was an exception we get it as a return value, if - // the evaluation failed for some other reason, then a default - // exception is raised. - MOZ_ASSERT(!JS_IsExceptionPending(wndCx), - "Exception should be delivered as return value."); - if (args.rval().isUndefined()) { - MOZ_ASSERT(rv == NS_ERROR_OUT_OF_MEMORY); - return false; - } - - // If there was an exception thrown we should set it - // on the calling context. - RootedValue exn(wndCx, args.rval()); - // First we should reset the return value. - args.rval().set(UndefinedValue()); - - // Then clone the exception. - if (CloneNonReflectors(cx, &exn)) - JS_SetPendingException(cx, exn); - - return false; - } - } - - // Let's clone the return value back to the callers compartment. - if (!CloneNonReflectors(cx, args.rval())) { - args.rval().set(UndefinedValue()); - return false; - } - - return true; -} - -static bool -sandbox_enumerate(JSContext *cx, HandleObject obj) -{ - return JS_EnumerateStandardClasses(cx, obj); -} - -static bool -sandbox_resolve(JSContext *cx, HandleObject obj, HandleId id) -{ - bool resolved; - return JS_ResolveStandardClass(cx, obj, id, &resolved); -} - -static void -sandbox_finalize(JSFreeOp *fop, JSObject *obj) -{ - nsIScriptObjectPrincipal *sop = - static_cast(xpc_GetJSPrivate(obj)); - MOZ_ASSERT(sop); - static_cast(sop)->ForgetGlobalObject(); - NS_IF_RELEASE(sop); - DestroyProtoAndIfaceCache(obj); -} - -static bool -sandbox_convert(JSContext *cx, HandleObject obj, JSType type, MutableHandleValue vp) -{ - if (type == JSTYPE_OBJECT) { - vp.set(OBJECT_TO_JSVAL(obj)); - return true; - } - - return JS_ConvertStub(cx, obj, type, vp); -} - -static JSClass SandboxClass = { - "Sandbox", - XPCONNECT_GLOBAL_FLAGS, - JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, - sandbox_enumerate, sandbox_resolve, sandbox_convert, sandbox_finalize, - NULL, NULL, NULL, NULL, TraceXPCGlobal -}; - -static const JSFunctionSpec SandboxFunctions[] = { - JS_FS("dump", SandboxDump, 1,0), - JS_FS("debug", SandboxDebug, 1,0), - JS_FS("importFunction", SandboxImport, 1,0), - JS_FS_END -}; - -/***************************************************************************/ -nsXPCComponents_utils_Sandbox::nsXPCComponents_utils_Sandbox() -{ -} - -nsXPCComponents_utils_Sandbox::~nsXPCComponents_utils_Sandbox() -{ -} - -NS_INTERFACE_MAP_BEGIN(nsXPCComponents_utils_Sandbox) - NS_INTERFACE_MAP_ENTRY(nsIXPCComponents_utils_Sandbox) - NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable) - NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCComponents_utils_Sandbox) -NS_INTERFACE_MAP_END_THREADSAFE - -NS_IMPL_ADDREF(nsXPCComponents_utils_Sandbox) -NS_IMPL_RELEASE(nsXPCComponents_utils_Sandbox) - -// We use the nsIXPScriptable macros to generate lots of stuff for us. -#define XPC_MAP_CLASSNAME nsXPCComponents_utils_Sandbox -#define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_utils_Sandbox" -#define XPC_MAP_WANT_CALL -#define XPC_MAP_WANT_CONSTRUCT -#define XPC_MAP_FLAGS 0 -#include "xpc_map_end.h" /* This #undef's the above. */ - -xpc::SandboxProxyHandler xpc::sandboxProxyHandler; - -bool -xpc::IsSandboxPrototypeProxy(JSObject *obj) -{ - return js::IsProxy(obj) && - js::GetProxyHandler(obj) == &xpc::sandboxProxyHandler; -} - -bool -xpc::SandboxCallableProxyHandler::call(JSContext *cx, JS::Handle proxy, - const JS::CallArgs &args) -{ - // We forward the call to our underlying callable. - - // The parent of our proxy is the SandboxProxyHandler proxy - RootedObject sandboxProxy(cx, JS_GetParent(proxy)); - MOZ_ASSERT(js::IsProxy(sandboxProxy) && - js::GetProxyHandler(sandboxProxy) == &xpc::sandboxProxyHandler); - - // The parent of the sandboxProxy is the sandbox global, and the - // target object is the original proto. - RootedObject sandboxGlobal(cx, JS_GetParent(sandboxProxy)); - MOZ_ASSERT(js::GetObjectJSClass(sandboxGlobal) == &SandboxClass); - - // If our this object is the sandbox global, we call with this set to the - // original proto instead. - // - // There are two different ways we can compute |this|. If we use - // JS_THIS_VALUE, we'll get the bonafide |this| value as passed by the - // caller, which may be undefined if a global function was invoked without - // an explicit invocant. If we use JS_THIS or JS_THIS_OBJECT, the |this| - // in |vp| will be coerced to the global, which is not the correct - // behavior in ES5 strict mode. And we have no way to compute strictness - // here. - // - // The naive approach is simply to use JS_THIS_VALUE here. If |this| was - // explicit, we can remap it appropriately. If it was implicit, then we - // leave it as undefined, and let the callee sort it out. Since the callee - // is generally in the same compartment as its global (eg the Window's - // compartment, not the Sandbox's), the callee will generally compute the - // correct |this|. - // - // However, this breaks down in the Xray case. If the sandboxPrototype - // is an Xray wrapper, then we'll end up reifying the native methods in - // the Sandbox's scope, which means that they'll compute |this| to be the - // Sandbox, breaking old-style XPC_WN_CallMethod methods. - // - // Luckily, the intent of Xrays is to provide a vanilla view of a foreign - // DOM interface, which means that we don't care about script-enacted - // strictness in the prototype's home compartment. Indeed, since DOM - // methods are always non-strict, we can just assume non-strict semantics - // if the sandboxPrototype is an Xray Wrapper, which lets us appropriately - // remap |this|. - JS::Value thisVal = - WrapperFactory::IsXrayWrapper(sandboxProxy) ? args.computeThis(cx) : args.thisv(); - if (thisVal == ObjectValue(*sandboxGlobal)) { - thisVal = ObjectValue(*js::GetProxyTargetObject(sandboxProxy)); - } - - return JS::Call(cx, thisVal, js::GetProxyPrivate(proxy), args.length(), args.array(), - args.rval()); -} - -xpc::SandboxCallableProxyHandler xpc::sandboxCallableProxyHandler; - -// Wrap a callable such that if we're called with oldThisObj as the -// "this" we will instead call it with newThisObj as the this. -static JSObject* -WrapCallable(JSContext *cx, JSObject *callable, JSObject *sandboxProtoProxy) -{ - MOZ_ASSERT(JS_ObjectIsCallable(cx, callable)); - // Our proxy is wrapping the callable. So we need to use the - // callable as the private. We use the given sandboxProtoProxy as - // the parent, and our call() hook depends on that. - MOZ_ASSERT(js::IsProxy(sandboxProtoProxy) && - js::GetProxyHandler(sandboxProtoProxy) == - &xpc::sandboxProxyHandler); - - RootedValue priv(cx, ObjectValue(*callable)); - return js::NewProxyObject(cx, &xpc::sandboxCallableProxyHandler, - priv, nullptr, - sandboxProtoProxy, js::ProxyIsCallable); -} - -template -bool BindPropertyOp(JSContext *cx, Op &op, JSPropertyDescriptor *desc, HandleId id, - unsigned attrFlag, HandleObject sandboxProtoProxy) -{ - if (!op) { - return true; - } - - RootedObject func(cx); - if (desc->attrs & attrFlag) { - // Already an object - func = JS_FUNC_TO_DATA_PTR(JSObject *, op); - } else { - // We have an actual property op. For getters, we use 0 - // args, for setters we use 1 arg. - uint32_t args = (attrFlag == JSPROP_GETTER) ? 0 : 1; - RootedObject obj(cx, desc->obj); - func = GeneratePropertyOp(cx, obj, id, args, op); - if (!func) - return false; - } - func = WrapCallable(cx, func, sandboxProtoProxy); - if (!func) - return false; - op = JS_DATA_TO_FUNC_PTR(Op, func.get()); - desc->attrs |= attrFlag; - return true; -} - -extern bool -XPC_WN_Helper_GetProperty(JSContext *cx, HandleObject obj, HandleId id, MutableHandleValue vp); -extern bool -XPC_WN_Helper_SetProperty(JSContext *cx, HandleObject obj, HandleId id, bool strict, MutableHandleValue vp); - -bool -xpc::SandboxProxyHandler::getPropertyDescriptor(JSContext *cx, - JS::Handle proxy, - JS::Handle id, - JS::MutableHandle desc, - unsigned flags) -{ - JS::RootedObject obj(cx, wrappedObject(proxy)); - - MOZ_ASSERT(js::GetObjectCompartment(obj) == js::GetObjectCompartment(proxy)); - if (!JS_GetPropertyDescriptorById(cx, obj, id, - flags, desc)) - return false; - - if (!desc.object()) - return true; // No property, nothing to do - - // Now fix up the getter/setter/value as needed to be bound to desc->obj - // Don't mess with holder_get and holder_set, though, because those rely on - // the "vp is prefilled with the value in the slot" behavior that property - // ops can in theory rely on, but our property op forwarder doesn't know how - // to make that happen. Since we really only need to rebind the DOM methods - // here, not rebindings holder_get and holder_set is OK. - // - // Similarly, don't mess with XPC_WN_Helper_GetProperty and - // XPC_WN_Helper_SetProperty, for the same reasons: that could confuse our - // access to expandos when we're not doing Xrays. - if (desc.getter() != xpc::holder_get && - desc.getter() != XPC_WN_Helper_GetProperty && - !BindPropertyOp(cx, desc.getter(), desc.address(), id, JSPROP_GETTER, proxy)) - return false; - if (desc.setter() != xpc::holder_set && - desc.setter() != XPC_WN_Helper_SetProperty && - !BindPropertyOp(cx, desc.setter(), desc.address(), id, JSPROP_SETTER, proxy)) - return false; - if (desc.value().isObject()) { - JSObject* val = &desc.value().toObject(); - if (JS_ObjectIsCallable(cx, val)) { - val = WrapCallable(cx, val, proxy); - if (!val) - return false; - desc.value().setObject(*val); - } - } - - return true; -} - -bool -xpc::SandboxProxyHandler::getOwnPropertyDescriptor(JSContext *cx, - JS::Handle proxy, - JS::Handle id, - JS::MutableHandle desc, - unsigned flags) -{ - if (!getPropertyDescriptor(cx, proxy, id, desc, flags)) - return false; - - if (desc.object() != wrappedObject(proxy)) - desc.object().set(nullptr); - - return true; -} - -/* - * Reuse the BaseProxyHandler versions of the derived traps that are implemented - * in terms of the fundamental traps. - */ - -bool -xpc::SandboxProxyHandler::has(JSContext *cx, JS::Handle proxy, - JS::Handle id, bool *bp) -{ - return BaseProxyHandler::has(cx, proxy, id, bp); -} -bool -xpc::SandboxProxyHandler::hasOwn(JSContext *cx, JS::Handle proxy, - JS::Handle id, bool *bp) -{ - return BaseProxyHandler::hasOwn(cx, proxy, id, bp); -} - -bool -xpc::SandboxProxyHandler::get(JSContext *cx, JS::Handle proxy, - JS::Handle receiver, - JS::Handle id, - JS::MutableHandle vp) -{ - return BaseProxyHandler::get(cx, proxy, receiver, id, vp); -} - -bool -xpc::SandboxProxyHandler::set(JSContext *cx, JS::Handle proxy, - JS::Handle receiver, - JS::Handle id, - bool strict, - JS::MutableHandle vp) -{ - return BaseProxyHandler::set(cx, proxy, receiver, id, strict, vp); -} - -bool -xpc::SandboxProxyHandler::keys(JSContext *cx, JS::Handle proxy, - AutoIdVector &props) -{ - return BaseProxyHandler::keys(cx, proxy, props); -} - -bool -xpc::SandboxProxyHandler::iterate(JSContext *cx, JS::Handle proxy, - unsigned flags, JS::MutableHandle vp) -{ - return BaseProxyHandler::iterate(cx, proxy, flags, vp); -} - -nsresult -xpc_CreateSandboxObject(JSContext *cx, jsval *vp, nsISupports *prinOrSop, SandboxOptions& options) -{ - // Create the sandbox global object - nsresult rv; - nsCOMPtr xpc(do_GetService(nsIXPConnect::GetCID(), &rv)); - if (NS_FAILED(rv)) - return NS_ERROR_XPC_UNEXPECTED; - - nsCOMPtr principal = do_QueryInterface(prinOrSop); - if (!principal) { - nsCOMPtr sop = do_QueryInterface(prinOrSop); - if (sop) { - principal = sop->GetPrincipal(); - } else { - principal = do_CreateInstance("@mozilla.org/nullprincipal;1", &rv); - MOZ_ASSERT(NS_FAILED(rv) || principal, "Bad return from do_CreateInstance"); - - if (!principal || NS_FAILED(rv)) { - if (NS_SUCCEEDED(rv)) { - rv = NS_ERROR_FAILURE; - } - - return rv; - } - } - MOZ_ASSERT(principal); - } - - JS::CompartmentOptions compartmentOptions; - compartmentOptions.setZone(options.sameZoneAs - ? JS::SameZoneAs(js::UncheckedUnwrap(options.sameZoneAs)) - : JS::SystemZone); - RootedObject sandbox(cx, xpc::CreateGlobalObject(cx, &SandboxClass, - principal, compartmentOptions)); - if (!sandbox) - return NS_ERROR_FAILURE; - - // Set up the wantXrays flag, which indicates whether xrays are desired even - // for same-origin access. - // - // This flag has historically been ignored for chrome sandboxes due to - // quirks in the wrapping implementation that have now been removed. Indeed, - // same-origin Xrays for chrome->chrome access seems a bit superfluous. - // Arguably we should just flip the default for chrome and still honor the - // flag, but such a change would break code in subtle ways for minimal - // benefit. So we just switch it off here. - xpc::GetCompartmentPrivate(sandbox)->wantXrays = - AccessCheck::isChrome(sandbox) ? false : options.wantXrays; - - { - JSAutoCompartment ac(cx, sandbox); - - if (options.proto) { - bool ok = JS_WrapObject(cx, options.proto.address()); - if (!ok) - return NS_ERROR_XPC_UNEXPECTED; - - if (xpc::WrapperFactory::IsXrayWrapper(options.proto) && !options.wantXrays) { - RootedValue v(cx, ObjectValue(*options.proto)); - if (!xpc::WrapperFactory::WaiveXrayAndWrap(cx, v.address())) - return NS_ERROR_FAILURE; - options.proto = &v.toObject(); - } - - // Now check what sort of thing we've got in |proto| - JSObject *unwrappedProto = js::UncheckedUnwrap(options.proto, false); - js::Class *unwrappedClass = js::GetObjectClass(unwrappedProto); - if (IS_WN_CLASS(unwrappedClass) || - mozilla::dom::IsDOMClass(Jsvalify(unwrappedClass))) { - // Wrap it up in a proxy that will do the right thing in terms - // of this-binding for methods. - RootedValue priv(cx, ObjectValue(*options.proto)); - options.proto = js::NewProxyObject(cx, &xpc::sandboxProxyHandler, - priv, nullptr, sandbox); - if (!options.proto) - return NS_ERROR_OUT_OF_MEMORY; - } - - ok = JS_SetPrototype(cx, sandbox, options.proto); - if (!ok) - return NS_ERROR_XPC_UNEXPECTED; - } - - nsCOMPtr sbp = - new SandboxPrivate(principal, sandbox); - - // Pass on ownership of sbp to |sandbox|. - JS_SetPrivate(sandbox, sbp.forget().get()); - - bool allowComponents = nsContentUtils::IsSystemPrincipal(principal) || - nsContentUtils::IsExpandedPrincipal(principal); - if (options.wantComponents && allowComponents && - !nsXPCComponents::AttachComponentsObject(cx, GetObjectScope(sandbox))) - return NS_ERROR_XPC_UNEXPECTED; - - if (!XPCNativeWrapper::AttachNewConstructorObject(cx, sandbox)) - return NS_ERROR_XPC_UNEXPECTED; - - if (!JS_DefineFunctions(cx, sandbox, SandboxFunctions)) - return NS_ERROR_XPC_UNEXPECTED; - - if (options.wantXHRConstructor && - !JS_DefineFunction(cx, sandbox, "XMLHttpRequest", CreateXMLHttpRequest, 0, JSFUN_CONSTRUCTOR)) - return NS_ERROR_XPC_UNEXPECTED; - - if (options.wantExportHelpers && - (!JS_DefineFunction(cx, sandbox, "exportFunction", ExportFunction, 3, 0) || - !JS_DefineFunction(cx, sandbox, "evalInWindow", EvalInWindow, 2, 0))) - return NS_ERROR_XPC_UNEXPECTED; - - } - - if (vp) { - // We have this crazy behavior where wantXrays=false also implies that the - // returned sandbox is implicitly waived. We've stopped advertising it, but - // keep supporting it for now. - *vp = OBJECT_TO_JSVAL(sandbox); - if (options.wantXrays && !JS_WrapValue(cx, vp)) - return NS_ERROR_UNEXPECTED; - if (!options.wantXrays && !xpc::WrapperFactory::WaiveXrayAndWrap(cx, vp)) - return NS_ERROR_UNEXPECTED; - } - - // Set the location information for the new global, so that tools like - // about:memory may use that information - xpc::SetLocationForGlobal(sandbox, options.sandboxName); - - JS_FireOnNewGlobalObject(cx, sandbox); - - return NS_OK; -} - -/* bool call(in nsIXPConnectWrappedNative wrapper, - in JSContextPtr cx, - in JSObjectPtr obj, - in uint32_t argc, - in JSValPtr argv, - in JSValPtr vp); -*/ -NS_IMETHODIMP -nsXPCComponents_utils_Sandbox::Call(nsIXPConnectWrappedNative *wrapper, JSContext *cx, - JSObject *objArg, const CallArgs &args, bool *_retval) -{ - RootedObject obj(cx, objArg); - return CallOrConstruct(wrapper, cx, obj, args, _retval); -} - -/* bool construct(in nsIXPConnectWrappedNative wrapper, - in JSContextPtr cx, - in JSObjectPtr obj, - in uint32_t argc, - in JSValPtr argv, - in JSValPtr vp); -*/ -NS_IMETHODIMP -nsXPCComponents_utils_Sandbox::Construct(nsIXPConnectWrappedNative *wrapper, JSContext *cx, - JSObject *objArg, const CallArgs &args, bool *_retval) -{ - RootedObject obj(cx, objArg); - return CallOrConstruct(wrapper, cx, obj, args, _retval); -} - -// for sandbox constructor the first argument can be a URI string in which case -// we use the related Codebase Principal for the sandbox -nsresult -GetPrincipalFromString(JSContext *cx, HandleString codebase, nsIPrincipal **principal) -{ - MOZ_ASSERT(principal); - MOZ_ASSERT(codebase); - nsCOMPtr uri; - nsDependentJSString codebaseStr; - NS_ENSURE_TRUE(codebaseStr.init(cx, codebase), NS_ERROR_FAILURE); - nsresult rv = NS_NewURI(getter_AddRefs(uri), codebaseStr); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr secman = - do_GetService(kScriptSecurityManagerContractID); - NS_ENSURE_TRUE(secman, NS_ERROR_FAILURE); - - // We could allow passing in the app-id and browser-element info to the - // sandbox constructor. But creating a sandbox based on a string is a - // deprecated API so no need to add features to it. - rv = secman->GetNoAppCodebasePrincipal(uri, principal); - NS_ENSURE_SUCCESS(rv, rv); - NS_ENSURE_TRUE(*principal, NS_ERROR_FAILURE); - - return NS_OK; -} - -// for sandbox constructor the first argument can be a principal object or -// a script object principal (Document, Window) -nsresult -GetPrincipalOrSOP(JSContext *cx, HandleObject from, nsISupports **out) -{ - MOZ_ASSERT(out); - *out = NULL; - - nsXPConnect* xpc = nsXPConnect::XPConnect(); - nsCOMPtr wrapper; - xpc->GetWrappedNativeOfJSObject(cx, from, - getter_AddRefs(wrapper)); - - NS_ENSURE_TRUE(wrapper, NS_ERROR_INVALID_ARG); - - if (nsCOMPtr sop = do_QueryWrappedNative(wrapper)) { - sop.forget(out); - return NS_OK; - } - - nsCOMPtr principal = do_QueryWrappedNative(wrapper); - principal.forget(out); - NS_ENSURE_TRUE(*out, NS_ERROR_INVALID_ARG); - - return NS_OK; -} - -// the first parameter of the sandbox constructor might be an array of principals, either in string -// format or actual objects (see GetPrincipalOrSOP) -nsresult -GetExpandedPrincipal(JSContext *cx, HandleObject arrayObj, nsIExpandedPrincipal **out) -{ - MOZ_ASSERT(out); - uint32_t length; - - if (!JS_IsArrayObject(cx, arrayObj) || - !JS_GetArrayLength(cx, arrayObj, &length) || - !length) - { - // we need a white list of principals or uri strings to create an - // expanded principal, if we got an empty array or something else - // report error - return NS_ERROR_INVALID_ARG; - } - - nsTArray< nsCOMPtr > allowedDomains(length); - allowedDomains.SetLength(length); - nsIScriptSecurityManager *ssm = XPCWrapper::GetSecurityManager(); - NS_ENSURE_TRUE(ssm, NS_ERROR_XPC_UNEXPECTED); - - for (uint32_t i = 0; i < length; ++i) { - RootedValue allowed(cx); - if (!JS_GetElement(cx, arrayObj, i, &allowed)) - return NS_ERROR_INVALID_ARG; - - nsresult rv; - nsCOMPtr principal; - if (allowed.isString()) { - // in case of string let's try to fetch a codebase principal from it - RootedString str(cx, allowed.toString()); - rv = GetPrincipalFromString(cx, str, getter_AddRefs(principal)); - NS_ENSURE_SUCCESS(rv, rv); - } else if (allowed.isObject()) { - // in case of object let's see if it's a Principal or a ScriptObjectPrincipal - nsCOMPtr prinOrSop; - RootedObject obj(cx, &allowed.toObject()); - rv = GetPrincipalOrSOP(cx, obj, getter_AddRefs(prinOrSop)); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr sop(do_QueryInterface(prinOrSop)); - principal = do_QueryInterface(prinOrSop); - if (sop) { - principal = sop->GetPrincipal(); - } - } - NS_ENSURE_TRUE(principal, NS_ERROR_INVALID_ARG); - - // We do not allow ExpandedPrincipals to contain any system principals - bool isSystem; - rv = ssm->IsSystemPrincipal(principal, &isSystem); - NS_ENSURE_SUCCESS(rv, rv); - NS_ENSURE_FALSE(isSystem, NS_ERROR_INVALID_ARG); - allowedDomains[i] = principal; - } - - nsCOMPtr result = new nsExpandedPrincipal(allowedDomains); - result.forget(out); - return NS_OK; -} - -// helper that tries to get a property form the options object -nsresult -GetPropFromOptions(JSContext *cx, HandleObject from, const char *name, MutableHandleValue prop, - bool *found) -{ - if (!JS_HasProperty(cx, from, name, found)) - return NS_ERROR_INVALID_ARG; - - if (found && !JS_GetProperty(cx, from, name, prop)) - return NS_ERROR_INVALID_ARG; - - return NS_OK; -} - -// helper that tries to get a boolean property form the options object -nsresult -GetBoolPropFromOptions(JSContext *cx, HandleObject from, const char *name, bool *prop) -{ - MOZ_ASSERT(prop); - - - RootedValue value(cx); - bool found; - if (NS_FAILED(GetPropFromOptions(cx, from, name, &value, &found))) - return NS_ERROR_INVALID_ARG; - - if (!found) - return NS_OK; - - if (!value.isBoolean()) - return NS_ERROR_INVALID_ARG; - - *prop = value.toBoolean(); - return NS_OK; -} - -// helper that tries to get an object property form the options object -nsresult -GetObjPropFromOptions(JSContext *cx, HandleObject from, const char *name, JSObject **prop) -{ - MOZ_ASSERT(prop); - - RootedValue value(cx); - bool found; - if (NS_FAILED(GetPropFromOptions(cx, from, name, &value, &found))) - return NS_ERROR_INVALID_ARG; - - if (!found) { - *prop = NULL; - return NS_OK; - } - - if (!value.isObject()) - return NS_ERROR_INVALID_ARG; - - *prop = &value.toObject(); - return NS_OK; -} - -// helper that tries to get a string property form the options object -nsresult -GetStringPropFromOptions(JSContext *cx, HandleObject from, const char *name, nsCString &prop) -{ - RootedValue value(cx); - bool found; - nsresult rv = GetPropFromOptions(cx, from, name, &value, &found); - NS_ENSURE_SUCCESS(rv, rv); - - if (!found) - return NS_OK; - - NS_ENSURE_TRUE(value.isString(), NS_ERROR_INVALID_ARG); - - char *tmp = JS_EncodeString(cx, value.toString()); - NS_ENSURE_TRUE(tmp, NS_ERROR_INVALID_ARG); - prop.Adopt(tmp, strlen(tmp)); - return NS_OK; -} - -// helper that parsing the sandbox options object (from) and sets the fields of the incoming options struct (options) -nsresult -ParseOptionsObject(JSContext *cx, jsval from, SandboxOptions &options) -{ - NS_ENSURE_TRUE(from.isObject(), NS_ERROR_INVALID_ARG); - RootedObject optionsObject(cx, &from.toObject()); - nsresult rv = GetObjPropFromOptions(cx, optionsObject, - "sandboxPrototype", options.proto.address()); - NS_ENSURE_SUCCESS(rv, rv); - - rv = GetBoolPropFromOptions(cx, optionsObject, - "wantXrays", &options.wantXrays); - NS_ENSURE_SUCCESS(rv, rv); - - rv = GetBoolPropFromOptions(cx, optionsObject, - "wantComponents", &options.wantComponents); - NS_ENSURE_SUCCESS(rv, rv); - - rv = GetBoolPropFromOptions(cx, optionsObject, - "wantXHRConstructor", &options.wantXHRConstructor); - NS_ENSURE_SUCCESS(rv, rv); - - rv = GetBoolPropFromOptions(cx, optionsObject, - "wantExportHelpers", &options.wantExportHelpers); - NS_ENSURE_SUCCESS(rv, rv); - - rv = GetStringPropFromOptions(cx, optionsObject, - "sandboxName", options.sandboxName); - NS_ENSURE_SUCCESS(rv, rv); - - rv = GetObjPropFromOptions(cx, optionsObject, - "sameZoneAs", options.sameZoneAs.address()); - NS_ENSURE_SUCCESS(rv, rv); - - return NS_OK; -} - -static nsresult -AssembleSandboxMemoryReporterName(JSContext *cx, nsCString &sandboxName) -{ - // Use a default name when the caller did not provide a sandboxName. - if (sandboxName.IsEmpty()) - sandboxName = NS_LITERAL_CSTRING("[anonymous sandbox]"); - - nsXPConnect* xpc = nsXPConnect::XPConnect(); - // Get the xpconnect native call context. - nsAXPCNativeCallContext *cc = nullptr; - xpc->GetCurrentNativeCallContext(&cc); - NS_ENSURE_TRUE(cc, NS_ERROR_INVALID_ARG); - - // Get the current source info from xpc. - nsCOMPtr frame; - xpc->GetCurrentJSStack(getter_AddRefs(frame)); - - // Append the caller's location information. - if (frame) { - nsCString location; - int32_t lineNumber = 0; - frame->GetFilename(getter_Copies(location)); - frame->GetLineNumber(&lineNumber); - - sandboxName.AppendLiteral(" (from: "); - sandboxName.Append(location); - sandboxName.AppendLiteral(":"); - sandboxName.AppendInt(lineNumber); - sandboxName.AppendLiteral(")"); - } - - return NS_OK; -} - -// static -nsresult -nsXPCComponents_utils_Sandbox::CallOrConstruct(nsIXPConnectWrappedNative *wrapper, - JSContext *cx, HandleObject obj, - const CallArgs &args, bool *_retval) -{ - if (args.length() < 1) - return ThrowAndFail(NS_ERROR_XPC_NOT_ENOUGH_ARGS, cx, _retval); - - nsresult rv; - - // Make sure to set up principals on the sandbox before initing classes - nsCOMPtr principal; - nsCOMPtr expanded; - nsCOMPtr prinOrSop; - - if (args[0].isString()) { - RootedString str(cx, args[0].toString()); - rv = GetPrincipalFromString(cx, str, getter_AddRefs(principal)); - prinOrSop = principal; - } else if (args[0].isObject()) { - RootedObject obj(cx, &args[0].toObject()); - if (JS_IsArrayObject(cx, obj)) { - rv = GetExpandedPrincipal(cx, obj, getter_AddRefs(expanded)); - prinOrSop = expanded; - } else { - rv = GetPrincipalOrSOP(cx, obj, getter_AddRefs(prinOrSop)); - } - } else { - return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval); - } - - if (NS_FAILED(rv)) - return ThrowAndFail(rv, cx, _retval); - - SandboxOptions options(cx); - - if (args.length() > 1 && args[1].isObject()) { - if (NS_FAILED(ParseOptionsObject(cx, args[1], options))) - return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval); - } - - if (NS_FAILED(AssembleSandboxMemoryReporterName(cx, options.sandboxName))) - return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval); - - rv = xpc_CreateSandboxObject(cx, args.rval().address(), prinOrSop, options); - - if (NS_FAILED(rv)) - return ThrowAndFail(rv, cx, _retval); - - *_retval = true; - - return rv; -} - -class ContextHolder : public nsIScriptObjectPrincipal -{ -public: - ContextHolder(JSContext *aOuterCx, HandleObject aSandbox, nsIPrincipal *aPrincipal); - virtual ~ContextHolder(); - - JSContext * GetJSContext() - { - return mJSContext; - } - - nsIPrincipal * GetPrincipal() { return mPrincipal; } - - NS_DECL_ISUPPORTS - -private: - JSContext* mJSContext; - nsCOMPtr mPrincipal; -}; - -NS_IMPL_ISUPPORTS1(ContextHolder, nsIScriptObjectPrincipal) - -ContextHolder::ContextHolder(JSContext *aOuterCx, - HandleObject aSandbox, - nsIPrincipal *aPrincipal) - : mJSContext(JS_NewContext(JS_GetRuntime(aOuterCx), 1024)), - mPrincipal(aPrincipal) -{ - if (mJSContext) { - bool isChrome; - DebugOnly rv = XPCWrapper::GetSecurityManager()-> - IsSystemPrincipal(mPrincipal, &isChrome); - MOZ_ASSERT(NS_SUCCEEDED(rv)); - - JS_SetOptions(mJSContext, - JS_GetOptions(mJSContext) | - JSOPTION_DONT_REPORT_UNCAUGHT | - JSOPTION_PRIVATE_IS_NSISUPPORTS); - js::SetDefaultObjectForContext(mJSContext, aSandbox); - JS_SetContextPrivate(mJSContext, this); - } -} - -ContextHolder::~ContextHolder() -{ - if (mJSContext) - JS_DestroyContextNoGC(mJSContext); -} - -/***************************************************************************/ - /* void evalInSandbox(in AString source, in nativeobj sandbox); */ NS_IMETHODIMP nsXPCComponents_Utils::EvalInSandbox(const nsAString& source, @@ -4203,109 +2840,6 @@ nsXPCComponents_Utils::EvalInSandbox(const nsAString& source, return NS_OK; } -nsresult -xpc_EvalInSandbox(JSContext *cx, HandleObject sandboxArg, const nsAString& source, - const char *filename, int32_t lineNo, - JSVersion jsVersion, bool returnStringOnly, MutableHandleValue rval) -{ - JS_AbortIfWrongThread(JS_GetRuntime(cx)); - rval.set(UndefinedValue()); - - bool waiveXray = xpc::WrapperFactory::HasWaiveXrayFlag(sandboxArg); - RootedObject sandbox(cx, js::CheckedUnwrap(sandboxArg)); - if (!sandbox || js::GetObjectJSClass(sandbox) != &SandboxClass) { - return NS_ERROR_INVALID_ARG; - } - - nsIScriptObjectPrincipal *sop = - (nsIScriptObjectPrincipal*)xpc_GetJSPrivate(sandbox); - MOZ_ASSERT(sop, "Invalid sandbox passed"); - nsCOMPtr prin = sop->GetPrincipal(); - NS_ENSURE_TRUE(prin, NS_ERROR_FAILURE); - - nsAutoCString filenameBuf; - if (!filename) { - // Default to the spec of the principal. - nsJSPrincipals::get(prin)->GetScriptLocation(filenameBuf); - filename = filenameBuf.get(); - lineNo = 1; - } - - // We create a separate cx to do the sandbox evaluation. Scope it. - RootedValue v(cx, UndefinedValue()); - RootedValue exn(cx, UndefinedValue()); - bool ok = true; - { - // Make a special cx for the sandbox and push it. - // NB: As soon as the RefPtr goes away, the cx goes away. So declare - // it first so that it disappears last. - nsRefPtr sandcxHolder = new ContextHolder(cx, sandbox, prin); - JSContext *sandcx = sandcxHolder->GetJSContext(); - if (!sandcx) { - JS_ReportError(cx, "Can't prepare context for evalInSandbox"); - return NS_ERROR_OUT_OF_MEMORY; - } - nsCxPusher pusher; - pusher.Push(sandcx); - JSAutoCompartment ac(sandcx, sandbox); - - JS::CompileOptions options(sandcx); - options.setPrincipals(nsJSPrincipals::get(prin)) - .setFileAndLine(filename, lineNo); - if (jsVersion != JSVERSION_DEFAULT) - options.setVersion(jsVersion); - JS::RootedObject rootedSandbox(sandcx, sandbox); - ok = JS::Evaluate(sandcx, rootedSandbox, options, - PromiseFlatString(source).get(), source.Length(), - v.address()); - if (ok && returnStringOnly && !v.isUndefined()) { - JSString *str = JS_ValueToString(sandcx, v); - ok = !!str; - v = ok ? JS::StringValue(str) : JS::UndefinedValue(); - } - - // If the sandbox threw an exception, grab it off the context. - if (JS_GetPendingException(sandcx, exn.address())) { - MOZ_ASSERT(!ok); - JS_ClearPendingException(sandcx); - if (returnStringOnly) { - // The caller asked for strings only, convert the - // exception into a string. - JSString *str = JS_ValueToString(sandcx, exn); - exn = str ? JS::StringValue(str) : JS::UndefinedValue(); - } - } - } - - // - // Alright, we're back on the caller's cx. If an error occured, try to - // wrap and set the exception. Otherwise, wrap the return value. - // - - if (!ok) { - // If we end up without an exception, it was probably due to OOM along - // the way, in which case we thow. Otherwise, wrap it. - if (exn.isUndefined() || !JS_WrapValue(cx, exn.address())) - return NS_ERROR_OUT_OF_MEMORY; - - // Set the exception on our caller's cx. - JS_SetPendingException(cx, exn); - return NS_ERROR_FAILURE; - } - - // Transitively apply Xray waivers if |sb| was waived. - if (waiveXray) { - ok = xpc::WrapperFactory::WaiveXrayAndWrap(cx, v.address()); - } else { - ok = JS_WrapValue(cx, v.address()); - } - NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE); - - // Whew! - rval.set(v); - return NS_OK; -} - /* JSObject import (in AUTF8String registryLocation, * [optional] in JSObject targetObj); */ @@ -4564,73 +3098,6 @@ nsXPCComponents_Utils::CreateDateIn(const Value &vobj, int64_t msec, JSContext * return NS_OK; } -bool -NonCloningFunctionForwarder(JSContext *cx, unsigned argc, Value *vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - - RootedValue v(cx, js::GetFunctionNativeReserved(&args.callee(), 0)); - MOZ_ASSERT(v.isObject(), "weird function"); - - JSObject *obj = JS_THIS_OBJECT(cx, vp); - if (!obj) { - return false; - } - return JS_CallFunctionValue(cx, obj, v, args.length(), args.array(), vp); -} - -/* - * Forwards the call to the exported function. Clones all the non reflectors, ignores - * the |this| argument. - */ -bool -CloningFunctionForwarder(JSContext *cx, unsigned argc, Value *vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - - RootedValue v(cx, js::GetFunctionNativeReserved(&args.callee(), 0)); - NS_ASSERTION(v.isObject(), "weird function"); - RootedObject origFunObj(cx, UncheckedUnwrap(&v.toObject())); - { - JSAutoCompartment ac(cx, origFunObj); - // Note: only the arguments are cloned not the |this| or the |callee|. - // Function forwarder does not use those. - for (unsigned i = 0; i < args.length(); i++) { - if (!CloneNonReflectors(cx, args[i])) { - return false; - } - } - - // JS API does not support any JSObject to JSFunction conversion, - // so let's use JS_CallFunctionValue instead. - RootedValue functionVal(cx); - functionVal.setObject(*origFunObj); - - if (!JS_CallFunctionValue(cx, nullptr, functionVal, args.length(), args.array(), vp)) - return false; - } - - // Return value must be wrapped. - return JS_WrapValue(cx, vp); -} - -bool -NewFunctionForwarder(JSContext *cx, HandleId id, HandleObject callable, bool doclone, - MutableHandleValue vp) -{ - JSFunction *fun = js::NewFunctionByIdWithReserved(cx, doclone ? CloningFunctionForwarder : - NonCloningFunctionForwarder, - 0,0, JS::CurrentGlobalOrNull(cx), id); - - if (!fun) - return false; - - JSObject *funobj = JS_GetFunctionObject(fun); - js::SetFunctionNativeReserved(funobj, 0, ObjectValue(*callable)); - vp.setObject(*funobj); - return true; -} - /* void makeObjectPropsNormal(jsval vobj); */ NS_IMETHODIMP nsXPCComponents_Utils::MakeObjectPropsNormal(const Value &vobj, JSContext *cx) @@ -4860,7 +3327,7 @@ nsXPCComponents_Utils::NukeSandbox(const JS::Value &obj, JSContext *cx) JSObject *wrapper = &obj.toObject(); NS_ENSURE_TRUE(IsWrapper(wrapper), NS_ERROR_INVALID_ARG); JSObject *sb = UncheckedUnwrap(wrapper); - NS_ENSURE_TRUE(GetObjectJSClass(sb) == &SandboxClass, NS_ERROR_INVALID_ARG); + NS_ENSURE_TRUE(IsSandbox(sb), NS_ERROR_INVALID_ARG); NukeCrossCompartmentWrappers(cx, AllCompartments(), SingleCompartment(GetObjectCompartment(sb)), NukeWindowReferences); diff --git a/js/xpconnect/src/moz.build b/js/xpconnect/src/moz.build index 5adf20a1bd77..da8880f24f37 100644 --- a/js/xpconnect/src/moz.build +++ b/js/xpconnect/src/moz.build @@ -51,6 +51,7 @@ CPP_SOURCES += [ 'nsCxPusher.cpp', 'nsScriptError.cpp', 'nsXPConnect.cpp', + 'Sandbox.cpp', ] FAIL_ON_WARNINGS = True diff --git a/js/xpconnect/src/xpcprivate.h b/js/xpconnect/src/xpcprivate.h index 9042a47fb210..8fa989ab3fa9 100644 --- a/js/xpconnect/src/xpcprivate.h +++ b/js/xpconnect/src/xpcprivate.h @@ -3681,7 +3681,22 @@ xpc_GetSafeJSContext() return XPCJSRuntime::Get()->GetJSContextStack()->GetSafeJSContext(); } +bool +NewFunctionForwarder(JSContext *cx, JS::HandleId id, JS::HandleObject callable, + bool doclone, JS::MutableHandleValue vp); + +nsresult +ThrowAndFail(nsresult errNum, JSContext* cx, bool* retval); + +// Infallible. +already_AddRefed +NewSandboxConstructor(); + +bool +IsSandbox(JSObject *obj); + namespace xpc { + struct SandboxOptions { SandboxOptions(JSContext *cx) : wantXrays(true)