michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* vim: set ts=8 sts=4 et sw=4 tw=99: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "WaiveXrayWrapper.h" michael@0: #include "FilteringWrapper.h" michael@0: #include "XrayWrapper.h" michael@0: #include "AccessCheck.h" michael@0: #include "XPCWrapper.h" michael@0: #include "ChromeObjectWrapper.h" michael@0: #include "WrapperFactory.h" michael@0: michael@0: #include "xpcprivate.h" michael@0: #include "XPCMaps.h" michael@0: #include "mozilla/dom/BindingUtils.h" michael@0: #include "jsfriendapi.h" michael@0: #include "mozilla/Likely.h" michael@0: #include "nsContentUtils.h" michael@0: michael@0: using namespace JS; michael@0: using namespace js; michael@0: using namespace mozilla; michael@0: michael@0: namespace xpc { michael@0: michael@0: // When chrome pulls a naked property across the membrane using michael@0: // .wrappedJSObject, we want it to cross the membrane into the michael@0: // chrome compartment without automatically being wrapped into an michael@0: // X-ray wrapper. We achieve this by wrapping it into a special michael@0: // transparent wrapper in the origin (non-chrome) compartment. When michael@0: // an object with that special wrapper applied crosses into chrome, michael@0: // we know to not apply an X-ray wrapper. michael@0: Wrapper XrayWaiver(WrapperFactory::WAIVE_XRAY_WRAPPER_FLAG); michael@0: michael@0: // When objects for which we waived the X-ray wrapper cross into michael@0: // chrome, we wrap them into a special cross-compartment wrapper michael@0: // that transitively extends the waiver to all properties we get michael@0: // off it. michael@0: WaiveXrayWrapper WaiveXrayWrapper::singleton(0); michael@0: michael@0: bool michael@0: WrapperFactory::IsCOW(JSObject *obj) michael@0: { michael@0: return IsWrapper(obj) && michael@0: Wrapper::wrapperHandler(obj) == &ChromeObjectWrapper::singleton; michael@0: } michael@0: michael@0: JSObject * michael@0: WrapperFactory::GetXrayWaiver(HandleObject obj) michael@0: { michael@0: // Object should come fully unwrapped but outerized. michael@0: MOZ_ASSERT(obj == UncheckedUnwrap(obj)); michael@0: MOZ_ASSERT(!js::GetObjectClass(obj)->ext.outerObject); michael@0: XPCWrappedNativeScope *scope = GetObjectScope(obj); michael@0: MOZ_ASSERT(scope); michael@0: michael@0: if (!scope->mWaiverWrapperMap) michael@0: return nullptr; michael@0: michael@0: JSObject* xrayWaiver = scope->mWaiverWrapperMap->Find(obj); michael@0: if (xrayWaiver) michael@0: JS::ExposeObjectToActiveJS(xrayWaiver); michael@0: michael@0: return xrayWaiver; michael@0: } michael@0: michael@0: JSObject * michael@0: WrapperFactory::CreateXrayWaiver(JSContext *cx, HandleObject obj) michael@0: { michael@0: // The caller is required to have already done a lookup. michael@0: // NB: This implictly performs the assertions of GetXrayWaiver. michael@0: MOZ_ASSERT(!GetXrayWaiver(obj)); michael@0: XPCWrappedNativeScope *scope = GetObjectScope(obj); michael@0: michael@0: JSAutoCompartment ac(cx, obj); michael@0: JSObject *waiver = Wrapper::New(cx, obj, michael@0: JS_GetGlobalForObject(cx, obj), michael@0: &XrayWaiver); michael@0: if (!waiver) michael@0: return nullptr; michael@0: michael@0: // Add the new waiver to the map. It's important that we only ever have michael@0: // one waiver for the lifetime of the target object. michael@0: if (!scope->mWaiverWrapperMap) { michael@0: scope->mWaiverWrapperMap = michael@0: JSObject2JSObjectMap::newMap(XPC_WRAPPER_MAP_SIZE); michael@0: MOZ_ASSERT(scope->mWaiverWrapperMap); michael@0: } michael@0: if (!scope->mWaiverWrapperMap->Add(cx, obj, waiver)) michael@0: return nullptr; michael@0: return waiver; michael@0: } michael@0: michael@0: JSObject * michael@0: WrapperFactory::WaiveXray(JSContext *cx, JSObject *objArg) michael@0: { michael@0: RootedObject obj(cx, objArg); michael@0: obj = UncheckedUnwrap(obj); michael@0: MOZ_ASSERT(!js::IsInnerObject(obj)); michael@0: michael@0: JSObject *waiver = GetXrayWaiver(obj); michael@0: if (waiver) michael@0: return waiver; michael@0: return CreateXrayWaiver(cx, obj); michael@0: } michael@0: michael@0: // DoubleWrap is called from PrepareForWrapping to maintain the state that michael@0: // we're supposed to waive Xray wrappers for the given on. On entrance, it michael@0: // expects |cx->compartment != obj->compartment()|. The returned object will michael@0: // be in the same compartment as |obj|. michael@0: JSObject * michael@0: WrapperFactory::DoubleWrap(JSContext *cx, HandleObject obj, unsigned flags) michael@0: { michael@0: if (flags & WrapperFactory::WAIVE_XRAY_WRAPPER_FLAG) { michael@0: JSAutoCompartment ac(cx, obj); michael@0: return WaiveXray(cx, obj); michael@0: } michael@0: return obj; michael@0: } michael@0: michael@0: JSObject * michael@0: WrapperFactory::PrepareForWrapping(JSContext *cx, HandleObject scope, michael@0: HandleObject objArg, unsigned flags) michael@0: { michael@0: RootedObject obj(cx, objArg); michael@0: // Outerize any raw inner objects at the entry point here, so that we don't michael@0: // have to worry about them for the rest of the wrapping code. michael@0: if (js::IsInnerObject(obj)) { michael@0: JSAutoCompartment ac(cx, obj); michael@0: obj = JS_ObjectToOuterObject(cx, obj); michael@0: NS_ENSURE_TRUE(obj, nullptr); michael@0: // The outerization hook wraps, which means that we can end up with a michael@0: // CCW here if |obj| was a navigated-away-from inner. Strip any CCWs. michael@0: obj = js::UncheckedUnwrap(obj); michael@0: MOZ_ASSERT(js::IsOuterObject(obj)); michael@0: } michael@0: michael@0: // If we've got an outer window, there's nothing special that needs to be michael@0: // done here, and we can move on to the next phase of wrapping. We handle michael@0: // this case first to allow us to assert against wrappers below. michael@0: if (js::IsOuterObject(obj)) michael@0: return DoubleWrap(cx, obj, flags); michael@0: michael@0: // Here are the rules for wrapping: michael@0: // We should never get a proxy here (the JS engine unwraps those for us). michael@0: MOZ_ASSERT(!IsWrapper(obj)); michael@0: michael@0: // If the object being wrapped is a prototype for a standard class and the michael@0: // wrapper does not subsumes the wrappee, use the one from the content michael@0: // compartment. This is generally safer all-around, and in the COW case this michael@0: // lets us safely take advantage of things like .forEach() via the michael@0: // ChromeObjectWrapper machinery. michael@0: // michael@0: // If the prototype chain of chrome object |obj| looks like this: michael@0: // michael@0: // obj => foo => bar => chromeWin.StandardClass.prototype michael@0: // michael@0: // The prototype chain of COW(obj) looks lke this: michael@0: // michael@0: // COW(obj) => COW(foo) => COW(bar) => contentWin.StandardClass.prototype michael@0: // michael@0: // NB: We now remap all non-subsuming access of standard prototypes. michael@0: // michael@0: // NB: We need to ignore domain here so that the security relationship we michael@0: // compute here can't change over time. See the comment above the other michael@0: // subsumes call below. michael@0: bool subsumes = AccessCheck::subsumes(js::GetContextCompartment(cx), michael@0: js::GetObjectCompartment(obj)); michael@0: XrayType xrayType = GetXrayType(obj); michael@0: if (!subsumes && xrayType == NotXray) { michael@0: JSProtoKey key = JSProto_Null; michael@0: { michael@0: JSAutoCompartment ac(cx, obj); michael@0: key = IdentifyStandardPrototype(obj); michael@0: } michael@0: if (key != JSProto_Null) { michael@0: RootedObject homeProto(cx); michael@0: if (!JS_GetClassPrototype(cx, key, &homeProto)) michael@0: return nullptr; michael@0: MOZ_ASSERT(homeProto); michael@0: // No need to double-wrap here. We should never have waivers to michael@0: // COWs. michael@0: return homeProto; michael@0: } michael@0: } michael@0: michael@0: // Now, our object is ready to be wrapped, but several objects (notably michael@0: // nsJSIIDs) have a wrapper per scope. If we are about to wrap one of michael@0: // those objects in a security wrapper, then we need to hand back the michael@0: // wrapper for the new scope instead. Also, global objects don't move michael@0: // between scopes so for those we also want to return the wrapper. So... michael@0: if (!IS_WN_REFLECTOR(obj) || !js::GetObjectParent(obj)) michael@0: return DoubleWrap(cx, obj, flags); michael@0: michael@0: XPCWrappedNative *wn = XPCWrappedNative::Get(obj); michael@0: michael@0: JSAutoCompartment ac(cx, obj); michael@0: XPCCallContext ccx(JS_CALLER, cx, obj); michael@0: RootedObject wrapScope(cx, scope); michael@0: michael@0: { michael@0: if (NATIVE_HAS_FLAG(&ccx, WantPreCreate)) { michael@0: // We have a precreate hook. This object might enforce that we only michael@0: // ever create JS object for it. michael@0: michael@0: // Note: this penalizes objects that only have one wrapper, but are michael@0: // being accessed across compartments. We would really prefer to michael@0: // replace the above code with a test that says "do you only have one michael@0: // wrapper?" michael@0: nsresult rv = wn->GetScriptableInfo()->GetCallback()-> michael@0: PreCreate(wn->Native(), cx, scope, wrapScope.address()); michael@0: NS_ENSURE_SUCCESS(rv, DoubleWrap(cx, obj, flags)); michael@0: michael@0: // If the handed back scope differs from the passed-in scope and is in michael@0: // a separate compartment, then this object is explicitly requesting michael@0: // that we don't create a second JS object for it: create a security michael@0: // wrapper. michael@0: if (js::GetObjectCompartment(scope) != js::GetObjectCompartment(wrapScope)) michael@0: return DoubleWrap(cx, obj, flags); michael@0: michael@0: RootedObject currentScope(cx, JS_GetGlobalForObject(cx, obj)); michael@0: if (MOZ_UNLIKELY(wrapScope != currentScope)) { michael@0: // The wrapper claims it wants to be in the new scope, but michael@0: // currently has a reflection that lives in the old scope. This michael@0: // can mean one of two things, both of which are rare: michael@0: // michael@0: // 1 - The object has a PreCreate hook (we checked for it above), michael@0: // but is deciding to request one-wrapper-per-scope (rather than michael@0: // one-wrapper-per-native) for some reason. Usually, a PreCreate michael@0: // hook indicates one-wrapper-per-native. In this case we want to michael@0: // make a new wrapper in the new scope. michael@0: // michael@0: // 2 - We're midway through wrapper reparenting. The document has michael@0: // moved to a new scope, but |wn| hasn't been moved yet, and michael@0: // we ended up calling JS_WrapObject() on its JS object. In this michael@0: // case, we want to return the existing wrapper. michael@0: // michael@0: // So we do a trick: call PreCreate _again_, but say that we're michael@0: // wrapping for the old scope, rather than the new one. If (1) is michael@0: // the case, then PreCreate will return the scope we pass to it michael@0: // (the old scope). If (2) is the case, PreCreate will return the michael@0: // scope of the document (the new scope). michael@0: RootedObject probe(cx); michael@0: rv = wn->GetScriptableInfo()->GetCallback()-> michael@0: PreCreate(wn->Native(), cx, currentScope, probe.address()); michael@0: michael@0: // Check for case (2). michael@0: if (probe != currentScope) { michael@0: MOZ_ASSERT(probe == wrapScope); michael@0: return DoubleWrap(cx, obj, flags); michael@0: } michael@0: michael@0: // Ok, must be case (1). Fall through and create a new wrapper. michael@0: } michael@0: michael@0: // Nasty hack for late-breaking bug 781476. This will confuse identity checks, michael@0: // but it's probably better than any of our alternatives. michael@0: // michael@0: // Note: We have to ignore domain here. The JS engine assumes that, given a michael@0: // compartment c, if c->wrap(x) returns a cross-compartment wrapper at time t0, michael@0: // it will also return a cross-compartment wrapper for any time t1 > t0 unless michael@0: // an explicit transplant is performed. In particular, wrapper recomputation michael@0: // assumes that recomputing a wrapper will always result in a wrapper. michael@0: // michael@0: // This doesn't actually pose a security issue, because we'll still compute michael@0: // the correct (opaque) wrapper for the object below given the security michael@0: // characteristics of the two compartments. michael@0: if (!AccessCheck::isChrome(js::GetObjectCompartment(wrapScope)) && michael@0: AccessCheck::subsumes(js::GetObjectCompartment(wrapScope), michael@0: js::GetObjectCompartment(obj))) michael@0: { michael@0: return DoubleWrap(cx, obj, flags); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // This public WrapNativeToJSVal API enters the compartment of 'wrapScope' michael@0: // so we don't have to. michael@0: RootedValue v(cx); michael@0: nsresult rv = michael@0: nsXPConnect::XPConnect()->WrapNativeToJSVal(cx, wrapScope, wn->Native(), nullptr, michael@0: &NS_GET_IID(nsISupports), false, &v); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: michael@0: obj.set(&v.toObject()); michael@0: MOZ_ASSERT(IS_WN_REFLECTOR(obj), "bad object"); michael@0: michael@0: // Because the underlying native didn't have a PreCreate hook, we had michael@0: // to a new (or possibly pre-existing) XPCWN in our compartment. michael@0: // This could be a problem for chrome code that passes XPCOM objects michael@0: // across compartments, because the effects of QI would disappear across michael@0: // compartments. michael@0: // michael@0: // So whenever we pull an XPCWN across compartments in this manner, we michael@0: // give the destination object the union of the two native sets. We try michael@0: // to do this cleverly in the common case to avoid too much overhead. michael@0: XPCWrappedNative *newwn = XPCWrappedNative::Get(obj); michael@0: XPCNativeSet *unionSet = XPCNativeSet::GetNewOrUsed(newwn->GetSet(), michael@0: wn->GetSet(), false); michael@0: if (!unionSet) michael@0: return nullptr; michael@0: newwn->SetSet(unionSet); michael@0: michael@0: return DoubleWrap(cx, obj, flags); michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: static void michael@0: DEBUG_CheckUnwrapSafety(HandleObject obj, js::Wrapper *handler, michael@0: JSCompartment *origin, JSCompartment *target) michael@0: { michael@0: if (AccessCheck::isChrome(target) || xpc::IsUniversalXPConnectEnabled(target)) { michael@0: // If the caller is chrome (or effectively so), unwrap should always be allowed. michael@0: MOZ_ASSERT(!handler->hasSecurityPolicy()); michael@0: } else if (handler == &FilteringWrapper::singleton) { michael@0: // We explicitly use a SecurityWrapper to protect privileged callers from michael@0: // less-privileged objects that they should never see. Skip the check in michael@0: // this case. michael@0: } else { michael@0: // Otherwise, it should depend on whether the target subsumes the origin. michael@0: MOZ_ASSERT(handler->hasSecurityPolicy() == !AccessCheck::subsumesConsideringDomain(target, origin)); michael@0: } michael@0: } michael@0: #else michael@0: #define DEBUG_CheckUnwrapSafety(obj, handler, origin, target) {} michael@0: #endif michael@0: michael@0: static Wrapper * michael@0: SelectWrapper(bool securityWrapper, bool wantXrays, XrayType xrayType, michael@0: bool waiveXrays, bool originIsXBLScope) michael@0: { michael@0: // Waived Xray uses a modified CCW that has transparent behavior but michael@0: // transitively waives Xrays on arguments. michael@0: if (waiveXrays) { michael@0: MOZ_ASSERT(!securityWrapper); michael@0: return &WaiveXrayWrapper::singleton; michael@0: } michael@0: michael@0: // If we don't want or can't use Xrays, select a wrapper that's either michael@0: // entirely transparent or entirely opaque. michael@0: if (!wantXrays || xrayType == NotXray) { michael@0: if (!securityWrapper) michael@0: return &CrossCompartmentWrapper::singleton; michael@0: // In general, we don't want opaque function wrappers to be callable. michael@0: // But in the case of XBL, we rely on content being able to invoke michael@0: // functions exposed from the XBL scope. We could remove this exception, michael@0: // if needed, by using ExportFunction to generate the content-side michael@0: // representations of XBL methods. michael@0: else if (originIsXBLScope) michael@0: return &FilteringWrapper::singleton; michael@0: return &FilteringWrapper::singleton; michael@0: } michael@0: michael@0: // Ok, we're using Xray. If this isn't a security wrapper, use the permissive michael@0: // version and skip the filter. michael@0: if (!securityWrapper) { michael@0: if (xrayType == XrayForWrappedNative) michael@0: return &PermissiveXrayXPCWN::singleton; michael@0: else if (xrayType == XrayForDOMObject) michael@0: return &PermissiveXrayDOM::singleton; michael@0: MOZ_ASSERT(xrayType == XrayForJSObject); michael@0: return &PermissiveXrayJS::singleton; michael@0: } michael@0: michael@0: // This is a security wrapper. Use the security versions and filter. michael@0: if (xrayType == XrayForWrappedNative) michael@0: return &FilteringWrapper::singleton; michael@0: else if (xrayType == XrayForDOMObject) michael@0: return &FilteringWrapper::singleton; michael@0: // There's never any reason to expose pure JS objects to non-subsuming actors. michael@0: // Just use an opaque wrapper in this case. michael@0: MOZ_ASSERT(xrayType == XrayForJSObject); michael@0: return &FilteringWrapper::singleton; michael@0: } michael@0: michael@0: JSObject * michael@0: WrapperFactory::Rewrap(JSContext *cx, HandleObject existing, HandleObject obj, michael@0: HandleObject wrappedProto, HandleObject parent, michael@0: unsigned flags) michael@0: { michael@0: MOZ_ASSERT(!IsWrapper(obj) || michael@0: GetProxyHandler(obj) == &XrayWaiver || michael@0: js::GetObjectClass(obj)->ext.innerObject, michael@0: "wrapped object passed to rewrap"); michael@0: MOZ_ASSERT(!XrayUtils::IsXPCWNHolderClass(JS_GetClass(obj)), "trying to wrap a holder"); michael@0: MOZ_ASSERT(!js::IsInnerObject(obj)); michael@0: // We sometimes end up here after nsContentUtils has been shut down but before michael@0: // XPConnect has been shut down, so check the context stack the roundabout way. michael@0: MOZ_ASSERT(XPCJSRuntime::Get()->GetJSContextStack()->Peek() == cx); michael@0: michael@0: // Compute the information we need to select the right wrapper. michael@0: JSCompartment *origin = js::GetObjectCompartment(obj); michael@0: JSCompartment *target = js::GetContextCompartment(cx); michael@0: bool originIsChrome = AccessCheck::isChrome(origin); michael@0: bool targetIsChrome = AccessCheck::isChrome(target); michael@0: bool originSubsumesTarget = AccessCheck::subsumesConsideringDomain(origin, target); michael@0: bool targetSubsumesOrigin = AccessCheck::subsumesConsideringDomain(target, origin); michael@0: bool sameOrigin = targetSubsumesOrigin && originSubsumesTarget; michael@0: XrayType xrayType = GetXrayType(obj); michael@0: bool waiveXrayFlag = flags & WAIVE_XRAY_WRAPPER_FLAG; michael@0: michael@0: Wrapper *wrapper; michael@0: CompartmentPrivate *targetdata = EnsureCompartmentPrivate(target); michael@0: michael@0: // michael@0: // First, handle the special cases. michael@0: // michael@0: michael@0: // If UniversalXPConnect is enabled, this is just some dumb mochitest. Use michael@0: // a vanilla CCW. michael@0: if (xpc::IsUniversalXPConnectEnabled(target)) { michael@0: wrapper = &CrossCompartmentWrapper::singleton; michael@0: } michael@0: michael@0: // If this is a chrome object being exposed to content without Xrays, use michael@0: // a COW. michael@0: else if (originIsChrome && !targetIsChrome && xrayType == NotXray) { michael@0: wrapper = &ChromeObjectWrapper::singleton; michael@0: } michael@0: michael@0: // Normally, a non-xrayable non-waived content object that finds itself in michael@0: // a privileged scope is wrapped with a CrossCompartmentWrapper, even though michael@0: // the lack of a waiver _really_ should give it an opaque wrapper. This is michael@0: // a bit too entrenched to change for content-chrome, but we can at least fix michael@0: // it for XBL scopes. michael@0: // michael@0: // See bug 843829. michael@0: else if (targetSubsumesOrigin && !originSubsumesTarget && michael@0: !waiveXrayFlag && xrayType == NotXray && michael@0: IsXBLScope(target)) michael@0: { michael@0: wrapper = &FilteringWrapper::singleton; michael@0: } michael@0: michael@0: // michael@0: // Now, handle the regular cases. michael@0: // michael@0: // These are wrappers we can compute using a rule-based approach. In order michael@0: // to do so, we need to compute some parameters. michael@0: // michael@0: else { michael@0: michael@0: // The wrapper is a security wrapper (protecting the wrappee) if and michael@0: // only if the target does not subsume the origin. michael@0: bool securityWrapper = !targetSubsumesOrigin; michael@0: michael@0: // Xrays are warranted if either the target or the origin don't trust michael@0: // each other. This is generally the case, unless the two are same-origin michael@0: // and the caller has not requested same-origin Xrays. michael@0: // michael@0: // Xrays are a bidirectional protection, since it affords clarity to the michael@0: // caller and privacy to the callee. michael@0: bool wantXrays = !(sameOrigin && !targetdata->wantXrays); michael@0: michael@0: // If Xrays are warranted, the caller may waive them for non-security michael@0: // wrappers. michael@0: bool waiveXrays = wantXrays && !securityWrapper && waiveXrayFlag; michael@0: michael@0: // We have slightly different behavior for the case when the object michael@0: // being wrapped is in an XBL scope. michael@0: bool originIsXBLScope = IsXBLScope(origin); michael@0: michael@0: wrapper = SelectWrapper(securityWrapper, wantXrays, xrayType, waiveXrays, michael@0: originIsXBLScope); michael@0: } michael@0: michael@0: if (!targetSubsumesOrigin) { michael@0: // Do a belt-and-suspenders check against exposing eval()/Function() to michael@0: // non-subsuming content. michael@0: JSFunction *fun = JS_GetObjectFunction(obj); michael@0: if (fun) { michael@0: if (JS_IsBuiltinEvalFunction(fun) || JS_IsBuiltinFunctionConstructor(fun)) { michael@0: JS_ReportError(cx, "Permission denied to expose eval or Function to non-subsuming content"); michael@0: return nullptr; michael@0: } michael@0: } michael@0: } michael@0: michael@0: DEBUG_CheckUnwrapSafety(obj, wrapper, origin, target); michael@0: michael@0: if (existing) michael@0: return Wrapper::Renew(cx, existing, obj, wrapper); michael@0: michael@0: return Wrapper::New(cx, obj, parent, wrapper); michael@0: } michael@0: michael@0: // Call WaiveXrayAndWrap when you have a JS object that you don't want to be michael@0: // wrapped in an Xray wrapper. cx->compartment is the compartment that will be michael@0: // using the returned object. If the object to be wrapped is already in the michael@0: // correct compartment, then this returns the unwrapped object. michael@0: bool michael@0: WrapperFactory::WaiveXrayAndWrap(JSContext *cx, MutableHandleValue vp) michael@0: { michael@0: if (vp.isPrimitive()) michael@0: return JS_WrapValue(cx, vp); michael@0: michael@0: RootedObject obj(cx, &vp.toObject()); michael@0: if (!WaiveXrayAndWrap(cx, &obj)) michael@0: return false; michael@0: michael@0: vp.setObject(*obj); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: WrapperFactory::WaiveXrayAndWrap(JSContext *cx, MutableHandleObject argObj) michael@0: { michael@0: MOZ_ASSERT(argObj); michael@0: RootedObject obj(cx, js::UncheckedUnwrap(argObj)); michael@0: MOZ_ASSERT(!js::IsInnerObject(obj)); michael@0: if (js::IsObjectInContextCompartment(obj, cx)) { michael@0: argObj.set(obj); michael@0: return true; michael@0: } michael@0: michael@0: // Even though waivers have no effect on access by scopes that don't subsume michael@0: // the underlying object, good defense-in-depth dictates that we should avoid michael@0: // handing out waivers to callers that can't use them. The transitive waiving michael@0: // machinery unconditionally calls WaiveXrayAndWrap on return values from michael@0: // waived functions, even though the return value might be not be same-origin michael@0: // with the function. So if we find ourselves trying to create a waiver for michael@0: // |cx|, we should check whether the caller has any business with waivers michael@0: // to things in |obj|'s compartment. michael@0: JSCompartment *target = js::GetContextCompartment(cx); michael@0: JSCompartment *origin = js::GetObjectCompartment(obj); michael@0: obj = AccessCheck::subsumes(target, origin) ? WaiveXray(cx, obj) : obj; michael@0: if (!obj) michael@0: return false; michael@0: michael@0: if (!JS_WrapObject(cx, &obj)) michael@0: return false; michael@0: argObj.set(obj); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: WrapperFactory::XrayWrapperNotShadowing(JSObject *wrapper, jsid id) michael@0: { michael@0: ResolvingId *rid = ResolvingId::getResolvingIdFromWrapper(wrapper); michael@0: return rid->isXrayShadowing(id); michael@0: } michael@0: michael@0: /* michael@0: * Calls to JS_TransplantObject* should go through these helpers here so that michael@0: * waivers get fixed up properly. michael@0: */ michael@0: michael@0: static bool michael@0: FixWaiverAfterTransplant(JSContext *cx, HandleObject oldWaiver, HandleObject newobj) michael@0: { michael@0: MOZ_ASSERT(Wrapper::wrapperHandler(oldWaiver) == &XrayWaiver); michael@0: MOZ_ASSERT(!js::IsCrossCompartmentWrapper(newobj)); michael@0: michael@0: // Create a waiver in the new compartment. We know there's not one already michael@0: // because we _just_ transplanted, which means that |newobj| was either michael@0: // created from scratch, or was previously cross-compartment wrapper (which michael@0: // should have no waiver). CreateXrayWaiver asserts this. michael@0: JSObject *newWaiver = WrapperFactory::CreateXrayWaiver(cx, newobj); michael@0: if (!newWaiver) michael@0: return false; michael@0: michael@0: // Update all the cross-compartment references to oldWaiver to point to michael@0: // newWaiver. michael@0: if (!js::RemapAllWrappersForObject(cx, oldWaiver, newWaiver)) michael@0: return false; michael@0: michael@0: // There should be no same-compartment references to oldWaiver, and we michael@0: // just remapped all cross-compartment references. It's dead, so we can michael@0: // remove it from the map. michael@0: XPCWrappedNativeScope *scope = GetObjectScope(oldWaiver); michael@0: JSObject *key = Wrapper::wrappedObject(oldWaiver); michael@0: MOZ_ASSERT(scope->mWaiverWrapperMap->Find(key)); michael@0: scope->mWaiverWrapperMap->Remove(key); michael@0: return true; michael@0: } michael@0: michael@0: JSObject * michael@0: TransplantObject(JSContext *cx, JS::HandleObject origobj, JS::HandleObject target) michael@0: { michael@0: RootedObject oldWaiver(cx, WrapperFactory::GetXrayWaiver(origobj)); michael@0: RootedObject newIdentity(cx, JS_TransplantObject(cx, origobj, target)); michael@0: if (!newIdentity || !oldWaiver) michael@0: return newIdentity; michael@0: michael@0: if (!FixWaiverAfterTransplant(cx, oldWaiver, newIdentity)) michael@0: return nullptr; michael@0: return newIdentity; michael@0: } michael@0: michael@0: nsIGlobalObject * michael@0: GetNativeForGlobal(JSObject *obj) michael@0: { michael@0: MOZ_ASSERT(JS_IsGlobalObject(obj)); michael@0: if (!MaybeGetObjectScope(obj)) michael@0: return nullptr; michael@0: michael@0: // Every global needs to hold a native as its private or be a michael@0: // WebIDL object with an nsISupports DOM object. michael@0: MOZ_ASSERT((GetObjectClass(obj)->flags & (JSCLASS_PRIVATE_IS_NSISUPPORTS | michael@0: JSCLASS_HAS_PRIVATE)) || michael@0: dom::UnwrapDOMObjectToISupports(obj)); michael@0: michael@0: nsISupports *native = dom::UnwrapDOMObjectToISupports(obj); michael@0: if (!native) { michael@0: native = static_cast(js::GetObjectPrivate(obj)); michael@0: MOZ_ASSERT(native); michael@0: michael@0: // In some cases (like for windows) it is a wrapped native, michael@0: // in other cases (sandboxes, backstage passes) it's just michael@0: // a direct pointer to the native. If it's a wrapped native michael@0: // let's unwrap it first. michael@0: if (nsCOMPtr wn = do_QueryInterface(native)) { michael@0: native = wn->Native(); michael@0: } michael@0: } michael@0: michael@0: nsCOMPtr global = do_QueryInterface(native); michael@0: MOZ_ASSERT(global, "Native held by global needs to implement nsIGlobalObject!"); michael@0: michael@0: return global; michael@0: } michael@0: michael@0: }