1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 /*
8  * The Components.Sandbox object.
9  */
10 
11 #include "AccessCheck.h"
12 #include "jsfriendapi.h"
13 #include "js/Array.h"             // JS::GetArrayLength, JS::IsArrayObject
14 #include "js/CallAndConstruct.h"  // JS::Call, JS::IsCallable
15 #include "js/CharacterEncoding.h"
16 #include "js/CompilationAndEvaluation.h"
17 #include "js/Object.h"  // JS::GetClass, JS::GetCompartment, JS::GetReservedSlot
18 #include "js/PropertyAndElement.h"  // JS_DefineFunction, JS_DefineFunctions, JS_DefineProperty, JS_GetElement, JS_GetProperty, JS_HasProperty, JS_SetProperty, JS_SetPropertyById
19 #include "js/PropertyDescriptor.h"  // JS::PropertyDescriptor, JS_GetOwnPropertyDescriptorById, JS_GetPropertyDescriptorById
20 #include "js/PropertySpec.h"
21 #include "js/Proxy.h"
22 #include "js/SourceText.h"
23 #include "js/StructuredClone.h"
24 #include "nsContentUtils.h"
25 #include "nsGlobalWindow.h"
26 #include "nsIException.h"  // for nsIStackFrame
27 #include "nsIScriptContext.h"
28 #include "nsIScriptObjectPrincipal.h"
29 #include "nsIURI.h"
30 #include "nsJSUtils.h"
31 #include "nsNetUtil.h"
32 #include "ExpandedPrincipal.h"
33 #include "WrapperFactory.h"
34 #include "xpcprivate.h"
35 #include "xpc_make_class.h"
36 #include "XPCWrapper.h"
37 #include "Crypto.h"
38 #include "mozilla/dom/AbortControllerBinding.h"
39 #include "mozilla/dom/AutoEntryScript.h"
40 #include "mozilla/dom/BindingCallContext.h"
41 #include "mozilla/dom/BindingUtils.h"
42 #include "mozilla/dom/BlobBinding.h"
43 #include "mozilla/dom/cache/CacheStorage.h"
44 #include "mozilla/dom/CSSBinding.h"
45 #include "mozilla/dom/CSSRuleBinding.h"
46 #include "mozilla/dom/DirectoryBinding.h"
47 #include "mozilla/dom/DocumentBinding.h"
48 #include "mozilla/dom/DOMExceptionBinding.h"
49 #include "mozilla/dom/DOMParserBinding.h"
50 #include "mozilla/dom/DOMTokenListBinding.h"
51 #include "mozilla/dom/ElementBinding.h"
52 #include "mozilla/dom/EventBinding.h"
53 #include "mozilla/dom/Exceptions.h"
54 #include "mozilla/dom/IndexedDatabaseManager.h"
55 #include "mozilla/dom/Fetch.h"
56 #include "mozilla/dom/FileBinding.h"
57 #include "mozilla/dom/HeadersBinding.h"
58 #include "mozilla/dom/IOUtilsBinding.h"
59 #include "mozilla/dom/InspectorUtilsBinding.h"
60 #include "mozilla/dom/MessageChannelBinding.h"
61 #include "mozilla/dom/MessagePortBinding.h"
62 #include "mozilla/dom/NodeBinding.h"
63 #include "mozilla/dom/NodeFilterBinding.h"
64 #include "mozilla/dom/PathUtilsBinding.h"
65 #include "mozilla/dom/PerformanceBinding.h"
66 #include "mozilla/dom/PromiseBinding.h"
67 #include "mozilla/dom/PromiseDebuggingBinding.h"
68 #include "mozilla/dom/RangeBinding.h"
69 #include "mozilla/dom/RequestBinding.h"
70 #ifdef MOZ_DOM_STREAMS
71 #  include "mozilla/dom/ReadableStreamBinding.h"
72 #endif
73 #include "mozilla/dom/ResponseBinding.h"
74 #ifdef MOZ_WEBRTC
75 #  include "mozilla/dom/RTCIdentityProviderRegistrar.h"
76 #endif
77 #include "mozilla/dom/FileReaderBinding.h"
78 #include "mozilla/dom/ScriptSettings.h"
79 #include "mozilla/dom/SelectionBinding.h"
80 #include "mozilla/dom/StorageManager.h"
81 #include "mozilla/dom/TextDecoderBinding.h"
82 #include "mozilla/dom/TextEncoderBinding.h"
83 #include "mozilla/dom/UnionConversions.h"
84 #include "mozilla/dom/URLBinding.h"
85 #include "mozilla/dom/URLSearchParamsBinding.h"
86 #include "mozilla/dom/XMLHttpRequest.h"
87 #include "mozilla/dom/WebSocketBinding.h"
88 #include "mozilla/dom/WindowBinding.h"
89 #include "mozilla/dom/XMLSerializerBinding.h"
90 #include "mozilla/dom/FormDataBinding.h"
91 #include "mozilla/dom/nsCSPContext.h"
92 #include "mozilla/BasePrincipal.h"
93 #include "mozilla/DeferredFinalize.h"
94 #include "mozilla/ExtensionPolicyService.h"
95 #include "mozilla/Maybe.h"
96 #include "mozilla/NullPrincipal.h"
97 #include "mozilla/ResultExtensions.h"
98 #include "mozilla/StaticPrefs_extensions.h"
99 
100 using namespace mozilla;
101 using namespace JS;
102 using namespace xpc;
103 
104 using mozilla::dom::DestroyProtoAndIfaceCache;
105 using mozilla::dom::IndexedDatabaseManager;
106 
107 NS_IMPL_CYCLE_COLLECTION_CLASS(SandboxPrivate)
108 
109 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(SandboxPrivate)
110   NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
111   NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
112   NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
113   tmp->UnlinkObjectsInGlobal();
114 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
115 
116 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(SandboxPrivate)
117   tmp->TraverseObjectsInGlobal(cb);
118 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
119 
120 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(SandboxPrivate)
121 
122 NS_IMPL_CYCLE_COLLECTING_ADDREF(SandboxPrivate)
123 NS_IMPL_CYCLE_COLLECTING_RELEASE(SandboxPrivate)
124 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SandboxPrivate)
125   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
126   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIScriptObjectPrincipal)
127   NS_INTERFACE_MAP_ENTRY(nsIScriptObjectPrincipal)
128   NS_INTERFACE_MAP_ENTRY(nsIGlobalObject)
129   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
130 NS_INTERFACE_MAP_END
131 
132 class nsXPCComponents_utils_Sandbox : public nsIXPCComponents_utils_Sandbox,
133                                       public nsIXPCScriptable {
134  public:
135   // Aren't macros nice?
136   NS_DECL_ISUPPORTS
137   NS_DECL_NSIXPCCOMPONENTS_UTILS_SANDBOX
138   NS_DECL_NSIXPCSCRIPTABLE
139 
140  public:
141   nsXPCComponents_utils_Sandbox();
142 
143  private:
144   virtual ~nsXPCComponents_utils_Sandbox();
145 
146   static nsresult CallOrConstruct(nsIXPConnectWrappedNative* wrapper,
147                                   JSContext* cx, HandleObject obj,
148                                   const CallArgs& args, bool* _retval);
149 };
150 
NewSandboxConstructor()151 already_AddRefed<nsIXPCComponents_utils_Sandbox> xpc::NewSandboxConstructor() {
152   nsCOMPtr<nsIXPCComponents_utils_Sandbox> sbConstructor =
153       new nsXPCComponents_utils_Sandbox();
154   return sbConstructor.forget();
155 }
156 
SandboxDump(JSContext * cx,unsigned argc,Value * vp)157 static bool SandboxDump(JSContext* cx, unsigned argc, Value* vp) {
158   if (!nsJSUtils::DumpEnabled()) {
159     return true;
160   }
161 
162   CallArgs args = CallArgsFromVp(argc, vp);
163 
164   if (args.length() == 0) {
165     return true;
166   }
167 
168   RootedString str(cx, ToString(cx, args[0]));
169   if (!str) {
170     return false;
171   }
172 
173   JS::UniqueChars utf8str = JS_EncodeStringToUTF8(cx, str);
174   char* cstr = utf8str.get();
175   if (!cstr) {
176     return false;
177   }
178 
179 #if defined(XP_MACOSX)
180   // Be nice and convert all \r to \n.
181   char* c = cstr;
182   char* cEnd = cstr + strlen(cstr);
183   while (c < cEnd) {
184     if (*c == '\r') {
185       *c = '\n';
186     }
187     c++;
188   }
189 #endif
190   MOZ_LOG(nsContentUtils::DOMDumpLog(), mozilla::LogLevel::Debug,
191           ("[Sandbox.Dump] %s", cstr));
192 #ifdef ANDROID
193   __android_log_write(ANDROID_LOG_INFO, "GeckoDump", cstr);
194 #endif
195   fputs(cstr, stdout);
196   fflush(stdout);
197   args.rval().setBoolean(true);
198   return true;
199 }
200 
SandboxDebug(JSContext * cx,unsigned argc,Value * vp)201 static bool SandboxDebug(JSContext* cx, unsigned argc, Value* vp) {
202 #ifdef DEBUG
203   return SandboxDump(cx, argc, vp);
204 #else
205   return true;
206 #endif
207 }
208 
SandboxImport(JSContext * cx,unsigned argc,Value * vp)209 static bool SandboxImport(JSContext* cx, unsigned argc, Value* vp) {
210   CallArgs args = CallArgsFromVp(argc, vp);
211 
212   if (args.length() < 1 || args[0].isPrimitive()) {
213     XPCThrower::Throw(NS_ERROR_INVALID_ARG, cx);
214     return false;
215   }
216 
217   RootedString funname(cx);
218   if (args.length() > 1) {
219     // Use the second parameter as the function name.
220     funname = ToString(cx, args[1]);
221     if (!funname) {
222       return false;
223     }
224   } else {
225     // NB: funobj must only be used to get the JSFunction out.
226     RootedObject funobj(cx, &args[0].toObject());
227     if (js::IsProxy(funobj)) {
228       funobj = XPCWrapper::UnsafeUnwrapSecurityWrapper(funobj);
229     }
230 
231     JSAutoRealm ar(cx, funobj);
232 
233     RootedValue funval(cx, ObjectValue(*funobj));
234     JSFunction* fun = JS_ValueToFunction(cx, funval);
235     if (!fun) {
236       XPCThrower::Throw(NS_ERROR_INVALID_ARG, cx);
237       return false;
238     }
239 
240     // Use the actual function name as the name.
241     funname = JS_GetFunctionId(fun);
242     if (!funname) {
243       XPCThrower::Throw(NS_ERROR_INVALID_ARG, cx);
244       return false;
245     }
246   }
247   JS_MarkCrossZoneIdValue(cx, StringValue(funname));
248 
249   RootedId id(cx);
250   if (!JS_StringToId(cx, funname, &id)) {
251     return false;
252   }
253 
254   // We need to resolve the this object, because this function is used
255   // unbound and should still work and act on the original sandbox.
256 
257   RootedObject thisObject(cx);
258   if (!args.computeThis(cx, &thisObject)) {
259     return false;
260   }
261 
262   if (!JS_SetPropertyById(cx, thisObject, id, args[0])) {
263     return false;
264   }
265 
266   args.rval().setUndefined();
267   return true;
268 }
269 
SandboxCreateCrypto(JSContext * cx,JS::HandleObject obj)270 static bool SandboxCreateCrypto(JSContext* cx, JS::HandleObject obj) {
271   MOZ_ASSERT(JS_IsGlobalObject(obj));
272 
273   nsIGlobalObject* native = xpc::NativeGlobal(obj);
274   MOZ_ASSERT(native);
275 
276   dom::Crypto* crypto = new dom::Crypto(native);
277   JS::RootedObject wrapped(cx, crypto->WrapObject(cx, nullptr));
278   return JS_DefineProperty(cx, obj, "crypto", wrapped, JSPROP_ENUMERATE);
279 }
280 
281 #ifdef MOZ_WEBRTC
SandboxCreateRTCIdentityProvider(JSContext * cx,JS::HandleObject obj)282 static bool SandboxCreateRTCIdentityProvider(JSContext* cx,
283                                              JS::HandleObject obj) {
284   MOZ_ASSERT(JS_IsGlobalObject(obj));
285 
286   nsCOMPtr<nsIGlobalObject> nativeGlobal = xpc::NativeGlobal(obj);
287   MOZ_ASSERT(nativeGlobal);
288 
289   dom::RTCIdentityProviderRegistrar* registrar =
290       new dom::RTCIdentityProviderRegistrar(nativeGlobal);
291   JS::RootedObject wrapped(cx, registrar->WrapObject(cx, nullptr));
292   return JS_DefineProperty(cx, obj, "rtcIdentityProvider", wrapped,
293                            JSPROP_ENUMERATE);
294 }
295 #endif
296 
SetFetchRequestFromValue(JSContext * cx,RequestOrUSVString & request,const MutableHandleValue & requestOrUrl)297 static bool SetFetchRequestFromValue(JSContext* cx, RequestOrUSVString& request,
298                                      const MutableHandleValue& requestOrUrl) {
299   RequestOrUSVStringArgument requestHolder(request);
300   bool noMatch = true;
301   if (requestOrUrl.isObject() &&
302       !requestHolder.TrySetToRequest(cx, requestOrUrl, noMatch, false)) {
303     return false;
304   }
305   if (noMatch && !requestHolder.TrySetToUSVString(cx, requestOrUrl, noMatch)) {
306     return false;
307   }
308   if (noMatch) {
309     return false;
310   }
311   return true;
312 }
313 
SandboxFetch(JSContext * cx,JS::HandleObject scope,const CallArgs & args)314 static bool SandboxFetch(JSContext* cx, JS::HandleObject scope,
315                          const CallArgs& args) {
316   if (args.length() < 1) {
317     JS_ReportErrorASCII(cx, "fetch requires at least 1 argument");
318     return false;
319   }
320 
321   RequestOrUSVString request;
322   if (!SetFetchRequestFromValue(cx, request, args[0])) {
323     JS_ReportErrorASCII(cx, "fetch requires a string or Request in argument 1");
324     return false;
325   }
326   RootedDictionary<dom::RequestInit> options(cx);
327   BindingCallContext callCx(cx, "fetch");
328   if (!options.Init(cx, args.hasDefined(1) ? args[1] : JS::NullHandleValue,
329                     "Argument 2", false)) {
330     return false;
331   }
332   nsCOMPtr<nsIGlobalObject> global = xpc::NativeGlobal(scope);
333   if (!global) {
334     return false;
335   }
336   dom::CallerType callerType = nsContentUtils::IsSystemCaller(cx)
337                                    ? dom::CallerType::System
338                                    : dom::CallerType::NonSystem;
339   ErrorResult rv;
340   RefPtr<dom::Promise> response = FetchRequest(
341       global, Constify(request), Constify(options), callerType, rv);
342   if (rv.MaybeSetPendingException(cx)) {
343     return false;
344   }
345 
346   args.rval().setObject(*response->PromiseObj());
347   return true;
348 }
349 
SandboxFetchPromise(JSContext * cx,unsigned argc,Value * vp)350 static bool SandboxFetchPromise(JSContext* cx, unsigned argc, Value* vp) {
351   CallArgs args = CallArgsFromVp(argc, vp);
352   RootedObject scope(cx, JS::CurrentGlobalOrNull(cx));
353   if (SandboxFetch(cx, scope, args)) {
354     return true;
355   }
356   return ConvertExceptionToPromise(cx, args.rval());
357 }
358 
SandboxCreateFetch(JSContext * cx,HandleObject obj)359 static bool SandboxCreateFetch(JSContext* cx, HandleObject obj) {
360   MOZ_ASSERT(JS_IsGlobalObject(obj));
361 
362   return JS_DefineFunction(cx, obj, "fetch", SandboxFetchPromise, 2, 0) &&
363          dom::Request_Binding::GetConstructorObject(cx) &&
364          dom::Response_Binding::GetConstructorObject(cx) &&
365          dom::Headers_Binding::GetConstructorObject(cx);
366 }
367 
SandboxCreateStorage(JSContext * cx,JS::HandleObject obj)368 static bool SandboxCreateStorage(JSContext* cx, JS::HandleObject obj) {
369   MOZ_ASSERT(JS_IsGlobalObject(obj));
370 
371   nsIGlobalObject* native = xpc::NativeGlobal(obj);
372   MOZ_ASSERT(native);
373 
374   dom::StorageManager* storageManager = new dom::StorageManager(native);
375   JS::RootedObject wrapped(cx, storageManager->WrapObject(cx, nullptr));
376   return JS_DefineProperty(cx, obj, "storage", wrapped, JSPROP_ENUMERATE);
377 }
378 
SandboxStructuredClone(JSContext * cx,unsigned argc,Value * vp)379 static bool SandboxStructuredClone(JSContext* cx, unsigned argc, Value* vp) {
380   CallArgs args = CallArgsFromVp(argc, vp);
381 
382   if (!args.requireAtLeast(cx, "structuredClone", 1)) {
383     return false;
384   }
385 
386   RootedDictionary<dom::StructuredSerializeOptions> options(cx);
387   BindingCallContext callCx(cx, "structuredClone");
388   if (!options.Init(cx, args.hasDefined(1) ? args[1] : JS::NullHandleValue,
389                     "Argument 2", false)) {
390     return false;
391   }
392 
393   nsIGlobalObject* global = CurrentNativeGlobal(cx);
394   if (!global) {
395     JS_ReportErrorASCII(cx, "structuredClone: Missing global");
396     return false;
397   }
398 
399   JS::Rooted<JS::Value> result(cx);
400   ErrorResult rv;
401   nsContentUtils::StructuredClone(cx, global, args[0], options, &result, rv);
402   if (rv.MaybeSetPendingException(cx)) {
403     return false;
404   }
405 
406   MOZ_ASSERT_IF(result.isGCThing(),
407                 !JS::GCThingIsMarkedGray(result.toGCCellPtr()));
408   args.rval().set(result);
409   return true;
410 }
411 
SandboxCreateStructuredClone(JSContext * cx,HandleObject obj)412 static bool SandboxCreateStructuredClone(JSContext* cx, HandleObject obj) {
413   MOZ_ASSERT(JS_IsGlobalObject(obj));
414 
415   return JS_DefineFunction(cx, obj, "structuredClone", SandboxStructuredClone,
416                            1, 0);
417 }
418 
SandboxIsProxy(JSContext * cx,unsigned argc,Value * vp)419 static bool SandboxIsProxy(JSContext* cx, unsigned argc, Value* vp) {
420   CallArgs args = CallArgsFromVp(argc, vp);
421   if (args.length() < 1) {
422     JS_ReportErrorASCII(cx, "Function requires at least 1 argument");
423     return false;
424   }
425   if (!args[0].isObject()) {
426     args.rval().setBoolean(false);
427     return true;
428   }
429 
430   RootedObject obj(cx, &args[0].toObject());
431   // CheckedUnwrapStatic is OK here, since we only care about whether
432   // it's a scripted proxy and the things CheckedUnwrapStatic fails on
433   // are not.
434   obj = js::CheckedUnwrapStatic(obj);
435   if (!obj) {
436     args.rval().setBoolean(false);
437     return true;
438   }
439 
440   args.rval().setBoolean(js::IsScriptedProxy(obj));
441   return true;
442 }
443 
444 /*
445  * Expected type of the arguments and the return value:
446  * function exportFunction(function funToExport,
447  *                         object targetScope,
448  *                         [optional] object options)
449  */
SandboxExportFunction(JSContext * cx,unsigned argc,Value * vp)450 static bool SandboxExportFunction(JSContext* cx, unsigned argc, Value* vp) {
451   CallArgs args = CallArgsFromVp(argc, vp);
452   if (args.length() < 2) {
453     JS_ReportErrorASCII(cx, "Function requires at least 2 arguments");
454     return false;
455   }
456 
457   RootedValue options(cx, args.length() > 2 ? args[2] : UndefinedValue());
458   return ExportFunction(cx, args[0], args[1], options, args.rval());
459 }
460 
SandboxCreateObjectIn(JSContext * cx,unsigned argc,Value * vp)461 static bool SandboxCreateObjectIn(JSContext* cx, unsigned argc, Value* vp) {
462   CallArgs args = CallArgsFromVp(argc, vp);
463   if (args.length() < 1) {
464     JS_ReportErrorASCII(cx, "Function requires at least 1 argument");
465     return false;
466   }
467 
468   RootedObject optionsObj(cx);
469   bool calledWithOptions = args.length() > 1;
470   if (calledWithOptions) {
471     if (!args[1].isObject()) {
472       JS_ReportErrorASCII(
473           cx, "Expected the 2nd argument (options) to be an object");
474       return false;
475     }
476     optionsObj = &args[1].toObject();
477   }
478 
479   CreateObjectInOptions options(cx, optionsObj);
480   if (calledWithOptions && !options.Parse()) {
481     return false;
482   }
483 
484   return xpc::CreateObjectIn(cx, args[0], options, args.rval());
485 }
486 
SandboxCloneInto(JSContext * cx,unsigned argc,Value * vp)487 static bool SandboxCloneInto(JSContext* cx, unsigned argc, Value* vp) {
488   CallArgs args = CallArgsFromVp(argc, vp);
489   if (args.length() < 2) {
490     JS_ReportErrorASCII(cx, "Function requires at least 2 arguments");
491     return false;
492   }
493 
494   RootedValue options(cx, args.length() > 2 ? args[2] : UndefinedValue());
495   return xpc::CloneInto(cx, args[0], args[1], options, args.rval());
496 }
497 
sandbox_finalize(JSFreeOp * fop,JSObject * obj)498 static void sandbox_finalize(JSFreeOp* fop, JSObject* obj) {
499   SandboxPrivate* priv = SandboxPrivate::GetPrivate(obj);
500   if (!priv) {
501     // priv can be null if CreateSandboxObject fails in the middle.
502     return;
503   }
504 
505   priv->ForgetGlobalObject(obj);
506   DestroyProtoAndIfaceCache(obj);
507   DeferredFinalize(static_cast<nsIScriptObjectPrincipal*>(priv));
508 }
509 
sandbox_moved(JSObject * obj,JSObject * old)510 static size_t sandbox_moved(JSObject* obj, JSObject* old) {
511   // Note that this hook can be called before the private pointer is set. In
512   // this case the SandboxPrivate will not exist yet, so there is nothing to
513   // do.
514   SandboxPrivate* priv = SandboxPrivate::GetPrivate(obj);
515   if (!priv) {
516     return 0;
517   }
518 
519   return priv->ObjectMoved(obj, old);
520 }
521 
522 #define XPCONNECT_SANDBOX_CLASS_METADATA_SLOT \
523   (XPCONNECT_GLOBAL_EXTRA_SLOT_OFFSET)
524 
525 static const JSClassOps SandboxClassOps = {
526     nullptr,                         // addProperty
527     nullptr,                         // delProperty
528     nullptr,                         // enumerate
529     JS_NewEnumerateStandardClasses,  // newEnumerate
530     JS_ResolveStandardClass,         // resolve
531     JS_MayResolveStandardClass,      // mayResolve
532     sandbox_finalize,                // finalize
533     nullptr,                         // call
534     nullptr,                         // hasInstance
535     nullptr,                         // construct
536     JS_GlobalObjectTraceHook,        // trace
537 };
538 
539 static const js::ClassExtension SandboxClassExtension = {
540     sandbox_moved,  // objectMovedOp
541 };
542 
543 static const JSClass SandboxClass = {
544     "Sandbox",
545     XPCONNECT_GLOBAL_FLAGS_WITH_EXTRA_SLOTS(1) | JSCLASS_FOREGROUND_FINALIZE,
546     &SandboxClassOps,
547     JS_NULL_CLASS_SPEC,
548     &SandboxClassExtension,
549     JS_NULL_OBJECT_OPS};
550 
551 static const JSFunctionSpec SandboxFunctions[] = {
552     JS_FN("dump", SandboxDump, 1, 0), JS_FN("debug", SandboxDebug, 1, 0),
553     JS_FN("importFunction", SandboxImport, 1, 0), JS_FS_END};
554 
IsSandbox(JSObject * obj)555 bool xpc::IsSandbox(JSObject* obj) {
556   const JSClass* clasp = JS::GetClass(obj);
557   return clasp == &SandboxClass;
558 }
559 
560 /***************************************************************************/
561 nsXPCComponents_utils_Sandbox::nsXPCComponents_utils_Sandbox() = default;
562 
563 nsXPCComponents_utils_Sandbox::~nsXPCComponents_utils_Sandbox() = default;
564 
565 NS_IMPL_QUERY_INTERFACE(nsXPCComponents_utils_Sandbox,
566                         nsIXPCComponents_utils_Sandbox, nsIXPCScriptable)
567 
568 NS_IMPL_ADDREF(nsXPCComponents_utils_Sandbox)
569 NS_IMPL_RELEASE(nsXPCComponents_utils_Sandbox)
570 
571 // We use the nsIXPScriptable macros to generate lots of stuff for us.
572 #define XPC_MAP_CLASSNAME nsXPCComponents_utils_Sandbox
573 #define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_utils_Sandbox"
574 #define XPC_MAP_FLAGS (XPC_SCRIPTABLE_WANT_CALL | XPC_SCRIPTABLE_WANT_CONSTRUCT)
575 #include "xpc_map_end.h" /* This #undef's the above. */
576 
577 class SandboxProxyHandler : public js::Wrapper {
578  public:
SandboxProxyHandler()579   constexpr SandboxProxyHandler() : js::Wrapper(0) {}
580 
581   virtual bool getOwnPropertyDescriptor(
582       JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
583       JS::MutableHandle<Maybe<JS::PropertyDescriptor>> desc) const override;
584 
585   // We just forward the high-level methods to the BaseProxyHandler versions
586   // which implement them in terms of lower-level methods.
587   virtual bool has(JSContext* cx, JS::Handle<JSObject*> proxy,
588                    JS::Handle<jsid> id, bool* bp) const override;
589   virtual bool get(JSContext* cx, JS::Handle<JSObject*> proxy,
590                    JS::HandleValue receiver, JS::Handle<jsid> id,
591                    JS::MutableHandle<JS::Value> vp) const override;
592   virtual bool set(JSContext* cx, JS::Handle<JSObject*> proxy,
593                    JS::Handle<jsid> id, JS::Handle<JS::Value> v,
594                    JS::Handle<JS::Value> receiver,
595                    JS::ObjectOpResult& result) const override;
596 
597   virtual bool hasOwn(JSContext* cx, JS::Handle<JSObject*> proxy,
598                       JS::Handle<jsid> id, bool* bp) const override;
599   virtual bool getOwnEnumerablePropertyKeys(
600       JSContext* cx, JS::Handle<JSObject*> proxy,
601       JS::MutableHandleIdVector props) const override;
602   virtual bool enumerate(JSContext* cx, JS::Handle<JSObject*> proxy,
603                          JS::MutableHandleIdVector props) const override;
604 
605  private:
606   // Implements the custom getPropertyDescriptor behavior. If the getOwn
607   // argument is true we only look for "own" properties.
608   bool getPropertyDescriptorImpl(
609       JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
610       bool getOwn, JS::MutableHandle<Maybe<JS::PropertyDescriptor>> desc) const;
611 };
612 
613 static const SandboxProxyHandler sandboxProxyHandler;
614 
615 namespace xpc {
616 
IsSandboxPrototypeProxy(JSObject * obj)617 bool IsSandboxPrototypeProxy(JSObject* obj) {
618   return js::IsProxy(obj) && js::GetProxyHandler(obj) == &sandboxProxyHandler;
619 }
620 
IsWebExtensionContentScriptSandbox(JSObject * obj)621 bool IsWebExtensionContentScriptSandbox(JSObject* obj) {
622   return IsSandbox(obj) &&
623          CompartmentPrivate::Get(obj)->isWebExtensionContentScript;
624 }
625 
626 }  // namespace xpc
627 
628 // A proxy handler that lets us wrap callables and invoke them with
629 // the correct this object, while forwarding all other operations down
630 // to them directly.
631 class SandboxCallableProxyHandler : public js::Wrapper {
632  public:
SandboxCallableProxyHandler()633   constexpr SandboxCallableProxyHandler() : js::Wrapper(0) {}
634 
635   virtual bool call(JSContext* cx, JS::Handle<JSObject*> proxy,
636                     const JS::CallArgs& args) const override;
637 
638   static const size_t SandboxProxySlot = 0;
639 
getSandboxProxy(JS::Handle<JSObject * > proxy)640   static inline JSObject* getSandboxProxy(JS::Handle<JSObject*> proxy) {
641     return &js::GetProxyReservedSlot(proxy, SandboxProxySlot).toObject();
642   }
643 };
644 
645 static const SandboxCallableProxyHandler sandboxCallableProxyHandler;
646 
call(JSContext * cx,JS::Handle<JSObject * > proxy,const JS::CallArgs & args) const647 bool SandboxCallableProxyHandler::call(JSContext* cx,
648                                        JS::Handle<JSObject*> proxy,
649                                        const JS::CallArgs& args) const {
650   // We forward the call to our underlying callable.
651 
652   // Get our SandboxProxyHandler proxy.
653   RootedObject sandboxProxy(cx, getSandboxProxy(proxy));
654   MOZ_ASSERT(js::IsProxy(sandboxProxy) &&
655              js::GetProxyHandler(sandboxProxy) == &sandboxProxyHandler);
656 
657   // The global of the sandboxProxy is the sandbox global, and the
658   // target object is the original proto.
659   RootedObject sandboxGlobal(cx, JS::GetNonCCWObjectGlobal(sandboxProxy));
660   MOZ_ASSERT(IsSandbox(sandboxGlobal));
661 
662   // If our this object is the sandbox global, we call with this set to the
663   // original proto instead.
664   //
665   // There are two different ways we can compute |this|. If we use
666   // JS_THIS_VALUE, we'll get the bonafide |this| value as passed by the
667   // caller, which may be undefined if a global function was invoked without
668   // an explicit invocant. If we use JS_THIS or JS_THIS_OBJECT, the |this|
669   // in |vp| will be coerced to the global, which is not the correct
670   // behavior in ES5 strict mode. And we have no way to compute strictness
671   // here.
672   //
673   // The naive approach is simply to use JS_THIS_VALUE here. If |this| was
674   // explicit, we can remap it appropriately. If it was implicit, then we
675   // leave it as undefined, and let the callee sort it out. Since the callee
676   // is generally in the same compartment as its global (eg the Window's
677   // compartment, not the Sandbox's), the callee will generally compute the
678   // correct |this|.
679   //
680   // However, this breaks down in the Xray case. If the sandboxPrototype
681   // is an Xray wrapper, then we'll end up reifying the native methods in
682   // the Sandbox's scope, which means that they'll compute |this| to be the
683   // Sandbox, breaking old-style XPC_WN_CallMethod methods.
684   //
685   // Luckily, the intent of Xrays is to provide a vanilla view of a foreign
686   // DOM interface, which means that we don't care about script-enacted
687   // strictness in the prototype's home compartment. Indeed, since DOM
688   // methods are always non-strict, we can just assume non-strict semantics
689   // if the sandboxPrototype is an Xray Wrapper, which lets us appropriately
690   // remap |this|.
691   bool isXray = WrapperFactory::IsXrayWrapper(sandboxProxy);
692   RootedValue thisVal(cx, args.thisv());
693   if (isXray) {
694     RootedObject thisObject(cx);
695     if (!args.computeThis(cx, &thisObject)) {
696       return false;
697     }
698     thisVal.setObject(*thisObject);
699   }
700 
701   if (thisVal == ObjectValue(*sandboxGlobal)) {
702     thisVal = ObjectValue(*js::GetProxyTargetObject(sandboxProxy));
703   }
704 
705   RootedValue func(cx, js::GetProxyPrivate(proxy));
706   return JS::Call(cx, thisVal, func, args, args.rval());
707 }
708 
709 /*
710  * Wrap a callable such that if we're called with oldThisObj as the
711  * "this" we will instead call it with newThisObj as the this.
712  */
WrapCallable(JSContext * cx,HandleObject callable,HandleObject sandboxProtoProxy)713 static JSObject* WrapCallable(JSContext* cx, HandleObject callable,
714                               HandleObject sandboxProtoProxy) {
715   MOZ_ASSERT(JS::IsCallable(callable));
716   // Our proxy is wrapping the callable.  So we need to use the
717   // callable as the private.  We put the given sandboxProtoProxy in
718   // an extra slot, and our call() hook depends on that.
719   MOZ_ASSERT(js::IsProxy(sandboxProtoProxy) &&
720              js::GetProxyHandler(sandboxProtoProxy) == &sandboxProxyHandler);
721 
722   RootedValue priv(cx, ObjectValue(*callable));
723   // We want to claim to have the same proto as our wrapped callable, so set
724   // ourselves up with a lazy proto.
725   js::ProxyOptions options;
726   options.setLazyProto(true);
727   JSObject* obj = js::NewProxyObject(cx, &sandboxCallableProxyHandler, priv,
728                                      nullptr, options);
729   if (obj) {
730     js::SetProxyReservedSlot(obj, SandboxCallableProxyHandler::SandboxProxySlot,
731                              ObjectValue(*sandboxProtoProxy));
732   }
733 
734   return obj;
735 }
736 
WrapAccessorFunction(JSContext * cx,MutableHandleObject accessor,HandleObject sandboxProtoProxy)737 bool WrapAccessorFunction(JSContext* cx, MutableHandleObject accessor,
738                           HandleObject sandboxProtoProxy) {
739   if (!accessor) {
740     return true;
741   }
742 
743   accessor.set(WrapCallable(cx, accessor, sandboxProtoProxy));
744   return !!accessor;
745 }
746 
IsMaybeWrappedDOMConstructor(JSObject * obj)747 static bool IsMaybeWrappedDOMConstructor(JSObject* obj) {
748   // We really care about the underlying object here, which might be wrapped in
749   // cross-compartment wrappers.  CheckedUnwrapStatic is fine, since we just
750   // care whether it's a DOM constructor.
751   obj = js::CheckedUnwrapStatic(obj);
752   if (!obj) {
753     return false;
754   }
755 
756   return dom::IsDOMConstructor(obj);
757 }
758 
getPropertyDescriptorImpl(JSContext * cx,JS::Handle<JSObject * > proxy,JS::Handle<jsid> id,bool getOwn,MutableHandle<Maybe<PropertyDescriptor>> desc_) const759 bool SandboxProxyHandler::getPropertyDescriptorImpl(
760     JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
761     bool getOwn, MutableHandle<Maybe<PropertyDescriptor>> desc_) const {
762   JS::RootedObject obj(cx, wrappedObject(proxy));
763 
764   MOZ_ASSERT(JS::GetCompartment(obj) == JS::GetCompartment(proxy));
765 
766   if (getOwn) {
767     if (!JS_GetOwnPropertyDescriptorById(cx, obj, id, desc_)) {
768       return false;
769     }
770   } else {
771     Rooted<JSObject*> holder(cx);
772     if (!JS_GetPropertyDescriptorById(cx, obj, id, desc_, &holder)) {
773       return false;
774     }
775   }
776 
777   if (desc_.isNothing()) {
778     return true;
779   }
780 
781   Rooted<PropertyDescriptor> desc(cx, *desc_);
782 
783   // Now fix up the getter/setter/value as needed.
784   if (desc.hasGetter() && !WrapAccessorFunction(cx, desc.getter(), proxy)) {
785     return false;
786   }
787   if (desc.hasSetter() && !WrapAccessorFunction(cx, desc.setter(), proxy)) {
788     return false;
789   }
790   if (desc.hasValue() && desc.value().isObject()) {
791     RootedObject val(cx, &desc.value().toObject());
792     if (JS::IsCallable(val) &&
793         // Don't wrap DOM constructors: they don't care about the "this"
794         // they're invoked with anyway, being constructors.  And if we wrap
795         // them here we break invariants like Node == Node and whatnot.
796         !IsMaybeWrappedDOMConstructor(val)) {
797       val = WrapCallable(cx, val, proxy);
798       if (!val) {
799         return false;
800       }
801       desc.value().setObject(*val);
802     }
803   }
804 
805   desc_.set(Some(desc.get()));
806   return true;
807 }
808 
getOwnPropertyDescriptor(JSContext * cx,JS::Handle<JSObject * > proxy,JS::Handle<jsid> id,MutableHandle<Maybe<PropertyDescriptor>> desc) const809 bool SandboxProxyHandler::getOwnPropertyDescriptor(
810     JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
811     MutableHandle<Maybe<PropertyDescriptor>> desc) const {
812   return getPropertyDescriptorImpl(cx, proxy, id, /* getOwn = */ true, desc);
813 }
814 
815 /*
816  * Reuse the BaseProxyHandler versions of the derived traps that are implemented
817  * in terms of the fundamental traps.
818  */
819 
has(JSContext * cx,JS::Handle<JSObject * > proxy,JS::Handle<jsid> id,bool * bp) const820 bool SandboxProxyHandler::has(JSContext* cx, JS::Handle<JSObject*> proxy,
821                               JS::Handle<jsid> id, bool* bp) const {
822   // This uses JS_GetPropertyDescriptorById for backward compatibility.
823   Rooted<Maybe<PropertyDescriptor>> desc(cx);
824   if (!getPropertyDescriptorImpl(cx, proxy, id, /* getOwn = */ false, &desc)) {
825     return false;
826   }
827 
828   *bp = desc.isSome();
829   return true;
830 }
hasOwn(JSContext * cx,JS::Handle<JSObject * > proxy,JS::Handle<jsid> id,bool * bp) const831 bool SandboxProxyHandler::hasOwn(JSContext* cx, JS::Handle<JSObject*> proxy,
832                                  JS::Handle<jsid> id, bool* bp) const {
833   return BaseProxyHandler::hasOwn(cx, proxy, id, bp);
834 }
835 
get(JSContext * cx,JS::Handle<JSObject * > proxy,JS::Handle<JS::Value> receiver,JS::Handle<jsid> id,JS::MutableHandle<Value> vp) const836 bool SandboxProxyHandler::get(JSContext* cx, JS::Handle<JSObject*> proxy,
837                               JS::Handle<JS::Value> receiver,
838                               JS::Handle<jsid> id,
839                               JS::MutableHandle<Value> vp) const {
840   // This uses JS_GetPropertyDescriptorById for backward compatibility.
841   Rooted<Maybe<PropertyDescriptor>> desc(cx);
842   if (!getPropertyDescriptorImpl(cx, proxy, id, /* getOwn = */ false, &desc)) {
843     return false;
844   }
845 
846   if (desc.isNothing()) {
847     vp.setUndefined();
848     return true;
849   } else {
850     desc->assertComplete();
851   }
852 
853   // Everything after here follows [[Get]] for ordinary objects.
854   if (desc->isDataDescriptor()) {
855     vp.set(desc->value());
856     return true;
857   }
858 
859   MOZ_ASSERT(desc->isAccessorDescriptor());
860   RootedObject getter(cx, desc->getter());
861 
862   if (!getter) {
863     vp.setUndefined();
864     return true;
865   }
866 
867   return Call(cx, receiver, getter, HandleValueArray::empty(), vp);
868 }
869 
set(JSContext * cx,JS::Handle<JSObject * > proxy,JS::Handle<jsid> id,JS::Handle<Value> v,JS::Handle<Value> receiver,JS::ObjectOpResult & result) const870 bool SandboxProxyHandler::set(JSContext* cx, JS::Handle<JSObject*> proxy,
871                               JS::Handle<jsid> id, JS::Handle<Value> v,
872                               JS::Handle<Value> receiver,
873                               JS::ObjectOpResult& result) const {
874   return BaseProxyHandler::set(cx, proxy, id, v, receiver, result);
875 }
876 
getOwnEnumerablePropertyKeys(JSContext * cx,JS::Handle<JSObject * > proxy,MutableHandleIdVector props) const877 bool SandboxProxyHandler::getOwnEnumerablePropertyKeys(
878     JSContext* cx, JS::Handle<JSObject*> proxy,
879     MutableHandleIdVector props) const {
880   return BaseProxyHandler::getOwnEnumerablePropertyKeys(cx, proxy, props);
881 }
882 
enumerate(JSContext * cx,JS::Handle<JSObject * > proxy,JS::MutableHandleIdVector props) const883 bool SandboxProxyHandler::enumerate(JSContext* cx, JS::Handle<JSObject*> proxy,
884                                     JS::MutableHandleIdVector props) const {
885   return BaseProxyHandler::enumerate(cx, proxy, props);
886 }
887 
Parse(JSContext * cx,JS::HandleObject obj)888 bool xpc::GlobalProperties::Parse(JSContext* cx, JS::HandleObject obj) {
889   uint32_t length;
890   bool ok = JS::GetArrayLength(cx, obj, &length);
891   NS_ENSURE_TRUE(ok, false);
892   for (uint32_t i = 0; i < length; i++) {
893     RootedValue nameValue(cx);
894     ok = JS_GetElement(cx, obj, i, &nameValue);
895     NS_ENSURE_TRUE(ok, false);
896     if (!nameValue.isString()) {
897       JS_ReportErrorASCII(cx, "Property names must be strings");
898       return false;
899     }
900     JSLinearString* nameStr = JS_EnsureLinearString(cx, nameValue.toString());
901     if (!nameStr) {
902       return false;
903     }
904 
905     if (JS_LinearStringEqualsLiteral(nameStr, "AbortController")) {
906       AbortController = true;
907     } else if (JS_LinearStringEqualsLiteral(nameStr, "Blob")) {
908       Blob = true;
909     } else if (JS_LinearStringEqualsLiteral(nameStr, "ChromeUtils")) {
910       ChromeUtils = true;
911     } else if (JS_LinearStringEqualsLiteral(nameStr, "CSS")) {
912       CSS = true;
913     } else if (JS_LinearStringEqualsLiteral(nameStr, "CSSRule")) {
914       CSSRule = true;
915     } else if (JS_LinearStringEqualsLiteral(nameStr, "Document")) {
916       Document = true;
917     } else if (JS_LinearStringEqualsLiteral(nameStr, "Directory")) {
918       Directory = true;
919     } else if (JS_LinearStringEqualsLiteral(nameStr, "DOMException")) {
920       DOMException = true;
921     } else if (JS_LinearStringEqualsLiteral(nameStr, "DOMParser")) {
922       DOMParser = true;
923     } else if (JS_LinearStringEqualsLiteral(nameStr, "DOMTokenList")) {
924       DOMTokenList = true;
925     } else if (JS_LinearStringEqualsLiteral(nameStr, "Element")) {
926       Element = true;
927     } else if (JS_LinearStringEqualsLiteral(nameStr, "Event")) {
928       Event = true;
929     } else if (JS_LinearStringEqualsLiteral(nameStr, "File")) {
930       File = true;
931     } else if (JS_LinearStringEqualsLiteral(nameStr, "FileReader")) {
932       FileReader = true;
933     } else if (JS_LinearStringEqualsLiteral(nameStr, "FormData")) {
934       FormData = true;
935     } else if (JS_LinearStringEqualsLiteral(nameStr, "Headers")) {
936       Headers = true;
937     } else if (JS_LinearStringEqualsLiteral(nameStr, "IOUtils")) {
938       IOUtils = true;
939     } else if (JS_LinearStringEqualsLiteral(nameStr, "InspectorUtils")) {
940       InspectorUtils = true;
941     } else if (JS_LinearStringEqualsLiteral(nameStr, "MessageChannel")) {
942       MessageChannel = true;
943     } else if (JS_LinearStringEqualsLiteral(nameStr, "Node")) {
944       Node = true;
945     } else if (JS_LinearStringEqualsLiteral(nameStr, "NodeFilter")) {
946       NodeFilter = true;
947     } else if (JS_LinearStringEqualsLiteral(nameStr, "PathUtils")) {
948       PathUtils = true;
949     } else if (JS_LinearStringEqualsLiteral(nameStr, "Performance")) {
950       Performance = true;
951     } else if (JS_LinearStringEqualsLiteral(nameStr, "PromiseDebugging")) {
952       PromiseDebugging = true;
953     } else if (JS_LinearStringEqualsLiteral(nameStr, "Range")) {
954       Range = true;
955     } else if (JS_LinearStringEqualsLiteral(nameStr, "Selection")) {
956       Selection = true;
957     } else if (JS_LinearStringEqualsLiteral(nameStr, "TextDecoder")) {
958       TextDecoder = true;
959     } else if (JS_LinearStringEqualsLiteral(nameStr, "TextEncoder")) {
960       TextEncoder = true;
961     } else if (JS_LinearStringEqualsLiteral(nameStr, "URL")) {
962       URL = true;
963     } else if (JS_LinearStringEqualsLiteral(nameStr, "URLSearchParams")) {
964       URLSearchParams = true;
965     } else if (JS_LinearStringEqualsLiteral(nameStr, "XMLHttpRequest")) {
966       XMLHttpRequest = true;
967     } else if (JS_LinearStringEqualsLiteral(nameStr, "WebSocket")) {
968       WebSocket = true;
969     } else if (JS_LinearStringEqualsLiteral(nameStr, "Window")) {
970       Window = true;
971     } else if (JS_LinearStringEqualsLiteral(nameStr, "XMLSerializer")) {
972       XMLSerializer = true;
973 #ifdef MOZ_DOM_STREAMS
974     } else if (JS_LinearStringEqualsLiteral(nameStr, "ReadableStream")) {
975       ReadableStream = true;
976 #endif
977     } else if (JS_LinearStringEqualsLiteral(nameStr, "atob")) {
978       atob = true;
979     } else if (JS_LinearStringEqualsLiteral(nameStr, "btoa")) {
980       btoa = true;
981     } else if (JS_LinearStringEqualsLiteral(nameStr, "caches")) {
982       caches = true;
983     } else if (JS_LinearStringEqualsLiteral(nameStr, "crypto")) {
984       crypto = true;
985     } else if (JS_LinearStringEqualsLiteral(nameStr, "fetch")) {
986       fetch = true;
987     } else if (JS_LinearStringEqualsLiteral(nameStr, "storage")) {
988       storage = true;
989     } else if (JS_LinearStringEqualsLiteral(nameStr, "structuredClone")) {
990       structuredClone = true;
991     } else if (JS_LinearStringEqualsLiteral(nameStr, "indexedDB")) {
992       indexedDB = true;
993     } else if (JS_LinearStringEqualsLiteral(nameStr, "isSecureContext")) {
994       isSecureContext = true;
995 #ifdef MOZ_WEBRTC
996     } else if (JS_LinearStringEqualsLiteral(nameStr, "rtcIdentityProvider")) {
997       rtcIdentityProvider = true;
998 #endif
999     } else {
1000       RootedString nameStr(cx, nameValue.toString());
1001       JS::UniqueChars name = JS_EncodeStringToUTF8(cx, nameStr);
1002       if (!name) {
1003         return false;
1004       }
1005 
1006       JS_ReportErrorUTF8(cx, "Unknown property name: %s", name.get());
1007       return false;
1008     }
1009   }
1010   return true;
1011 }
1012 
Define(JSContext * cx,JS::HandleObject obj)1013 bool xpc::GlobalProperties::Define(JSContext* cx, JS::HandleObject obj) {
1014   MOZ_ASSERT(js::GetContextCompartment(cx) == JS::GetCompartment(obj));
1015   // Properties will be exposed to System automatically but not to Sandboxes
1016   // if |[Exposed=System]| is specified.
1017   // This function holds common properties not exposed automatically but able
1018   // to be requested either in |Cu.importGlobalProperties| or
1019   // |wantGlobalProperties| of a sandbox.
1020   if (AbortController &&
1021       !dom::AbortController_Binding::GetConstructorObject(cx)) {
1022     return false;
1023   }
1024 
1025   if (Blob && !dom::Blob_Binding::GetConstructorObject(cx)) return false;
1026 
1027   if (ChromeUtils && !dom::ChromeUtils_Binding::GetConstructorObject(cx)) {
1028     return false;
1029   }
1030 
1031   if (CSS && !dom::CSS_Binding::GetConstructorObject(cx)) {
1032     return false;
1033   }
1034 
1035   if (CSSRule && !dom::CSSRule_Binding::GetConstructorObject(cx)) {
1036     return false;
1037   }
1038 
1039   if (Directory && !dom::Directory_Binding::GetConstructorObject(cx))
1040     return false;
1041 
1042   if (Document && !dom::Document_Binding::GetConstructorObject(cx)) {
1043     return false;
1044   }
1045 
1046   if (DOMException && !dom::DOMException_Binding::GetConstructorObject(cx)) {
1047     return false;
1048   }
1049 
1050   if (DOMParser && !dom::DOMParser_Binding::GetConstructorObject(cx)) {
1051     return false;
1052   }
1053 
1054   if (DOMTokenList && !dom::DOMTokenList_Binding::GetConstructorObject(cx)) {
1055     return false;
1056   }
1057 
1058   if (Element && !dom::Element_Binding::GetConstructorObject(cx)) return false;
1059 
1060   if (Event && !dom::Event_Binding::GetConstructorObject(cx)) return false;
1061 
1062   if (File && !dom::File_Binding::GetConstructorObject(cx)) return false;
1063 
1064   if (FileReader && !dom::FileReader_Binding::GetConstructorObject(cx)) {
1065     return false;
1066   }
1067 
1068   if (FormData && !dom::FormData_Binding::GetConstructorObject(cx))
1069     return false;
1070 
1071   if (Headers && !dom::Headers_Binding::GetConstructorObject(cx)) {
1072     return false;
1073   }
1074 
1075   if (IOUtils && !dom::IOUtils_Binding::GetConstructorObject(cx)) {
1076     return false;
1077   }
1078 
1079   if (InspectorUtils && !dom::InspectorUtils_Binding::GetConstructorObject(cx))
1080     return false;
1081 
1082   if (MessageChannel &&
1083       (!dom::MessageChannel_Binding::GetConstructorObject(cx) ||
1084        !dom::MessagePort_Binding::GetConstructorObject(cx)))
1085     return false;
1086 
1087   if (Node && !dom::Node_Binding::GetConstructorObject(cx)) {
1088     return false;
1089   }
1090 
1091   if (NodeFilter && !dom::NodeFilter_Binding::GetConstructorObject(cx)) {
1092     return false;
1093   }
1094 
1095   if (PathUtils && !dom::PathUtils_Binding::GetConstructorObject(cx)) {
1096     return false;
1097   }
1098 
1099   if (Performance && !dom::Performance_Binding::GetConstructorObject(cx)) {
1100     return false;
1101   }
1102 
1103   if (PromiseDebugging &&
1104       !dom::PromiseDebugging_Binding::GetConstructorObject(cx)) {
1105     return false;
1106   }
1107 
1108   if (Range && !dom::Range_Binding::GetConstructorObject(cx)) {
1109     return false;
1110   }
1111 
1112   if (Selection && !dom::Selection_Binding::GetConstructorObject(cx)) {
1113     return false;
1114   }
1115 
1116   if (TextDecoder && !dom::TextDecoder_Binding::GetConstructorObject(cx))
1117     return false;
1118 
1119   if (TextEncoder && !dom::TextEncoder_Binding::GetConstructorObject(cx))
1120     return false;
1121 
1122   if (URL && !dom::URL_Binding::GetConstructorObject(cx)) return false;
1123 
1124   if (URLSearchParams &&
1125       !dom::URLSearchParams_Binding::GetConstructorObject(cx))
1126     return false;
1127 
1128   if (XMLHttpRequest && !dom::XMLHttpRequest_Binding::GetConstructorObject(cx))
1129     return false;
1130 
1131   if (WebSocket && !dom::WebSocket_Binding::GetConstructorObject(cx))
1132     return false;
1133 
1134   if (Window && !dom::Window_Binding::GetConstructorObject(cx)) return false;
1135 
1136   if (XMLSerializer && !dom::XMLSerializer_Binding::GetConstructorObject(cx))
1137     return false;
1138 
1139 #ifdef MOZ_DOM_STREAMS
1140   if (ReadableStream && !dom::ReadableStream_Binding::GetConstructorObject(cx))
1141     return false;
1142 #endif
1143 
1144   if (atob && !JS_DefineFunction(cx, obj, "atob", Atob, 1, 0)) return false;
1145 
1146   if (btoa && !JS_DefineFunction(cx, obj, "btoa", Btoa, 1, 0)) return false;
1147 
1148   if (caches && !dom::cache::CacheStorage::DefineCaches(cx, obj)) {
1149     return false;
1150   }
1151 
1152   if (crypto && !SandboxCreateCrypto(cx, obj)) {
1153     return false;
1154   }
1155 
1156   if (fetch && !SandboxCreateFetch(cx, obj)) {
1157     return false;
1158   }
1159 
1160   if (storage && !SandboxCreateStorage(cx, obj)) {
1161     return false;
1162   }
1163 
1164   if (structuredClone && !SandboxCreateStructuredClone(cx, obj)) {
1165     return false;
1166   }
1167 
1168   // Note that isSecureContext here doesn't mean the context is actually secure
1169   // - just that the caller wants the property defined
1170   if (isSecureContext) {
1171     bool hasSecureContext = IsSecureContextOrObjectIsFromSecureContext(cx, obj);
1172     JS::Rooted<JS::Value> secureJsValue(cx, JS::BooleanValue(hasSecureContext));
1173     return JS_DefineProperty(cx, obj, "isSecureContext", secureJsValue,
1174                              JSPROP_ENUMERATE);
1175   }
1176 
1177 #ifdef MOZ_WEBRTC
1178   if (rtcIdentityProvider && !SandboxCreateRTCIdentityProvider(cx, obj)) {
1179     return false;
1180   }
1181 #endif
1182 
1183   return true;
1184 }
1185 
DefineInXPCComponents(JSContext * cx,JS::HandleObject obj)1186 bool xpc::GlobalProperties::DefineInXPCComponents(JSContext* cx,
1187                                                   JS::HandleObject obj) {
1188   if (indexedDB && !IndexedDatabaseManager::DefineIndexedDB(cx, obj))
1189     return false;
1190 
1191   return Define(cx, obj);
1192 }
1193 
DefineInSandbox(JSContext * cx,JS::HandleObject obj)1194 bool xpc::GlobalProperties::DefineInSandbox(JSContext* cx,
1195                                             JS::HandleObject obj) {
1196   MOZ_ASSERT(IsSandbox(obj));
1197   MOZ_ASSERT(js::GetContextCompartment(cx) == JS::GetCompartment(obj));
1198 
1199   if (indexedDB && !(IndexedDatabaseManager::ResolveSandboxBinding(cx) &&
1200                      IndexedDatabaseManager::DefineIndexedDB(cx, obj)))
1201     return false;
1202 
1203   return Define(cx, obj);
1204 }
1205 
1206 /**
1207  * If enabled, apply the extension base CSP, then apply the
1208  * content script CSP which will either be a default or one
1209  * provided by the extension in its manifest.
1210  */
ApplyAddonContentScriptCSP(nsISupports * prinOrSop)1211 nsresult ApplyAddonContentScriptCSP(nsISupports* prinOrSop) {
1212   nsCOMPtr<nsIPrincipal> principal = do_QueryInterface(prinOrSop);
1213   if (!principal) {
1214     return NS_OK;
1215   }
1216 
1217   auto* basePrin = BasePrincipal::Cast(principal);
1218   // We only get an addonPolicy if the principal is an
1219   // expanded principal with an extension principal in it.
1220   auto* addonPolicy = basePrin->ContentScriptAddonPolicy();
1221   if (!addonPolicy) {
1222     return NS_OK;
1223   }
1224   // For backwards compatibility, content scripts have no CSP
1225   // in manifest v2.  Only apply content script CSP to V3 or later.
1226   if (addonPolicy->ManifestVersion() < 3) {
1227     return NS_OK;
1228   }
1229 
1230   nsString url;
1231   MOZ_TRY_VAR(url, addonPolicy->GetURL(u""_ns));
1232 
1233   nsCOMPtr<nsIURI> selfURI;
1234   MOZ_TRY(NS_NewURI(getter_AddRefs(selfURI), url));
1235 
1236   const nsAString& baseCSP = addonPolicy->BaseCSP();
1237 
1238   // If we got here, we're definitly an expanded principal.
1239   auto expanded = basePrin->As<ExpandedPrincipal>();
1240   nsCOMPtr<nsIContentSecurityPolicy> csp;
1241 
1242 #ifdef MOZ_DEBUG
1243   // Bug 1548468: Move CSP off ExpandedPrincipal
1244   expanded->GetCsp(getter_AddRefs(csp));
1245   if (csp) {
1246     uint32_t count = 0;
1247     csp->GetPolicyCount(&count);
1248     if (count > 0) {
1249       // Ensure that the policy was not already added.
1250       nsAutoString parsedPolicyStr;
1251       for (uint32_t i = 0; i < count; i++) {
1252         csp->GetPolicyString(i, parsedPolicyStr);
1253         MOZ_ASSERT(!parsedPolicyStr.Equals(baseCSP));
1254       }
1255     }
1256   }
1257 #endif
1258 
1259   // Create a clone of the expanded principal to be used for the call to
1260   // SetRequestContextWithPrincipal (to prevent the CSP and expanded
1261   // principal instances to keep each other alive indefinitely, see
1262   // Bug 1741600).
1263   //
1264   // This may not be necessary anymore once Bug 1548468 will move CSP
1265   // off ExpandedPrincipal.
1266   RefPtr<ExpandedPrincipal> clonedPrincipal = ExpandedPrincipal::Create(
1267       expanded->AllowList(), expanded->OriginAttributesRef());
1268   MOZ_ASSERT(clonedPrincipal);
1269 
1270   csp = new nsCSPContext();
1271   MOZ_TRY(
1272       csp->SetRequestContextWithPrincipal(clonedPrincipal, selfURI, u""_ns, 0));
1273 
1274   MOZ_TRY(csp->AppendPolicy(baseCSP, false, false));
1275 
1276   expanded->SetCsp(csp);
1277   return NS_OK;
1278 }
1279 
CreateSandboxObject(JSContext * cx,MutableHandleValue vp,nsISupports * prinOrSop,SandboxOptions & options)1280 nsresult xpc::CreateSandboxObject(JSContext* cx, MutableHandleValue vp,
1281                                   nsISupports* prinOrSop,
1282                                   SandboxOptions& options) {
1283   // Create the sandbox global object
1284   nsCOMPtr<nsIPrincipal> principal = do_QueryInterface(prinOrSop);
1285   nsCOMPtr<nsIGlobalObject> obj = do_QueryInterface(prinOrSop);
1286   if (obj) {
1287     nsGlobalWindowInner* window =
1288         WindowOrNull(js::UncheckedUnwrap(obj->GetGlobalJSObject(), false));
1289     // If we have a secure context window inherit from it's parent
1290     if (window && window->IsSecureContext()) {
1291       options.forceSecureContext = true;
1292     }
1293   }
1294   if (!principal) {
1295     nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(prinOrSop);
1296     if (sop) {
1297       principal = sop->GetPrincipal();
1298     } else {
1299       RefPtr<NullPrincipal> nullPrin =
1300           NullPrincipal::CreateWithoutOriginAttributes();
1301       principal = nullPrin;
1302     }
1303   }
1304   MOZ_ASSERT(principal);
1305 
1306   JS::RealmOptions realmOptions;
1307 
1308   auto& creationOptions = realmOptions.creationOptions();
1309 
1310   bool isSystemPrincipal = principal->IsSystemPrincipal();
1311 
1312   if (isSystemPrincipal) {
1313     options.forceSecureContext = true;
1314   }
1315 
1316   // If we are able to see [SecureContext] API code
1317   if (options.forceSecureContext) {
1318     creationOptions.setSecureContext(true);
1319   }
1320 
1321   xpc::SetPrefableRealmOptions(realmOptions);
1322   if (options.sameZoneAs) {
1323     creationOptions.setNewCompartmentInExistingZone(
1324         js::UncheckedUnwrap(options.sameZoneAs));
1325   } else if (options.freshZone) {
1326     creationOptions.setNewCompartmentAndZone();
1327   } else if (isSystemPrincipal && !options.invisibleToDebugger &&
1328              !options.freshCompartment) {
1329     // Use a shared system compartment for system-principal sandboxes that don't
1330     // require invisibleToDebugger (this is a compartment property, see bug
1331     // 1482215).
1332     creationOptions.setExistingCompartment(xpc::PrivilegedJunkScope());
1333   } else {
1334     creationOptions.setNewCompartmentInSystemZone();
1335   }
1336 
1337   creationOptions.setInvisibleToDebugger(options.invisibleToDebugger)
1338       .setTrace(TraceXPCGlobal);
1339 
1340   realmOptions.behaviors().setDiscardSource(options.discardSource);
1341 
1342   if (isSystemPrincipal) {
1343     realmOptions.behaviors().setClampAndJitterTime(false);
1344   }
1345 
1346   const JSClass* clasp = &SandboxClass;
1347 
1348   RootedObject sandbox(
1349       cx, xpc::CreateGlobalObject(cx, clasp, principal, realmOptions));
1350   if (!sandbox) {
1351     return NS_ERROR_FAILURE;
1352   }
1353 
1354   // Use exclusive expandos for non-system-principal sandboxes.
1355   bool hasExclusiveExpandos = !isSystemPrincipal;
1356 
1357   // Set up the wantXrays flag, which indicates whether xrays are desired even
1358   // for same-origin access.
1359   //
1360   // This flag has historically been ignored for chrome sandboxes due to
1361   // quirks in the wrapping implementation that have now been removed. Indeed,
1362   // same-origin Xrays for chrome->chrome access seems a bit superfluous.
1363   // Arguably we should just flip the default for chrome and still honor the
1364   // flag, but such a change would break code in subtle ways for minimal
1365   // benefit. So we just switch it off here.
1366   bool wantXrays = AccessCheck::isChrome(sandbox) ? false : options.wantXrays;
1367 
1368   if (creationOptions.compartmentSpecifier() ==
1369       JS::CompartmentSpecifier::ExistingCompartment) {
1370     // Make sure the compartment we're reusing has flags that match what we
1371     // would set on a new compartment.
1372     CompartmentPrivate* priv = CompartmentPrivate::Get(sandbox);
1373     MOZ_RELEASE_ASSERT(priv->allowWaivers == options.allowWaivers);
1374     MOZ_RELEASE_ASSERT(priv->isWebExtensionContentScript ==
1375                        options.isWebExtensionContentScript);
1376     MOZ_RELEASE_ASSERT(priv->isUAWidgetCompartment == options.isUAWidgetScope);
1377     MOZ_RELEASE_ASSERT(priv->hasExclusiveExpandos == hasExclusiveExpandos);
1378     MOZ_RELEASE_ASSERT(priv->wantXrays == wantXrays);
1379   } else {
1380     CompartmentPrivate* priv = CompartmentPrivate::Get(sandbox);
1381     priv->allowWaivers = options.allowWaivers;
1382     priv->isWebExtensionContentScript = options.isWebExtensionContentScript;
1383     priv->isUAWidgetCompartment = options.isUAWidgetScope;
1384     priv->hasExclusiveExpandos = hasExclusiveExpandos;
1385     priv->wantXrays = wantXrays;
1386   }
1387 
1388   {
1389     JSAutoRealm ar(cx, sandbox);
1390 
1391     // This creates a SandboxPrivate and passes ownership of it to |sandbox|.
1392     SandboxPrivate::Create(principal, sandbox);
1393 
1394     // Ensure |Object.prototype| is instantiated before prototype-
1395     // splicing below.
1396     if (!JS::GetRealmObjectPrototype(cx)) {
1397       return NS_ERROR_XPC_UNEXPECTED;
1398     }
1399 
1400     if (options.proto) {
1401       bool ok = JS_WrapObject(cx, &options.proto);
1402       if (!ok) {
1403         return NS_ERROR_XPC_UNEXPECTED;
1404       }
1405 
1406       // Now check what sort of thing we've got in |proto|, and figure out
1407       // if we need a SandboxProxyHandler.
1408       //
1409       // Note that, in the case of a window, we can't require that the
1410       // Sandbox subsumes the prototype, because we have to hold our
1411       // reference to it via an outer window, and the window may navigate
1412       // at any time. So we have to handle that case separately.
1413       bool useSandboxProxy =
1414           !!WindowOrNull(js::UncheckedUnwrap(options.proto, false));
1415       if (!useSandboxProxy) {
1416         // We just wrapped options.proto into the compartment of whatever Realm
1417         // is on the cx, so use that same realm for the CheckedUnwrapDynamic
1418         // call.
1419         JSObject* unwrappedProto =
1420             js::CheckedUnwrapDynamic(options.proto, cx, false);
1421         if (!unwrappedProto) {
1422           JS_ReportErrorASCII(cx, "Sandbox must subsume sandboxPrototype");
1423           return NS_ERROR_INVALID_ARG;
1424         }
1425         const JSClass* unwrappedClass = JS::GetClass(unwrappedProto);
1426         useSandboxProxy = IS_WN_CLASS(unwrappedClass) ||
1427                           mozilla::dom::IsDOMClass(unwrappedClass);
1428       }
1429 
1430       if (useSandboxProxy) {
1431         // Wrap it up in a proxy that will do the right thing in terms
1432         // of this-binding for methods.
1433         RootedValue priv(cx, ObjectValue(*options.proto));
1434         options.proto =
1435             js::NewProxyObject(cx, &sandboxProxyHandler, priv, nullptr);
1436         if (!options.proto) {
1437           return NS_ERROR_OUT_OF_MEMORY;
1438         }
1439       }
1440 
1441       ok = JS_SetPrototype(cx, sandbox, options.proto);
1442       if (!ok) {
1443         return NS_ERROR_XPC_UNEXPECTED;
1444       }
1445     }
1446 
1447     bool allowComponents = principal->IsSystemPrincipal();
1448     if (options.wantComponents && allowComponents &&
1449         !ObjectScope(sandbox)->AttachComponentsObject(cx))
1450       return NS_ERROR_XPC_UNEXPECTED;
1451 
1452     if (!XPCNativeWrapper::AttachNewConstructorObject(cx, sandbox)) {
1453       return NS_ERROR_XPC_UNEXPECTED;
1454     }
1455 
1456     if (!JS_DefineFunctions(cx, sandbox, SandboxFunctions)) {
1457       return NS_ERROR_XPC_UNEXPECTED;
1458     }
1459 
1460     if (options.wantExportHelpers &&
1461         (!JS_DefineFunction(cx, sandbox, "exportFunction",
1462                             SandboxExportFunction, 3, 0) ||
1463          !JS_DefineFunction(cx, sandbox, "createObjectIn",
1464                             SandboxCreateObjectIn, 2, 0) ||
1465          !JS_DefineFunction(cx, sandbox, "cloneInto", SandboxCloneInto, 3, 0) ||
1466          !JS_DefineFunction(cx, sandbox, "isProxy", SandboxIsProxy, 1, 0)))
1467       return NS_ERROR_XPC_UNEXPECTED;
1468 
1469     if (!options.globalProperties.DefineInSandbox(cx, sandbox)) {
1470       return NS_ERROR_XPC_UNEXPECTED;
1471     }
1472   }
1473 
1474   // We handle the case where the context isn't in a compartment for the
1475   // benefit of UnprivilegedJunkScope().
1476   vp.setObject(*sandbox);
1477   if (js::GetContextCompartment(cx) && !JS_WrapValue(cx, vp)) {
1478     return NS_ERROR_UNEXPECTED;
1479   }
1480 
1481   // Set the location information for the new global, so that tools like
1482   // about:memory may use that information
1483   xpc::SetLocationForGlobal(sandbox, options.sandboxName);
1484 
1485   xpc::SetSandboxMetadata(cx, sandbox, options.metadata);
1486 
1487   JSAutoRealm ar(cx, sandbox);
1488   JS_FireOnNewGlobalObject(cx, sandbox);
1489 
1490   return NS_OK;
1491 }
1492 
1493 NS_IMETHODIMP
Call(nsIXPConnectWrappedNative * wrapper,JSContext * cx,JSObject * objArg,const CallArgs & args,bool * _retval)1494 nsXPCComponents_utils_Sandbox::Call(nsIXPConnectWrappedNative* wrapper,
1495                                     JSContext* cx, JSObject* objArg,
1496                                     const CallArgs& args, bool* _retval) {
1497   RootedObject obj(cx, objArg);
1498   return CallOrConstruct(wrapper, cx, obj, args, _retval);
1499 }
1500 
1501 NS_IMETHODIMP
Construct(nsIXPConnectWrappedNative * wrapper,JSContext * cx,JSObject * objArg,const CallArgs & args,bool * _retval)1502 nsXPCComponents_utils_Sandbox::Construct(nsIXPConnectWrappedNative* wrapper,
1503                                          JSContext* cx, JSObject* objArg,
1504                                          const CallArgs& args, bool* _retval) {
1505   RootedObject obj(cx, objArg);
1506   return CallOrConstruct(wrapper, cx, obj, args, _retval);
1507 }
1508 
1509 /*
1510  * For sandbox constructor the first argument can be a URI string in which case
1511  * we use the related Content Principal for the sandbox.
1512  */
ParsePrincipal(JSContext * cx,HandleString contentUrl,const OriginAttributes & aAttrs,nsIPrincipal ** principal)1513 bool ParsePrincipal(JSContext* cx, HandleString contentUrl,
1514                     const OriginAttributes& aAttrs, nsIPrincipal** principal) {
1515   MOZ_ASSERT(principal);
1516   MOZ_ASSERT(contentUrl);
1517   nsCOMPtr<nsIURI> uri;
1518   nsAutoJSString contentStr;
1519   NS_ENSURE_TRUE(contentStr.init(cx, contentUrl), false);
1520   nsresult rv = NS_NewURI(getter_AddRefs(uri), contentStr);
1521   if (NS_FAILED(rv)) {
1522     JS_ReportErrorASCII(cx, "Creating URI from string failed");
1523     return false;
1524   }
1525 
1526   // We could allow passing in the app-id and browser-element info to the
1527   // sandbox constructor. But creating a sandbox based on a string is a
1528   // deprecated API so no need to add features to it.
1529   nsCOMPtr<nsIPrincipal> prin =
1530       BasePrincipal::CreateContentPrincipal(uri, aAttrs);
1531   prin.forget(principal);
1532 
1533   if (!*principal) {
1534     JS_ReportErrorASCII(cx, "Creating Principal from URI failed");
1535     return false;
1536   }
1537   return true;
1538 }
1539 
1540 /*
1541  * For sandbox constructor the first argument can be a principal object or
1542  * a script object principal (Document, Window).
1543  */
GetPrincipalOrSOP(JSContext * cx,HandleObject from,nsISupports ** out)1544 static bool GetPrincipalOrSOP(JSContext* cx, HandleObject from,
1545                               nsISupports** out) {
1546   MOZ_ASSERT(out);
1547   *out = nullptr;
1548 
1549   // We might have a Window here, so need ReflectorToISupportsDynamic
1550   nsCOMPtr<nsISupports> native = ReflectorToISupportsDynamic(from, cx);
1551 
1552   if (nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(native)) {
1553     sop.forget(out);
1554     return true;
1555   }
1556 
1557   nsCOMPtr<nsIPrincipal> principal = do_QueryInterface(native);
1558   principal.forget(out);
1559   NS_ENSURE_TRUE(*out, false);
1560 
1561   return true;
1562 }
1563 
1564 /*
1565  * The first parameter of the sandbox constructor might be an array of
1566  * principals, either in string format or actual objects (see GetPrincipalOrSOP)
1567  */
GetExpandedPrincipal(JSContext * cx,HandleObject arrayObj,const SandboxOptions & options,nsIExpandedPrincipal ** out)1568 static bool GetExpandedPrincipal(JSContext* cx, HandleObject arrayObj,
1569                                  const SandboxOptions& options,
1570                                  nsIExpandedPrincipal** out) {
1571   MOZ_ASSERT(out);
1572   uint32_t length;
1573 
1574   if (!JS::GetArrayLength(cx, arrayObj, &length)) {
1575     return false;
1576   }
1577   if (!length) {
1578     // We need a whitelist of principals or uri strings to create an
1579     // expanded principal, if we got an empty array or something else
1580     // report error.
1581     JS_ReportErrorASCII(cx, "Expected an array of URI strings");
1582     return false;
1583   }
1584 
1585   nsTArray<nsCOMPtr<nsIPrincipal>> allowedDomains(length);
1586   allowedDomains.SetLength(length);
1587 
1588   // If an originAttributes option has been specified, we will use that as the
1589   // OriginAttribute of all of the string arguments passed to this function.
1590   // Otherwise, we will use the OriginAttributes of a principal or SOP object
1591   // in the array, if any.  If no such object is present, and all we have are
1592   // strings, then we will use a default OriginAttribute.
1593   // Otherwise, we will use the origin attributes of the passed object(s). If
1594   // more than one object is specified, we ensure that the OAs match.
1595   Maybe<OriginAttributes> attrs;
1596   if (options.originAttributes) {
1597     attrs.emplace();
1598     JS::RootedValue val(cx, JS::ObjectValue(*options.originAttributes));
1599     if (!attrs->Init(cx, val)) {
1600       // The originAttributes option, if specified, must be valid!
1601       JS_ReportErrorASCII(cx, "Expected a valid OriginAttributes object");
1602       return false;
1603     }
1604   }
1605 
1606   // Now we go over the array in two passes.  In the first pass, we ignore
1607   // strings, and only process objects.  Assuming that no originAttributes
1608   // option has been passed, if we encounter a principal or SOP object, we
1609   // grab its OA and save it if it's the first OA encountered, otherwise
1610   // check to make sure that it is the same as the OA found before.
1611   // In the second pass, we ignore objects, and use the OA found in pass 0
1612   // (or the previously computed OA if we have obtained it from the options)
1613   // to construct content principals.
1614   //
1615   // The effective OA selected above will also be set as the OA of the
1616   // expanded principal object.
1617 
1618   // First pass:
1619   for (uint32_t i = 0; i < length; ++i) {
1620     RootedValue allowed(cx);
1621     if (!JS_GetElement(cx, arrayObj, i, &allowed)) {
1622       return false;
1623     }
1624 
1625     nsCOMPtr<nsIPrincipal> principal;
1626     if (allowed.isObject()) {
1627       // In case of object let's see if it's a Principal or a
1628       // ScriptObjectPrincipal.
1629       nsCOMPtr<nsISupports> prinOrSop;
1630       RootedObject obj(cx, &allowed.toObject());
1631       if (!GetPrincipalOrSOP(cx, obj, getter_AddRefs(prinOrSop))) {
1632         return false;
1633       }
1634 
1635       nsCOMPtr<nsIScriptObjectPrincipal> sop(do_QueryInterface(prinOrSop));
1636       principal = do_QueryInterface(prinOrSop);
1637       if (sop) {
1638         principal = sop->GetPrincipal();
1639       }
1640       NS_ENSURE_TRUE(principal, false);
1641 
1642       if (!options.originAttributes) {
1643         const OriginAttributes prinAttrs = principal->OriginAttributesRef();
1644         if (attrs.isNothing()) {
1645           attrs.emplace(prinAttrs);
1646         } else if (prinAttrs != attrs.ref()) {
1647           // If attrs is from a previously encountered principal in the
1648           // array, we need to ensure that it matches the OA of the
1649           // principal we have here.
1650           // If attrs comes from OriginAttributes, we don't need
1651           // this check.
1652           return false;
1653         }
1654       }
1655 
1656       // We do not allow ExpandedPrincipals to contain any system principals.
1657       bool isSystem = principal->IsSystemPrincipal();
1658       if (isSystem) {
1659         JS_ReportErrorASCII(
1660             cx, "System principal is not allowed in an expanded principal");
1661         return false;
1662       }
1663       allowedDomains[i] = principal;
1664     } else if (allowed.isString()) {
1665       // Skip any string arguments - we handle them in the next pass.
1666     } else {
1667       // Don't know what this is.
1668       return false;
1669     }
1670   }
1671 
1672   if (attrs.isNothing()) {
1673     // If no OriginAttributes was found in the first pass, fall back to a
1674     // default one.
1675     attrs.emplace();
1676   }
1677 
1678   // Second pass:
1679   for (uint32_t i = 0; i < length; ++i) {
1680     RootedValue allowed(cx);
1681     if (!JS_GetElement(cx, arrayObj, i, &allowed)) {
1682       return false;
1683     }
1684 
1685     nsCOMPtr<nsIPrincipal> principal;
1686     if (allowed.isString()) {
1687       // In case of string let's try to fetch a content principal from it.
1688       RootedString str(cx, allowed.toString());
1689 
1690       // attrs here is either a default OriginAttributes in case the
1691       // originAttributes option isn't specified, and no object in the array
1692       // provides a principal.  Otherwise it's either the forced principal, or
1693       // the principal found before, so we can use it here.
1694       if (!ParsePrincipal(cx, str, attrs.ref(), getter_AddRefs(principal))) {
1695         return false;
1696       }
1697       NS_ENSURE_TRUE(principal, false);
1698       allowedDomains[i] = principal;
1699     } else {
1700       MOZ_ASSERT(allowed.isObject());
1701     }
1702   }
1703 
1704   RefPtr<ExpandedPrincipal> result =
1705       ExpandedPrincipal::Create(allowedDomains, attrs.ref());
1706   result.forget(out);
1707   return true;
1708 }
1709 
1710 /*
1711  * Helper that tries to get a property from the options object.
1712  */
ParseValue(const char * name,MutableHandleValue prop,bool * aFound)1713 bool OptionsBase::ParseValue(const char* name, MutableHandleValue prop,
1714                              bool* aFound) {
1715   bool found;
1716   bool ok = JS_HasProperty(mCx, mObject, name, &found);
1717   NS_ENSURE_TRUE(ok, false);
1718 
1719   if (aFound) {
1720     *aFound = found;
1721   }
1722 
1723   if (!found) {
1724     return true;
1725   }
1726 
1727   return JS_GetProperty(mCx, mObject, name, prop);
1728 }
1729 
1730 /*
1731  * Helper that tries to get a boolean property from the options object.
1732  */
ParseBoolean(const char * name,bool * prop)1733 bool OptionsBase::ParseBoolean(const char* name, bool* prop) {
1734   MOZ_ASSERT(prop);
1735   RootedValue value(mCx);
1736   bool found;
1737   bool ok = ParseValue(name, &value, &found);
1738   NS_ENSURE_TRUE(ok, false);
1739 
1740   if (!found) {
1741     return true;
1742   }
1743 
1744   if (!value.isBoolean()) {
1745     JS_ReportErrorASCII(mCx, "Expected a boolean value for property %s", name);
1746     return false;
1747   }
1748 
1749   *prop = value.toBoolean();
1750   return true;
1751 }
1752 
1753 /*
1754  * Helper that tries to get an object property from the options object.
1755  */
ParseObject(const char * name,MutableHandleObject prop)1756 bool OptionsBase::ParseObject(const char* name, MutableHandleObject prop) {
1757   RootedValue value(mCx);
1758   bool found;
1759   bool ok = ParseValue(name, &value, &found);
1760   NS_ENSURE_TRUE(ok, false);
1761 
1762   if (!found) {
1763     return true;
1764   }
1765 
1766   if (!value.isObject()) {
1767     JS_ReportErrorASCII(mCx, "Expected an object value for property %s", name);
1768     return false;
1769   }
1770   prop.set(&value.toObject());
1771   return true;
1772 }
1773 
1774 /*
1775  * Helper that tries to get an object property from the options object.
1776  */
ParseJSString(const char * name,MutableHandleString prop)1777 bool OptionsBase::ParseJSString(const char* name, MutableHandleString prop) {
1778   RootedValue value(mCx);
1779   bool found;
1780   bool ok = ParseValue(name, &value, &found);
1781   NS_ENSURE_TRUE(ok, false);
1782 
1783   if (!found) {
1784     return true;
1785   }
1786 
1787   if (!value.isString()) {
1788     JS_ReportErrorASCII(mCx, "Expected a string value for property %s", name);
1789     return false;
1790   }
1791   prop.set(value.toString());
1792   return true;
1793 }
1794 
1795 /*
1796  * Helper that tries to get a string property from the options object.
1797  */
ParseString(const char * name,nsCString & prop)1798 bool OptionsBase::ParseString(const char* name, nsCString& prop) {
1799   RootedValue value(mCx);
1800   bool found;
1801   bool ok = ParseValue(name, &value, &found);
1802   NS_ENSURE_TRUE(ok, false);
1803 
1804   if (!found) {
1805     return true;
1806   }
1807 
1808   if (!value.isString()) {
1809     JS_ReportErrorASCII(mCx, "Expected a string value for property %s", name);
1810     return false;
1811   }
1812 
1813   JS::UniqueChars tmp = JS_EncodeStringToLatin1(mCx, value.toString());
1814   NS_ENSURE_TRUE(tmp, false);
1815   prop.Assign(tmp.get(), strlen(tmp.get()));
1816   return true;
1817 }
1818 
1819 /*
1820  * Helper that tries to get a string property from the options object.
1821  */
ParseString(const char * name,nsString & prop)1822 bool OptionsBase::ParseString(const char* name, nsString& prop) {
1823   RootedValue value(mCx);
1824   bool found;
1825   bool ok = ParseValue(name, &value, &found);
1826   NS_ENSURE_TRUE(ok, false);
1827 
1828   if (!found) {
1829     return true;
1830   }
1831 
1832   if (!value.isString()) {
1833     JS_ReportErrorASCII(mCx, "Expected a string value for property %s", name);
1834     return false;
1835   }
1836 
1837   nsAutoJSString strVal;
1838   if (!strVal.init(mCx, value.toString())) {
1839     return false;
1840   }
1841 
1842   prop = strVal;
1843   return true;
1844 }
1845 
1846 /*
1847  * Helper that tries to get jsid property from the options object.
1848  */
ParseId(const char * name,MutableHandleId prop)1849 bool OptionsBase::ParseId(const char* name, MutableHandleId prop) {
1850   RootedValue value(mCx);
1851   bool found;
1852   bool ok = ParseValue(name, &value, &found);
1853   NS_ENSURE_TRUE(ok, false);
1854 
1855   if (!found) {
1856     return true;
1857   }
1858 
1859   return JS_ValueToId(mCx, value, prop);
1860 }
1861 
1862 /*
1863  * Helper that tries to get a uint32_t property from the options object.
1864  */
ParseUInt32(const char * name,uint32_t * prop)1865 bool OptionsBase::ParseUInt32(const char* name, uint32_t* prop) {
1866   MOZ_ASSERT(prop);
1867   RootedValue value(mCx);
1868   bool found;
1869   bool ok = ParseValue(name, &value, &found);
1870   NS_ENSURE_TRUE(ok, false);
1871 
1872   if (!found) {
1873     return true;
1874   }
1875 
1876   if (!JS::ToUint32(mCx, value, prop)) {
1877     JS_ReportErrorASCII(mCx, "Expected a uint32_t value for property %s", name);
1878     return false;
1879   }
1880 
1881   return true;
1882 }
1883 
1884 /*
1885  * Helper that tries to get a list of DOM constructors and other helpers from
1886  * the options object.
1887  */
ParseGlobalProperties()1888 bool SandboxOptions::ParseGlobalProperties() {
1889   RootedValue value(mCx);
1890   bool found;
1891   bool ok = ParseValue("wantGlobalProperties", &value, &found);
1892   NS_ENSURE_TRUE(ok, false);
1893   if (!found) {
1894     return true;
1895   }
1896 
1897   if (!value.isObject()) {
1898     JS_ReportErrorASCII(mCx,
1899                         "Expected an array value for wantGlobalProperties");
1900     return false;
1901   }
1902 
1903   RootedObject ctors(mCx, &value.toObject());
1904   bool isArray;
1905   if (!JS::IsArrayObject(mCx, ctors, &isArray)) {
1906     return false;
1907   }
1908   if (!isArray) {
1909     JS_ReportErrorASCII(mCx,
1910                         "Expected an array value for wantGlobalProperties");
1911     return false;
1912   }
1913 
1914   return globalProperties.Parse(mCx, ctors);
1915 }
1916 
1917 /*
1918  * Helper that parsing the sandbox options object (from) and sets the fields of
1919  * the incoming options struct (options).
1920  */
Parse()1921 bool SandboxOptions::Parse() {
1922   /* All option names must be ASCII-only. */
1923   bool ok = ParseObject("sandboxPrototype", &proto) &&
1924             ParseBoolean("wantXrays", &wantXrays) &&
1925             ParseBoolean("allowWaivers", &allowWaivers) &&
1926             ParseBoolean("wantComponents", &wantComponents) &&
1927             ParseBoolean("wantExportHelpers", &wantExportHelpers) &&
1928             ParseBoolean("isWebExtensionContentScript",
1929                          &isWebExtensionContentScript) &&
1930             ParseBoolean("forceSecureContext", &forceSecureContext) &&
1931             ParseString("sandboxName", sandboxName) &&
1932             ParseObject("sameZoneAs", &sameZoneAs) &&
1933             ParseBoolean("freshCompartment", &freshCompartment) &&
1934             ParseBoolean("freshZone", &freshZone) &&
1935             ParseBoolean("invisibleToDebugger", &invisibleToDebugger) &&
1936             ParseBoolean("discardSource", &discardSource) &&
1937             ParseGlobalProperties() && ParseValue("metadata", &metadata) &&
1938             ParseUInt32("userContextId", &userContextId) &&
1939             ParseObject("originAttributes", &originAttributes);
1940   if (!ok) {
1941     return false;
1942   }
1943 
1944   if (freshZone && sameZoneAs) {
1945     JS_ReportErrorASCII(mCx, "Cannot use both sameZoneAs and freshZone");
1946     return false;
1947   }
1948 
1949   return true;
1950 }
1951 
AssembleSandboxMemoryReporterName(JSContext * cx,nsCString & sandboxName)1952 static nsresult AssembleSandboxMemoryReporterName(JSContext* cx,
1953                                                   nsCString& sandboxName) {
1954   // Use a default name when the caller did not provide a sandboxName.
1955   if (sandboxName.IsEmpty()) {
1956     sandboxName = "[anonymous sandbox]"_ns;
1957   } else {
1958 #ifndef DEBUG
1959     // Adding the caller location is fairly expensive, so in non-debug
1960     // builds, only add it if we don't have an explicit sandbox name.
1961     return NS_OK;
1962 #endif
1963   }
1964 
1965   // Get the xpconnect native call context.
1966   XPCCallContext* cc = XPCJSContext::Get()->GetCallContext();
1967   NS_ENSURE_TRUE(cc, NS_ERROR_INVALID_ARG);
1968 
1969   // Get the current source info from xpc.
1970   nsCOMPtr<nsIStackFrame> frame = dom::GetCurrentJSStack();
1971 
1972   // Append the caller's location information.
1973   if (frame) {
1974     nsString location;
1975     frame->GetFilename(cx, location);
1976     int32_t lineNumber = frame->GetLineNumber(cx);
1977 
1978     sandboxName.AppendLiteral(" (from: ");
1979     sandboxName.Append(NS_ConvertUTF16toUTF8(location));
1980     sandboxName.Append(':');
1981     sandboxName.AppendInt(lineNumber);
1982     sandboxName.Append(')');
1983   }
1984 
1985   return NS_OK;
1986 }
1987 
1988 // static
CallOrConstruct(nsIXPConnectWrappedNative * wrapper,JSContext * cx,HandleObject obj,const CallArgs & args,bool * _retval)1989 nsresult nsXPCComponents_utils_Sandbox::CallOrConstruct(
1990     nsIXPConnectWrappedNative* wrapper, JSContext* cx, HandleObject obj,
1991     const CallArgs& args, bool* _retval) {
1992   if (args.length() < 1) {
1993     return ThrowAndFail(NS_ERROR_XPC_NOT_ENOUGH_ARGS, cx, _retval);
1994   }
1995 
1996   nsresult rv;
1997   bool ok = false;
1998   bool calledWithOptions = args.length() > 1;
1999   if (calledWithOptions && !args[1].isObject()) {
2000     return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval);
2001   }
2002 
2003   RootedObject optionsObject(cx,
2004                              calledWithOptions ? &args[1].toObject() : nullptr);
2005 
2006   SandboxOptions options(cx, optionsObject);
2007   if (calledWithOptions && !options.Parse()) {
2008     return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval);
2009   }
2010 
2011   // Make sure to set up principals on the sandbox before initing classes.
2012   nsCOMPtr<nsIPrincipal> principal;
2013   nsCOMPtr<nsIExpandedPrincipal> expanded;
2014   nsCOMPtr<nsISupports> prinOrSop;
2015 
2016   if (args[0].isString()) {
2017     RootedString str(cx, args[0].toString());
2018     OriginAttributes attrs;
2019     if (options.originAttributes) {
2020       JS::RootedValue val(cx, JS::ObjectValue(*options.originAttributes));
2021       if (!attrs.Init(cx, val)) {
2022         // The originAttributes option, if specified, must be valid!
2023         JS_ReportErrorASCII(cx, "Expected a valid OriginAttributes object");
2024         return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval);
2025       }
2026     }
2027     attrs.mUserContextId = options.userContextId;
2028     ok = ParsePrincipal(cx, str, attrs, getter_AddRefs(principal));
2029     prinOrSop = principal;
2030   } else if (args[0].isObject()) {
2031     RootedObject obj(cx, &args[0].toObject());
2032     bool isArray;
2033     if (!JS::IsArrayObject(cx, obj, &isArray)) {
2034       ok = false;
2035     } else if (isArray) {
2036       if (options.userContextId != 0) {
2037         // We don't support passing a userContextId with an array.
2038         ok = false;
2039       } else {
2040         ok = GetExpandedPrincipal(cx, obj, options, getter_AddRefs(expanded));
2041         prinOrSop = expanded;
2042         // If this is an addon content script we need to apply the csp.
2043         MOZ_TRY(ApplyAddonContentScriptCSP(prinOrSop));
2044       }
2045     } else {
2046       ok = GetPrincipalOrSOP(cx, obj, getter_AddRefs(prinOrSop));
2047     }
2048   } else if (args[0].isNull()) {
2049     // Null means that we just pass prinOrSop = nullptr, and get an
2050     // NullPrincipal.
2051     ok = true;
2052   }
2053 
2054   if (!ok) {
2055     return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval);
2056   }
2057 
2058   if (NS_FAILED(AssembleSandboxMemoryReporterName(cx, options.sandboxName))) {
2059     return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval);
2060   }
2061 
2062   if (options.metadata.isNullOrUndefined()) {
2063     // If the caller is running in a sandbox, inherit.
2064     RootedObject callerGlobal(cx, JS::GetScriptedCallerGlobal(cx));
2065     if (IsSandbox(callerGlobal)) {
2066       rv = GetSandboxMetadata(cx, callerGlobal, &options.metadata);
2067       if (NS_WARN_IF(NS_FAILED(rv))) {
2068         return rv;
2069       }
2070     }
2071   }
2072 
2073   rv = CreateSandboxObject(cx, args.rval(), prinOrSop, options);
2074 
2075   if (NS_FAILED(rv)) {
2076     return ThrowAndFail(rv, cx, _retval);
2077   }
2078 
2079   *_retval = true;
2080   return NS_OK;
2081 }
2082 
EvalInSandbox(JSContext * cx,HandleObject sandboxArg,const nsAString & source,const nsACString & filename,int32_t lineNo,bool enforceFilenameRestrictions,MutableHandleValue rval)2083 nsresult xpc::EvalInSandbox(JSContext* cx, HandleObject sandboxArg,
2084                             const nsAString& source, const nsACString& filename,
2085                             int32_t lineNo, bool enforceFilenameRestrictions,
2086                             MutableHandleValue rval) {
2087   JS_AbortIfWrongThread(cx);
2088   rval.set(UndefinedValue());
2089 
2090   bool waiveXray = xpc::WrapperFactory::HasWaiveXrayFlag(sandboxArg);
2091   // CheckedUnwrapStatic is fine here, since we're checking for "is it a
2092   // sandbox".
2093   RootedObject sandbox(cx, js::CheckedUnwrapStatic(sandboxArg));
2094   if (!sandbox || !IsSandbox(sandbox)) {
2095     return NS_ERROR_INVALID_ARG;
2096   }
2097 
2098   SandboxPrivate* priv = SandboxPrivate::GetPrivate(sandbox);
2099   nsIScriptObjectPrincipal* sop = priv;
2100   MOZ_ASSERT(sop, "Invalid sandbox passed");
2101   nsCOMPtr<nsIPrincipal> prin = sop->GetPrincipal();
2102   NS_ENSURE_TRUE(prin, NS_ERROR_FAILURE);
2103 
2104   nsAutoCString filenameBuf;
2105   if (!filename.IsVoid() && filename.Length() != 0) {
2106     filenameBuf.Assign(filename);
2107   } else {
2108     // Default to the spec of the principal.
2109     nsresult rv = nsJSPrincipals::get(prin)->GetScriptLocation(filenameBuf);
2110     NS_ENSURE_SUCCESS(rv, rv);
2111     lineNo = 1;
2112   }
2113 
2114   // We create a separate cx to do the sandbox evaluation. Scope it.
2115   RootedValue v(cx, UndefinedValue());
2116   RootedValue exn(cx, UndefinedValue());
2117   bool ok = true;
2118   {
2119     // We're about to evaluate script, so make an AutoEntryScript.
2120     // This is clearly Gecko-specific and not in any spec.
2121     mozilla::dom::AutoEntryScript aes(priv, "XPConnect sandbox evaluation");
2122     JSContext* sandcx = aes.cx();
2123     JSAutoRealm ar(sandcx, sandbox);
2124 
2125     JS::CompileOptions options(sandcx);
2126     options.setFileAndLine(filenameBuf.get(), lineNo);
2127     options.setSkipFilenameValidation(!enforceFilenameRestrictions);
2128     MOZ_ASSERT(JS_IsGlobalObject(sandbox));
2129 
2130     const nsPromiseFlatString& flat = PromiseFlatString(source);
2131 
2132     JS::SourceText<char16_t> buffer;
2133     ok = buffer.init(sandcx, flat.get(), flat.Length(),
2134                      JS::SourceOwnership::Borrowed) &&
2135          JS::Evaluate(sandcx, options, buffer, &v);
2136 
2137     // If the sandbox threw an exception, grab it off the context.
2138     if (aes.HasException()) {
2139       if (!aes.StealException(&exn)) {
2140         return NS_ERROR_OUT_OF_MEMORY;
2141       }
2142     }
2143   }
2144 
2145   //
2146   // Alright, we're back on the caller's cx. If an error occured, try to
2147   // wrap and set the exception. Otherwise, wrap the return value.
2148   //
2149 
2150   if (!ok) {
2151     // If we end up without an exception, it was probably due to OOM along
2152     // the way, in which case we thow. Otherwise, wrap it.
2153     if (exn.isUndefined() || !JS_WrapValue(cx, &exn)) {
2154       return NS_ERROR_OUT_OF_MEMORY;
2155     }
2156 
2157     // Set the exception on our caller's cx.
2158     JS_SetPendingException(cx, exn);
2159     return NS_ERROR_FAILURE;
2160   }
2161 
2162   // Transitively apply Xray waivers if |sb| was waived.
2163   if (waiveXray) {
2164     ok = xpc::WrapperFactory::WaiveXrayAndWrap(cx, &v);
2165   } else {
2166     ok = JS_WrapValue(cx, &v);
2167   }
2168   NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
2169 
2170   // Whew!
2171   rval.set(v);
2172   return NS_OK;
2173 }
2174 
GetSandboxMetadata(JSContext * cx,HandleObject sandbox,MutableHandleValue rval)2175 nsresult xpc::GetSandboxMetadata(JSContext* cx, HandleObject sandbox,
2176                                  MutableHandleValue rval) {
2177   MOZ_ASSERT(NS_IsMainThread());
2178   MOZ_ASSERT(IsSandbox(sandbox));
2179 
2180   RootedValue metadata(cx);
2181   {
2182     JSAutoRealm ar(cx, sandbox);
2183     metadata =
2184         JS::GetReservedSlot(sandbox, XPCONNECT_SANDBOX_CLASS_METADATA_SLOT);
2185   }
2186 
2187   if (!JS_WrapValue(cx, &metadata)) {
2188     return NS_ERROR_UNEXPECTED;
2189   }
2190 
2191   rval.set(metadata);
2192   return NS_OK;
2193 }
2194 
SetSandboxMetadata(JSContext * cx,HandleObject sandbox,HandleValue metadataArg)2195 nsresult xpc::SetSandboxMetadata(JSContext* cx, HandleObject sandbox,
2196                                  HandleValue metadataArg) {
2197   MOZ_ASSERT(NS_IsMainThread());
2198   MOZ_ASSERT(IsSandbox(sandbox));
2199 
2200   RootedValue metadata(cx);
2201 
2202   JSAutoRealm ar(cx, sandbox);
2203   if (!JS_StructuredClone(cx, metadataArg, &metadata, nullptr, nullptr)) {
2204     return NS_ERROR_UNEXPECTED;
2205   }
2206 
2207   JS_SetReservedSlot(sandbox, XPCONNECT_SANDBOX_CLASS_METADATA_SLOT, metadata);
2208 
2209   return NS_OK;
2210 }
2211