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 #include "WaiveXrayWrapper.h"
8 #include "FilteringWrapper.h"
9 #include "XrayWrapper.h"
10 #include "AccessCheck.h"
11 #include "XPCWrapper.h"
12 #include "ChromeObjectWrapper.h"
13 #include "WrapperFactory.h"
14 
15 #include "xpcprivate.h"
16 #include "XPCMaps.h"
17 #include "mozilla/dom/BindingUtils.h"
18 #include "jsfriendapi.h"
19 #include "js/friend/WindowProxy.h"  // js::IsWindow, js::IsWindowProxy
20 #include "js/Object.h"              // JS::GetPrivate, JS::GetCompartment
21 #include "mozilla/Likely.h"
22 #include "mozilla/dom/ScriptSettings.h"
23 #include "mozilla/dom/MaybeCrossOriginObject.h"
24 #include "nsContentUtils.h"
25 #include "nsXULAppAPI.h"
26 
27 using namespace JS;
28 using namespace js;
29 using namespace mozilla;
30 
31 namespace xpc {
32 
33 // When chrome pulls a naked property across the membrane using
34 // .wrappedJSObject, we want it to cross the membrane into the
35 // chrome compartment without automatically being wrapped into an
36 // X-ray wrapper. We achieve this by wrapping it into a special
37 // transparent wrapper in the origin (non-chrome) compartment. When
38 // an object with that special wrapper applied crosses into chrome,
39 // we know to not apply an X-ray wrapper.
40 const Wrapper XrayWaiver(WrapperFactory::WAIVE_XRAY_WRAPPER_FLAG);
41 
42 // When objects for which we waived the X-ray wrapper cross into
43 // chrome, we wrap them into a special cross-compartment wrapper
44 // that transitively extends the waiver to all properties we get
45 // off it.
46 const WaiveXrayWrapper WaiveXrayWrapper::singleton(0);
47 
IsOpaqueWrapper(JSObject * obj)48 bool WrapperFactory::IsOpaqueWrapper(JSObject* obj) {
49   return IsWrapper(obj) &&
50          Wrapper::wrapperHandler(obj) == &PermissiveXrayOpaque::singleton;
51 }
52 
IsCOW(JSObject * obj)53 bool WrapperFactory::IsCOW(JSObject* obj) {
54   return IsWrapper(obj) &&
55          Wrapper::wrapperHandler(obj) == &ChromeObjectWrapper::singleton;
56 }
57 
GetXrayWaiver(HandleObject obj)58 JSObject* WrapperFactory::GetXrayWaiver(HandleObject obj) {
59   // Object should come fully unwrapped but outerized.
60   MOZ_ASSERT(obj == UncheckedUnwrap(obj));
61   MOZ_ASSERT(!js::IsWindow(obj));
62   XPCWrappedNativeScope* scope = ObjectScope(obj);
63   MOZ_ASSERT(scope);
64 
65   if (!scope->mWaiverWrapperMap) {
66     return nullptr;
67   }
68 
69   return scope->mWaiverWrapperMap->Find(obj);
70 }
71 
CreateXrayWaiver(JSContext * cx,HandleObject obj,bool allowExisting)72 JSObject* WrapperFactory::CreateXrayWaiver(JSContext* cx, HandleObject obj,
73                                            bool allowExisting) {
74   // The caller is required to have already done a lookup, unless it's
75   // trying to replace an existing waiver.
76   // NB: This implictly performs the assertions of GetXrayWaiver.
77   MOZ_ASSERT(bool(GetXrayWaiver(obj)) == allowExisting);
78   XPCWrappedNativeScope* scope = ObjectScope(obj);
79 
80   JSAutoRealm ar(cx, obj);
81   JSObject* waiver = Wrapper::New(cx, obj, &XrayWaiver);
82   if (!waiver) {
83     return nullptr;
84   }
85 
86   // Add the new waiver to the map. It's important that we only ever have
87   // one waiver for the lifetime of the target object.
88   if (!scope->mWaiverWrapperMap) {
89     scope->mWaiverWrapperMap = mozilla::MakeUnique<JSObject2JSObjectMap>();
90   }
91   if (!scope->mWaiverWrapperMap->Add(cx, obj, waiver)) {
92     return nullptr;
93   }
94   return waiver;
95 }
96 
WaiveXray(JSContext * cx,JSObject * objArg)97 JSObject* WrapperFactory::WaiveXray(JSContext* cx, JSObject* objArg) {
98   RootedObject obj(cx, objArg);
99   obj = UncheckedUnwrap(obj);
100   MOZ_ASSERT(!js::IsWindow(obj));
101 
102   JSObject* waiver = GetXrayWaiver(obj);
103   if (!waiver) {
104     waiver = CreateXrayWaiver(cx, obj);
105   }
106   JS::AssertObjectIsNotGray(waiver);
107   return waiver;
108 }
109 
110 /* static */
AllowWaiver(JS::Compartment * target,JS::Compartment * origin)111 bool WrapperFactory::AllowWaiver(JS::Compartment* target,
112                                  JS::Compartment* origin) {
113   return CompartmentPrivate::Get(target)->allowWaivers &&
114          CompartmentOriginInfo::Subsumes(target, origin);
115 }
116 
117 /* static */
AllowWaiver(JSObject * wrapper)118 bool WrapperFactory::AllowWaiver(JSObject* wrapper) {
119   MOZ_ASSERT(js::IsCrossCompartmentWrapper(wrapper));
120   return AllowWaiver(JS::GetCompartment(wrapper),
121                      JS::GetCompartment(js::UncheckedUnwrap(wrapper)));
122 }
123 
ShouldWaiveXray(JSContext * cx,JSObject * originalObj)124 inline bool ShouldWaiveXray(JSContext* cx, JSObject* originalObj) {
125   unsigned flags;
126   (void)js::UncheckedUnwrap(originalObj, /* stopAtWindowProxy = */ true,
127                             &flags);
128 
129   // If the original object did not point through an Xray waiver, we're done.
130   if (!(flags & WrapperFactory::WAIVE_XRAY_WRAPPER_FLAG)) {
131     return false;
132   }
133 
134   // If the original object was not a cross-compartment wrapper, that means
135   // that the caller explicitly created a waiver. Preserve it so that things
136   // like WaiveXrayAndWrap work.
137   if (!(flags & Wrapper::CROSS_COMPARTMENT)) {
138     return true;
139   }
140 
141   // Otherwise, this is a case of explicitly passing a wrapper across a
142   // compartment boundary. In that case, we only want to preserve waivers
143   // in transactions between same-origin compartments.
144   JS::Compartment* oldCompartment = JS::GetCompartment(originalObj);
145   JS::Compartment* newCompartment = js::GetContextCompartment(cx);
146   bool sameOrigin = false;
147   if (OriginAttributes::IsRestrictOpenerAccessForFPI()) {
148     sameOrigin =
149         CompartmentOriginInfo::Subsumes(oldCompartment, newCompartment) &&
150         CompartmentOriginInfo::Subsumes(newCompartment, oldCompartment);
151   } else {
152     sameOrigin = CompartmentOriginInfo::SubsumesIgnoringFPD(oldCompartment,
153                                                             newCompartment) &&
154                  CompartmentOriginInfo::SubsumesIgnoringFPD(newCompartment,
155                                                             oldCompartment);
156   }
157   return sameOrigin;
158 }
159 
160 // Special handling is needed when wrapping local and remote window proxies.
161 // This function returns true if it found a window proxy and dealt with it.
MaybeWrapWindowProxy(JSContext * cx,HandleObject origObj,HandleObject obj,MutableHandleObject retObj)162 static bool MaybeWrapWindowProxy(JSContext* cx, HandleObject origObj,
163                                  HandleObject obj, MutableHandleObject retObj) {
164   bool isWindowProxy = js::IsWindowProxy(obj);
165 
166   if (!isWindowProxy &&
167       !dom::IsRemoteObjectProxy(obj, dom::prototypes::id::Window)) {
168     return false;
169   }
170 
171   dom::BrowsingContext* bc = nullptr;
172   if (isWindowProxy) {
173     nsGlobalWindowInner* win =
174         WindowOrNull(js::UncheckedUnwrap(obj, /* stopAtWindowProxy = */ false));
175     if (win && win->GetOuterWindow()) {
176       bc = win->GetOuterWindow()->GetBrowsingContext();
177     }
178     if (!bc) {
179       retObj.set(obj);
180       return true;
181     }
182   } else {
183     bc = dom::GetBrowsingContext(obj);
184     MOZ_ASSERT(bc);
185   }
186 
187   // We should only have a remote window proxy if bc is in a state where we
188   // expect remote window proxies. Otherwise, they should have been cleaned up
189   // by a call to CleanUpDanglingRemoteOuterWindowProxies().
190   MOZ_RELEASE_ASSERT(isWindowProxy || bc->CanHaveRemoteOuterProxies());
191 
192   if (bc->IsInProcess()) {
193     retObj.set(obj);
194   } else {
195     // If bc is not in process, then use a remote window proxy, whether or not
196     // obj is one already.
197     if (!dom::GetRemoteOuterWindowProxy(cx, bc, origObj, retObj)) {
198       MOZ_CRASH("GetRemoteOuterWindowProxy failed");
199     }
200   }
201 
202   return true;
203 }
204 
PrepareForWrapping(JSContext * cx,HandleObject scope,HandleObject origObj,HandleObject objArg,HandleObject objectPassedToWrap,MutableHandleObject retObj)205 void WrapperFactory::PrepareForWrapping(JSContext* cx, HandleObject scope,
206                                         HandleObject origObj,
207                                         HandleObject objArg,
208                                         HandleObject objectPassedToWrap,
209                                         MutableHandleObject retObj) {
210   // The JS engine calls ToWindowProxyIfWindow and deals with dead wrappers.
211   MOZ_ASSERT(!js::IsWindow(objArg));
212   MOZ_ASSERT(!JS_IsDeadWrapper(objArg));
213 
214   bool waive = ShouldWaiveXray(cx, objectPassedToWrap);
215   RootedObject obj(cx, objArg);
216   retObj.set(nullptr);
217 
218   // There are a few cases related to window proxies that are handled first to
219   // allow us to assert against wrappers below.
220   if (MaybeWrapWindowProxy(cx, origObj, obj, retObj)) {
221     if (waive) {
222       // We don't put remote window proxies in a waiving wrapper.
223       MOZ_ASSERT(js::IsWindowProxy(obj));
224       retObj.set(WaiveXray(cx, retObj));
225     }
226     return;
227   }
228 
229   // Here are the rules for wrapping:
230   // We should never get a proxy here (the JS engine unwraps those for us).
231   MOZ_ASSERT(!IsWrapper(obj));
232 
233   // Now, our object is ready to be wrapped, but several objects (notably
234   // nsJSIIDs) have a wrapper per scope. If we are about to wrap one of
235   // those objects in a security wrapper, then we need to hand back the
236   // wrapper for the new scope instead. Also, global objects don't move
237   // between scopes so for those we also want to return the wrapper. So...
238   if (!IS_WN_REFLECTOR(obj) || JS_IsGlobalObject(obj)) {
239     retObj.set(waive ? WaiveXray(cx, obj) : obj);
240     return;
241   }
242 
243   XPCWrappedNative* wn = XPCWrappedNative::Get(obj);
244 
245   JSAutoRealm ar(cx, obj);
246   XPCCallContext ccx(cx, obj);
247   RootedObject wrapScope(cx, scope);
248 
249   if (ccx.GetScriptable() && ccx.GetScriptable()->WantPreCreate()) {
250     // We have a precreate hook. This object might enforce that we only
251     // ever create JS object for it.
252 
253     // Note: this penalizes objects that only have one wrapper, but are
254     // being accessed across compartments. We would really prefer to
255     // replace the above code with a test that says "do you only have one
256     // wrapper?"
257     nsresult rv = wn->GetScriptable()->PreCreate(wn->Native(), cx, scope,
258                                                  wrapScope.address());
259     if (NS_FAILED(rv)) {
260       retObj.set(waive ? WaiveXray(cx, obj) : obj);
261       return;
262     }
263 
264     // If the handed back scope differs from the passed-in scope and is in
265     // a separate compartment, then this object is explicitly requesting
266     // that we don't create a second JS object for it: create a security
267     // wrapper.
268     //
269     // Note: The only two objects that still use PreCreate are BackstagePass
270     // and Components, both of which unconditionally request their canonical
271     // scope. Since SpiderMonkey only invokes the prewrap callback in
272     // situations where the object is nominally cross-compartment, we should
273     // always get a different scope here.
274     MOZ_RELEASE_ASSERT(JS::GetCompartment(scope) !=
275                        JS::GetCompartment(wrapScope));
276     retObj.set(waive ? WaiveXray(cx, obj) : obj);
277     return;
278   }
279 
280   // This public WrapNativeToJSVal API enters the compartment of 'wrapScope'
281   // so we don't have to.
282   RootedValue v(cx);
283   nsresult rv = nsXPConnect::XPConnect()->WrapNativeToJSVal(
284       cx, wrapScope, wn->Native(), nullptr, &NS_GET_IID(nsISupports), false,
285       &v);
286   if (NS_FAILED(rv)) {
287     return;
288   }
289 
290   obj.set(&v.toObject());
291   MOZ_ASSERT(IS_WN_REFLECTOR(obj), "bad object");
292   JS::AssertObjectIsNotGray(obj);  // We should never return gray reflectors.
293 
294   // Because the underlying native didn't have a PreCreate hook, we had
295   // to a new (or possibly pre-existing) XPCWN in our compartment.
296   // This could be a problem for chrome code that passes XPCOM objects
297   // across compartments, because the effects of QI would disappear across
298   // compartments.
299   //
300   // So whenever we pull an XPCWN across compartments in this manner, we
301   // give the destination object the union of the two native sets. We try
302   // to do this cleverly in the common case to avoid too much overhead.
303   XPCWrappedNative* newwn = XPCWrappedNative::Get(obj);
304   RefPtr<XPCNativeSet> unionSet =
305       XPCNativeSet::GetNewOrUsed(cx, newwn->GetSet(), wn->GetSet(), false);
306   if (!unionSet) {
307     return;
308   }
309   newwn->SetSet(unionSet.forget());
310 
311   retObj.set(waive ? WaiveXray(cx, obj) : obj);
312 }
313 
314 // This check is completely symmetric, so we don't need to keep track of origin
315 // vs target here.  Two compartments may have had transparent CCWs between them
316 // only if they are same-origin (ignoring document.domain) or have both had
317 // document.domain set at some point and are same-site.  In either case they
318 // will have the same SiteIdentifier, so check that first.
CompartmentsMayHaveHadTransparentCCWs(CompartmentPrivate * private1,CompartmentPrivate * private2)319 static bool CompartmentsMayHaveHadTransparentCCWs(
320     CompartmentPrivate* private1, CompartmentPrivate* private2) {
321   auto& info1 = private1->originInfo;
322   auto& info2 = private2->originInfo;
323 
324   if (!info1.SiteRef().Equals(info2.SiteRef())) {
325     return false;
326   }
327 
328   return info1.GetPrincipalIgnoringDocumentDomain()->FastEquals(
329              info2.GetPrincipalIgnoringDocumentDomain()) ||
330          (info1.HasChangedDocumentDomain() && info2.HasChangedDocumentDomain());
331 }
332 
333 #ifdef DEBUG
DEBUG_CheckUnwrapSafety(HandleObject obj,const js::Wrapper * handler,JS::Realm * origin,JS::Realm * target)334 static void DEBUG_CheckUnwrapSafety(HandleObject obj,
335                                     const js::Wrapper* handler,
336                                     JS::Realm* origin, JS::Realm* target) {
337   JS::Compartment* targetCompartment = JS::GetCompartmentForRealm(target);
338   if (!js::AllowNewWrapper(targetCompartment, obj)) {
339     // The JS engine should have returned a dead wrapper in this case and we
340     // shouldn't even get here.
341     MOZ_ASSERT_UNREACHABLE("CheckUnwrapSafety called for a dead wrapper");
342   } else if (AccessCheck::isChrome(targetCompartment)) {
343     // If the caller is chrome (or effectively so), unwrap should always be
344     // allowed, but we might have a CrossOriginObjectWrapper here which allows
345     // it dynamically.
346     MOZ_ASSERT(!handler->hasSecurityPolicy() ||
347                handler == &CrossOriginObjectWrapper::singleton);
348   } else {
349     // Otherwise, it should depend on whether the target subsumes the origin.
350     bool subsumes =
351         (OriginAttributes::IsRestrictOpenerAccessForFPI()
352              ? AccessCheck::subsumesConsideringDomain(target, origin)
353              : AccessCheck::subsumesConsideringDomainIgnoringFPD(target,
354                                                                  origin));
355     if (!subsumes) {
356       // If the target (which is where the wrapper lives) does not subsume the
357       // origin (which is where the wrapped object lives), then we should
358       // generally have a security check on the wrapper here.  There is one
359       // exception, though: things that used to be same-origin and then stopped
360       // due to document.domain changes.  In that case we will have a
361       // transparent cross-compartment wrapper here even though "subsumes" is no
362       // longer true.
363       CompartmentPrivate* originCompartmentPrivate =
364           CompartmentPrivate::Get(origin);
365       CompartmentPrivate* targetCompartmentPrivate =
366           CompartmentPrivate::Get(target);
367       if (!originCompartmentPrivate->wantXrays &&
368           !targetCompartmentPrivate->wantXrays &&
369           CompartmentsMayHaveHadTransparentCCWs(originCompartmentPrivate,
370                                                 targetCompartmentPrivate)) {
371         // We should have a transparent CCW, unless we have a cross-origin
372         // object, in which case it will be a CrossOriginObjectWrapper.
373         MOZ_ASSERT(handler == &CrossCompartmentWrapper::singleton ||
374                    handler == &CrossOriginObjectWrapper::singleton);
375       } else {
376         MOZ_ASSERT(handler->hasSecurityPolicy());
377       }
378     } else {
379       // Even if target subsumes origin, we might have a wrapper with a security
380       // policy here, if it happens to be a CrossOriginObjectWrapper.
381       MOZ_ASSERT(!handler->hasSecurityPolicy() ||
382                  handler == &CrossOriginObjectWrapper::singleton);
383     }
384   }
385 }
386 #else
387 #  define DEBUG_CheckUnwrapSafety(obj, handler, origin, target) \
388     {}
389 #endif
390 
391 const CrossOriginObjectWrapper CrossOriginObjectWrapper::singleton;
392 
dynamicCheckedUnwrapAllowed(HandleObject obj,JSContext * cx) const393 bool CrossOriginObjectWrapper::dynamicCheckedUnwrapAllowed(
394     HandleObject obj, JSContext* cx) const {
395   MOZ_ASSERT(js::GetProxyHandler(obj) == this,
396              "Why are we getting called for some random object?");
397   JSObject* target = wrappedObject(obj);
398   return dom::MaybeCrossOriginObjectMixins::IsPlatformObjectSameOrigin(cx,
399                                                                        target);
400 }
401 
SelectWrapper(bool securityWrapper,XrayType xrayType,bool waiveXrays,JSObject * obj)402 static const Wrapper* SelectWrapper(bool securityWrapper, XrayType xrayType,
403                                     bool waiveXrays, JSObject* obj) {
404   // Waived Xray uses a modified CCW that has transparent behavior but
405   // transitively waives Xrays on arguments.
406   if (waiveXrays) {
407     MOZ_ASSERT(!securityWrapper);
408     return &WaiveXrayWrapper::singleton;
409   }
410 
411   // If we don't want or can't use Xrays, select a wrapper that's either
412   // entirely transparent or entirely opaque.
413   if (xrayType == NotXray) {
414     if (!securityWrapper) {
415       return &CrossCompartmentWrapper::singleton;
416     }
417     return &FilteringWrapper<CrossCompartmentSecurityWrapper,
418                              Opaque>::singleton;
419   }
420 
421   // Ok, we're using Xray. If this isn't a security wrapper, use the permissive
422   // version and skip the filter.
423   if (!securityWrapper) {
424     if (xrayType == XrayForDOMObject) {
425       return &PermissiveXrayDOM::singleton;
426     } else if (xrayType == XrayForJSObject) {
427       return &PermissiveXrayJS::singleton;
428     }
429     MOZ_ASSERT(xrayType == XrayForOpaqueObject);
430     return &PermissiveXrayOpaque::singleton;
431   }
432 
433   // There's never any reason to expose other objects to non-subsuming actors.
434   // Just use an opaque wrapper in these cases.
435   return &FilteringWrapper<CrossCompartmentSecurityWrapper, Opaque>::singleton;
436 }
437 
Rewrap(JSContext * cx,HandleObject existing,HandleObject obj)438 JSObject* WrapperFactory::Rewrap(JSContext* cx, HandleObject existing,
439                                  HandleObject obj) {
440   MOZ_ASSERT(!IsWrapper(obj) || GetProxyHandler(obj) == &XrayWaiver ||
441                  js::IsWindowProxy(obj),
442              "wrapped object passed to rewrap");
443   MOZ_ASSERT(!js::IsWindow(obj));
444   MOZ_ASSERT(dom::IsJSAPIActive());
445 
446   // Compute the information we need to select the right wrapper.
447   JS::Realm* origin = js::GetNonCCWObjectRealm(obj);
448   JS::Realm* target = js::GetContextRealm(cx);
449   MOZ_ASSERT(target, "Why is our JSContext not in a Realm?");
450   bool originIsChrome = AccessCheck::isChrome(origin);
451   bool targetIsChrome = AccessCheck::isChrome(target);
452   bool originSubsumesTarget =
453       OriginAttributes::IsRestrictOpenerAccessForFPI()
454           ? AccessCheck::subsumesConsideringDomain(origin, target)
455           : AccessCheck::subsumesConsideringDomainIgnoringFPD(origin, target);
456   bool targetSubsumesOrigin =
457       OriginAttributes::IsRestrictOpenerAccessForFPI()
458           ? AccessCheck::subsumesConsideringDomain(target, origin)
459           : AccessCheck::subsumesConsideringDomainIgnoringFPD(target, origin);
460   bool sameOrigin = targetSubsumesOrigin && originSubsumesTarget;
461 
462   const Wrapper* wrapper;
463 
464   CompartmentPrivate* originCompartmentPrivate =
465       CompartmentPrivate::Get(origin);
466   CompartmentPrivate* targetCompartmentPrivate =
467       CompartmentPrivate::Get(target);
468 
469   // Track whether we decided to use a transparent wrapper because of
470   // document.domain usage, so we don't override that decision.
471   bool isTransparentWrapperDueToDocumentDomain = false;
472 
473   //
474   // First, handle the special cases.
475   //
476 
477   // Special handling for chrome objects being exposed to content.
478   if (originIsChrome && !targetIsChrome) {
479     // If this is a chrome function being exposed to content, we need to allow
480     // call (but nothing else).
481     if ((IdentifyStandardInstance(obj) == JSProto_Function)) {
482       wrapper = &FilteringWrapper<CrossCompartmentSecurityWrapper,
483                                   OpaqueWithCall>::singleton;
484     }
485 
486     // For vanilla JSObjects exposed from chrome to content, we use a wrapper
487     // that fails silently in a few cases. We'd like to get rid of this
488     // eventually, but in their current form they don't cause much trouble.
489     else if (IdentifyStandardInstance(obj) == JSProto_Object) {
490       wrapper = &ChromeObjectWrapper::singleton;
491     }
492 
493     // Otherwise we get an opaque wrapper.
494     else {
495       wrapper =
496           &FilteringWrapper<CrossCompartmentSecurityWrapper, Opaque>::singleton;
497     }
498   }
499 
500   // Special handling for the web's cross-origin objects (WindowProxy and
501   // Location).  We only need or want to do this in web-like contexts, where all
502   // security relationships are symmetric and there are no forced Xrays.
503   else if (originSubsumesTarget == targetSubsumesOrigin &&
504            // Check for the more rare case of cross-origin objects before doing
505            // the more-likely-to-pass checks for wantXrays.
506            IsCrossOriginAccessibleObject(obj) &&
507            (!targetSubsumesOrigin || (!originCompartmentPrivate->wantXrays &&
508                                       !targetCompartmentPrivate->wantXrays))) {
509     wrapper = &CrossOriginObjectWrapper::singleton;
510   }
511 
512   // Special handling for other web objects.  Again, we only want this in
513   // web-like contexts (symmetric security relationships, no forced Xrays).  In
514   // this situation, if the two compartments may ever have had transparent CCWs
515   // between them, we want to keep using transparent CCWs.
516   else if (originSubsumesTarget == targetSubsumesOrigin &&
517            !originCompartmentPrivate->wantXrays &&
518            !targetCompartmentPrivate->wantXrays &&
519            CompartmentsMayHaveHadTransparentCCWs(originCompartmentPrivate,
520                                                  targetCompartmentPrivate)) {
521     isTransparentWrapperDueToDocumentDomain = true;
522     wrapper = &CrossCompartmentWrapper::singleton;
523   }
524 
525   //
526   // Now, handle the regular cases.
527   //
528   // These are wrappers we can compute using a rule-based approach. In order
529   // to do so, we need to compute some parameters.
530   //
531   else {
532     // The wrapper is a security wrapper (protecting the wrappee) if and
533     // only if the target does not subsume the origin.
534     bool securityWrapper = !targetSubsumesOrigin;
535 
536     // Xrays are warranted if either the target or the origin don't trust
537     // each other. This is generally the case, unless the two are same-origin
538     // and the caller has not requested same-origin Xrays.
539     //
540     // Xrays are a bidirectional protection, since it affords clarity to the
541     // caller and privacy to the callee.
542     bool sameOriginXrays = originCompartmentPrivate->wantXrays ||
543                            targetCompartmentPrivate->wantXrays;
544     bool wantXrays = !sameOrigin || sameOriginXrays;
545 
546     XrayType xrayType = wantXrays ? GetXrayType(obj) : NotXray;
547 
548     // If Xrays are warranted, the caller may waive them for non-security
549     // wrappers (unless explicitly forbidden from doing so).
550     bool waiveXrays = wantXrays && !securityWrapper &&
551                       targetCompartmentPrivate->allowWaivers &&
552                       HasWaiveXrayFlag(obj);
553 
554     wrapper = SelectWrapper(securityWrapper, xrayType, waiveXrays, obj);
555   }
556 
557   if (!targetSubsumesOrigin && !isTransparentWrapperDueToDocumentDomain) {
558     // Do a belt-and-suspenders check against exposing eval()/Function() to
559     // non-subsuming content.
560     if (JSFunction* fun = JS_GetObjectFunction(obj)) {
561       if (JS_IsBuiltinEvalFunction(fun) ||
562           JS_IsBuiltinFunctionConstructor(fun)) {
563         NS_WARNING(
564             "Trying to expose eval or Function to non-subsuming content!");
565         wrapper = &FilteringWrapper<CrossCompartmentSecurityWrapper,
566                                     Opaque>::singleton;
567       }
568     }
569   }
570 
571   DEBUG_CheckUnwrapSafety(obj, wrapper, origin, target);
572 
573   if (existing) {
574     return Wrapper::Renew(existing, obj, wrapper);
575   }
576 
577   return Wrapper::New(cx, obj, wrapper);
578 }
579 
580 // Call WaiveXrayAndWrap when you have a JS object that you don't want to be
581 // wrapped in an Xray wrapper. cx->compartment is the compartment that will be
582 // using the returned object. If the object to be wrapped is already in the
583 // correct compartment, then this returns the unwrapped object.
WaiveXrayAndWrap(JSContext * cx,MutableHandleValue vp)584 bool WrapperFactory::WaiveXrayAndWrap(JSContext* cx, MutableHandleValue vp) {
585   if (vp.isPrimitive()) {
586     return JS_WrapValue(cx, vp);
587   }
588 
589   RootedObject obj(cx, &vp.toObject());
590   if (!WaiveXrayAndWrap(cx, &obj)) {
591     return false;
592   }
593 
594   vp.setObject(*obj);
595   return true;
596 }
597 
WaiveXrayAndWrap(JSContext * cx,MutableHandleObject argObj)598 bool WrapperFactory::WaiveXrayAndWrap(JSContext* cx,
599                                       MutableHandleObject argObj) {
600   MOZ_ASSERT(argObj);
601   RootedObject obj(cx, js::UncheckedUnwrap(argObj));
602   MOZ_ASSERT(!js::IsWindow(obj));
603   if (js::IsObjectInContextCompartment(obj, cx)) {
604     argObj.set(obj);
605     return true;
606   }
607 
608   // Even though waivers have no effect on access by scopes that don't subsume
609   // the underlying object, good defense-in-depth dictates that we should avoid
610   // handing out waivers to callers that can't use them. The transitive waiving
611   // machinery unconditionally calls WaiveXrayAndWrap on return values from
612   // waived functions, even though the return value might be not be same-origin
613   // with the function. So if we find ourselves trying to create a waiver for
614   // |cx|, we should check whether the caller has any business with waivers
615   // to things in |obj|'s compartment.
616   JS::Compartment* target = js::GetContextCompartment(cx);
617   JS::Compartment* origin = JS::GetCompartment(obj);
618   obj = AllowWaiver(target, origin) ? WaiveXray(cx, obj) : obj;
619   if (!obj) {
620     return false;
621   }
622 
623   if (!JS_WrapObject(cx, &obj)) {
624     return false;
625   }
626   argObj.set(obj);
627   return true;
628 }
629 
630 /*
631  * Calls to JS_TransplantObject* should go through these helpers here so that
632  * waivers get fixed up properly.
633  */
634 
FixWaiverAfterTransplant(JSContext * cx,HandleObject oldWaiver,HandleObject newobj,bool crossCompartmentTransplant)635 static bool FixWaiverAfterTransplant(JSContext* cx, HandleObject oldWaiver,
636                                      HandleObject newobj,
637                                      bool crossCompartmentTransplant) {
638   MOZ_ASSERT(Wrapper::wrapperHandler(oldWaiver) == &XrayWaiver);
639   MOZ_ASSERT(!js::IsCrossCompartmentWrapper(newobj));
640 
641   if (crossCompartmentTransplant) {
642     // If the new compartment has a CCW for oldWaiver, nuke this CCW. This
643     // prevents confusing RemapAllWrappersForObject: it would call RemapWrapper
644     // with two same-compartment objects (the CCW and the new waiver).
645     //
646     // This can happen when loading a chrome page in a content frame and there
647     // exists a CCW from the chrome compartment to oldWaiver wrapping the window
648     // we just transplanted:
649     //
650     // Compartment 1  |  Compartment 2
651     // ----------------------------------------
652     // CCW1 -----------> oldWaiver --> CCW2 --+
653     // newWaiver                              |
654     // WindowProxy <--------------------------+
655     js::NukeCrossCompartmentWrapperIfExists(cx, JS::GetCompartment(newobj),
656                                             oldWaiver);
657   } else {
658     // We kept the same object identity, so the waiver should be a
659     // waiver for our object, just in the wrong Realm.
660     MOZ_ASSERT(newobj == Wrapper::wrappedObject(oldWaiver));
661   }
662 
663   // Create a waiver in the new compartment. We know there's not one already in
664   // the crossCompartmentTransplant case because we _just_ transplanted, which
665   // means that |newobj| was either created from scratch, or was previously
666   // cross-compartment wrapper (which should have no waiver). On the other hand,
667   // in the !crossCompartmentTransplant case we know one already exists.
668   // CreateXrayWaiver asserts all this.
669   RootedObject newWaiver(
670       cx, WrapperFactory::CreateXrayWaiver(
671               cx, newobj, /* allowExisting = */ !crossCompartmentTransplant));
672   if (!newWaiver) {
673     return false;
674   }
675 
676   if (!crossCompartmentTransplant) {
677     // CreateXrayWaiver should have updated the map to point to the new waiver.
678     MOZ_ASSERT(WrapperFactory::GetXrayWaiver(newobj) == newWaiver);
679   }
680 
681   // Update all the cross-compartment references to oldWaiver to point to
682   // newWaiver.
683   if (!js::RemapAllWrappersForObject(cx, oldWaiver, newWaiver)) {
684     return false;
685   }
686 
687   if (crossCompartmentTransplant) {
688     // There should be no same-compartment references to oldWaiver, and we
689     // just remapped all cross-compartment references. It's dead, so we can
690     // remove it from the map.
691     XPCWrappedNativeScope* scope = ObjectScope(oldWaiver);
692     JSObject* key = Wrapper::wrappedObject(oldWaiver);
693     MOZ_ASSERT(scope->mWaiverWrapperMap->Find(key));
694     scope->mWaiverWrapperMap->Remove(key);
695   }
696 
697   return true;
698 }
699 
TransplantObject(JSContext * cx,JS::HandleObject origobj,JS::HandleObject target)700 JSObject* TransplantObject(JSContext* cx, JS::HandleObject origobj,
701                            JS::HandleObject target) {
702   RootedObject oldWaiver(cx, WrapperFactory::GetXrayWaiver(origobj));
703   MOZ_ASSERT_IF(oldWaiver, GetNonCCWObjectRealm(oldWaiver) ==
704                                GetNonCCWObjectRealm(origobj));
705   RootedObject newIdentity(cx, JS_TransplantObject(cx, origobj, target));
706   if (!newIdentity || !oldWaiver) {
707     return newIdentity;
708   }
709 
710   bool crossCompartmentTransplant = (newIdentity != origobj);
711   if (!crossCompartmentTransplant) {
712     // We might still have been transplanted across realms within a single
713     // compartment.
714     if (GetNonCCWObjectRealm(oldWaiver) == GetNonCCWObjectRealm(newIdentity)) {
715       // The old waiver is same-realm with the new object; nothing else to do
716       // here.
717       return newIdentity;
718     }
719   }
720 
721   if (!FixWaiverAfterTransplant(cx, oldWaiver, newIdentity,
722                                 crossCompartmentTransplant)) {
723     return nullptr;
724   }
725   return newIdentity;
726 }
727 
TransplantObjectRetainingXrayExpandos(JSContext * cx,JS::HandleObject origobj,JS::HandleObject target)728 JSObject* TransplantObjectRetainingXrayExpandos(JSContext* cx,
729                                                 JS::HandleObject origobj,
730                                                 JS::HandleObject target) {
731   // Save the chain of objects that carry origobj's Xray expando properties
732   // (from all compartments). TransplantObject will blow this away; we'll
733   // restore it manually afterwards.
734   RootedObject expandoChain(
735       cx, GetXrayTraits(origobj)->detachExpandoChain(origobj));
736 
737   RootedObject newIdentity(cx, TransplantObject(cx, origobj, target));
738 
739   // Copy Xray expando properties to the new wrapper.
740   if (!GetXrayTraits(newIdentity)
741            ->cloneExpandoChain(cx, newIdentity, expandoChain)) {
742     // Failure here means some expandos were not copied over. The object graph
743     // and the Xray machinery are left in a consistent state, but mysteriously
744     // losing these expandos is too weird to allow.
745     MOZ_CRASH();
746   }
747 
748   return newIdentity;
749 }
750 
NukeXrayWaiver(JSContext * cx,JS::HandleObject obj)751 static void NukeXrayWaiver(JSContext* cx, JS::HandleObject obj) {
752   RootedObject waiver(cx, WrapperFactory::GetXrayWaiver(obj));
753   if (!waiver) {
754     return;
755   }
756 
757   XPCWrappedNativeScope* scope = ObjectScope(waiver);
758   JSObject* key = Wrapper::wrappedObject(waiver);
759   MOZ_ASSERT(scope->mWaiverWrapperMap->Find(key));
760   scope->mWaiverWrapperMap->Remove(key);
761 
762   js::NukeNonCCWProxy(cx, waiver);
763 
764   // Get rid of any CCWs the waiver may have had.
765   if (!JS_RefreshCrossCompartmentWrappers(cx, waiver)) {
766     MOZ_CRASH();
767   }
768 }
769 
TransplantObjectNukingXrayWaiver(JSContext * cx,JS::HandleObject origObj,JS::HandleObject target)770 JSObject* TransplantObjectNukingXrayWaiver(JSContext* cx,
771                                            JS::HandleObject origObj,
772                                            JS::HandleObject target) {
773   NukeXrayWaiver(cx, origObj);
774   return JS_TransplantObject(cx, origObj, target);
775 }
776 
NativeGlobal(JSObject * obj)777 nsIGlobalObject* NativeGlobal(JSObject* obj) {
778   obj = JS::GetNonCCWObjectGlobal(obj);
779 
780   // Every global needs to hold a native as its private or be a
781   // WebIDL object with an nsISupports DOM object.
782   MOZ_ASSERT((JS::GetClass(obj)->flags &
783               (JSCLASS_PRIVATE_IS_NSISUPPORTS | JSCLASS_HAS_PRIVATE)) ||
784              dom::UnwrapDOMObjectToISupports(obj));
785 
786   nsISupports* native = dom::UnwrapDOMObjectToISupports(obj);
787   if (!native) {
788     native = static_cast<nsISupports*>(JS::GetPrivate(obj));
789     MOZ_ASSERT(native);
790 
791     // In some cases (like for windows) it is a wrapped native,
792     // in other cases (sandboxes, backstage passes) it's just
793     // a direct pointer to the native. If it's a wrapped native
794     // let's unwrap it first.
795     if (nsCOMPtr<nsIXPConnectWrappedNative> wn = do_QueryInterface(native)) {
796       native = wn->Native();
797     }
798   }
799 
800   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(native);
801   MOZ_ASSERT(global,
802              "Native held by global needs to implement nsIGlobalObject!");
803 
804   return global;
805 }
806 
CurrentNativeGlobal(JSContext * cx)807 nsIGlobalObject* CurrentNativeGlobal(JSContext* cx) {
808   return xpc::NativeGlobal(JS::CurrentGlobalOrNull(cx));
809 }
810 
811 }  // namespace xpc
812