js/xpconnect/wrappers/WrapperFactory.cpp

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

michael@0 1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
michael@0 2 /* vim: set ts=8 sts=4 et sw=4 tw=99: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 #include "WaiveXrayWrapper.h"
michael@0 8 #include "FilteringWrapper.h"
michael@0 9 #include "XrayWrapper.h"
michael@0 10 #include "AccessCheck.h"
michael@0 11 #include "XPCWrapper.h"
michael@0 12 #include "ChromeObjectWrapper.h"
michael@0 13 #include "WrapperFactory.h"
michael@0 14
michael@0 15 #include "xpcprivate.h"
michael@0 16 #include "XPCMaps.h"
michael@0 17 #include "mozilla/dom/BindingUtils.h"
michael@0 18 #include "jsfriendapi.h"
michael@0 19 #include "mozilla/Likely.h"
michael@0 20 #include "nsContentUtils.h"
michael@0 21
michael@0 22 using namespace JS;
michael@0 23 using namespace js;
michael@0 24 using namespace mozilla;
michael@0 25
michael@0 26 namespace xpc {
michael@0 27
michael@0 28 // When chrome pulls a naked property across the membrane using
michael@0 29 // .wrappedJSObject, we want it to cross the membrane into the
michael@0 30 // chrome compartment without automatically being wrapped into an
michael@0 31 // X-ray wrapper. We achieve this by wrapping it into a special
michael@0 32 // transparent wrapper in the origin (non-chrome) compartment. When
michael@0 33 // an object with that special wrapper applied crosses into chrome,
michael@0 34 // we know to not apply an X-ray wrapper.
michael@0 35 Wrapper XrayWaiver(WrapperFactory::WAIVE_XRAY_WRAPPER_FLAG);
michael@0 36
michael@0 37 // When objects for which we waived the X-ray wrapper cross into
michael@0 38 // chrome, we wrap them into a special cross-compartment wrapper
michael@0 39 // that transitively extends the waiver to all properties we get
michael@0 40 // off it.
michael@0 41 WaiveXrayWrapper WaiveXrayWrapper::singleton(0);
michael@0 42
michael@0 43 bool
michael@0 44 WrapperFactory::IsCOW(JSObject *obj)
michael@0 45 {
michael@0 46 return IsWrapper(obj) &&
michael@0 47 Wrapper::wrapperHandler(obj) == &ChromeObjectWrapper::singleton;
michael@0 48 }
michael@0 49
michael@0 50 JSObject *
michael@0 51 WrapperFactory::GetXrayWaiver(HandleObject obj)
michael@0 52 {
michael@0 53 // Object should come fully unwrapped but outerized.
michael@0 54 MOZ_ASSERT(obj == UncheckedUnwrap(obj));
michael@0 55 MOZ_ASSERT(!js::GetObjectClass(obj)->ext.outerObject);
michael@0 56 XPCWrappedNativeScope *scope = GetObjectScope(obj);
michael@0 57 MOZ_ASSERT(scope);
michael@0 58
michael@0 59 if (!scope->mWaiverWrapperMap)
michael@0 60 return nullptr;
michael@0 61
michael@0 62 JSObject* xrayWaiver = scope->mWaiverWrapperMap->Find(obj);
michael@0 63 if (xrayWaiver)
michael@0 64 JS::ExposeObjectToActiveJS(xrayWaiver);
michael@0 65
michael@0 66 return xrayWaiver;
michael@0 67 }
michael@0 68
michael@0 69 JSObject *
michael@0 70 WrapperFactory::CreateXrayWaiver(JSContext *cx, HandleObject obj)
michael@0 71 {
michael@0 72 // The caller is required to have already done a lookup.
michael@0 73 // NB: This implictly performs the assertions of GetXrayWaiver.
michael@0 74 MOZ_ASSERT(!GetXrayWaiver(obj));
michael@0 75 XPCWrappedNativeScope *scope = GetObjectScope(obj);
michael@0 76
michael@0 77 JSAutoCompartment ac(cx, obj);
michael@0 78 JSObject *waiver = Wrapper::New(cx, obj,
michael@0 79 JS_GetGlobalForObject(cx, obj),
michael@0 80 &XrayWaiver);
michael@0 81 if (!waiver)
michael@0 82 return nullptr;
michael@0 83
michael@0 84 // Add the new waiver to the map. It's important that we only ever have
michael@0 85 // one waiver for the lifetime of the target object.
michael@0 86 if (!scope->mWaiverWrapperMap) {
michael@0 87 scope->mWaiverWrapperMap =
michael@0 88 JSObject2JSObjectMap::newMap(XPC_WRAPPER_MAP_SIZE);
michael@0 89 MOZ_ASSERT(scope->mWaiverWrapperMap);
michael@0 90 }
michael@0 91 if (!scope->mWaiverWrapperMap->Add(cx, obj, waiver))
michael@0 92 return nullptr;
michael@0 93 return waiver;
michael@0 94 }
michael@0 95
michael@0 96 JSObject *
michael@0 97 WrapperFactory::WaiveXray(JSContext *cx, JSObject *objArg)
michael@0 98 {
michael@0 99 RootedObject obj(cx, objArg);
michael@0 100 obj = UncheckedUnwrap(obj);
michael@0 101 MOZ_ASSERT(!js::IsInnerObject(obj));
michael@0 102
michael@0 103 JSObject *waiver = GetXrayWaiver(obj);
michael@0 104 if (waiver)
michael@0 105 return waiver;
michael@0 106 return CreateXrayWaiver(cx, obj);
michael@0 107 }
michael@0 108
michael@0 109 // DoubleWrap is called from PrepareForWrapping to maintain the state that
michael@0 110 // we're supposed to waive Xray wrappers for the given on. On entrance, it
michael@0 111 // expects |cx->compartment != obj->compartment()|. The returned object will
michael@0 112 // be in the same compartment as |obj|.
michael@0 113 JSObject *
michael@0 114 WrapperFactory::DoubleWrap(JSContext *cx, HandleObject obj, unsigned flags)
michael@0 115 {
michael@0 116 if (flags & WrapperFactory::WAIVE_XRAY_WRAPPER_FLAG) {
michael@0 117 JSAutoCompartment ac(cx, obj);
michael@0 118 return WaiveXray(cx, obj);
michael@0 119 }
michael@0 120 return obj;
michael@0 121 }
michael@0 122
michael@0 123 JSObject *
michael@0 124 WrapperFactory::PrepareForWrapping(JSContext *cx, HandleObject scope,
michael@0 125 HandleObject objArg, unsigned flags)
michael@0 126 {
michael@0 127 RootedObject obj(cx, objArg);
michael@0 128 // Outerize any raw inner objects at the entry point here, so that we don't
michael@0 129 // have to worry about them for the rest of the wrapping code.
michael@0 130 if (js::IsInnerObject(obj)) {
michael@0 131 JSAutoCompartment ac(cx, obj);
michael@0 132 obj = JS_ObjectToOuterObject(cx, obj);
michael@0 133 NS_ENSURE_TRUE(obj, nullptr);
michael@0 134 // The outerization hook wraps, which means that we can end up with a
michael@0 135 // CCW here if |obj| was a navigated-away-from inner. Strip any CCWs.
michael@0 136 obj = js::UncheckedUnwrap(obj);
michael@0 137 MOZ_ASSERT(js::IsOuterObject(obj));
michael@0 138 }
michael@0 139
michael@0 140 // If we've got an outer window, there's nothing special that needs to be
michael@0 141 // done here, and we can move on to the next phase of wrapping. We handle
michael@0 142 // this case first to allow us to assert against wrappers below.
michael@0 143 if (js::IsOuterObject(obj))
michael@0 144 return DoubleWrap(cx, obj, flags);
michael@0 145
michael@0 146 // Here are the rules for wrapping:
michael@0 147 // We should never get a proxy here (the JS engine unwraps those for us).
michael@0 148 MOZ_ASSERT(!IsWrapper(obj));
michael@0 149
michael@0 150 // If the object being wrapped is a prototype for a standard class and the
michael@0 151 // wrapper does not subsumes the wrappee, use the one from the content
michael@0 152 // compartment. This is generally safer all-around, and in the COW case this
michael@0 153 // lets us safely take advantage of things like .forEach() via the
michael@0 154 // ChromeObjectWrapper machinery.
michael@0 155 //
michael@0 156 // If the prototype chain of chrome object |obj| looks like this:
michael@0 157 //
michael@0 158 // obj => foo => bar => chromeWin.StandardClass.prototype
michael@0 159 //
michael@0 160 // The prototype chain of COW(obj) looks lke this:
michael@0 161 //
michael@0 162 // COW(obj) => COW(foo) => COW(bar) => contentWin.StandardClass.prototype
michael@0 163 //
michael@0 164 // NB: We now remap all non-subsuming access of standard prototypes.
michael@0 165 //
michael@0 166 // NB: We need to ignore domain here so that the security relationship we
michael@0 167 // compute here can't change over time. See the comment above the other
michael@0 168 // subsumes call below.
michael@0 169 bool subsumes = AccessCheck::subsumes(js::GetContextCompartment(cx),
michael@0 170 js::GetObjectCompartment(obj));
michael@0 171 XrayType xrayType = GetXrayType(obj);
michael@0 172 if (!subsumes && xrayType == NotXray) {
michael@0 173 JSProtoKey key = JSProto_Null;
michael@0 174 {
michael@0 175 JSAutoCompartment ac(cx, obj);
michael@0 176 key = IdentifyStandardPrototype(obj);
michael@0 177 }
michael@0 178 if (key != JSProto_Null) {
michael@0 179 RootedObject homeProto(cx);
michael@0 180 if (!JS_GetClassPrototype(cx, key, &homeProto))
michael@0 181 return nullptr;
michael@0 182 MOZ_ASSERT(homeProto);
michael@0 183 // No need to double-wrap here. We should never have waivers to
michael@0 184 // COWs.
michael@0 185 return homeProto;
michael@0 186 }
michael@0 187 }
michael@0 188
michael@0 189 // Now, our object is ready to be wrapped, but several objects (notably
michael@0 190 // nsJSIIDs) have a wrapper per scope. If we are about to wrap one of
michael@0 191 // those objects in a security wrapper, then we need to hand back the
michael@0 192 // wrapper for the new scope instead. Also, global objects don't move
michael@0 193 // between scopes so for those we also want to return the wrapper. So...
michael@0 194 if (!IS_WN_REFLECTOR(obj) || !js::GetObjectParent(obj))
michael@0 195 return DoubleWrap(cx, obj, flags);
michael@0 196
michael@0 197 XPCWrappedNative *wn = XPCWrappedNative::Get(obj);
michael@0 198
michael@0 199 JSAutoCompartment ac(cx, obj);
michael@0 200 XPCCallContext ccx(JS_CALLER, cx, obj);
michael@0 201 RootedObject wrapScope(cx, scope);
michael@0 202
michael@0 203 {
michael@0 204 if (NATIVE_HAS_FLAG(&ccx, WantPreCreate)) {
michael@0 205 // We have a precreate hook. This object might enforce that we only
michael@0 206 // ever create JS object for it.
michael@0 207
michael@0 208 // Note: this penalizes objects that only have one wrapper, but are
michael@0 209 // being accessed across compartments. We would really prefer to
michael@0 210 // replace the above code with a test that says "do you only have one
michael@0 211 // wrapper?"
michael@0 212 nsresult rv = wn->GetScriptableInfo()->GetCallback()->
michael@0 213 PreCreate(wn->Native(), cx, scope, wrapScope.address());
michael@0 214 NS_ENSURE_SUCCESS(rv, DoubleWrap(cx, obj, flags));
michael@0 215
michael@0 216 // If the handed back scope differs from the passed-in scope and is in
michael@0 217 // a separate compartment, then this object is explicitly requesting
michael@0 218 // that we don't create a second JS object for it: create a security
michael@0 219 // wrapper.
michael@0 220 if (js::GetObjectCompartment(scope) != js::GetObjectCompartment(wrapScope))
michael@0 221 return DoubleWrap(cx, obj, flags);
michael@0 222
michael@0 223 RootedObject currentScope(cx, JS_GetGlobalForObject(cx, obj));
michael@0 224 if (MOZ_UNLIKELY(wrapScope != currentScope)) {
michael@0 225 // The wrapper claims it wants to be in the new scope, but
michael@0 226 // currently has a reflection that lives in the old scope. This
michael@0 227 // can mean one of two things, both of which are rare:
michael@0 228 //
michael@0 229 // 1 - The object has a PreCreate hook (we checked for it above),
michael@0 230 // but is deciding to request one-wrapper-per-scope (rather than
michael@0 231 // one-wrapper-per-native) for some reason. Usually, a PreCreate
michael@0 232 // hook indicates one-wrapper-per-native. In this case we want to
michael@0 233 // make a new wrapper in the new scope.
michael@0 234 //
michael@0 235 // 2 - We're midway through wrapper reparenting. The document has
michael@0 236 // moved to a new scope, but |wn| hasn't been moved yet, and
michael@0 237 // we ended up calling JS_WrapObject() on its JS object. In this
michael@0 238 // case, we want to return the existing wrapper.
michael@0 239 //
michael@0 240 // So we do a trick: call PreCreate _again_, but say that we're
michael@0 241 // wrapping for the old scope, rather than the new one. If (1) is
michael@0 242 // the case, then PreCreate will return the scope we pass to it
michael@0 243 // (the old scope). If (2) is the case, PreCreate will return the
michael@0 244 // scope of the document (the new scope).
michael@0 245 RootedObject probe(cx);
michael@0 246 rv = wn->GetScriptableInfo()->GetCallback()->
michael@0 247 PreCreate(wn->Native(), cx, currentScope, probe.address());
michael@0 248
michael@0 249 // Check for case (2).
michael@0 250 if (probe != currentScope) {
michael@0 251 MOZ_ASSERT(probe == wrapScope);
michael@0 252 return DoubleWrap(cx, obj, flags);
michael@0 253 }
michael@0 254
michael@0 255 // Ok, must be case (1). Fall through and create a new wrapper.
michael@0 256 }
michael@0 257
michael@0 258 // Nasty hack for late-breaking bug 781476. This will confuse identity checks,
michael@0 259 // but it's probably better than any of our alternatives.
michael@0 260 //
michael@0 261 // Note: We have to ignore domain here. The JS engine assumes that, given a
michael@0 262 // compartment c, if c->wrap(x) returns a cross-compartment wrapper at time t0,
michael@0 263 // it will also return a cross-compartment wrapper for any time t1 > t0 unless
michael@0 264 // an explicit transplant is performed. In particular, wrapper recomputation
michael@0 265 // assumes that recomputing a wrapper will always result in a wrapper.
michael@0 266 //
michael@0 267 // This doesn't actually pose a security issue, because we'll still compute
michael@0 268 // the correct (opaque) wrapper for the object below given the security
michael@0 269 // characteristics of the two compartments.
michael@0 270 if (!AccessCheck::isChrome(js::GetObjectCompartment(wrapScope)) &&
michael@0 271 AccessCheck::subsumes(js::GetObjectCompartment(wrapScope),
michael@0 272 js::GetObjectCompartment(obj)))
michael@0 273 {
michael@0 274 return DoubleWrap(cx, obj, flags);
michael@0 275 }
michael@0 276 }
michael@0 277 }
michael@0 278
michael@0 279 // This public WrapNativeToJSVal API enters the compartment of 'wrapScope'
michael@0 280 // so we don't have to.
michael@0 281 RootedValue v(cx);
michael@0 282 nsresult rv =
michael@0 283 nsXPConnect::XPConnect()->WrapNativeToJSVal(cx, wrapScope, wn->Native(), nullptr,
michael@0 284 &NS_GET_IID(nsISupports), false, &v);
michael@0 285 NS_ENSURE_SUCCESS(rv, nullptr);
michael@0 286
michael@0 287 obj.set(&v.toObject());
michael@0 288 MOZ_ASSERT(IS_WN_REFLECTOR(obj), "bad object");
michael@0 289
michael@0 290 // Because the underlying native didn't have a PreCreate hook, we had
michael@0 291 // to a new (or possibly pre-existing) XPCWN in our compartment.
michael@0 292 // This could be a problem for chrome code that passes XPCOM objects
michael@0 293 // across compartments, because the effects of QI would disappear across
michael@0 294 // compartments.
michael@0 295 //
michael@0 296 // So whenever we pull an XPCWN across compartments in this manner, we
michael@0 297 // give the destination object the union of the two native sets. We try
michael@0 298 // to do this cleverly in the common case to avoid too much overhead.
michael@0 299 XPCWrappedNative *newwn = XPCWrappedNative::Get(obj);
michael@0 300 XPCNativeSet *unionSet = XPCNativeSet::GetNewOrUsed(newwn->GetSet(),
michael@0 301 wn->GetSet(), false);
michael@0 302 if (!unionSet)
michael@0 303 return nullptr;
michael@0 304 newwn->SetSet(unionSet);
michael@0 305
michael@0 306 return DoubleWrap(cx, obj, flags);
michael@0 307 }
michael@0 308
michael@0 309 #ifdef DEBUG
michael@0 310 static void
michael@0 311 DEBUG_CheckUnwrapSafety(HandleObject obj, js::Wrapper *handler,
michael@0 312 JSCompartment *origin, JSCompartment *target)
michael@0 313 {
michael@0 314 if (AccessCheck::isChrome(target) || xpc::IsUniversalXPConnectEnabled(target)) {
michael@0 315 // If the caller is chrome (or effectively so), unwrap should always be allowed.
michael@0 316 MOZ_ASSERT(!handler->hasSecurityPolicy());
michael@0 317 } else if (handler == &FilteringWrapper<CrossCompartmentSecurityWrapper, GentlyOpaque>::singleton) {
michael@0 318 // We explicitly use a SecurityWrapper to protect privileged callers from
michael@0 319 // less-privileged objects that they should never see. Skip the check in
michael@0 320 // this case.
michael@0 321 } else {
michael@0 322 // Otherwise, it should depend on whether the target subsumes the origin.
michael@0 323 MOZ_ASSERT(handler->hasSecurityPolicy() == !AccessCheck::subsumesConsideringDomain(target, origin));
michael@0 324 }
michael@0 325 }
michael@0 326 #else
michael@0 327 #define DEBUG_CheckUnwrapSafety(obj, handler, origin, target) {}
michael@0 328 #endif
michael@0 329
michael@0 330 static Wrapper *
michael@0 331 SelectWrapper(bool securityWrapper, bool wantXrays, XrayType xrayType,
michael@0 332 bool waiveXrays, bool originIsXBLScope)
michael@0 333 {
michael@0 334 // Waived Xray uses a modified CCW that has transparent behavior but
michael@0 335 // transitively waives Xrays on arguments.
michael@0 336 if (waiveXrays) {
michael@0 337 MOZ_ASSERT(!securityWrapper);
michael@0 338 return &WaiveXrayWrapper::singleton;
michael@0 339 }
michael@0 340
michael@0 341 // If we don't want or can't use Xrays, select a wrapper that's either
michael@0 342 // entirely transparent or entirely opaque.
michael@0 343 if (!wantXrays || xrayType == NotXray) {
michael@0 344 if (!securityWrapper)
michael@0 345 return &CrossCompartmentWrapper::singleton;
michael@0 346 // In general, we don't want opaque function wrappers to be callable.
michael@0 347 // But in the case of XBL, we rely on content being able to invoke
michael@0 348 // functions exposed from the XBL scope. We could remove this exception,
michael@0 349 // if needed, by using ExportFunction to generate the content-side
michael@0 350 // representations of XBL methods.
michael@0 351 else if (originIsXBLScope)
michael@0 352 return &FilteringWrapper<CrossCompartmentSecurityWrapper, OpaqueWithCall>::singleton;
michael@0 353 return &FilteringWrapper<CrossCompartmentSecurityWrapper, Opaque>::singleton;
michael@0 354 }
michael@0 355
michael@0 356 // Ok, we're using Xray. If this isn't a security wrapper, use the permissive
michael@0 357 // version and skip the filter.
michael@0 358 if (!securityWrapper) {
michael@0 359 if (xrayType == XrayForWrappedNative)
michael@0 360 return &PermissiveXrayXPCWN::singleton;
michael@0 361 else if (xrayType == XrayForDOMObject)
michael@0 362 return &PermissiveXrayDOM::singleton;
michael@0 363 MOZ_ASSERT(xrayType == XrayForJSObject);
michael@0 364 return &PermissiveXrayJS::singleton;
michael@0 365 }
michael@0 366
michael@0 367 // This is a security wrapper. Use the security versions and filter.
michael@0 368 if (xrayType == XrayForWrappedNative)
michael@0 369 return &FilteringWrapper<SecurityXrayXPCWN,
michael@0 370 CrossOriginAccessiblePropertiesOnly>::singleton;
michael@0 371 else if (xrayType == XrayForDOMObject)
michael@0 372 return &FilteringWrapper<SecurityXrayDOM,
michael@0 373 CrossOriginAccessiblePropertiesOnly>::singleton;
michael@0 374 // There's never any reason to expose pure JS objects to non-subsuming actors.
michael@0 375 // Just use an opaque wrapper in this case.
michael@0 376 MOZ_ASSERT(xrayType == XrayForJSObject);
michael@0 377 return &FilteringWrapper<CrossCompartmentSecurityWrapper, Opaque>::singleton;
michael@0 378 }
michael@0 379
michael@0 380 JSObject *
michael@0 381 WrapperFactory::Rewrap(JSContext *cx, HandleObject existing, HandleObject obj,
michael@0 382 HandleObject wrappedProto, HandleObject parent,
michael@0 383 unsigned flags)
michael@0 384 {
michael@0 385 MOZ_ASSERT(!IsWrapper(obj) ||
michael@0 386 GetProxyHandler(obj) == &XrayWaiver ||
michael@0 387 js::GetObjectClass(obj)->ext.innerObject,
michael@0 388 "wrapped object passed to rewrap");
michael@0 389 MOZ_ASSERT(!XrayUtils::IsXPCWNHolderClass(JS_GetClass(obj)), "trying to wrap a holder");
michael@0 390 MOZ_ASSERT(!js::IsInnerObject(obj));
michael@0 391 // We sometimes end up here after nsContentUtils has been shut down but before
michael@0 392 // XPConnect has been shut down, so check the context stack the roundabout way.
michael@0 393 MOZ_ASSERT(XPCJSRuntime::Get()->GetJSContextStack()->Peek() == cx);
michael@0 394
michael@0 395 // Compute the information we need to select the right wrapper.
michael@0 396 JSCompartment *origin = js::GetObjectCompartment(obj);
michael@0 397 JSCompartment *target = js::GetContextCompartment(cx);
michael@0 398 bool originIsChrome = AccessCheck::isChrome(origin);
michael@0 399 bool targetIsChrome = AccessCheck::isChrome(target);
michael@0 400 bool originSubsumesTarget = AccessCheck::subsumesConsideringDomain(origin, target);
michael@0 401 bool targetSubsumesOrigin = AccessCheck::subsumesConsideringDomain(target, origin);
michael@0 402 bool sameOrigin = targetSubsumesOrigin && originSubsumesTarget;
michael@0 403 XrayType xrayType = GetXrayType(obj);
michael@0 404 bool waiveXrayFlag = flags & WAIVE_XRAY_WRAPPER_FLAG;
michael@0 405
michael@0 406 Wrapper *wrapper;
michael@0 407 CompartmentPrivate *targetdata = EnsureCompartmentPrivate(target);
michael@0 408
michael@0 409 //
michael@0 410 // First, handle the special cases.
michael@0 411 //
michael@0 412
michael@0 413 // If UniversalXPConnect is enabled, this is just some dumb mochitest. Use
michael@0 414 // a vanilla CCW.
michael@0 415 if (xpc::IsUniversalXPConnectEnabled(target)) {
michael@0 416 wrapper = &CrossCompartmentWrapper::singleton;
michael@0 417 }
michael@0 418
michael@0 419 // If this is a chrome object being exposed to content without Xrays, use
michael@0 420 // a COW.
michael@0 421 else if (originIsChrome && !targetIsChrome && xrayType == NotXray) {
michael@0 422 wrapper = &ChromeObjectWrapper::singleton;
michael@0 423 }
michael@0 424
michael@0 425 // Normally, a non-xrayable non-waived content object that finds itself in
michael@0 426 // a privileged scope is wrapped with a CrossCompartmentWrapper, even though
michael@0 427 // the lack of a waiver _really_ should give it an opaque wrapper. This is
michael@0 428 // a bit too entrenched to change for content-chrome, but we can at least fix
michael@0 429 // it for XBL scopes.
michael@0 430 //
michael@0 431 // See bug 843829.
michael@0 432 else if (targetSubsumesOrigin && !originSubsumesTarget &&
michael@0 433 !waiveXrayFlag && xrayType == NotXray &&
michael@0 434 IsXBLScope(target))
michael@0 435 {
michael@0 436 wrapper = &FilteringWrapper<CrossCompartmentSecurityWrapper, GentlyOpaque>::singleton;
michael@0 437 }
michael@0 438
michael@0 439 //
michael@0 440 // Now, handle the regular cases.
michael@0 441 //
michael@0 442 // These are wrappers we can compute using a rule-based approach. In order
michael@0 443 // to do so, we need to compute some parameters.
michael@0 444 //
michael@0 445 else {
michael@0 446
michael@0 447 // The wrapper is a security wrapper (protecting the wrappee) if and
michael@0 448 // only if the target does not subsume the origin.
michael@0 449 bool securityWrapper = !targetSubsumesOrigin;
michael@0 450
michael@0 451 // Xrays are warranted if either the target or the origin don't trust
michael@0 452 // each other. This is generally the case, unless the two are same-origin
michael@0 453 // and the caller has not requested same-origin Xrays.
michael@0 454 //
michael@0 455 // Xrays are a bidirectional protection, since it affords clarity to the
michael@0 456 // caller and privacy to the callee.
michael@0 457 bool wantXrays = !(sameOrigin && !targetdata->wantXrays);
michael@0 458
michael@0 459 // If Xrays are warranted, the caller may waive them for non-security
michael@0 460 // wrappers.
michael@0 461 bool waiveXrays = wantXrays && !securityWrapper && waiveXrayFlag;
michael@0 462
michael@0 463 // We have slightly different behavior for the case when the object
michael@0 464 // being wrapped is in an XBL scope.
michael@0 465 bool originIsXBLScope = IsXBLScope(origin);
michael@0 466
michael@0 467 wrapper = SelectWrapper(securityWrapper, wantXrays, xrayType, waiveXrays,
michael@0 468 originIsXBLScope);
michael@0 469 }
michael@0 470
michael@0 471 if (!targetSubsumesOrigin) {
michael@0 472 // Do a belt-and-suspenders check against exposing eval()/Function() to
michael@0 473 // non-subsuming content.
michael@0 474 JSFunction *fun = JS_GetObjectFunction(obj);
michael@0 475 if (fun) {
michael@0 476 if (JS_IsBuiltinEvalFunction(fun) || JS_IsBuiltinFunctionConstructor(fun)) {
michael@0 477 JS_ReportError(cx, "Permission denied to expose eval or Function to non-subsuming content");
michael@0 478 return nullptr;
michael@0 479 }
michael@0 480 }
michael@0 481 }
michael@0 482
michael@0 483 DEBUG_CheckUnwrapSafety(obj, wrapper, origin, target);
michael@0 484
michael@0 485 if (existing)
michael@0 486 return Wrapper::Renew(cx, existing, obj, wrapper);
michael@0 487
michael@0 488 return Wrapper::New(cx, obj, parent, wrapper);
michael@0 489 }
michael@0 490
michael@0 491 // Call WaiveXrayAndWrap when you have a JS object that you don't want to be
michael@0 492 // wrapped in an Xray wrapper. cx->compartment is the compartment that will be
michael@0 493 // using the returned object. If the object to be wrapped is already in the
michael@0 494 // correct compartment, then this returns the unwrapped object.
michael@0 495 bool
michael@0 496 WrapperFactory::WaiveXrayAndWrap(JSContext *cx, MutableHandleValue vp)
michael@0 497 {
michael@0 498 if (vp.isPrimitive())
michael@0 499 return JS_WrapValue(cx, vp);
michael@0 500
michael@0 501 RootedObject obj(cx, &vp.toObject());
michael@0 502 if (!WaiveXrayAndWrap(cx, &obj))
michael@0 503 return false;
michael@0 504
michael@0 505 vp.setObject(*obj);
michael@0 506 return true;
michael@0 507 }
michael@0 508
michael@0 509 bool
michael@0 510 WrapperFactory::WaiveXrayAndWrap(JSContext *cx, MutableHandleObject argObj)
michael@0 511 {
michael@0 512 MOZ_ASSERT(argObj);
michael@0 513 RootedObject obj(cx, js::UncheckedUnwrap(argObj));
michael@0 514 MOZ_ASSERT(!js::IsInnerObject(obj));
michael@0 515 if (js::IsObjectInContextCompartment(obj, cx)) {
michael@0 516 argObj.set(obj);
michael@0 517 return true;
michael@0 518 }
michael@0 519
michael@0 520 // Even though waivers have no effect on access by scopes that don't subsume
michael@0 521 // the underlying object, good defense-in-depth dictates that we should avoid
michael@0 522 // handing out waivers to callers that can't use them. The transitive waiving
michael@0 523 // machinery unconditionally calls WaiveXrayAndWrap on return values from
michael@0 524 // waived functions, even though the return value might be not be same-origin
michael@0 525 // with the function. So if we find ourselves trying to create a waiver for
michael@0 526 // |cx|, we should check whether the caller has any business with waivers
michael@0 527 // to things in |obj|'s compartment.
michael@0 528 JSCompartment *target = js::GetContextCompartment(cx);
michael@0 529 JSCompartment *origin = js::GetObjectCompartment(obj);
michael@0 530 obj = AccessCheck::subsumes(target, origin) ? WaiveXray(cx, obj) : obj;
michael@0 531 if (!obj)
michael@0 532 return false;
michael@0 533
michael@0 534 if (!JS_WrapObject(cx, &obj))
michael@0 535 return false;
michael@0 536 argObj.set(obj);
michael@0 537 return true;
michael@0 538 }
michael@0 539
michael@0 540 bool
michael@0 541 WrapperFactory::XrayWrapperNotShadowing(JSObject *wrapper, jsid id)
michael@0 542 {
michael@0 543 ResolvingId *rid = ResolvingId::getResolvingIdFromWrapper(wrapper);
michael@0 544 return rid->isXrayShadowing(id);
michael@0 545 }
michael@0 546
michael@0 547 /*
michael@0 548 * Calls to JS_TransplantObject* should go through these helpers here so that
michael@0 549 * waivers get fixed up properly.
michael@0 550 */
michael@0 551
michael@0 552 static bool
michael@0 553 FixWaiverAfterTransplant(JSContext *cx, HandleObject oldWaiver, HandleObject newobj)
michael@0 554 {
michael@0 555 MOZ_ASSERT(Wrapper::wrapperHandler(oldWaiver) == &XrayWaiver);
michael@0 556 MOZ_ASSERT(!js::IsCrossCompartmentWrapper(newobj));
michael@0 557
michael@0 558 // Create a waiver in the new compartment. We know there's not one already
michael@0 559 // because we _just_ transplanted, which means that |newobj| was either
michael@0 560 // created from scratch, or was previously cross-compartment wrapper (which
michael@0 561 // should have no waiver). CreateXrayWaiver asserts this.
michael@0 562 JSObject *newWaiver = WrapperFactory::CreateXrayWaiver(cx, newobj);
michael@0 563 if (!newWaiver)
michael@0 564 return false;
michael@0 565
michael@0 566 // Update all the cross-compartment references to oldWaiver to point to
michael@0 567 // newWaiver.
michael@0 568 if (!js::RemapAllWrappersForObject(cx, oldWaiver, newWaiver))
michael@0 569 return false;
michael@0 570
michael@0 571 // There should be no same-compartment references to oldWaiver, and we
michael@0 572 // just remapped all cross-compartment references. It's dead, so we can
michael@0 573 // remove it from the map.
michael@0 574 XPCWrappedNativeScope *scope = GetObjectScope(oldWaiver);
michael@0 575 JSObject *key = Wrapper::wrappedObject(oldWaiver);
michael@0 576 MOZ_ASSERT(scope->mWaiverWrapperMap->Find(key));
michael@0 577 scope->mWaiverWrapperMap->Remove(key);
michael@0 578 return true;
michael@0 579 }
michael@0 580
michael@0 581 JSObject *
michael@0 582 TransplantObject(JSContext *cx, JS::HandleObject origobj, JS::HandleObject target)
michael@0 583 {
michael@0 584 RootedObject oldWaiver(cx, WrapperFactory::GetXrayWaiver(origobj));
michael@0 585 RootedObject newIdentity(cx, JS_TransplantObject(cx, origobj, target));
michael@0 586 if (!newIdentity || !oldWaiver)
michael@0 587 return newIdentity;
michael@0 588
michael@0 589 if (!FixWaiverAfterTransplant(cx, oldWaiver, newIdentity))
michael@0 590 return nullptr;
michael@0 591 return newIdentity;
michael@0 592 }
michael@0 593
michael@0 594 nsIGlobalObject *
michael@0 595 GetNativeForGlobal(JSObject *obj)
michael@0 596 {
michael@0 597 MOZ_ASSERT(JS_IsGlobalObject(obj));
michael@0 598 if (!MaybeGetObjectScope(obj))
michael@0 599 return nullptr;
michael@0 600
michael@0 601 // Every global needs to hold a native as its private or be a
michael@0 602 // WebIDL object with an nsISupports DOM object.
michael@0 603 MOZ_ASSERT((GetObjectClass(obj)->flags & (JSCLASS_PRIVATE_IS_NSISUPPORTS |
michael@0 604 JSCLASS_HAS_PRIVATE)) ||
michael@0 605 dom::UnwrapDOMObjectToISupports(obj));
michael@0 606
michael@0 607 nsISupports *native = dom::UnwrapDOMObjectToISupports(obj);
michael@0 608 if (!native) {
michael@0 609 native = static_cast<nsISupports *>(js::GetObjectPrivate(obj));
michael@0 610 MOZ_ASSERT(native);
michael@0 611
michael@0 612 // In some cases (like for windows) it is a wrapped native,
michael@0 613 // in other cases (sandboxes, backstage passes) it's just
michael@0 614 // a direct pointer to the native. If it's a wrapped native
michael@0 615 // let's unwrap it first.
michael@0 616 if (nsCOMPtr<nsIXPConnectWrappedNative> wn = do_QueryInterface(native)) {
michael@0 617 native = wn->Native();
michael@0 618 }
michael@0 619 }
michael@0 620
michael@0 621 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(native);
michael@0 622 MOZ_ASSERT(global, "Native held by global needs to implement nsIGlobalObject!");
michael@0 623
michael@0 624 return global;
michael@0 625 }
michael@0 626
michael@0 627 }

mercurial