/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* 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 "jsfriendapi.h" #include "js/Array.h" // JS::GetArrayLength, JS::IsArrayObject #include "js/CharacterEncoding.h" #include "js/CompilationAndEvaluation.h" #include "js/Object.h" // JS::GetClass, JS::GetCompartment, JS::GetReservedSlot #include "js/PropertySpec.h" #include "js/Proxy.h" #include "js/SourceText.h" #include "js/StructuredClone.h" #include "nsContentUtils.h" #include "nsGlobalWindow.h" #include "nsIException.h" // for nsIStackFrame #include "nsIScriptContext.h" #include "nsIScriptObjectPrincipal.h" #include "nsIURI.h" #include "nsJSUtils.h" #include "nsNetUtil.h" #include "ExpandedPrincipal.h" #include "WrapperFactory.h" #include "xpcprivate.h" #include "xpc_make_class.h" #include "XPCWrapper.h" #include "Crypto.h" #include "mozilla/dom/BindingCallContext.h" #include "mozilla/dom/BindingUtils.h" #include "mozilla/dom/BlobBinding.h" #include "mozilla/dom/cache/CacheStorage.h" #include "mozilla/dom/CSSBinding.h" #include "mozilla/dom/CSSRuleBinding.h" #include "mozilla/dom/DirectoryBinding.h" #include "mozilla/dom/DocumentBinding.h" #include "mozilla/dom/DOMExceptionBinding.h" #include "mozilla/dom/DOMParserBinding.h" #include "mozilla/dom/DOMTokenListBinding.h" #include "mozilla/dom/ElementBinding.h" #include "mozilla/dom/EventBinding.h" #include "mozilla/dom/Exceptions.h" #include "mozilla/dom/IndexedDatabaseManager.h" #include "mozilla/dom/Fetch.h" #include "mozilla/dom/FileBinding.h" #include "mozilla/dom/HeadersBinding.h" #include "mozilla/dom/InspectorUtilsBinding.h" #include "mozilla/dom/MessageChannelBinding.h" #include "mozilla/dom/MessagePortBinding.h" #include "mozilla/dom/NodeBinding.h" #include "mozilla/dom/NodeFilterBinding.h" #include "mozilla/dom/PerformanceBinding.h" #include "mozilla/dom/PromiseBinding.h" #include "mozilla/dom/PromiseDebuggingBinding.h" #include "mozilla/dom/RangeBinding.h" #include "mozilla/dom/RequestBinding.h" #include "mozilla/dom/ResponseBinding.h" #ifdef MOZ_WEBRTC # include "mozilla/dom/RTCIdentityProviderRegistrar.h" #endif #include "mozilla/dom/FileReaderBinding.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/SelectionBinding.h" #include "mozilla/dom/TextDecoderBinding.h" #include "mozilla/dom/TextEncoderBinding.h" #include "mozilla/dom/UnionConversions.h" #include "mozilla/dom/URLBinding.h" #include "mozilla/dom/URLSearchParamsBinding.h" #include "mozilla/dom/XMLHttpRequest.h" #include "mozilla/dom/XMLSerializerBinding.h" #include "mozilla/dom/FormDataBinding.h" #include "mozilla/dom/nsCSPContext.h" #ifdef MOZ_GLEAN # include "mozilla/glean/bindings/Glean.h" # include "mozilla/glean/bindings/GleanPings.h" #endif #include "mozilla/BasePrincipal.h" #include "mozilla/DeferredFinalize.h" #include "mozilla/ExtensionPolicyService.h" #include "mozilla/NullPrincipal.h" #include "mozilla/ResultExtensions.h" #include "mozilla/StaticPrefs_extensions.h" using namespace mozilla; using namespace JS; using namespace xpc; using mozilla::dom::DestroyProtoAndIfaceCache; using mozilla::dom::IndexedDatabaseManager; NS_IMPL_CYCLE_COLLECTION_CLASS(SandboxPrivate) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(SandboxPrivate) NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR tmp->UnlinkObjectsInGlobal(); NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(SandboxPrivate) tmp->TraverseObjectsInGlobal(cb); NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(SandboxPrivate) NS_IMPL_CYCLE_COLLECTING_ADDREF(SandboxPrivate) NS_IMPL_CYCLE_COLLECTING_RELEASE(SandboxPrivate) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SandboxPrivate) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIScriptObjectPrincipal) NS_INTERFACE_MAP_ENTRY(nsIScriptObjectPrincipal) NS_INTERFACE_MAP_ENTRY(nsIGlobalObject) NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) NS_INTERFACE_MAP_END class nsXPCComponents_utils_Sandbox : public nsIXPCComponents_utils_Sandbox, public nsIXPCScriptable { public: // Aren't macros nice? NS_DECL_ISUPPORTS NS_DECL_NSIXPCCOMPONENTS_UTILS_SANDBOX NS_DECL_NSIXPCSCRIPTABLE public: nsXPCComponents_utils_Sandbox(); private: virtual ~nsXPCComponents_utils_Sandbox(); static nsresult CallOrConstruct(nsIXPConnectWrappedNative* wrapper, JSContext* cx, HandleObject obj, const CallArgs& args, bool* _retval); }; already_AddRefed xpc::NewSandboxConstructor() { nsCOMPtr sbConstructor = new nsXPCComponents_utils_Sandbox(); return sbConstructor.forget(); } static bool SandboxDump(JSContext* cx, unsigned argc, Value* vp) { if (!nsJSUtils::DumpEnabled()) { return true; } CallArgs args = CallArgsFromVp(argc, vp); if (args.length() == 0) { return true; } RootedString str(cx, ToString(cx, args[0])); if (!str) { return false; } JS::UniqueChars utf8str = JS_EncodeStringToUTF8(cx, str); char* cstr = utf8str.get(); if (!cstr) { return false; } #if defined(XP_MACOSX) // Be nice and convert all \r to \n. char* c = cstr; char* cEnd = cstr + strlen(cstr); while (c < cEnd) { if (*c == '\r') { *c = '\n'; } c++; } #endif #ifdef ANDROID __android_log_write(ANDROID_LOG_INFO, "GeckoDump", cstr); #endif fputs(cstr, stdout); fflush(stdout); args.rval().setBoolean(true); return true; } static bool SandboxDebug(JSContext* cx, unsigned argc, Value* 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 = ToString(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); } JSAutoRealm ar(cx, funobj); RootedValue funval(cx, ObjectValue(*funobj)); JSFunction* fun = JS_ValueToFunction(cx, funval); 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; } } JS_MarkCrossZoneIdValue(cx, StringValue(funname)); RootedId id(cx); if (!JS_StringToId(cx, funname, &id)) { 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); if (!args.computeThis(cx, &thisObject)) { return false; } if (!JS_SetPropertyById(cx, thisObject, id, args[0])) { return false; } args.rval().setUndefined(); return true; } static bool SandboxCreateCrypto(JSContext* cx, JS::HandleObject obj) { MOZ_ASSERT(JS_IsGlobalObject(obj)); nsIGlobalObject* native = xpc::NativeGlobal(obj); MOZ_ASSERT(native); dom::Crypto* crypto = new dom::Crypto(native); JS::RootedObject wrapped(cx, crypto->WrapObject(cx, nullptr)); return JS_DefineProperty(cx, obj, "crypto", wrapped, JSPROP_ENUMERATE); } #ifdef MOZ_WEBRTC static bool SandboxCreateRTCIdentityProvider(JSContext* cx, JS::HandleObject obj) { MOZ_ASSERT(JS_IsGlobalObject(obj)); nsCOMPtr nativeGlobal = xpc::NativeGlobal(obj); MOZ_ASSERT(nativeGlobal); dom::RTCIdentityProviderRegistrar* registrar = new dom::RTCIdentityProviderRegistrar(nativeGlobal); JS::RootedObject wrapped(cx, registrar->WrapObject(cx, nullptr)); return JS_DefineProperty(cx, obj, "rtcIdentityProvider", wrapped, JSPROP_ENUMERATE); } #endif static bool SetFetchRequestFromValue(JSContext* cx, RequestOrUSVString& request, const MutableHandleValue& requestOrUrl) { RequestOrUSVStringArgument requestHolder(request); bool noMatch = true; if (requestOrUrl.isObject() && !requestHolder.TrySetToRequest(cx, requestOrUrl, noMatch, false)) { return false; } if (noMatch && !requestHolder.TrySetToUSVString(cx, requestOrUrl, noMatch)) { return false; } if (noMatch) { return false; } return true; } static bool SandboxFetch(JSContext* cx, JS::HandleObject scope, const CallArgs& args) { if (args.length() < 1) { JS_ReportErrorASCII(cx, "fetch requires at least 1 argument"); return false; } RequestOrUSVString request; if (!SetFetchRequestFromValue(cx, request, args[0])) { JS_ReportErrorASCII(cx, "fetch requires a string or Request in argument 1"); return false; } RootedDictionary options(cx); BindingCallContext callCx(cx, "fetch"); if (!options.Init(cx, args.hasDefined(1) ? args[1] : JS::NullHandleValue, "Argument 2", false)) { return false; } nsCOMPtr global = xpc::NativeGlobal(scope); if (!global) { return false; } dom::CallerType callerType = nsContentUtils::IsSystemCaller(cx) ? dom::CallerType::System : dom::CallerType::NonSystem; ErrorResult rv; RefPtr response = FetchRequest( global, Constify(request), Constify(options), callerType, rv); if (rv.MaybeSetPendingException(cx)) { return false; } args.rval().setObject(*response->PromiseObj()); return true; } static bool SandboxFetchPromise(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedObject scope(cx, JS::CurrentGlobalOrNull(cx)); if (SandboxFetch(cx, scope, args)) { return true; } return ConvertExceptionToPromise(cx, args.rval()); } static bool SandboxCreateFetch(JSContext* cx, HandleObject obj) { MOZ_ASSERT(JS_IsGlobalObject(obj)); return JS_DefineFunction(cx, obj, "fetch", SandboxFetchPromise, 2, 0) && dom::Request_Binding::GetConstructorObject(cx) && dom::Response_Binding::GetConstructorObject(cx) && dom::Headers_Binding::GetConstructorObject(cx); } static bool SandboxIsProxy(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (args.length() < 1) { JS_ReportErrorASCII(cx, "Function requires at least 1 argument"); return false; } if (!args[0].isObject()) { args.rval().setBoolean(false); return true; } RootedObject obj(cx, &args[0].toObject()); // CheckedUnwrapStatic is OK here, since we only care about whether // it's a scripted proxy and the things CheckedUnwrapStatic fails on // are not. obj = js::CheckedUnwrapStatic(obj); if (!obj) { args.rval().setBoolean(false); return true; } args.rval().setBoolean(js::IsScriptedProxy(obj)); return true; } /* * Expected type of the arguments and the return value: * function exportFunction(function funToExport, * object targetScope, * [optional] object options) */ static bool SandboxExportFunction(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (args.length() < 2) { JS_ReportErrorASCII(cx, "Function requires at least 2 arguments"); return false; } RootedValue options(cx, args.length() > 2 ? args[2] : UndefinedValue()); return ExportFunction(cx, args[0], args[1], options, args.rval()); } static bool SandboxCreateObjectIn(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (args.length() < 1) { JS_ReportErrorASCII(cx, "Function requires at least 1 argument"); return false; } RootedObject optionsObj(cx); bool calledWithOptions = args.length() > 1; if (calledWithOptions) { if (!args[1].isObject()) { JS_ReportErrorASCII( cx, "Expected the 2nd argument (options) to be an object"); return false; } optionsObj = &args[1].toObject(); } CreateObjectInOptions options(cx, optionsObj); if (calledWithOptions && !options.Parse()) { return false; } return xpc::CreateObjectIn(cx, args[0], options, args.rval()); } static bool SandboxCloneInto(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (args.length() < 2) { JS_ReportErrorASCII(cx, "Function requires at least 2 arguments"); return false; } RootedValue options(cx, args.length() > 2 ? args[2] : UndefinedValue()); return xpc::CloneInto(cx, args[0], args[1], options, args.rval()); } static void sandbox_finalize(JSFreeOp* fop, JSObject* obj) { nsIScriptObjectPrincipal* sop = static_cast(xpc_GetJSPrivate(obj)); if (!sop) { // sop can be null if CreateSandboxObject fails in the middle. return; } static_cast(sop)->ForgetGlobalObject(obj); DestroyProtoAndIfaceCache(obj); DeferredFinalize(sop); } static size_t sandbox_moved(JSObject* obj, JSObject* old) { // Note that this hook can be called before the private pointer is set. In // this case the SandboxPrivate will not exist yet, so there is nothing to // do. nsIScriptObjectPrincipal* sop = static_cast(xpc_GetJSPrivate(obj)); if (!sop) { return 0; } return static_cast(sop)->ObjectMoved(obj, old); } #define XPCONNECT_SANDBOX_CLASS_METADATA_SLOT \ (XPCONNECT_GLOBAL_EXTRA_SLOT_OFFSET) static const JSClassOps SandboxClassOps = { nullptr, // addProperty nullptr, // delProperty nullptr, // enumerate JS_NewEnumerateStandardClasses, // newEnumerate JS_ResolveStandardClass, // resolve JS_MayResolveStandardClass, // mayResolve sandbox_finalize, // finalize nullptr, // call nullptr, // hasInstance nullptr, // construct JS_GlobalObjectTraceHook, // trace }; static const js::ClassExtension SandboxClassExtension = { sandbox_moved, // objectMovedOp }; static const JSClass SandboxClass = { "Sandbox", XPCONNECT_GLOBAL_FLAGS_WITH_EXTRA_SLOTS(1) | JSCLASS_FOREGROUND_FINALIZE, &SandboxClassOps, JS_NULL_CLASS_SPEC, &SandboxClassExtension, JS_NULL_OBJECT_OPS}; static const JSFunctionSpec SandboxFunctions[] = { JS_FN("dump", SandboxDump, 1, 0), JS_FN("debug", SandboxDebug, 1, 0), JS_FN("importFunction", SandboxImport, 1, 0), JS_FS_END}; bool xpc::IsSandbox(JSObject* obj) { const JSClass* clasp = JS::GetClass(obj); return clasp == &SandboxClass; } /***************************************************************************/ nsXPCComponents_utils_Sandbox::nsXPCComponents_utils_Sandbox() = default; nsXPCComponents_utils_Sandbox::~nsXPCComponents_utils_Sandbox() = default; NS_IMPL_QUERY_INTERFACE(nsXPCComponents_utils_Sandbox, nsIXPCComponents_utils_Sandbox, nsIXPCScriptable) 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_FLAGS (XPC_SCRIPTABLE_WANT_CALL | XPC_SCRIPTABLE_WANT_CONSTRUCT) #include "xpc_map_end.h" /* This #undef's the above. */ class SandboxProxyHandler : public js::Wrapper { public: constexpr SandboxProxyHandler() : js::Wrapper(0) {} virtual bool getOwnPropertyDescriptor( JSContext* cx, JS::Handle proxy, JS::Handle id, JS::MutableHandle desc) const override; // We just forward the high-level methods to the BaseProxyHandler versions // which implement them in terms of lower-level methods. virtual bool has(JSContext* cx, JS::Handle proxy, JS::Handle id, bool* bp) const override; virtual bool get(JSContext* cx, JS::Handle proxy, JS::HandleValue receiver, JS::Handle id, JS::MutableHandle vp) const override; virtual bool set(JSContext* cx, JS::Handle proxy, JS::Handle id, JS::Handle v, JS::Handle receiver, JS::ObjectOpResult& result) const override; virtual bool hasOwn(JSContext* cx, JS::Handle proxy, JS::Handle id, bool* bp) const override; virtual bool getOwnEnumerablePropertyKeys( JSContext* cx, JS::Handle proxy, JS::MutableHandleIdVector props) const override; virtual bool enumerate(JSContext* cx, JS::Handle proxy, JS::MutableHandleIdVector props) const override; private: // Implements the custom getPropertyDescriptor behavior. If the getOwn // argument is true we only look for "own" properties. bool getPropertyDescriptorImpl( JSContext* cx, JS::Handle proxy, JS::Handle id, bool getOwn, JS::MutableHandle desc) const; }; static const SandboxProxyHandler sandboxProxyHandler; namespace xpc { bool IsSandboxPrototypeProxy(JSObject* obj) { return js::IsProxy(obj) && js::GetProxyHandler(obj) == &sandboxProxyHandler; } } // namespace xpc // A proxy handler that lets us wrap callables and invoke them with // the correct this object, while forwarding all other operations down // to them directly. class SandboxCallableProxyHandler : public js::Wrapper { public: constexpr SandboxCallableProxyHandler() : js::Wrapper(0) {} virtual bool call(JSContext* cx, JS::Handle proxy, const JS::CallArgs& args) const override; static const size_t SandboxProxySlot = 0; static inline JSObject* getSandboxProxy(JS::Handle proxy) { return &js::GetProxyReservedSlot(proxy, SandboxProxySlot).toObject(); } }; static const SandboxCallableProxyHandler sandboxCallableProxyHandler; bool SandboxCallableProxyHandler::call(JSContext* cx, JS::Handle proxy, const JS::CallArgs& args) const { // We forward the call to our underlying callable. // Get our SandboxProxyHandler proxy. RootedObject sandboxProxy(cx, getSandboxProxy(proxy)); MOZ_ASSERT(js::IsProxy(sandboxProxy) && js::GetProxyHandler(sandboxProxy) == &sandboxProxyHandler); // The global of the sandboxProxy is the sandbox global, and the // target object is the original proto. RootedObject sandboxGlobal(cx, JS::GetNonCCWObjectGlobal(sandboxProxy)); MOZ_ASSERT(IsSandbox(sandboxGlobal)); // 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|. bool isXray = WrapperFactory::IsXrayWrapper(sandboxProxy); RootedValue thisVal(cx, args.thisv()); if (isXray) { RootedObject thisObject(cx); if (!args.computeThis(cx, &thisObject)) { return false; } thisVal.setObject(*thisObject); } if (thisVal == ObjectValue(*sandboxGlobal)) { thisVal = ObjectValue(*js::GetProxyTargetObject(sandboxProxy)); } RootedValue func(cx, js::GetProxyPrivate(proxy)); return JS::Call(cx, thisVal, func, args, args.rval()); } /* * 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, HandleObject callable, HandleObject sandboxProtoProxy) { MOZ_ASSERT(JS::IsCallable(callable)); // Our proxy is wrapping the callable. So we need to use the // callable as the private. We put the given sandboxProtoProxy in // an extra slot, and our call() hook depends on that. MOZ_ASSERT(js::IsProxy(sandboxProtoProxy) && js::GetProxyHandler(sandboxProtoProxy) == &sandboxProxyHandler); RootedValue priv(cx, ObjectValue(*callable)); // We want to claim to have the same proto as our wrapped callable, so set // ourselves up with a lazy proto. js::ProxyOptions options; options.setLazyProto(true); JSObject* obj = js::NewProxyObject(cx, &sandboxCallableProxyHandler, priv, nullptr, options); if (obj) { js::SetProxyReservedSlot(obj, SandboxCallableProxyHandler::SandboxProxySlot, ObjectValue(*sandboxProtoProxy)); } return obj; } template bool WrapAccessorFunction(JSContext* cx, Op& op, PropertyDescriptor* desc, unsigned attrFlag, HandleObject sandboxProtoProxy) { if (!op) { return true; } if (!(desc->attrs & attrFlag)) { XPCThrower::Throw(NS_ERROR_UNEXPECTED, cx); return false; } RootedObject func(cx, JS_FUNC_TO_DATA_PTR(JSObject*, op)); func = WrapCallable(cx, func, sandboxProtoProxy); if (!func) { return false; } op = JS_DATA_TO_FUNC_PTR(Op, func.get()); return true; } static bool IsMaybeWrappedDOMConstructor(JSObject* obj) { // We really care about the underlying object here, which might be wrapped in // cross-compartment wrappers. CheckedUnwrapStatic is fine, since we just // care whether it's a DOM constructor. obj = js::CheckedUnwrapStatic(obj); if (!obj) { return false; } return dom::IsDOMConstructor(obj); } bool SandboxProxyHandler::getPropertyDescriptorImpl( JSContext* cx, JS::Handle proxy, JS::Handle id, bool getOwn, JS::MutableHandle desc) const { JS::RootedObject obj(cx, wrappedObject(proxy)); MOZ_ASSERT(JS::GetCompartment(obj) == JS::GetCompartment(proxy)); if (getOwn) { if (!JS_GetOwnPropertyDescriptorById(cx, obj, id, desc)) { return false; } } else { if (!JS_GetPropertyDescriptorById(cx, obj, id, 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. if (!WrapAccessorFunction(cx, desc.getter(), desc.address(), JSPROP_GETTER, proxy)) return false; if (!WrapAccessorFunction(cx, desc.setter(), desc.address(), JSPROP_SETTER, proxy)) return false; if (desc.value().isObject()) { RootedObject val(cx, &desc.value().toObject()); if (JS::IsCallable(val) && // Don't wrap DOM constructors: they don't care about the "this" // they're invoked with anyway, being constructors. And if we wrap // them here we break invariants like Node == Node and whatnot. !IsMaybeWrappedDOMConstructor(val)) { val = WrapCallable(cx, val, proxy); if (!val) { return false; } desc.value().setObject(*val); } } return true; } bool SandboxProxyHandler::getOwnPropertyDescriptor( JSContext* cx, JS::Handle proxy, JS::Handle id, JS::MutableHandle desc) const { return getPropertyDescriptorImpl(cx, proxy, id, /* getOwn = */ true, desc); } /* * Reuse the BaseProxyHandler versions of the derived traps that are implemented * in terms of the fundamental traps. */ bool SandboxProxyHandler::has(JSContext* cx, JS::Handle proxy, JS::Handle id, bool* bp) const { // This uses JS_GetPropertyDescriptorById for backward compatibility. Rooted desc(cx); if (!getPropertyDescriptorImpl(cx, proxy, id, /* getOwn = */ false, &desc)) { return false; } *bp = !!desc.object(); return true; } bool SandboxProxyHandler::hasOwn(JSContext* cx, JS::Handle proxy, JS::Handle id, bool* bp) const { return BaseProxyHandler::hasOwn(cx, proxy, id, bp); } bool SandboxProxyHandler::get(JSContext* cx, JS::Handle proxy, JS::Handle receiver, JS::Handle id, JS::MutableHandle vp) const { // This uses JS_GetPropertyDescriptorById for backward compatibility. Rooted desc(cx); if (!getPropertyDescriptorImpl(cx, proxy, id, /* getOwn = */ false, &desc)) { return false; } desc.assertCompleteIfFound(); if (!desc.object()) { vp.setUndefined(); return true; } // Everything after here follows [[Get]] for ordinary objects. if (desc.isDataDescriptor()) { vp.set(desc.value()); return true; } MOZ_ASSERT(desc.isAccessorDescriptor()); RootedObject getter(cx, desc.getterObject()); if (!getter) { vp.setUndefined(); return true; } return Call(cx, receiver, getter, HandleValueArray::empty(), vp); } bool SandboxProxyHandler::set(JSContext* cx, JS::Handle proxy, JS::Handle id, JS::Handle v, JS::Handle receiver, JS::ObjectOpResult& result) const { return BaseProxyHandler::set(cx, proxy, id, v, receiver, result); } bool SandboxProxyHandler::getOwnEnumerablePropertyKeys( JSContext* cx, JS::Handle proxy, MutableHandleIdVector props) const { return BaseProxyHandler::getOwnEnumerablePropertyKeys(cx, proxy, props); } bool SandboxProxyHandler::enumerate(JSContext* cx, JS::Handle proxy, JS::MutableHandleIdVector props) const { return BaseProxyHandler::enumerate(cx, proxy, props); } bool xpc::GlobalProperties::Parse(JSContext* cx, JS::HandleObject obj) { uint32_t length; bool ok = JS::GetArrayLength(cx, obj, &length); NS_ENSURE_TRUE(ok, false); for (uint32_t i = 0; i < length; i++) { RootedValue nameValue(cx); ok = JS_GetElement(cx, obj, i, &nameValue); NS_ENSURE_TRUE(ok, false); if (!nameValue.isString()) { JS_ReportErrorASCII(cx, "Property names must be strings"); return false; } JSLinearString* nameStr = JS_EnsureLinearString(cx, nameValue.toString()); if (!nameStr) { return false; } if (JS_LinearStringEqualsLiteral(nameStr, "Blob")) { Blob = true; } else if (JS_LinearStringEqualsLiteral(nameStr, "ChromeUtils")) { ChromeUtils = true; } else if (JS_LinearStringEqualsLiteral(nameStr, "CSS")) { CSS = true; } else if (JS_LinearStringEqualsLiteral(nameStr, "CSSRule")) { CSSRule = true; } else if (JS_LinearStringEqualsLiteral(nameStr, "Document")) { Document = true; } else if (JS_LinearStringEqualsLiteral(nameStr, "Directory")) { Directory = true; } else if (JS_LinearStringEqualsLiteral(nameStr, "DOMException")) { DOMException = true; } else if (JS_LinearStringEqualsLiteral(nameStr, "DOMParser")) { DOMParser = true; } else if (JS_LinearStringEqualsLiteral(nameStr, "DOMTokenList")) { DOMTokenList = true; } else if (JS_LinearStringEqualsLiteral(nameStr, "Element")) { Element = true; } else if (JS_LinearStringEqualsLiteral(nameStr, "Event")) { Event = true; } else if (JS_LinearStringEqualsLiteral(nameStr, "File")) { File = true; } else if (JS_LinearStringEqualsLiteral(nameStr, "FileReader")) { FileReader = true; } else if (JS_LinearStringEqualsLiteral(nameStr, "FormData")) { FormData = true; } else if (JS_LinearStringEqualsLiteral(nameStr, "Headers")) { Headers = true; } else if (JS_LinearStringEqualsLiteral(nameStr, "InspectorUtils")) { InspectorUtils = true; } else if (JS_LinearStringEqualsLiteral(nameStr, "MessageChannel")) { MessageChannel = true; } else if (JS_LinearStringEqualsLiteral(nameStr, "Node")) { Node = true; } else if (JS_LinearStringEqualsLiteral(nameStr, "NodeFilter")) { NodeFilter = true; } else if (JS_LinearStringEqualsLiteral(nameStr, "Performance")) { Performance = true; } else if (JS_LinearStringEqualsLiteral(nameStr, "PromiseDebugging")) { PromiseDebugging = true; } else if (JS_LinearStringEqualsLiteral(nameStr, "Range")) { Range = true; } else if (JS_LinearStringEqualsLiteral(nameStr, "Selection")) { Selection = true; } else if (JS_LinearStringEqualsLiteral(nameStr, "TextDecoder")) { TextDecoder = true; } else if (JS_LinearStringEqualsLiteral(nameStr, "TextEncoder")) { TextEncoder = true; } else if (JS_LinearStringEqualsLiteral(nameStr, "URL")) { URL = true; } else if (JS_LinearStringEqualsLiteral(nameStr, "URLSearchParams")) { URLSearchParams = true; } else if (JS_LinearStringEqualsLiteral(nameStr, "XMLHttpRequest")) { XMLHttpRequest = true; } else if (JS_LinearStringEqualsLiteral(nameStr, "XMLSerializer")) { XMLSerializer = true; } else if (JS_LinearStringEqualsLiteral(nameStr, "atob")) { atob = true; } else if (JS_LinearStringEqualsLiteral(nameStr, "btoa")) { btoa = true; } else if (JS_LinearStringEqualsLiteral(nameStr, "caches")) { caches = true; } else if (JS_LinearStringEqualsLiteral(nameStr, "crypto")) { crypto = true; } else if (JS_LinearStringEqualsLiteral(nameStr, "fetch")) { fetch = true; } else if (JS_LinearStringEqualsLiteral(nameStr, "indexedDB")) { indexedDB = true; } else if (JS_LinearStringEqualsLiteral(nameStr, "isSecureContext")) { isSecureContext = true; #ifdef MOZ_GLEAN } else if (JS_LinearStringEqualsLiteral(nameStr, "Glean")) { glean = true; } else if (JS_LinearStringEqualsLiteral(nameStr, "GleanPings")) { gleanPings = true; #endif #ifdef MOZ_WEBRTC } else if (JS_LinearStringEqualsLiteral(nameStr, "rtcIdentityProvider")) { rtcIdentityProvider = true; #endif } else { RootedString nameStr(cx, nameValue.toString()); JS::UniqueChars name = JS_EncodeStringToUTF8(cx, nameStr); if (!name) { return false; } JS_ReportErrorUTF8(cx, "Unknown property name: %s", name.get()); return false; } } return true; } bool xpc::GlobalProperties::Define(JSContext* cx, JS::HandleObject obj) { MOZ_ASSERT(js::GetContextCompartment(cx) == JS::GetCompartment(obj)); // Properties will be exposed to System automatically but not to Sandboxes // if |[Exposed=System]| is specified. // This function holds common properties not exposed automatically but able // to be requested either in |Cu.importGlobalProperties| or // |wantGlobalProperties| of a sandbox. if (Blob && !dom::Blob_Binding::GetConstructorObject(cx)) return false; if (ChromeUtils && !dom::ChromeUtils_Binding::GetConstructorObject(cx)) { return false; } if (CSS && !dom::CSS_Binding::GetConstructorObject(cx)) { return false; } if (CSSRule && !dom::CSSRule_Binding::GetConstructorObject(cx)) { return false; } if (Directory && !dom::Directory_Binding::GetConstructorObject(cx)) return false; if (Document && !dom::Document_Binding::GetConstructorObject(cx)) { return false; } if (DOMException && !dom::DOMException_Binding::GetConstructorObject(cx)) { return false; } if (DOMParser && !dom::DOMParser_Binding::GetConstructorObject(cx)) { return false; } if (DOMTokenList && !dom::DOMTokenList_Binding::GetConstructorObject(cx)) { return false; } if (Element && !dom::Element_Binding::GetConstructorObject(cx)) return false; if (Event && !dom::Event_Binding::GetConstructorObject(cx)) return false; if (File && !dom::File_Binding::GetConstructorObject(cx)) return false; if (FileReader && !dom::FileReader_Binding::GetConstructorObject(cx)) { return false; } if (FormData && !dom::FormData_Binding::GetConstructorObject(cx)) return false; if (Headers && !dom::Headers_Binding::GetConstructorObject(cx)) { return false; } if (InspectorUtils && !dom::InspectorUtils_Binding::GetConstructorObject(cx)) return false; if (MessageChannel && (!dom::MessageChannel_Binding::GetConstructorObject(cx) || !dom::MessagePort_Binding::GetConstructorObject(cx))) return false; if (Node && !dom::Node_Binding::GetConstructorObject(cx)) { return false; } if (NodeFilter && !dom::NodeFilter_Binding::GetConstructorObject(cx)) { return false; } if (Performance && !dom::Performance_Binding::GetConstructorObject(cx)) { return false; } if (PromiseDebugging && !dom::PromiseDebugging_Binding::GetConstructorObject(cx)) { return false; } if (Range && !dom::Range_Binding::GetConstructorObject(cx)) { return false; } if (Selection && !dom::Selection_Binding::GetConstructorObject(cx)) { return false; } if (TextDecoder && !dom::TextDecoder_Binding::GetConstructorObject(cx)) return false; if (TextEncoder && !dom::TextEncoder_Binding::GetConstructorObject(cx)) return false; if (URL && !dom::URL_Binding::GetConstructorObject(cx)) return false; if (URLSearchParams && !dom::URLSearchParams_Binding::GetConstructorObject(cx)) return false; if (XMLHttpRequest && !dom::XMLHttpRequest_Binding::GetConstructorObject(cx)) return false; if (XMLSerializer && !dom::XMLSerializer_Binding::GetConstructorObject(cx)) return false; if (atob && !JS_DefineFunction(cx, obj, "atob", Atob, 1, 0)) return false; if (btoa && !JS_DefineFunction(cx, obj, "btoa", Btoa, 1, 0)) return false; if (caches && !dom::cache::CacheStorage::DefineCaches(cx, obj)) { return false; } if (crypto && !SandboxCreateCrypto(cx, obj)) { return false; } if (fetch && !SandboxCreateFetch(cx, obj)) { return false; } // Note that isSecureContext here doesn't mean the context is actually secure // - just that the caller wants the property defined if (isSecureContext) { bool hasSecureContext = IsSecureContextOrObjectIsFromSecureContext(cx, obj); JS::Rooted secureJsValue(cx, JS::BooleanValue(hasSecureContext)); return JS_DefineProperty(cx, obj, "isSecureContext", secureJsValue, JSPROP_ENUMERATE); } #ifdef MOZ_WEBRTC if (rtcIdentityProvider && !SandboxCreateRTCIdentityProvider(cx, obj)) { return false; } #endif return true; } bool xpc::GlobalProperties::DefineInXPCComponents(JSContext* cx, JS::HandleObject obj) { if (indexedDB && !IndexedDatabaseManager::DefineIndexedDB(cx, obj)) return false; #ifdef MOZ_GLEAN if (glean && !mozilla::glean::Glean::DefineGlean(cx, obj)) return false; if (gleanPings && !mozilla::glean::GleanPings::DefineGleanPings(cx, obj)) { return false; } #endif return Define(cx, obj); } bool xpc::GlobalProperties::DefineInSandbox(JSContext* cx, JS::HandleObject obj) { MOZ_ASSERT(IsSandbox(obj)); MOZ_ASSERT(js::GetContextCompartment(cx) == JS::GetCompartment(obj)); if (indexedDB && !(IndexedDatabaseManager::ResolveSandboxBinding(cx) && IndexedDatabaseManager::DefineIndexedDB(cx, obj))) return false; return Define(cx, obj); } /** * If enabled, apply the extension base CSP, then apply the * content script CSP which will either be a default or one * provided by the extension in its manifest. */ nsresult ApplyAddonContentScriptCSP(nsISupports* prinOrSop) { nsCOMPtr principal = do_QueryInterface(prinOrSop); if (!principal) { return NS_OK; } auto* basePrin = BasePrincipal::Cast(principal); // We only get an addonPolicy if the principal is an // expanded principal with an extension principal in it. auto* addonPolicy = basePrin->ContentScriptAddonPolicy(); if (!addonPolicy) { return NS_OK; } // For backwards compatibility, content scripts have no CSP // in manifest v2. Only apply content script CSP to V3 or later. if (addonPolicy->ManifestVersion() < 3) { return NS_OK; } nsString url; MOZ_TRY_VAR(url, addonPolicy->GetURL(u""_ns)); nsCOMPtr selfURI; MOZ_TRY(NS_NewURI(getter_AddRefs(selfURI), url)); const nsAString& baseCSP = addonPolicy->BaseCSP(); // If we got here, we're definitly an expanded principal. auto expanded = basePrin->As(); nsCOMPtr csp; #ifdef MOZ_DEBUG // Bug 1548468: Move CSP off ExpandedPrincipal expanded->GetCsp(getter_AddRefs(csp)); if (csp) { uint32_t count = 0; csp->GetPolicyCount(&count); if (count > 0) { // Ensure that the policy was not already added. nsAutoString parsedPolicyStr; for (uint32_t i = 0; i < count; i++) { csp->GetPolicyString(i, parsedPolicyStr); MOZ_ASSERT(!parsedPolicyStr.Equals(baseCSP)); } } } #endif csp = new nsCSPContext(); MOZ_TRY(csp->SetRequestContextWithPrincipal(expanded, selfURI, u""_ns, 0)); MOZ_TRY(csp->AppendPolicy(baseCSP, false, false)); expanded->SetCsp(csp); return NS_OK; } nsresult xpc::CreateSandboxObject(JSContext* cx, MutableHandleValue vp, nsISupports* prinOrSop, SandboxOptions& options) { // Create the sandbox global object nsCOMPtr principal = do_QueryInterface(prinOrSop); nsCOMPtr obj = do_QueryInterface(prinOrSop); if (obj) { nsGlobalWindowInner* window = WindowOrNull(js::UncheckedUnwrap(obj->GetGlobalJSObject(), false)); // If we have a secure context window inherit from it's parent if (window && window->IsSecureContext()) { options.forceSecureContext = true; } } if (!principal) { nsCOMPtr sop = do_QueryInterface(prinOrSop); if (sop) { principal = sop->GetPrincipal(); } else { RefPtr nullPrin = NullPrincipal::CreateWithoutOriginAttributes(); principal = nullPrin; } } MOZ_ASSERT(principal); JS::RealmOptions realmOptions; auto& creationOptions = realmOptions.creationOptions(); bool isSystemPrincipal = principal->IsSystemPrincipal(); if (isSystemPrincipal) { options.forceSecureContext = true; } // If we are able to see [SecureContext] API code if (options.forceSecureContext) { creationOptions.setSecureContext(true); } xpc::SetPrefableRealmOptions(realmOptions); if (options.sameZoneAs) { creationOptions.setNewCompartmentInExistingZone( js::UncheckedUnwrap(options.sameZoneAs)); } else if (options.freshZone) { creationOptions.setNewCompartmentAndZone(); } else if (isSystemPrincipal && !options.invisibleToDebugger && !options.freshCompartment) { // Use a shared system compartment for system-principal sandboxes that don't // require invisibleToDebugger (this is a compartment property, see bug // 1482215). creationOptions.setExistingCompartment(xpc::PrivilegedJunkScope()); } else { creationOptions.setNewCompartmentInSystemZone(); } creationOptions.setInvisibleToDebugger(options.invisibleToDebugger) .setTrace(TraceXPCGlobal); realmOptions.behaviors().setDiscardSource(options.discardSource); if (isSystemPrincipal) { realmOptions.behaviors().setClampAndJitterTime(false); } const JSClass* clasp = &SandboxClass; RootedObject sandbox( cx, xpc::CreateGlobalObject(cx, clasp, principal, realmOptions)); if (!sandbox) { return NS_ERROR_FAILURE; } // Use exclusive expandos for non-system-principal sandboxes. bool hasExclusiveExpandos = !isSystemPrincipal; // 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. bool wantXrays = AccessCheck::isChrome(sandbox) ? false : options.wantXrays; if (creationOptions.compartmentSpecifier() == JS::CompartmentSpecifier::ExistingCompartment) { // Make sure the compartment we're reusing has flags that match what we // would set on a new compartment. CompartmentPrivate* priv = CompartmentPrivate::Get(sandbox); MOZ_RELEASE_ASSERT(priv->allowWaivers == options.allowWaivers); MOZ_RELEASE_ASSERT(priv->isWebExtensionContentScript == options.isWebExtensionContentScript); MOZ_RELEASE_ASSERT(priv->isUAWidgetCompartment == options.isUAWidgetScope); MOZ_RELEASE_ASSERT(priv->hasExclusiveExpandos == hasExclusiveExpandos); MOZ_RELEASE_ASSERT(priv->wantXrays == wantXrays); } else { CompartmentPrivate* priv = CompartmentPrivate::Get(sandbox); priv->allowWaivers = options.allowWaivers; priv->isWebExtensionContentScript = options.isWebExtensionContentScript; priv->isUAWidgetCompartment = options.isUAWidgetScope; priv->hasExclusiveExpandos = hasExclusiveExpandos; priv->wantXrays = wantXrays; } { JSAutoRealm ar(cx, sandbox); // This creates a SandboxPrivate and passes ownership of it to |sandbox|. SandboxPrivate::Create(principal, sandbox); // Ensure |Object.prototype| is instantiated before prototype- // splicing below. if (!JS::GetRealmObjectPrototype(cx)) { return NS_ERROR_XPC_UNEXPECTED; } if (options.proto) { bool ok = JS_WrapObject(cx, &options.proto); if (!ok) { return NS_ERROR_XPC_UNEXPECTED; } // Now check what sort of thing we've got in |proto|, and figure out // if we need a SandboxProxyHandler. // // Note that, in the case of a window, we can't require that the // Sandbox subsumes the prototype, because we have to hold our // reference to it via an outer window, and the window may navigate // at any time. So we have to handle that case separately. bool useSandboxProxy = !!WindowOrNull(js::UncheckedUnwrap(options.proto, false)); if (!useSandboxProxy) { // We just wrapped options.proto into the compartment of whatever Realm // is on the cx, so use that same realm for the CheckedUnwrapDynamic // call. JSObject* unwrappedProto = js::CheckedUnwrapDynamic(options.proto, cx, false); if (!unwrappedProto) { JS_ReportErrorASCII(cx, "Sandbox must subsume sandboxPrototype"); return NS_ERROR_INVALID_ARG; } const JSClass* unwrappedClass = JS::GetClass(unwrappedProto); useSandboxProxy = IS_WN_CLASS(unwrappedClass) || mozilla::dom::IsDOMClass(unwrappedClass); } if (useSandboxProxy) { // 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, &sandboxProxyHandler, priv, nullptr); if (!options.proto) { return NS_ERROR_OUT_OF_MEMORY; } } ok = JS_SplicePrototype(cx, sandbox, options.proto); if (!ok) { return NS_ERROR_XPC_UNEXPECTED; } } bool allowComponents = principal->IsSystemPrincipal(); if (options.wantComponents && allowComponents && !ObjectScope(sandbox)->AttachComponentsObject(cx)) 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.wantExportHelpers && (!JS_DefineFunction(cx, sandbox, "exportFunction", SandboxExportFunction, 3, 0) || !JS_DefineFunction(cx, sandbox, "createObjectIn", SandboxCreateObjectIn, 2, 0) || !JS_DefineFunction(cx, sandbox, "cloneInto", SandboxCloneInto, 3, 0) || !JS_DefineFunction(cx, sandbox, "isProxy", SandboxIsProxy, 1, 0))) return NS_ERROR_XPC_UNEXPECTED; if (!options.globalProperties.DefineInSandbox(cx, sandbox)) { return NS_ERROR_XPC_UNEXPECTED; } } // We handle the case where the context isn't in a compartment for the // benefit of UnprivilegedJunkScope(). vp.setObject(*sandbox); if (js::GetContextCompartment(cx) && !JS_WrapValue(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); xpc::SetSandboxMetadata(cx, sandbox, options.metadata); JSAutoRealm ar(cx, sandbox); JS_FireOnNewGlobalObject(cx, sandbox); return NS_OK; } 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); } 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 Content Principal for the sandbox. */ bool ParsePrincipal(JSContext* cx, HandleString contentUrl, const OriginAttributes& aAttrs, nsIPrincipal** principal) { MOZ_ASSERT(principal); MOZ_ASSERT(contentUrl); nsCOMPtr uri; nsAutoJSString contentStr; NS_ENSURE_TRUE(contentStr.init(cx, contentUrl), false); nsresult rv = NS_NewURI(getter_AddRefs(uri), contentStr); if (NS_FAILED(rv)) { JS_ReportErrorASCII(cx, "Creating URI from string failed"); return false; } // 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. nsCOMPtr prin = BasePrincipal::CreateContentPrincipal(uri, aAttrs); prin.forget(principal); if (!*principal) { JS_ReportErrorASCII(cx, "Creating Principal from URI failed"); return false; } return true; } /* * For sandbox constructor the first argument can be a principal object or * a script object principal (Document, Window). */ static bool GetPrincipalOrSOP(JSContext* cx, HandleObject from, nsISupports** out) { MOZ_ASSERT(out); *out = nullptr; // We might have a Window here, so need ReflectorToISupportsDynamic nsCOMPtr native = ReflectorToISupportsDynamic(from, cx); if (nsCOMPtr sop = do_QueryInterface(native)) { sop.forget(out); return true; } nsCOMPtr principal = do_QueryInterface(native); principal.forget(out); NS_ENSURE_TRUE(*out, false); return true; } /* * The first parameter of the sandbox constructor might be an array of * principals, either in string format or actual objects (see GetPrincipalOrSOP) */ static bool GetExpandedPrincipal(JSContext* cx, HandleObject arrayObj, const SandboxOptions& options, nsIExpandedPrincipal** out) { MOZ_ASSERT(out); uint32_t length; if (!JS::GetArrayLength(cx, arrayObj, &length)) { return false; } if (!length) { // We need a whitelist of principals or uri strings to create an // expanded principal, if we got an empty array or something else // report error. JS_ReportErrorASCII(cx, "Expected an array of URI strings"); return false; } nsTArray > allowedDomains(length); allowedDomains.SetLength(length); // If an originAttributes option has been specified, we will use that as the // OriginAttribute of all of the string arguments passed to this function. // Otherwise, we will use the OriginAttributes of a principal or SOP object // in the array, if any. If no such object is present, and all we have are // strings, then we will use a default OriginAttribute. // Otherwise, we will use the origin attributes of the passed object(s). If // more than one object is specified, we ensure that the OAs match. Maybe attrs; if (options.originAttributes) { attrs.emplace(); JS::RootedValue val(cx, JS::ObjectValue(*options.originAttributes)); if (!attrs->Init(cx, val)) { // The originAttributes option, if specified, must be valid! JS_ReportErrorASCII(cx, "Expected a valid OriginAttributes object"); return false; } } // Now we go over the array in two passes. In the first pass, we ignore // strings, and only process objects. Assuming that no originAttributes // option has been passed, if we encounter a principal or SOP object, we // grab its OA and save it if it's the first OA encountered, otherwise // check to make sure that it is the same as the OA found before. // In the second pass, we ignore objects, and use the OA found in pass 0 // (or the previously computed OA if we have obtained it from the options) // to construct content principals. // // The effective OA selected above will also be set as the OA of the // expanded principal object. // First pass: for (uint32_t i = 0; i < length; ++i) { RootedValue allowed(cx); if (!JS_GetElement(cx, arrayObj, i, &allowed)) { return false; } nsCOMPtr principal; 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()); if (!GetPrincipalOrSOP(cx, obj, getter_AddRefs(prinOrSop))) { return false; } nsCOMPtr sop(do_QueryInterface(prinOrSop)); principal = do_QueryInterface(prinOrSop); if (sop) { principal = sop->GetPrincipal(); } NS_ENSURE_TRUE(principal, false); if (!options.originAttributes) { const OriginAttributes prinAttrs = principal->OriginAttributesRef(); if (attrs.isNothing()) { attrs.emplace(prinAttrs); } else if (prinAttrs != attrs.ref()) { // If attrs is from a previously encountered principal in the // array, we need to ensure that it matches the OA of the // principal we have here. // If attrs comes from OriginAttributes, we don't need // this check. return false; } } // We do not allow ExpandedPrincipals to contain any system principals. bool isSystem = principal->IsSystemPrincipal(); if (isSystem) { JS_ReportErrorASCII( cx, "System principal is not allowed in an expanded principal"); return false; } allowedDomains[i] = principal; } else if (allowed.isString()) { // Skip any string arguments - we handle them in the next pass. } else { // Don't know what this is. return false; } } if (attrs.isNothing()) { // If no OriginAttributes was found in the first pass, fall back to a // default one. attrs.emplace(); } // Second pass: for (uint32_t i = 0; i < length; ++i) { RootedValue allowed(cx); if (!JS_GetElement(cx, arrayObj, i, &allowed)) { return false; } nsCOMPtr principal; if (allowed.isString()) { // In case of string let's try to fetch a content principal from it. RootedString str(cx, allowed.toString()); // attrs here is either a default OriginAttributes in case the // originAttributes option isn't specified, and no object in the array // provides a principal. Otherwise it's either the forced principal, or // the principal found before, so we can use it here. if (!ParsePrincipal(cx, str, attrs.ref(), getter_AddRefs(principal))) { return false; } NS_ENSURE_TRUE(principal, false); allowedDomains[i] = principal; } else { MOZ_ASSERT(allowed.isObject()); } } RefPtr result = ExpandedPrincipal::Create(allowedDomains, attrs.ref()); result.forget(out); return true; } /* * Helper that tries to get a property from the options object. */ bool OptionsBase::ParseValue(const char* name, MutableHandleValue prop, bool* aFound) { bool found; bool ok = JS_HasProperty(mCx, mObject, name, &found); NS_ENSURE_TRUE(ok, false); if (aFound) { *aFound = found; } if (!found) { return true; } return JS_GetProperty(mCx, mObject, name, prop); } /* * Helper that tries to get a boolean property from the options object. */ bool OptionsBase::ParseBoolean(const char* name, bool* prop) { MOZ_ASSERT(prop); RootedValue value(mCx); bool found; bool ok = ParseValue(name, &value, &found); NS_ENSURE_TRUE(ok, false); if (!found) { return true; } if (!value.isBoolean()) { JS_ReportErrorASCII(mCx, "Expected a boolean value for property %s", name); return false; } *prop = value.toBoolean(); return true; } /* * Helper that tries to get an object property from the options object. */ bool OptionsBase::ParseObject(const char* name, MutableHandleObject prop) { RootedValue value(mCx); bool found; bool ok = ParseValue(name, &value, &found); NS_ENSURE_TRUE(ok, false); if (!found) { return true; } if (!value.isObject()) { JS_ReportErrorASCII(mCx, "Expected an object value for property %s", name); return false; } prop.set(&value.toObject()); return true; } /* * Helper that tries to get an object property from the options object. */ bool OptionsBase::ParseJSString(const char* name, MutableHandleString prop) { RootedValue value(mCx); bool found; bool ok = ParseValue(name, &value, &found); NS_ENSURE_TRUE(ok, false); if (!found) { return true; } if (!value.isString()) { JS_ReportErrorASCII(mCx, "Expected a string value for property %s", name); return false; } prop.set(value.toString()); return true; } /* * Helper that tries to get a string property from the options object. */ bool OptionsBase::ParseString(const char* name, nsCString& prop) { RootedValue value(mCx); bool found; bool ok = ParseValue(name, &value, &found); NS_ENSURE_TRUE(ok, false); if (!found) { return true; } if (!value.isString()) { JS_ReportErrorASCII(mCx, "Expected a string value for property %s", name); return false; } JS::UniqueChars tmp = JS_EncodeStringToLatin1(mCx, value.toString()); NS_ENSURE_TRUE(tmp, false); prop.Assign(tmp.get(), strlen(tmp.get())); return true; } /* * Helper that tries to get a string property from the options object. */ bool OptionsBase::ParseString(const char* name, nsString& prop) { RootedValue value(mCx); bool found; bool ok = ParseValue(name, &value, &found); NS_ENSURE_TRUE(ok, false); if (!found) { return true; } if (!value.isString()) { JS_ReportErrorASCII(mCx, "Expected a string value for property %s", name); return false; } nsAutoJSString strVal; if (!strVal.init(mCx, value.toString())) { return false; } prop = strVal; return true; } /* * Helper that tries to get jsid property from the options object. */ bool OptionsBase::ParseId(const char* name, MutableHandleId prop) { RootedValue value(mCx); bool found; bool ok = ParseValue(name, &value, &found); NS_ENSURE_TRUE(ok, false); if (!found) { return true; } return JS_ValueToId(mCx, value, prop); } /* * Helper that tries to get a uint32_t property from the options object. */ bool OptionsBase::ParseUInt32(const char* name, uint32_t* prop) { MOZ_ASSERT(prop); RootedValue value(mCx); bool found; bool ok = ParseValue(name, &value, &found); NS_ENSURE_TRUE(ok, false); if (!found) { return true; } if (!JS::ToUint32(mCx, value, prop)) { JS_ReportErrorASCII(mCx, "Expected a uint32_t value for property %s", name); return false; } return true; } /* * Helper that tries to get a list of DOM constructors and other helpers from * the options object. */ bool SandboxOptions::ParseGlobalProperties() { RootedValue value(mCx); bool found; bool ok = ParseValue("wantGlobalProperties", &value, &found); NS_ENSURE_TRUE(ok, false); if (!found) { return true; } if (!value.isObject()) { JS_ReportErrorASCII(mCx, "Expected an array value for wantGlobalProperties"); return false; } RootedObject ctors(mCx, &value.toObject()); bool isArray; if (!JS::IsArrayObject(mCx, ctors, &isArray)) { return false; } if (!isArray) { JS_ReportErrorASCII(mCx, "Expected an array value for wantGlobalProperties"); return false; } return globalProperties.Parse(mCx, ctors); } /* * Helper that parsing the sandbox options object (from) and sets the fields of * the incoming options struct (options). */ bool SandboxOptions::Parse() { /* All option names must be ASCII-only. */ bool ok = ParseObject("sandboxPrototype", &proto) && ParseBoolean("wantXrays", &wantXrays) && ParseBoolean("allowWaivers", &allowWaivers) && ParseBoolean("wantComponents", &wantComponents) && ParseBoolean("wantExportHelpers", &wantExportHelpers) && ParseBoolean("isWebExtensionContentScript", &isWebExtensionContentScript) && ParseBoolean("forceSecureContext", &forceSecureContext) && ParseString("sandboxName", sandboxName) && ParseObject("sameZoneAs", &sameZoneAs) && ParseBoolean("freshCompartment", &freshCompartment) && ParseBoolean("freshZone", &freshZone) && ParseBoolean("invisibleToDebugger", &invisibleToDebugger) && ParseBoolean("discardSource", &discardSource) && ParseGlobalProperties() && ParseValue("metadata", &metadata) && ParseUInt32("userContextId", &userContextId) && ParseObject("originAttributes", &originAttributes); if (!ok) { return false; } if (freshZone && sameZoneAs) { JS_ReportErrorASCII(mCx, "Cannot use both sameZoneAs and freshZone"); return false; } return true; } static nsresult AssembleSandboxMemoryReporterName(JSContext* cx, nsCString& sandboxName) { // Use a default name when the caller did not provide a sandboxName. if (sandboxName.IsEmpty()) { sandboxName = "[anonymous sandbox]"_ns; } else { #ifndef DEBUG // Adding the caller location is fairly expensive, so in non-debug // builds, only add it if we don't have an explicit sandbox name. return NS_OK; #endif } // Get the xpconnect native call context. XPCCallContext* cc = XPCJSContext::Get()->GetCallContext(); NS_ENSURE_TRUE(cc, NS_ERROR_INVALID_ARG); // Get the current source info from xpc. nsCOMPtr frame = dom::GetCurrentJSStack(); // Append the caller's location information. if (frame) { nsString location; frame->GetFilename(cx, location); int32_t lineNumber = frame->GetLineNumber(cx); sandboxName.AppendLiteral(" (from: "); sandboxName.Append(NS_ConvertUTF16toUTF8(location)); sandboxName.Append(':'); sandboxName.AppendInt(lineNumber); sandboxName.Append(')'); } 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; bool ok = false; bool calledWithOptions = args.length() > 1; if (calledWithOptions && !args[1].isObject()) { return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval); } RootedObject optionsObject(cx, calledWithOptions ? &args[1].toObject() : nullptr); SandboxOptions options(cx, optionsObject); if (calledWithOptions && !options.Parse()) { return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval); } // 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()); OriginAttributes attrs; if (options.originAttributes) { JS::RootedValue val(cx, JS::ObjectValue(*options.originAttributes)); if (!attrs.Init(cx, val)) { // The originAttributes option, if specified, must be valid! JS_ReportErrorASCII(cx, "Expected a valid OriginAttributes object"); return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval); } } attrs.mUserContextId = options.userContextId; ok = ParsePrincipal(cx, str, attrs, getter_AddRefs(principal)); prinOrSop = principal; } else if (args[0].isObject()) { RootedObject obj(cx, &args[0].toObject()); bool isArray; if (!JS::IsArrayObject(cx, obj, &isArray)) { ok = false; } else if (isArray) { if (options.userContextId != 0) { // We don't support passing a userContextId with an array. ok = false; } else { ok = GetExpandedPrincipal(cx, obj, options, getter_AddRefs(expanded)); prinOrSop = expanded; // If this is an addon content script we need to apply the csp. MOZ_TRY(ApplyAddonContentScriptCSP(prinOrSop)); } } else { ok = GetPrincipalOrSOP(cx, obj, getter_AddRefs(prinOrSop)); } } else if (args[0].isNull()) { // Null means that we just pass prinOrSop = nullptr, and get an // NullPrincipal. ok = true; } if (!ok) { return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval); } if (NS_FAILED(AssembleSandboxMemoryReporterName(cx, options.sandboxName))) { return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval); } if (options.metadata.isNullOrUndefined()) { // If the caller is running in a sandbox, inherit. RootedObject callerGlobal(cx, JS::GetScriptedCallerGlobal(cx)); if (IsSandbox(callerGlobal)) { rv = GetSandboxMetadata(cx, callerGlobal, &options.metadata); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } } rv = CreateSandboxObject(cx, args.rval(), prinOrSop, options); if (NS_FAILED(rv)) { return ThrowAndFail(rv, cx, _retval); } *_retval = true; return NS_OK; } nsresult xpc::EvalInSandbox(JSContext* cx, HandleObject sandboxArg, const nsAString& source, const nsACString& filename, int32_t lineNo, bool enforceFilenameRestrictions, MutableHandleValue rval) { JS_AbortIfWrongThread(cx); rval.set(UndefinedValue()); bool waiveXray = xpc::WrapperFactory::HasWaiveXrayFlag(sandboxArg); // CheckedUnwrapStatic is fine here, since we're checking for "is it a // sandbox". RootedObject sandbox(cx, js::CheckedUnwrapStatic(sandboxArg)); if (!sandbox || !IsSandbox(sandbox)) { return NS_ERROR_INVALID_ARG; } nsIScriptObjectPrincipal* sop = static_cast(xpc_GetJSPrivate(sandbox)); MOZ_ASSERT(sop, "Invalid sandbox passed"); SandboxPrivate* priv = static_cast(sop); nsCOMPtr prin = sop->GetPrincipal(); NS_ENSURE_TRUE(prin, NS_ERROR_FAILURE); nsAutoCString filenameBuf; if (!filename.IsVoid() && filename.Length() != 0) { filenameBuf.Assign(filename); } else { // Default to the spec of the principal. nsresult rv = nsJSPrincipals::get(prin)->GetScriptLocation(filenameBuf); NS_ENSURE_SUCCESS(rv, rv); 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; { // We're about to evaluate script, so make an AutoEntryScript. // This is clearly Gecko-specific and not in any spec. mozilla::dom::AutoEntryScript aes(priv, "XPConnect sandbox evaluation"); JSContext* sandcx = aes.cx(); JSAutoRealm ar(sandcx, sandbox); JS::CompileOptions options(sandcx); options.setFileAndLine(filenameBuf.get(), lineNo); options.setSkipFilenameValidation(!enforceFilenameRestrictions); MOZ_ASSERT(JS_IsGlobalObject(sandbox)); const nsPromiseFlatString& flat = PromiseFlatString(source); JS::SourceText buffer; ok = buffer.init(sandcx, flat.get(), flat.Length(), JS::SourceOwnership::Borrowed) && JS::Evaluate(sandcx, options, buffer, &v); // If the sandbox threw an exception, grab it off the context. if (aes.HasException()) { if (!aes.StealException(&exn)) { return NS_ERROR_OUT_OF_MEMORY; } } } // // 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)) { 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); } else { ok = JS_WrapValue(cx, &v); } NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE); // Whew! rval.set(v); return NS_OK; } nsresult xpc::GetSandboxMetadata(JSContext* cx, HandleObject sandbox, MutableHandleValue rval) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(IsSandbox(sandbox)); RootedValue metadata(cx); { JSAutoRealm ar(cx, sandbox); metadata = JS::GetReservedSlot(sandbox, XPCONNECT_SANDBOX_CLASS_METADATA_SLOT); } if (!JS_WrapValue(cx, &metadata)) { return NS_ERROR_UNEXPECTED; } rval.set(metadata); return NS_OK; } nsresult xpc::SetSandboxMetadata(JSContext* cx, HandleObject sandbox, HandleValue metadataArg) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(IsSandbox(sandbox)); RootedValue metadata(cx); JSAutoRealm ar(cx, sandbox); if (!JS_StructuredClone(cx, metadataArg, &metadata, nullptr, nullptr)) { return NS_ERROR_UNEXPECTED; } JS_SetReservedSlot(sandbox, XPCONNECT_SANDBOX_CLASS_METADATA_SLOT, metadata); return NS_OK; }