Bug 965727 - Implement referrer directive for CSP. (r=jst,ckerschb)

This commit is contained in:
Sid Stamm 2014-12-17 14:14:04 -05:00
parent 6fad6fa0b3
commit 53bc033453
11 changed files with 160 additions and 6 deletions

View File

@ -2987,6 +2987,33 @@ nsDocument::InitCSP(nsIChannel* aChannel)
}
}
// ----- 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

View File

@ -673,10 +673,15 @@ nsHTMLDocument::StartDocumentLoad(const char* aCommand,
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
@ -1641,6 +1646,15 @@ nsHTMLDocument::Open(JSContext* cx,
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);

View File

@ -20,7 +20,7 @@ interface nsIURI;
typedef unsigned short CSPDirective;
[scriptable, uuid(69b7663e-117a-4a3b-81bd-d86420b7c79e)]
[scriptable, uuid(68434447-b816-4473-a731-efc4f6d59902)]
interface nsIContentSecurityPolicy : nsISerializable
{
/**
@ -47,6 +47,7 @@ interface nsIContentSecurityPolicy : nsISerializable
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
@ -60,6 +61,21 @@ interface nsIContentSecurityPolicy : nsISerializable
*/
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.

View File

@ -36,6 +36,7 @@
#include "nsString.h"
#include "prlog.h"
#include "mozilla/dom/CSPReportBinding.h"
#include "mozilla/net/ReferrerPolicy.h"
using namespace mozilla;
@ -305,6 +306,34 @@ nsCSPContext::GetPolicyCount(uint32_t *outPolicyCount)
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)
{

View File

@ -14,6 +14,7 @@
#include "nsReadableUtils.h"
#include "nsServiceManagerUtils.h"
#include "nsUnicharUtils.h"
#include "mozilla/net/ReferrerPolicy.h"
using namespace mozilla;
@ -857,6 +858,30 @@ nsCSPParser::sourceList(nsTArray<nsCSPBaseSrc*>& outSrcs)
}
}
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)
{
@ -900,6 +925,14 @@ nsCSPParser::directiveValue(nsTArray<nsCSPBaseSrc*>& outSrcs)
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);
}

View File

@ -113,6 +113,7 @@ class nsCSPParser {
void directive();
nsCSPDirective* directiveName();
void directiveValue(nsTArray<nsCSPBaseSrc*>& outSrcs);
void referrerDirectiveValue();
void sourceList(nsTArray<nsCSPBaseSrc*>& outSrcs);
nsCSPBaseSrc* sourceExpression();
nsCSPSchemeSrc* schemeSource();

View File

@ -899,7 +899,14 @@ 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("; ");
}

View File

@ -73,7 +73,8 @@ static const char* CSPStrDirectives[] = {
"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)
@ -332,6 +333,12 @@ class nsCSPPolicy {
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,
@ -345,6 +352,7 @@ class nsCSPPolicy {
private:
nsTArray<nsCSPDirective*> mDirectives;
bool mReportOnly;
nsString mReferrerPolicy;
};
#endif /* nsCSPUtils_h___ */

View File

@ -56,6 +56,19 @@ ReferrerPolicyFromString(const nsAString& content)
}
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

View File

@ -960,9 +960,14 @@ 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.
@ -973,7 +978,7 @@ nsHtml5TreeOpExecutor::SetSpeculationReferrerPolicy(const nsAString& aReferrerPo
// speculation phase. The actual referrer policy will be set by
// HTMLMetaElement::BindToTree().
mSpeculationReferrerPolicyWasSet = true;
mSpeculationReferrerPolicy = policy;
mSpeculationReferrerPolicy = aReferrerPolicy;
}
}

View File

@ -266,6 +266,7 @@ class nsHtml5TreeOpExecutor MOZ_FINAL : public nsHtml5DocumentBuilder,
void SetSpeculationBase(const nsAString& aURL);
void SetSpeculationReferrerPolicy(ReferrerPolicy aReferrerPolicy);
void SetSpeculationReferrerPolicy(const nsAString& aReferrerPolicy);
void AddBase(const nsAString& aURL);