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