Bug 800915 - Add infrastructure to flag security wrappers as unsafe to unwrap. r=mrbkap
authorBobby Holley <bobbyholley@gmail.com>
Mon, 12 Nov 2012 17:35:32 -0800
changeset 113047 8e3d976d5dc5c0029cf810a83f7f5bcecf0aeac9
parent 113046 c7499faaec23ae05c3031ae248f5f8a5d8d313a3
child 113048 c567df2244f59d53a71467afa461a12adad8ba7f
push id23848
push useremorley@mozilla.com
push dateTue, 13 Nov 2012 16:29:34 +0000
treeherdermozilla-central@d56d537a1843 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmrbkap
bugs800915
milestone19.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 800915 - Add infrastructure to flag security wrappers as unsafe to unwrap. r=mrbkap
dom/base/nsGlobalWindow.cpp
js/src/jswrapper.cpp
js/src/jswrapper.h
js/xpconnect/wrappers/WrapperFactory.cpp
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -514,17 +514,17 @@ nsPIDOMWindow::~nsPIDOMWindow() {}
 
 //*****************************************************************************
 // nsOuterWindowProxy: Outer Window Proxy
 //*****************************************************************************
 
 class nsOuterWindowProxy : public js::Wrapper
 {
 public:
-  nsOuterWindowProxy() : js::Wrapper(0) {}
+  nsOuterWindowProxy() : js::Wrapper(0) { setSafeToUnwrap(false); }
 
   virtual bool isOuterWindow() {
     return true;
   }
   JSString *obj_toString(JSContext *cx, JSObject *wrapper);
   void finalize(JSFreeOp *fop, JSObject *proxy);
 
   static nsOuterWindowProxy singleton;
--- a/js/src/jswrapper.cpp
+++ b/js/src/jswrapper.cpp
@@ -155,16 +155,17 @@ js::IsCrossCompartmentWrapper(RawObject 
         return (op);                                                         \
     JS_END_MACRO
 
 #define SET(action) CHECKED(action, SET)
 #define GET(action) CHECKED(action, GET)
 
 Wrapper::Wrapper(unsigned flags, bool hasPrototype) : DirectProxyHandler(&sWrapperFamily)
                                                     , mFlags(flags)
+                                                    , mSafeToUnwrap(true)
 {
     setHasPrototype(hasPrototype);
 }
 
 Wrapper::~Wrapper()
 {
 }
 
@@ -791,17 +792,19 @@ CrossCompartmentWrapper::getPrototypeOf(
 
 CrossCompartmentWrapper CrossCompartmentWrapper::singleton(0u);
 
 /* Security wrappers. */
 
 template <class Base>
 SecurityWrapper<Base>::SecurityWrapper(unsigned flags)
   : Base(flags)
-{}
+{
+    Base::setSafeToUnwrap(false);
+}
 
 template <class Base>
 bool
 SecurityWrapper<Base>::nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl,
                                   CallArgs args)
 {
     /*
      * Let this through until compartment-per-global lets us have stronger
--- a/js/src/jswrapper.h
+++ b/js/src/jswrapper.h
@@ -24,30 +24,40 @@ class DummyFrameGuard;
  * Handler in the sense that it can be "unwrapped" in C++, exposing the underlying
  * object (Direct Proxy Handlers have an underlying target object, but don't
  * expect to expose this object via any kind of unwrapping operation). Callers
  * should be careful to avoid unwrapping security wrappers in the wrong context.
  */
 class JS_FRIEND_API(Wrapper) : public DirectProxyHandler
 {
     unsigned mFlags;
+    bool mSafeToUnwrap;
 
   public:
     enum Action {
         GET,
         SET,
         CALL,
         PUNCTURE
     };
 
     enum Flags {
         CROSS_COMPARTMENT = 1 << 0,
         LAST_USED_FLAG = CROSS_COMPARTMENT
     };
 
+    /*
+     * Wrappers can explicitly specify that they are unsafe to unwrap from a
+     * security perspective (as is the case for SecurityWrappers). If a wrapper
+     * is not safe to unwrap, operations requiring full access to the underlying
+     * object (via UnwrapObjectChecked) will throw. Otherwise, they will succeed.
+     */
+    void setSafeToUnwrap(bool safe) { mSafeToUnwrap = safe; };
+    bool isSafeToUnwrap() { return mSafeToUnwrap; };
+
     static JSObject *New(JSContext *cx, JSObject *obj, JSObject *proto,
                          JSObject *parent, Wrapper *handler);
 
     static JSObject *Renew(JSContext *cx, JSObject *existing, JSObject *obj, Wrapper *handler);
 
     static Wrapper *wrapperHandler(RawObject wrapper);
 
     static JSObject *wrappedObject(RawObject wrapper);
--- a/js/xpconnect/wrappers/WrapperFactory.cpp
+++ b/js/xpconnect/wrappers/WrapperFactory.cpp
@@ -286,16 +286,41 @@ static XPCWrappedNative *
 GetWrappedNative(JSContext *cx, JSObject *obj)
 {
     obj = JS_ObjectToInnerObject(cx, obj);
     return IS_WN_WRAPPER(obj)
            ? static_cast<XPCWrappedNative *>(js::GetObjectPrivate(obj))
            : nullptr;
 }
 
+#ifdef DEBUG
+static void
+DEBUG_CheckUnwrapSafety(JSObject *obj, js::Wrapper *handler,
+                        JSCompartment *origin, JSCompartment *target)
+{
+    typedef FilteringWrapper<CrossCompartmentSecurityWrapper, OnlyIfSubjectIsSystem> XSOW;
+
+    if (AccessCheck::isChrome(target) || xpc::IsUniversalXPConnectEnabled(target)) {
+        // If the caller is chrome (or effectively so), unwrap should always be allowed.
+        MOZ_ASSERT(handler->isSafeToUnwrap());
+    } else if (WrapperFactory::IsLocationObject(obj) ||
+               WrapperFactory::IsComponentsObject(obj) ||
+               handler == &XSOW::singleton)
+    {
+        // This is an object that is restricted regardless of origin.
+        MOZ_ASSERT(!handler->isSafeToUnwrap());
+    } else {
+        // Otherwise, it should depend on whether the target subsumes the origin.
+        MOZ_ASSERT(handler->isSafeToUnwrap() == AccessCheck::subsumes(target, origin));
+    }
+}
+#else
+#define DEBUG_CheckUnwrapSafety(obj, handler, origin, target) {}
+#endif
+
 JSObject *
 WrapperFactory::Rewrap(JSContext *cx, JSObject *existing, JSObject *obj,
                        JSObject *wrappedProto, JSObject *parent,
                        unsigned flags)
 {
     NS_ASSERTION(!IsWrapper(obj) ||
                  GetProxyHandler(obj) == &XrayWaiver ||
                  js::GetObjectClass(obj)->ext.innerObject,
@@ -438,16 +463,18 @@ WrapperFactory::Rewrap(JSContext *cx, JS
                 wrapper = &FilteringWrapper<SecurityXrayXPCWN, LocationPolicy>::singleton;
             } else {
                 wrapper = &FilteringWrapper<SecurityXrayXPCWN,
                                             CrossOriginAccessiblePropertiesOnly>::singleton;
             }
         }
     }
 
+    DEBUG_CheckUnwrapSafety(obj, wrapper, origin, target);
+
     if (existing && proxyProto == wrappedProto)
         return Wrapper::Renew(cx, existing, obj, wrapper);
 
     return Wrapper::New(cx, obj, proxyProto, parent, wrapper);
 }
 
 JSObject *
 WrapperFactory::WrapForSameCompartment(JSContext *cx, JSObject *obj)
@@ -460,17 +487,19 @@ WrapperFactory::WrapForSameCompartment(J
     if (!IS_WN_WRAPPER(obj))
         return obj;
 
     // Extract the WN. It should exist.
     XPCWrappedNative *wn = static_cast<XPCWrappedNative *>(xpc_GetJSPrivate(obj));
     MOZ_ASSERT(wn, "Trying to wrap a dead WN!");
 
     // The WN knows what to do.
-    return wn->GetSameCompartmentSecurityWrapper(cx);
+    JSObject *wrapper = wn->GetSameCompartmentSecurityWrapper(cx);
+    MOZ_ASSERT_IF(wrapper != obj, !Wrapper::wrapperHandler(wrapper)->isSafeToUnwrap());
+    return wrapper;
 }
 
 bool
 WrapperFactory::IsLocationObject(JSObject *obj)
 {
     const char *name = js::GetObjectClass(obj)->name;
     return name[0] == 'L' && !strcmp(name, "Location");
 }