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