Bug 1214824 - Forbid CPOW usage if add-on declares it is multiprocessCompatible (r=mrbkap)

This commit is contained in:
Bill McCloskey 2016-06-07 16:31:03 -07:00
parent 7afaf8c45c
commit c952246316
10 changed files with 162 additions and 13 deletions

View File

@ -63,26 +63,83 @@ ForbidUnsafeBrowserCPOWs()
return result;
}
// Should we allow CPOWs in aAddonId, even though it's marked as multiprocess
// compatible? This is controlled by two prefs:
// If dom.ipc.cpows.forbid-cpows-in-compat-addons is false, then we allow the CPOW.
// If dom.ipc.cpows.forbid-cpows-in-compat-addons is true:
// We check if aAddonId is listed in dom.ipc.cpows.allow-cpows-in-compat-addons
// (which should be a comma-separated string). If it's present there, we allow
// the CPOW. Otherwise we forbid the CPOW.
static bool
ForbidCPOWsInCompatibleAddon(const nsACString& aAddonId)
{
bool forbid = Preferences::GetBool("dom.ipc.cpows.forbid-cpows-in-compat-addons", false);
if (!forbid) {
return false;
}
nsCString allow;
allow.Assign(',');
allow.Append(Preferences::GetCString("dom.ipc.cpows.allow-cpows-in-compat-addons"));
allow.Append(',');
nsCString searchString(",");
searchString.Append(aAddonId);
searchString.Append(',');
return allow.Find(searchString) == kNotFound;
}
bool
JavaScriptParent::allowMessage(JSContext* cx)
{
MessageChannel* channel = GetIPCChannel();
if (channel->IsInTransaction())
return true;
// If we're running browser code, then we allow all safe CPOWs and forbid
// unsafe CPOWs based on a pref (which defaults to forbidden). We also allow
// CPOWs unconditionally in selected globals (based on
// Cu.permitCPOWsInScope).
//
// If we're running add-on code, then we check if the add-on is multiprocess
// compatible (which eventually translates to a given setting of allowCPOWs
// on the scopw). If it's not compatible, then we allow the CPOW but
// warn. If it is marked as compatible, then we check the
// ForbidCPOWsInCompatibleAddon; see the comment there.
if (ForbidUnsafeBrowserCPOWs()) {
nsIGlobalObject* global = dom::GetIncumbentGlobal();
JSObject* jsGlobal = global ? global->GetGlobalJSObject() : nullptr;
if (jsGlobal) {
JSAutoCompartment ac(cx, jsGlobal);
if (!JS::AddonIdOfObject(jsGlobal) && !xpc::CompartmentPrivate::Get(jsGlobal)->allowCPOWs) {
MessageChannel* channel = GetIPCChannel();
bool isSafe = channel->IsInTransaction();
bool warn = !isSafe;
nsIGlobalObject* global = dom::GetIncumbentGlobal();
JSObject* jsGlobal = global ? global->GetGlobalJSObject() : nullptr;
if (jsGlobal) {
JSAutoCompartment ac(cx, jsGlobal);
JSAddonId* addonId = JS::AddonIdOfObject(jsGlobal);
if (!xpc::CompartmentPrivate::Get(jsGlobal)->allowCPOWs) {
if (!addonId && ForbidUnsafeBrowserCPOWs() && !isSafe) {
Telemetry::Accumulate(Telemetry::BROWSER_SHIM_USAGE_BLOCKED, 1);
JS_ReportError(cx, "unsafe CPOW usage forbidden");
return false;
}
if (addonId) {
JSFlatString* flat = JS_ASSERT_STRING_IS_FLAT(JS::StringOfAddonId(addonId));
nsString addonIdString;
AssignJSFlatString(addonIdString, flat);
NS_ConvertUTF16toUTF8 addonIdCString(addonIdString);
Telemetry::Accumulate(Telemetry::ADDON_FORBIDDEN_CPOW_USAGE, addonIdCString);
if (ForbidCPOWsInCompatibleAddon(addonIdCString)) {
JS_ReportError(cx, "CPOW usage forbidden in this add-on");
return false;
}
warn = true;
}
}
}
if (!warn)
return true;
static bool disableUnsafeCPOWWarnings = PR_GetEnv("DISABLE_UNSAFE_CPOW_WARNINGS");
if (!disableUnsafeCPOWWarnings) {
nsCOMPtr<nsIConsoleService> console(do_GetService(NS_CONSOLESERVICE_CONTRACTID));
@ -91,7 +148,7 @@ JavaScriptParent::allowMessage(JSContext* cx)
uint32_t lineno = 0, column = 0;
nsJSUtils::GetCallingLocation(cx, filename, &lineno, &column);
nsCOMPtr<nsIScriptError> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID));
error->Init(NS_LITERAL_STRING("unsafe CPOW usage"), filename,
error->Init(NS_LITERAL_STRING("unsafe/forbidden CPOW usage"), filename,
EmptyString(), lineno, column,
nsIScriptError::warningFlag, "chrome javascript");
console->LogMessage(error);

View File

@ -679,6 +679,9 @@ interface nsIXPCComponents_Utils : nsISupports
[implicit_jscontext]
void setAddonCallInterposition(in jsval target);
[implicit_jscontext]
void allowCPOWsInAddon(in ACString addonId, in bool allow);
/*
* Return a fractional number of milliseconds from process
* startup, measured with a monotonic clock.

View File

@ -3358,6 +3358,20 @@ nsXPCComponents_Utils::SetAddonCallInterposition(HandleValue target,
return NS_OK;
}
NS_IMETHODIMP
nsXPCComponents_Utils::AllowCPOWsInAddon(const nsACString& addonIdStr,
bool allow,
JSContext* cx)
{
JSAddonId* addonId = xpc::NewAddonId(cx, addonIdStr);
if (!addonId)
return NS_ERROR_FAILURE;
if (!XPCWrappedNativeScope::AllowCPOWsInAddon(cx, addonId, allow))
return NS_ERROR_FAILURE;
return NS_OK;
}
NS_IMETHODIMP
nsXPCComponents_Utils::Now(double* aRetval)
{

View File

@ -26,8 +26,10 @@ using namespace JS;
XPCWrappedNativeScope* XPCWrappedNativeScope::gScopes = nullptr;
XPCWrappedNativeScope* XPCWrappedNativeScope::gDyingScopes = nullptr;
bool XPCWrappedNativeScope::gShutdownObserverInitialized = false;
XPCWrappedNativeScope::InterpositionMap* XPCWrappedNativeScope::gInterpositionMap = nullptr;
InterpositionWhitelistArray* XPCWrappedNativeScope::gInterpositionWhitelists = nullptr;
XPCWrappedNativeScope::AddonSet* XPCWrappedNativeScope::gAllowCPOWAddonSet = nullptr;
NS_IMPL_ISUPPORTS(XPCWrappedNativeScope::ClearInterpositionsObserver, nsIObserver)
@ -52,6 +54,11 @@ XPCWrappedNativeScope::ClearInterpositionsObserver::Observe(nsISupports* subject
gInterpositionWhitelists = nullptr;
}
if (gAllowCPOWAddonSet) {
delete gAllowCPOWAddonSet;
gAllowCPOWAddonSet = nullptr;
}
nsContentUtils::UnregisterShutdownObserver(this);
return NS_OK;
}
@ -156,6 +163,11 @@ XPCWrappedNativeScope::XPCWrappedNativeScope(JSContext* cx,
}
}
}
if (addonId) {
// We forbid CPOWs unless they're specifically allowed.
priv->allowCPOWs = gAllowCPOWAddonSet ? gAllowCPOWAddonSet->has(addonId) : false;
}
}
// static
@ -704,9 +716,10 @@ XPCWrappedNativeScope::SetAddonInterposition(JSContext* cx,
bool ok = gInterpositionMap->init();
NS_ENSURE_TRUE(ok, false);
// Make sure to clear the map at shutdown.
// Note: this will take care of gInterpositionWhitelists too.
nsContentUtils::RegisterShutdownObserver(new ClearInterpositionsObserver());
if (!gShutdownObserverInitialized) {
gShutdownObserverInitialized = true;
nsContentUtils::RegisterShutdownObserver(new ClearInterpositionsObserver());
}
}
if (interp) {
bool ok = gInterpositionMap->put(addonId, interp);
@ -718,6 +731,30 @@ XPCWrappedNativeScope::SetAddonInterposition(JSContext* cx,
return true;
}
/* static */ bool
XPCWrappedNativeScope::AllowCPOWsInAddon(JSContext* cx,
JSAddonId* addonId,
bool allow)
{
if (!gAllowCPOWAddonSet) {
gAllowCPOWAddonSet = new AddonSet();
bool ok = gAllowCPOWAddonSet->init();
NS_ENSURE_TRUE(ok, false);
if (!gShutdownObserverInitialized) {
gShutdownObserverInitialized = true;
nsContentUtils::RegisterShutdownObserver(new ClearInterpositionsObserver());
}
}
if (allow) {
bool ok = gAllowCPOWAddonSet->put(addonId);
NS_ENSURE_TRUE(ok, false);
} else {
gAllowCPOWAddonSet->remove(addonId);
}
return true;
}
nsCOMPtr<nsIAddonInterposition>
XPCWrappedNativeScope::GetInterposition()
{

View File

@ -1287,6 +1287,20 @@ SetAddonInterposition(const nsACString& addonIdStr, nsIAddonInterposition* inter
return XPCWrappedNativeScope::SetAddonInterposition(jsapi.cx(), addonId, interposition);
}
bool
AllowCPOWsInAddon(const nsACString& addonIdStr, bool allow)
{
JSAddonId* addonId;
// We enter the junk scope just to allocate a string, which actually will go
// in the system zone.
AutoJSAPI jsapi;
jsapi.Init(xpc::PrivilegedJunkScope());
addonId = NewAddonId(jsapi.cx(), addonIdStr);
if (!addonId)
return false;
return XPCWrappedNativeScope::AllowCPOWsInAddon(jsapi.cx(), addonId, allow);
}
} // namespace xpc
namespace mozilla {

View File

@ -997,6 +997,10 @@ public:
js::PointerHasher<JSAddonId*, 3>,
js::SystemAllocPolicy> InterpositionMap;
typedef js::HashSet<JSAddonId*,
js::PointerHasher<JSAddonId*, 3>,
js::SystemAllocPolicy> AddonSet;
// Gets the appropriate scope object for XBL in this scope. The context
// must be same-compartment with the global upon entering, and the scope
// object is wrapped into the compartment of the global.
@ -1028,6 +1032,8 @@ public:
void SetAddonCallInterposition() { mHasCallInterpositions = true; }
bool HasCallInterposition() { return mHasCallInterpositions; };
static bool AllowCPOWsInAddon(JSContext* cx, JSAddonId* addonId, bool allow);
protected:
virtual ~XPCWrappedNativeScope();
@ -1045,7 +1051,9 @@ private:
static XPCWrappedNativeScope* gScopes;
static XPCWrappedNativeScope* gDyingScopes;
static bool gShutdownObserverInitialized;
static InterpositionMap* gInterpositionMap;
static AddonSet* gAllowCPOWAddonSet;
static InterpositionWhitelistArray* gInterpositionWhitelists;

View File

@ -503,6 +503,9 @@ SharedMemoryEnabled();
bool
SetAddonInterposition(const nsACString& addonId, nsIAddonInterposition* interposition);
bool
AllowCPOWsInAddon(const nsACString& addonId, bool allow);
bool
ExtraWarningsForSystemJS();

View File

@ -42,6 +42,14 @@
"keyed": true,
"description": "Reasons why add-on shims were used, keyed by add-on ID."
},
"ADDON_FORBIDDEN_CPOW_USAGE": {
"expires_in_version": "never",
"kind": "count",
"keyed": true,
"description": "Counts the number of times a given add-on used CPOWs when it was marked as e10s compatible.",
"bug_numbers": [1214824],
"alert_emails": ["wmccloskey@mozilla.com"]
},
"BROWSER_SHIM_USAGE_BLOCKED": {
"expires_in_version": "never",
"kind": "count",

View File

@ -4606,6 +4606,7 @@ this.XPIProvider = {
let interposition = Cc["@mozilla.org/addons/multiprocess-shims;1"].
getService(Ci.nsIAddonInterposition);
Cu.setAddonInterposition(aId, interposition);
Cu.allowCPOWsInAddon(aId, true);
}
if (!aFile.exists()) {
@ -4682,6 +4683,7 @@ this.XPIProvider = {
// In case the add-on was not multiprocess-compatible, deregister
// any interpositions for it.
Cu.setAddonInterposition(aId, null);
Cu.allowCPOWsInAddon(aId, false);
this.activeAddons.delete(aId);
delete this.bootstrappedAddons[aId];

View File

@ -669,6 +669,9 @@ RegisterExtensionInterpositions(nsINIParser &parser)
if (!xpc::SetAddonInterposition(addonId, interposition))
continue;
if (!xpc::AllowCPOWsInAddon(addonId, true))
continue;
}
while (true);
}