Bug 663570 - MetaCSP Part 1: CSP parser changes (r=sicking)
authorChristoph Kerschbaumer <mozilla@christophkerschbaumer.com>
Sat, 14 Nov 2015 19:27:59 -0800
changeset 306706 632197cb15891288551963bd8330fd17774e8e48
parent 306705 cbdb6f72771d2afbed2134ee674a7f61bfa8371b
child 306707 7939f4d62b7876f5b44411c9ee60547d832b88a5
push id5513
push userraliiev@mozilla.com
push dateMon, 25 Jan 2016 13:55:34 +0000
treeherdermozilla-beta@5ee97dd05b5c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssicking
bugs663570
milestone45.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 663570 - MetaCSP Part 1: CSP parser changes (r=sicking)
dom/base/nsDocument.cpp
dom/interfaces/security/nsIContentSecurityPolicy.idl
dom/locales/en-US/chrome/security/csp.properties
dom/security/nsCSPContext.cpp
dom/security/nsCSPParser.cpp
dom/security/nsCSPParser.h
dom/security/test/TestCSPParser.cpp
dom/security/test/unit/test_csp_reports.js
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -2610,17 +2610,17 @@ AppendCSPFromHeader(nsIContentSecurityPo
 {
   // Need to tokenize the header value since multiple headers could be
   // concatenated into one comma-separated list of policies.
   // See RFC2616 section 4.2 (last paragraph)
   nsresult rv = NS_OK;
   nsCharSeparatedTokenizer tokenizer(aHeaderValue, ',');
   while (tokenizer.hasMoreTokens()) {
       const nsSubstring& policy = tokenizer.nextToken();
-      rv = csp->AppendPolicy(policy, aReportOnly);
+      rv = csp->AppendPolicy(policy, aReportOnly, false);
       NS_ENSURE_SUCCESS(rv, rv);
       {
         MOZ_LOG(gCspPRLog, LogLevel::Debug,
                 ("CSP refined with policy: \"%s\"",
                 NS_ConvertUTF16toUTF8(policy).get()));
       }
   }
   return NS_OK;
@@ -2763,32 +2763,32 @@ nsDocument::InitCSP(nsIChannel* aChannel
   nsCOMPtr<nsIURI> selfURI;
   aChannel->GetURI(getter_AddRefs(selfURI));
 
   // Store the request context for violation reports
   csp->SetRequestContext(this, nullptr);
 
   // ----- if the doc is an app and we want a default CSP, apply it.
   if (applyAppDefaultCSP) {
-    csp->AppendPolicy(appDefaultCSP, false);
+    csp->AppendPolicy(appDefaultCSP, false, false);
   }
 
   // ----- if the doc is an app and specifies a CSP in its manifest, apply it.
   if (applyAppManifestCSP) {
-    csp->AppendPolicy(appManifestCSP, false);
+    csp->AppendPolicy(appManifestCSP, false, false);
   }
 
   // ----- if the doc is part of Loop, apply the loop CSP
   if (applyLoopCSP) {
     nsAdoptingString loopCSP;
     loopCSP = Preferences::GetString("loop.CSP");
     NS_ASSERTION(loopCSP, "Missing loop.CSP preference");
     // If the pref has been removed, we continue without setting a CSP
     if (loopCSP) {
-      csp->AppendPolicy(loopCSP, false);
+      csp->AppendPolicy(loopCSP, false, false);
     }
   }
 
   // ----- if there's a full-strength CSP header, apply it.
   if (!cspHeaderValue.IsEmpty()) {
     rv = AppendCSPFromHeader(csp, cspHeaderValue, false);
     NS_ENSURE_SUCCESS(rv, rv);
   }
--- a/dom/interfaces/security/nsIContentSecurityPolicy.idl
+++ b/dom/interfaces/security/nsIContentSecurityPolicy.idl
@@ -16,17 +16,17 @@ interface nsIURI;
  * nsIContentSecurityPolicy
  * Describes an XPCOM component used to model and enforce CSPs.  Instances of
  * this class may have multiple policies within them, but there should only be
  * one of these per document/principal.
  */
 
 typedef unsigned short CSPDirective;
 
-[scriptable, builtinclass, uuid(b9a029c3-9484-4bf7-826d-0c6b545790bc)]
+[scriptable, builtinclass, uuid(b3c4c0ae-bd5e-4cad-87e0-8d210dbb3f9f)]
 interface nsIContentSecurityPolicy : nsISerializable
 {
   /**
    * Directives supported by Content Security Policy.  These are enums for
    * the CSPDirective type.
    * The NO_DIRECTIVE entry is  used for checking default permissions and
    * returning failure when asking CSP which directive to check.
    *
@@ -85,22 +85,27 @@ interface nsIContentSecurityPolicy : nsI
    * @return
    *        true if a referrer policy is specified, false if it's unspecified.
    */
   bool getReferrerPolicy(out unsigned long policy);
 
   /**
    * Parse and install a CSP policy.
    * @param aPolicy
-   *        String representation of the policy (e.g., header value)
+   *        String representation of the policy
+   *        (e.g., header value, meta content)
    * @param reportOnly
    *        Should this policy affect content, script and style processing or
    *        just send reports if it is violated?
+   * @param deliveredViaMetaTag
+   *        Indicates whether the policy was delivered via the meta tag.
    */
-  void appendPolicy(in AString policyString, in boolean reportOnly);
+  void appendPolicy(in AString policyString,
+                    in boolean reportOnly,
+                    in boolean deliveredViaMetaTag);
 
   /*
    * Whether this policy allows inline script or style.
    * @param aContentPolicyType Either TYPE_SCRIPT or TYPE_STYLESHEET
    * @param aNonce The nonce string to check against the policy
    * @param aContent  The content of the inline resource to hash
    *        (and compare to the hashes listed in the policy)
    * @param aLineNumber The line number of the inline resource
--- a/dom/locales/en-US/chrome/security/csp.properties
+++ b/dom/locales/en-US/chrome/security/csp.properties
@@ -27,16 +27,19 @@ couldNotParseReportURI = couldn't parse 
 # %1$S is the unknown directive
 couldNotProcessUnknownDirective = Couldn't process unknown directive '%1$S'
 # LOCALIZATION NOTE (ignoringUnknownOption):
 # %1$S is the option that could not be understood
 ignoringUnknownOption = Ignoring unknown option %1$S
 # LOCALIZATION NOTE (ignoringDuplicateSrc):
 # %1$S defines the duplicate src
 ignoringDuplicateSrc = Ignoring duplicate source %1$S
+# LOCALIZATION NOTE (ignoringSrcFromMetaCSP):
+# %1$S defines the ignored src
+ignoringSrcFromMetaCSP = Ignoring source '%1$S' (Not supported when delivered via meta element).
 # LOCALIZATION NOTE (ignoringSrcWithinScriptSrc):
 # %1$S is the ignored src
 # script-src is a directive name and should not be localized
 ignoringSrcWithinScriptSrc = Ignoring "%1$S" within script-src: nonce-source or hash-source specified
 # LOCALIZATION NOTE (reportURInotHttpsOrHttp2):
 # %1$S is the ETLD of the report URI that is not HTTP or HTTPS
 reportURInotHttpsOrHttp2 = The report URI (%1$S) should be an HTTP or HTTPS URI.
 # LOCALIZATION NOTE (reportURInotInReportOnlyHeader):
--- a/dom/security/nsCSPContext.cpp
+++ b/dom/security/nsCSPContext.cpp
@@ -337,24 +337,27 @@ nsCSPContext::GetReferrerPolicy(uint32_t
     }
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsCSPContext::AppendPolicy(const nsAString& aPolicyString,
-                           bool aReportOnly)
+                           bool aReportOnly,
+                           bool aDeliveredViaMetaTag)
 {
   CSPCONTEXTLOG(("nsCSPContext::AppendPolicy: %s",
                  NS_ConvertUTF16toUTF8(aPolicyString).get()));
 
   // Use the mSelfURI from setRequestContext, see bug 991474
   NS_ASSERTION(mSelfURI, "mSelfURI required for AppendPolicy, but not set");
-  nsCSPPolicy* policy = nsCSPParser::parseContentSecurityPolicy(aPolicyString, mSelfURI, aReportOnly, this);
+  nsCSPPolicy* policy = nsCSPParser::parseContentSecurityPolicy(aPolicyString, mSelfURI,
+                                                                aReportOnly, this,
+                                                                aDeliveredViaMetaTag);
   if (policy) {
     mPolicies.AppendElement(policy);
     // reset cache since effective policy changes
     mShouldLoadCache.Clear();
   }
   return NS_OK;
 }
 
@@ -1348,20 +1351,25 @@ nsCSPContext::Read(nsIObjectInputStream*
 
     rv = aStream->ReadString(policyString);
     NS_ENSURE_SUCCESS(rv, rv);
 
     bool reportOnly = false;
     rv = aStream->ReadBoolean(&reportOnly);
     NS_ENSURE_SUCCESS(rv, rv);
 
+    // @param deliveredViaMetaTag:
+    // when parsing the CSP policy string initially we already remove directives
+    // that should not be processed when delivered via the meta tag. Such directives
+    // will not be present at this point anymore.
     nsCSPPolicy* policy = nsCSPParser::parseContentSecurityPolicy(policyString,
                                                                   mSelfURI,
                                                                   reportOnly,
-                                                                  this);
+                                                                  this,
+                                                                  false);
     if (policy) {
       mPolicies.AppendElement(policy);
     }
   }
 
   return NS_OK;
 }
 
--- a/dom/security/nsCSPParser.cpp
+++ b/dom/security/nsCSPParser.cpp
@@ -117,24 +117,26 @@ nsCSPTokenizer::tokenizeCSPPolicy(const 
 
   tokenizer.generateTokens(outTokens);
 }
 
 /* ===== nsCSPParser ==================== */
 
 nsCSPParser::nsCSPParser(cspTokens& aTokens,
                          nsIURI* aSelfURI,
-                         nsCSPContext* aCSPContext)
+                         nsCSPContext* aCSPContext,
+                         bool aDeliveredViaMetaTag)
  : mHasHashOrNonce(false)
  , mUnsafeInlineKeywordSrc(nullptr)
  , mChildSrc(nullptr)
  , mFrameSrc(nullptr)
  , mTokens(aTokens)
  , mSelfURI(aSelfURI)
  , mCSPContext(aCSPContext)
+ , mDeliveredViaMetaTag(aDeliveredViaMetaTag)
 {
   CSPPARSERLOG(("nsCSPParser::nsCSPParser"));
 }
 
 nsCSPParser::~nsCSPParser()
 {
   CSPPARSERLOG(("nsCSPParser::~nsCSPParser"));
 }
@@ -986,16 +988,30 @@ nsCSPParser::directiveName()
   // (see http://www.w3.org/TR/CSP11/#parsing)
   if (mPolicy->hasDirective(CSP_StringToCSPDirective(mCurToken))) {
     const char16_t* params[] = { mCurToken.get() };
     logWarningErrorToConsole(nsIScriptError::warningFlag, "duplicateDirective",
                              params, ArrayLength(params));
     return nullptr;
   }
 
+  // CSP delivered via meta tag should ignore the following directives:
+  // report-uri, frame-ancestors, and sandbox, see:
+  // http://www.w3.org/TR/CSP11/#delivery-html-meta-element
+  if (mDeliveredViaMetaTag &&
+       ((CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE)) ||
+        (CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::FRAME_ANCESTORS_DIRECTIVE)))) {
+    // log to the console to indicate that meta CSP is ignoring the directive
+    const char16_t* params[] = { mCurToken.get() };
+    logWarningErrorToConsole(nsIScriptError::warningFlag,
+                             "ignoringSrcFromMetaCSP",
+                             params, ArrayLength(params));
+    return nullptr;
+  }
+
   // special case handling for upgrade-insecure-requests
   if (CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE)) {
     return new nsUpgradeInsecureDirective(CSP_StringToCSPDirective(mCurToken));
   }
 
   // child-src has it's own class to handle frame-src if necessary
   if (CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::CHILD_SRC_DIRECTIVE)) {
     mChildSrc = new nsCSPChildSrcDirective(CSP_StringToCSPDirective(mCurToken));
@@ -1111,39 +1127,42 @@ nsCSPParser::policy()
 
   return mPolicy;
 }
 
 nsCSPPolicy*
 nsCSPParser::parseContentSecurityPolicy(const nsAString& aPolicyString,
                                         nsIURI *aSelfURI,
                                         bool aReportOnly,
-                                        nsCSPContext* aCSPContext)
+                                        nsCSPContext* aCSPContext,
+                                        bool aDeliveredViaMetaTag)
 {
   if (CSPPARSERLOGENABLED()) {
     CSPPARSERLOG(("nsCSPParser::parseContentSecurityPolicy, policy: %s",
                  NS_ConvertUTF16toUTF8(aPolicyString).get()));
     nsAutoCString spec;
     aSelfURI->GetSpec(spec);
     CSPPARSERLOG(("nsCSPParser::parseContentSecurityPolicy, selfURI: %s", spec.get()));
     CSPPARSERLOG(("nsCSPParser::parseContentSecurityPolicy, reportOnly: %s",
                  (aReportOnly ? "true" : "false")));
+    CSPPARSERLOG(("nsCSPParser::parseContentSecurityPolicy, deliveredViaMetaTag: %s",
+                 (aDeliveredViaMetaTag ? "true" : "false")));
   }
 
   NS_ASSERTION(aSelfURI, "Can not parseContentSecurityPolicy without aSelfURI");
 
   // Separate all input into tokens and store them in the form of:
   // [ [ name, src, src, ... ], [ name, src, src, ... ], ... ]
   // The tokenizer itself can not fail; all eventual errors
   // are detected in the parser itself.
 
   nsTArray< nsTArray<nsString> > tokens;
   nsCSPTokenizer::tokenizeCSPPolicy(aPolicyString, tokens);
 
-  nsCSPParser parser(tokens, aSelfURI, aCSPContext);
+  nsCSPParser parser(tokens, aSelfURI, aCSPContext, aDeliveredViaMetaTag);
 
   // Start the parser to generate a new CSPPolicy using the generated tokens.
   nsCSPPolicy* policy = parser.policy();
 
   // Check that report-only policies define a report-uri, otherwise log warning.
   if (aReportOnly) {
     policy->setReportOnlyFlag(true);
     if (!policy->hasDirective(nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE)) {
--- a/dom/security/nsCSPParser.h
+++ b/dom/security/nsCSPParser.h
@@ -95,22 +95,25 @@ class nsCSPParser {
      * Internally the input string is separated into string tokens and policy() is called, which starts
      * parsing the policy. The parser calls one function after the other according the the source-list
      * from http://www.w3.org/TR/CSP11/#source-list. E.g., the parser can only call port() after the parser
      * has already processed any possible host in host(), similar to a finite state machine.
      */
     static nsCSPPolicy* parseContentSecurityPolicy(const nsAString &aPolicyString,
                                                    nsIURI *aSelfURI,
                                                    bool aReportOnly,
-                                                   nsCSPContext* aCSPContext);
+                                                   nsCSPContext* aCSPContext,
+                                                   bool aDeliveredViaMetaTag);
 
   private:
     nsCSPParser(cspTokens& aTokens,
                 nsIURI* aSelfURI,
-                nsCSPContext* aCSPContext);
+                nsCSPContext* aCSPContext,
+                bool aDeliveredViaMetaTag);
+
     ~nsCSPParser();
 
 
     // Parsing the CSP using the source-list from http://www.w3.org/TR/CSP11/#source-list
     nsCSPPolicy*    policy();
     void            directive();
     nsCSPDirective* directiveName();
     void            directiveValue(nsTArray<nsCSPBaseSrc*>& outSrcs);
@@ -241,11 +244,12 @@ class nsCSPParser {
     // should honor instead.
     nsCSPChildSrcDirective* mChildSrc;
     nsCSPDirective*         mFrameSrc;
 
     cspTokens          mTokens;
     nsIURI*            mSelfURI;
     nsCSPPolicy*       mPolicy;
     nsCSPContext*      mCSPContext; // used for console logging
+    bool               mDeliveredViaMetaTag;
 };
 
 #endif /* nsCSPParser_h___ */
--- a/dom/security/test/TestCSPParser.cpp
+++ b/dom/security/test/TestCSPParser.cpp
@@ -112,17 +112,17 @@ nsresult runTest(uint32_t aExpectedPolic
   // for testing the parser we only need to set a principal which is needed
   // to translate the keyword 'self' into an actual URI.
   rv = csp->SetRequestContext(nullptr, selfURIPrincipal);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // append a policy
   nsString policyStr;
   policyStr.AssignASCII(aPolicy);
-  rv = csp->AppendPolicy(policyStr, false);
+  rv = csp->AppendPolicy(policyStr, false, false);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // when executing fuzzy tests we do not care about the actual output
   // of the parser, we just want to make sure that the parser is not crashing.
   if (aExpectedPolicyCount == kFuzzyExpectedPolicyCount) {
     return NS_OK;
   }
 
--- a/dom/security/test/unit/test_csp_reports.js
+++ b/dom/security/test/unit/test_csp_reports.js
@@ -79,17 +79,17 @@ function makeTest(id, expectedJSON, useR
 
   let ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
               .getService(Ci.nsIScriptSecurityManager);
   principal = ssm.getSimpleCodebasePrincipal(selfuri);
   csp.setRequestContext(null, principal);
 
   // Load up the policy
   // set as report-only if that's the case
-  csp.appendPolicy(policy, useReportOnlyPolicy);
+  csp.appendPolicy(policy, useReportOnlyPolicy, false);
 
   // prime the report server
   var handler = makeReportHandler("/test" + id, "Test " + id, expectedJSON);
   httpServer.registerPathHandler("/test" + id, handler);
 
   //trigger the violation
   callback(csp);
 }