Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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 | } |