Bug 1081990 - Generalize CheckPassToChrome machinery to operate on call/construct for all FilteringWrappers. r=gabor
authorBobby Holley <bobbyholley@gmail.com>
Sat, 18 Oct 2014 11:02:10 +0200
changeset 211112 712da524ebdd413c8dabf493505867f55973e1b7
parent 211111 c66cc3b4f587e877edc838ca6f0ef567ac9981b5
child 211113 576bab9d7f4cb6ec00188939b4046dd925cbb8ad
push id27667
push usercbook@mozilla.com
push dateMon, 20 Oct 2014 12:40:56 +0000
treeherdermozilla-central@cc2d8bdbccb8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgabor
bugs1081990
milestone36.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 1081990 - Generalize CheckPassToChrome machinery to operate on call/construct for all FilteringWrappers. r=gabor
js/xpconnect/wrappers/AccessCheck.h
js/xpconnect/wrappers/ChromeObjectWrapper.cpp
js/xpconnect/wrappers/ChromeObjectWrapper.h
js/xpconnect/wrappers/FilteringWrapper.cpp
js/xpconnect/wrappers/FilteringWrapper.h
--- a/js/xpconnect/wrappers/AccessCheck.h
+++ b/js/xpconnect/wrappers/AccessCheck.h
@@ -20,27 +20,35 @@ class AccessCheck {
     static bool subsumes(JSObject *a, JSObject *b);
     static bool wrapperSubsumes(JSObject *wrapper);
     static bool subsumesConsideringDomain(JSCompartment *a, JSCompartment *b);
     static bool isChrome(JSCompartment *compartment);
     static bool isChrome(JSObject *obj);
     static nsIPrincipal *getPrincipal(JSCompartment *compartment);
     static bool isCrossOriginAccessPermitted(JSContext *cx, JS::HandleObject obj,
                                              JS::HandleId id, js::Wrapper::Action act);
+    static bool checkPassToPrivilegedCode(JSContext *cx, JS::HandleObject wrapper,
+                                          JS::HandleValue value);
+    static bool checkPassToPrivilegedCode(JSContext *cx, JS::HandleObject wrapper,
+                                          const JS::CallArgs &args);
 };
 
 enum CrossOriginObjectType {
     CrossOriginWindow,
     CrossOriginLocation,
     CrossOriginOpaque
 };
 CrossOriginObjectType IdentifyCrossOriginObject(JSObject *obj);
 
 struct Policy {
     static const bool AllowGetPrototypeOf = false;
+
+    static bool checkCall(JSContext *cx, JS::HandleObject wrapper, const JS::CallArgs &args) {
+        MOZ_CRASH("As a rule, filtering wrappers are non-callable");
+    }
 };
 
 // This policy allows no interaction with the underlying callable. Everything throws.
 struct Opaque : public Policy {
     static bool check(JSContext *cx, JSObject *wrapper, jsid id, js::Wrapper::Action act) {
         return false;
     }
     static bool deny(js::Wrapper::Action act, JS::HandleId id) {
@@ -57,16 +65,19 @@ struct OpaqueWithCall : public Policy {
         return act == js::Wrapper::CALL;
     }
     static bool deny(js::Wrapper::Action act, JS::HandleId id) {
         return false;
     }
     static bool allowNativeCall(JSContext *cx, JS::IsAcceptableThis test, JS::NativeImpl impl) {
         return false;
     }
+    static bool checkCall(JSContext *cx, JS::HandleObject wrapper, const JS::CallArgs &args) {
+        return AccessCheck::checkPassToPrivilegedCode(cx, wrapper, args);
+    }
 };
 
 // This policy only permits access to properties that are safe to be used
 // across origins.
 struct CrossOriginAccessiblePropertiesOnly : public Policy {
     static bool check(JSContext *cx, JS::HandleObject wrapper, JS::HandleId id, js::Wrapper::Action act) {
         return AccessCheck::isCrossOriginAccessPermitted(cx, wrapper, id, act);
     }
@@ -91,13 +102,17 @@ struct ExposedPropertiesOnly : public Po
     static const bool AllowGetPrototypeOf = true;
 
     static bool check(JSContext *cx, JS::HandleObject wrapper, JS::HandleId id, js::Wrapper::Action act);
 
     static bool deny(js::Wrapper::Action act, JS::HandleId id);
     static bool allowNativeCall(JSContext *cx, JS::IsAcceptableThis test, JS::NativeImpl impl) {
         return false;
     }
+    static bool checkCall(JSContext *cx, JS::HandleObject wrapper, const JS::CallArgs &args) {
+        // XXXbholley - This goes away in the next patch.
+        return AccessCheck::checkPassToPrivilegedCode(cx, wrapper, args);
+    }
 };
 
 }
 
 #endif /* __AccessCheck_h__ */
