summaryrefslogtreecommitdiffstats
path: root/js/xpconnect/src/Sandbox.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/xpconnect/src/Sandbox.cpp')
-rw-r--r--js/xpconnect/src/Sandbox.cpp2258
1 files changed, 2258 insertions, 0 deletions
diff --git a/js/xpconnect/src/Sandbox.cpp b/js/xpconnect/src/Sandbox.cpp
new file mode 100644
index 0000000000..77dbf2d02e
--- /dev/null
+++ b/js/xpconnect/src/Sandbox.cpp
@@ -0,0 +1,2258 @@
+/* -*- 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 "nsGlobalWindowInner.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));
+ JS::Rooted<JSFunction*> fun(cx, JS_ValueToFunction(cx, funval));
+ if (!fun) {
+ XPCThrower::Throw(NS_ERROR_INVALID_ARG, cx);
+ return false;
+ }
+
+ // Use the actual function name as the name.
+ if (!JS_GetFunctionId(cx, fun, &funname)) {
+ return false;
+ }
+ 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);
+}