Bug 999656 - Fix mappings between content type and CSP directives and refactor permits functions in CSP. r=ckerschb

This commit is contained in:
Sid Stamm 2014-12-10 13:54:00 +01:00
parent 121e12c359
commit f6c54bcc92
8 changed files with 312 additions and 346 deletions

View File

@ -3565,7 +3565,12 @@ nsDocument::SetBaseURI(nsIURI* aURI)
NS_ENSURE_SUCCESS(rv, rv);
if (csp) {
bool permitsBaseURI = false;
rv = csp->PermitsBaseURI(aURI, &permitsBaseURI);
// base-uri is only enforced if explicitly defined in the
// policy - do *not* consult default-src, see:
// http://www.w3.org/TR/CSP2/#directive-default-src
rv = csp->Permits(aURI, nsIContentSecurityPolicy::BASE_URI_DIRECTIVE,
true, &permitsBaseURI);
NS_ENSURE_SUCCESS(rv, rv);
if (!permitsBaseURI) {
return NS_OK;

View File

@ -1633,7 +1633,12 @@ HTMLFormElement::GetActionURL(nsIURI** aActionURL,
NS_ENSURE_SUCCESS(rv, rv);
if (csp) {
bool permitsFormAction = true;
rv = csp->PermitsFormAction(actionURL, &permitsFormAction);
// form-action is only enforced if explicitly defined in the
// policy - do *not* consult default-src, see:
// http://www.w3.org/TR/CSP2/#directive-default-src
rv = csp->Permits(actionURL, nsIContentSecurityPolicy::FORM_ACTION_DIRECTIVE,
true, &permitsFormAction);
NS_ENSURE_SUCCESS(rv, rv);
if (!permitsFormAction) {
rv = NS_ERROR_CSP_FORM_ACTION_VIOLATION;

View File

@ -18,9 +18,35 @@ interface nsIURI;
* one of these per document/principal.
*/
[scriptable, uuid(dc86b261-5e41-4cab-ace3-a0278f5a7ec7)]
typedef unsigned short CSPDirective;
[scriptable, uuid(69b7663e-117a-4a3b-81bd-d86420b7c79e)]
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.
*
* NOTE: When implementing a new directive, you will need to add it here but also
* add it to the CSPStrDirectives array in nsCSPUtils.h.
*/
const unsigned short NO_DIRECTIVE = 0;
const unsigned short DEFAULT_SRC_DIRECTIVE = 1;
const unsigned short SCRIPT_SRC_DIRECTIVE = 2;
const unsigned short OBJECT_SRC_DIRECTIVE = 3;
const unsigned short STYLE_SRC_DIRECTIVE = 4;
const unsigned short IMG_SRC_DIRECTIVE = 5;
const unsigned short MEDIA_SRC_DIRECTIVE = 6;
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;
/**
* Accessor method for a read-only string version of the policy at a given
@ -196,24 +222,29 @@ interface nsIContentSecurityPolicy : nsISerializable
*/
boolean permitsAncestry(in nsIDocShell docShell);
/**
* Whether this policy allows setting the document's base URI to
* a given value.
*
* @return
* Whether or not the provided URI is allowed to be used as the
* document's base URI. (block the setting if false).
*/
boolean permitsBaseURI(in nsIURI aURI);
/**
* Whether this policy allows submitting HTML forms to a given URI.
* Checks if a specific directive permits loading of a URI.
*
* NOTE: Calls to this may trigger violation reports when queried, so the
* return value should not be cached.
*
* @param aURI
* The URI about to be loaded or used.
* @param aDir
* The CSPDirective to query (see above constants *_DIRECTIVE).
* @param aSpecific
* If "true" and the directive is specified to fall back to "default-src"
* when it's not explicitly provided, directivePermits will NOT try
* default-src when the specific directive is not used. Setting this to
* "false" allows CSP to fall back to default-src. This function
* behaves the same for both values of canUseDefault when querying
* directives that don't fall-back.
* @return
* Whether or not the provided URI is allowed to be used as a
* form's action URI.
* Whether or not the provided URI is allowed by CSP under the given
* directive. (block the pending operation if false).
*/
boolean permitsFormAction(in nsIURI aURI);
boolean permits(in nsIURI aURI, in CSPDirective aDir, in boolean aSpecific);
/**
* Delegate method called by the service when sub-elements of the protected

View File

@ -135,6 +135,13 @@ nsCSPContext::ShouldLoad(nsContentPolicyType aContentType,
// Default decision, CSP can revise it if there's a policy to enforce
*outDecision = nsIContentPolicy::ACCEPT;
// If the content type doesn't map to a CSP directive, there's nothing for
// CSP to do.
CSPDirective dir = CSP_ContentTypeToDirective(aContentType);
if (dir == nsIContentSecurityPolicy::NO_DIRECTIVE) {
return NS_OK;
}
// This may be a load or a preload. If it is a preload, the document will
// not have been fully parsed yet, and aRequestContext will be an
// nsIDOMHTMLDocument rather than the nsIDOMHTMLElement associated with the
@ -165,38 +172,23 @@ nsCSPContext::ShouldLoad(nsContentPolicyType aContentType,
}
}
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;
}
// aExtra is only non-null if the channel got redirected.
bool wasRedirected = (aExtra != nullptr);
nsCOMPtr<nsIURI> originalURI = do_QueryInterface(aExtra);
bool permitted = permitsInternal(dir,
aContentLocation,
originalURI,
nonce,
wasRedirected,
isPreload,
false, // allow fallback to default-src
true, // send violation reports
true); // send blocked URI in violation reports
*outDecision = permitted ? nsIContentPolicy::ACCEPT
: nsIContentPolicy::REJECT_SERVER;
// Do not send a report or notify observers if this is a preload - the
// decision may be wrong due to the inability to get the nonce, and will
// incorrectly fail the unit tests.
if (!isPreload) {
nsCOMPtr<nsIURI> originalURI = do_QueryInterface(aExtra);
this->AsyncReportViolation(aContentLocation,
originalURI, /* in case of redirect originalURI is not null */
violatedDirective,
p, /* policy index */
EmptyString(), /* no observer subject */
EmptyString(), /* no source file */
EmptyString(), /* no script sample */
0); /* no line number */
}
}
}
// Done looping, cache any relevant result
if (cacheKey.Length() > 0 && !isPreload) {
mShouldLoadCache.Put(cacheKey, *outDecision);
@ -212,6 +204,64 @@ nsCSPContext::ShouldLoad(nsContentPolicyType aContentType,
return NS_OK;
}
bool
nsCSPContext::permitsInternal(CSPDirective aDir,
nsIURI* aContentLocation,
nsIURI* aOriginalURI,
const nsAString& aNonce,
bool aWasRedirected,
bool aIsPreload,
bool aSpecific,
bool aSendViolationReports,
bool aSendContentLocationInViolationReports)
{
bool permits = true;
nsAutoString violatedDirective;
for (uint32_t p = 0; p < mPolicies.Length(); p++) {
// According to the W3C CSP spec, frame-ancestors checks are ignored for
// report-only policies (when "monitoring").
if (aDir == nsIContentSecurityPolicy::FRAME_ANCESTORS_DIRECTIVE &&
mPolicies[p]->getReportOnlyFlag()) {
continue;
}
if (!mPolicies[p]->permits(aDir,
aContentLocation,
aNonce,
aWasRedirected,
aSpecific,
violatedDirective)) {
// If the policy is violated and not report-only, reject the load and
// report to the console
if (!mPolicies[p]->getReportOnlyFlag()) {
CSPCONTEXTLOG(("nsCSPContext::permitsInternal, false"));
permits = false;
}
// Do not send a report or notify observers if this is a preload - the
// decision may be wrong due to the inability to get the nonce, and will
// incorrectly fail the unit tests.
if (!aIsPreload && aSendViolationReports) {
this->AsyncReportViolation((aSendContentLocationInViolationReports ?
aContentLocation : nullptr),
aOriginalURI, /* in case of redirect originalURI is not null */
violatedDirective,
p, /* policy index */
EmptyString(), /* no observer subject */
EmptyString(), /* no source file */
EmptyString(), /* no script sample */
0); /* no line number */
}
}
}
return permits;
}
/* ===== nsISupports implementation ========== */
NS_IMPL_CLASSINFO(nsCSPContext,
@ -1041,128 +1091,66 @@ nsCSPContext::PermitsAncestry(nsIDocShell* aDocShell, bool* outPermitsAncestry)
// Now that we've got the ancestry chain in ancestorsArray, time to check
// them against any CSP.
for (uint32_t i = 0; i < mPolicies.Length(); i++) {
// NOTE: the ancestors are not allowed to be sent cross origin; this is a
// restriction not placed on subresource loads.
// According to the W3C CSP spec, frame-ancestors checks are ignored for
// report-only policies (when "monitoring").
if (mPolicies[i]->getReportOnlyFlag()) {
continue;
}
for (uint32_t a = 0; a < ancestorsArray.Length(); a++) {
// TODO(sid) the mapping from frame-ancestors context to TYPE_DOCUMENT is
// forced. while this works for now, we will implement something in
// bug 999656.
for (uint32_t a = 0; a < ancestorsArray.Length(); a++) {
#ifdef PR_LOGGING
{
nsAutoCString spec;
ancestorsArray[a]->GetSpec(spec);
CSPCONTEXTLOG(("nsCSPContext::PermitsAncestry, checking ancestor: %s", spec.get()));
}
{
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);
// omit the ancestor URI in violation reports 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),
nullptr, /* originalURI in case of redirect */
violatedDirective,
i, /* policy index */
EmptyString(), /* no observer subject */
EmptyString(), /* no source file */
EmptyString(), /* no script sample */
0); /* no line number */
*outPermitsAncestry = false;
}
bool permits = permitsInternal(nsIContentSecurityPolicy::FRAME_ANCESTORS_DIRECTIVE,
ancestorsArray[a],
nullptr, // no redirect here.
EmptyString(), // no nonce
false, // no redirect here.
false, // not a preload.
true, // specific, do not use default-src
true, // send violation reports
okToSendAncestor);
if (!permits) {
*outPermitsAncestry = false;
}
}
return NS_OK;
}
NS_IMETHODIMP
nsCSPContext::PermitsBaseURI(nsIURI* aURI, bool* outPermitsBaseURI)
nsCSPContext::Permits(nsIURI* aURI,
CSPDirective aDir,
bool aSpecific,
bool* outPermits)
{
// Can't perform check without aURI
if (aURI == nullptr) {
return NS_ERROR_FAILURE;
}
*outPermitsBaseURI = true;
for (uint32_t i = 0; i < mPolicies.Length(); i++) {
if (!mPolicies[i]->permitsBaseURI(aURI)) {
// policy is violated, report to caller if not report-only
if (!mPolicies[i]->getReportOnlyFlag()) {
*outPermitsBaseURI = false;
}
nsAutoString violatedDirective;
mPolicies[i]->getDirectiveAsString(CSP_BASE_URI, violatedDirective);
this->AsyncReportViolation(aURI,
nullptr, /* originalURI in case of redirect */
violatedDirective,
i, /* policy index */
EmptyString(), /* no observer subject */
EmptyString(), /* no source file */
EmptyString(), /* no script sample */
0); /* no line number */
}
}
*outPermits = permitsInternal(aDir,
aURI,
nullptr, // no original (pre-redirect) URI
EmptyString(), // no nonce
false, // not redirected.
false, // not a preload.
aSpecific,
true, // send violation reports
true); // send blocked URI in violation reports
#ifdef PR_LOGGING
{
nsAutoCString spec;
aURI->GetSpec(spec);
CSPCONTEXTLOG(("nsCSPContext::PermitsBaseURI, aUri: %s, isAllowed: %s",
spec.get(),
*outPermitsBaseURI ? "allow" : "deny"));
}
#endif
return NS_OK;
}
NS_IMETHODIMP
nsCSPContext::PermitsFormAction(nsIURI* aURI, bool* outPermitsFormAction)
{
// Can't perform check without aURI
if (!aURI) {
return NS_ERROR_FAILURE;
}
*outPermitsFormAction = true;
for (uint32_t i = 0; i < mPolicies.Length(); i++) {
if (!mPolicies[i]->permitsFormAction(aURI)) {
// policy is violated, report to caller if not report-only
if (!mPolicies[i]->getReportOnlyFlag()) {
*outPermitsFormAction = false;
}
nsAutoString violatedDirective;
mPolicies[i]->getDirectiveAsString(CSP_FORM_ACTION, violatedDirective);
this->AsyncReportViolation(aURI,
mSelfURI,
violatedDirective,
i, /* policy index */
EmptyString(), /* no observer subject */
EmptyString(), /* no source file */
EmptyString(), /* no script sample */
0); /* no line number */
}
}
#ifdef PR_LOGGING
{
nsAutoCString spec;
aURI->GetSpec(spec);
CSPCONTEXTLOG(("nsCSPContext::PermitsFormAction, aUri: %s, isAllowed: %s",
spec.get(),
*outPermitsFormAction ? "allow" : "deny"));
CSPCONTEXTLOG(("nsCSPContext::Permits, aUri: %s, aDir: %d, isAllowed: %s",
spec.get(), aDir,
*outPermits ? "allow" : "deny"));
}
#endif

View File

@ -61,6 +61,16 @@ class nsCSPContext : public nsIContentSecurityPolicy
bool* outShouldReportViolations,
bool* outIsAllowed) const;
bool permitsInternal(CSPDirective aDir,
nsIURI* aContentLocation,
nsIURI* aOriginalURI,
const nsAString& aNonce,
bool aWasRedirected,
bool aIsPreload,
bool aSpecific,
bool aSendViolationReports,
bool aSendContentLocationInViolationReports);
nsCOMPtr<nsIURI> mReferrer;
uint64_t mInnerWindowID; // used for web console logging
nsTArray<nsCSPPolicy*> mPolicies;

View File

@ -896,7 +896,7 @@ nsCSPParser::directiveValue(nsTArray<nsCSPBaseSrc*>& outSrcs)
// 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], CSP_REPORT_URI)) {
if (CSP_IsDirective(mCurDir[0], nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE)) {
reportURIList(outSrcs);
return;
}
@ -924,7 +924,7 @@ nsCSPParser::directiveName()
// http://www.w3.org/TR/2014/WD-CSP11-20140211/#reflected-xss
// Currently we are not supporting that directive, hence we log a
// warning to the console and ignore the directive including its values.
if (CSP_IsDirective(mCurToken, CSP_REFLECTED_XSS)) {
if (CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::REFLECTED_XSS_DIRECTIVE)) {
const char16_t* params[] = { mCurToken.get() };
logWarningErrorToConsole(nsIScriptError::warningFlag, "notSupportingDirective",
params, ArrayLength(params));
@ -933,13 +933,13 @@ nsCSPParser::directiveName()
// Make sure the directive does not already exist
// (see http://www.w3.org/TR/CSP11/#parsing)
if (mPolicy->directiveExists(CSP_DirectiveToEnum(mCurToken))) {
if (mPolicy->hasDirective(CSP_StringToCSPDirective(mCurToken))) {
const char16_t* params[] = { mCurToken.get() };
logWarningErrorToConsole(nsIScriptError::warningFlag, "duplicateDirective",
params, ArrayLength(params));
return nullptr;
}
return new nsCSPDirective(CSP_DirectiveToEnum(mCurToken));
return new nsCSPDirective(CSP_StringToCSPDirective(mCurToken));
}
// directive = *WSP [ directive-name [ WSP directive-value ] ]
@ -1039,7 +1039,7 @@ nsCSPParser::parseContentSecurityPolicy(const nsAString& aPolicyString,
// Check that report-only policies define a report-uri, otherwise log warning.
if (aReportOnly) {
policy->setReportOnlyFlag(true);
if (!policy->directiveExists(CSP_REPORT_URI)) {
if (!policy->hasDirective(nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE)) {
nsAutoCString prePath;
nsresult rv = aSelfURI->GetPrePath(prePath);
NS_ENSURE_SUCCESS(rv, policy);

View File

@ -127,6 +127,59 @@ CSP_LogLocalizedStr(const char16_t* aName,
}
/* ===== Helpers ============================ */
CSPDirective
CSP_ContentTypeToDirective(nsContentPolicyType aType)
{
switch (aType) {
case nsIContentPolicy::TYPE_IMAGE:
case nsIContentPolicy::TYPE_IMAGESET:
return nsIContentSecurityPolicy::IMG_SRC_DIRECTIVE;
// BLock XSLT as script, see bug 910139
case nsIContentPolicy::TYPE_XSLT:
case nsIContentPolicy::TYPE_SCRIPT:
return nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE;
case nsIContentPolicy::TYPE_STYLESHEET:
return nsIContentSecurityPolicy::STYLE_SRC_DIRECTIVE;
case nsIContentPolicy::TYPE_FONT:
return nsIContentSecurityPolicy::FONT_SRC_DIRECTIVE;
case nsIContentPolicy::TYPE_MEDIA:
return nsIContentSecurityPolicy::MEDIA_SRC_DIRECTIVE;
// TYPE_DOCUMENT shouldn't be used since it's specifically whitelisted by
// the CSPService, but in case we do want to know which directive to check,
// FRAME_SRC is the best fit.
case nsIContentPolicy::TYPE_DOCUMENT:
case nsIContentPolicy::TYPE_SUBDOCUMENT:
return nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE;
case nsIContentPolicy::TYPE_WEBSOCKET:
case nsIContentPolicy::TYPE_XMLHTTPREQUEST:
case nsIContentPolicy::TYPE_BEACON:
case nsIContentPolicy::TYPE_FETCH:
return nsIContentSecurityPolicy::CONNECT_SRC_DIRECTIVE;
case nsIContentPolicy::TYPE_OBJECT:
case nsIContentPolicy::TYPE_OBJECT_SUBREQUEST:
return nsIContentSecurityPolicy::OBJECT_SRC_DIRECTIVE;
case nsIContentPolicy::TYPE_XBL:
case nsIContentPolicy::TYPE_PING:
case nsIContentPolicy::TYPE_DTD:
case nsIContentPolicy::TYPE_OTHER:
return nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE;
// CSP can not block csp reports, fall through to error
case nsIContentPolicy::TYPE_CSP_REPORT:
// Fall through to error for all other directives
default:
MOZ_ASSERT(false, "Can not map nsContentPolicyType to CSPDirective");
}
return nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE;
}
nsCSPHostSrc*
CSP_CreateHostSrcFromURI(nsIURI* aURI)
@ -155,11 +208,9 @@ CSP_CreateHostSrcFromURI(nsIURI* aURI)
bool
CSP_IsValidDirective(const nsAString& aDir)
{
static_assert(CSP_LAST_DIRECTIVE_VALUE ==
(sizeof(CSPStrDirectives) / sizeof(CSPStrDirectives[0])),
"CSP_LAST_DIRECTIVE_VALUE does not match length of CSPStrDirectives");
uint32_t numDirs = (sizeof(CSPStrDirectives) / sizeof(CSPStrDirectives[0]));
for (uint32_t i = 0; i < CSP_LAST_DIRECTIVE_VALUE; i++) {
for (uint32_t i = 0; i < numDirs; i++) {
if (aDir.LowerCaseEqualsASCII(CSPStrDirectives[i])) {
return true;
}
@ -167,9 +218,9 @@ CSP_IsValidDirective(const nsAString& aDir)
return false;
}
bool
CSP_IsDirective(const nsAString& aValue, enum CSPDirective aDir)
CSP_IsDirective(const nsAString& aValue, CSPDirective aDir)
{
return aValue.LowerCaseEqualsASCII(CSP_EnumToDirective(aDir));
return aValue.LowerCaseEqualsASCII(CSP_CSPDirectiveToString(aDir));
}
bool
@ -470,7 +521,7 @@ nsCSPHostSrc::appendPath(const nsAString& aPath)
/* ===== nsCSPKeywordSrc ===================== */
nsCSPKeywordSrc::nsCSPKeywordSrc(CSPKeyword aKeyword)
nsCSPKeywordSrc::nsCSPKeywordSrc(enum CSPKeyword aKeyword)
{
NS_ASSERTION((aKeyword != CSP_SELF),
"'self' should have been replaced in the parser");
@ -624,7 +675,7 @@ nsCSPReportURI::toString(nsAString& outStr) const
/* ===== nsCSPDirective ====================== */
nsCSPDirective::nsCSPDirective(enum CSPDirective aDirective)
nsCSPDirective::nsCSPDirective(CSPDirective aDirective)
{
mDirective = aDirective;
}
@ -680,7 +731,7 @@ void
nsCSPDirective::toString(nsAString& outStr) const
{
// Append directive name
outStr.AppendASCII(CSP_EnumToDirective(mDirective));
outStr.AppendASCII(CSP_CSPDirectiveToString(mDirective));
outStr.AppendASCII(" ");
// Append srcs
@ -693,62 +744,6 @@ nsCSPDirective::toString(nsAString& outStr) const
}
}
enum CSPDirective
CSP_ContentTypeToDirective(nsContentPolicyType aType)
{
switch (aType) {
case nsIContentPolicy::TYPE_IMAGE:
case nsIContentPolicy::TYPE_IMAGESET:
return CSP_IMG_SRC;
case nsIContentPolicy::TYPE_SCRIPT:
return CSP_SCRIPT_SRC;
case nsIContentPolicy::TYPE_STYLESHEET:
return CSP_STYLE_SRC;
case nsIContentPolicy::TYPE_FONT:
return CSP_FONT_SRC;
case nsIContentPolicy::TYPE_MEDIA:
return CSP_MEDIA_SRC;
case nsIContentPolicy::TYPE_SUBDOCUMENT:
return CSP_FRAME_SRC;
// BLock XSLT as script, see bug 910139
case nsIContentPolicy::TYPE_XSLT:
return CSP_SCRIPT_SRC;
// TODO(sid): fix this mapping to be more precise (bug 999656)
case nsIContentPolicy::TYPE_DOCUMENT:
return CSP_FRAME_ANCESTORS;
case nsIContentPolicy::TYPE_WEBSOCKET:
case nsIContentPolicy::TYPE_XMLHTTPREQUEST:
case nsIContentPolicy::TYPE_BEACON:
case nsIContentPolicy::TYPE_FETCH:
return CSP_CONNECT_SRC;
case nsIContentPolicy::TYPE_OBJECT:
case nsIContentPolicy::TYPE_OBJECT_SUBREQUEST:
return CSP_OBJECT_SRC;
case nsIContentPolicy::TYPE_XBL:
case nsIContentPolicy::TYPE_PING:
case nsIContentPolicy::TYPE_DTD:
case nsIContentPolicy::TYPE_OTHER:
return CSP_DEFAULT_SRC;
// CSP can not block csp reports, fall through to error
case nsIContentPolicy::TYPE_CSP_REPORT:
// Fall through to error for all other directives
default:
NS_ASSERTION(false, "Can not map nsContentPolicyType to CSPDirective");
}
return CSP_DEFAULT_SRC;
}
bool
nsCSPDirective::restrictsContentType(nsContentPolicyType aContentType) const
{
@ -762,7 +757,7 @@ nsCSPDirective::restrictsContentType(nsContentPolicyType aContentType) const
void
nsCSPDirective::getReportURIs(nsTArray<nsString> &outReportURIs) const
{
NS_ASSERTION((mDirective == CSP_REPORT_URI), "not a report-uri directive");
NS_ASSERTION((mDirective == nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE), "not a report-uri directive");
// append uris
nsString tmpReportURI;
@ -791,18 +786,28 @@ nsCSPPolicy::~nsCSPPolicy()
}
bool
nsCSPPolicy::permits(nsContentPolicyType aContentType,
nsCSPPolicy::permits(CSPDirective aDir,
nsIURI* aUri,
bool aSpecific) const
{
nsString outp;
return this->permits(aDir, aUri, EmptyString(), false, aSpecific, outp);
}
bool
nsCSPPolicy::permits(CSPDirective aDir,
nsIURI* aUri,
const nsAString& aNonce,
bool aWasRedirected,
bool aSpecific,
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()));
CSPUTILSLOG(("nsCSPPolicy::permits, aUri: %s, aDir: %d, aSpecific: %s",
spec.get(), aDir, aSpecific ? "true" : "false"));
}
#endif
@ -810,11 +815,10 @@ nsCSPPolicy::permits(nsContentPolicyType aContentType,
nsCSPDirective* defaultDir = nullptr;
// Try to find a relevant directive
// 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]->equals(aDir)) {
if (!mDirectives[i]->permits(aUri, aNonce, aWasRedirected)) {
mDirectives[i]->toString(outViolatedDirective);
return false;
@ -826,16 +830,9 @@ nsCSPPolicy::permits(nsContentPolicyType aContentType,
}
}
// If [frame-ancestors] is not listed explicitly then default to true
// without consulting [default-src]
// 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 (!aSpecific && defaultDir) {
if (!defaultDir->permits(aUri, aNonce, aWasRedirected)) {
defaultDir->toString(outViolatedDirective);
return false;
@ -843,56 +840,8 @@ nsCSPPolicy::permits(nsContentPolicyType aContentType,
return true;
}
// unspecified default-src should default to no restrictions
// see bug 764937
return true;
}
bool
nsCSPPolicy::permitsBaseURI(nsIURI* aUri) const
{
#ifdef PR_LOGGING
{
nsAutoCString spec;
aUri->GetSpec(spec);
CSPUTILSLOG(("nsCSPPolicy::permitsBaseURI, aUri: %s", spec.get()));
}
#endif
// Try to find a base-uri directive
for (uint32_t i = 0; i < mDirectives.Length(); i++) {
if (mDirectives[i]->equals(CSP_BASE_URI)) {
return mDirectives[i]->permits(aUri);
}
}
// base-uri is only enforced if explicitly defined in the
// policy - do *not* consult default-src, see:
// http://www.w3.org/TR/CSP11/#directive-default-src
return true;
}
bool
nsCSPPolicy::permitsFormAction(nsIURI* aUri) const
{
#ifdef PR_LOGGING
{
nsAutoCString spec;
aUri->GetSpec(spec);
CSPUTILSLOG(("nsCSPPolicy::permitsFormAction, aUri: %s", spec.get()));
}
#endif
// Try to find a form-action directive
for (uint32_t i = 0; i < mDirectives.Length(); i++) {
if (mDirectives[i]->equals(CSP_FORM_ACTION)) {
return mDirectives[i]->permits(aUri);
}
}
// form-action is only enforced if explicitly defined in the
// policy - do *not* consult default-src, see:
// http://www.w3.org/TR/CSP2/#directive-default-src
// Nothing restricts this, so we're allowing the load
// See bug 764937
return true;
}
@ -958,7 +907,7 @@ nsCSPPolicy::toString(nsAString& outStr) const
}
bool
nsCSPPolicy::directiveExists(enum CSPDirective aDir) const
nsCSPPolicy::hasDirective(CSPDirective aDir) const
{
for (uint32_t i = 0; i < mDirectives.Length(); i++) {
if (mDirectives[i]->equals(aDir)) {
@ -999,7 +948,7 @@ nsCSPPolicy::getDirectiveStringForContentType(nsContentPolicyType aContentType,
}
void
nsCSPPolicy::getDirectiveAsString(enum CSPDirective aDir, nsAString& outDirective) const
nsCSPPolicy::getDirectiveAsString(CSPDirective aDir, nsAString& outDirective) const
{
for (uint32_t i = 0; i < mDirectives.Length(); i++) {
if (mDirectives[i]->equals(aDir)) {
@ -1013,7 +962,7 @@ void
nsCSPPolicy::getReportURIs(nsTArray<nsString>& outReportURIs) const
{
for (uint32_t i = 0; i < mDirectives.Length(); i++) {
if (mDirectives[i]->equals(CSP_REPORT_URI)) {
if (mDirectives[i]->equals(nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE)) {
mDirectives[i]->getReportURIs(outReportURIs);
return;
}

View File

@ -8,6 +8,7 @@
#include "nsCOMPtr.h"
#include "nsIContentPolicy.h"
#include "nsIContentSecurityPolicy.h"
#include "nsIURI.h"
#include "nsString.h"
#include "nsTArray.h"
@ -54,72 +55,45 @@ void CSP_LogMessage(const nsAString& aMessage,
#define SCRIPT_HASH_VIOLATION_OBSERVER_TOPIC "Inline Script had invalid hash"
#define STYLE_HASH_VIOLATION_OBSERVER_TOPIC "Inline Style had invalid hash"
// Please add any new enum items not only to CSPDirective, but also add
// a string version for every enum >> using the same index << to
// CSPStrDirectives underneath.
enum CSPDirective {
CSP_DEFAULT_SRC = 0,
CSP_SCRIPT_SRC,
CSP_OBJECT_SRC,
CSP_STYLE_SRC,
CSP_IMG_SRC,
CSP_MEDIA_SRC,
CSP_FRAME_SRC,
CSP_FONT_SRC,
CSP_CONNECT_SRC,
CSP_REPORT_URI,
CSP_FRAME_ANCESTORS,
CSP_REFLECTED_XSS,
CSP_BASE_URI,
CSP_FORM_ACTION,
// CSP_LAST_DIRECTIVE_VALUE always needs to be the last element in the enum
// because we use it to calculate the size for the char* array.
CSP_LAST_DIRECTIVE_VALUE
};
// these strings map to the CSPDirectives in nsIContentSecurityPolicy
// NOTE: When implementing a new directive, you will need to add it here but also
// add a corresponding entry to the constants in nsIContentSecurityPolicy.idl
static const char* CSPStrDirectives[] = {
"default-src", // CSP_DEFAULT_SRC = 0
"script-src", // CSP_SCRIPT_SRC
"object-src", // CSP_OBJECT_SRC
"style-src", // CSP_STYLE_SRC
"img-src", // CSP_IMG_SRC
"media-src", // CSP_MEDIA_SRC
"frame-src", // CSP_FRAME_SRC
"font-src", // CSP_FONT_SRC
"connect-src", // CSP_CONNECT_SRC
"report-uri", // CSP_REPORT_URI
"frame-ancestors", // CSP_FRAME_ANCESTORS
"reflected-xss", // CSP_REFLECTED_XSS
"base-uri", // CSP_BASE_URI
"form-action" // CSP_FORM_ACTION
"-error-", // NO_DIRECTIVE
"default-src", // DEFAULT_SRC_DIRECTIVE
"script-src", // SCRIPT_SRC_DIRECTIVE
"object-src", // OBJECT_SRC_DIRECTIVE
"style-src", // STYLE_SRC_DIRECTIVE
"img-src", // IMG_SRC_DIRECTIVE
"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
};
inline const char* CSP_EnumToDirective(enum CSPDirective aDir)
inline const char* CSP_CSPDirectiveToString(CSPDirective aDir)
{
// Make sure all elements in enum CSPDirective got added to CSPStrDirectives.
static_assert((sizeof(CSPStrDirectives) / sizeof(CSPStrDirectives[0]) ==
static_cast<uint32_t>(CSP_LAST_DIRECTIVE_VALUE)),
"CSP_LAST_DIRECTIVE_VALUE does not match length of CSPStrDirectives");
return CSPStrDirectives[static_cast<uint32_t>(aDir)];
}
inline CSPDirective CSP_DirectiveToEnum(const nsAString& aDir)
inline CSPDirective CSP_StringToCSPDirective(const nsAString& aDir)
{
nsString lowerDir = PromiseFlatString(aDir);
ToLowerCase(lowerDir);
static_assert(CSP_LAST_DIRECTIVE_VALUE ==
(sizeof(CSPStrDirectives) / sizeof(CSPStrDirectives[0])),
"CSP_LAST_DIRECTIVE_VALUE does not match length of CSPStrDirectives");
for (uint32_t i = 0; i < CSP_LAST_DIRECTIVE_VALUE; i++) {
uint32_t numDirs = (sizeof(CSPStrDirectives) / sizeof(CSPStrDirectives[0]));
for (uint32_t i = 1; i < numDirs; i++) {
if (lowerDir.EqualsASCII(CSPStrDirectives[i])) {
return static_cast<CSPDirective>(i);
}
}
NS_ASSERTION(false, "Can not convert unknown Directive to Enum");
return CSP_LAST_DIRECTIVE_VALUE;
NS_ASSERTION(false, "Can not convert unknown Directive to Integer");
return nsIContentSecurityPolicy::NO_DIRECTIVE;
}
// Please add any new enum items not only to CSPKeyword, but also add
@ -182,9 +156,11 @@ class nsCSPHostSrc;
nsCSPHostSrc* CSP_CreateHostSrcFromURI(nsIURI* aURI);
bool CSP_IsValidDirective(const nsAString& aDir);
bool CSP_IsDirective(const nsAString& aValue, enum CSPDirective aDir);
bool CSP_IsDirective(const nsAString& aValue, CSPDirective aDir);
bool CSP_IsKeyword(const nsAString& aValue, enum CSPKeyword aKey);
bool CSP_IsQuotelessKeyword(const nsAString& aKey);
CSPDirective CSP_ContentTypeToDirective(nsContentPolicyType aType);
/* =============== nsCSPSrc ================== */
@ -296,7 +272,7 @@ class nsCSPReportURI : public nsCSPBaseSrc {
class nsCSPDirective {
public:
nsCSPDirective();
explicit nsCSPDirective(enum CSPDirective aDirective);
explicit nsCSPDirective(CSPDirective aDirective);
virtual ~nsCSPDirective();
bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected) const;
@ -310,9 +286,9 @@ class nsCSPDirective {
bool restrictsContentType(nsContentPolicyType aContentType) const;
inline bool isDefaultDirective() const
{ return mDirective == CSP_DEFAULT_SRC; }
{ return mDirective == nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE; }
inline bool equals(enum CSPDirective aDirective) const
inline bool equals(CSPDirective aDirective) const
{ return (mDirective == aDirective); }
void getReportURIs(nsTArray<nsString> &outReportURIs) const;
@ -329,13 +305,15 @@ class nsCSPPolicy {
nsCSPPolicy();
virtual ~nsCSPPolicy();
bool permits(nsContentPolicyType aContentType,
bool permits(CSPDirective aDirective,
nsIURI* aUri,
const nsAString& aNonce,
bool aWasRedirected,
bool aSpecific,
nsAString& outViolatedDirective) const;
bool permitsBaseURI(nsIURI* aUri) const;
bool permitsFormAction(nsIURI* aUri) const;
bool permits(CSPDirective aDir,
nsIURI* aUri,
bool aSpecific) const;
bool allows(nsContentPolicyType aContentType,
enum CSPKeyword aKeyword,
const nsAString& aHashOrNonce) const;
@ -346,7 +324,7 @@ class nsCSPPolicy {
inline void addDirective(nsCSPDirective* aDir)
{ mDirectives.AppendElement(aDir); }
bool directiveExists(enum CSPDirective aDir) const;
bool hasDirective(CSPDirective aDir) const;
inline void setReportOnlyFlag(bool aFlag)
{ mReportOnly = aFlag; }
@ -359,7 +337,7 @@ class nsCSPPolicy {
void getDirectiveStringForContentType(nsContentPolicyType aContentType,
nsAString& outDirective) const;
void getDirectiveAsString(enum CSPDirective aDir, nsAString& outDirective) const;
void getDirectiveAsString(CSPDirective aDir, nsAString& outDirective) const;
inline uint32_t getNumDirectives() const
{ return mDirectives.Length(); }