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
--- 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();
}