fune/toolkit/components/extensions/ExtensionPolicyService.cpp
Kris Maglione b3ba0520ee Bug 1396449: Part 1 - Use WebExtensionPolicy objects in extension content principals. r=krizsa
Going through the extension policy service rather than using
WebExtensionPolicy objects directly adds a lot of unnecessary overhead to
common operations on extension principals, and also makes the code more
complicated than it needs to be.

We also use weak references to policy objects here, since principals should
ideally lose as much of their elevated privileges as possible once the
extension instance that created them has been destroyed (which is something we
couldn't handle easily when we simply tracked ID strings).

MozReview-Commit-ID: KDNvVdvLkIt

--HG--
extra : rebase_source : 1b567919d2461bd0315d1a7d89f330cbd585f579
2017-09-05 11:04:43 -07:00

422 lines
12 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
/* 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 "mozilla/ExtensionPolicyService.h"
#include "mozilla/extensions/WebExtensionContentScript.h"
#include "mozilla/extensions/WebExtensionPolicy.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/Preferences.h"
#include "mozilla/Services.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/ContentParent.h"
#include "mozIExtensionProcessScript.h"
#include "nsEscape.h"
#include "nsGkAtoms.h"
#include "nsIChannel.h"
#include "nsIContentPolicy.h"
#include "nsIDOMDocument.h"
#include "nsIDocument.h"
#include "nsILoadInfo.h"
#include "nsNetUtil.h"
#include "nsPIDOMWindow.h"
#include "nsXULAppAPI.h"
namespace mozilla {
using namespace extensions;
#define DEFAULT_BASE_CSP \
"script-src 'self' https://* moz-extension: blob: filesystem: 'unsafe-eval' 'unsafe-inline'; " \
"object-src 'self' https://* moz-extension: blob: filesystem:;"
#define DEFAULT_DEFAULT_CSP \
"script-src 'self'; object-src 'self';"
#define OBS_TOPIC_PRELOAD_SCRIPT "web-extension-preload-content-script"
#define OBS_TOPIC_LOAD_SCRIPT "web-extension-load-content-script"
static mozIExtensionProcessScript&
ProcessScript()
{
static nsCOMPtr<mozIExtensionProcessScript> sProcessScript;
if (MOZ_UNLIKELY(!sProcessScript)) {
sProcessScript = do_GetService("@mozilla.org/webextensions/extension-process-script;1");
MOZ_RELEASE_ASSERT(sProcessScript);
ClearOnShutdown(&sProcessScript);
}
return *sProcessScript;
}
/*****************************************************************************
* ExtensionPolicyService
*****************************************************************************/
/* static */ bool ExtensionPolicyService::sRemoteExtensions;
/* static */ ExtensionPolicyService&
ExtensionPolicyService::GetSingleton()
{
static RefPtr<ExtensionPolicyService> sExtensionPolicyService;
if (MOZ_UNLIKELY(!sExtensionPolicyService)) {
sExtensionPolicyService = new ExtensionPolicyService();
ClearOnShutdown(&sExtensionPolicyService);
}
return *sExtensionPolicyService.get();
}
ExtensionPolicyService::ExtensionPolicyService()
{
mObs = services::GetObserverService();
MOZ_RELEASE_ASSERT(mObs);
Preferences::AddBoolVarCache(&sRemoteExtensions, "extensions.webextensions.remote", false);
RegisterObservers();
}
bool
ExtensionPolicyService::IsExtensionProcess() const
{
if (sRemoteExtensions && XRE_IsContentProcess()) {
auto& remoteType = dom::ContentChild::GetSingleton()->GetRemoteType();
return remoteType.EqualsLiteral(EXTENSION_REMOTE_TYPE);
}
return XRE_IsParentProcess();
}
WebExtensionPolicy*
ExtensionPolicyService::GetByURL(const URLInfo& aURL)
{
if (aURL.Scheme() == nsGkAtoms::moz_extension) {
return GetByHost(aURL.Host());
}
return nullptr;
}
void
ExtensionPolicyService::GetAll(nsTArray<RefPtr<WebExtensionPolicy>>& aResult)
{
for (auto iter = mExtensions.Iter(); !iter.Done(); iter.Next()) {
aResult.AppendElement(iter.Data());
}
}
bool
ExtensionPolicyService::RegisterExtension(WebExtensionPolicy& aPolicy)
{
bool ok = (!GetByID(aPolicy.Id()) &&
!GetByHost(aPolicy.MozExtensionHostname()));
MOZ_ASSERT(ok);
if (!ok) {
return false;
}
mExtensions.Put(aPolicy.Id(), &aPolicy);
mExtensionHosts.Put(aPolicy.MozExtensionHostname(), &aPolicy);
return true;
}
bool
ExtensionPolicyService::UnregisterExtension(WebExtensionPolicy& aPolicy)
{
bool ok = (GetByID(aPolicy.Id()) == &aPolicy &&
GetByHost(aPolicy.MozExtensionHostname()) == &aPolicy);
MOZ_ASSERT(ok);
if (!ok) {
return false;
}
mExtensions.Remove(aPolicy.Id());
mExtensionHosts.Remove(aPolicy.MozExtensionHostname());
return true;
}
void
ExtensionPolicyService::BaseCSP(nsAString& aBaseCSP) const
{
nsresult rv;
rv = Preferences::GetString("extensions.webextensions.base-content-security-policy", aBaseCSP);
if (NS_FAILED(rv)) {
aBaseCSP.AssignLiteral(DEFAULT_BASE_CSP);
}
}
void
ExtensionPolicyService::DefaultCSP(nsAString& aDefaultCSP) const
{
nsresult rv;
rv = Preferences::GetString("extensions.webextensions.default-content-security-policy", aDefaultCSP);
if (NS_FAILED(rv)) {
aDefaultCSP.AssignLiteral(DEFAULT_DEFAULT_CSP);
}
}
/*****************************************************************************
* Content script management
*****************************************************************************/
void
ExtensionPolicyService::RegisterObservers()
{
mObs->AddObserver(this, "content-document-global-created", false);
mObs->AddObserver(this, "document-element-inserted", false);
if (XRE_IsContentProcess()) {
mObs->AddObserver(this, "http-on-opening-request", false);
}
}
void
ExtensionPolicyService::UnregisterObservers()
{
mObs->RemoveObserver(this, "content-document-global-created");
mObs->RemoveObserver(this, "document-element-inserted");
if (XRE_IsContentProcess()) {
mObs->RemoveObserver(this, "http-on-opening-request");
}
}
nsresult
ExtensionPolicyService::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData)
{
if (!strcmp(aTopic, "content-document-global-created")) {
nsCOMPtr<nsPIDOMWindowOuter> win = do_QueryInterface(aSubject);
if (win) {
CheckWindow(win);
}
} else if (!strcmp(aTopic, "document-element-inserted")) {
nsCOMPtr<nsIDocument> doc = do_QueryInterface(aSubject);
if (doc) {
CheckDocument(doc);
}
} else if (!strcmp(aTopic, "http-on-opening-request")) {
nsCOMPtr<nsIChannel> chan = do_QueryInterface(aSubject);
if (chan) {
CheckRequest(chan);
}
}
return NS_OK;
}
// Checks a request for matching content scripts, and begins pre-loading them
// if necessary.
void
ExtensionPolicyService::CheckRequest(nsIChannel* aChannel)
{
nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
if (!loadInfo) {
return;
}
auto loadType = loadInfo->GetExternalContentPolicyType();
if (loadType != nsIContentPolicy::TYPE_DOCUMENT &&
loadType != nsIContentPolicy::TYPE_SUBDOCUMENT) {
return;
}
nsCOMPtr<nsIURI> uri;
if (NS_FAILED(aChannel->GetURI(getter_AddRefs(uri)))) {
return;
}
CheckContentScripts({uri.get(), loadInfo}, true);
}
// Checks a document, just after the document element has been inserted, for
// matching content scripts or extension principals, and loads them if
// necessary.
void
ExtensionPolicyService::CheckDocument(nsIDocument* aDocument)
{
nsCOMPtr<nsPIDOMWindowOuter> win = aDocument->GetWindow();
if (win) {
if (win->GetDocumentURI()) {
CheckContentScripts(win.get(), false);
}
nsIPrincipal* principal = aDocument->NodePrincipal();
RefPtr<WebExtensionPolicy> policy = BasePrincipal::Cast(principal)->AddonPolicy();
if (policy) {
nsCOMPtr<nsIDOMDocument> doc = do_QueryInterface(aDocument);
ProcessScript().InitExtensionDocument(policy, doc);
}
}
}
// Checks for loads of about:blank into new window globals, and loads any
// matching content scripts. about:blank loads do not trigger document element
// inserted events, so they're the only load type that are special cased this
// way.
void
ExtensionPolicyService::CheckWindow(nsPIDOMWindowOuter* aWindow)
{
// We only care about non-initial document loads here. The initial
// about:blank document will usually be re-used to load another document.
nsCOMPtr<nsIDocument> doc = aWindow->GetExtantDoc();
if (!doc || doc->IsInitialDocument() ||
doc->GetReadyStateEnum() == nsIDocument::READYSTATE_UNINITIALIZED) {
return;
}
nsCOMPtr<nsIURI> docUri = doc->GetDocumentURI();
nsCOMPtr<nsIURI> uri;
if (!docUri || NS_FAILED(docUri->CloneIgnoringRef(getter_AddRefs(uri))) ||
!NS_IsAboutBlank(uri)) {
return;
}
CheckContentScripts(aWindow, false);
}
void
ExtensionPolicyService::CheckContentScripts(const DocInfo& aDocInfo, bool aIsPreload)
{
for (auto iter = mExtensions.Iter(); !iter.Done(); iter.Next()) {
RefPtr<WebExtensionPolicy> policy = iter.Data();
for (auto& script : policy->ContentScripts()) {
if (script->Matches(aDocInfo)) {
if (aIsPreload) {
ProcessScript().PreloadContentScript(script);
} else {
ProcessScript().LoadContentScript(script, aDocInfo.GetWindow());
}
}
}
}
}
/*****************************************************************************
* nsIAddonPolicyService
*****************************************************************************/
nsresult
ExtensionPolicyService::GetBaseCSP(nsAString& aBaseCSP)
{
BaseCSP(aBaseCSP);
return NS_OK;
}
nsresult
ExtensionPolicyService::GetDefaultCSP(nsAString& aDefaultCSP)
{
DefaultCSP(aDefaultCSP);
return NS_OK;
}
nsresult
ExtensionPolicyService::GetAddonCSP(const nsAString& aAddonId,
nsAString& aResult)
{
if (WebExtensionPolicy* policy = GetByID(aAddonId)) {
policy->GetContentSecurityPolicy(aResult);
return NS_OK;
}
return NS_ERROR_INVALID_ARG;
}
nsresult
ExtensionPolicyService::GetGeneratedBackgroundPageUrl(const nsACString& aHostname,
nsACString& aResult)
{
if (WebExtensionPolicy* policy = GetByHost(aHostname)) {
nsAutoCString url("data:text/html,");
nsCString html = policy->BackgroundPageHTML();
nsAutoCString escaped;
url.Append(NS_EscapeURL(html, esc_Minimal, escaped));
aResult = url;
return NS_OK;
}
return NS_ERROR_INVALID_ARG;
}
nsresult
ExtensionPolicyService::AddonHasPermission(const nsAString& aAddonId,
const nsAString& aPerm,
bool* aResult)
{
if (WebExtensionPolicy* policy = GetByID(aAddonId)) {
*aResult = policy->HasPermission(aPerm);
return NS_OK;
}
return NS_ERROR_INVALID_ARG;
}
nsresult
ExtensionPolicyService::AddonMayLoadURI(const nsAString& aAddonId,
nsIURI* aURI,
bool aExplicit,
bool* aResult)
{
if (WebExtensionPolicy* policy = GetByID(aAddonId)) {
*aResult = policy->CanAccessURI(aURI, aExplicit);
return NS_OK;
}
return NS_ERROR_INVALID_ARG;
}
nsresult
ExtensionPolicyService::GetExtensionName(const nsAString& aAddonId,
nsAString& aName)
{
if (WebExtensionPolicy* policy = GetByID(aAddonId)) {
aName.Assign(policy->Name());
return NS_OK;
}
return NS_ERROR_INVALID_ARG;
}
nsresult
ExtensionPolicyService::ExtensionURILoadableByAnyone(nsIURI* aURI, bool* aResult)
{
URLInfo url(aURI);
if (WebExtensionPolicy* policy = GetByURL(url)) {
*aResult = policy->IsPathWebAccessible(url.FilePath());
return NS_OK;
}
return NS_ERROR_INVALID_ARG;
}
nsresult
ExtensionPolicyService::ExtensionURIToAddonId(nsIURI* aURI, nsAString& aResult)
{
if (WebExtensionPolicy* policy = GetByURL(aURI)) {
policy->GetId(aResult);
} else {
aResult.SetIsVoid(true);
}
return NS_OK;
}
NS_IMPL_CYCLE_COLLECTION(ExtensionPolicyService, mExtensions, mExtensionHosts)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ExtensionPolicyService)
NS_INTERFACE_MAP_ENTRY(nsIAddonPolicyService)
NS_INTERFACE_MAP_ENTRY(nsIObserver)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAddonPolicyService)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(ExtensionPolicyService)
NS_IMPL_CYCLE_COLLECTING_RELEASE(ExtensionPolicyService)
} // namespace mozilla