Bug 872772 - Fix up lost Xray waivers when wrapping into the content compartment. r=gabor
authorBobby Holley <bobbyholley@gmail.com>
Fri, 17 May 2013 10:44:25 -0700
changeset 143764 532f418b93516e1c9481991fe5b7bc71f78265f0
parent 143763 341f9653ff921293c2dc3a38741c4f4fcd1f13b4
child 143765 c98f7f0305a7c0d4984354779b4791be73003b88
push id2697
push userbbajaj@mozilla.com
push dateMon, 05 Aug 2013 18:49:53 +0000
treeherdermozilla-beta@dfec938c7b63 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgabor
bugs872772
milestone24.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 872772 - Fix up lost Xray waivers when wrapping into the content compartment. r=gabor
js/xpconnect/wrappers/XrayWrapper.cpp
--- a/js/xpconnect/wrappers/XrayWrapper.cpp
+++ b/js/xpconnect/wrappers/XrayWrapper.cpp
@@ -26,16 +26,19 @@
 #include "nsGlobalWindow.h"
 
 using namespace mozilla::dom;
 using namespace JS;
 using namespace mozilla;
 
 using js::PropertyDescriptor;
 using js::Wrapper;
+using js::IsCrossCompartmentWrapper;
+using js::UncheckedUnwrap;
+using js::CheckedUnwrap;
 
 namespace xpc {
 
 static const uint32_t JSSLOT_RESOLVING = 0;
 
 static XPCWrappedNative *GetWrappedNative(JSObject *obj);
 
 namespace XrayUtils {
@@ -1634,16 +1637,73 @@ XrayWrapper<Base, Traits>::getOwnPropert
 
     if (!Traits::singleton.resolveOwnProperty(cx, *this, wrapper, holder, id, desc, flags))
         return false;
     if (desc->obj)
         desc->obj = wrapper;
     return true;
 }
 
+// Consider what happens when chrome does |xray.expando = xray.wrappedJSObject|.
+//
+// Since the expando comes from the target compartment, wrapping it back into
+// the target compartment to define it on the expando object ends up stripping
+// off the Xray waiver that gives |xray| and |xray.wrappedJSObject| different
+// identities. This is generally the right thing to do when wrapping across
+// compartments, but is incorrect in the special case of the Xray expando
+// object. Manually re-apply Xrays if necessary.
+//
+// NB: In order to satisfy the invariants of WaiveXray, we need to pass
+// in an object sans security wrapper, which means we need to strip off any
+// potential same-compartment security wrapper that may have been applied
+// to the content object. This is ok, because the the expando object is only
+// ever accessed by code across the compartment boundary.
+static bool
+RecreateLostWaivers(JSContext *cx, PropertyDescriptor *orig,
+                    MutableHandle<PropertyDescriptor> wrapped)
+{
+    // Compute whether the original objects were waived, and implicitly, whether
+    // they were objects at all.
+    bool valueWasWaived =
+        orig->value.isObject() &&
+        WrapperFactory::HasWaiveXrayFlag(&orig->value.toObject());
+    bool getterWasWaived =
+        (orig->attrs & JSPROP_GETTER) &&
+        WrapperFactory::HasWaiveXrayFlag(JS_FUNC_TO_DATA_PTR(JSObject*, orig->getter));
+    bool setterWasWaived =
+        (orig->attrs & JSPROP_SETTER) &&
+        WrapperFactory::HasWaiveXrayFlag(JS_FUNC_TO_DATA_PTR(JSObject*, orig->setter));
+
+    // Recreate waivers. Note that for value, we need an extra UncheckedUnwrap
+    // to handle same-compartment security wrappers (see above). This should
+    // never happen for getters/setters.
+
+    RootedObject rewaived(cx);
+    if (valueWasWaived && !IsCrossCompartmentWrapper(&wrapped.value().toObject())) {
+        rewaived = &wrapped.value().toObject();
+        rewaived = WrapperFactory::WaiveXray(cx, UncheckedUnwrap(rewaived));
+        NS_ENSURE_TRUE(rewaived, false);
+        wrapped.value().set(ObjectValue(*rewaived));
+    }
+    if (getterWasWaived && !IsCrossCompartmentWrapper(wrapped.getterObject())) {
+        MOZ_ASSERT(CheckedUnwrap(wrapped.getterObject()));
+        rewaived = WrapperFactory::WaiveXray(cx, wrapped.getterObject());
+        NS_ENSURE_TRUE(rewaived, false);
+        wrapped.setGetterObject(rewaived);
+    }
+    if (setterWasWaived && !IsCrossCompartmentWrapper(wrapped.setterObject())) {
+        MOZ_ASSERT(CheckedUnwrap(wrapped.setterObject()));
+        rewaived = WrapperFactory::WaiveXray(cx, wrapped.setterObject());
+        NS_ENSURE_TRUE(rewaived, false);
+        wrapped.setSetterObject(rewaived);
+    }
+
+    return true;
+}
+
 template <typename Base, typename Traits>
 bool
 XrayWrapper<Base, Traits>::defineProperty(JSContext *cx, HandleObject wrapper,
                                           HandleId id, PropertyDescriptor *desc)
 {
     assertEnteredPolicy(cx, wrapper, id);
 
     // NB: We still need JSRESOLVE_ASSIGNING here for the time being, because it
@@ -1672,16 +1732,20 @@ XrayWrapper<Base, Traits>::definePropert
     if (!expandoObject)
         return false;
 
     // Wrap the property descriptor for the target compartment.
     Rooted<PropertyDescriptor> wrappedDesc(cx, *desc);
     if (!JS_WrapPropertyDescriptor(cx, wrappedDesc.address()))
         return false;
 
+    // Fix up Xray waivers.
+    if (!RecreateLostWaivers(cx, desc, &wrappedDesc))
+        return false;
+
     return JS_DefinePropertyById(cx, expandoObject, id, wrappedDesc.value(),
                                  wrappedDesc.getter(), wrappedDesc.setter(),
                                  wrappedDesc.get().attrs);
 }
 
 template <typename Base, typename Traits>
 bool
 XrayWrapper<Base, Traits>::getOwnPropertyNames(JSContext *cx, HandleObject wrapper,