Bug 1415644: Create a list of restricted domains. r=aswan,mixedpuppy
authorKris Maglione <maglione.k@gmail.com>
Sat, 03 Mar 2018 16:28:18 -0800
changeset 407481 39e131181d442409a5df2ed945c02aca2b9baca2
parent 407480 f96e8d3c02251534d208bafb046d007970e284a5
child 407482 c291e7dfe010bbaaecac186abfbd8310fba127de
push id33602
push usernerli@mozilla.com
push dateSat, 10 Mar 2018 09:59:03 +0000
treeherdermozilla-central@0817a733d45a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaswan, mixedpuppy
bugs1415644
milestone60.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 1415644: Create a list of restricted domains. r=aswan,mixedpuppy MozReview-Commit-ID: A0AkaBG33In
modules/libpref/init/all.js
toolkit/components/extensions/MatchPattern.cpp
toolkit/components/extensions/MatchPattern.h
toolkit/components/extensions/WebExtensionPolicy.cpp
toolkit/components/extensions/WebExtensionPolicy.h
toolkit/components/extensions/webrequest/ChannelWrapper.cpp
toolkit/components/extensions/webrequest/ChannelWrapper.h
toolkit/modules/addons/WebRequest.jsm
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5112,16 +5112,17 @@ pref("extensions.webExtensionsMinPlatfor
 pref("extensions.legacy.enabled", true);
 pref("extensions.allow-non-mpc-extensions", true);
 
 // Other webextensions prefs
 pref("extensions.webextensions.keepStorageOnUninstall", false);
 pref("extensions.webextensions.keepUuidOnUninstall", false);
 // Redirect basedomain used by identity api
 pref("extensions.webextensions.identity.redirectDomain", "extensions.allizom.org");
+pref("extensions.webextensions.restrictedDomains", "accounts-static.cdn.mozilla.net,accounts.firefox.com,addons.cdn.mozilla.net,addons.mozilla.org,api.accounts.firefox.com,content.cdn.mozilla.net,content.cdn.mozilla.net,discovery.addons.mozilla.org,input.mozilla.org,install.mozilla.org,oauth.accounts.firefox.com,profile.accounts.firefox.com,support.mozilla.org,sync.services.mozilla.com,testpilot.firefox.com");
 // Whether or not webextension themes are supported.
 pref("extensions.webextensions.themes.enabled", false);
 pref("extensions.webextensions.themes.icons.enabled", false);
 pref("extensions.webextensions.remote", false);
 // Whether or not the moz-extension resource loads are remoted. For debugging
 // purposes only. Setting this to false will break moz-extension URI loading
 // unless other process sandboxing and extension remoting prefs are changed.
 pref("extensions.webextensions.protocol.remote", true);
--- a/toolkit/components/extensions/MatchPattern.cpp
+++ b/toolkit/components/extensions/MatchPattern.cpp
@@ -126,16 +126,25 @@ const nsCString&
 URLInfo::Host() const
 {
   if (mHost.IsVoid()) {
     Unused << mURI->GetHost(mHost);
   }
   return mHost;
 }
 
+const nsAtom*
+URLInfo::HostAtom() const
+{
+  if (!mHostAtom) {
+    mHostAtom = NS_Atomize(Host());
+  }
+  return mHostAtom;
+}
+
 const nsString&
 URLInfo::FilePath() const
 {
   if (mFilePath.IsEmpty()) {
     nsCString path;
     nsCOMPtr<nsIURL> url = do_QueryInterface(mURI);
     if (url && NS_SUCCEEDED(url->GetFilePath(path))) {
       AppendUTF8toUTF16(path, mFilePath);
--- a/toolkit/components/extensions/MatchPattern.h
+++ b/toolkit/components/extensions/MatchPattern.h
@@ -150,31 +150,33 @@ public:
   URLInfo(const URLInfo& aOther)
     : URLInfo(aOther.mURI.get())
   {}
 
   nsIURI* URI() const { return mURI; }
 
   nsAtom* Scheme() const;
   const nsCString& Host() const;
+  const nsAtom* HostAtom() const;
   const nsString& Path() const;
   const nsString& FilePath() const;
   const nsString& Spec() const;
   const nsCString& CSpec() const;
 
   bool InheritsPrincipal() const;
 
 private:
   nsIURI* URINoRef() const;
 
   nsCOMPtr<nsIURI> mURI;
   mutable nsCOMPtr<nsIURI> mURINoRef;
 
   mutable RefPtr<nsAtom> mScheme;
   mutable nsCString mHost;
+  mutable RefPtr<nsAtom> mHostAtom;
 
   mutable nsString mPath;
   mutable nsString mFilePath;
   mutable nsString mSpec;
   mutable nsCString mCSpec;
 
   mutable Maybe<bool> mInheritsPrincipal;
 };
--- a/toolkit/components/extensions/WebExtensionPolicy.cpp
+++ b/toolkit/components/extensions/WebExtensionPolicy.cpp
@@ -6,16 +6,17 @@
 #include "mozilla/ExtensionPolicyService.h"
 #include "mozilla/extensions/WebExtensionContentScript.h"
 #include "mozilla/extensions/WebExtensionPolicy.h"
 
 #include "mozilla/AddonManagerWebAPI.h"
 #include "mozilla/ResultExtensions.h"
 #include "nsEscape.h"
 #include "nsIDocShell.h"
+#include "nsIObserver.h"
 #include "nsISubstitutingProtocolHandler.h"
 #include "nsNetUtil.h"
 #include "nsPrintfCString.h"
 
 namespace mozilla {
 namespace extensions {
 
 using namespace dom;
@@ -29,16 +30,19 @@ static const char kBackgroundPageHTMLSta
 
 static const char kBackgroundPageHTMLScript[] = "\n\
     <script type=\"text/javascript\" src=\"%s\"></script>";
 
 static const char kBackgroundPageHTMLEnd[] = "\n\
   <body>\n\
 </html>";
 
+static const char kRestrictedDomainPref[] =
+  "extensions.webextensions.restrictedDomains";
+
 static inline ExtensionPolicyService&
 EPS()
 {
   return ExtensionPolicyService::GetSingleton();
 }
 
 static nsISubstitutingProtocolHandler*
 Proto()
@@ -260,16 +264,116 @@ WebExtensionPolicy::UseRemoteWebExtensio
 }
 
 /* static */ bool
 WebExtensionPolicy::IsExtensionProcess(GlobalObject& aGlobal)
 {
   return EPS().IsExtensionProcess();
 }
 
+namespace {
+  /**
+   * Maintains a dynamically updated AtomSet based on the comma-separated
+   * values in the given string pref.
+   */
+  class AtomSetPref : public nsIObserver
+                    , public nsSupportsWeakReference
+  {
+  public:
+    NS_DECL_ISUPPORTS
+    NS_DECL_NSIOBSERVER
+
+    static already_AddRefed<AtomSetPref>
+    Create(const char* aPref)
+    {
+      RefPtr<AtomSetPref> self = new AtomSetPref(aPref);
+      Preferences::AddWeakObserver(self, aPref);
+      return self.forget();
+    }
+
+    const AtomSet& Get() const;
+
+    bool Contains(const nsAtom* aAtom) const
+    {
+      return Get().Contains(aAtom);
+    }
+
+  protected:
+    virtual ~AtomSetPref() = default;
+
+    AtomSetPref(const char* aPref) : mPref(aPref)
+    {}
+
+  private:
+    mutable Maybe<AtomSet> mAtomSet;
+    const char* mPref;
+  };
+
+  const AtomSet&
+  AtomSetPref::Get() const
+  {
+    if (mAtomSet.isNothing()) {
+      nsAutoCString eltsString;
+      Unused << Preferences::GetCString(mPref, eltsString);
+
+      AutoTArray<nsString, 32> elts;
+      for (const nsACString& elt : eltsString.Split(',')) {
+        elts.AppendElement(NS_ConvertUTF8toUTF16(elt));
+        elts.LastElement().StripWhitespace();
+      }
+      mAtomSet.emplace(elts);
+    }
+
+    return mAtomSet.ref();
+  }
+
+  NS_IMETHODIMP
+  AtomSetPref::Observe(nsISupports *aSubject, const char *aTopic,
+                       const char16_t *aData)
+  {
+    mAtomSet.reset();
+    return NS_OK;
+  }
+
+  NS_IMPL_ISUPPORTS(AtomSetPref, nsIObserver, nsISupportsWeakReference)
+};
+
+/* static */ bool
+WebExtensionPolicy::IsRestrictedDoc(const DocInfo& aDoc)
+{
+  // With the exception of top-level about:blank documents with null
+  // principals, we never match documents that have non-codebase principals,
+  // including those with null principals or system principals.
+  if (aDoc.Principal() && !aDoc.Principal()->GetIsCodebasePrincipal()) {
+    return true;
+  }
+
+  return IsRestrictedURI(aDoc.PrincipalURL());
+}
+
+/* static */ bool
+WebExtensionPolicy::IsRestrictedURI(const URLInfo &aURI)
+{
+  static RefPtr<AtomSetPref> domains;
+  if (!domains) {
+    domains = AtomSetPref::Create(kRestrictedDomainPref);
+    ClearOnShutdown(&domains);
+  }
+
+  if (domains->Contains(aURI.HostAtom())) {
+    return true;
+  }
+
+  if (AddonManagerWebAPI::IsValidSite(aURI.URI())) {
+    return true;
+  }
+
+  return false;
+}
+
 nsCString
 WebExtensionPolicy::BackgroundPageHTML() const
 {
   nsAutoCString result;
 
   if (mBackgroundScripts.IsNull()) {
     result.SetIsVoid(true);
     return result;
@@ -358,17 +462,16 @@ WebExtensionContentScript::WebExtensionC
     mIncludeGlobs.SetValue().AppendElements(aInit.mIncludeGlobs.Value());
   }
 
   if (!aInit.mExcludeGlobs.IsNull()) {
     mExcludeGlobs.SetValue().AppendElements(aInit.mExcludeGlobs.Value());
   }
 }
 
-
 bool
 WebExtensionContentScript::Matches(const DocInfo& aDoc) const
 {
   if (!mFrameID.IsNull()) {
     if (aDoc.FrameID() != mFrameID.Value()) {
       return false;
     }
   } else {
@@ -385,30 +488,21 @@ WebExtensionContentScript::Matches(const
   // matchAboutBlank is true and it has the null principal. In all other
   // cases, we test the URL of the principal that it inherits.
   if (mMatchAboutBlank && aDoc.IsTopLevel() &&
       aDoc.URL().Spec().EqualsLiteral("about:blank") &&
       aDoc.Principal() && aDoc.Principal()->GetIsNullPrincipal()) {
     return true;
   }
 
-  // With the exception of top-level about:blank documents with null
-  // principals, we never match documents that have non-codebase principals,
-  // including those with null principals or system principals.
-  if (aDoc.Principal() && !aDoc.Principal()->GetIsCodebasePrincipal()) {
+  if (mExtension->IsRestrictedDoc(aDoc)) {
     return false;
   }
 
-  // Content scripts are not allowed on pages that have elevated
-  // privileges via mozAddonManager (see bug 1280234)
-  if (AddonManagerWebAPI::IsValidSite(aDoc.PrincipalURL().URI())) {
-    return false;
-  }
-
-  URLInfo urlinfo(aDoc.PrincipalURL());
+  auto& urlinfo = aDoc.PrincipalURL();
   if (mHasActiveTabPermission && aDoc.ShouldMatchActiveTabPermission() &&
       MatchPattern::MatchesAllURLs(urlinfo)) {
     return true;
   }
 
   return MatchesURI(urlinfo);
 }
 
@@ -426,17 +520,17 @@ WebExtensionContentScript::MatchesURI(co
   if (!mIncludeGlobs.IsNull() && !mIncludeGlobs.Value().Matches(aURL.Spec())) {
     return false;
   }
 
   if (!mExcludeGlobs.IsNull() && mExcludeGlobs.Value().Matches(aURL.Spec())) {
     return false;
   }
 
-  if (AddonManagerWebAPI::IsValidSite(aURL.URI())) {
+  if (mExtension->IsRestrictedURI(aURL)) {
     return false;
   }
 
   return true;
 }
 
 
 JSObject*
--- a/toolkit/components/extensions/WebExtensionPolicy.h
+++ b/toolkit/components/extensions/WebExtensionPolicy.h
@@ -20,16 +20,17 @@
 #include "nsWrapperCache.h"
 
 namespace mozilla {
 namespace extensions {
 
 using dom::WebExtensionInit;
 using dom::WebExtensionLocalizeCallback;
 
+class DocInfo;
 class WebExtensionContentScript;
 
 class WebExtensionPolicy final : public nsISupports
                                , public nsWrapperCache
                                , public SupportsWeakPtr<WebExtensionPolicy>
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
@@ -62,33 +63,37 @@ public:
   void RegisterContentScript(WebExtensionContentScript& script,
                              ErrorResult& aRv);
 
   void UnregisterContentScript(const WebExtensionContentScript& script,
                                ErrorResult& aRv);
 
   bool CanAccessURI(const URLInfo& aURI, bool aExplicit = false) const
   {
-    return mHostPermissions && mHostPermissions->Matches(aURI, aExplicit);
+    return (!IsRestrictedURI(aURI) &&
+            mHostPermissions && mHostPermissions->Matches(aURI, aExplicit));
   }
 
   bool IsPathWebAccessible(const nsAString& aPath) const
   {
     return mWebAccessiblePaths.Matches(aPath);
   }
 
   bool HasPermission(const nsAtom* aPermission) const
   {
     return mPermissions->Contains(aPermission);
   }
   bool HasPermission(const nsAString& aPermission) const
   {
     return mPermissions->Contains(aPermission);
   }
 
+  static bool IsRestrictedDoc(const DocInfo& aDoc);
+  static bool IsRestrictedURI(const URLInfo& aURI);
+
   nsCString BackgroundPageHTML() const;
 
   void Localize(const nsAString& aInput, nsString& aResult) const;
 
   const nsString& Name() const
   {
     return mName;
   }
--- a/toolkit/components/extensions/webrequest/ChannelWrapper.cpp
+++ b/toolkit/components/extensions/webrequest/ChannelWrapper.cpp
@@ -370,37 +370,31 @@ ChannelWrapper::IsSystemLoad() const
     if (nsIPrincipal* prin = loadInfo->TriggeringPrincipal()) {
       return IsSystemPrincipal(prin);
     }
   }
   return false;
 }
 
 bool
-ChannelWrapper::GetCanModify(ErrorResult& aRv) const
+ChannelWrapper::CanModify() const
 {
-  nsCOMPtr<nsIURI> uri = FinalURI();
-  nsAutoCString spec;
-  if (uri) {
-    uri->GetSpec(spec);
-  }
-  if (!uri || AddonManagerWebAPI::IsValidSite(uri)) {
+  if (WebExtensionPolicy::IsRestrictedURI(FinalURLInfo())) {
     return false;
   }
 
   if (nsCOMPtr<nsILoadInfo> loadInfo = GetLoadInfo()) {
     if (nsIPrincipal* prin = loadInfo->LoadingPrincipal()) {
       if (IsSystemPrincipal(prin)) {
         return false;
       }
 
-      if (prin->GetIsCodebasePrincipal() &&
-          (NS_FAILED(prin->GetURI(getter_AddRefs(uri))) ||
-           AddonManagerWebAPI::IsValidSite(uri))) {
-          return false;
+      auto* docURI = DocumentURLInfo();
+      if (docURI && WebExtensionPolicy::IsRestrictedURI(*docURI)) {
+        return false;
       }
     }
   }
   return true;
 }
 
 already_AddRefed<nsIURI>
 ChannelWrapper::GetOriginURI() const
@@ -642,17 +636,17 @@ ChannelWrapper::GetFrameAncestors(nsILoa
  * Response filtering
  *****************************************************************************/
 
 void
 ChannelWrapper::RegisterTraceableChannel(const WebExtensionPolicy& aAddon, nsITabParent* aTabParent)
 {
   // We can't attach new listeners after the response has started, so don't
   // bother registering anything.
-  if (mResponseStarted) {
+  if (mResponseStarted || !CanModify()) {
     return;
   }
 
   mAddonEntries.Put(aAddon.Id(), aTabParent);
   if (!mChannelEntry) {
     mChannelEntry = WebRequestService::GetSingleton().RegisterChannel(this);
     CheckEventListeners();
   }
--- a/toolkit/components/extensions/webrequest/ChannelWrapper.h
+++ b/toolkit/components/extensions/webrequest/ChannelWrapper.h
@@ -204,17 +204,21 @@ public:
   already_AddRefed<nsIURI> GetDocumentURI() const;
 
 
   already_AddRefed<nsILoadContext> GetLoadContext() const;
 
   already_AddRefed<nsIDOMElement> GetBrowserElement() const;
 
 
-  bool GetCanModify(ErrorResult& aRv) const;
+  bool CanModify() const;
+  bool GetCanModify(ErrorResult& aRv) const
+  {
+    return CanModify();
+  }
 
 
   void GetProxyInfo(dom::Nullable<dom::MozProxyInfo>& aRetVal, ErrorResult& aRv) const;
 
   void GetRemoteAddress(nsCString& aRetVal) const;
 
 
   void GetRequestHeaders(nsTArray<dom::MozHTTPHeader>& aRetVal, ErrorResult& aRv) const;
--- a/toolkit/modules/addons/WebRequest.jsm
+++ b/toolkit/modules/addons/WebRequest.jsm
@@ -759,17 +759,17 @@ HttpObserverManager = {
           data.requestHeaders = requestHeaders.toArray();
         }
 
         if (opts.responseHeaders) {
           responseHeaders = responseHeaders || new ResponseHeaderChanger(channel);
           data.responseHeaders = responseHeaders.toArray();
         }
 
-        if (opts.requestBody) {
+        if (opts.requestBody && channel.canModify) {
           requestBody = requestBody || WebRequestUpload.createRequestBody(channel.channel);
           data.requestBody = requestBody;
         }
 
         try {
           let result = callback(data);
 
           // isProxy is set during onAuth if the auth request is for a proxy.