Bug 965727 - Implement referrer directive for CSP. (r=jst,ckerschb)
authorSid Stamm <sstamm@mozilla.com>
Wed, 17 Dec 2014 14:14:04 -0500
changeset 247740 eabee47625c6d680786f498c60d9cf4c1a7ef238
parent 247739 7c2973cb4fa1a0b0c1044a16c10d046b2538e0ea
child 247741 91fd3f8df7edaa5a63d01f3843fafeae14ad7b1a
push id698
push userjlund@mozilla.com
push dateMon, 23 Mar 2015 22:08:11 +0000
treeherdermozilla-release@b0c0ae7b02a3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjst, ckerschb
bugs965727
milestone37.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 965727 - Implement referrer directive for CSP. (r=jst,ckerschb)
dom/base/nsDocument.cpp
dom/html/nsHTMLDocument.cpp
dom/interfaces/security/nsIContentSecurityPolicy.idl
dom/security/nsCSPContext.cpp
dom/security/nsCSPParser.cpp
dom/security/nsCSPParser.h
dom/security/nsCSPUtils.cpp
dom/security/nsCSPUtils.h
netwerk/base/public/ReferrerPolicy.h
parser/html/nsHtml5TreeOpExecutor.cpp
parser/html/nsHtml5TreeOpExecutor.h
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -2982,16 +2982,43 @@ nsDocument::InitCSP(nsIChannel* aChannel
       PR_LOG(gCspPRLog, PR_LOG_DEBUG,
               ("CSP doesn't like frame's ancestry, not loading."));
 #endif
       // stop!  ERROR page!
       aChannel->Cancel(NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION);
     }
   }
 
+  // ----- Set up any Referrer Policy specified by CSP
+  bool hasReferrerPolicy = false;
+  uint32_t referrerPolicy = mozilla::net::RP_Default;
+  rv = csp->GetReferrerPolicy(&referrerPolicy, &hasReferrerPolicy);
+  NS_ENSURE_SUCCESS(rv, rv);
+  if (hasReferrerPolicy) {
+    // Referrer policy spec (section 6.1) says that once the referrer policy
+    // is set, any future attempts to change it result in No-Referrer.
+    if (!mReferrerPolicySet) {
+      mReferrerPolicy = static_cast<ReferrerPolicy>(referrerPolicy);
+      mReferrerPolicySet = true;
+    } else if (mReferrerPolicy != referrerPolicy) {
+      mReferrerPolicy = mozilla::net::RP_No_Referrer;
+#ifdef PR_LOGGING
+      {
+        PR_LOG(gCspPRLog, PR_LOG_DEBUG, ("%s %s",
+                "CSP wants to set referrer, but nsDocument"
+                "already has it set. No referrers will be sent"));
+      }
+#endif
+    }
+
+    // Referrer Policy is set separately for the speculative parser in
+    // nsHTMLDocument::StartDocumentLoad() so there's nothing to do here for
+    // speculative loads.
+  }
+
   rv = principal->SetCsp(csp);
   NS_ENSURE_SUCCESS(rv, rv);
 #ifdef PR_LOGGING
   PR_LOG(gCspPRLog, PR_LOG_DEBUG,
          ("Inserted CSP into principal %p", principal));
 #endif
 
   return NS_OK;
