bug 836922 - support mulitiple CSP policies at the same time. r=jst,grobinson

This commit is contained in:
Sid Stamm 2013-09-12 09:25:32 -07:00
parent c17399e56b
commit 3004080c1a
11 changed files with 585 additions and 548 deletions

View File

@ -10,10 +10,12 @@ interface nsIDocShell;
/**
* nsIContentSecurityPolicy
* Describes an XPCOM component used to model an enforce CSPs.
* 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.
*/
[scriptable, uuid(230b126d-afc3-4588-9794-3e135594d626)]
[scriptable, uuid(e5020ec3-1437-46f5-b4eb-8b60766d02c0)]
interface nsIContentSecurityPolicy : nsISupports
{
@ -25,61 +27,87 @@ interface nsIContentSecurityPolicy : nsISupports
attribute boolean isInitialized;
/**
* When set to true, content load-blocking and fail-closed are disabled: CSP
* will ONLY send reports, and not modify behavior.
* Accessor method for a read-only string version of the policy at a given
* index.
*/
attribute boolean reportOnlyMode;
AString getPolicy(in unsigned long index);
/**
* A read-only string version of the policy for debugging.
* Returns the number of policies attached to this CSP instance. Useful with
* getPolicy().
*/
readonly attribute AString policy;
attribute long policyCount;
/**
* 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
* String representation of the policy (e.g., header value)
* @param selfURI
* the URI of the protected document/principal
* @param reportOnly
* Should this policy affect content, script and style processing or
* just send reports if it is violated?
* @param specCompliant
* Whether or not the policy conforms to the W3C specification.
* If this is false, that indicates this policy is from the older
* implementation with different semantics and directive names.
*/
void appendPolicy(in AString policyString, in nsIURI selfURI,
in boolean reportOnly, in boolean specCompliant);
/**
* Whether this policy allows in-page script.
* @param shouldReportViolation
* @param shouldReportViolations
* Whether or not the use of inline script should be reported.
* This function always returns "true" for report-only policies, but when
* the report-only policy is violated, shouldReportViolation is true as
* well.
* any policy (report-only or otherwise) is violated,
* shouldReportViolations is true as well.
* @return
* Whether or not the effects of the inline script should be allowed
* (block the compilation if false).
*/
boolean getAllowsInlineScript(out boolean shouldReportViolation);
boolean getAllowsInlineScript(out boolean shouldReportViolations);
/**
* whether this policy allows eval and eval-like functions
* such as setTimeout("code string", time).
* @param shouldReportViolation
* @param shouldReportViolations
* Whether or not the use of eval should be reported.
* This function always returns "true" for report-only policies, but when
* the report-only policy is violated, shouldReportViolation is true as
* well.
* This function returns "true" when violating report-only policies, but
* when any policy (report-only or otherwise) is violated,
* shouldReportViolations is true as well.
* @return
* Whether or not the effects of the eval call should be allowed
* (block the call if false).
*/
boolean getAllowsEval(out boolean shouldReportViolation);
boolean getAllowsEval(out boolean shouldReportViolations);
/**
* Whether this policy allows in-page styles.
* This includes <style> tags with text content and style="" attributes in
* HTML elements.
* @param shouldReportViolation
* Whether or not the use of eval should be reported.
* This function always returns "true" for report-only policies, but when
* the report-only policy is violated, shouldReportViolation is true as
* well.
* @param shouldReportViolations
* Whether or not the use of inline style should be reported.
* If there are report-only policies, this function may return true
* (don't block), but one or more policy may still want to send
* violation reports so shouldReportViolations will be true even if the
* inline style should be permitted.
* @return
* Whether or not the effects of the eval call should be allowed
* (block the call if false).
* Whether or not the effects of the inline style should be allowed
* (block the rules if false).
*/
boolean getAllowsInlineStyle(out boolean shouldReportViolation);
boolean getAllowsInlineStyle(out boolean shouldReportViolations);
/**
* Log policy violation on the Error Console and send a report if a report-uri
* is present in the policy
* For each violated policy (of type violationType), log policy violation on
* the Error Console and send a report to report-uris present in the violated
* policies.
*
* @param violationType
* one of the VIOLATION_TYPE_* constants, e.g. inline-script or eval
@ -99,47 +127,24 @@ interface nsIContentSecurityPolicy : nsISupports
const unsigned short VIOLATION_TYPE_EVAL = 2;
const unsigned short VIOLATION_TYPE_INLINE_STYLE = 3;
/**
* Manually triggers violation report sending given a URI and reason.
* The URI may be null, in which case "self" is sent.
* @param blockedURI
* the URI that violated the policy
* @param violatedDirective
* the directive that was violated.
* @param scriptSample
* a sample of the violating inline script
* @param lineNum
* source line number of the violation (if available)
* @return
* nothing.
*/
void sendReports(in AString blockedURI,
in AString violatedDirective,
in AString scriptSample,
in int32_t lineNum);
/**
* Called after the CSP object is created to fill in the appropriate request
* and request header information needed in case a report needs to be sent.
*/
void scanRequestData(in nsIHttpChannel aChannel);
/**
* Updates the policy currently stored in the CSP to be "refined" or
* tightened by the one specified in the string policyString.
*/
void refinePolicy(in AString policyString, in nsIURI selfURI, in boolean specCompliant);
/**
* Verifies ancestry as permitted by the policy.
*
* Calls to this may trigger violation reports when queried, so
* this value should not be cached.
* NOTE: Calls to this may trigger violation reports when queried, so this
* value should not be cached.
*
* @param docShell
* containing the protected resource
* @return
* true if the frame's ancestors are all permitted by policy
* true if the frame's ancestors are all allowed by policy (except for
* report-only policies, which will send reports and then return true
* here when violated).
*/
boolean permitsAncestry(in nsIDocShell docShell);
@ -151,11 +156,11 @@ interface nsIContentSecurityPolicy : nsISupports
* Calls to this may trigger violation reports when queried, so
* this value should not be cached.
*/
short shouldLoad(in unsigned long aContentType,
in nsIURI aContentLocation,
in nsIURI aRequestOrigin,
in nsISupports aContext,
in ACString aMimeTypeGuess,
short shouldLoad(in unsigned long aContentType,
in nsIURI aContentLocation,
in nsIURI aRequestOrigin,
in nsISupports aContext,
in ACString aMimeTypeGuess,
in nsISupports aExtra);
/**
@ -163,10 +168,10 @@ interface nsIContentSecurityPolicy : nsISupports
* document are being processed. Given a bit of information about the request,
* decides whether or not the policy is satisfied.
*/
short shouldProcess(in unsigned long aContentType,
in nsIURI aContentLocation,
in nsIURI aRequestOrigin,
in nsISupports aContext,
short shouldProcess(in unsigned long aContentType,
in nsIURI aContentLocation,
in nsIURI aRequestOrigin,
in nsISupports aContext,
in ACString aMimeType,
in nsISupports aExtra);

View File

@ -108,7 +108,7 @@ this.CSPdebug = function CSPdebug(aMsg) {
}
// Callback to resume a request once the policy-uri has been fetched
function CSPPolicyURIListener(policyURI, docRequest, csp) {
function CSPPolicyURIListener(policyURI, docRequest, csp, reportOnly) {
this._policyURI = policyURI; // location of remote policy
this._docRequest = docRequest; // the parent document request
this._csp = csp; // parent document's CSP
@ -116,6 +116,7 @@ function CSPPolicyURIListener(policyURI, docRequest, csp) {
this._wrapper = null; // nsIScriptableInputStream
this._docURI = docRequest.QueryInterface(Ci.nsIChannel)
.URI; // parent document URI (to be used as 'self')
this._reportOnly = reportOnly;
}
CSPPolicyURIListener.prototype = {
@ -147,8 +148,8 @@ CSPPolicyURIListener.prototype = {
if (Components.isSuccessCode(status)) {
// send the policy we received back to the parent document's CSP
// for parsing
this._csp.refinePolicy(this._policy, this._docURI,
this._csp._specCompliant);
this._csp.appendPolicy(this._policy, this._docURI,
this._reportOnly, this._csp._specCompliant);
}
else {
// problem fetching policy so fail closed
@ -177,6 +178,7 @@ this.CSPRep = function CSPRep(aSpecCompliant) {
this._allowEval = false;
this._allowInlineScripts = false;
this._reportOnlyMode = false;
// don't auto-populate _directives, so it is easier to find bugs
this._directives = {};
@ -244,12 +246,14 @@ CSPRep.ALLOW_DIRECTIVE = "allow";
* @returns
* an instance of CSPRep
*/
CSPRep.fromString = function(aStr, self, docRequest, csp) {
CSPRep.fromString = function(aStr, self, docRequest, csp, reportOnly) {
if (typeof reportOnly === 'undefined') reportOnly = false;
var SD = CSPRep.SRC_DIRECTIVES_OLD;
var UD = CSPRep.URI_DIRECTIVES;
var aCSPR = new CSPRep();
aCSPR._originalText = aStr;
aCSPR._innerWindowID = innerWindowFromRequest(docRequest);
aCSPR._reportOnlyMode = reportOnly;
var selfUri = null;
if (self instanceof Ci.nsIURI) {
@ -451,7 +455,7 @@ CSPRep.fromString = function(aStr, self, docRequest, csp) {
// policy-uri can't be abused for CSRF
chan.loadFlags |= Ci.nsIChannel.LOAD_ANONYMOUS;
chan.loadGroup = docRequest.loadGroup;
chan.asyncOpen(new CSPPolicyURIListener(uri, docRequest, csp), null);
chan.asyncOpen(new CSPPolicyURIListener(uri, docRequest, csp, reportOnly), null);
}
catch (e) {
// resume the document request and apply most restrictive policy
@ -499,12 +503,15 @@ CSPRep.fromString = function(aStr, self, docRequest, csp) {
*/
// When we deprecate our original CSP implementation, we rename this to
// CSPRep.fromString and remove the existing CSPRep.fromString above.
CSPRep.fromStringSpecCompliant = function(aStr, self, docRequest, csp) {
CSPRep.fromStringSpecCompliant = function(aStr, self, docRequest, csp, reportOnly) {
if (typeof reportOnly === 'undefined') reportOnly = false;
var SD = CSPRep.SRC_DIRECTIVES_NEW;
var UD = CSPRep.URI_DIRECTIVES;
var aCSPR = new CSPRep(true);
aCSPR._originalText = aStr;
aCSPR._innerWindowID = innerWindowFromRequest(docRequest);
aCSPR._reportOnlyMode = reportOnly;
var selfUri = null;
if (self instanceof Ci.nsIURI) {

View File

@ -44,21 +44,15 @@ Cu.import("resource://gre/modules/CSPUtils.jsm");
function ContentSecurityPolicy() {
CSPdebug("CSP CREATED");
this._isInitialized = false;
this._reportOnlyMode = false;
this._policy = CSPRep.fromString("default-src *");
// default options "wide open" since this policy will be intersected soon
this._policy._allowInlineScripts = true;
this._policy._allowInlineStyles = true;
this._policy._allowEval = true;
this._policies = [];
this._request = "";
this._requestOrigin = "";
this._requestPrincipal = "";
this._referrer = "";
this._docRequest = null;
CSPdebug("CSP POLICY INITED TO 'default-src *'");
CSPdebug("CSP object initialized, no policies to enforce yet");
this._cache = { };
}
@ -135,45 +129,68 @@ ContentSecurityPolicy.prototype = {
this._isInitialized = foo;
},
get policy () {
return this._policy.toString();
},
getAllowsInlineScript: function(shouldReportViolation) {
// report it?
shouldReportViolation.value = !this._policy.allowsInlineScripts;
// allow it to execute?
return this._reportOnlyMode || this._policy.allowsInlineScripts;
},
getAllowsEval: function(shouldReportViolation) {
// report it?
shouldReportViolation.value = !this._policy.allowsEvalInScripts;
// allow it to execute?
return this._reportOnlyMode || this._policy.allowsEvalInScripts;
},
getAllowsInlineStyle: function(shouldReportViolation) {
// report it?
shouldReportViolation.value = !this._policy.allowsInlineStyles;
// allow it to execute?
return this._reportOnlyMode || this._policy.allowsInlineStyles;
_getPolicyInternal: function(index) {
if (index < 0 || index >= this._policies.length) {
throw Cr.NS_ERROR_FAILURE;
}
return this._policies[index];
},
_buildViolatedDirectiveString:
function(aDirectiveName) {
function(aDirectiveName, aPolicy) {
var SD = CSPRep.SRC_DIRECTIVES_NEW;
var cspContext = (SD[aDirectiveName] in this._policy._directives) ? SD[aDirectiveName] : SD.DEFAULT_SRC;
var directive = this._policy._directives[cspContext];
var cspContext = (SD[aDirectiveName] in aPolicy._directives) ? SD[aDirectiveName] : SD.DEFAULT_SRC;
var directive = aPolicy._directives[cspContext];
return cspContext + ' ' + directive.toString();
},
/**
* Log policy violation on the Error Console and send a report if a report-uri
* is present in the policy
* Returns policy string representing the policy at "index".
*/
getPolicy: function(index) {
return this._getPolicyInternal(index).toString();
},
/**
* Returns count of policies.
*/
get numPolicies() {
return this._policies.length;
},
getAllowsInlineScript: function(shouldReportViolations) {
// report it? (for each policy, is it violated?)
shouldReportViolations.value = this._policies.some(function(a) { return !a.allowsInlineScripts; });
// allow it to execute? (Do all the policies allow it to execute)?
return this._policies.every(function(a) {
return a._reportOnlyMode || a.allowsInlineScripts;
});
},
getAllowsEval: function(shouldReportViolations) {
// report it? (for each policy, is it violated?)
shouldReportViolations.value = this._policies.some(function(a) { return !a.allowsEvalInScripts; });
// allow it to execute? (Do all the policies allow it to execute)?
return this._policies.every(function(a) {
return a._reportOnlyMode || a.allowsEvalInScripts;
});
},
getAllowsInlineStyle: function(shouldReportViolations) {
// report it? (for each policy, is it violated?)
shouldReportViolations.value = this._policies.some(function(a) { return !a.allowsInlineStyles; });
// allow it to execute? (Do all the policies allow it to execute)?
return this._policies.every(function(a) {
return a._reportOnlyMode || a.allowsInlineStyles;
});
},
/**
* For each policy, log any violation on the Error Console and send a report
* if a report-uri is present in the policy
*
* @param aViolationType
* one of the VIOLATION_TYPE_* constants, e.g. inline-script or eval
@ -185,51 +202,44 @@ ContentSecurityPolicy.prototype = {
* source line number of the violation (if available)
*/
logViolationDetails:
function(aViolationType, aSourceFile, aScriptSample, aLineNum) {
// allowsInlineScript and allowsEval both return true when report-only mode
// is enabled, resulting in a call to this function. Therefore we need to
// check that the policy was in fact violated before logging any violations
switch (aViolationType) {
case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_STYLE:
if (!this._policy.allowsInlineStyles) {
var violatedDirective = this._buildViolatedDirectiveString('STYLE_SRC');
this._asyncReportViolation('self', null, violatedDirective, INLINE_STYLE_VIOLATION_OBSERVER_SUBJECT,
aSourceFile, aScriptSample, aLineNum);
}
break;
case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_SCRIPT:
if (!this._policy.allowsInlineScripts) {
var violatedDirective = this._buildViolatedDirectiveString('SCRIPT_SRC');
this._asyncReportViolation('self', null, violatedDirective, INLINE_SCRIPT_VIOLATION_OBSERVER_SUBJECT,
function(aViolationType, aSourceFile, aScriptSample, aLineNum, violatedPolicyIndex) {
for (let policyIndex=0; policyIndex < this._policies.length; policyIndex++) {
let policy = this._policies[policyIndex];
// call-sites to the eval/inline checks recieve two return values: allows
// and violates. Policies that are report-only allow the
// loads/compilations but violations should still be reported. Not all
// policies in this nsIContentSecurityPolicy instance will be violated,
// which is why we must check again here.
switch (aViolationType) {
case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_STYLE:
if (!policy.allowsInlineStyles) {
var violatedDirective = this._buildViolatedDirectiveString('STYLE_SRC', policy);
this._asyncReportViolation('self', null, violatedDirective, policyIndex,
INLINE_STYLE_VIOLATION_OBSERVER_SUBJECT,
aSourceFile, aScriptSample, aLineNum);
}
break;
case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_EVAL:
if (!this._policy.allowsEvalInScripts) {
var violatedDirective = this._buildViolatedDirectiveString('SCRIPT_SRC');
this._asyncReportViolation('self', null, violatedDirective, EVAL_VIOLATION_OBSERVER_SUBJECT,
aSourceFile, aScriptSample, aLineNum);
break;
case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_SCRIPT:
if (!policy.allowsInlineScripts) {
var violatedDirective = this._buildViolatedDirectiveString('SCRIPT_SRC', policy);
this._asyncReportViolation('self', null, violatedDirective, policyIndex,
INLINE_SCRIPT_VIOLATION_OBSERVER_SUBJECT,
aSourceFile, aScriptSample, aLineNum);
}
break;
case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_EVAL:
if (!policy.allowsEvalInScripts) {
var violatedDirective = this._buildViolatedDirectiveString('SCRIPT_SRC', policy);
this._asyncReportViolation('self', null, violatedDirective, policyIndex,
EVAL_VIOLATION_OBSERVER_SUBJECT,
aSourceFile, aScriptSample, aLineNum);
}
break;
}
break;
}
},
set reportOnlyMode(val) {
this._reportOnlyMode = val;
},
get reportOnlyMode () {
return this._reportOnlyMode;
},
/*
// Having a setter is a bad idea... opens up the policy to "loosening"
// Instead, use "refinePolicy."
set policy (aStr) {
this._policy = CSPRep.fromString(aStr);
},
*/
/**
* Given an nsIHttpChannel, fill out the appropriate data.
*/
@ -265,26 +275,29 @@ ContentSecurityPolicy.prototype = {
/* ........ Methods .............. */
/**
* Given a new policy, intersects the currently enforced policy with the new
* one and stores the result. The effect is a "tightening" or refinement of
* an old policy. This is called any time a new policy is encountered and
* the effective policy has to be refined.
* Adds a new policy to our list of policies for this CSP context.
* @returns the count of policies.
*/
refinePolicy:
function csp_refinePolicy(aPolicy, selfURI, aSpecCompliant) {
CSPdebug("REFINE POLICY: " + aPolicy);
CSPdebug(" SELF: " + selfURI.asciiSpec);
appendPolicy:
function csp_appendPolicy(aPolicy, selfURI, aReportOnly, aSpecCompliant) {
#ifndef MOZ_B2G
CSPdebug("APPENDING POLICY: " + aPolicy);
CSPdebug(" SELF: " + selfURI.asciiSpec);
CSPdebug("CSP 1.0 COMPLIANT : " + aSpecCompliant);
#endif
// For nested schemes such as view-source: make sure we are taking the
// innermost URI to use as 'self' since that's where we will extract the
// scheme, host and port from
if (selfURI instanceof Ci.nsINestedURI) {
#ifndef MOZ_B2G
CSPdebug(" INNER: " + selfURI.innermostURI.asciiSpec);
#endif
selfURI = selfURI.innermostURI;
}
// stay uninitialized until policy merging is done
this._isInitialized = false;
// stay uninitialized until policy setup is done
var newpolicy;
// If there is a policy-uri, fetch the policy, then re-call this function.
// (1) parse and create a CSPRep object
@ -294,29 +307,37 @@ ContentSecurityPolicy.prototype = {
// If we want to be CSP 1.0 spec compliant, use the new parser.
// The old one will be deprecated in the future and will be
// removed at that time.
var newpolicy;
if (aSpecCompliant) {
newpolicy = CSPRep.fromStringSpecCompliant(aPolicy,
selfURI,
this._docRequest,
this);
this,
aReportOnly);
} else {
newpolicy = CSPRep.fromString(aPolicy,
selfURI,
this._docRequest,
this);
this,
aReportOnly);
}
// (2) Intersect the currently installed CSPRep object with the new one
var intersect = this._policy.intersectWith(newpolicy);
newpolicy._specCompliant = !!aSpecCompliant;
newpolicy._isInitialized = true;
this._policies.push(newpolicy);
this._cache = {}; // reset cache since effective policy changes
},
// (3) Save the result
this._policy = intersect;
this._policy._specCompliant = !!aSpecCompliant;
this._isInitialized = true;
this._cache = {};
/**
* Removes a policy from the array of policies.
*/
removePolicy:
function csp_removePolicy(index) {
if (index < 0 || index >= this._policies.length) {
CSPdebug("Cannot remove policy " + index + "; not enough policies.");
return;
}
this._policies.splice(index, 1);
this._cache = {}; // reset cache since effective policy changes
},
/**
@ -324,17 +345,29 @@ ContentSecurityPolicy.prototype = {
*/
sendReports:
function(blockedUri, originalUri, violatedDirective,
aSourceFile, aScriptSample, aLineNum) {
var uriString = this._policy.getReportURIs();
violatedPolicyIndex, aSourceFile,
aScriptSample, aLineNum) {
let policy = this._getPolicyInternal(violatedPolicyIndex);
if (!policy) {
CSPdebug("ERROR in SendReports: policy " + violatedPolicyIndex + " is not defined.");
return;
}
var uriString = policy.getReportURIs();
var uris = uriString.split(/\s+/);
if (uris.length > 0) {
// see if we need to sanitize the blocked-uri
let blocked = '';
if (originalUri) {
// We've redirected, only report the blocked origin
let clone = blockedUri.clone();
clone.path = '';
blocked = clone.asciiSpec;
try {
let clone = blockedUri.clone();
clone.path = '';
blocked = clone.asciiSpec;
} catch(e) {
CSPdebug(".... blockedUri can't be cloned: " + blockedUri);
}
}
else if (blockedUri instanceof Ci.nsIURI) {
blocked = blockedUri.cloneIgnoringRef().asciiSpec;
@ -400,7 +433,7 @@ ContentSecurityPolicy.prototype = {
// we need to set an nsIChannelEventSink on the channel object
// so we can tell it to not follow redirects when posting the reports
chan.notificationCallbacks = new CSPReportRedirectSink(this._policy);
chan.notificationCallbacks = new CSPReportRedirectSink(policy);
if (this._docRequest) {
chan.loadGroup = this._docRequest.loadGroup;
}
@ -435,8 +468,8 @@ ContentSecurityPolicy.prototype = {
} catch(e) {
// it's possible that the URI was invalid, just log a
// warning and skip over that.
this._policy.log(WARN_FLAG, CSPLocalizer.getFormatStr("triedToSendReport", [uris[i]]));
this._policy.log(WARN_FLAG, CSPLocalizer.getFormatStr("errorWas", [e.toString()]));
policy.log(WARN_FLAG, CSPLocalizer.getFormatStr("triedToSendReport", [uris[i]]));
policy.log(WARN_FLAG, CSPLocalizer.getFormatStr("errorWas", [e.toString()]));
}
}
}
@ -446,8 +479,9 @@ ContentSecurityPolicy.prototype = {
* Logs a meaningful CSP warning to the developer console.
*/
logToConsole:
function(blockedUri, originalUri, violatedDirective,
function(blockedUri, originalUri, violatedDirective, aViolatedPolicyIndex,
aSourceFile, aScriptSample, aLineNum, aObserverSubject) {
let policy = this._policies[aViolatedPolicyIndex];
switch(aObserverSubject.data) {
case INLINE_STYLE_VIOLATION_OBSERVER_SUBJECT:
violatedDirective = CSPLocalizer.getStr("inlineStyleBlocked");
@ -465,24 +499,37 @@ ContentSecurityPolicy.prototype = {
} else {
violationMessage = CSPLocalizer.getFormatStr("CSPViolation", [violatedDirective]);
}
this._policy.log(WARN_FLAG, violationMessage,
(aSourceFile) ? aSourceFile : null,
(aScriptSample) ? decodeURIComponent(aScriptSample) : null,
(aLineNum) ? aLineNum : null);
policy.log(WARN_FLAG, violationMessage,
(aSourceFile) ? aSourceFile : null,
(aScriptSample) ? decodeURIComponent(aScriptSample) : null,
(aLineNum) ? aLineNum : null);
},
/**
* Exposed Method to analyze docShell for approved frame ancestry.
* Also sends violation reports if necessary.
* NOTE: Also sends violation reports if necessary.
* @param docShell
* the docShell for this policy's resource.
* @return
* true if the frame ancestry is allowed by this policy.
* true if the frame ancestry is allowed by this policy and the load
* should progress.
*/
permitsAncestry:
function(docShell) {
// Cannot shortcut checking all the policies since violation reports have
// to be triggered if any policy wants it.
var permitted = true;
for (let i = 0; i < this._policies.length; i++) {
if (!this._permitsAncestryInternal(docShell, this._policies[i], i)) {
permitted = false;
}
}
return permitted;
},
_permitsAncestryInternal:
function(docShell, policy, policyIndex) {
if (!docShell) { return false; }
CSPdebug(" in permitsAncestry(), docShell = " + docShell);
// walk up this docShell tree until we hit chrome
var dst = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
@ -504,7 +551,9 @@ ContentSecurityPolicy.prototype = {
ancestor.userPass = '';
} catch (ex) {}
#ifndef MOZ_B2G
CSPdebug(" found frame ancestor " + ancestor.asciiSpec);
#endif
ancestors.push(ancestor);
}
}
@ -515,17 +564,16 @@ ContentSecurityPolicy.prototype = {
let cspContext = CSPRep.SRC_DIRECTIVES_NEW.FRAME_ANCESTORS;
for (let i in ancestors) {
let ancestor = ancestors[i];
if (!this._policy.permits(ancestor, cspContext)) {
if (!policy.permits(ancestor, cspContext)) {
// report the frame-ancestor violation
let directive = this._policy._directives[cspContext];
let violatedPolicy = (directive._isImplicit
? 'default-src' : 'frame-ancestors ')
+ directive.toString();
let directive = policy._directives[cspContext];
let violatedPolicy = 'frame-ancestors ' + directive.toString();
this._asyncReportViolation(ancestors[i], null, violatedPolicy);
this._asyncReportViolation(ancestors[i], null, violatedPolicy,
policyIndex);
// need to lie if we are testing in report-only mode
return this._reportOnlyMode;
return policy._reportOnlyMode;
}
}
return true;
@ -581,63 +629,82 @@ ContentSecurityPolicy.prototype = {
let cp = Ci.nsIContentPolicy;
// iterate through all the _policies and send reports where a policy is
// violated. After the check, determine the overall effect (blocked or
// loaded?) and cache it.
let policyAllowsLoadArray = [];
for (let policyIndex=0; policyIndex < this._policies.length; policyIndex++) {
let policy = this._policies[policyIndex];
#ifndef MOZ_B2G
CSPdebug("policy is " + (this._policy._specCompliant ?
"1.0 compliant" : "pre-1.0"));
CSPdebug("policy is " + (policy._specCompliant ?
"1.0 compliant" : "pre-1.0"));
CSPdebug("policy is " + (policy._reportOnlyMode ?
"report-only" : "blocking"));
#endif
if (aContentType == cp.TYPE_XMLHTTPREQUEST && this._policy._specCompliant) {
cspContext = ContentSecurityPolicy._MAPPINGS[CSP_TYPE_XMLHTTPREQUEST_SPEC_COMPLIANT];
} else if (aContentType == cp.TYPE_WEBSOCKET && this._policy._specCompliant) {
cspContext = ContentSecurityPolicy._MAPPINGS[CSP_TYPE_WEBSOCKET_SPEC_COMPLIANT];
} else {
cspContext = ContentSecurityPolicy._MAPPINGS[aContentType];
}
CSPdebug("shouldLoad cspContext = " + cspContext);
// if the mapping is null, there's no policy, let it through.
if (!cspContext) {
return Ci.nsIContentPolicy.ACCEPT;
}
// otherwise, honor the translation
// var source = aContentLocation.scheme + "://" + aContentLocation.hostPort;
var res = this._policy.permits(aContentLocation, cspContext)
? Ci.nsIContentPolicy.ACCEPT
: Ci.nsIContentPolicy.REJECT_SERVER;
// frame-ancestors is taken care of early on (as this document is loaded)
// If the result is *NOT* ACCEPT, then send report
if (res != Ci.nsIContentPolicy.ACCEPT) {
CSPdebug("blocking request for " + aContentLocation.asciiSpec);
try {
let directive = "unknown directive",
violatedPolicy = "unknown policy";
// The policy might not explicitly declare each source directive (so
// the cspContext may be implicit). If so, we have to report
// violations as appropriate: specific or the default-src directive.
if (this._policy._directives.hasOwnProperty(cspContext)) {
directive = this._policy._directives[cspContext];
violatedPolicy = cspContext + ' ' + directive.toString();
} else if (this._policy._directives.hasOwnProperty("default-src")) {
directive = this._policy._directives["default-src"];
violatedPolicy = "default-src " + directive.toString();
} else {
violatedPolicy = "unknown directive";
CSPdebug('ERROR in blocking content: ' +
'CSP is not sure which part of the policy caused this block');
}
this._asyncReportViolation(aContentLocation, aOriginalUri, violatedPolicy);
} catch(e) {
CSPdebug('---------------- ERROR: ' + e);
if (aContentType == cp.TYPE_XMLHTTPREQUEST && this._policies[policyIndex]._specCompliant) {
cspContext = ContentSecurityPolicy._MAPPINGS[CSP_TYPE_XMLHTTPREQUEST_SPEC_COMPLIANT];
} else if (aContentType == cp.TYPE_WEBSOCKET && this._policies[policyIndex]._specCompliant) {
cspContext = ContentSecurityPolicy._MAPPINGS[CSP_TYPE_WEBSOCKET_SPEC_COMPLIANT];
} else {
cspContext = ContentSecurityPolicy._MAPPINGS[aContentType];
}
}
let ret = (this._reportOnlyMode ? Ci.nsIContentPolicy.ACCEPT : res);
#ifndef MOZ_B2G
CSPdebug("shouldLoad cspContext = " + cspContext);
#endif
// if the mapping is null, there's no policy, let it through.
if (!cspContext) {
return Ci.nsIContentPolicy.ACCEPT;
}
// otherwise, honor the translation
// var source = aContentLocation.scheme + "://" + aContentLocation.hostPort;
var res = policy.permits(aContentLocation, cspContext)
? cp.ACCEPT : cp.REJECT_SERVER;
// record whether the thing should be blocked or just reported.
policyAllowsLoadArray.push(res == cp.ACCEPT || policy._reportOnlyMode);
// frame-ancestors is taken care of early on (as this document is loaded)
// If the result is *NOT* ACCEPT, then send report
if (res != Ci.nsIContentPolicy.ACCEPT) {
CSPdebug("blocking request for " + aContentLocation.asciiSpec);
try {
let directive = "unknown directive",
violatedPolicy = "unknown policy";
// The policy might not explicitly declare each source directive (so
// the cspContext may be implicit). If so, we have to report
// violations as appropriate: specific or the default-src directive.
if (policy._directives.hasOwnProperty(cspContext)) {
directive = policy._directives[cspContext];
violatedPolicy = cspContext + ' ' + directive.toString();
} else if (policy._directives.hasOwnProperty("default-src")) {
directive = policy._directives["default-src"];
violatedPolicy = "default-src " + directive.toString();
} else {
violatedPolicy = "unknown directive";
CSPdebug('ERROR in blocking content: ' +
'CSP is not sure which part of the policy caused this block');
}
this._asyncReportViolation(aContentLocation, aOriginalUri,
violatedPolicy, policyIndex);
} catch(e) {
CSPdebug('---------------- ERROR: ' + e);
}
}
} // end for-each loop over policies
// the ultimate decision is based on whether any policies want to reject
// the load. The array keeps track of whether the policies allowed the
// loads. If any doesn't, we'll reject the load (and cache the result).
let ret = (policyAllowsLoadArray.some(function(a,b) { return !a; }) ?
cp.REJECT_SERVER : cp.ACCEPT);
if (key) {
this._cache[key] = ret;
}
@ -669,6 +736,9 @@ ContentSecurityPolicy.prototype = {
* The original URI if the blocked content is a redirect, else null
* @param aViolatedDirective
* the directive that was violated (string).
* @param aViolatedPolicyIndex
* the index of the policy that was violated (so we know where to send
* the reports).
* @param aObserverSubject
* optional, subject sent to the nsIObservers listening to the CSP
* violation topic.
@ -680,7 +750,8 @@ ContentSecurityPolicy.prototype = {
* source line number of the violation (if available)
*/
_asyncReportViolation:
function(aBlockedContentSource, aOriginalUri, aViolatedDirective, aObserverSubject,
function(aBlockedContentSource, aOriginalUri, aViolatedDirective,
aViolatedPolicyIndex, aObserverSubject,
aSourceFile, aScriptSample, aLineNum) {
// if optional observerSubject isn't specified, default to the source of
// the violation.
@ -704,10 +775,11 @@ ContentSecurityPolicy.prototype = {
CSP_VIOLATION_TOPIC,
aViolatedDirective);
reportSender.sendReports(aBlockedContentSource, aOriginalUri,
aViolatedDirective,
aViolatedDirective, aViolatedPolicyIndex,
aSourceFile, aScriptSample, aLineNum);
reportSender.logToConsole(aBlockedContentSource, aOriginalUri,
aViolatedDirective, aSourceFile, aScriptSample,
aViolatedDirective, aViolatedPolicyIndex,
aSourceFile, aScriptSample,
aLineNum, aObserverSubject);
}, Ci.nsIThread.DISPATCH_NORMAL);

View File

@ -60,88 +60,97 @@ CSPService::ShouldLoad(uint32_t aContentType,
nsIPrincipal *aRequestPrincipal,
int16_t *aDecision)
{
if (!aContentLocation)
return NS_ERROR_FAILURE;
if (!aContentLocation)
return NS_ERROR_FAILURE;
#ifdef PR_LOGGING
{
nsAutoCString location;
aContentLocation->GetSpec(location);
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
("CSPService::ShouldLoad called for %s", location.get()));
}
#endif
// default decision, CSP can revise it if there's a policy to enforce
*aDecision = nsIContentPolicy::ACCEPT;
// No need to continue processing if CSP is disabled
if (!sCSPEnabled)
return NS_OK;
// shortcut for about: chrome: and resource: and javascript: uris since
// they're not subject to CSP content policy checks.
bool schemeMatch = false;
NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("about", &schemeMatch), NS_OK);
if (schemeMatch)
return NS_OK;
NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("chrome", &schemeMatch), NS_OK);
if (schemeMatch)
return NS_OK;
NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("resource", &schemeMatch), NS_OK);
if (schemeMatch)
return NS_OK;
NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("javascript", &schemeMatch), NS_OK);
if (schemeMatch)
return NS_OK;
// These content types are not subject to CSP content policy checks:
// TYPE_CSP_REPORT, TYPE_REFRESH, TYPE_DOCUMENT
// (their mappings are null in contentSecurityPolicy.js)
if (aContentType == nsIContentPolicy::TYPE_CSP_REPORT ||
aContentType == nsIContentPolicy::TYPE_REFRESH ||
aContentType == nsIContentPolicy::TYPE_DOCUMENT) {
return NS_OK;
}
// find the principal of the document that initiated this request and see
// if it has a CSP policy object
nsCOMPtr<nsINode> node(do_QueryInterface(aRequestContext));
nsCOMPtr<nsIPrincipal> principal;
nsCOMPtr<nsIContentSecurityPolicy> csp;
if (node) {
principal = node->NodePrincipal();
principal->GetCsp(getter_AddRefs(csp));
if (csp) {
#ifdef PR_LOGGING
nsAutoString policy;
csp->GetPolicy(policy);
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
("Document has CSP: %s",
NS_ConvertUTF16toUTF8(policy).get()));
#endif
// obtain the enforcement decision
// (don't pass aExtra, we use that slot for redirects)
csp->ShouldLoad(aContentType,
aContentLocation,
aRequestOrigin,
aRequestContext,
aMimeTypeGuess,
nullptr,
aDecision);
}
}
#ifdef PR_LOGGING
else {
nsAutoCString uriSpec;
aContentLocation->GetSpec(uriSpec);
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
("COULD NOT get nsINode for location: %s", uriSpec.get()));
}
{
nsAutoCString location;
aContentLocation->GetSpec(location);
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
("CSPService::ShouldLoad called for %s", location.get()));
}
#endif
// default decision, CSP can revise it if there's a policy to enforce
*aDecision = nsIContentPolicy::ACCEPT;
// No need to continue processing if CSP is disabled
if (!sCSPEnabled)
return NS_OK;
// shortcut for about: chrome: and resource: and javascript: uris since
// they're not subject to CSP content policy checks.
bool schemeMatch = false;
NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("about", &schemeMatch), NS_OK);
if (schemeMatch)
return NS_OK;
NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("chrome", &schemeMatch), NS_OK);
if (schemeMatch)
return NS_OK;
NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("resource", &schemeMatch), NS_OK);
if (schemeMatch)
return NS_OK;
NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("javascript", &schemeMatch), NS_OK);
if (schemeMatch)
return NS_OK;
// These content types are not subject to CSP content policy checks:
// TYPE_CSP_REPORT, TYPE_REFRESH, TYPE_DOCUMENT
// (their mappings are null in contentSecurityPolicy.js)
if (aContentType == nsIContentPolicy::TYPE_CSP_REPORT ||
aContentType == nsIContentPolicy::TYPE_REFRESH ||
aContentType == nsIContentPolicy::TYPE_DOCUMENT) {
return NS_OK;
}
// find the principal of the document that initiated this request and see
// if it has a CSP policy object
nsCOMPtr<nsINode> node(do_QueryInterface(aRequestContext));
nsCOMPtr<nsIPrincipal> principal;
nsCOMPtr<nsIContentSecurityPolicy> csp;
if (node) {
principal = node->NodePrincipal();
principal->GetCsp(getter_AddRefs(csp));
if (csp) {
#ifdef PR_LOGGING
{
int numPolicies = 0;
nsresult rv = csp->GetPolicyCount(&numPolicies);
if (NS_SUCCEEDED(rv)) {
for (int i=0; i<numPolicies; i++) {
nsAutoString policy;
csp->GetPolicy(i, policy);
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
("Document has CSP[%d]: %s", i,
NS_ConvertUTF16toUTF8(policy).get()));
}
}
}
#endif
// obtain the enforcement decision
// (don't pass aExtra, we use that slot for redirects)
csp->ShouldLoad(aContentType,
aContentLocation,
aRequestOrigin,
aRequestContext,
aMimeTypeGuess,
nullptr,
aDecision);
}
}
#ifdef PR_LOGGING
else {
nsAutoCString uriSpec;
aContentLocation->GetSpec(uriSpec);
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
("COULD NOT get nsINode for location: %s", uriSpec.get()));
}
#endif
return NS_OK;
}
NS_IMETHODIMP
@ -154,52 +163,60 @@ CSPService::ShouldProcess(uint32_t aContentType,
nsIPrincipal *aRequestPrincipal,
int16_t *aDecision)
{
if (!aContentLocation)
return NS_ERROR_FAILURE;
if (!aContentLocation)
return NS_ERROR_FAILURE;
// default decision is to accept the item
*aDecision = nsIContentPolicy::ACCEPT;
// default decision is to accept the item
*aDecision = nsIContentPolicy::ACCEPT;
// No need to continue processing if CSP is disabled
if (!sCSPEnabled)
return NS_OK;
// find the nsDocument that initiated this request and see if it has a
// CSP policy object
nsCOMPtr<nsINode> node(do_QueryInterface(aRequestContext));
nsCOMPtr<nsIPrincipal> principal;
nsCOMPtr<nsIContentSecurityPolicy> csp;
if (node) {
principal = node->NodePrincipal();
principal->GetCsp(getter_AddRefs(csp));
if (csp) {
#ifdef PR_LOGGING
nsAutoString policy;
csp->GetPolicy(policy);
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
("shouldProcess - document has policy: %s",
NS_ConvertUTF16toUTF8(policy).get()));
#endif
// obtain the enforcement decision
csp->ShouldProcess(aContentType,
aContentLocation,
aRequestOrigin,
aRequestContext,
aMimeTypeGuess,
aExtra,
aDecision);
}
}
#ifdef PR_LOGGING
else {
nsAutoCString uriSpec;
aContentLocation->GetSpec(uriSpec);
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
("COULD NOT get nsINode for location: %s", uriSpec.get()));
}
#endif
// No need to continue processing if CSP is disabled
if (!sCSPEnabled)
return NS_OK;
// find the nsDocument that initiated this request and see if it has a
// CSP policy object
nsCOMPtr<nsINode> node(do_QueryInterface(aRequestContext));
nsCOMPtr<nsIPrincipal> principal;
nsCOMPtr<nsIContentSecurityPolicy> csp;
if (node) {
principal = node->NodePrincipal();
principal->GetCsp(getter_AddRefs(csp));
if (csp) {
#ifdef PR_LOGGING
{
int numPolicies = 0;
nsresult rv = csp->GetPolicyCount(&numPolicies);
if (NS_SUCCEEDED(rv)) {
for (int i=0; i<numPolicies; i++) {
nsAutoString policy;
csp->GetPolicy(i, policy);
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
("shouldProcess - document has policy[%d]: %s", i,
NS_ConvertUTF16toUTF8(policy).get()));
}
}
}
#endif
// obtain the enforcement decision
csp->ShouldProcess(aContentType,
aContentLocation,
aRequestOrigin,
aRequestContext,
aMimeTypeGuess,
aExtra,
aDecision);
}
}
#ifdef PR_LOGGING
else {
nsAutoCString uriSpec;
aContentLocation->GetSpec(uriSpec);
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
("COULD NOT get nsINode for location: %s", uriSpec.get()));
}
#endif
return NS_OK;
}
/* nsIChannelEventSink implementation */

View File

@ -2458,6 +2458,30 @@ nsDocument::SendToConsole(nsCOMArray<nsISecurityConsoleMessage>& aMessages)
}
}
static nsresult
AppendCSPFromHeader(nsIContentSecurityPolicy* csp, const nsAString& aHeaderValue,
nsIURI* aSelfURI, bool aReportOnly, bool aSpecCompliant)
{
// 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, aSelfURI, aReportOnly, aSpecCompliant);
NS_ENSURE_SUCCESS(rv, rv);
#ifdef PR_LOGGING
{
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
("CSP refined with policy: \"%s\"",
NS_ConvertUTF16toUTF8(policy).get()));
}
#endif
}
return NS_OK;
}
nsresult
nsDocument::InitCSP(nsIChannel* aChannel)
{
@ -2502,15 +2526,23 @@ nsDocument::InitCSP(nsIChannel* aChannel)
bool specCompliantEnabled =
Preferences::GetBool("security.csp.speccompliant");
// If spec compliant pref isn't set, pretend we never got these headers.
if ((!cspHeaderValue.IsEmpty() || !cspROHeaderValue.IsEmpty()) &&
!specCompliantEnabled) {
// If spec compliant pref isn't set, pretend we never got
// these headers.
if (!specCompliantEnabled) {
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
("Got spec compliant CSP headers but pref was not set"));
cspHeaderValue.Truncate();
cspROHeaderValue.Truncate();
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
("Got spec compliant CSP headers but pref was not set"));
cspHeaderValue.Truncate();
cspROHeaderValue.Truncate();
}
// If the old header is present, warn that it will be deprecated.
if (!cspOldHeaderValue.IsEmpty() || !cspOldROHeaderValue.IsEmpty()) {
mCSPWebConsoleErrorQueue.Add("OldCSPHeaderDeprecated");
// Also, if the new headers AND the old headers were present, warn
// that the old headers will be ignored.
if (!cspHeaderValue.IsEmpty() || !cspROHeaderValue.IsEmpty()) {
mCSPWebConsoleErrorQueue.Add("BothCSPHeadersPresent");
}
}
@ -2571,19 +2603,13 @@ nsDocument::InitCSP(nsIChannel* aChannel)
}
// used as a "self" identifier for the CSP.
nsCOMPtr<nsIURI> chanURI;
aChannel->GetURI(getter_AddRefs(chanURI));
nsCOMPtr<nsIURI> selfURI;
aChannel->GetURI(getter_AddRefs(selfURI));
// Store the request context for violation reports
csp->ScanRequestData(httpChannel);
// The CSP is refined in the following order:
// 1. Default app CSP, if applicable
// 2. App manifest CSP, if provided
// 3. HTTP header CSP, if provided
// Note that since each application of refinePolicy is a set intersection,
// the order in which multiple CSP's are refined does not matter.
// ----- if the doc is an app and we want a default CSP, apply it.
if (applyAppDefaultCSP) {
nsAdoptingString appCSP;
if (appStatus == nsIPrincipal::APP_STATUS_PRIVILEGED) {
@ -2594,92 +2620,43 @@ nsDocument::InitCSP(nsIChannel* aChannel)
NS_ASSERTION(appCSP, "App, but no default CSP in security.apps.certified.CSP.default");
}
if (appCSP)
if (appCSP) {
// Use the 1.0 CSP parser for apps if the pref to do so is set.
csp->RefinePolicy(appCSP, chanURI, specCompliantEnabled);
csp->AppendPolicy(appCSP, selfURI, false, specCompliantEnabled);
}
}
// ----- if the doc is an app and specifies a CSP in its manifest, apply it.
if (applyAppManifestCSP) {
// Use the 1.0 CSP parser for apps if the pref to do so is set.
csp->RefinePolicy(appManifestCSP, chanURI, specCompliantEnabled);
csp->AppendPolicy(appManifestCSP, selfURI, false, specCompliantEnabled);
}
// While we are supporting both CSP 1.0 and the x- headers, the 1.0 headers
// take priority. If any spec-compliant headers are present, the x- headers
// are ignored, and the spec compliant parser is used.
bool cspSpecCompliant = (!cspHeaderValue.IsEmpty() || !cspROHeaderValue.IsEmpty());
// If the old header is present, warn that it will be deprecated.
if (!cspOldHeaderValue.IsEmpty() || !cspOldROHeaderValue.IsEmpty()) {
mCSPWebConsoleErrorQueue.Add("OldCSPHeaderDeprecated");
// Also, if the new headers AND the old headers were present, warn
// that the old headers will be ignored.
if (cspSpecCompliant) {
mCSPWebConsoleErrorQueue.Add("BothCSPHeadersPresent");
}
}
// can coexist with x- headers. If both exist, they're both enforced, but
// there's a warning posted in the web console that the x-headers are going
// away.
// ----- if there's a full-strength CSP header, apply it.
bool applyCSPFromHeader =
(( cspSpecCompliant && !cspHeaderValue.IsEmpty()) ||
(!cspSpecCompliant && !cspOldHeaderValue.IsEmpty()));
if (applyCSPFromHeader) {
// 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)
nsCharSeparatedTokenizer tokenizer(cspSpecCompliant ?
cspHeaderValue :
cspOldHeaderValue, ',');
while (tokenizer.hasMoreTokens()) {
const nsSubstring& policy = tokenizer.nextToken();
csp->RefinePolicy(policy, chanURI, cspSpecCompliant);
#ifdef PR_LOGGING
{
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
("CSP refined with policy: \"%s\"",
NS_ConvertUTF16toUTF8(policy).get()));
}
#endif
}
if (!cspOldHeaderValue.IsEmpty()) {
rv = AppendCSPFromHeader(csp, cspOldHeaderValue, selfURI, false, false);
NS_ENSURE_SUCCESS(rv, rv);
}
// ----- if there's a report-only CSP header, apply it
if (( cspSpecCompliant && !cspROHeaderValue.IsEmpty()) ||
(!cspSpecCompliant && !cspOldROHeaderValue.IsEmpty())) {
// post a warning and skip report-only CSP when both read only and regular
// CSP policies are present since CSP only allows one policy and it can't
// be partially report-only.
if (applyAppDefaultCSP || applyCSPFromHeader) {
mCSPWebConsoleErrorQueue.Add("ReportOnlyCSPIgnored");
#ifdef PR_LOGGING
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
("Skipped report-only CSP init for document %p because another, enforced policy is set", this));
#endif
} else {
// we can apply the report-only policy because there's no other CSP
// applied.
csp->SetReportOnlyMode(true);
if (!cspHeaderValue.IsEmpty()) {
rv = AppendCSPFromHeader(csp, cspHeaderValue, selfURI, false, true);
NS_ENSURE_SUCCESS(rv, rv);
}
// 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)
nsCharSeparatedTokenizer tokenizer(cspSpecCompliant ?
cspROHeaderValue :
cspOldROHeaderValue, ',');
while (tokenizer.hasMoreTokens()) {
const nsSubstring& policy = tokenizer.nextToken();
csp->RefinePolicy(policy, chanURI, cspSpecCompliant);
#ifdef PR_LOGGING
{
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
("CSP (report-only) refined with policy: \"%s\"",
NS_ConvertUTF16toUTF8(policy).get()));
}
#endif
}
}
// ----- if there's a report-only CSP header, apply it.
if (!cspOldROHeaderValue.IsEmpty()) {
rv = AppendCSPFromHeader(csp, cspOldROHeaderValue, selfURI, true, false);
NS_ENSURE_SUCCESS(rv, rv);
}
if (!cspROHeaderValue.IsEmpty()) {
rv = AppendCSPFromHeader(csp, cspROHeaderValue, selfURI, true, true);
NS_ENSURE_SUCCESS(rv, rv);
}
// ----- Enforce frame-ancestor policy on any applied policies

View File

@ -67,7 +67,7 @@ listener.prototype = {
// nsIContentSecurityPolicy instance. The problem is, this cspr_str is a
// string and not a policy due to the way it's exposed from
// nsIContentSecurityPolicy, so we have to re-parse it.
let cspr_str = this._csp.policy;
let cspr_str = this._csp.getPolicy(0);
let cspr = CSPRep.fromString(cspr_str, mkuri(DOCUMENT_URI));
// and in reparsing it, we lose the 'self' relationships, so need to also

View File

@ -79,10 +79,8 @@ function makeTest(id, expectedJSON, useReportOnlyPolicy, callback) {
csp.scanRequestData(selfchan);
// Load up the policy
csp.refinePolicy(policy, selfuri, false);
// set as report-only if that's the case
if (useReportOnlyPolicy) csp.reportOnlyMode = true;
csp.appendPolicy(policy, selfuri, useReportOnlyPolicy, false);
// prime the report server
var handler = makeReportHandler("/test" + id, "Test " + id, expectedJSON);
@ -100,7 +98,7 @@ function run_test() {
// test that inline script violations cause a report.
makeTest(0, {"blocked-uri": "self"}, false,
function(csp) {
let inlineOK = true, oReportViolation = {};
let inlineOK = true, oReportViolation = {'value': false};
inlineOK = csp.getAllowsInlineScript(oReportViolation);
// this is not a report only policy, so it better block inline scripts
@ -108,17 +106,19 @@ function run_test() {
// ... and cause reports to go out
do_check_true(oReportViolation.value);
// force the logging, since the getter doesn't.
csp.logViolationDetails(Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_SCRIPT,
selfuri.asciiSpec,
"script sample",
0);
if (oReportViolation.value) {
// force the logging, since the getter doesn't.
csp.logViolationDetails(Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_SCRIPT,
selfuri.asciiSpec,
"script sample",
0);
}
});
// test that eval violations cause a report.
makeTest(1, {"blocked-uri": "self"}, false,
function(csp) {
let evalOK = true, oReportViolation = {};
let evalOK = true, oReportViolation = {'value': false};
evalOK = csp.getAllowsEval(oReportViolation);
// this is not a report only policy, so it better block eval
@ -126,11 +126,13 @@ function run_test() {
// ... and cause reports to go out
do_check_true(oReportViolation.value);
// force the logging, since the getter doesn't.
csp.logViolationDetails(Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_SCRIPT,
selfuri.asciiSpec,
"script sample",
1);
if (oReportViolation.value) {
// force the logging, since the getter doesn't.
csp.logViolationDetails(Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_EVAL,
selfuri.asciiSpec,
"script sample",
1);
}
});
makeTest(2, {"blocked-uri": "http://blocked.test/foo.js"}, false,
@ -144,25 +146,28 @@ function run_test() {
// test that inline script violations cause a report in report-only policy
makeTest(3, {"blocked-uri": "self"}, true,
function(csp) {
let inlineOK = true, oReportViolation = {};
let inlineOK = true, oReportViolation = {'value': false};
inlineOK = csp.getAllowsInlineScript(oReportViolation);
// this is a report only policy, so it better allow inline scripts
do_check_true(inlineOK);
// ... but still cause reports to go out
// ... and cause reports to go out
do_check_true(oReportViolation.value);
// force the logging, since the getter doesn't.
csp.logViolationDetails(Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_SCRIPT,
selfuri.asciiSpec,
"script sample",
3);
if (oReportViolation.value) {
// force the logging, since the getter doesn't.
csp.logViolationDetails(Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_SCRIPT,
selfuri.asciiSpec,
"script sample",
3);
}
});
// test that eval violations cause a report in report-only policy
makeTest(4, {"blocked-uri": "self"}, true,
function(csp) {
let evalOK = true, oReportViolation = {};
let evalOK = true, oReportViolation = {'value': false};
evalOK = csp.getAllowsEval(oReportViolation);
// this is a report only policy, so it better allow eval
@ -170,10 +175,12 @@ function run_test() {
// ... but still cause reports to go out
do_check_true(oReportViolation.value);
// force the logging, since the getter doesn't.
csp.logViolationDetails(Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_SCRIPT,
selfuri.asciiSpec,
"script sample",
4);
if (oReportViolation.value) {
// force the logging, since the getter doesn't.
csp.logViolationDetails(Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_SCRIPT,
selfuri.asciiSpec,
"script sample",
4);
}
});
}

View File

@ -680,7 +680,7 @@ test(function test_FrameAncestor_ignores_userpass_bug779918() {
function testPermits(aChildUri, aParentUri, aContentType) {
let cspObj = Cc["@mozilla.org/contentsecuritypolicy;1"]
.createInstance(Ci.nsIContentSecurityPolicy);
cspObj.refinePolicy(testPolicy, aChildUri, false);
cspObj.appendPolicy(testPolicy, aChildUri, false, false);
let docshellparent = Cc["@mozilla.org/docshell;1"]
.createInstance(Ci.nsIDocShell);
let docshellchild = Cc["@mozilla.org/docshell;1"]
@ -909,56 +909,6 @@ test(
do_check_false(p_none.permits("http://bar.com"));
});
test(
function test_bug783497_refinePolicyIssues() {
const firstPolicy = "allow 'self'; img-src 'self'; script-src 'self'; options 'bogus-option'";
const secondPolicy = "default-src 'none'; script-src 'self'";
var cspObj = Cc["@mozilla.org/contentsecuritypolicy;1"]
.createInstance(Ci.nsIContentSecurityPolicy);
var selfURI = URI("http://self.com/");
function testPermits(aUri, aContentType) {
return cspObj.shouldLoad(aContentType, aUri, null, null, null, null)
== Ci.nsIContentPolicy.ACCEPT;
};
// everything is allowed by the default policy
do_check_true(testPermits(URI("http://self.com/foo.js"),
Ci.nsIContentPolicy.TYPE_SCRIPT));
do_check_true(testPermits(URI("http://other.com/foo.js"),
Ci.nsIContentPolicy.TYPE_SCRIPT));
do_check_true(testPermits(URI("http://self.com/foo.png"),
Ci.nsIContentPolicy.TYPE_IMAGE));
do_check_true(testPermits(URI("http://other.com/foo.png"),
Ci.nsIContentPolicy.TYPE_IMAGE));
// fold in the first policy
cspObj.refinePolicy(firstPolicy, selfURI, false);
// script-src and img-src are limited to self after the first policy
do_check_true(testPermits(URI("http://self.com/foo.js"),
Ci.nsIContentPolicy.TYPE_SCRIPT));
do_check_false(testPermits(URI("http://other.com/foo.js"),
Ci.nsIContentPolicy.TYPE_SCRIPT));
do_check_true(testPermits(URI("http://self.com/foo.png"),
Ci.nsIContentPolicy.TYPE_IMAGE));
do_check_false(testPermits(URI("http://other.com/foo.png"),
Ci.nsIContentPolicy.TYPE_IMAGE));
// fold in the second policy
cspObj.refinePolicy(secondPolicy, selfURI, false);
// script-src is self and img-src is none after the merge
do_check_true(testPermits(URI("http://self.com/foo.js"),
Ci.nsIContentPolicy.TYPE_SCRIPT));
do_check_false(testPermits(URI("http://other.com/foo.js"),
Ci.nsIContentPolicy.TYPE_SCRIPT));
do_check_false(testPermits(URI("http://self.com/foo.png"),
Ci.nsIContentPolicy.TYPE_IMAGE));
do_check_false(testPermits(URI("http://other.com/foo.png"),
Ci.nsIContentPolicy.TYPE_IMAGE));
});
test(
function test_bug764937_defaultSrcMissing() {
@ -974,7 +924,7 @@ test(
};
const policy = "script-src 'self'";
cspObjSpecCompliant.refinePolicy(policy, selfURI, true);
cspObjSpecCompliant.appendPolicy(policy, selfURI, false, true);
// Spec-Compliant policy default-src defaults to *.
// This means all images are allowed, and only 'self'
@ -992,7 +942,7 @@ test(
URI("http://bar.com/foo.js"),
Ci.nsIContentPolicy.TYPE_SCRIPT));
cspObjOld.refinePolicy(policy, selfURI, false);
cspObjOld.appendPolicy(policy, selfURI, false, false);
// non-Spec-Compliant policy default-src defaults to 'none'
// This means all images are blocked, and so are all scripts (because the

View File

@ -254,7 +254,7 @@ nsJSScriptTimeoutHandler::Init(nsGlobalWindow *aWindow, bool *aIsInterval,
NS_ENSURE_SUCCESS(rv, rv);
if (reportViolation) {
// TODO : FIX DATA in violation report.
// TODO : need actual script sample in violation report.
NS_NAMED_LITERAL_STRING(scriptSample, "call to eval() or related function blocked by CSP");
// Get the calling location.
@ -268,9 +268,9 @@ nsJSScriptTimeoutHandler::Init(nsGlobalWindow *aWindow, bool *aIsInterval,
}
csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL,
NS_ConvertUTF8toUTF16(aFileName),
scriptSample,
lineNum);
NS_ConvertUTF8toUTF16(aFileName),
scriptSample,
lineNum);
}
if (!allowsEval) {

View File

@ -674,8 +674,10 @@ public:
if (csp) {
NS_NAMED_LITERAL_STRING(scriptSample,
"Call to eval() or related function blocked by CSP.");
csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL,
mFileName, scriptSample, mLineNum);
if (mWorkerPrivate->GetReportCSPViolations()) {
csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL,
mFileName, scriptSample, mLineNum);
}
}
nsRefPtr<LogViolationDetailsResponseRunnable> response =

View File

@ -464,7 +464,7 @@ nsStyleUtil::CSPAllowsInlineStyle(nsIPrincipal* aPrincipal,
if (csp) {
bool inlineOK = true;
bool reportViolation = false;
bool reportViolation;
rv = csp->GetAllowsInlineStyle(&reportViolation, &inlineOK);
if (NS_FAILED(rv)) {
if (aRv)
@ -485,9 +485,9 @@ nsStyleUtil::CSPAllowsInlineStyle(nsIPrincipal* aPrincipal,
}
csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_INLINE_STYLE,
NS_ConvertUTF8toUTF16(asciiSpec),
aStyleText,
aLineNumber);
NS_ConvertUTF8toUTF16(asciiSpec),
aStyleText,
aLineNumber);
}
if (!inlineOK) {