diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /js/xpconnect/src/Sandbox.cpp | |
parent | Initial commit. (diff) | |
download | firefox-esr-upstream.tar.xz firefox-esr-upstream.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | js/xpconnect/src/Sandbox.cpp | 2256 |
1 files changed, 2256 insertions, 0 deletions
diff --git a/js/xpconnect/src/Sandbox.cpp b/js/xpconnect/src/Sandbox.cpp new file mode 100644 index 0000000000..b75bebc6f1 --- /dev/null +++ b/js/xpconnect/src/Sandbox.cpp @@ -0,0 +1,2256 @@ +/* -*- 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/CallAndConstruct.h" // JS::Call, JS::IsCallable +#include "js/CharacterEncoding.h" +#include "js/CompilationAndEvaluation.h" +#include "js/Object.h" // JS::GetClass, JS::GetCompartment, JS::GetReservedSlot +#include "js/PropertyAndElement.h" // JS_DefineFunction, JS_DefineFunctions, JS_DefineProperty, JS_GetElement, JS_GetProperty, JS_HasProperty, JS_SetProperty, JS_SetPropertyById +#include "js/PropertyDescriptor.h" // JS::PropertyDescriptor, JS_GetOwnPropertyDescriptorById, JS_GetPropertyDescriptorById +#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/Result.h" +#include "mozilla/dom/AbortControllerBinding.h" +#include "mozilla/dom/AutoEntryScript.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/IOUtilsBinding.h" +#include "mozilla/dom/InspectorUtilsBinding.h" +#include "mozilla/dom/MessageChannelBinding.h" +#include "mozilla/dom/MessagePortBinding.h" +#include "mozilla/dom/MIDIInputMapBinding.h" +#include "mozilla/dom/MIDIOutputMapBinding.h" +#include "mozilla/dom/ModuleLoader.h" +#include "mozilla/dom/NodeBinding.h" +#include "mozilla/dom/NodeFilterBinding.h" +#include "mozilla/dom/PathUtilsBinding.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/ReadableStreamBinding.h" +#include "mozilla/dom/ResponseBinding.h" +#ifdef MOZ_WEBRTC +# include "mozilla/dom/RTCIdentityProviderRegistrar.h" +#endif +#include "mozilla/dom/FileReaderBinding.h" +#include "mozilla/dom/ScriptLoader.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/SelectionBinding.h" +#include "mozilla/dom/StorageManager.h" +#include "mozilla/dom/TextDecoderBinding.h" +#include "mozilla/dom/TextEncoderBinding.h" +#include "mozilla/dom/URLBinding.h" +#include "mozilla/dom/URLSearchParamsBinding.h" +#include "mozilla/dom/XMLHttpRequest.h" +#include "mozilla/dom/WebSocketBinding.h" +#include "mozilla/dom/WindowBinding.h" +#include "mozilla/dom/XMLSerializerBinding.h" +#include "mozilla/dom/FormDataBinding.h" +#include "mozilla/dom/nsCSPContext.h" +#include "mozilla/ipc/BackgroundUtils.h" +#include "mozilla/ipc/PBackgroundSharedTypes.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/DeferredFinalize.h" +#include "mozilla/ExtensionPolicyService.h" +#include "mozilla/Maybe.h" +#include "mozilla/NullPrincipal.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/StaticPrefs_extensions.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace JS; +using namespace JS::loader; +using namespace xpc; + +using mozilla::dom::DestroyProtoAndIfaceCache; +using mozilla::dom::IndexedDatabaseManager; + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_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 + NS_IMPL_CYCLE_COLLECTION_UNLINK(mModuleLoader) + tmp->UnlinkObjectsInGlobal(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(SandboxPrivate) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mModuleLoader) + tmp->TraverseObjectsInGlobal(cb); +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +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<nsIXPCComponents_utils_Sandbox> xpc::NewSandboxConstructor() { + nsCOMPtr<nsIXPCComponents_utils_Sandbox> 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 + MOZ_LOG(nsContentUtils::DOMDumpLog(), mozilla::LogLevel::Debug, + ("[Sandbox.Dump] %s", cstr)); +#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; +} + +bool xpc::SandboxCreateCrypto(JSContext* cx, JS::Handle<JSObject*> 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<nsIGlobalObject> 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 SandboxFetch(JSContext* cx, JS::HandleObject scope, + const CallArgs& args) { + if (args.length() < 1) { + JS_ReportErrorASCII(cx, "fetch requires at least 1 argument"); + return false; + } + + BindingCallContext callCx(cx, "fetch"); + RequestOrUSVString request; + if (!request.Init(callCx, args[0], "Argument 1")) { + return false; + } + RootedDictionary<dom::RequestInit> options(cx); + if (!options.Init(callCx, args.hasDefined(1) ? args[1] : JS::NullHandleValue, + "Argument 2", false)) { + return false; + } + nsCOMPtr<nsIGlobalObject> global = xpc::NativeGlobal(scope); + if (!global) { + return false; + } + dom::CallerType callerType = nsContentUtils::IsSystemCaller(cx) + ? dom::CallerType::System + : dom::CallerType::NonSystem; + ErrorResult rv; + RefPtr<dom::Promise> 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()); +} + +bool xpc::SandboxCreateFetch(JSContext* cx, JS::Handle<JSObject*> 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 SandboxCreateStorage(JSContext* cx, JS::HandleObject obj) { + MOZ_ASSERT(JS_IsGlobalObject(obj)); + + nsIGlobalObject* native = xpc::NativeGlobal(obj); + MOZ_ASSERT(native); + + dom::StorageManager* storageManager = new dom::StorageManager(native); + JS::RootedObject wrapped(cx, storageManager->WrapObject(cx, nullptr)); + return JS_DefineProperty(cx, obj, "storage", wrapped, JSPROP_ENUMERATE); +} + +static bool SandboxStructuredClone(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "structuredClone", 1)) { + return false; + } + + RootedDictionary<dom::StructuredSerializeOptions> options(cx); + BindingCallContext callCx(cx, "structuredClone"); + if (!options.Init(cx, args.hasDefined(1) ? args[1] : JS::NullHandleValue, + "Argument 2", false)) { + return false; + } + + nsIGlobalObject* global = CurrentNativeGlobal(cx); + if (!global) { + JS_ReportErrorASCII(cx, "structuredClone: Missing global"); + return false; + } + + JS::Rooted<JS::Value> result(cx); + ErrorResult rv; + nsContentUtils::StructuredClone(cx, global, args[0], options, &result, rv); + if (rv.MaybeSetPendingException(cx)) { + return false; + } + + MOZ_ASSERT_IF(result.isGCThing(), + !JS::GCThingIsMarkedGray(result.toGCCellPtr())); + args.rval().set(result); + return true; +} + +bool xpc::SandboxCreateStructuredClone(JSContext* cx, HandleObject obj) { + MOZ_ASSERT(JS_IsGlobalObject(obj)); + + return JS_DefineFunction(cx, obj, "structuredClone", SandboxStructuredClone, + 1, 0); +} + +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(JS::GCContext* gcx, JSObject* obj) { + SandboxPrivate* priv = SandboxPrivate::GetPrivate(obj); + if (!priv) { + // priv can be null if CreateSandboxObject fails in the middle. + return; + } + + priv->ForgetGlobalObject(obj); + DestroyProtoAndIfaceCache(obj); + DeferredFinalize(static_cast<nsIScriptObjectPrincipal*>(priv)); +} + +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. + SandboxPrivate* priv = SandboxPrivate::GetPrivate(obj); + if (!priv) { + return 0; + } + + return priv->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, // 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<JSObject*> proxy, JS::Handle<jsid> id, + JS::MutableHandle<Maybe<JS::PropertyDescriptor>> 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<JSObject*> proxy, + JS::Handle<jsid> id, bool* bp) const override; + virtual bool get(JSContext* cx, JS::Handle<JSObject*> proxy, + JS::HandleValue receiver, JS::Handle<jsid> id, + JS::MutableHandle<JS::Value> vp) const override; + virtual bool set(JSContext* cx, JS::Handle<JSObject*> proxy, + JS::Handle<jsid> id, JS::Handle<JS::Value> v, + JS::Handle<JS::Value> receiver, + JS::ObjectOpResult& result) const override; + + virtual bool hasOwn(JSContext* cx, JS::Handle<JSObject*> proxy, + JS::Handle<jsid> id, bool* bp) const override; + virtual bool getOwnEnumerablePropertyKeys( + JSContext* cx, JS::Handle<JSObject*> proxy, + JS::MutableHandleIdVector props) const override; + virtual bool enumerate(JSContext* cx, JS::Handle<JSObject*> 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<JSObject*> proxy, JS::Handle<jsid> id, + bool getOwn, JS::MutableHandle<Maybe<JS::PropertyDescriptor>> desc) const; +}; + +static const SandboxProxyHandler sandboxProxyHandler; + +namespace xpc { + +bool IsSandboxPrototypeProxy(JSObject* obj) { + return js::IsProxy(obj) && js::GetProxyHandler(obj) == &sandboxProxyHandler; +} + +bool IsWebExtensionContentScriptSandbox(JSObject* obj) { + return IsSandbox(obj) && + CompartmentPrivate::Get(obj)->isWebExtensionContentScript; +} + +} // 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<JSObject*> proxy, + const JS::CallArgs& args) const override; + + static const size_t SandboxProxySlot = 0; + + static inline JSObject* getSandboxProxy(JS::Handle<JSObject*> proxy) { + return &js::GetProxyReservedSlot(proxy, SandboxProxySlot).toObject(); + } +}; + +static const SandboxCallableProxyHandler sandboxCallableProxyHandler; + +bool SandboxCallableProxyHandler::call(JSContext* cx, + JS::Handle<JSObject*> 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; +} + +bool WrapAccessorFunction(JSContext* cx, MutableHandleObject accessor, + HandleObject sandboxProtoProxy) { + if (!accessor) { + return true; + } + + accessor.set(WrapCallable(cx, accessor, sandboxProtoProxy)); + return !!accessor; +} + +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<JSObject*> proxy, JS::Handle<jsid> id, + bool getOwn, MutableHandle<Maybe<PropertyDescriptor>> 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 { + Rooted<JSObject*> holder(cx); + if (!JS_GetPropertyDescriptorById(cx, obj, id, desc_, &holder)) { + return false; + } + } + + if (desc_.isNothing()) { + return true; + } + + Rooted<PropertyDescriptor> desc(cx, *desc_); + + // Now fix up the getter/setter/value as needed. + if (desc.hasGetter() && !WrapAccessorFunction(cx, desc.getter(), proxy)) { + return false; + } + if (desc.hasSetter() && !WrapAccessorFunction(cx, desc.setter(), proxy)) { + return false; + } + if (desc.hasValue() && 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); + } + } + + desc_.set(Some(desc.get())); + return true; +} + +bool SandboxProxyHandler::getOwnPropertyDescriptor( + JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id, + MutableHandle<Maybe<PropertyDescriptor>> 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<JSObject*> proxy, + JS::Handle<jsid> id, bool* bp) const { + // This uses JS_GetPropertyDescriptorById for backward compatibility. + Rooted<Maybe<PropertyDescriptor>> desc(cx); + if (!getPropertyDescriptorImpl(cx, proxy, id, /* getOwn = */ false, &desc)) { + return false; + } + + *bp = desc.isSome(); + return true; +} +bool SandboxProxyHandler::hasOwn(JSContext* cx, JS::Handle<JSObject*> proxy, + JS::Handle<jsid> id, bool* bp) const { + return BaseProxyHandler::hasOwn(cx, proxy, id, bp); +} + +bool SandboxProxyHandler::get(JSContext* cx, JS::Handle<JSObject*> proxy, + JS::Handle<JS::Value> receiver, + JS::Handle<jsid> id, + JS::MutableHandle<Value> vp) const { + // This uses JS_GetPropertyDescriptorById for backward compatibility. + Rooted<Maybe<PropertyDescriptor>> desc(cx); + if (!getPropertyDescriptorImpl(cx, proxy, id, /* getOwn = */ false, &desc)) { + return false; + } + + if (desc.isNothing()) { + vp.setUndefined(); + return true; + } else { + desc->assertComplete(); + } + + // 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->getter()); + + if (!getter) { + vp.setUndefined(); + return true; + } + + return Call(cx, receiver, getter, HandleValueArray::empty(), vp); +} + +bool SandboxProxyHandler::set(JSContext* cx, JS::Handle<JSObject*> proxy, + JS::Handle<jsid> id, JS::Handle<Value> v, + JS::Handle<Value> receiver, + JS::ObjectOpResult& result) const { + return BaseProxyHandler::set(cx, proxy, id, v, receiver, result); +} + +bool SandboxProxyHandler::getOwnEnumerablePropertyKeys( + JSContext* cx, JS::Handle<JSObject*> proxy, + MutableHandleIdVector props) const { + return BaseProxyHandler::getOwnEnumerablePropertyKeys(cx, proxy, props); +} + +bool SandboxProxyHandler::enumerate(JSContext* cx, JS::Handle<JSObject*> 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, "AbortController")) { + AbortController = true; + } else 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, "IOUtils")) { + IOUtils = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "InspectorUtils")) { + InspectorUtils = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "MessageChannel")) { + MessageChannel = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "MIDIInputMap")) { + MIDIInputMap = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "MIDIOutputMap")) { + MIDIOutputMap = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "Node")) { + Node = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "NodeFilter")) { + NodeFilter = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "PathUtils")) { + PathUtils = 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, "WebSocket")) { + WebSocket = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "Window")) { + Window = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "XMLSerializer")) { + XMLSerializer = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "ReadableStream")) { + ReadableStream = 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, "storage")) { + storage = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "structuredClone")) { + structuredClone = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "indexedDB")) { + indexedDB = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "isSecureContext")) { + isSecureContext = true; +#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 (AbortController && + !dom::AbortController_Binding::GetConstructorObject(cx)) { + return false; + } + + 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 (IOUtils && !dom::IOUtils_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 (MIDIInputMap && !dom::MIDIInputMap_Binding::GetConstructorObject(cx)) { + return false; + } + + if (MIDIOutputMap && !dom::MIDIOutputMap_Binding::GetConstructorObject(cx)) { + return false; + } + + if (Node && !dom::Node_Binding::GetConstructorObject(cx)) { + return false; + } + + if (NodeFilter && !dom::NodeFilter_Binding::GetConstructorObject(cx)) { + return false; + } + + if (PathUtils && !dom::PathUtils_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 (WebSocket && !dom::WebSocket_Binding::GetConstructorObject(cx)) + return false; + + if (Window && !dom::Window_Binding::GetConstructorObject(cx)) return false; + + if (XMLSerializer && !dom::XMLSerializer_Binding::GetConstructorObject(cx)) + return false; + + if (ReadableStream && !dom::ReadableStream_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; + } + + if (storage && !SandboxCreateStorage(cx, obj)) { + return false; + } + + if (structuredClone && !SandboxCreateStructuredClone(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<JS::Value> 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; + + 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<nsIPrincipal> 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<nsIURI> 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<ExpandedPrincipal>(); + nsCOMPtr<nsIContentSecurityPolicy> 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 + + // Create a clone of the expanded principal to be used for the call to + // SetRequestContextWithPrincipal (to prevent the CSP and expanded + // principal instances to keep each other alive indefinitely, see + // Bug 1741600). + // + // This may not be necessary anymore once Bug 1548468 will move CSP + // off ExpandedPrincipal. + RefPtr<ExpandedPrincipal> clonedPrincipal = ExpandedPrincipal::Create( + expanded->AllowList(), expanded->OriginAttributesRef()); + MOZ_ASSERT(clonedPrincipal); + + csp = new nsCSPContext(); + MOZ_TRY( + csp->SetRequestContextWithPrincipal(clonedPrincipal, 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<nsIPrincipal> principal = do_QueryInterface(prinOrSop); + nsCOMPtr<nsIGlobalObject> 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<nsIScriptObjectPrincipal> sop = do_QueryInterface(prinOrSop); + if (sop) { + principal = sop->GetPrincipal(); + } else { + RefPtr<NullPrincipal> 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 = unwrappedClass->isWrappedNative() || + 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_SetPrototype(cx, sandbox, options.proto); + if (!ok) { + return NS_ERROR_XPC_UNEXPECTED; + } + } + + bool allowComponents = principal->IsSystemPrincipal(); + if (options.wantComponents && allowComponents) { + if (!ObjectScope(sandbox)->AttachComponentsObject(cx)) { + return NS_ERROR_XPC_UNEXPECTED; + } + + if (!ObjectScope(sandbox)->AttachJSServices(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<nsIURI> 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<nsIPrincipal> 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<nsISupports> native = ReflectorToISupportsDynamic(from, cx); + + if (nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(native)) { + sop.forget(out); + return true; + } + + nsCOMPtr<nsIPrincipal> 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<nsCOMPtr<nsIPrincipal>> 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<OriginAttributes> 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<nsIPrincipal> principal; + if (allowed.isObject()) { + // In case of object let's see if it's a Principal or a + // ScriptObjectPrincipal. + nsCOMPtr<nsISupports> prinOrSop; + RootedObject obj(cx, &allowed.toObject()); + if (!GetPrincipalOrSOP(cx, obj, getter_AddRefs(prinOrSop))) { + return false; + } + + nsCOMPtr<nsIScriptObjectPrincipal> 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<nsIPrincipal> 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<ExpandedPrincipal> 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<nsIStackFrame> 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<nsIPrincipal> principal; + nsCOMPtr<nsIExpandedPrincipal> expanded; + nsCOMPtr<nsISupports> 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; + } + + SandboxPrivate* priv = SandboxPrivate::GetPrivate(sandbox); + nsIScriptObjectPrincipal* sop = priv; + MOZ_ASSERT(sop, "Invalid sandbox passed"); + nsCOMPtr<nsIPrincipal> 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<char16_t> 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; +} + +ModuleLoaderBase* SandboxPrivate::GetModuleLoader(JSContext* aCx) { + if (mModuleLoader) { + return mModuleLoader; + } + + JSObject* object = GetGlobalJSObject(); + nsGlobalWindowInner* sandboxWindow = xpc::SandboxWindowOrNull(object, aCx); + if (!sandboxWindow) { + return nullptr; + } + + ModuleLoader* mainModuleLoader = + static_cast<ModuleLoader*>(sandboxWindow->GetModuleLoader(aCx)); + + ScriptLoader* scriptLoader = mainModuleLoader->GetScriptLoader(); + + ModuleLoader* moduleLoader = + new ModuleLoader(scriptLoader, this, ModuleLoader::WebExtension); + scriptLoader->RegisterContentScriptModuleLoader(moduleLoader); + mModuleLoader = moduleLoader; + + return moduleLoader; +} + +mozilla::Result<mozilla::ipc::PrincipalInfo, nsresult> +SandboxPrivate::GetStorageKey() { + MOZ_ASSERT(NS_IsMainThread()); + + mozilla::ipc::PrincipalInfo principalInfo; + nsresult rv = PrincipalToPrincipalInfo(mPrincipal, &principalInfo); + if (NS_WARN_IF(NS_FAILED(rv))) { + return mozilla::Err(rv); + } + + // Block expanded and null principals, let content and system through. + if (principalInfo.type() != + mozilla::ipc::PrincipalInfo::TContentPrincipalInfo && + principalInfo.type() != + mozilla::ipc::PrincipalInfo::TSystemPrincipalInfo) { + return Err(NS_ERROR_DOM_SECURITY_ERR); + } + + return std::move(principalInfo); +} |