--- a/dom/html/nsHTMLDocument.cpp
+++ b/dom/html/nsHTMLDocument.cpp
@@ -668,20 +668,25 @@ nsHTMLDocument::StartDocumentLoad(const 
 
   // These are the charset source and charset for the parser.  This can differ
   // from that for the document if the channel is a wyciwyg channel.
   int32_t parserCharsetSource;
   nsAutoCString parserCharset;
 
   nsCOMPtr<nsIWyciwygChannel> wyciwygChannel;
   
-  // For error reporting
+  // For error reporting and referrer policy setting
   nsHtml5TreeOpExecutor* executor = nullptr;
   if (loadAsHtml5) {
     executor = static_cast<nsHtml5TreeOpExecutor*> (mParser->GetContentSink());
+    if (mReferrerPolicySet) {
+      // CSP may have set the referrer policy, so a speculative parser should
+      // start with the new referrer policy.
+      executor->SetSpeculationReferrerPolicy(static_cast<ReferrerPolicy>(mReferrerPolicy));
+    }
   }
 
   if (!IsHTML() || !docShell) { // no docshell for text/html XHR
     charsetSource = IsHTML() ? kCharsetFromFallback
                              : kCharsetFromDocTypeDefault;
     charset.AssignLiteral("UTF-8");
     TryChannelCharset(aChannel, charsetSource, charset, executor);
     parserCharsetSource = charsetSource;
@@ -1636,16 +1641,25 @@ nsHTMLDocument::Open(JSContext* cx,
 
   // Store the security info of the caller now that we're done
   // resetting the document.
   mSecurityInfo = securityInfo;
 
   mParserAborted = false;
   mParser = nsHtml5Module::NewHtml5Parser();
   nsHtml5Module::Initialize(mParser, this, uri, shell, channel);
+  if (mReferrerPolicySet) {
+    // CSP may have set the referrer policy, so a speculative parser should
+    // start with the new referrer policy.
+    nsHtml5TreeOpExecutor* executor = nullptr;
+    executor = static_cast<nsHtml5TreeOpExecutor*> (mParser->GetContentSink());
+    if (executor && mReferrerPolicySet) {
+      executor->SetSpeculationReferrerPolicy(static_cast<ReferrerPolicy>(mReferrerPolicy));
+    }
+  }
 
   // This will be propagated to the parser when someone actually calls write()
   SetContentTypeInternal(contentType);
 
   // Prepare the docshell and the document viewer for the impending
   // out of band document.write()
   shell->PrepareForNewContentModel();
 
--- a/dom/interfaces/security/nsIContentSecurityPolicy.idl
+++ b/dom/interfaces/security/nsIContentSecurityPolicy.idl
@@ -15,17 +15,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, uuid(69b7663e-117a-4a3b-81bd-d86420b7c79e)]
+[scriptable, uuid(68434447-b816-4473-a731-efc4f6d59902)]
 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.
    *
@@ -42,30 +42,46 @@ interface nsIContentSecurityPolicy : nsI
   const unsigned short FRAME_SRC_DIRECTIVE        = 7;
   const unsigned short FONT_SRC_DIRECTIVE         = 8;
   const unsigned short CONNECT_SRC_DIRECTIVE      = 9;
   const unsigned short REPORT_URI_DIRECTIVE       = 10;
   const unsigned short FRAME_ANCESTORS_DIRECTIVE  = 11;
   const unsigned short REFLECTED_XSS_DIRECTIVE    = 12;
   const unsigned short BASE_URI_DIRECTIVE         = 13;
   const unsigned short FORM_ACTION_DIRECTIVE      = 14;
+  const unsigned short REFERRER_DIRECTIVE         = 15;
 
   /**
    * Accessor method for a read-only string version of the policy at a given
    * index.
    */
   AString getPolicy(in unsigned long index);
 
   /**
    * Returns the number of policies attached to this CSP instance.  Useful with
    * getPolicy().
    */
   readonly attribute unsigned long policyCount;
 
   /**
+   * Obtains the referrer policy (as integer) for this browsing context as
+   * specified in CSP.  If there are multiple policies and...
+   *  - only one sets a referrer policy: that policy is returned
+   *  - more than one sets different referrer policies: no-referrer is returned
+   *  - more than one set equivalent policies: that policy is returned
+   * For the enumeration of policies see ReferrerPolicy.h and nsIHttpChannel.
+   *
+   * @param aPolicy
+   *        The referrer policy to use for the protected resource.
+   * @return
+   *        true if a referrer policy is specified, false if it's unspecified.
+   */
+  bool getReferrerPolicy(out unsigned long policy);
+
+  /**
    * Remove a policy associated with this CSP context.
    * @throws NS_ERROR_FAILURE if the index is out of bounds or invalid.
    */
   void removePolicy(in unsigned long index);
 
   /**
    * Parse and install a CSP policy.
    * @param aPolicy
--- a/dom/security/nsCSPContext.cpp
+++ b/dom/security/nsCSPContext.cpp
@@ -31,16 +31,17 @@
 #include "nsNetUtil.h"
 #include "nsNullPrincipal.h"
 #include "nsIContentPolicy.h"
 #include "nsSupportsPrimitives.h"
 #include "nsThreadUtils.h"
 #include "nsString.h"
 #include "prlog.h"
 #include "mozilla/dom/CSPReportBinding.h"
+#include "mozilla/net/ReferrerPolicy.h"
 
 using namespace mozilla;
 
 #if defined(PR_LOGGING)
 static PRLogModuleInfo *
 GetCspContextLog()
 {
   static PRLogModuleInfo *gCspContextPRLog;
@@ -301,16 +302,44 @@ nsCSPContext::GetPolicy(uint32_t aIndex,
 NS_IMETHODIMP
 nsCSPContext::GetPolicyCount(uint32_t *outPolicyCount)
 {
   *outPolicyCount = mPolicies.Length();
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsCSPContext::GetReferrerPolicy(uint32_t* outPolicy, bool* outIsSet)
+{
+  *outIsSet = false;
+  *outPolicy = mozilla::net::RP_Default;
+  nsAutoString refpol;
+  mozilla::net::ReferrerPolicy previousPolicy = mozilla::net::RP_Default;
+  for (uint32_t i = 0; i < mPolicies.Length(); i++) {
+    mPolicies[i]->getReferrerPolicy(refpol);
+    // an empty string in refpol means it wasn't set (that's the default in
+    // nsCSPPolicy).
+    if (!refpol.IsEmpty()) {
+      // if there are two policies that specify a referrer policy, then they
+      // must agree or the employed policy is no-referrer.
+      uint32_t currentPolicy = mozilla::net::ReferrerPolicyFromString(refpol);
+      if (*outIsSet && previousPolicy != currentPolicy) {
+        *outPolicy = mozilla::net::RP_No_Referrer;
+        return NS_OK;
+      }
+
+      *outPolicy = currentPolicy;
+      *outIsSet = true;
+    }
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsCSPContext::RemovePolicy(uint32_t aIndex)
 {
   if (aIndex >= mPolicies.Length()) {
     return NS_ERROR_ILLEGAL_VALUE;
   }
   mPolicies.RemoveElementAt(aIndex);
   // reset cache since effective policy changes
   mShouldLoadCache.Clear();
--- a/dom/security/nsCSPParser.cpp
+++ b/dom/security/nsCSPParser.cpp
@@ -9,16 +9,17 @@
 #include "nsCSPUtils.h"
 #include "nsIConsoleService.h"
 #include "nsIScriptError.h"
 #include "nsIStringBundle.h"
 #include "nsNetUtil.h"
 #include "nsReadableUtils.h"
 #include "nsServiceManagerUtils.h"
 #include "nsUnicharUtils.h"
+#include "mozilla/net/ReferrerPolicy.h"
 
 using namespace mozilla;
 
 #if defined(PR_LOGGING)
 static PRLogModuleInfo*
 GetCspParserLog()
 {
   static PRLogModuleInfo* gCspParserPRLog;
@@ -853,16 +854,40 @@ nsCSPParser::sourceList(nsTArray<nsCSPBa
       const char16_t* params[] = { unicodeNone.get() };
       logWarningErrorToConsole(nsIScriptError::warningFlag, "ignoringUnknownOption",
                                params, ArrayLength(params));
     }
   }
 }
 
 void
+nsCSPParser::referrerDirectiveValue()
+{
+  // directive-value   = "none" / "none-when-downgrade" / "origin" / "origin-when-cross-origin" / "unsafe-url"
+  // directive name is token 0, we need to examine the remaining tokens (and
+  // there should only be one token in the value).
+  CSPPARSERLOG(("nsCSPParser::referrerDirectiveValue"));
+
+  if (mCurDir.Length() > 2) {
+    CSPPARSERLOG(("Too many tokens in referrer directive, got %d expected 1",
+                 mCurDir.Length() - 1));
+    return;
+  }
+
+  if (!mozilla::net::IsValidReferrerPolicy(mCurDir[1])) {
+    CSPPARSERLOG(("invalid value for referrer directive: %s",
+                  NS_ConvertUTF16toUTF8(mCurDir[1]).get()));
+    return;
+  }
+
+  // the referrer policy is valid, so go ahead and use it.
+  mPolicy->setReferrerPolicy(&mCurDir[1]);
+}
+
+void
 nsCSPParser::reportURIList(nsTArray<nsCSPBaseSrc*>& outSrcs)
 {
   nsCOMPtr<nsIURI> uri;
   nsresult rv;
 
   // remember, srcs start at index 1
   for (uint32_t i = 1; i < mCurDir.Length(); i++) {
     mCurToken = mCurDir[i];
@@ -895,16 +920,24 @@ nsCSPParser::directiveValue(nsTArray<nsC
 
   // The tokenzier already generated an array in the form of
   // [ name, src, src, ... ], no need to parse again, but
   // special case handling in case the directive is report-uri.
   if (CSP_IsDirective(mCurDir[0], nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE)) {
     reportURIList(outSrcs);
     return;
   }
+
+  // special case handling of the referrer directive (since it doesn't contain
+  // source lists)
+  if (CSP_IsDirective(mCurDir[0], nsIContentSecurityPolicy::REFERRER_DIRECTIVE)) {
+    referrerDirectiveValue();
+    return;
+  }
+
   // Otherwise just forward to sourceList
   sourceList(outSrcs);
 }
 
 // directive-name = 1*( ALPHA / DIGIT / "-" )
 nsCSPDirective*
 nsCSPParser::directiveName()
 {
--- a/dom/security/nsCSPParser.h
+++ b/dom/security/nsCSPParser.h
@@ -108,16 +108,17 @@ class nsCSPParser {
     ~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);
+    void            referrerDirectiveValue();
     void            sourceList(nsTArray<nsCSPBaseSrc*>& outSrcs);
     nsCSPBaseSrc*   sourceExpression();
     nsCSPSchemeSrc* schemeSource();
     nsCSPHostSrc*   hostSource();
     nsCSPBaseSrc*   keywordSource();
     nsCSPNonceSrc*  nonceSource();
     nsCSPHashSrc*   hashSource();
     nsCSPHostSrc*   appHost(); // helper function to support app specific hosts
--- a/dom/security/nsCSPUtils.cpp
+++ b/dom/security/nsCSPUtils.cpp
@@ -894,17 +894,24 @@ nsCSPPolicy::allows(nsContentPolicyType 
   return allows(aContentType, aKeyword, NS_LITERAL_STRING(""));
 }
 
 void
 nsCSPPolicy::toString(nsAString& outStr) const
 {
   uint32_t length = mDirectives.Length();
   for (uint32_t i = 0; i < length; ++i) {
-    mDirectives[i]->toString(outStr);
+
+    if (mDirectives[i]->equals(nsIContentSecurityPolicy::REFERRER_DIRECTIVE)) {
+      outStr.AppendASCII(CSP_CSPDirectiveToString(nsIContentSecurityPolicy::REFERRER_DIRECTIVE));
+      outStr.AppendASCII(" ");
+      outStr.Append(mReferrerPolicy);
+    } else {
+      mDirectives[i]->toString(outStr);
+    }
     if (i != (length - 1)) {
       outStr.AppendASCII("; ");
     }
   }
 }
 
 bool
 nsCSPPolicy::hasDirective(CSPDirective aDir) const
--- a/dom/security/nsCSPUtils.h
+++ b/dom/security/nsCSPUtils.h
@@ -68,17 +68,18 @@ static const char* CSPStrDirectives[] = 
   "media-src",       // MEDIA_SRC_DIRECTIVE
   "frame-src",       // FRAME_SRC_DIRECTIVE
   "font-src",        // FONT_SRC_DIRECTIVE
   "connect-src",     // CONNECT_SRC_DIRECTIVE
   "report-uri",      // REPORT_URI_DIRECTIVE
   "frame-ancestors", // FRAME_ANCESTORS_DIRECTIVE
   "reflected-xss",   // REFLECTED_XSS_DIRECTIVE
   "base-uri",        // BASE_URI_DIRECTIVE
-  "form-action"      // FORM_ACTION_DIRECTIVE
+  "form-action",     // FORM_ACTION_DIRECTIVE
+  "referrer"         // REFERRER_DIRECTIVE
 };
 
 inline const char* CSP_CSPDirectiveToString(CSPDirective aDir)
 {
   return CSPStrDirectives[static_cast<uint32_t>(aDir)];
 }
 
 inline CSPDirective CSP_StringToCSPDirective(const nsAString& aDir)
@@ -327,24 +328,31 @@ class nsCSPPolicy {
     bool hasDirective(CSPDirective aDir) const;
 
     inline void setReportOnlyFlag(bool aFlag)
       { mReportOnly = aFlag; }
 
     inline bool getReportOnlyFlag() const
       { return mReportOnly; }
 
+    inline void setReferrerPolicy(const nsAString* aValue)
+      { mReferrerPolicy = *aValue; }
+
+    inline void getReferrerPolicy(nsAString& outPolicy) const
+      { outPolicy.Assign(mReferrerPolicy); }
+
     void getReportURIs(nsTArray<nsString> &outReportURIs) const;
 
     void getDirectiveStringForContentType(nsContentPolicyType aContentType,
                                           nsAString& outDirective) const;
 
     void getDirectiveAsString(CSPDirective aDir, nsAString& outDirective) const;
 
     inline uint32_t getNumDirectives() const
       { return mDirectives.Length(); }
 
   private:
     nsTArray<nsCSPDirective*> mDirectives;
     bool                      mReportOnly;
+    nsString                  mReferrerPolicy;
 };
 
 #endif /* nsCSPUtils_h___ */
--- a/netwerk/base/public/ReferrerPolicy.h
+++ b/netwerk/base/public/ReferrerPolicy.h
@@ -51,11 +51,24 @@ ReferrerPolicyFromString(const nsAString
       content.LowerCaseEqualsLiteral("unsafe-url")) {
     return RP_Unsafe_URL;
   }
   // Spec says if none of the previous match, use No_Referrer.
   return RP_No_Referrer;
 
 }
 
+inline bool
+IsValidReferrerPolicy(const nsAString& content)
+{
+  return content.LowerCaseEqualsLiteral("never")
+      || content.LowerCaseEqualsLiteral("no-referrer")
+      || content.LowerCaseEqualsLiteral("origin")
+      || content.LowerCaseEqualsLiteral("default")
+      || content.LowerCaseEqualsLiteral("no-referrer-when-downgrade")
+      || content.LowerCaseEqualsLiteral("origin-when-crossorigin")
+      || content.LowerCaseEqualsLiteral("always")
+      || content.LowerCaseEqualsLiteral("unsafe-url");
+}
+
 } } //namespace mozilla::net
 
 #endif
