LCOV - code coverage report
Current view: top level - toolkit/mozapps/extensions - AddonContentPolicy.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 11 179 6.1 %
Date: 2017-07-14 16:53:18 Functions: 5 29 17.2 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
       2             : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
       3             : /* This Source Code Form is subject to the terms of the Mozilla Public
       4             :  * License, v. 2.0. If a copy of the MPL was not distributed with this
       5             :  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
       6             : 
       7             : #include "AddonContentPolicy.h"
       8             : 
       9             : #include "mozilla/dom/nsCSPUtils.h"
      10             : #include "nsCOMPtr.h"
      11             : #include "nsContentPolicyUtils.h"
      12             : #include "nsContentTypeParser.h"
      13             : #include "nsContentUtils.h"
      14             : #include "nsIConsoleService.h"
      15             : #include "nsIContentSecurityPolicy.h"
      16             : #include "nsIContent.h"
      17             : #include "nsIDocument.h"
      18             : #include "nsIEffectiveTLDService.h"
      19             : #include "nsIScriptError.h"
      20             : #include "nsIStringBundle.h"
      21             : #include "nsIUUIDGenerator.h"
      22             : #include "nsIURI.h"
      23             : #include "nsNetCID.h"
      24             : #include "nsNetUtil.h"
      25             : 
      26             : using namespace mozilla;
      27             : 
      28             : /* Enforces content policies for WebExtension scopes. Currently:
      29             :  *
      30             :  *  - Prevents loading scripts with a non-default JavaScript version.
      31             :  *  - Checks custom content security policies for sufficiently stringent
      32             :  *    script-src and object-src directives.
      33             :  */
      34             : 
      35             : #define VERSIONED_JS_BLOCKED_MESSAGE \
      36             :   u"Versioned JavaScript is a non-standard, deprecated extension, and is " \
      37             :   u"not supported in WebExtension code. For alternatives, please see: " \
      38             :   u"https://developer.mozilla.org/Add-ons/WebExtensions/Tips"
      39             : 
      40           2 : AddonContentPolicy::AddonContentPolicy()
      41             : {
      42           2 : }
      43             : 
      44           0 : AddonContentPolicy::~AddonContentPolicy()
      45             : {
      46           0 : }
      47             : 
      48          24 : NS_IMPL_ISUPPORTS(AddonContentPolicy, nsIContentPolicy, nsIAddonContentPolicy)
      49             : 
      50             : static nsresult
      51           0 : GetWindowIDFromContext(nsISupports* aContext, uint64_t *aResult)
      52             : {
      53           0 :   NS_ENSURE_TRUE(aContext, NS_ERROR_FAILURE);
      54             : 
      55           0 :   nsCOMPtr<nsIContent> content = do_QueryInterface(aContext);
      56           0 :   NS_ENSURE_TRUE(content, NS_ERROR_FAILURE);
      57             : 
      58           0 :   nsCOMPtr<nsIDocument> document = content->OwnerDoc();
      59           0 :   NS_ENSURE_TRUE(document, NS_ERROR_FAILURE);
      60             : 
      61           0 :   nsCOMPtr<nsPIDOMWindowInner> window = document->GetInnerWindow();
      62           0 :   NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
      63             : 
      64           0 :   *aResult = window->WindowID();
      65           0 :   return NS_OK;
      66             : }
      67             : 
      68             : static nsresult
      69           0 : LogMessage(const nsAString &aMessage, nsIURI* aSourceURI, const nsAString &aSourceSample,
      70             :            nsISupports* aContext)
      71             : {
      72           0 :   nsCOMPtr<nsIScriptError> error = do_CreateInstance(NS_SCRIPTERROR_CONTRACTID);
      73           0 :   NS_ENSURE_TRUE(error, NS_ERROR_OUT_OF_MEMORY);
      74             : 
      75           0 :   nsCString sourceName = aSourceURI->GetSpecOrDefault();
      76             : 
      77           0 :   uint64_t windowID = 0;
      78           0 :   GetWindowIDFromContext(aContext, &windowID);
      79             : 
      80             :   nsresult rv =
      81           0 :     error->InitWithWindowID(aMessage, NS_ConvertUTF8toUTF16(sourceName),
      82             :                             aSourceSample, 0, 0, nsIScriptError::errorFlag,
      83           0 :                             "JavaScript", windowID);
      84           0 :   NS_ENSURE_SUCCESS(rv, rv);
      85             : 
      86           0 :   nsCOMPtr<nsIConsoleService> console = do_GetService(NS_CONSOLESERVICE_CONTRACTID);
      87           0 :   NS_ENSURE_TRUE(console, NS_ERROR_OUT_OF_MEMORY);
      88             : 
      89           0 :   console->LogMessage(error);
      90           0 :   return NS_OK;
      91             : }
      92             : 
      93             : 
      94             : // Content policy enforcement:
      95             : 
      96             : NS_IMETHODIMP
      97          23 : AddonContentPolicy::ShouldLoad(uint32_t aContentType,
      98             :                                nsIURI* aContentLocation,
      99             :                                nsIURI* aRequestOrigin,
     100             :                                nsISupports* aContext,
     101             :                                const nsACString& aMimeTypeGuess,
     102             :                                nsISupports* aExtra,
     103             :                                nsIPrincipal* aRequestPrincipal,
     104             :                                int16_t* aShouldLoad)
     105             : {
     106          23 :   MOZ_ASSERT(aContentType == nsContentUtils::InternalContentPolicyTypeToExternal(aContentType),
     107             :              "We should only see external content policy types here.");
     108             : 
     109          23 :   *aShouldLoad = nsIContentPolicy::ACCEPT;
     110             : 
     111          23 :   if (!aRequestOrigin) {
     112           9 :     return NS_OK;
     113             :   }
     114             : 
     115             :   // Only apply this policy to requests from documents loaded from
     116             :   // moz-extension URLs, or to resources being loaded from moz-extension URLs.
     117             :   bool equals;
     118          42 :   if (!((NS_SUCCEEDED(aContentLocation->SchemeIs("moz-extension", &equals)) && equals) ||
     119          28 :         (NS_SUCCEEDED(aRequestOrigin->SchemeIs("moz-extension", &equals)) && equals))) {
     120          14 :     return NS_OK;
     121             :   }
     122             : 
     123           0 :   if (aContentType == nsIContentPolicy::TYPE_SCRIPT) {
     124           0 :     NS_ConvertUTF8toUTF16 typeString(aMimeTypeGuess);
     125           0 :     nsContentTypeParser mimeParser(typeString);
     126             : 
     127             :     // Reject attempts to load JavaScript scripts with a non-default version.
     128           0 :     nsAutoString mimeType, version;
     129           0 :     if (NS_SUCCEEDED(mimeParser.GetType(mimeType)) &&
     130           0 :         nsContentUtils::IsJavascriptMIMEType(mimeType) &&
     131           0 :         NS_SUCCEEDED(mimeParser.GetParameter("version", version))) {
     132           0 :       *aShouldLoad = nsIContentPolicy::REJECT_REQUEST;
     133             : 
     134           0 :       LogMessage(NS_LITERAL_STRING(VERSIONED_JS_BLOCKED_MESSAGE),
     135           0 :                  aRequestOrigin, typeString, aContext);
     136           0 :       return NS_OK;
     137             :     }
     138             :   }
     139             : 
     140           0 :   return NS_OK;
     141             : }
     142             : 
     143             : NS_IMETHODIMP
     144           0 : AddonContentPolicy::ShouldProcess(uint32_t aContentType,
     145             :                                   nsIURI* aContentLocation,
     146             :                                   nsIURI* aRequestOrigin,
     147             :                                   nsISupports* aRequestingContext,
     148             :                                   const nsACString& aMimeTypeGuess,
     149             :                                   nsISupports* aExtra,
     150             :                                   nsIPrincipal* aRequestPrincipal,
     151             :                                   int16_t* aShouldProcess)
     152             : {
     153           0 :   MOZ_ASSERT(aContentType == nsContentUtils::InternalContentPolicyTypeToExternal(aContentType),
     154             :              "We should only see external content policy types here.");
     155             : 
     156           0 :   *aShouldProcess = nsIContentPolicy::ACCEPT;
     157           0 :   return NS_OK;
     158             : }
     159             : 
     160             : 
     161             : // CSP Validation:
     162             : 
     163             : static const char* allowedSchemes[] = {
     164             :   "blob",
     165             :   "filesystem",
     166             :   nullptr
     167             : };
     168             : 
     169             : static const char* allowedHostSchemes[] = {
     170             :   "https",
     171             :   "moz-extension",
     172             :   nullptr
     173             : };
     174             : 
     175             : /**
     176             :  * Validates a CSP directive to ensure that it is sufficiently stringent.
     177             :  * In particular, ensures that:
     178             :  *
     179             :  *  - No remote sources are allowed other than from https: schemes
     180             :  *
     181             :  *  - No remote sources specify host wildcards for generic domains
     182             :  *    (*.blogspot.com, *.com, *)
     183             :  *
     184             :  *  - All remote sources and local extension sources specify a host
     185             :  *
     186             :  *  - No scheme sources are allowed other than blob:, filesystem:,
     187             :  *    moz-extension:, and https:
     188             :  *
     189             :  *  - No keyword sources are allowed other than 'none', 'self', 'unsafe-eval',
     190             :  *    and hash sources.
     191             :  */
     192           0 : class CSPValidator final : public nsCSPSrcVisitor {
     193             :   public:
     194           0 :     CSPValidator(nsAString& aURL, CSPDirective aDirective, bool aDirectiveRequired = true) :
     195             :       mURL(aURL),
     196             :       mDirective(CSP_CSPDirectiveToString(aDirective)),
     197           0 :       mFoundSelf(false)
     198             :     {
     199             :       // Start with the default error message for a missing directive, since no
     200             :       // visitors will be called if the directive isn't present.
     201           0 :       if (aDirectiveRequired) {
     202           0 :         FormatError("csp.error.missing-directive");
     203             :       }
     204           0 :     }
     205             : 
     206             :     // Visitors
     207             : 
     208           0 :     bool visitSchemeSrc(const nsCSPSchemeSrc& src) override
     209             :     {
     210           0 :       nsAutoString scheme;
     211           0 :       src.getScheme(scheme);
     212             : 
     213           0 :       if (SchemeInList(scheme, allowedHostSchemes)) {
     214           0 :         FormatError("csp.error.missing-host", scheme);
     215           0 :         return false;
     216             :       }
     217           0 :       if (!SchemeInList(scheme, allowedSchemes)) {
     218           0 :         FormatError("csp.error.illegal-protocol", scheme);
     219           0 :         return false;
     220             :       }
     221           0 :       return true;
     222             :     };
     223             : 
     224           0 :     bool visitHostSrc(const nsCSPHostSrc& src) override
     225             :     {
     226           0 :       nsAutoString scheme, host;
     227             : 
     228           0 :       src.getScheme(scheme);
     229           0 :       src.getHost(host);
     230             : 
     231           0 :       if (scheme.LowerCaseEqualsLiteral("https")) {
     232           0 :         if (!HostIsAllowed(host)) {
     233           0 :           FormatError("csp.error.illegal-host-wildcard", scheme);
     234           0 :           return false;
     235             :         }
     236           0 :       } else if (scheme.LowerCaseEqualsLiteral("moz-extension")) {
     237             :         // The CSP parser silently converts 'self' keywords to the origin
     238             :         // URL, so we need to reconstruct the URL to see if it was present.
     239           0 :         if (!mFoundSelf) {
     240           0 :           nsAutoString url(u"moz-extension://");
     241           0 :           url.Append(host);
     242             : 
     243           0 :           mFoundSelf = url.Equals(mURL);
     244             :         }
     245             : 
     246           0 :         if (host.IsEmpty() || host.EqualsLiteral("*")) {
     247           0 :           FormatError("csp.error.missing-host", scheme);
     248           0 :           return false;
     249             :         }
     250           0 :       } else if (!SchemeInList(scheme, allowedSchemes)) {
     251           0 :         FormatError("csp.error.illegal-protocol", scheme);
     252           0 :         return false;
     253             :       }
     254             : 
     255           0 :       return true;
     256             :     };
     257             : 
     258           0 :     bool visitKeywordSrc(const nsCSPKeywordSrc& src) override
     259             :     {
     260           0 :       switch (src.getKeyword()) {
     261             :       case CSP_NONE:
     262             :       case CSP_SELF:
     263             :       case CSP_UNSAFE_EVAL:
     264           0 :         return true;
     265             : 
     266             :       default:
     267           0 :         NS_ConvertASCIItoUTF16 keyword(CSP_EnumToKeyword(src.getKeyword()));
     268             : 
     269           0 :         FormatError("csp.error.illegal-keyword", keyword);
     270           0 :         return false;
     271             :       }
     272             :     };
     273             : 
     274           0 :     bool visitNonceSrc(const nsCSPNonceSrc& src) override
     275             :     {
     276           0 :       FormatError("csp.error.illegal-keyword", NS_LITERAL_STRING("'nonce-*'"));
     277           0 :       return false;
     278             :     };
     279             : 
     280           0 :     bool visitHashSrc(const nsCSPHashSrc& src) override
     281             :     {
     282           0 :       return true;
     283             :     };
     284             : 
     285             :     // Accessors
     286             : 
     287           0 :     inline nsAString& GetError()
     288             :     {
     289           0 :       return mError;
     290             :     };
     291             : 
     292           0 :     inline bool FoundSelf()
     293             :     {
     294           0 :       return mFoundSelf;
     295             :     };
     296             : 
     297             : 
     298             :     // Formatters
     299             : 
     300             :     template <typename... T>
     301           0 :     inline void FormatError(const char* aName, const T ...aParams)
     302             :     {
     303           0 :       const char16_t* params[] = { mDirective.get(), aParams.get()... };
     304           0 :       FormatErrorParams(aName, params, MOZ_ARRAY_LENGTH(params));
     305           0 :     };
     306             : 
     307             :   private:
     308             :     // Validators
     309             : 
     310           0 :     bool HostIsAllowed(nsAString& host)
     311             :     {
     312           0 :       if (host.First() == '*') {
     313           0 :         if (host.EqualsLiteral("*") || host[1] != '.') {
     314           0 :           return false;
     315             :         }
     316             : 
     317           0 :         host.Cut(0, 2);
     318             : 
     319             :         nsCOMPtr<nsIEffectiveTLDService> tldService =
     320           0 :           do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
     321             : 
     322           0 :         if (!tldService) {
     323           0 :           return false;
     324             :         }
     325             : 
     326           0 :         NS_ConvertUTF16toUTF8 cHost(host);
     327           0 :         nsAutoCString publicSuffix;
     328             : 
     329           0 :         nsresult rv = tldService->GetPublicSuffixFromHost(cHost, publicSuffix);
     330             : 
     331           0 :         return NS_SUCCEEDED(rv) && !cHost.Equals(publicSuffix);
     332             :       }
     333             : 
     334           0 :       return true;
     335             :     };
     336             : 
     337           0 :     bool SchemeInList(nsAString& scheme, const char** schemes)
     338             :     {
     339           0 :       for (; *schemes; schemes++) {
     340           0 :         if (scheme.LowerCaseEqualsASCII(*schemes)) {
     341           0 :           return true;
     342             :         }
     343             :       }
     344           0 :       return false;
     345             :     };
     346             : 
     347             : 
     348             :     // Formatters
     349             : 
     350             :     already_AddRefed<nsIStringBundle>
     351           0 :     GetStringBundle()
     352             :     {
     353             :       nsCOMPtr<nsIStringBundleService> sbs =
     354           0 :         mozilla::services::GetStringBundleService();
     355           0 :       NS_ENSURE_TRUE(sbs, nullptr);
     356             : 
     357           0 :       nsCOMPtr<nsIStringBundle> stringBundle;
     358           0 :       sbs->CreateBundle("chrome://global/locale/extensions.properties",
     359           0 :                         getter_AddRefs(stringBundle));
     360             : 
     361           0 :       return stringBundle.forget();
     362             :     };
     363             : 
     364           0 :     void FormatErrorParams(const char* aName, const char16_t** aParams, int32_t aLength)
     365             :     {
     366           0 :       nsresult rv = NS_ERROR_FAILURE;
     367             : 
     368           0 :       nsCOMPtr<nsIStringBundle> stringBundle = GetStringBundle();
     369             : 
     370           0 :       if (stringBundle) {
     371           0 :         NS_ConvertASCIItoUTF16 name(aName);
     372             : 
     373           0 :         rv = stringBundle->FormatStringFromName(name.get(), aParams, aLength,
     374           0 :                                                 getter_Copies(mError));
     375             :       }
     376             : 
     377           0 :       if (NS_WARN_IF(NS_FAILED(rv))) {
     378           0 :         mError.AssignLiteral("An unexpected error occurred");
     379             :       }
     380           0 :     };
     381             : 
     382             : 
     383             :     // Data members
     384             : 
     385             :     nsAutoString mURL;
     386             :     NS_ConvertASCIItoUTF16 mDirective;
     387             :     nsXPIDLString mError;
     388             : 
     389             :     bool mFoundSelf;
     390             : };
     391             : 
     392             : /**
     393             :  * Validates a custom content security policy string for use by an add-on.
     394             :  * In particular, ensures that:
     395             :  *
     396             :  *  - Both object-src and script-src directives are present, and meet
     397             :  *    the policies required by the CSPValidator class
     398             :  *
     399             :  *  - The script-src directive includes the source 'self'
     400             :  */
     401             : NS_IMETHODIMP
     402           0 : AddonContentPolicy::ValidateAddonCSP(const nsAString& aPolicyString,
     403             :                                      nsAString& aResult)
     404             : {
     405             :   nsresult rv;
     406             : 
     407             :   // Validate against a randomly-generated extension origin.
     408             :   // There is no add-on-specific behavior in the CSP code, beyond the ability
     409             :   // for add-ons to specify a custom policy, but the parser requires a valid
     410             :   // origin in order to operate correctly.
     411           0 :   nsAutoString url(u"moz-extension://");
     412             :   {
     413           0 :     nsCOMPtr<nsIUUIDGenerator> uuidgen = services::GetUUIDGenerator();
     414           0 :     NS_ENSURE_TRUE(uuidgen, NS_ERROR_FAILURE);
     415             : 
     416             :     nsID id;
     417           0 :     rv = uuidgen->GenerateUUIDInPlace(&id);
     418           0 :     NS_ENSURE_SUCCESS(rv, rv);
     419             : 
     420             :     char idString[NSID_LENGTH];
     421           0 :     id.ToProvidedString(idString);
     422             : 
     423           0 :     MOZ_RELEASE_ASSERT(idString[0] == '{' && idString[NSID_LENGTH - 2] == '}',
     424             :                        "UUID generator did not return a valid UUID");
     425             : 
     426           0 :     url.AppendASCII(idString + 1, NSID_LENGTH - 3);
     427             :   }
     428             : 
     429             : 
     430             :   RefPtr<BasePrincipal> principal =
     431           0 :     BasePrincipal::CreateCodebasePrincipal(NS_ConvertUTF16toUTF8(url));
     432             : 
     433           0 :   nsCOMPtr<nsIContentSecurityPolicy> csp;
     434           0 :   rv = principal->EnsureCSP(nullptr, getter_AddRefs(csp));
     435           0 :   NS_ENSURE_SUCCESS(rv, rv);
     436             : 
     437             : 
     438           0 :   csp->AppendPolicy(aPolicyString, false, false);
     439             : 
     440           0 :   const nsCSPPolicy* policy = csp->GetPolicy(0);
     441           0 :   if (!policy) {
     442           0 :     CSPValidator validator(url, nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE);
     443           0 :     aResult.Assign(validator.GetError());
     444           0 :     return NS_OK;
     445             :   }
     446             : 
     447           0 :   bool haveValidDefaultSrc = false;
     448             :   {
     449           0 :     CSPDirective directive = nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE;
     450           0 :     CSPValidator validator(url, directive);
     451             : 
     452           0 :     haveValidDefaultSrc = policy->visitDirectiveSrcs(directive, &validator);
     453             :   }
     454             : 
     455           0 :   aResult.SetIsVoid(true);
     456             :   {
     457           0 :     CSPDirective directive = nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE;
     458           0 :     CSPValidator validator(url, directive, !haveValidDefaultSrc);
     459             : 
     460           0 :     if (!policy->visitDirectiveSrcs(directive, &validator)) {
     461           0 :       aResult.Assign(validator.GetError());
     462           0 :     } else if (!validator.FoundSelf()) {
     463           0 :       validator.FormatError("csp.error.missing-source", NS_LITERAL_STRING("'self'"));
     464           0 :       aResult.Assign(validator.GetError());
     465             :     }
     466             :   }
     467             : 
     468           0 :   if (aResult.IsVoid()) {
     469           0 :     CSPDirective directive = nsIContentSecurityPolicy::OBJECT_SRC_DIRECTIVE;
     470           0 :     CSPValidator validator(url, directive, !haveValidDefaultSrc);
     471             : 
     472           0 :     if (!policy->visitDirectiveSrcs(directive, &validator)) {
     473           0 :       aResult.Assign(validator.GetError());
     474             :     }
     475             :   }
     476             : 
     477           0 :   return NS_OK;
     478             : }

Generated by: LCOV version 1.13