Bug 1438691 - [Please never land this] Tripwire observer notifications draft
authorMike Conley <mconley@mozilla.com>
Wed, 14 Feb 2018 16:44:55 -0500
changeset 755908 b1178aebe6d38bebdb751226c699cd026508c8a4
parent 754993 ff7175ca8486b5c8ca18d547eb938acd71c5c643
push id99317
push usermconley@mozilla.com
push dateThu, 15 Feb 2018 23:03:37 +0000
bugs1438691
milestone60.0a1
Bug 1438691 - [Please never land this] Tripwire observer notifications Quick-and-dirty hack that tries to notice when privileged JS attempts to access things outside of the top-level document compartment. There's a WebExtension that goes along with this. MozReview-Commit-ID: AYAiZE3Jafa
browser/base/content/content.js
dom/base/ChromeUtils.cpp
dom/base/ChromeUtils.h
dom/webidl/ChromeUtils.webidl
js/xpconnect/src/XPCJSRuntime.cpp
js/xpconnect/src/xpcprivate.h
js/xpconnect/wrappers/WaiveXrayWrapper.cpp
js/xpconnect/wrappers/XrayWrapper.cpp
toolkit/content/process-content.js
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -60,16 +60,17 @@ addEventListener("pageshow", function(ev
 });
 addEventListener("DOMAutoComplete", function(event) {
   LoginManagerContent.onUsernameInput(event);
 });
 addEventListener("blur", function(event) {
   LoginManagerContent.onUsernameInput(event);
 });
 
+
 const SEC_ERROR_BASE          = Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE;
 const MOZILLA_PKIX_ERROR_BASE = Ci.nsINSSErrorsService.MOZILLA_PKIX_ERROR_BASE;
 
 const SEC_ERROR_EXPIRED_CERTIFICATE                = SEC_ERROR_BASE + 11;
 const SEC_ERROR_UNKNOWN_ISSUER                     = SEC_ERROR_BASE + 13;
 const SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE         = SEC_ERROR_BASE + 30;
 const SEC_ERROR_OCSP_FUTURE_RESPONSE               = SEC_ERROR_BASE + 131;
 const SEC_ERROR_OCSP_OLD_RESPONSE                  = SEC_ERROR_BASE + 132;
@@ -1267,8 +1268,17 @@ let OfflineApps = {
     }
   },
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                          Ci.nsISupportsWeakReference]),
 };
 
 addEventListener("MozApplicationManifest", OfflineApps, false);
 addMessageListener("OfflineApps:StartFetching", OfflineApps);
+
+addEventListener("load", (e) => {
+  dump("Content is listening for cross-origin access for: " + content.document.nodePrincipal.origin + "\n");
+  if (content.frames.length > 0) {
+    ChromeUtils.logCrossOriginAccess(content.document.nodePrincipal);
+    dump("Trying to do a bad thing...\n");
+    dump("Subframe textContent length: " + content.frames[0].document.body.textContent.length + "\n");
+  }
+}, true);
--- a/dom/base/ChromeUtils.cpp
+++ b/dom/base/ChromeUtils.cpp
@@ -14,16 +14,17 @@
 #include "mozilla/CycleCollectedJSRuntime.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/dom/IdleDeadline.h"
 #include "mozilla/dom/UnionTypes.h"
 #include "mozilla/dom/WindowBinding.h" // For IdleRequestCallback/Options
 #include "nsThreadUtils.h"
 #include "mozJSComponentLoader.h"
 #include "GeckoProfiler.h"
