diff --git a/js/xpconnect/wrappers/AccessCheck.cpp b/js/xpconnect/wrappers/AccessCheck.cpp index d4828152a66d..0e7b8a2416bd 100644 --- a/js/xpconnect/wrappers/AccessCheck.cpp +++ b/js/xpconnect/wrappers/AccessCheck.cpp @@ -89,18 +89,12 @@ bool AccessCheck::isChrome(JSObject* obj) { return isChrome(js::GetObjectCompartment(obj)); } -CrossOriginObjectType IdentifyCrossOriginObject(JSObject* obj) { +bool IsCrossOriginAccessibleObject(JSObject* obj) { obj = js::UncheckedUnwrap(obj, /* stopAtWindowProxy = */ false); const js::Class* clasp = js::GetObjectClass(obj); - if (clasp->name[0] == 'L' && !strcmp(clasp->name, "Location")) { - return CrossOriginLocation; - } - if (clasp->name[0] == 'W' && !strcmp(clasp->name, "Window")) { - return CrossOriginWindow; - } - - return CrossOriginOpaque; + return (clasp->name[0] == 'L' && !strcmp(clasp->name, "Location")) || + (clasp->name[0] == 'W' && !strcmp(clasp->name, "Window")); } bool AccessCheck::checkPassToPrivilegedCode(JSContext* cx, HandleObject wrapper, diff --git a/js/xpconnect/wrappers/AccessCheck.h b/js/xpconnect/wrappers/AccessCheck.h index 597e0d50f0ae..be4ad4938a3f 100644 --- a/js/xpconnect/wrappers/AccessCheck.h +++ b/js/xpconnect/wrappers/AccessCheck.h @@ -35,12 +35,13 @@ class AccessCheck { const nsACString& accessType); }; -enum CrossOriginObjectType { - CrossOriginWindow, - CrossOriginLocation, - CrossOriginOpaque -}; -CrossOriginObjectType IdentifyCrossOriginObject(JSObject* obj); +/** + * Returns true if the given object (which is expected to be stripped of + * cross-compartment wrappers in practice, but this function doesn't assume + * that) is a WindowProxy or Location object, which need special wrapping + * behavior due to being usable cross-origin in limited ways. + */ +bool IsCrossOriginAccessibleObject(JSObject* obj); struct Policy { static bool checkCall(JSContext* cx, JS::HandleObject wrapper, diff --git a/js/xpconnect/wrappers/WrapperFactory.cpp b/js/xpconnect/wrappers/WrapperFactory.cpp index d0afd39d19a7..470c5de744da 100644 --- a/js/xpconnect/wrappers/WrapperFactory.cpp +++ b/js/xpconnect/wrappers/WrapperFactory.cpp @@ -339,13 +339,16 @@ static void DEBUG_CheckUnwrapSafety(HandleObject obj, } else if (AccessCheck::isChrome(target) || xpc::IsUniversalXPConnectEnabled(target)) { // If the caller is chrome (or effectively so), unwrap should always be - // allowed. - MOZ_ASSERT(!handler->hasSecurityPolicy()); + // allowed, but we might have a CrossOriginObjectWrapper here which allows + // it dynamically. + MOZ_ASSERT(!handler->hasSecurityPolicy() || + handler == &CrossOriginObjectWrapper::singleton); } else if (RealmPrivate::Get(origin)->forcePermissiveCOWs) { // Similarly, if this is a privileged scope that has opted to make itself // accessible to the world (allowed only during automation), unwrap should - // be allowed. - MOZ_ASSERT(!handler->hasSecurityPolicy()); + // be allowed. Again, it might be allowed dynamically. + MOZ_ASSERT(!handler->hasSecurityPolicy() || + handler == &CrossOriginObjectWrapper::singleton); } else { // Otherwise, it should depend on whether the target subsumes the origin. JS::Compartment* originComp = JS::GetCompartmentForRealm(origin); @@ -354,7 +357,17 @@ static void DEBUG_CheckUnwrapSafety(HandleObject obj, ? AccessCheck::subsumesConsideringDomain(target, originComp) : AccessCheck::subsumesConsideringDomainIgnoringFPD(target, originComp)); - MOZ_ASSERT(handler->hasSecurityPolicy() == !subsumes); + if (!subsumes) { + // If the target (which is where the wrapper lives) does not subsume the + // origin (which is where the wrapped object lives), then we should have a + // security check on the wrapper here. + MOZ_ASSERT(handler->hasSecurityPolicy()); + } else { + // Even if target subsumes origin, we might have a wrapper with a security + // policy here, if it happens to be a CrossOriginObjectWrapper. + MOZ_ASSERT(!handler->hasSecurityPolicy() || + handler == &CrossOriginObjectWrapper::singleton); + } } } #else @@ -404,12 +417,6 @@ static const Wrapper* SelectWrapper(bool securityWrapper, XrayType xrayType, return &PermissiveXrayOpaque::singleton; } - // This is a security wrapper. Use the security versions and filter. - if (xrayType == XrayForDOMObject && - IdentifyCrossOriginObject(obj) != CrossOriginOpaque) { - return &CrossOriginObjectWrapper::singleton; - } - // There's never any reason to expose other objects to non-subsuming actors. // Just use an opaque wrapper in these cases. // @@ -501,6 +508,18 @@ JSObject* WrapperFactory::Rewrap(JSContext* cx, HandleObject existing, } } + // Special handling for the web's cross-origin objects (WindowProxy and + // Location). We only need or want to do this in web-like contexts, where all + // security relationships are symmetric and there are no forced Xrays. + else if (originSubsumesTarget == targetSubsumesOrigin && + // Check for the more rare case of cross-origin objects before doing + // the more-likely-to-pass checks for wantXrays. + IsCrossOriginAccessibleObject(obj) && + (!targetSubsumesOrigin || (!originCompartmentPrivate->wantXrays && + !targetCompartmentPrivate->wantXrays))) { + wrapper = &CrossOriginObjectWrapper::singleton; + } + // // Now, handle the regular cases. //