--- a/parser/html/nsHtml5TreeOpExecutor.cpp
+++ b/parser/html/nsHtml5TreeOpExecutor.cpp
@@ -955,30 +955,35 @@ nsHtml5TreeOpExecutor::SetSpeculationBas
                                      charset.get(), mDocument->GetDocumentURI());
   NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to create a URI");
 }
 
 void
 nsHtml5TreeOpExecutor::SetSpeculationReferrerPolicy(const nsAString& aReferrerPolicy)
 {
   ReferrerPolicy policy = mozilla::net::ReferrerPolicyFromString(aReferrerPolicy);
+  return SetSpeculationReferrerPolicy(policy);
+}
 
+void
+nsHtml5TreeOpExecutor::SetSpeculationReferrerPolicy(ReferrerPolicy aReferrerPolicy)
+{
   if (mSpeculationReferrerPolicyWasSet &&
-      policy != mSpeculationReferrerPolicy) {
+      aReferrerPolicy != mSpeculationReferrerPolicy) {
     // According to the Referrer Policy spec, if there's already been a policy
     // set and another attempt is made to set a _different_ policy, the result
     // is a "No Referrer" policy.
     mSpeculationReferrerPolicy = mozilla::net::RP_No_Referrer;
   }
   else {
     // Record "speculated" referrer policy locally and thread through the
     // speculation phase.  The actual referrer policy will be set by
     // HTMLMetaElement::BindToTree().
     mSpeculationReferrerPolicyWasSet = true;
-    mSpeculationReferrerPolicy = policy;
+    mSpeculationReferrerPolicy = aReferrerPolicy;
   }
 }
 
 #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
 uint32_t nsHtml5TreeOpExecutor::sAppendBatchMaxSize = 0;
 uint32_t nsHtml5TreeOpExecutor::sAppendBatchSlotsExamined = 0;
 uint32_t nsHtml5TreeOpExecutor::sAppendBatchExaminations = 0;
 uint32_t nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop = 0;
--- a/parser/html/nsHtml5TreeOpExecutor.h
+++ b/parser/html/nsHtml5TreeOpExecutor.h
@@ -261,16 +261,17 @@ class nsHtml5TreeOpExecutor MOZ_FINAL : 
 
     void PreloadStyle(const nsAString& aURL, const nsAString& aCharset,
 		      const nsAString& aCrossOrigin);
 
     void PreloadImage(const nsAString& aURL, const nsAString& aCrossOrigin);
 
     void SetSpeculationBase(const nsAString& aURL);
 
+    void SetSpeculationReferrerPolicy(ReferrerPolicy aReferrerPolicy);
     void SetSpeculationReferrerPolicy(const nsAString& aReferrerPolicy);
     
     void AddBase(const nsAString& aURL);
 
     static void InitializeStatics();
 
   private:
     nsHtml5Parser* GetParser();