+#include "xpcprivate.h"
 
 namespace mozilla {
 namespace dom {
 
 /* static */ void
 ChromeUtils::NondeterministicGetWeakMapKeys(GlobalObject& aGlobal,
                                             JS::Handle<JS::Value> aMap,
                                             JS::MutableHandle<JS::Value> aRetval,
@@ -560,16 +561,29 @@ ChromeUtils::DefineModuleGetter(const Gl
                                 ErrorResult& aRv)
 {
   if (!module_getter::DefineGetter(global.Context(), target, id, resourceURI)) {
     aRv.NoteJSContextException(global.Context());
   }
 }
 
 /* static */ void
+ChromeUtils::LogCrossOriginAccess(const GlobalObject& global,
+                                  nsIPrincipal* aPrincipal,
+                                  ErrorResult& aRv)
+{
+  if (!aPrincipal) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+
+  xpc::CompartmentPrivate::Get(global.Get())->logAccessOutsidePrincipal = aPrincipal;
+}
+
+/* static */ void
 ChromeUtils::OriginAttributesToSuffix(dom::GlobalObject& aGlobal,
                                       const dom::OriginAttributesDictionary& aAttrs,
                                       nsCString& aSuffix)
 
 {
   OriginAttributes attrs(aAttrs);
   attrs.CreateSuffix(aSuffix);
 }
--- a/dom/base/ChromeUtils.h
+++ b/dom/base/ChromeUtils.h
@@ -161,14 +161,18 @@ public:
                      JS::MutableHandle<JSObject*> aRetval,
                      ErrorResult& aRv);
 
   static void DefineModuleGetter(const GlobalObject& global,
                                  JS::Handle<JSObject*> target,
                                  const nsAString& id,
                                  const nsAString& resourceURI,
                                  ErrorResult& aRv);
+
+  static void LogCrossOriginAccess(const GlobalObject& global,
+                                   nsIPrincipal* principal,
+                                   ErrorResult& aRv);
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_ChromeUtils__
--- a/dom/webidl/ChromeUtils.webidl
+++ b/dom/webidl/ChromeUtils.webidl
@@ -289,16 +289,19 @@ partial namespace ChromeUtils {
    * @param target The target object on which to define the property.
    * @param id The name of the property to define, and of the symbol to
    *           import.
    * @param resourceURI The resource URI of the module, as passed to
    *                    ChromeUtils.import.
    */
   [Throws]
   void defineModuleGetter(object target, DOMString id, DOMString resourceURI);
+
+  [Throws]
+  void logCrossOriginAccess(Principal principal);
 };
 
 /**
  * Used by principals and the script security manager to represent origin
  * attributes. The first dictionary is designed to contain the full set of
  * OriginAttributes, the second is used for pattern-matching (i.e. does this
  * OriginAttributesDictionary match the non-empty attributes in this pattern).
  *
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -164,17 +164,18 @@ public:
   bool mContinuation;
   bool mActive;
   bool mPurge;
 };
 
 namespace xpc {
 
 CompartmentPrivate::CompartmentPrivate(JSCompartment* c)
-    : wantXrays(false)
+    : logAccessOutsidePrincipal(nullptr)
+    , wantXrays(false)
     , allowWaivers(true)
     , isWebExtensionContentScript(false)
     , hasInterposition(false)
     , waiveInterposition(false)
     , addonCallInterposition(false)
     , allowCPOWs(false)
     , isContentXBLCompartment(false)
     , isAddonCompartment(false)
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -3004,16 +3004,18 @@ public:
     }
 
     static CompartmentPrivate* Get(JSObject* object)
     {
         JSCompartment* compartment = js::GetObjectCompartment(object);
         return Get(compartment);
     }
 
+    nsCOMPtr<nsIPrincipal> logAccessOutsidePrincipal;
+
     // Controls whether this compartment gets Xrays to same-origin. This behavior
     // is deprecated, but is still the default for sandboxes for compatibity
     // reasons.
     bool wantXrays;
 
     // Controls whether this compartment is allowed to waive Xrays to content
     // that it subsumes. This should generally be true, except in cases where we
     // want to prevent code from depending on Xray Waivers (which might make it
--- a/js/xpconnect/wrappers/WaiveXrayWrapper.cpp
+++ b/js/xpconnect/wrappers/WaiveXrayWrapper.cpp
@@ -2,16 +2,17 @@
 /* vim: set ts=8 sts=4 et sw=4 tw=99: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WaiveXrayWrapper.h"
 #include "WrapperFactory.h"
 #include "jsapi.h"
+#include "nsContentUtils.h"
 
 using namespace JS;
 
 namespace xpc {
 
 static bool
 WaiveAccessors(JSContext* cx, MutableHandle<PropertyDescriptor> desc)
 {
@@ -30,24 +31,54 @@ WaiveAccessors(JSContext* cx, MutableHan
     }
     return true;
 }
 
 bool
 WaiveXrayWrapper::getPropertyDescriptor(JSContext* cx, HandleObject wrapper, HandleId id,
                                         MutableHandle<PropertyDescriptor> desc) const
 {
+    CompartmentPrivate* priv = CompartmentPrivate::Get(CurrentGlobalOrNull(cx));
+    nsIPrincipal* logOutsidePrincipal = priv->logAccessOutsidePrincipal;
+    if (logOutsidePrincipal) {
+      nsIPrincipal* thisPrincipal = nsContentUtils::ObjectPrincipal(wrapper);
+      MOZ_ASSERT(thisPrincipal);
+      bool equal = false;
+      thisPrincipal->Equals(logOutsidePrincipal, &equal);
+      if (!equal) {
+        MOZ_ASSERT(NS_IsMainThread());
+        nsCOMPtr<nsIObserverService> os(do_GetService("@mozilla.org/observer-service;1"));
+        if (os) {
+          os->NotifyObservers(nullptr, "opp-violation", nullptr);
+        }
+      }
+    }
     return CrossCompartmentWrapper::getPropertyDescriptor(cx, wrapper, id, desc) &&
            WrapperFactory::WaiveXrayAndWrap(cx, desc.value()) && WaiveAccessors(cx, desc);
 }
 
 bool
 WaiveXrayWrapper::getOwnPropertyDescriptor(JSContext* cx, HandleObject wrapper, HandleId id,
                                            MutableHandle<PropertyDescriptor> desc) const
 {
+    CompartmentPrivate* priv = CompartmentPrivate::Get(CurrentGlobalOrNull(cx));
+    nsIPrincipal* logOutsidePrincipal = priv->logAccessOutsidePrincipal;
+    if (logOutsidePrincipal) {
+      nsIPrincipal* thisPrincipal = nsContentUtils::ObjectPrincipal(wrapper);
+      MOZ_ASSERT(thisPrincipal);
+      bool equal = false;
+      thisPrincipal->Equals(logOutsidePrincipal, &equal);
+      if (!equal) {
+        MOZ_ASSERT(NS_IsMainThread());
+        nsCOMPtr<nsIObserverService> os(do_GetService("@mozilla.org/observer-service;1"));
+        if (os) {
+          os->NotifyObservers(nullptr, "opp-violation", nullptr);
+        }
+      }
+    }
     return CrossCompartmentWrapper::getOwnPropertyDescriptor(cx, wrapper, id, desc) &&
            WrapperFactory::WaiveXrayAndWrap(cx, desc.value()) && WaiveAccessors(cx, desc);
 }
 
 bool
 WaiveXrayWrapper::get(JSContext* cx, HandleObject wrapper, HandleValue receiver, HandleId id,
                       MutableHandleValue vp) const
 {
--- a/js/xpconnect/wrappers/XrayWrapper.cpp
+++ b/js/xpconnect/wrappers/XrayWrapper.cpp
@@ -2071,16 +2071,32 @@ XrayWrapper<Base, Traits>::isExtensible(
 }
 
 template <typename Base, typename Traits>
 bool
 XrayWrapper<Base, Traits>::getPropertyDescriptor(JSContext* cx, HandleObject wrapper, HandleId id,
                                                  JS::MutableHandle<PropertyDescriptor> desc)
                                                  const
 {
+    CompartmentPrivate* priv = CompartmentPrivate::Get(CurrentGlobalOrNull(cx));
+    nsIPrincipal* logOutsidePrincipal = priv->logAccessOutsidePrincipal;
+    if (logOutsidePrincipal) {
+      nsIPrincipal* thisPrincipal = ObjectPrincipal(wrapper);
+      MOZ_ASSERT(thisPrincipal);
+      bool equal = false;
+      thisPrincipal->Equals(logOutsidePrincipal, &equal);
+      if (!equal) {
+        MOZ_ASSERT(NS_IsMainThread());
+        nsCOMPtr<nsIObserverService> os(do_GetService("@mozilla.org/observer-service;1"));
+        if (os) {
+          os->NotifyObservers(nullptr, "opp-violation", nullptr);
+        }
+      }
+    }
+
     // We can't assert !Traits::HasPrototypes here, because
     // CrossOriginXrayWrapper::getOwnPropertyDescriptor calls us, but it uses
     // DOMXrayTraits, which have HasPrototype.
 
     assertEnteredPolicy(cx, wrapper, id, BaseProxyHandler::GET | BaseProxyHandler::SET |
                                          BaseProxyHandler::GET_PROPERTY_DESCRIPTOR);
     RootedObject target(cx, Traits::getTargetObject(wrapper));
     RootedObject holder(cx, Traits::singleton.ensureHolder(cx, wrapper));
@@ -2166,16 +2182,32 @@ XrayWrapper<Base, Traits>::getPropertyDe
 }
 
 template <typename Base, typename Traits>
 bool
 XrayWrapper<Base, Traits>::getOwnPropertyDescriptor(JSContext* cx, HandleObject wrapper, HandleId id,
                                                     JS::MutableHandle<PropertyDescriptor> desc)
                                                     const
 {
+    CompartmentPrivate* priv = CompartmentPrivate::Get(CurrentGlobalOrNull(cx));
+    nsIPrincipal* logOutsidePrincipal = priv->logAccessOutsidePrincipal;
+    if (logOutsidePrincipal) {
+      nsIPrincipal* thisPrincipal = ObjectPrincipal(wrapper);
+      MOZ_ASSERT(thisPrincipal);
+      bool equal = false;
+      thisPrincipal->Equals(logOutsidePrincipal, &equal);
+      if (!equal) {
+        MOZ_ASSERT(NS_IsMainThread());
+        nsCOMPtr<nsIObserverService> os(do_GetService("@mozilla.org/observer-service;1"));
+        if (os) {
+          os->NotifyObservers(nullptr, "opp-violation", nullptr);
+        }
+      }
+    }
+
     assertEnteredPolicy(cx, wrapper, id, BaseProxyHandler::GET | BaseProxyHandler::SET |
                                          BaseProxyHandler::GET_PROPERTY_DESCRIPTOR);
     RootedObject target(cx, Traits::getTargetObject(wrapper));
     RootedObject holder(cx, Traits::singleton.ensureHolder(cx, wrapper));
     if (!holder)
         return false;
 
     if (!Traits::singleton.resolveOwnProperty(cx, wrapper, target, holder, id, desc))
--- a/toolkit/content/process-content.js
+++ b/toolkit/content/process-content.js
@@ -19,16 +19,17 @@ Services.cpmm.addMessageListener("gmp-pl
   gmpservice.RunPluginCrashCallbacks(msg.data.pluginID, msg.data.pluginName);
 });
 
 if (gInContentProcess) {
   let ProcessObserver = {
     TOPICS: [
       "inner-window-destroyed",
       "xpcom-shutdown",
+      "opp-violation",
     ],
 
     init() {
       for (let topic of this.TOPICS) {
         Services.obs.addObserver(this, topic);
         Services.cpmm.addMessageListener("Memory:GetSummary", this);
       }
     },
@@ -71,14 +72,19 @@ if (gInContentProcess) {
           Services.cpmm.sendAsyncMessage("Toolkit:inner-window-destroyed",
                                          innerWindowID);
           break;
         }
         case "xpcom-shutdown": {
           this.uninit();
           break;
         }
+        case "opp-violation": {
+          let stack = new Error().stack.split("\n").slice(1);
+          Services.cpmm.sendAsyncMessage("Tripwire:opp-violation", { stack });
+          break;
+        }
       }
     },
   };
 
   ProcessObserver.init();
 }