1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 : * vim: set ts=4 sw=4 et tw=99 ft=cpp:
3 : *
4 : * ***** BEGIN LICENSE BLOCK *****
5 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 : *
7 : * The contents of this file are subject to the Mozilla Public License Version
8 : * 1.1 (the "License"); you may not use this file except in compliance with
9 : * the License. You may obtain a copy of the License at
10 : * http://www.mozilla.org/MPL/
11 : *
12 : * Software distributed under the License is distributed on an "AS IS" basis,
13 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14 : * for the specific language governing rights and limitations under the
15 : * License.
16 : *
17 : * The Original Code is mozilla.org code, released
18 : * June 24, 2010.
19 : *
20 : * The Initial Developer of the Original Code is
21 : * The Mozilla Foundation
22 : *
23 : * Contributor(s):
24 : * Andreas Gal <gal@mozilla.com>
25 : *
26 : * Alternatively, the contents of this file may be used under the terms of
27 : * either of the GNU General Public License Version 2 or later (the "GPL"),
28 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 : * in which case the provisions of the GPL or the LGPL are applicable instead
30 : * of those above. If you wish to allow use of your version of this file only
31 : * under the terms of either the GPL or the LGPL, and not to allow others to
32 : * use your version of this file under the terms of the MPL, indicate your
33 : * decision by deleting the provisions above and replace them with the notice
34 : * and other provisions required by the GPL or the LGPL. If you do not delete
35 : * the provisions above, a recipient may use your version of this file under
36 : * the terms of any one of the MPL, the GPL or the LGPL.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 :
40 : #include "WrapperFactory.h"
41 : #include "CrossOriginWrapper.h"
42 : #include "FilteringWrapper.h"
43 : #include "XrayWrapper.h"
44 : #include "AccessCheck.h"
45 : #include "XPCWrapper.h"
46 :
47 : #include "xpcprivate.h"
48 : #include "dombindings.h"
49 : #include "XPCMaps.h"
50 :
51 : #include "jsfriendapi.h"
52 :
53 : using namespace js;
54 :
55 : namespace xpc {
56 :
57 : // When chrome pulls a naked property across the membrane using
58 : // .wrappedJSObject, we want it to cross the membrane into the
59 : // chrome compartment without automatically being wrapped into an
60 : // X-ray wrapper. We achieve this by wrapping it into a special
61 : // transparent wrapper in the origin (non-chrome) compartment. When
62 : // an object with that special wrapper applied crosses into chrome,
63 : // we know to not apply an X-ray wrapper.
64 1464 : Wrapper WaiveXrayWrapperWrapper(WrapperFactory::WAIVE_XRAY_WRAPPER_FLAG);
65 :
66 : // Objects that haven't been explicitly waived, but have been exposed
67 : // to chrome don't want a CrossOriginWrapper, since that deeply-waives
68 : // but need the transparent behavior of a CrossOriginWrapper. The
69 : // NoWaiverWrapper is like a CrossOriginWrapper that can also hand out
70 : // XrayWrappers as return values.
71 1464 : NoWaiverWrapper NoWaiverWrapper::singleton(0);
72 :
73 : // When objects for which we waived the X-ray wrapper cross into
74 : // chrome, we wrap them into a special cross-compartment wrapper
75 : // that transitively extends the waiver to all properties we get
76 : // off it.
77 1464 : CrossOriginWrapper CrossOriginWrapper::singleton(0);
78 :
79 : static JSObject *
80 19477 : GetCurrentOuter(JSContext *cx, JSObject *obj)
81 : {
82 19477 : obj = JS_ObjectToOuterObject(cx, obj);
83 19477 : if (!obj)
84 0 : return NULL;
85 :
86 19477 : if (IsWrapper(obj) && !js::GetObjectClass(obj)->ext.innerObject) {
87 0 : obj = UnwrapObject(obj);
88 0 : NS_ASSERTION(js::GetObjectClass(obj)->ext.innerObject,
89 : "weird object, expecting an outer window proxy");
90 : }
91 :
92 19477 : return obj;
93 : }
94 :
95 : JSObject *
96 0 : WrapperFactory::WaiveXray(JSContext *cx, JSObject *obj)
97 : {
98 0 : obj = UnwrapObject(obj);
99 :
100 : // We have to make sure that if we're wrapping an outer window, that
101 : // the .wrappedJSObject also wraps the outer window.
102 0 : obj = GetCurrentOuter(cx, obj);
103 :
104 : {
105 : // See if we already have a waiver wrapper for this object.
106 : CompartmentPrivate *priv =
107 0 : (CompartmentPrivate *)JS_GetCompartmentPrivate(js::GetObjectCompartment(obj));
108 0 : JSObject *wobj = nsnull;
109 0 : if (priv && priv->waiverWrapperMap) {
110 0 : wobj = priv->waiverWrapperMap->Find(obj);
111 0 : xpc_UnmarkGrayObject(wobj);
112 : }
113 :
114 : // No wrapper yet, make one.
115 0 : if (!wobj) {
116 0 : JSObject *proto = js::GetObjectProto(obj);
117 0 : if (proto && !(proto = WaiveXray(cx, proto)))
118 0 : return nsnull;
119 :
120 0 : JSAutoEnterCompartment ac;
121 0 : if (!ac.enter(cx, obj) || !JS_WrapObject(cx, &proto))
122 0 : return nsnull;
123 : wobj = Wrapper::New(cx, obj, proto, JS_GetGlobalForObject(cx, obj),
124 0 : &WaiveXrayWrapperWrapper);
125 0 : if (!wobj)
126 0 : return nsnull;
127 :
128 : // Add the new wrapper so we find it next time.
129 0 : if (priv) {
130 0 : if (!priv->waiverWrapperMap) {
131 0 : priv->waiverWrapperMap = JSObject2JSObjectMap::newMap(XPC_WRAPPER_MAP_SIZE);
132 0 : if (!priv->waiverWrapperMap)
133 0 : return nsnull;
134 : }
135 0 : if (!priv->waiverWrapperMap->Add(obj, wobj))
136 0 : return nsnull;
137 : }
138 : }
139 :
140 0 : obj = wobj;
141 : }
142 :
143 0 : return obj;
144 : }
145 :
146 : // DoubleWrap is called from PrepareForWrapping to maintain the state that
147 : // we're supposed to waive Xray wrappers for the given on. On entrance, it
148 : // expects |cx->compartment != obj->compartment()|. The returned object will
149 : // be in the same compartment as |obj|.
150 : JSObject *
151 19477 : WrapperFactory::DoubleWrap(JSContext *cx, JSObject *obj, unsigned flags)
152 : {
153 19477 : if (flags & WrapperFactory::WAIVE_XRAY_WRAPPER_FLAG) {
154 0 : JSAutoEnterCompartment ac;
155 0 : if (!ac.enter(cx, obj))
156 0 : return nsnull;
157 :
158 0 : return WaiveXray(cx, obj);
159 : }
160 19477 : return obj;
161 : }
162 :
163 : JSObject *
164 19477 : WrapperFactory::PrepareForWrapping(JSContext *cx, JSObject *scope, JSObject *obj, unsigned flags)
165 : {
166 : // Don't unwrap an outer window, just double wrap it if needed.
167 19477 : if (js::GetObjectClass(obj)->ext.innerObject)
168 0 : return DoubleWrap(cx, obj, flags);
169 :
170 : // Here are the rules for wrapping:
171 : // We should never get a proxy here (the JS engine unwraps those for us).
172 19477 : JS_ASSERT(!IsWrapper(obj));
173 :
174 : // As soon as an object is wrapped in a security wrapper, it morphs to be
175 : // a fat wrapper. (see also: bug XXX).
176 19477 : if (IS_SLIM_WRAPPER(obj) && !MorphSlimWrapper(cx, obj))
177 0 : return nsnull;
178 :
179 : // We only hand out outer objects to script.
180 19477 : obj = GetCurrentOuter(cx, obj);
181 19477 : if (!obj)
182 0 : return nsnull;
183 :
184 19477 : if (js::GetObjectClass(obj)->ext.innerObject)
185 0 : return DoubleWrap(cx, obj, flags);
186 :
187 : // Now, our object is ready to be wrapped, but several objects (notably
188 : // nsJSIIDs) have a wrapper per scope. If we are about to wrap one of
189 : // those objects in a security wrapper, then we need to hand back the
190 : // wrapper for the new scope instead. Also, global objects don't move
191 : // between scopes so for those we also want to return the wrapper. So...
192 19477 : if (!IS_WN_WRAPPER(obj) || !js::GetObjectParent(obj))
193 18417 : return DoubleWrap(cx, obj, flags);
194 :
195 1060 : XPCWrappedNative *wn = static_cast<XPCWrappedNative *>(xpc_GetJSPrivate(obj));
196 :
197 : // If the object doesn't have classinfo we want to return the same
198 : // XPCWrappedNative so that we keep the same set of interfaces.
199 1060 : if (!wn->GetClassInfo())
200 739 : return DoubleWrap(cx, obj, flags);
201 :
202 642 : JSAutoEnterCompartment ac;
203 321 : if (!ac.enter(cx, obj))
204 0 : return nsnull;
205 642 : XPCCallContext ccx(JS_CALLER, cx, obj);
206 :
207 : {
208 321 : if (NATIVE_HAS_FLAG(&ccx, WantPreCreate)) {
209 : // We have a precreate hook. This object might enforce that we only
210 : // ever create JS object for it.
211 2 : JSObject *originalScope = scope;
212 2 : nsresult rv = wn->GetScriptableInfo()->GetCallback()->
213 2 : PreCreate(wn->Native(), cx, scope, &scope);
214 2 : NS_ENSURE_SUCCESS(rv, DoubleWrap(cx, obj, flags));
215 :
216 : // If the handed back scope differs from the passed-in scope and is in
217 : // a separate compartment, then this object is explicitly requesting
218 : // that we don't create a second JS object for it: create a security
219 : // wrapper.
220 2 : if (js::GetObjectCompartment(originalScope) != js::GetObjectCompartment(scope))
221 0 : return DoubleWrap(cx, obj, flags);
222 :
223 : // Note: this penalizes objects that only have one wrapper, but are
224 : // being accessed across compartments. We would really prefer to
225 : // replace the above code with a test that says "do you only have one
226 : // wrapper?"
227 : }
228 : }
229 :
230 : // NB: Passing a holder here inhibits slim wrappers under
231 : // WrapNativeToJSVal.
232 642 : nsCOMPtr<nsIXPConnectJSObjectHolder> holder;
233 :
234 : // This public WrapNativeToJSVal API enters the compartment of 'scope'
235 : // so we don't have to.
236 : jsval v;
237 : nsresult rv =
238 321 : nsXPConnect::FastGetXPConnect()->WrapNativeToJSVal(cx, scope, wn->Native(), nsnull,
239 : &NS_GET_IID(nsISupports), false,
240 321 : &v, getter_AddRefs(holder));
241 321 : if (NS_SUCCEEDED(rv)) {
242 321 : obj = JSVAL_TO_OBJECT(v);
243 321 : NS_ASSERTION(IS_WN_WRAPPER(obj), "bad object");
244 :
245 321 : XPCWrappedNative *newwn = static_cast<XPCWrappedNative *>(xpc_GetJSPrivate(obj));
246 321 : if (newwn->GetSet()->GetInterfaceCount() < wn->GetSet()->GetInterfaceCount())
247 158 : newwn->SetSet(wn->GetSet());
248 : }
249 :
250 321 : return DoubleWrap(cx, obj, flags);
251 : }
252 :
253 : static XPCWrappedNative *
254 50 : GetWrappedNative(JSContext *cx, JSObject *obj)
255 : {
256 50 : obj = JS_ObjectToInnerObject(cx, obj);
257 50 : return IS_WN_WRAPPER(obj)
258 0 : ? static_cast<XPCWrappedNative *>(js::GetObjectPrivate(obj))
259 50 : : nsnull;
260 : }
261 :
262 : static bool
263 206 : CanXray(JSObject *obj, bool *proxy)
264 : {
265 206 : if (IS_WN_WRAPPER(obj) || js::GetObjectClass(obj)->ext.innerObject) {
266 0 : *proxy = false;
267 0 : return true;
268 : }
269 206 : return (*proxy = mozilla::dom::binding::instanceIsProxy(obj));
270 : }
271 :
272 : JSObject *
273 10841 : WrapperFactory::Rewrap(JSContext *cx, JSObject *obj, JSObject *wrappedProto, JSObject *parent,
274 : unsigned flags)
275 : {
276 10841 : NS_ASSERTION(!IsWrapper(obj) ||
277 : GetProxyHandler(obj) == &WaiveXrayWrapperWrapper ||
278 : js::GetObjectClass(obj)->ext.innerObject,
279 : "wrapped object passed to rewrap");
280 10841 : NS_ASSERTION(JS_GetClass(obj) != &XrayUtils::HolderClass, "trying to wrap a holder");
281 :
282 10841 : JSCompartment *origin = js::GetObjectCompartment(obj);
283 10841 : JSCompartment *target = js::GetContextCompartment(cx);
284 10841 : JSObject *xrayHolder = nsnull;
285 :
286 : Wrapper *wrapper;
287 : CompartmentPrivate *targetdata =
288 10841 : static_cast<CompartmentPrivate *>(JS_GetCompartmentPrivate(target));
289 10841 : if (AccessCheck::isChrome(target)) {
290 10791 : if (AccessCheck::isChrome(origin)) {
291 10585 : wrapper = &CrossCompartmentWrapper::singleton;
292 : } else {
293 : bool isSystem;
294 : {
295 412 : JSAutoEnterCompartment ac;
296 206 : if (!ac.enter(cx, obj))
297 0 : return nsnull;
298 206 : JSObject *globalObj = JS_GetGlobalForObject(cx, obj);
299 206 : JS_ASSERT(globalObj);
300 412 : isSystem = JS_IsSystemObject(cx, globalObj);
301 : }
302 :
303 206 : if (isSystem) {
304 0 : wrapper = &CrossCompartmentWrapper::singleton;
305 206 : } else if (flags & WAIVE_XRAY_WRAPPER_FLAG) {
306 : // If we waived the X-ray wrapper for this object, wrap it into a
307 : // special wrapper to transitively maintain the X-ray waiver.
308 0 : wrapper = &CrossOriginWrapper::singleton;
309 : } else {
310 : // Native objects must be wrapped into an X-ray wrapper.
311 : bool proxy;
312 206 : if (CanXray(obj, &proxy)) {
313 0 : if (proxy) {
314 0 : wrapper = &XrayProxy::singleton;
315 : } else {
316 : typedef XrayWrapper<CrossCompartmentSecurityWrapper> Xray;
317 0 : wrapper = &Xray::singleton;
318 0 : xrayHolder = Xray::createHolder(cx, obj, parent);
319 0 : if (!xrayHolder)
320 0 : return nsnull;
321 : }
322 : } else {
323 206 : wrapper = &NoWaiverWrapper::singleton;
324 : }
325 : }
326 : }
327 50 : } else if (AccessCheck::isChrome(origin)) {
328 50 : JSFunction *fun = JS_GetObjectFunction(obj);
329 50 : if (fun) {
330 40 : if (JS_IsBuiltinEvalFunction(fun) || JS_IsBuiltinFunctionConstructor(fun)) {
331 0 : JS_ReportError(cx, "Not allowed to access chrome eval or Function from content");
332 0 : return nsnull;
333 : }
334 : }
335 :
336 : XPCWrappedNative *wn;
337 50 : if (targetdata &&
338 : (wn = GetWrappedNative(cx, obj)) &&
339 0 : wn->HasProto() && wn->GetProto()->ClassIsDOMObject()) {
340 : typedef XrayWrapper<CrossCompartmentSecurityWrapper> Xray;
341 : wrapper = &FilteringWrapper<Xray,
342 0 : CrossOriginAccessiblePropertiesOnly>::singleton;
343 0 : xrayHolder = Xray::createHolder(cx, obj, parent);
344 0 : if (!xrayHolder)
345 0 : return nsnull;
346 : } else {
347 : wrapper = &FilteringWrapper<CrossCompartmentSecurityWrapper,
348 50 : ExposedPropertiesOnly>::singleton;
349 : }
350 0 : } else if (AccessCheck::isSameOrigin(origin, target)) {
351 : // Same origin we use a transparent wrapper, unless the compartment asks
352 : // for an Xray or the wrapper needs a SOW.
353 : bool proxy;
354 0 : if (AccessCheck::needsSystemOnlyWrapper(obj)) {
355 : wrapper = &FilteringWrapper<CrossCompartmentSecurityWrapper,
356 0 : OnlyIfSubjectIsSystem>::singleton;
357 0 : } else if (targetdata && targetdata->wantXrays && CanXray(obj, &proxy)) {
358 0 : if (proxy) {
359 0 : wrapper = &XrayProxy::singleton;
360 : } else {
361 : typedef XrayWrapper<CrossCompartmentWrapper> Xray;
362 0 : wrapper = &Xray::singleton;
363 0 : xrayHolder = Xray::createHolder(cx, obj, parent);
364 0 : if (!xrayHolder)
365 0 : return nsnull;
366 : }
367 : } else {
368 0 : wrapper = &CrossCompartmentWrapper::singleton;
369 : }
370 : } else {
371 0 : NS_ASSERTION(!AccessCheck::needsSystemOnlyWrapper(obj),
372 : "bad object exposed across origins");
373 :
374 : // Cross origin we want to disallow scripting and limit access to
375 : // a predefined set of properties. XrayWrapper adds a property
376 : // (.wrappedJSObject) which allows bypassing the XrayWrapper, but
377 : // we filter out access to that property.
378 : bool proxy;
379 0 : if (!CanXray(obj, &proxy)) {
380 : wrapper = &FilteringWrapper<CrossCompartmentSecurityWrapper,
381 0 : CrossOriginAccessiblePropertiesOnly>::singleton;
382 : } else {
383 0 : if (proxy) {
384 : wrapper = &FilteringWrapper<XrayProxy,
385 0 : CrossOriginAccessiblePropertiesOnly>::singleton;
386 : } else {
387 : typedef XrayWrapper<CrossCompartmentSecurityWrapper> Xray;
388 :
389 : // Location objects can become same origin after navigation, so we might
390 : // have to grant transparent access later on.
391 0 : if (IsLocationObject(obj)) {
392 : wrapper = &FilteringWrapper<Xray,
393 0 : SameOriginOrCrossOriginAccessiblePropertiesOnly>::singleton;
394 : } else {
395 : wrapper = &FilteringWrapper<Xray,
396 0 : CrossOriginAccessiblePropertiesOnly>::singleton;
397 : }
398 :
399 0 : xrayHolder = Xray::createHolder(cx, obj, parent);
400 0 : if (!xrayHolder)
401 0 : return nsnull;
402 : }
403 : }
404 : }
405 :
406 10841 : JSObject *wrapperObj = Wrapper::New(cx, obj, wrappedProto, parent, wrapper);
407 10841 : if (!wrapperObj || !xrayHolder)
408 10841 : return wrapperObj;
409 :
410 0 : js::SetProxyExtra(wrapperObj, 0, js::ObjectValue(*xrayHolder));
411 0 : return wrapperObj;
412 : }
413 :
414 : typedef FilteringWrapper<XrayWrapper<SameCompartmentSecurityWrapper>,
415 : SameOriginOrCrossOriginAccessiblePropertiesOnly> LW;
416 :
417 : bool
418 1449246 : WrapperFactory::IsLocationObject(JSObject *obj)
419 : {
420 1449246 : const char *name = js::GetObjectClass(obj)->name;
421 1449246 : return name[0] == 'L' && !strcmp(name, "Location");
422 : }
423 :
424 : JSObject *
425 0 : WrapperFactory::WrapLocationObject(JSContext *cx, JSObject *obj)
426 : {
427 0 : JSObject *xrayHolder = LW::createHolder(cx, obj, js::GetObjectParent(obj));
428 0 : if (!xrayHolder)
429 0 : return nsnull;
430 : JSObject *wrapperObj = Wrapper::New(cx, obj, js::GetObjectProto(obj), js::GetObjectParent(obj),
431 0 : &LW::singleton);
432 0 : if (!wrapperObj)
433 0 : return nsnull;
434 0 : js::SetProxyExtra(wrapperObj, 0, js::ObjectValue(*xrayHolder));
435 0 : return wrapperObj;
436 : }
437 :
438 : // Call WaiveXrayAndWrap when you have a JS object that you don't want to be
439 : // wrapped in an Xray wrapper. cx->compartment is the compartment that will be
440 : // using the returned object. If the object to be wrapped is already in the
441 : // correct compartment, then this returns the unwrapped object.
442 : bool
443 0 : WrapperFactory::WaiveXrayAndWrap(JSContext *cx, jsval *vp)
444 : {
445 0 : if (JSVAL_IS_PRIMITIVE(*vp))
446 0 : return JS_WrapValue(cx, vp);
447 :
448 0 : JSObject *obj = js::UnwrapObject(JSVAL_TO_OBJECT(*vp));
449 0 : obj = GetCurrentOuter(cx, obj);
450 0 : if (js::IsObjectInContextCompartment(obj, cx)) {
451 0 : *vp = OBJECT_TO_JSVAL(obj);
452 0 : return true;
453 : }
454 :
455 0 : obj = WaiveXray(cx, obj);
456 0 : if (!obj)
457 0 : return false;
458 :
459 0 : *vp = OBJECT_TO_JSVAL(obj);
460 0 : return JS_WrapValue(cx, vp);
461 : }
462 :
463 : JSObject *
464 0 : WrapperFactory::WrapSOWObject(JSContext *cx, JSObject *obj)
465 : {
466 : JSObject *wrapperObj =
467 : Wrapper::New(cx, obj, JS_GetPrototype(obj), JS_GetGlobalForObject(cx, obj),
468 : &FilteringWrapper<SameCompartmentSecurityWrapper,
469 0 : OnlyIfSubjectIsSystem>::singleton);
470 0 : return wrapperObj;
471 : }
472 :
473 4392 : }
|