Bug 808292 - CSP: Implement path-level host-source matching, utils modifications (r=grobinson,sstamm)
authorChristoph Kerschbaumer <mozilla@christophkerschbaumer.com>
Tue, 12 Aug 2014 12:55:08 -0700
changeset 230482 c40236b38f0334f595dad0205fe62b0a38be91c8
parent 230481 0b54f2bba06cb85b1cf57d8f3a5ad67ab5ff807e
child 230483 31d8c9d1a0c394b18a09152203d91cebe88e9c40
push id4187
push userbhearsum@mozilla.com
push dateFri, 28 Nov 2014 15:29:12 +0000
treeherdermozilla-beta@f23cc6a30c11 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgrobinson, sstamm
bugs808292
milestone35.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 808292 - CSP: Implement path-level host-source matching, utils modifications (r=grobinson,sstamm)
content/base/src/nsCSPContext.cpp
content/base/src/nsCSPUtils.cpp
content/base/src/nsCSPUtils.h
--- a/content/base/src/nsCSPContext.cpp
+++ b/content/base/src/nsCSPContext.cpp
@@ -168,16 +168,19 @@ nsCSPContext::ShouldLoad(nsContentPolicy
     }
   }
 
   nsAutoString violatedDirective;
   for (uint32_t p = 0; p < mPolicies.Length(); p++) {
     if (!mPolicies[p]->permits(aContentType,
                                aContentLocation,
                                nonce,
+                               // aExtra is only non-null if
+                               // the channel got redirected.
+                               (aExtra != nullptr),
                                violatedDirective)) {
       // If the policy is violated and not report-only, reject the load and
       // report to the console
       if (!mPolicies[p]->getReportOnlyFlag()) {
         CSPCONTEXTLOG(("nsCSPContext::ShouldLoad, nsIContentPolicy::REJECT_SERVER"));
         *outDecision = nsIContentPolicy::REJECT_SERVER;
       }
 
@@ -787,17 +790,18 @@ class CSPReportSenderRunnable MOZ_FINAL 
                             const nsAString& aViolatedDirective,
                             const nsAString& aObserverSubject,
                             const nsAString& aSourceFile,
                             const nsAString& aScriptSample,
                             uint32_t aLineNum,
                             uint64_t aInnerWindowID,
                             nsCSPContext* aCSPContext)
       : mBlockedContentSource(aBlockedContentSource)
-      , mOriginalURI(aOriginalURI) , mViolatedPolicyIndex(aViolatedPolicyIndex)
+      , mOriginalURI(aOriginalURI)
+      , mViolatedPolicyIndex(aViolatedPolicyIndex)
       , mReportOnlyFlag(aReportOnlyFlag)
       , mViolatedDirective(aViolatedDirective)
       , mSourceFile(aSourceFile)
       , mScriptSample(aScriptSample)
       , mLineNum(aLineNum)
       , mInnerWindowID(aInnerWindowID)
       , mCSPContext(aCSPContext)
     {
@@ -1019,16 +1023,17 @@ nsCSPContext::PermitsAncestry(nsIDocShel
       nsAutoCString spec;
       ancestorsArray[a]->GetSpec(spec);
       CSPCONTEXTLOG(("nsCSPContext::PermitsAncestry, checking ancestor: %s", spec.get()));
       }
 #endif
       if (!mPolicies[i]->permits(nsIContentPolicy::TYPE_DOCUMENT,
                                  ancestorsArray[a],
                                  EmptyString(), // no nonce
+                                 false, // no redirect
                                  violatedDirective)) {
         // Policy is violated
         // Send reports, but omit the ancestor URI if cross-origin as per spec
         // (it is a violation of the same-origin policy).
         bool okToSendAncestor = NS_SecurityCompareURIs(ancestorsArray[a], mSelfURI, true);
 
         this->AsyncReportViolation((okToSendAncestor ? ancestorsArray[a] : nullptr),
                                    mSelfURI,
--- a/content/base/src/nsCSPUtils.cpp
+++ b/content/base/src/nsCSPUtils.cpp
@@ -209,17 +209,17 @@ nsCSPBaseSrc::nsCSPBaseSrc()
 nsCSPBaseSrc::~nsCSPBaseSrc()
 {
 }
 
 // ::permits is only called for external load requests, therefore:
 // nsCSPKeywordSrc and nsCSPHashSource fall back to this base class
 // implementation which will never allow the load.
 bool
-nsCSPBaseSrc::permits(nsIURI* aUri, const nsAString& aNonce) const
+nsCSPBaseSrc::permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected) const
 {
 #ifdef PR_LOGGING
   {
     nsAutoCString spec;
     aUri->GetSpec(spec);
     CSPUTILSLOG(("nsCSPBaseSrc::permits, aUri: %s", spec.get()));
   }
 #endif
@@ -246,17 +246,17 @@ nsCSPSchemeSrc::nsCSPSchemeSrc(const nsA
   ToLowerCase(mScheme);
 }
 
 nsCSPSchemeSrc::~nsCSPSchemeSrc()
 {
 }
 
 bool
-nsCSPSchemeSrc::permits(nsIURI* aUri, const nsAString& aNonce) const
+nsCSPSchemeSrc::permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected) const
 {
 #ifdef PR_LOGGING
   {
     nsAutoCString spec;
     aUri->GetSpec(spec);
     CSPUTILSLOG(("nsCSPSchemeSrc::permits, aUri: %s", spec.get()));
   }
 #endif
@@ -283,17 +283,17 @@ nsCSPHostSrc::nsCSPHostSrc(const nsAStri
   ToLowerCase(mHost);
 }
 
 nsCSPHostSrc::~nsCSPHostSrc()
 {
 }
 
 bool
-nsCSPHostSrc::permits(nsIURI* aUri, const nsAString& aNonce) const
+nsCSPHostSrc::permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected) const
 {
 #ifdef PR_LOGGING
   {
     nsAutoCString spec;
     aUri->GetSpec(spec);
     CSPUTILSLOG(("nsCSPHostSrc::permits, aUri: %s", spec.get()));
   }
 #endif
@@ -337,16 +337,44 @@ nsCSPHostSrc::permits(nsIURI* aUri, cons
       return false;
     }
   }
   // Check if hosts match.
   else if (!mHost.Equals(NS_ConvertUTF8toUTF16(uriHost))) {
     return false;
   }
 
+  // If there is a path, we have to enforce path-level matching,
+  // unless the channel got redirected, see:
+  // http://www.w3.org/TR/CSP11/#source-list-paths-and-redirects
+  if (!aWasRedirected && !mPath.IsEmpty()) {
+    // cloning uri so we can ignore the ref
+    nsCOMPtr<nsIURI> uri;
+    aUri->CloneIgnoringRef(getter_AddRefs(uri));
+
+    nsAutoCString uriPath;
+    rv = uri->GetPath(uriPath);
+    NS_ENSURE_SUCCESS(rv, false);
+    // check if the last character of mPath is '/'; if so
+    // we just have to check loading resource is within
+    // the allowed path.
+    if (mPath.Last() == '/') {
+      if (!StringBeginsWith(NS_ConvertUTF8toUTF16(uriPath), mPath)) {
+        return false;
+      }
+    }
+    // otherwise mPath whitelists a specific file, and we have to
+    // check if the loading resource matches that whitelisted file.
+    else {
+      if (!mPath.Equals(NS_ConvertUTF8toUTF16(uriPath))) {
+        return false;
+      }
+    }
+  }
+
   // If port uses wildcard, allow the load.
   if (mPort.EqualsASCII("*")) {
     return true;
   }
 
   // Check if ports match
   int32_t uriPort;
   rv = aUri->GetPort(&uriPort);
@@ -364,17 +392,17 @@ nsCSPHostSrc::permits(nsIURI* aUri, cons
   else {
     nsString portStr;
     portStr.AppendInt(uriPort);
     if (!mPort.Equals(portStr)) {
       return false;
     }
   }
 
-  // At the end: scheme, host, port, match; allow the load.
+  // At the end: scheme, host, path, and port match -> allow the load.
   return true;
 }
 
 void
 nsCSPHostSrc::toString(nsAString& outStr) const
 {
   // If mHost is a single "*", we append the wildcard and return.
   if (mHost.EqualsASCII("*") &&
@@ -392,19 +420,18 @@ nsCSPHostSrc::toString(nsAString& outStr
   outStr.Append(mHost);
 
   // append port
   if (!mPort.IsEmpty()) {
     outStr.AppendASCII(":");
     outStr.Append(mPort);
   }
 
-  // in CSP 1.1, paths are ignoed
-  // outStr.Append(mPath);
-  // outStr.Append(mFileAndArguments);
+  // append path
+  outStr.Append(mPath);
 }
 
 void
 nsCSPHostSrc::setScheme(const nsAString& aScheme)
 {
   mScheme = aScheme;
   ToLowerCase(mScheme);
 }
@@ -418,23 +445,16 @@ nsCSPHostSrc::setPort(const nsAString& a
 
 void
 nsCSPHostSrc::appendPath(const nsAString& aPath)
 {
   mPath.Append(aPath);
   ToLowerCase(mPath);
 }
 
-void
-nsCSPHostSrc::setFileAndArguments(const nsAString& aFile)
-{
-  mFileAndArguments = aFile;
-  ToLowerCase(mFileAndArguments);
-}
-
 /* ===== nsCSPKeywordSrc ===================== */
 
 nsCSPKeywordSrc::nsCSPKeywordSrc(CSPKeyword aKeyword)
 {
   NS_ASSERTION((aKeyword != CSP_SELF),
                "'self' should have been replaced in the parser");
   mKeyword = aKeyword;
 }
@@ -464,17 +484,17 @@ nsCSPNonceSrc::nsCSPNonceSrc(const nsASt
 {
 }
 
 nsCSPNonceSrc::~nsCSPNonceSrc()
 {
 }
 
 bool
-nsCSPNonceSrc::permits(nsIURI* aUri, const nsAString& aNonce) const
+nsCSPNonceSrc::permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected) const
 {
 #ifdef PR_LOGGING
   {
     nsAutoCString spec;
     aUri->GetSpec(spec);
     CSPUTILSLOG(("nsCSPNonceSrc::permits, aUri: %s, aNonce: %s",
                 spec.get(), NS_ConvertUTF16toUTF8(aNonce).get()));
   }
@@ -594,39 +614,39 @@ nsCSPDirective::nsCSPDirective(enum CSPD
 nsCSPDirective::~nsCSPDirective()
 {
   for (uint32_t i = 0; i < mSrcs.Length(); i++) {
     delete mSrcs[i];
   }
 }
 
 bool
-nsCSPDirective::permits(nsIURI* aUri, const nsAString& aNonce) const
+nsCSPDirective::permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected) const
 {
 #ifdef PR_LOGGING
   {
     nsAutoCString spec;
     aUri->GetSpec(spec);
     CSPUTILSLOG(("nsCSPDirective::permits, aUri: %s", spec.get()));
   }
 #endif
 
   for (uint32_t i = 0; i < mSrcs.Length(); i++) {
-    if (mSrcs[i]->permits(aUri, aNonce)) {
+    if (mSrcs[i]->permits(aUri, aNonce, aWasRedirected)) {
       return true;
     }
   }
   return false;
 }
 
 bool
 nsCSPDirective::permits(nsIURI* aUri) const
 {
   nsString dummyNonce;
-  return permits(aUri, dummyNonce);
+  return permits(aUri, dummyNonce, false);
 }
 
 bool
 nsCSPDirective::allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce) const
 {
   CSPUTILSLOG(("nsCSPDirective::allows, aKeyWord: %s, a HashOrNonce: %s",
               CSP_EnumToKeyword(aKeyword), NS_ConvertUTF16toUTF8(aHashOrNonce).get()));
 
@@ -749,16 +769,17 @@ nsCSPPolicy::~nsCSPPolicy()
     delete mDirectives[i];
   }
 }
 
 bool
 nsCSPPolicy::permits(nsContentPolicyType aContentType,
                      nsIURI* aUri,
                      const nsAString& aNonce,
+                     bool aWasRedirected,
                      nsAString& outViolatedDirective) const
 {
 #ifdef PR_LOGGING
   {
     nsAutoCString spec;
     aUri->GetSpec(spec);
     CSPUTILSLOG(("nsCSPPolicy::permits, aContentType: %d, aUri: %s, aNonce: %s",
                 aContentType, spec.get(), NS_ConvertUTF16toUTF8(aNonce).get()));
@@ -769,17 +790,17 @@ nsCSPPolicy::permits(nsContentPolicyType
 
   nsCSPDirective* defaultDir = nullptr;
 
   // These directive arrays are short (1-5 elements), not worth using a hashtable.
 
   for (uint32_t i = 0; i < mDirectives.Length(); i++) {
     // Check if the directive name matches
     if (mDirectives[i]->restrictsContentType(aContentType)) {
-      if (!mDirectives[i]->permits(aUri, aNonce)) {
+      if (!mDirectives[i]->permits(aUri, aNonce, aWasRedirected)) {
         mDirectives[i]->toString(outViolatedDirective);
         return false;
       }
       return true;
     }
     if (mDirectives[i]->isDefaultDirective()) {
       defaultDir = mDirectives[i];
     }
@@ -790,17 +811,17 @@ nsCSPPolicy::permits(nsContentPolicyType
   // TODO: currently [frame-ancestors] is mapped to TYPE_DOCUMENT (needs to be fixed)
   if (aContentType == nsIContentPolicy::TYPE_DOCUMENT) {
     return true;
   }
 
   // If the above loop runs through, we haven't found a matching directive.
   // Avoid relooping, just store the result of default-src while looping.
   if (defaultDir) {
-    if (!defaultDir->permits(aUri, aNonce)) {
+    if (!defaultDir->permits(aUri, aNonce, aWasRedirected)) {
       defaultDir->toString(outViolatedDirective);
       return false;
     }
     return true;
   }
 
   // unspecified default-src should default to no restrictions
   // see bug 764937
--- a/content/base/src/nsCSPUtils.h
+++ b/content/base/src/nsCSPUtils.h
@@ -186,56 +186,54 @@ bool CSP_IsQuotelessKeyword(const nsAStr
 
 /* =============== nsCSPSrc ================== */
 
 class nsCSPBaseSrc {
   public:
     nsCSPBaseSrc();
     virtual ~nsCSPBaseSrc();
 
-    virtual bool permits(nsIURI* aUri, const nsAString& aNonce) const;
+    virtual bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected) const;
     virtual bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce) const;
     virtual void toString(nsAString& outStr) const = 0;
 };
 
 /* =============== nsCSPSchemeSrc ============ */
 
 class nsCSPSchemeSrc : public nsCSPBaseSrc {
   public:
     explicit nsCSPSchemeSrc(const nsAString& aScheme);
     virtual ~nsCSPSchemeSrc();
 
-    bool permits(nsIURI* aUri, const nsAString& aNonce) const;
+    bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected) const;
     void toString(nsAString& outStr) const;
 
   private:
     nsString mScheme;
 };
 
 /* =============== nsCSPHostSrc ============== */
 
 class nsCSPHostSrc : public nsCSPBaseSrc {
   public:
     explicit nsCSPHostSrc(const nsAString& aHost);
     virtual ~nsCSPHostSrc();
 
-    bool permits(nsIURI* aUri, const nsAString& aNonce) const;
+    bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected) const;
     void toString(nsAString& outStr) const;
 
     void setScheme(const nsAString& aScheme);
     void setPort(const nsAString& aPort);
     void appendPath(const nsAString &aPath);
-    void setFileAndArguments(const nsAString& aFile);
 
   private:
     nsString mScheme;
     nsString mHost;
     nsString mPort;
     nsString mPath;
-    nsString mFileAndArguments;
 };
 
 /* =============== nsCSPKeywordSrc ============ */
 
 class nsCSPKeywordSrc : public nsCSPBaseSrc {
   public:
     explicit nsCSPKeywordSrc(CSPKeyword aKeyword);
     virtual ~nsCSPKeywordSrc();
@@ -249,17 +247,17 @@ class nsCSPKeywordSrc : public nsCSPBase
 
 /* =============== nsCSPNonceSource =========== */
 
 class nsCSPNonceSrc : public nsCSPBaseSrc {
   public:
     explicit nsCSPNonceSrc(const nsAString& aNonce);
     virtual ~nsCSPNonceSrc();
 
-    bool permits(nsIURI* aUri, const nsAString& aNonce) const;
+    bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected) const;
     bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce) const;
     void toString(nsAString& outStr) const;
 
   private:
     nsString mNonce;
 };
 
 /* =============== nsCSPHashSource ============ */
@@ -293,17 +291,17 @@ class nsCSPReportURI : public nsCSPBaseS
 /* =============== nsCSPDirective ============= */
 
 class nsCSPDirective {
   public:
     nsCSPDirective();
     explicit nsCSPDirective(enum CSPDirective aDirective);
     virtual ~nsCSPDirective();
 
-    bool permits(nsIURI* aUri, const nsAString& aNonce) const;
+    bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected) const;
     bool permits(nsIURI* aUri) const;
     bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce) const;
     void toString(nsAString& outStr) const;
 
     inline void addSrcs(const nsTArray<nsCSPBaseSrc*>& aSrcs)
       { mSrcs = aSrcs; }
 
     bool restrictsContentType(nsContentPolicyType aContentType) const;
@@ -326,16 +324,17 @@ class nsCSPDirective {
 class nsCSPPolicy {
   public:
     nsCSPPolicy();
     virtual ~nsCSPPolicy();
 
     bool permits(nsContentPolicyType aContentType,
                  nsIURI* aUri,
                  const nsAString& aNonce,
+                 bool aWasRedirected,
                  nsAString& outViolatedDirective) const;
     bool permitsBaseURI(nsIURI* aUri) const;
     bool allows(nsContentPolicyType aContentType,
                 enum CSPKeyword aKeyword,
                 const nsAString& aHashOrNonce) const;
     bool allows(nsContentPolicyType aContentType,
                 enum CSPKeyword aKeyword) const;
     void toString(nsAString& outStr) const;