--- a/js/xpconnect/wrappers/ChromeObjectWrapper.cpp
+++ b/js/xpconnect/wrappers/ChromeObjectWrapper.cpp
@@ -101,18 +101,18 @@ ChromeObjectWrapper::getPropertyDescript
     if (desc.object() || !wrapperProto)
         return true;
 
     // If not, try doing the lookup on the prototype.
     MOZ_ASSERT(js::IsObjectInContextCompartment(wrapper, cx));
     return JS_GetPropertyDescriptorById(cx, wrapperProto, id, desc);
 }
 
-static bool
-CheckPassToChrome(JSContext *cx, HandleObject wrapper, HandleValue v)
+bool
+AccessCheck::checkPassToPrivilegedCode(JSContext *cx, HandleObject wrapper, HandleValue v)
 {
     // Primitives are fine.
     if (!v.isObject())
         return true;
     RootedObject obj(cx, &v.toObject());
 
     // Non-wrappers are fine.
     if (!js::IsWrapper(obj))
@@ -123,67 +123,67 @@ CheckPassToChrome(JSContext *cx, HandleO
     // pass any objects at all to CPOWs.
     if (mozilla::jsipc::IsWrappedCPOW(obj) &&
         js::GetObjectCompartment(wrapper) == js::GetObjectCompartment(xpc::UnprivilegedJunkScope()) &&
         XRE_GetProcessType() == GeckoProcessType_Default)
     {
         return true;
     }
 
-    // COWs are fine to pass back if and only if they have __exposedProps__,
+    // COWs are fine to pass to chrome if and only if they have __exposedProps__,
     // since presumably content should never have a reason to pass an opaque
     // object back to chrome.
-    if (WrapperFactory::IsCOW(obj)) {
+    if (AccessCheck::isChrome(js::UncheckedUnwrap(wrapper)) && WrapperFactory::IsCOW(obj)) {
         RootedObject target(cx, js::UncheckedUnwrap(obj));
         JSAutoCompartment ac(cx, target);
         RootedId id(cx, GetRTIdByIndex(cx, XPCJSRuntime::IDX_EXPOSEDPROPS));
         bool found = false;
         if (!JS_HasPropertyById(cx, target, id, &found))
             return false;
         if (found)
             return true;
     }
 
     // Same-origin wrappers are fine.
     if (AccessCheck::wrapperSubsumes(obj))
         return true;
 
     // Badness.
-    JS_ReportError(cx, "Permission denied to pass object to chrome");
+    JS_ReportError(cx, "Permission denied to pass object to privileged code");
     return false;
 }
 
-static bool
-CheckPassToChrome(JSContext *cx, HandleObject wrapper, const CallArgs &args)
+bool
+AccessCheck::checkPassToPrivilegedCode(JSContext *cx, HandleObject wrapper, const CallArgs &args)
 {
-    if (!CheckPassToChrome(cx, wrapper, args.thisv()))
+    if (!checkPassToPrivilegedCode(cx, wrapper, args.thisv()))
         return false;
     for (size_t i = 0; i < args.length(); ++i) {
-        if (!CheckPassToChrome(cx, wrapper, args[i]))
+        if (!checkPassToPrivilegedCode(cx, wrapper, args[i]))
             return false;
     }
     return true;
 }
 
 bool
 ChromeObjectWrapper::defineProperty(JSContext *cx, HandleObject wrapper,
                                     HandleId id,
                                     MutableHandle<JSPropertyDescriptor> desc) const
 {
-    if (!CheckPassToChrome(cx, wrapper, desc.value()))
+    if (!AccessCheck::checkPassToPrivilegedCode(cx, wrapper, desc.value()))
         return false;
     return ChromeObjectWrapperBase::defineProperty(cx, wrapper, id, desc);
 }
 
 bool
 ChromeObjectWrapper::set(JSContext *cx, HandleObject wrapper,
                          HandleObject receiver, HandleId id,
                          bool strict, MutableHandleValue vp) const
 {
-    if (!CheckPassToChrome(cx, wrapper, vp))
+    if (!AccessCheck::checkPassToPrivilegedCode(cx, wrapper, vp))
         return false;
     return ChromeObjectWrapperBase::set(cx, wrapper, receiver, id, strict, vp);
 }
 
 
 
 bool
 ChromeObjectWrapper::has(JSContext *cx, HandleObject wrapper,
@@ -241,34 +241,16 @@ ChromeObjectWrapper::get(JSContext *cx, 
     if (!wrapperProto)
         return true;
 
     // Try the prototype.
     MOZ_ASSERT(js::IsObjectInContextCompartment(wrapper, cx));
     return js::GetGeneric(cx, wrapperProto, receiver, id, vp.address());
 }
 
-bool
-ChromeObjectWrapper::call(JSContext *cx, HandleObject wrapper,
-                      const CallArgs &args) const
-{
-    if (!CheckPassToChrome(cx, wrapper, args))
-        return false;
-    return ChromeObjectWrapperBase::call(cx, wrapper, args);
-}
-
-bool
-ChromeObjectWrapper::construct(JSContext *cx, HandleObject wrapper,
-                               const CallArgs &args) const
-{
-    if (!CheckPassToChrome(cx, wrapper, args))
-        return false;
-    return ChromeObjectWrapperBase::construct(cx, wrapper, args);
-}
-
 // SecurityWrapper categorically returns false for objectClassIs, but the
 // contacts API depends on Array.isArray returning true for COW-implemented
 // contacts. This isn't really ideal, but make it work for now.
 bool
 ChromeObjectWrapper::objectClassIs(HandleObject obj, js::ESClassValue classValue,
                                    JSContext *cx) const
 {
   return CrossCompartmentWrapper::objectClassIs(obj, classValue, cx);
--- a/js/xpconnect/wrappers/ChromeObjectWrapper.h
+++ b/js/xpconnect/wrappers/ChromeObjectWrapper.h
@@ -36,20 +36,16 @@ class ChromeObjectWrapper : public Chrom
                                 JS::MutableHandle<JSPropertyDescriptor> desc) const MOZ_OVERRIDE;
     virtual bool has(JSContext *cx, JS::Handle<JSObject*> wrapper,
                      JS::Handle<jsid> id, bool *bp) const MOZ_OVERRIDE;
     virtual bool get(JSContext *cx, JS::Handle<JSObject*> wrapper, JS::Handle<JSObject*> receiver,
                      JS::Handle<jsid> id, JS::MutableHandle<JS::Value> vp) const MOZ_OVERRIDE;
     virtual bool set(JSContext *cx, JS::Handle<JSObject*> wrapper,
                      JS::Handle<JSObject*> receiver, JS::Handle<jsid> id,
                      bool strict, JS::MutableHandle<JS::Value> vp) const MOZ_OVERRIDE;
-    virtual bool call(JSContext *cx, JS::Handle<JSObject*> wrapper,
-                      const JS::CallArgs &args) const MOZ_OVERRIDE;
-    virtual bool construct(JSContext *cx, JS::Handle<JSObject*> wrapper,
-                           const JS::CallArgs &args) const MOZ_OVERRIDE;
 
     virtual bool getPropertyDescriptor(JSContext *cx, JS::Handle<JSObject*> wrapper,
                                        JS::Handle<jsid> id,
                                        JS::MutableHandle<JSPropertyDescriptor> desc) const MOZ_OVERRIDE;
     virtual bool objectClassIs(JS::Handle<JSObject*> obj, js::ESClassValue classValue,
                                JSContext *cx) const MOZ_OVERRIDE;
 
     // NB: One might think we'd need to implement enumerate(),
--- a/js/xpconnect/wrappers/FilteringWrapper.cpp
+++ b/js/xpconnect/wrappers/FilteringWrapper.cpp
@@ -131,16 +131,36 @@ FilteringWrapper<Base, Policy>::iterate(
     // we don't know how to censor custom iterator objects. Instead we trigger
     // the default proxy iterate trap, which will ask enumerate() for the list
     // of (censored) ids.
     return js::BaseProxyHandler::iterate(cx, wrapper, flags, vp);
 }
 
 template <typename Base, typename Policy>
 bool
+FilteringWrapper<Base, Policy>::call(JSContext *cx, JS::Handle<JSObject*> wrapper,
+                                    const JS::CallArgs &args) const
+{
+    if (!Policy::checkCall(cx, wrapper, args))
+        return false;
+    return Base::call(cx, wrapper, args);
+}
+
+template <typename Base, typename Policy>
+bool
+FilteringWrapper<Base, Policy>::construct(JSContext *cx, JS::Handle<JSObject*> wrapper,
+                                          const JS::CallArgs &args) const
+{
+    if (!Policy::checkCall(cx, wrapper, args))
+        return false;
+    return Base::construct(cx, wrapper, args);
+}
+
+template <typename Base, typename Policy>
+bool
 FilteringWrapper<Base, Policy>::nativeCall(JSContext *cx, JS::IsAcceptableThis test,
                                            JS::NativeImpl impl, JS::CallArgs args) const
 {
     if (Policy::allowNativeCall(cx, test, impl))
         return Base::Permissive::nativeCall(cx, test, impl, args);
     return Base::Restrictive::nativeCall(cx, test, impl, args);
 }
 
--- a/js/xpconnect/wrappers/FilteringWrapper.h
+++ b/js/xpconnect/wrappers/FilteringWrapper.h
@@ -38,16 +38,22 @@ class FilteringWrapper : public Base {
 
     virtual bool getPropertyDescriptor(JSContext *cx, JS::Handle<JSObject*> wrapper,
                                        JS::Handle<jsid> id,
                                        JS::MutableHandle<JSPropertyDescriptor> desc) const MOZ_OVERRIDE;
     virtual bool getOwnEnumerablePropertyKeys(JSContext *cx, JS::Handle<JSObject*> wrapper,
                                               JS::AutoIdVector &props) const MOZ_OVERRIDE;
     virtual bool iterate(JSContext *cx, JS::Handle<JSObject*> wrapper, unsigned flags,
                          JS::MutableHandle<JS::Value> vp) const MOZ_OVERRIDE;
+
+    virtual bool call(JSContext *cx, JS::Handle<JSObject*> wrapper,
+                      const JS::CallArgs &args) const MOZ_OVERRIDE;
+    virtual bool construct(JSContext *cx, JS::Handle<JSObject*> wrapper,
+                           const JS::CallArgs &args) const MOZ_OVERRIDE;
+
     virtual bool nativeCall(JSContext *cx, JS::IsAcceptableThis test, JS::NativeImpl impl,
                             JS::CallArgs args) const MOZ_OVERRIDE;
 
     virtual bool defaultValue(JSContext *cx, JS::Handle<JSObject*> obj, JSType hint,
                               JS::MutableHandleValue vp) const MOZ_OVERRIDE;
 
     virtual bool getPrototypeOf(JSContext *cx, JS::HandleObject wrapper,
                                 JS::MutableHandleObject protop) const MOZ_OVERRIDE;