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