js/xpconnect/wrappers/WrapperFactory.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/js/xpconnect/wrappers/WrapperFactory.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,627 @@
     1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
     1.5 +/* vim: set ts=8 sts=4 et sw=4 tw=99: */
     1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +#include "WaiveXrayWrapper.h"
    1.11 +#include "FilteringWrapper.h"
    1.12 +#include "XrayWrapper.h"
    1.13 +#include "AccessCheck.h"
    1.14 +#include "XPCWrapper.h"
    1.15 +#include "ChromeObjectWrapper.h"
    1.16 +#include "WrapperFactory.h"
    1.17 +
    1.18 +#include "xpcprivate.h"
    1.19 +#include "XPCMaps.h"
    1.20 +#include "mozilla/dom/BindingUtils.h"
    1.21 +#include "jsfriendapi.h"
    1.22 +#include "mozilla/Likely.h"
    1.23 +#include "nsContentUtils.h"
    1.24 +
    1.25 +using namespace JS;
    1.26 +using namespace js;
    1.27 +using namespace mozilla;
    1.28 +
    1.29 +namespace xpc {
    1.30 +
    1.31 +// When chrome pulls a naked property across the membrane using
    1.32 +// .wrappedJSObject, we want it to cross the membrane into the
    1.33 +// chrome compartment without automatically being wrapped into an
    1.34 +// X-ray wrapper. We achieve this by wrapping it into a special
    1.35 +// transparent wrapper in the origin (non-chrome) compartment. When
    1.36 +// an object with that special wrapper applied crosses into chrome,
    1.37 +// we know to not apply an X-ray wrapper.
    1.38 +Wrapper XrayWaiver(WrapperFactory::WAIVE_XRAY_WRAPPER_FLAG);
    1.39 +
    1.40 +// When objects for which we waived the X-ray wrapper cross into
    1.41 +// chrome, we wrap them into a special cross-compartment wrapper
    1.42 +// that transitively extends the waiver to all properties we get
    1.43 +// off it.
    1.44 +WaiveXrayWrapper WaiveXrayWrapper::singleton(0);
    1.45 +
    1.46 +bool
    1.47 +WrapperFactory::IsCOW(JSObject *obj)
    1.48 +{
    1.49 +    return IsWrapper(obj) &&
    1.50 +           Wrapper::wrapperHandler(obj) == &ChromeObjectWrapper::singleton;
    1.51 +}
    1.52 +
    1.53 +JSObject *
    1.54 +WrapperFactory::GetXrayWaiver(HandleObject obj)
    1.55 +{
    1.56 +    // Object should come fully unwrapped but outerized.
    1.57 +    MOZ_ASSERT(obj == UncheckedUnwrap(obj));
    1.58 +    MOZ_ASSERT(!js::GetObjectClass(obj)->ext.outerObject);
    1.59 +    XPCWrappedNativeScope *scope = GetObjectScope(obj);
    1.60 +    MOZ_ASSERT(scope);
    1.61 +
    1.62 +    if (!scope->mWaiverWrapperMap)
    1.63 +        return nullptr;
    1.64 +
    1.65 +    JSObject* xrayWaiver = scope->mWaiverWrapperMap->Find(obj);
    1.66 +    if (xrayWaiver)
    1.67 +        JS::ExposeObjectToActiveJS(xrayWaiver);
    1.68 +
    1.69 +    return xrayWaiver;
    1.70 +}
    1.71 +
    1.72 +JSObject *
    1.73 +WrapperFactory::CreateXrayWaiver(JSContext *cx, HandleObject obj)
    1.74 +{
    1.75 +    // The caller is required to have already done a lookup.
    1.76 +    // NB: This implictly performs the assertions of GetXrayWaiver.
    1.77 +    MOZ_ASSERT(!GetXrayWaiver(obj));
    1.78 +    XPCWrappedNativeScope *scope = GetObjectScope(obj);
    1.79 +
    1.80 +    JSAutoCompartment ac(cx, obj);
    1.81 +    JSObject *waiver = Wrapper::New(cx, obj,
    1.82 +                                    JS_GetGlobalForObject(cx, obj),
    1.83 +                                    &XrayWaiver);
    1.84 +    if (!waiver)
    1.85 +        return nullptr;
    1.86 +
    1.87 +    // Add the new waiver to the map. It's important that we only ever have
    1.88 +    // one waiver for the lifetime of the target object.
    1.89 +    if (!scope->mWaiverWrapperMap) {
    1.90 +        scope->mWaiverWrapperMap =
    1.91 +          JSObject2JSObjectMap::newMap(XPC_WRAPPER_MAP_SIZE);
    1.92 +        MOZ_ASSERT(scope->mWaiverWrapperMap);
    1.93 +    }
    1.94 +    if (!scope->mWaiverWrapperMap->Add(cx, obj, waiver))
    1.95 +        return nullptr;
    1.96 +    return waiver;
    1.97 +}
    1.98 +
    1.99 +JSObject *
   1.100 +WrapperFactory::WaiveXray(JSContext *cx, JSObject *objArg)
   1.101 +{
   1.102 +    RootedObject obj(cx, objArg);
   1.103 +    obj = UncheckedUnwrap(obj);
   1.104 +    MOZ_ASSERT(!js::IsInnerObject(obj));
   1.105 +
   1.106 +    JSObject *waiver = GetXrayWaiver(obj);
   1.107 +    if (waiver)
   1.108 +        return waiver;
   1.109 +    return CreateXrayWaiver(cx, obj);
   1.110 +}
   1.111 +
   1.112 +// DoubleWrap is called from PrepareForWrapping to maintain the state that
   1.113 +// we're supposed to waive Xray wrappers for the given on. On entrance, it
   1.114 +// expects |cx->compartment != obj->compartment()|. The returned object will
   1.115 +// be in the same compartment as |obj|.
   1.116 +JSObject *
   1.117 +WrapperFactory::DoubleWrap(JSContext *cx, HandleObject obj, unsigned flags)
   1.118 +{
   1.119 +    if (flags & WrapperFactory::WAIVE_XRAY_WRAPPER_FLAG) {
   1.120 +        JSAutoCompartment ac(cx, obj);
   1.121 +        return WaiveXray(cx, obj);
   1.122 +    }
   1.123 +    return obj;
   1.124 +}
   1.125 +
   1.126 +JSObject *
   1.127 +WrapperFactory::PrepareForWrapping(JSContext *cx, HandleObject scope,
   1.128 +                                   HandleObject objArg, unsigned flags)
   1.129 +{
   1.130 +    RootedObject obj(cx, objArg);
   1.131 +    // Outerize any raw inner objects at the entry point here, so that we don't
   1.132 +    // have to worry about them for the rest of the wrapping code.
   1.133 +    if (js::IsInnerObject(obj)) {
   1.134 +        JSAutoCompartment ac(cx, obj);
   1.135 +        obj = JS_ObjectToOuterObject(cx, obj);
   1.136 +        NS_ENSURE_TRUE(obj, nullptr);
   1.137 +        // The outerization hook wraps, which means that we can end up with a
   1.138 +        // CCW here if |obj| was a navigated-away-from inner. Strip any CCWs.
   1.139 +        obj = js::UncheckedUnwrap(obj);
   1.140 +        MOZ_ASSERT(js::IsOuterObject(obj));
   1.141 +    }
   1.142 +
   1.143 +    // If we've got an outer window, there's nothing special that needs to be
   1.144 +    // done here, and we can move on to the next phase of wrapping. We handle
   1.145 +    // this case first to allow us to assert against wrappers below.
   1.146 +    if (js::IsOuterObject(obj))
   1.147 +        return DoubleWrap(cx, obj, flags);
   1.148 +
   1.149 +    // Here are the rules for wrapping:
   1.150 +    // We should never get a proxy here (the JS engine unwraps those for us).
   1.151 +    MOZ_ASSERT(!IsWrapper(obj));
   1.152 +
   1.153 +    // If the object being wrapped is a prototype for a standard class and the
   1.154 +    // wrapper does not subsumes the wrappee, use the one from the content
   1.155 +    // compartment. This is generally safer all-around, and in the COW case this
   1.156 +    // lets us safely take advantage of things like .forEach() via the
   1.157 +    // ChromeObjectWrapper machinery.
   1.158 +    //
   1.159 +    // If the prototype chain of chrome object |obj| looks like this:
   1.160 +    //
   1.161 +    // obj => foo => bar => chromeWin.StandardClass.prototype
   1.162 +    //
   1.163 +    // The prototype chain of COW(obj) looks lke this:
   1.164 +    //
   1.165 +    // COW(obj) => COW(foo) => COW(bar) => contentWin.StandardClass.prototype
   1.166 +    //
   1.167 +    // NB: We now remap all non-subsuming access of standard prototypes.
   1.168 +    //
   1.169 +    // NB: We need to ignore domain here so that the security relationship we
   1.170 +    // compute here can't change over time. See the comment above the other
   1.171 +    // subsumes call below.
   1.172 +    bool subsumes = AccessCheck::subsumes(js::GetContextCompartment(cx),
   1.173 +                                          js::GetObjectCompartment(obj));
   1.174 +    XrayType xrayType = GetXrayType(obj);
   1.175 +    if (!subsumes && xrayType == NotXray) {
   1.176 +        JSProtoKey key = JSProto_Null;
   1.177 +        {
   1.178 +            JSAutoCompartment ac(cx, obj);
   1.179 +            key = IdentifyStandardPrototype(obj);
   1.180 +        }
   1.181 +        if (key != JSProto_Null) {
   1.182 +            RootedObject homeProto(cx);
   1.183 +            if (!JS_GetClassPrototype(cx, key, &homeProto))
   1.184 +                return nullptr;
   1.185 +            MOZ_ASSERT(homeProto);
   1.186 +            // No need to double-wrap here. We should never have waivers to
   1.187 +            // COWs.
   1.188 +            return homeProto;
   1.189 +        }
   1.190 +    }
   1.191 +
   1.192 +    // Now, our object is ready to be wrapped, but several objects (notably
   1.193 +    // nsJSIIDs) have a wrapper per scope. If we are about to wrap one of
   1.194 +    // those objects in a security wrapper, then we need to hand back the
   1.195 +    // wrapper for the new scope instead. Also, global objects don't move
   1.196 +    // between scopes so for those we also want to return the wrapper. So...
   1.197 +    if (!IS_WN_REFLECTOR(obj) || !js::GetObjectParent(obj))
   1.198 +        return DoubleWrap(cx, obj, flags);
   1.199 +
   1.200 +    XPCWrappedNative *wn = XPCWrappedNative::Get(obj);
   1.201 +
   1.202 +    JSAutoCompartment ac(cx, obj);
   1.203 +    XPCCallContext ccx(JS_CALLER, cx, obj);
   1.204 +    RootedObject wrapScope(cx, scope);
   1.205 +
   1.206 +    {
   1.207 +        if (NATIVE_HAS_FLAG(&ccx, WantPreCreate)) {
   1.208 +            // We have a precreate hook. This object might enforce that we only
   1.209 +            // ever create JS object for it.
   1.210 +
   1.211 +            // Note: this penalizes objects that only have one wrapper, but are
   1.212 +            // being accessed across compartments. We would really prefer to
   1.213 +            // replace the above code with a test that says "do you only have one
   1.214 +            // wrapper?"
   1.215 +            nsresult rv = wn->GetScriptableInfo()->GetCallback()->
   1.216 +                PreCreate(wn->Native(), cx, scope, wrapScope.address());
   1.217 +            NS_ENSURE_SUCCESS(rv, DoubleWrap(cx, obj, flags));
   1.218 +
   1.219 +            // If the handed back scope differs from the passed-in scope and is in
   1.220 +            // a separate compartment, then this object is explicitly requesting
   1.221 +            // that we don't create a second JS object for it: create a security
   1.222 +            // wrapper.
   1.223 +            if (js::GetObjectCompartment(scope) != js::GetObjectCompartment(wrapScope))
   1.224 +                return DoubleWrap(cx, obj, flags);
   1.225 +
   1.226 +            RootedObject currentScope(cx, JS_GetGlobalForObject(cx, obj));
   1.227 +            if (MOZ_UNLIKELY(wrapScope != currentScope)) {
   1.228 +                // The wrapper claims it wants to be in the new scope, but
   1.229 +                // currently has a reflection that lives in the old scope. This
   1.230 +                // can mean one of two things, both of which are rare:
   1.231 +                //
   1.232 +                // 1 - The object has a PreCreate hook (we checked for it above),
   1.233 +                // but is deciding to request one-wrapper-per-scope (rather than
   1.234 +                // one-wrapper-per-native) for some reason. Usually, a PreCreate
   1.235 +                // hook indicates one-wrapper-per-native. In this case we want to
   1.236 +                // make a new wrapper in the new scope.
   1.237 +                //
   1.238 +                // 2 - We're midway through wrapper reparenting. The document has
   1.239 +                // moved to a new scope, but |wn| hasn't been moved yet, and
   1.240 +                // we ended up calling JS_WrapObject() on its JS object. In this
   1.241 +                // case, we want to return the existing wrapper.
   1.242 +                //
   1.243 +                // So we do a trick: call PreCreate _again_, but say that we're
   1.244 +                // wrapping for the old scope, rather than the new one. If (1) is
   1.245 +                // the case, then PreCreate will return the scope we pass to it
   1.246 +                // (the old scope). If (2) is the case, PreCreate will return the
   1.247 +                // scope of the document (the new scope).
   1.248 +                RootedObject probe(cx);
   1.249 +                rv = wn->GetScriptableInfo()->GetCallback()->
   1.250 +                    PreCreate(wn->Native(), cx, currentScope, probe.address());
   1.251 +
   1.252 +                // Check for case (2).
   1.253 +                if (probe != currentScope) {
   1.254 +                    MOZ_ASSERT(probe == wrapScope);
   1.255 +                    return DoubleWrap(cx, obj, flags);
   1.256 +                }
   1.257 +
   1.258 +                // Ok, must be case (1). Fall through and create a new wrapper.
   1.259 +            }
   1.260 +
   1.261 +            // Nasty hack for late-breaking bug 781476. This will confuse identity checks,
   1.262 +            // but it's probably better than any of our alternatives.
   1.263 +            //
   1.264 +            // Note: We have to ignore domain here. The JS engine assumes that, given a
   1.265 +            // compartment c, if c->wrap(x) returns a cross-compartment wrapper at time t0,
   1.266 +            // it will also return a cross-compartment wrapper for any time t1 > t0 unless
   1.267 +            // an explicit transplant is performed. In particular, wrapper recomputation
   1.268 +            // assumes that recomputing a wrapper will always result in a wrapper.
   1.269 +            //
   1.270 +            // This doesn't actually pose a security issue, because we'll still compute
   1.271 +            // the correct (opaque) wrapper for the object below given the security
   1.272 +            // characteristics of the two compartments.
   1.273 +            if (!AccessCheck::isChrome(js::GetObjectCompartment(wrapScope)) &&
   1.274 +                 AccessCheck::subsumes(js::GetObjectCompartment(wrapScope),
   1.275 +                                       js::GetObjectCompartment(obj)))
   1.276 +            {
   1.277 +                return DoubleWrap(cx, obj, flags);
   1.278 +            }
   1.279 +        }
   1.280 +    }
   1.281 +
   1.282 +    // This public WrapNativeToJSVal API enters the compartment of 'wrapScope'
   1.283 +    // so we don't have to.
   1.284 +    RootedValue v(cx);
   1.285 +    nsresult rv =
   1.286 +        nsXPConnect::XPConnect()->WrapNativeToJSVal(cx, wrapScope, wn->Native(), nullptr,
   1.287 +                                                    &NS_GET_IID(nsISupports), false, &v);
   1.288 +    NS_ENSURE_SUCCESS(rv, nullptr);
   1.289 +
   1.290 +    obj.set(&v.toObject());
   1.291 +    MOZ_ASSERT(IS_WN_REFLECTOR(obj), "bad object");
   1.292 +
   1.293 +    // Because the underlying native didn't have a PreCreate hook, we had
   1.294 +    // to a new (or possibly pre-existing) XPCWN in our compartment.
   1.295 +    // This could be a problem for chrome code that passes XPCOM objects
   1.296 +    // across compartments, because the effects of QI would disappear across
   1.297 +    // compartments.
   1.298 +    //
   1.299 +    // So whenever we pull an XPCWN across compartments in this manner, we
   1.300 +    // give the destination object the union of the two native sets. We try
   1.301 +    // to do this cleverly in the common case to avoid too much overhead.
   1.302 +    XPCWrappedNative *newwn = XPCWrappedNative::Get(obj);
   1.303 +    XPCNativeSet *unionSet = XPCNativeSet::GetNewOrUsed(newwn->GetSet(),
   1.304 +                                                        wn->GetSet(), false);
   1.305 +    if (!unionSet)
   1.306 +        return nullptr;
   1.307 +    newwn->SetSet(unionSet);
   1.308 +
   1.309 +    return DoubleWrap(cx, obj, flags);
   1.310 +}
   1.311 +
   1.312 +#ifdef DEBUG
   1.313 +static void
   1.314 +DEBUG_CheckUnwrapSafety(HandleObject obj, js::Wrapper *handler,
   1.315 +                        JSCompartment *origin, JSCompartment *target)
   1.316 +{
   1.317 +    if (AccessCheck::isChrome(target) || xpc::IsUniversalXPConnectEnabled(target)) {
   1.318 +        // If the caller is chrome (or effectively so), unwrap should always be allowed.
   1.319 +        MOZ_ASSERT(!handler->hasSecurityPolicy());
   1.320 +    } else if (handler == &FilteringWrapper<CrossCompartmentSecurityWrapper, GentlyOpaque>::singleton) {
   1.321 +        // We explicitly use a SecurityWrapper to protect privileged callers from
   1.322 +        // less-privileged objects that they should never see. Skip the check in
   1.323 +        // this case.
   1.324 +    } else {
   1.325 +        // Otherwise, it should depend on whether the target subsumes the origin.
   1.326 +        MOZ_ASSERT(handler->hasSecurityPolicy() == !AccessCheck::subsumesConsideringDomain(target, origin));
   1.327 +    }
   1.328 +}
   1.329 +#else
   1.330 +#define DEBUG_CheckUnwrapSafety(obj, handler, origin, target) {}
   1.331 +#endif
   1.332 +
   1.333 +static Wrapper *
   1.334 +SelectWrapper(bool securityWrapper, bool wantXrays, XrayType xrayType,
   1.335 +              bool waiveXrays, bool originIsXBLScope)
   1.336 +{
   1.337 +    // Waived Xray uses a modified CCW that has transparent behavior but
   1.338 +    // transitively waives Xrays on arguments.
   1.339 +    if (waiveXrays) {
   1.340 +        MOZ_ASSERT(!securityWrapper);
   1.341 +        return &WaiveXrayWrapper::singleton;
   1.342 +    }
   1.343 +
   1.344 +    // If we don't want or can't use Xrays, select a wrapper that's either
   1.345 +    // entirely transparent or entirely opaque.
   1.346 +    if (!wantXrays || xrayType == NotXray) {
   1.347 +        if (!securityWrapper)
   1.348 +            return &CrossCompartmentWrapper::singleton;
   1.349 +        // In general, we don't want opaque function wrappers to be callable.
   1.350 +        // But in the case of XBL, we rely on content being able to invoke
   1.351 +        // functions exposed from the XBL scope. We could remove this exception,
   1.352 +        // if needed, by using ExportFunction to generate the content-side
   1.353 +        // representations of XBL methods.
   1.354 +        else if (originIsXBLScope)
   1.355 +            return &FilteringWrapper<CrossCompartmentSecurityWrapper, OpaqueWithCall>::singleton;
   1.356 +        return &FilteringWrapper<CrossCompartmentSecurityWrapper, Opaque>::singleton;
   1.357 +    }
   1.358 +
   1.359 +    // Ok, we're using Xray. If this isn't a security wrapper, use the permissive
   1.360 +    // version and skip the filter.
   1.361 +    if (!securityWrapper) {
   1.362 +        if (xrayType == XrayForWrappedNative)
   1.363 +            return &PermissiveXrayXPCWN::singleton;
   1.364 +        else if (xrayType == XrayForDOMObject)
   1.365 +            return &PermissiveXrayDOM::singleton;
   1.366 +        MOZ_ASSERT(xrayType == XrayForJSObject);
   1.367 +        return &PermissiveXrayJS::singleton;
   1.368 +    }
   1.369 +
   1.370 +    // This is a security wrapper. Use the security versions and filter.
   1.371 +    if (xrayType == XrayForWrappedNative)
   1.372 +        return &FilteringWrapper<SecurityXrayXPCWN,
   1.373 +                                 CrossOriginAccessiblePropertiesOnly>::singleton;
   1.374 +    else if (xrayType == XrayForDOMObject)
   1.375 +        return &FilteringWrapper<SecurityXrayDOM,
   1.376 +                                 CrossOriginAccessiblePropertiesOnly>::singleton;
   1.377 +    // There's never any reason to expose pure JS objects to non-subsuming actors.
   1.378 +    // Just use an opaque wrapper in this case.
   1.379 +    MOZ_ASSERT(xrayType == XrayForJSObject);
   1.380 +    return &FilteringWrapper<CrossCompartmentSecurityWrapper, Opaque>::singleton;
   1.381 +}
   1.382 +
   1.383 +JSObject *
   1.384 +WrapperFactory::Rewrap(JSContext *cx, HandleObject existing, HandleObject obj,
   1.385 +                       HandleObject wrappedProto, HandleObject parent,
   1.386 +                       unsigned flags)
   1.387 +{
   1.388 +    MOZ_ASSERT(!IsWrapper(obj) ||
   1.389 +               GetProxyHandler(obj) == &XrayWaiver ||
   1.390 +               js::GetObjectClass(obj)->ext.innerObject,
   1.391 +               "wrapped object passed to rewrap");
   1.392 +    MOZ_ASSERT(!XrayUtils::IsXPCWNHolderClass(JS_GetClass(obj)), "trying to wrap a holder");
   1.393 +    MOZ_ASSERT(!js::IsInnerObject(obj));
   1.394 +    // We sometimes end up here after nsContentUtils has been shut down but before
   1.395 +    // XPConnect has been shut down, so check the context stack the roundabout way.
   1.396 +    MOZ_ASSERT(XPCJSRuntime::Get()->GetJSContextStack()->Peek() == cx);
   1.397 +
   1.398 +    // Compute the information we need to select the right wrapper.
   1.399 +    JSCompartment *origin = js::GetObjectCompartment(obj);
   1.400 +    JSCompartment *target = js::GetContextCompartment(cx);
   1.401 +    bool originIsChrome = AccessCheck::isChrome(origin);
   1.402 +    bool targetIsChrome = AccessCheck::isChrome(target);
   1.403 +    bool originSubsumesTarget = AccessCheck::subsumesConsideringDomain(origin, target);
   1.404 +    bool targetSubsumesOrigin = AccessCheck::subsumesConsideringDomain(target, origin);
   1.405 +    bool sameOrigin = targetSubsumesOrigin && originSubsumesTarget;
   1.406 +    XrayType xrayType = GetXrayType(obj);
   1.407 +    bool waiveXrayFlag = flags & WAIVE_XRAY_WRAPPER_FLAG;
   1.408 +
   1.409 +    Wrapper *wrapper;
   1.410 +    CompartmentPrivate *targetdata = EnsureCompartmentPrivate(target);
   1.411 +
   1.412 +    //
   1.413 +    // First, handle the special cases.
   1.414 +    //
   1.415 +
   1.416 +    // If UniversalXPConnect is enabled, this is just some dumb mochitest. Use
   1.417 +    // a vanilla CCW.
   1.418 +    if (xpc::IsUniversalXPConnectEnabled(target)) {
   1.419 +        wrapper = &CrossCompartmentWrapper::singleton;
   1.420 +    }
   1.421 +
   1.422 +    // If this is a chrome object being exposed to content without Xrays, use
   1.423 +    // a COW.
   1.424 +    else if (originIsChrome && !targetIsChrome && xrayType == NotXray) {
   1.425 +        wrapper = &ChromeObjectWrapper::singleton;
   1.426 +    }
   1.427 +
   1.428 +    // Normally, a non-xrayable non-waived content object that finds itself in
   1.429 +    // a privileged scope is wrapped with a CrossCompartmentWrapper, even though
   1.430 +    // the lack of a waiver _really_ should give it an opaque wrapper. This is
   1.431 +    // a bit too entrenched to change for content-chrome, but we can at least fix
   1.432 +    // it for XBL scopes.
   1.433 +    //
   1.434 +    // See bug 843829.
   1.435 +    else if (targetSubsumesOrigin && !originSubsumesTarget &&
   1.436 +             !waiveXrayFlag && xrayType == NotXray &&
   1.437 +             IsXBLScope(target))
   1.438 +    {
   1.439 +        wrapper = &FilteringWrapper<CrossCompartmentSecurityWrapper, GentlyOpaque>::singleton;
   1.440 +    }
   1.441 +
   1.442 +    //
   1.443 +    // Now, handle the regular cases.
   1.444 +    //
   1.445 +    // These are wrappers we can compute using a rule-based approach. In order
   1.446 +    // to do so, we need to compute some parameters.
   1.447 +    //
   1.448 +    else {
   1.449 +
   1.450 +        // The wrapper is a security wrapper (protecting the wrappee) if and
   1.451 +        // only if the target does not subsume the origin.
   1.452 +        bool securityWrapper = !targetSubsumesOrigin;
   1.453 +
   1.454 +        // Xrays are warranted if either the target or the origin don't trust
   1.455 +        // each other. This is generally the case, unless the two are same-origin
   1.456 +        // and the caller has not requested same-origin Xrays.
   1.457 +        //
   1.458 +        // Xrays are a bidirectional protection, since it affords clarity to the
   1.459 +        // caller and privacy to the callee.
   1.460 +        bool wantXrays = !(sameOrigin && !targetdata->wantXrays);
   1.461 +
   1.462 +        // If Xrays are warranted, the caller may waive them for non-security
   1.463 +        // wrappers.
   1.464 +        bool waiveXrays = wantXrays && !securityWrapper && waiveXrayFlag;
   1.465 +
   1.466 +        // We have slightly different behavior for the case when the object
   1.467 +        // being wrapped is in an XBL scope.
   1.468 +        bool originIsXBLScope = IsXBLScope(origin);
   1.469 +
   1.470 +        wrapper = SelectWrapper(securityWrapper, wantXrays, xrayType, waiveXrays,
   1.471 +                                originIsXBLScope);
   1.472 +    }
   1.473 +
   1.474 +    if (!targetSubsumesOrigin) {
   1.475 +        // Do a belt-and-suspenders check against exposing eval()/Function() to
   1.476 +        // non-subsuming content.
   1.477 +        JSFunction *fun = JS_GetObjectFunction(obj);
   1.478 +        if (fun) {
   1.479 +            if (JS_IsBuiltinEvalFunction(fun) || JS_IsBuiltinFunctionConstructor(fun)) {
   1.480 +                JS_ReportError(cx, "Permission denied to expose eval or Function to non-subsuming content");
   1.481 +                return nullptr;
   1.482 +            }
   1.483 +        }
   1.484 +    }
   1.485 +
   1.486 +    DEBUG_CheckUnwrapSafety(obj, wrapper, origin, target);
   1.487 +
   1.488 +    if (existing)
   1.489 +        return Wrapper::Renew(cx, existing, obj, wrapper);
   1.490 +
   1.491 +    return Wrapper::New(cx, obj, parent, wrapper);
   1.492 +}
   1.493 +
   1.494 +// Call WaiveXrayAndWrap when you have a JS object that you don't want to be
   1.495 +// wrapped in an Xray wrapper. cx->compartment is the compartment that will be
   1.496 +// using the returned object. If the object to be wrapped is already in the
   1.497 +// correct compartment, then this returns the unwrapped object.
   1.498 +bool
   1.499 +WrapperFactory::WaiveXrayAndWrap(JSContext *cx, MutableHandleValue vp)
   1.500 +{
   1.501 +    if (vp.isPrimitive())
   1.502 +        return JS_WrapValue(cx, vp);
   1.503 +
   1.504 +    RootedObject obj(cx, &vp.toObject());
   1.505 +    if (!WaiveXrayAndWrap(cx, &obj))
   1.506 +        return false;
   1.507 +
   1.508 +    vp.setObject(*obj);
   1.509 +    return true;
   1.510 +}
   1.511 +
   1.512 +bool
   1.513 +WrapperFactory::WaiveXrayAndWrap(JSContext *cx, MutableHandleObject argObj)
   1.514 +{
   1.515 +    MOZ_ASSERT(argObj);
   1.516 +    RootedObject obj(cx, js::UncheckedUnwrap(argObj));
   1.517 +    MOZ_ASSERT(!js::IsInnerObject(obj));
   1.518 +    if (js::IsObjectInContextCompartment(obj, cx)) {
   1.519 +        argObj.set(obj);
   1.520 +        return true;
   1.521 +    }
   1.522 +
   1.523 +    // Even though waivers have no effect on access by scopes that don't subsume
   1.524 +    // the underlying object, good defense-in-depth dictates that we should avoid
   1.525 +    // handing out waivers to callers that can't use them. The transitive waiving
   1.526 +    // machinery unconditionally calls WaiveXrayAndWrap on return values from
   1.527 +    // waived functions, even though the return value might be not be same-origin
   1.528 +    // with the function. So if we find ourselves trying to create a waiver for
   1.529 +    // |cx|, we should check whether the caller has any business with waivers
   1.530 +    // to things in |obj|'s compartment.
   1.531 +    JSCompartment *target = js::GetContextCompartment(cx);
   1.532 +    JSCompartment *origin = js::GetObjectCompartment(obj);
   1.533 +    obj = AccessCheck::subsumes(target, origin) ? WaiveXray(cx, obj) : obj;
   1.534 +    if (!obj)
   1.535 +        return false;
   1.536 +
   1.537 +    if (!JS_WrapObject(cx, &obj))
   1.538 +        return false;
   1.539 +    argObj.set(obj);
   1.540 +    return true;
   1.541 +}
   1.542 +
   1.543 +bool
   1.544 +WrapperFactory::XrayWrapperNotShadowing(JSObject *wrapper, jsid id)
   1.545 +{
   1.546 +    ResolvingId *rid = ResolvingId::getResolvingIdFromWrapper(wrapper);
   1.547 +    return rid->isXrayShadowing(id);
   1.548 +}
   1.549 +
   1.550 +/*
   1.551 + * Calls to JS_TransplantObject* should go through these helpers here so that
   1.552 + * waivers get fixed up properly.
   1.553 + */
   1.554 +
   1.555 +static bool
   1.556 +FixWaiverAfterTransplant(JSContext *cx, HandleObject oldWaiver, HandleObject newobj)
   1.557 +{
   1.558 +    MOZ_ASSERT(Wrapper::wrapperHandler(oldWaiver) == &XrayWaiver);
   1.559 +    MOZ_ASSERT(!js::IsCrossCompartmentWrapper(newobj));
   1.560 +
   1.561 +    // Create a waiver in the new compartment. We know there's not one already
   1.562 +    // because we _just_ transplanted, which means that |newobj| was either
   1.563 +    // created from scratch, or was previously cross-compartment wrapper (which
   1.564 +    // should have no waiver). CreateXrayWaiver asserts this.
   1.565 +    JSObject *newWaiver = WrapperFactory::CreateXrayWaiver(cx, newobj);
   1.566 +    if (!newWaiver)
   1.567 +        return false;
   1.568 +
   1.569 +    // Update all the cross-compartment references to oldWaiver to point to
   1.570 +    // newWaiver.
   1.571 +    if (!js::RemapAllWrappersForObject(cx, oldWaiver, newWaiver))
   1.572 +        return false;
   1.573 +
   1.574 +    // There should be no same-compartment references to oldWaiver, and we
   1.575 +    // just remapped all cross-compartment references. It's dead, so we can
   1.576 +    // remove it from the map.
   1.577 +    XPCWrappedNativeScope *scope = GetObjectScope(oldWaiver);
   1.578 +    JSObject *key = Wrapper::wrappedObject(oldWaiver);
   1.579 +    MOZ_ASSERT(scope->mWaiverWrapperMap->Find(key));
   1.580 +    scope->mWaiverWrapperMap->Remove(key);
   1.581 +    return true;
   1.582 +}
   1.583 +
   1.584 +JSObject *
   1.585 +TransplantObject(JSContext *cx, JS::HandleObject origobj, JS::HandleObject target)
   1.586 +{
   1.587 +    RootedObject oldWaiver(cx, WrapperFactory::GetXrayWaiver(origobj));
   1.588 +    RootedObject newIdentity(cx, JS_TransplantObject(cx, origobj, target));
   1.589 +    if (!newIdentity || !oldWaiver)
   1.590 +       return newIdentity;
   1.591 +
   1.592 +    if (!FixWaiverAfterTransplant(cx, oldWaiver, newIdentity))
   1.593 +        return nullptr;
   1.594 +    return newIdentity;
   1.595 +}
   1.596 +
   1.597 +nsIGlobalObject *
   1.598 +GetNativeForGlobal(JSObject *obj)
   1.599 +{
   1.600 +    MOZ_ASSERT(JS_IsGlobalObject(obj));
   1.601 +    if (!MaybeGetObjectScope(obj))
   1.602 +        return nullptr;
   1.603 +
   1.604 +    // Every global needs to hold a native as its private or be a
   1.605 +    // WebIDL object with an nsISupports DOM object.
   1.606 +    MOZ_ASSERT((GetObjectClass(obj)->flags & (JSCLASS_PRIVATE_IS_NSISUPPORTS |
   1.607 +                                             JSCLASS_HAS_PRIVATE)) ||
   1.608 +               dom::UnwrapDOMObjectToISupports(obj));
   1.609 +
   1.610 +    nsISupports *native = dom::UnwrapDOMObjectToISupports(obj);
   1.611 +    if (!native) {
   1.612 +        native = static_cast<nsISupports *>(js::GetObjectPrivate(obj));
   1.613 +        MOZ_ASSERT(native);
   1.614 +
   1.615 +        // In some cases (like for windows) it is a wrapped native,
   1.616 +        // in other cases (sandboxes, backstage passes) it's just
   1.617 +        // a direct pointer to the native. If it's a wrapped native
   1.618 +        // let's unwrap it first.
   1.619 +        if (nsCOMPtr<nsIXPConnectWrappedNative> wn = do_QueryInterface(native)) {
   1.620 +            native = wn->Native();
   1.621 +        }
   1.622 +    }
   1.623 +
   1.624 +    nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(native);
   1.625 +    MOZ_ASSERT(global, "Native held by global needs to implement nsIGlobalObject!");
   1.626 +
   1.627 +    return global;
   1.628 +}
   1.629 +
   1.630 +}

mercurial