Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /* vim: set ts=8 sts=4 et sw=4 tw=99: */
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include "WaiveXrayWrapper.h"
8 : #include "FilteringWrapper.h"
9 : #include "AddonWrapper.h"
10 : #include "XrayWrapper.h"
11 : #include "AccessCheck.h"
12 : #include "XPCWrapper.h"
13 : #include "ChromeObjectWrapper.h"
14 : #include "WrapperFactory.h"
15 :
16 : #include "xpcprivate.h"
17 : #include "XPCMaps.h"
18 : #include "mozilla/dom/BindingUtils.h"
19 : #include "jsfriendapi.h"
20 : #include "mozilla/jsipc/CrossProcessObjectWrappers.h"
21 : #include "mozilla/Likely.h"
22 : #include "mozilla/dom/ScriptSettings.h"
23 : #include "nsContentUtils.h"
24 : #include "nsXULAppAPI.h"
25 :
26 : using namespace JS;
27 : using namespace js;
28 : using namespace mozilla;
29 :
30 : namespace xpc {
31 :
32 : // When chrome pulls a naked property across the membrane using
33 : // .wrappedJSObject, we want it to cross the membrane into the
34 : // chrome compartment without automatically being wrapped into an
35 : // X-ray wrapper. We achieve this by wrapping it into a special
36 : // transparent wrapper in the origin (non-chrome) compartment. When
37 : // an object with that special wrapper applied crosses into chrome,
38 : // we know to not apply an X-ray wrapper.
39 : const Wrapper XrayWaiver(WrapperFactory::WAIVE_XRAY_WRAPPER_FLAG);
40 :
41 : // When objects for which we waived the X-ray wrapper cross into
42 : // chrome, we wrap them into a special cross-compartment wrapper
43 : // that transitively extends the waiver to all properties we get
44 : // off it.
45 : const WaiveXrayWrapper WaiveXrayWrapper::singleton(0);
46 :
47 : bool
48 0 : WrapperFactory::IsCOW(JSObject* obj)
49 : {
50 0 : return IsWrapper(obj) &&
51 0 : Wrapper::wrapperHandler(obj) == &ChromeObjectWrapper::singleton;
52 : }
53 :
54 : JSObject*
55 5 : WrapperFactory::GetXrayWaiver(HandleObject obj)
56 : {
57 : // Object should come fully unwrapped but outerized.
58 5 : MOZ_ASSERT(obj == UncheckedUnwrap(obj));
59 5 : MOZ_ASSERT(!js::IsWindow(obj));
60 5 : XPCWrappedNativeScope* scope = ObjectScope(obj);
61 5 : MOZ_ASSERT(scope);
62 :
63 5 : if (!scope->mWaiverWrapperMap)
64 4 : return nullptr;
65 :
66 1 : return scope->mWaiverWrapperMap->Find(obj);
67 : }
68 :
69 : JSObject*
70 1 : WrapperFactory::CreateXrayWaiver(JSContext* cx, HandleObject obj)
71 : {
72 : // The caller is required to have already done a lookup.
73 : // NB: This implictly performs the assertions of GetXrayWaiver.
74 1 : MOZ_ASSERT(!GetXrayWaiver(obj));
75 1 : XPCWrappedNativeScope* scope = ObjectScope(obj);
76 :
77 2 : JSAutoCompartment ac(cx, obj);
78 1 : JSObject* waiver = Wrapper::New(cx, obj, &XrayWaiver);
79 1 : if (!waiver)
80 0 : return nullptr;
81 :
82 : // Add the new waiver to the map. It's important that we only ever have
83 : // one waiver for the lifetime of the target object.
84 1 : if (!scope->mWaiverWrapperMap) {
85 : scope->mWaiverWrapperMap =
86 1 : JSObject2JSObjectMap::newMap(XPC_WRAPPER_MAP_LENGTH);
87 : }
88 1 : if (!scope->mWaiverWrapperMap->Add(cx, obj, waiver))
89 0 : return nullptr;
90 1 : return waiver;
91 : }
92 :
93 : JSObject*
94 2 : WrapperFactory::WaiveXray(JSContext* cx, JSObject* objArg)
95 : {
96 4 : RootedObject obj(cx, objArg);
97 2 : obj = UncheckedUnwrap(obj);
98 2 : MOZ_ASSERT(!js::IsWindow(obj));
99 :
100 2 : JSObject* waiver = GetXrayWaiver(obj);
101 2 : if (!waiver) {
102 1 : waiver = CreateXrayWaiver(cx, obj);
103 : }
104 2 : MOZ_ASSERT(JS::ObjectIsNotGray(waiver));
105 4 : return waiver;
106 : }
107 :
108 : /* static */ bool
109 1 : WrapperFactory::AllowWaiver(JSCompartment* target, JSCompartment* origin)
110 : {
111 2 : return CompartmentPrivate::Get(target)->allowWaivers &&
112 2 : AccessCheck::subsumes(target, origin);
113 : }
114 :
115 : /* static */ bool
116 0 : WrapperFactory::AllowWaiver(JSObject* wrapper) {
117 0 : MOZ_ASSERT(js::IsCrossCompartmentWrapper(wrapper));
118 0 : return AllowWaiver(js::GetObjectCompartment(wrapper),
119 0 : js::GetObjectCompartment(js::UncheckedUnwrap(wrapper)));
120 : }
121 :
122 : inline bool
123 26203 : ShouldWaiveXray(JSContext* cx, JSObject* originalObj)
124 : {
125 : unsigned flags;
126 26203 : (void) js::UncheckedUnwrap(originalObj, /* stopAtWindowProxy = */ true, &flags);
127 :
128 : // If the original object did not point through an Xray waiver, we're done.
129 26203 : if (!(flags & WrapperFactory::WAIVE_XRAY_WRAPPER_FLAG))
130 26202 : return false;
131 :
132 : // If the original object was not a cross-compartment wrapper, that means
133 : // that the caller explicitly created a waiver. Preserve it so that things
134 : // like WaiveXrayAndWrap work.
135 1 : if (!(flags & Wrapper::CROSS_COMPARTMENT))
136 1 : return true;
137 :
138 : // Otherwise, this is a case of explicitly passing a wrapper across a
139 : // compartment boundary. In that case, we only want to preserve waivers
140 : // in transactions between same-origin compartments.
141 0 : JSCompartment* oldCompartment = js::GetObjectCompartment(originalObj);
142 0 : JSCompartment* newCompartment = js::GetContextCompartment(cx);
143 0 : bool sameOrigin = false;
144 0 : if (OriginAttributes::IsRestrictOpenerAccessForFPI()) {
145 0 : sameOrigin =
146 0 : AccessCheck::subsumesConsideringDomain(oldCompartment, newCompartment) &&
147 0 : AccessCheck::subsumesConsideringDomain(newCompartment, oldCompartment);
148 : } else {
149 0 : sameOrigin =
150 0 : AccessCheck::subsumesConsideringDomainIgnoringFPD(oldCompartment, newCompartment) &&
151 0 : AccessCheck::subsumesConsideringDomainIgnoringFPD(newCompartment, oldCompartment);
152 : }
153 0 : return sameOrigin;
154 : }
155 :
156 : void
157 26203 : WrapperFactory::PrepareForWrapping(JSContext* cx, HandleObject scope,
158 : HandleObject objArg, HandleObject objectPassedToWrap,
159 : MutableHandleObject retObj)
160 : {
161 26203 : bool waive = ShouldWaiveXray(cx, objectPassedToWrap);
162 28971 : RootedObject obj(cx, objArg);
163 26203 : retObj.set(nullptr);
164 : // Outerize any raw inner objects at the entry point here, so that we don't
165 : // have to worry about them for the rest of the wrapping code.
166 26203 : if (js::IsWindow(obj)) {
167 16 : JSAutoCompartment ac(cx, obj);
168 8 : obj = js::ToWindowProxyIfWindow(obj);
169 8 : MOZ_ASSERT(obj);
170 : // ToWindowProxyIfWindow can return a CCW if |obj| was a
171 : // navigated-away-from Window. Strip any CCWs.
172 8 : obj = js::UncheckedUnwrap(obj);
173 8 : if (JS_IsDeadWrapper(obj)) {
174 0 : JS_ReportErrorASCII(cx, "Can't wrap dead object");
175 0 : return;
176 : }
177 8 : MOZ_ASSERT(js::IsWindowProxy(obj));
178 : // We crossed a compartment boundary there, so may now have a gray
179 : // object. This function is not allowed to return gray objects, so
180 : // don't do that.
181 8 : ExposeObjectToActiveJS(obj);
182 : }
183 :
184 : // If the object is a dead wrapper, return a new dead wrapper rather than
185 : // trying to wrap it for a different compartment.
186 26203 : if (JS_IsDeadWrapper(obj)) {
187 0 : retObj.set(JS_NewDeadWrapper(cx, obj));
188 0 : return;
189 : }
190 :
191 : // If we've somehow gotten to this point after either the source or target
192 : // compartment has been nuked, return a DeadObjectProxy to prevent further
193 : // access.
194 : // However, we always need to provide live wrappers for ScriptSourceObjects,
195 : // since they're used for cross-compartment cloned scripts, and need to
196 : // remain accessible even after the original compartment has been nuked.
197 26203 : JSCompartment* origin = js::GetObjectCompartment(obj);
198 26203 : JSCompartment* target = js::GetObjectCompartment(scope);
199 43926 : if (!JS_IsScriptSourceObject(obj) &&
200 35446 : (CompartmentPrivate::Get(origin)->wasNuked ||
201 17723 : CompartmentPrivate::Get(target)->wasNuked)) {
202 0 : NS_WARNING("Trying to create a wrapper into or out of a nuked compartment");
203 :
204 0 : retObj.set(JS_NewDeadWrapper(cx));
205 0 : return;
206 : }
207 :
208 :
209 : // If we've got a WindowProxy, there's nothing special that needs to be
210 : // done here, and we can move on to the next phase of wrapping. We handle
211 : // this case first to allow us to assert against wrappers below.
212 26203 : if (js::IsWindowProxy(obj)) {
213 425 : retObj.set(waive ? WaiveXray(cx, obj) : obj);
214 425 : return;
215 : }
216 :
217 : // Here are the rules for wrapping:
218 : // We should never get a proxy here (the JS engine unwraps those for us).
219 25778 : MOZ_ASSERT(!IsWrapper(obj));
220 :
221 : // Now, our object is ready to be wrapped, but several objects (notably
222 : // nsJSIIDs) have a wrapper per scope. If we are about to wrap one of
223 : // those objects in a security wrapper, then we need to hand back the
224 : // wrapper for the new scope instead. Also, global objects don't move
225 : // between scopes so for those we also want to return the wrapper. So...
226 25778 : if (!IS_WN_REFLECTOR(obj) || JS_IsGlobalObject(obj)) {
227 23007 : retObj.set(waive ? WaiveXray(cx, obj) : obj);
228 23007 : return;
229 : }
230 :
231 2771 : XPCWrappedNative* wn = XPCWrappedNative::Get(obj);
232 :
233 5539 : JSAutoCompartment ac(cx, obj);
234 5539 : XPCCallContext ccx(cx, obj);
235 5539 : RootedObject wrapScope(cx, scope);
236 :
237 : {
238 2771 : if (ccx.GetScriptable() && ccx.GetScriptable()->WantPreCreate()) {
239 : // We have a precreate hook. This object might enforce that we only
240 : // ever create JS object for it.
241 :
242 : // Note: this penalizes objects that only have one wrapper, but are
243 : // being accessed across compartments. We would really prefer to
244 : // replace the above code with a test that says "do you only have one
245 : // wrapper?"
246 114 : nsresult rv = wn->GetScriptable()->
247 114 : PreCreate(wn->Native(), cx, scope, wrapScope.address());
248 114 : if (NS_FAILED(rv)) {
249 0 : retObj.set(waive ? WaiveXray(cx, obj) : obj);
250 2 : return;
251 : }
252 :
253 : // If the handed back scope differs from the passed-in scope and is in
254 : // a separate compartment, then this object is explicitly requesting
255 : // that we don't create a second JS object for it: create a security
256 : // wrapper.
257 114 : if (js::GetObjectCompartment(scope) != js::GetObjectCompartment(wrapScope)) {
258 2 : retObj.set(waive ? WaiveXray(cx, obj) : obj);
259 2 : return;
260 : }
261 :
262 224 : RootedObject currentScope(cx, JS_GetGlobalForObject(cx, obj));
263 112 : if (MOZ_UNLIKELY(wrapScope != currentScope)) {
264 : // The wrapper claims it wants to be in the new scope, but
265 : // currently has a reflection that lives in the old scope. This
266 : // can mean one of two things, both of which are rare:
267 : //
268 : // 1 - The object has a PreCreate hook (we checked for it above),
269 : // but is deciding to request one-wrapper-per-scope (rather than
270 : // one-wrapper-per-native) for some reason. Usually, a PreCreate
271 : // hook indicates one-wrapper-per-native. In this case we want to
272 : // make a new wrapper in the new scope.
273 : //
274 : // 2 - We're midway through wrapper reparenting. The document has
275 : // moved to a new scope, but |wn| hasn't been moved yet, and
276 : // we ended up calling JS_WrapObject() on its JS object. In this
277 : // case, we want to return the existing wrapper.
278 : //
279 : // So we do a trick: call PreCreate _again_, but say that we're
280 : // wrapping for the old scope, rather than the new one. If (1) is
281 : // the case, then PreCreate will return the scope we pass to it
282 : // (the old scope). If (2) is the case, PreCreate will return the
283 : // scope of the document (the new scope).
284 224 : RootedObject probe(cx);
285 112 : rv = wn->GetScriptable()->
286 112 : PreCreate(wn->Native(), cx, currentScope, probe.address());
287 :
288 : // Check for case (2).
289 112 : if (probe != currentScope) {
290 0 : MOZ_ASSERT(probe == wrapScope);
291 0 : retObj.set(waive ? WaiveXray(cx, obj) : obj);
292 0 : return;
293 : }
294 :
295 : // Ok, must be case (1). Fall through and create a new wrapper.
296 : }
297 :
298 : // Nasty hack for late-breaking bug 781476. This will confuse identity checks,
299 : // but it's probably better than any of our alternatives.
300 : //
301 : // Note: We have to ignore domain here. The JS engine assumes that, given a
302 : // compartment c, if c->wrap(x) returns a cross-compartment wrapper at time t0,
303 : // it will also return a cross-compartment wrapper for any time t1 > t0 unless
304 : // an explicit transplant is performed. In particular, wrapper recomputation
305 : // assumes that recomputing a wrapper will always result in a wrapper.
306 : //
307 : // This doesn't actually pose a security issue, because we'll still compute
308 : // the correct (opaque) wrapper for the object below given the security
309 : // characteristics of the two compartments.
310 112 : if (!AccessCheck::isChrome(js::GetObjectCompartment(wrapScope)) &&
311 0 : AccessCheck::subsumes(js::GetObjectCompartment(wrapScope),
312 0 : js::GetObjectCompartment(obj)))
313 : {
314 0 : retObj.set(waive ? WaiveXray(cx, obj) : obj);
315 0 : return;
316 : }
317 : }
318 : }
319 :
320 : // This public WrapNativeToJSVal API enters the compartment of 'wrapScope'
321 : // so we don't have to.
322 5537 : RootedValue v(cx);
323 : nsresult rv =
324 8306 : nsXPConnect::XPConnect()->WrapNativeToJSVal(cx, wrapScope, wn->Native(), nullptr,
325 5538 : &NS_GET_IID(nsISupports), false, &v);
326 2768 : if (NS_FAILED(rv)) {
327 0 : return;
328 : }
329 :
330 2768 : obj.set(&v.toObject());
331 2768 : MOZ_ASSERT(IS_WN_REFLECTOR(obj), "bad object");
332 2768 : MOZ_ASSERT(JS::ObjectIsNotGray(obj), "Should never return gray reflectors");
333 :
334 : // Because the underlying native didn't have a PreCreate hook, we had
335 : // to a new (or possibly pre-existing) XPCWN in our compartment.
336 : // This could be a problem for chrome code that passes XPCOM objects
337 : // across compartments, because the effects of QI would disappear across
338 : // compartments.
339 : //
340 : // So whenever we pull an XPCWN across compartments in this manner, we
341 : // give the destination object the union of the two native sets. We try
342 : // to do this cleverly in the common case to avoid too much overhead.
343 2768 : XPCWrappedNative* newwn = XPCWrappedNative::Get(obj);
344 5536 : RefPtr<XPCNativeSet> unionSet = XPCNativeSet::GetNewOrUsed(newwn->GetSet(),
345 5536 : wn->GetSet(), false);
346 2768 : if (!unionSet) {
347 0 : return;
348 : }
349 2768 : newwn->SetSet(unionSet.forget());
350 :
351 2768 : retObj.set(waive ? WaiveXray(cx, obj) : obj);
352 : }
353 :
354 : #ifdef DEBUG
355 : static void
356 9310 : DEBUG_CheckUnwrapSafety(HandleObject obj, const js::Wrapper* handler,
357 : JSCompartment* origin, JSCompartment* target)
358 : {
359 17494 : if (!JS_IsScriptSourceObject(obj) &&
360 16368 : (CompartmentPrivate::Get(origin)->wasNuked || CompartmentPrivate::Get(target)->wasNuked)) {
361 : // If either compartment has already been nuked, we should have returned
362 : // a dead wrapper from our prewrap callback, and this function should
363 : // not be called.
364 0 : MOZ_ASSERT_UNREACHABLE("CheckUnwrapSafety called for a dead wrapper");
365 9310 : } else if (AccessCheck::isChrome(target) || xpc::IsUniversalXPConnectEnabled(target)) {
366 : // If the caller is chrome (or effectively so), unwrap should always be allowed.
367 9309 : MOZ_ASSERT(!handler->hasSecurityPolicy());
368 1 : } else if (CompartmentPrivate::Get(origin)->forcePermissiveCOWs) {
369 : // Similarly, if this is a privileged scope that has opted to make itself
370 : // accessible to the world (allowed only during automation), unwrap should
371 : // be allowed.
372 0 : MOZ_ASSERT(!handler->hasSecurityPolicy());
373 : } else {
374 : // Otherwise, it should depend on whether the target subsumes the origin.
375 1 : MOZ_ASSERT(handler->hasSecurityPolicy() == !(OriginAttributes::IsRestrictOpenerAccessForFPI() ?
376 : AccessCheck::subsumesConsideringDomain(target, origin) :
377 : AccessCheck::subsumesConsideringDomainIgnoringFPD(target, origin)));
378 : }
379 9310 : }
380 : #else
381 : #define DEBUG_CheckUnwrapSafety(obj, handler, origin, target) {}
382 : #endif
383 :
384 : static const Wrapper*
385 9308 : SelectWrapper(bool securityWrapper, XrayType xrayType, bool waiveXrays, JSObject* obj)
386 : {
387 : // Waived Xray uses a modified CCW that has transparent behavior but
388 : // transitively waives Xrays on arguments.
389 9308 : if (waiveXrays) {
390 0 : MOZ_ASSERT(!securityWrapper);
391 0 : return &WaiveXrayWrapper::singleton;
392 : }
393 :
394 : // If we don't want or can't use Xrays, select a wrapper that's either
395 : // entirely transparent or entirely opaque.
396 9308 : if (xrayType == NotXray) {
397 8062 : if (!securityWrapper)
398 8062 : return &CrossCompartmentWrapper::singleton;
399 0 : return &FilteringWrapper<CrossCompartmentSecurityWrapper, Opaque>::singleton;
400 : }
401 :
402 : // Ok, we're using Xray. If this isn't a security wrapper, use the permissive
403 : // version and skip the filter.
404 1246 : if (!securityWrapper) {
405 1245 : if (xrayType == XrayForWrappedNative)
406 0 : return &PermissiveXrayXPCWN::singleton;
407 1245 : else if (xrayType == XrayForDOMObject)
408 111 : return &PermissiveXrayDOM::singleton;
409 1134 : else if (xrayType == XrayForJSObject)
410 8 : return &PermissiveXrayJS::singleton;
411 1126 : MOZ_ASSERT(xrayType == XrayForOpaqueObject);
412 1126 : return &PermissiveXrayOpaque::singleton;
413 : }
414 :
415 : // This is a security wrapper. Use the security versions and filter.
416 1 : if (xrayType == XrayForDOMObject && IdentifyCrossOriginObject(obj) != CrossOriginOpaque)
417 : return &FilteringWrapper<CrossOriginXrayWrapper,
418 1 : CrossOriginAccessiblePropertiesOnly>::singleton;
419 :
420 : // There's never any reason to expose other objects to non-subsuming actors.
421 : // Just use an opaque wrapper in these cases.
422 : //
423 : // In general, we don't want opaque function wrappers to be callable.
424 : // But in the case of XBL, we rely on content being able to invoke
425 : // functions exposed from the XBL scope. We could remove this exception,
426 : // if needed, by using ExportFunction to generate the content-side
427 : // representations of XBL methods.
428 0 : if (xrayType == XrayForJSObject && IsInContentXBLScope(obj))
429 0 : return &FilteringWrapper<CrossCompartmentSecurityWrapper, OpaqueWithCall>::singleton;
430 0 : return &FilteringWrapper<CrossCompartmentSecurityWrapper, Opaque>::singleton;
431 : }
432 :
433 : static const Wrapper*
434 0 : SelectAddonWrapper(JSContext* cx, HandleObject obj, const Wrapper* wrapper)
435 : {
436 0 : JSAddonId* originAddon = JS::AddonIdOfObject(obj);
437 0 : JSAddonId* targetAddon = JS::AddonIdOfObject(JS::CurrentGlobalOrNull(cx));
438 :
439 0 : MOZ_ASSERT(AccessCheck::isChrome(JS::CurrentGlobalOrNull(cx)));
440 0 : MOZ_ASSERT(targetAddon);
441 :
442 0 : if (targetAddon == originAddon)
443 0 : return wrapper;
444 :
445 : // Add-on interposition only supports certain wrapper types, so we check if
446 : // we would have used one of the supported ones.
447 0 : if (wrapper == &CrossCompartmentWrapper::singleton)
448 0 : return &AddonWrapper<CrossCompartmentWrapper>::singleton;
449 0 : else if (wrapper == &PermissiveXrayXPCWN::singleton)
450 0 : return &AddonWrapper<PermissiveXrayXPCWN>::singleton;
451 0 : else if (wrapper == &PermissiveXrayDOM::singleton)
452 0 : return &AddonWrapper<PermissiveXrayDOM>::singleton;
453 :
454 : // |wrapper| is not supported for interposition, so we don't do it.
455 0 : return wrapper;
456 : }
457 :
458 : JSObject*
459 9310 : WrapperFactory::Rewrap(JSContext* cx, HandleObject existing, HandleObject obj)
460 : {
461 9310 : MOZ_ASSERT(!IsWrapper(obj) ||
462 : GetProxyHandler(obj) == &XrayWaiver ||
463 : js::IsWindowProxy(obj),
464 : "wrapped object passed to rewrap");
465 9310 : MOZ_ASSERT(!XrayUtils::IsXPCWNHolderClass(JS_GetClass(obj)), "trying to wrap a holder");
466 9310 : MOZ_ASSERT(!js::IsWindow(obj));
467 9310 : MOZ_ASSERT(dom::IsJSAPIActive());
468 :
469 : // Compute the information we need to select the right wrapper.
470 9310 : JSCompartment* origin = js::GetObjectCompartment(obj);
471 9310 : JSCompartment* target = js::GetContextCompartment(cx);
472 9310 : bool originIsChrome = AccessCheck::isChrome(origin);
473 9310 : bool targetIsChrome = AccessCheck::isChrome(target);
474 9310 : bool originSubsumesTarget = OriginAttributes::IsRestrictOpenerAccessForFPI() ?
475 : AccessCheck::subsumesConsideringDomain(origin, target) :
476 9310 : AccessCheck::subsumesConsideringDomainIgnoringFPD(origin, target);
477 9310 : bool targetSubsumesOrigin = OriginAttributes::IsRestrictOpenerAccessForFPI() ?
478 : AccessCheck::subsumesConsideringDomain(target, origin) :
479 9310 : AccessCheck::subsumesConsideringDomainIgnoringFPD(target, origin);
480 9310 : bool sameOrigin = targetSubsumesOrigin && originSubsumesTarget;
481 :
482 : const Wrapper* wrapper;
483 :
484 : CompartmentPrivate* originCompartmentPrivate =
485 9310 : CompartmentPrivate::Get(origin);
486 : CompartmentPrivate* targetCompartmentPrivate =
487 9310 : CompartmentPrivate::Get(target);
488 :
489 : //
490 : // First, handle the special cases.
491 : //
492 :
493 : // If UniversalXPConnect is enabled, this is just some dumb mochitest. Use
494 : // a vanilla CCW.
495 9310 : if (targetCompartmentPrivate->universalXPConnectEnabled) {
496 2 : CrashIfNotInAutomation();
497 2 : wrapper = &CrossCompartmentWrapper::singleton;
498 : }
499 :
500 : // Let the SpecialPowers scope make its stuff easily accessible to content.
501 9308 : else if (originCompartmentPrivate->forcePermissiveCOWs) {
502 0 : CrashIfNotInAutomation();
503 0 : wrapper = &CrossCompartmentWrapper::singleton;
504 : }
505 :
506 : // Special handling for chrome objects being exposed to content.
507 9308 : else if (originIsChrome && !targetIsChrome) {
508 : // If this is a chrome function being exposed to content, we need to allow
509 : // call (but nothing else). We allow CPOWs that purport to be function's
510 : // here, but only in the content process.
511 0 : if ((IdentifyStandardInstance(obj) == JSProto_Function ||
512 0 : (jsipc::IsCPOW(obj) && JS::IsCallable(obj) &&
513 0 : XRE_IsContentProcess())))
514 : {
515 0 : wrapper = &FilteringWrapper<CrossCompartmentSecurityWrapper, OpaqueWithCall>::singleton;
516 : }
517 :
518 : // For Vanilla JSObjects exposed from chrome to content, we use a wrapper
519 : // that supports __exposedProps__. We'd like to get rid of these eventually,
520 : // but in their current form they don't cause much trouble.
521 0 : else if (IdentifyStandardInstance(obj) == JSProto_Object) {
522 0 : wrapper = &ChromeObjectWrapper::singleton;
523 : }
524 :
525 : // Otherwise we get an opaque wrapper.
526 : else {
527 0 : wrapper = &FilteringWrapper<CrossCompartmentSecurityWrapper, Opaque>::singleton;
528 : }
529 : }
530 :
531 : //
532 : // Now, handle the regular cases.
533 : //
534 : // These are wrappers we can compute using a rule-based approach. In order
535 : // to do so, we need to compute some parameters.
536 : //
537 : else {
538 :
539 : // The wrapper is a security wrapper (protecting the wrappee) if and
540 : // only if the target does not subsume the origin.
541 9308 : bool securityWrapper = !targetSubsumesOrigin;
542 :
543 : // Xrays are warranted if either the target or the origin don't trust
544 : // each other. This is generally the case, unless the two are same-origin
545 : // and the caller has not requested same-origin Xrays.
546 : //
547 : // Xrays are a bidirectional protection, since it affords clarity to the
548 : // caller and privacy to the callee.
549 17490 : bool sameOriginXrays = originCompartmentPrivate->wantXrays ||
550 17490 : targetCompartmentPrivate->wantXrays;
551 9308 : bool wantXrays = !sameOrigin || sameOriginXrays;
552 :
553 9308 : XrayType xrayType = wantXrays ? GetXrayType(obj) : NotXray;
554 :
555 : // If Xrays are warranted, the caller may waive them for non-security
556 : // wrappers (unless explicitly forbidden from doing so).
557 2491 : bool waiveXrays = wantXrays && !securityWrapper &&
558 11798 : targetCompartmentPrivate->allowWaivers &&
559 10553 : HasWaiveXrayFlag(obj);
560 :
561 9308 : wrapper = SelectWrapper(securityWrapper, xrayType, waiveXrays, obj);
562 :
563 : // If we want to apply add-on interposition in the target compartment,
564 : // then we try to "upgrade" the wrapper to an interposing one.
565 9308 : if (targetCompartmentPrivate->scope->HasInterposition())
566 0 : wrapper = SelectAddonWrapper(cx, obj, wrapper);
567 : }
568 :
569 9310 : if (!targetSubsumesOrigin) {
570 : // Do a belt-and-suspenders check against exposing eval()/Function() to
571 : // non-subsuming content.
572 3 : if (JSFunction* fun = JS_GetObjectFunction(obj)) {
573 0 : if (JS_IsBuiltinEvalFunction(fun) || JS_IsBuiltinFunctionConstructor(fun)) {
574 0 : NS_WARNING("Trying to expose eval or Function to non-subsuming content!");
575 0 : wrapper = &FilteringWrapper<CrossCompartmentSecurityWrapper, Opaque>::singleton;
576 : }
577 : }
578 : }
579 :
580 9310 : DEBUG_CheckUnwrapSafety(obj, wrapper, origin, target);
581 :
582 9310 : if (existing)
583 10 : return Wrapper::Renew(existing, obj, wrapper);
584 :
585 9300 : return Wrapper::New(cx, obj, wrapper);
586 : }
587 :
588 : // Call WaiveXrayAndWrap when you have a JS object that you don't want to be
589 : // wrapped in an Xray wrapper. cx->compartment is the compartment that will be
590 : // using the returned object. If the object to be wrapped is already in the
591 : // correct compartment, then this returns the unwrapped object.
592 : bool
593 1 : WrapperFactory::WaiveXrayAndWrap(JSContext* cx, MutableHandleValue vp)
594 : {
595 1 : if (vp.isPrimitive())
596 0 : return JS_WrapValue(cx, vp);
597 :
598 2 : RootedObject obj(cx, &vp.toObject());
599 1 : if (!WaiveXrayAndWrap(cx, &obj))
600 0 : return false;
601 :
602 1 : vp.setObject(*obj);
603 1 : return true;
604 : }
605 :
606 : bool
607 1 : WrapperFactory::WaiveXrayAndWrap(JSContext* cx, MutableHandleObject argObj)
608 : {
609 1 : MOZ_ASSERT(argObj);
610 2 : RootedObject obj(cx, js::UncheckedUnwrap(argObj));
611 1 : MOZ_ASSERT(!js::IsWindow(obj));
612 1 : if (js::IsObjectInContextCompartment(obj, cx)) {
613 0 : argObj.set(obj);
614 0 : return true;
615 : }
616 :
617 : // Even though waivers have no effect on access by scopes that don't subsume
618 : // the underlying object, good defense-in-depth dictates that we should avoid
619 : // handing out waivers to callers that can't use them. The transitive waiving
620 : // machinery unconditionally calls WaiveXrayAndWrap on return values from
621 : // waived functions, even though the return value might be not be same-origin
622 : // with the function. So if we find ourselves trying to create a waiver for
623 : // |cx|, we should check whether the caller has any business with waivers
624 : // to things in |obj|'s compartment.
625 1 : JSCompartment* target = js::GetContextCompartment(cx);
626 1 : JSCompartment* origin = js::GetObjectCompartment(obj);
627 1 : obj = AllowWaiver(target, origin) ? WaiveXray(cx, obj) : obj;
628 1 : if (!obj)
629 0 : return false;
630 :
631 1 : if (!JS_WrapObject(cx, &obj))
632 0 : return false;
633 1 : argObj.set(obj);
634 1 : return true;
635 : }
636 :
637 : /*
638 : * Calls to JS_TransplantObject* should go through these helpers here so that
639 : * waivers get fixed up properly.
640 : */
641 :
642 : static bool
643 0 : FixWaiverAfterTransplant(JSContext* cx, HandleObject oldWaiver, HandleObject newobj)
644 : {
645 0 : MOZ_ASSERT(Wrapper::wrapperHandler(oldWaiver) == &XrayWaiver);
646 0 : MOZ_ASSERT(!js::IsCrossCompartmentWrapper(newobj));
647 :
648 : // Create a waiver in the new compartment. We know there's not one already
649 : // because we _just_ transplanted, which means that |newobj| was either
650 : // created from scratch, or was previously cross-compartment wrapper (which
651 : // should have no waiver). CreateXrayWaiver asserts this.
652 0 : JSObject* newWaiver = WrapperFactory::CreateXrayWaiver(cx, newobj);
653 0 : if (!newWaiver)
654 0 : return false;
655 :
656 : // Update all the cross-compartment references to oldWaiver to point to
657 : // newWaiver.
658 0 : if (!js::RemapAllWrappersForObject(cx, oldWaiver, newWaiver))
659 0 : return false;
660 :
661 : // There should be no same-compartment references to oldWaiver, and we
662 : // just remapped all cross-compartment references. It's dead, so we can
663 : // remove it from the map.
664 0 : XPCWrappedNativeScope* scope = ObjectScope(oldWaiver);
665 0 : JSObject* key = Wrapper::wrappedObject(oldWaiver);
666 0 : MOZ_ASSERT(scope->mWaiverWrapperMap->Find(key));
667 0 : scope->mWaiverWrapperMap->Remove(key);
668 0 : return true;
669 : }
670 :
671 : JSObject*
672 2 : TransplantObject(JSContext* cx, JS::HandleObject origobj, JS::HandleObject target)
673 : {
674 4 : RootedObject oldWaiver(cx, WrapperFactory::GetXrayWaiver(origobj));
675 4 : RootedObject newIdentity(cx, JS_TransplantObject(cx, origobj, target));
676 2 : if (!newIdentity || !oldWaiver)
677 2 : return newIdentity;
678 :
679 0 : if (!FixWaiverAfterTransplant(cx, oldWaiver, newIdentity))
680 0 : return nullptr;
681 0 : return newIdentity;
682 : }
683 :
684 : nsIGlobalObject*
685 5141 : NativeGlobal(JSObject* obj)
686 : {
687 5141 : obj = js::GetGlobalForObjectCrossCompartment(obj);
688 :
689 : // Every global needs to hold a native as its private or be a
690 : // WebIDL object with an nsISupports DOM object.
691 5141 : MOZ_ASSERT((GetObjectClass(obj)->flags & (JSCLASS_PRIVATE_IS_NSISUPPORTS |
692 : JSCLASS_HAS_PRIVATE)) ||
693 : dom::UnwrapDOMObjectToISupports(obj));
694 :
695 5141 : nsISupports* native = dom::UnwrapDOMObjectToISupports(obj);
696 5141 : if (!native) {
697 4408 : native = static_cast<nsISupports*>(js::GetObjectPrivate(obj));
698 4408 : MOZ_ASSERT(native);
699 :
700 : // In some cases (like for windows) it is a wrapped native,
701 : // in other cases (sandboxes, backstage passes) it's just
702 : // a direct pointer to the native. If it's a wrapped native
703 : // let's unwrap it first.
704 8816 : if (nsCOMPtr<nsIXPConnectWrappedNative> wn = do_QueryInterface(native)) {
705 3919 : native = wn->Native();
706 : }
707 : }
708 :
709 10282 : nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(native);
710 5141 : MOZ_ASSERT(global, "Native held by global needs to implement nsIGlobalObject!");
711 :
712 10282 : return global;
713 : }
714 :
715 : } // namespace xpc
|