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