Bug 855326 - CSP 1.1 nonce-source for scripts and styles r=mrbkap r=dholbert r=geekboy

This commit is contained in:
Garrett Robinson 2013-11-08 15:44:39 -08:00
parent 22a34fc8b5
commit 1da990368c
22 changed files with 516 additions and 67 deletions

View File

@ -489,7 +489,8 @@ nsScriptSecurityManager::ContentSecurityPolicyPermitsJSAction(JSContext *cx)
csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL,
fileName,
scriptSample,
lineNum);
lineNum,
EmptyString());
}
return evalOK;

View File

@ -15,7 +15,7 @@ interface nsIDocShell;
* one of these per document/principal.
*/
[scriptable, uuid(e5020ec3-1437-46f5-b4eb-8b60766d02c0)]
[scriptable, uuid(781b6511-f1fa-4e2c-8eff-1739d091eb2f)]
interface nsIContentSecurityPolicy : nsISupports
{
@ -104,6 +104,24 @@ interface nsIContentSecurityPolicy : nsISupports
*/
boolean getAllowsInlineStyle(out boolean shouldReportViolations);
/**
* Whether this policy accepts the given nonce
* @param aNonce
* The nonce string to check against the policy
* @param aContentType
* The type of element on which we encountered this nonce
* @param shouldReportViolation
* Whether or not the use of an incorrect nonce 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.
* @return
* Whether or not this nonce is valid
*/
boolean getAllowsNonce(in AString aNonce,
in unsigned long aContentType,
out boolean shouldReportViolation);
/**
* 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
@ -117,15 +135,22 @@ interface nsIContentSecurityPolicy : nsISupports
* sample of the violating content (to aid debugging)
* @param lineNum
* source line number of the violation (if available)
* @param aNonce
* (optional) If this is a nonce violation, include the nonce so we can
* recheck to determine which policies were violated and send the
* appropriate reports.
*/
void logViolationDetails(in unsigned short violationType,
in AString sourceFile,
in AString scriptSample,
in int32_t lineNum);
in int32_t lineNum,
[optional] in AString nonce);
const unsigned short VIOLATION_TYPE_INLINE_SCRIPT = 1;
const unsigned short VIOLATION_TYPE_EVAL = 2;
const unsigned short VIOLATION_TYPE_INLINE_STYLE = 3;
const unsigned short VIOLATION_TYPE_EVAL = 2;
const unsigned short VIOLATION_TYPE_INLINE_STYLE = 3;
const unsigned short VIOLATION_TYPE_NONCE_SCRIPT = 4;
const unsigned short VIOLATION_TYPE_NONCE_STYLE = 5;
/**
* Called after the CSP object is created to fill in the appropriate request

View File

@ -24,7 +24,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "Services",
// Module stuff
this.EXPORTED_SYMBOLS = ["CSPRep", "CSPSourceList", "CSPSource", "CSPHost",
"CSPdebug", "CSPViolationReportListener", "CSPLocalizer"];
"CSPdebug", "CSPViolationReportListener", "CSPLocalizer",
"CSPPrefObserver"];
var STRINGS_URI = "chrome://global/locale/security/csp.properties";
@ -65,25 +66,37 @@ const R_EXTHOSTSRC = new RegExp ("^" + R_HOSTSRC.source + "\\/[:print:]+$", 'i')
// keyword-source = "'self'" / "'unsafe-inline'" / "'unsafe-eval'"
const R_KEYWORDSRC = new RegExp ("^('self'|'unsafe-inline'|'unsafe-eval')$", 'i');
// nonce-source = "'nonce-" nonce-value "'"
// nonce-value = 1*( ALPHA / DIGIT / "+" / "/" )
const R_NONCESRC = new RegExp ("^'nonce-([a-zA-Z0-9\+\/]+)'$", 'i');
// source-exp = scheme-source / host-source / keyword-source
const R_SOURCEEXP = new RegExp (R_SCHEMESRC.source + "|" +
R_HOSTSRC.source + "|" +
R_KEYWORDSRC.source, 'i');
R_KEYWORDSRC.source + "|" +
R_NONCESRC.source, 'i');
var gPrefObserver = {
this.CSPPrefObserver = {
get debugEnabled () {
if (!this._branch)
this._initialize();
return this._debugEnabled;
},
get experimentalEnabled () {
if (!this._branch)
this._initialize();
return this._experimentalEnabled;
},
_initialize: function() {
var prefSvc = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefService);
this._branch = prefSvc.getBranch("security.csp.");
this._branch.addObserver("", this, false);
this._debugEnabled = this._branch.getBoolPref("debug");
this._experimentalEnabled = this._branch.getBoolPref("experimentalEnabled");
},
unregister: function() {
@ -95,11 +108,13 @@ var gPrefObserver = {
if (aTopic != "nsPref:changed") return;
if (aData === "debug")
this._debugEnabled = this._branch.getBoolPref("debug");
if (aData === "experimentalEnabled")
this._experimentalEnabled = this._branch.getBoolPref("experimentalEnabled");
},
};
this.CSPdebug = function CSPdebug(aMsg) {
if (!gPrefObserver.debugEnabled) return;
if (!CSPPrefObserver.debugEnabled) return;
aMsg = 'CSP debug: ' + aMsg + "\n";
Components.classes["@mozilla.org/consoleservice;1"]
@ -793,14 +808,28 @@ CSPRep.prototype = {
/**
* Determines if this policy accepts a URI.
* @param aContext
* @param aURI
* URI of the requested resource
* @param aDirective
* one of the SRC_DIRECTIVES defined above
* @param aContext
* Context of the resource being requested. This is a type inheriting
* from nsIDOMHTMLElement if this is called from shouldLoad to check
* an external resource load, and refers to the HTML element that is
* causing the resource load. Otherwise, it is a string containing
* a nonce from a nonce="" attribute if it is called from
* getAllowsNonce.
* @returns
* true if the policy permits the URI in given context.
*/
permits:
function csp_permits(aURI, aContext) {
if (!aURI) return false;
function csp_permits(aURI, aDirective, aContext) {
// In the case where permits is called from getAllowsNonce (for an inline
// element), aURI is null and aContext has a specific value. Otherwise,
// calling permits without aURI is invalid.
let checking_nonce = aContext instanceof Ci.nsIDOMHTMLElement ||
typeof aContext === 'string';
if (!aURI && !checking_nonce) return false;
// GLOBALLY ALLOW "about:" SCHEME
if (aURI instanceof String && aURI.substring(0,6) === "about:")
@ -811,13 +840,13 @@ CSPRep.prototype = {
// make sure the right directive set is used
let DIRS = this._specCompliant ? CSPRep.SRC_DIRECTIVES_NEW : CSPRep.SRC_DIRECTIVES_OLD;
let contextIsSrcDir = false;
let directiveInPolicy = false;
for (var i in DIRS) {
if (DIRS[i] === aContext) {
if (DIRS[i] === aDirective) {
// for catching calls with invalid contexts (below)
contextIsSrcDir = true;
if (this._directives.hasOwnProperty(aContext)) {
return this._directives[aContext].permits(aURI);
directiveInPolicy = true;
if (this._directives.hasOwnProperty(aDirective)) {
return this._directives[aDirective].permits(aURI, aContext);
}
//found matching dir, can stop looking
break;
@ -825,15 +854,15 @@ CSPRep.prototype = {
}
// frame-ancestors is a special case; it doesn't fall back to default-src.
if (aContext === DIRS.FRAME_ANCESTORS)
if (aDirective === DIRS.FRAME_ANCESTORS)
return true;
// All directives that don't fall back to default-src should have an escape
// hatch above (like frame-ancestors).
if (!contextIsSrcDir) {
if (!directiveInPolicy) {
// if this code runs, there's probably something calling permits() that
// shouldn't be calling permits().
CSPdebug("permits called with invalid load type: " + aContext);
CSPdebug("permits called with invalid load type: " + aDirective);
return false;
}
@ -842,7 +871,7 @@ CSPRep.prototype = {
// indicates no relevant directives were present and the load should be
// permitted).
if (this._directives.hasOwnProperty(DIRS.DEFAULT_SRC)) {
return this._directives[DIRS.DEFAULT_SRC].permits(aURI);
return this._directives[DIRS.DEFAULT_SRC].permits(aURI, aContext);
}
// no relevant directives present -- this means for CSP 1.0 that the load
@ -1081,12 +1110,12 @@ CSPSourceList.prototype = {
* true if the URI matches a source in this source list.
*/
permits:
function cspsd_permits(aURI) {
function cspsd_permits(aURI, aContext) {
if (this.isNone()) return false;
if (this.isAll()) return true;
for (var i in this._sources) {
if (this._sources[i].permits(aURI)) {
if (this._sources[i].permits(aURI, aContext)) {
return true;
}
}
@ -1102,6 +1131,7 @@ this.CSPSource = function CSPSource() {
this._scheme = undefined;
this._port = undefined;
this._host = undefined;
this._nonce = undefined;
//when set to true, this allows all source
this._permitAll = false;
@ -1346,6 +1376,19 @@ CSPSource.fromString = function(aStr, aCSPRep, self, enforceSelfChecks) {
return sObj;
}
// check for a nonce-source match
if (R_NONCESRC.test(aStr)) {
// We can't put this check outside of the regex test because R_NONCESRC is
// included in R_SOURCEEXP, which is const. By testing here, we can
// explicitly return null for nonces if experimental is not enabled,
// instead of letting it fall through and assuming it won't accidentally
// match something later in this function.
if (!CSPPrefObserver.experimentalEnabled) return null;
var nonceSrcMatch = R_NONCESRC.exec(aStr);
sObj._nonce = nonceSrcMatch[1];
return sObj;
}
// check for 'self' (case insensitive)
if (aStr.toUpperCase() === "'SELF'") {
if (!self) {
@ -1450,6 +1493,8 @@ CSPSource.prototype = {
s = s + this._host;
if (this.port)
s = s + ":" + this.port;
if (this._nonce)
s = s + "'nonce-" + this._nonce + "'";
return s;
},
@ -1465,6 +1510,7 @@ CSPSource.prototype = {
aClone._scheme = this._scheme;
aClone._port = this._port;
aClone._host = this._host ? this._host.clone() : undefined;
aClone._nonce = this._nonce;
aClone._isSelf = this._isSelf;
aClone._CSPRep = this._CSPRep;
return aClone;
@ -1474,11 +1520,24 @@ CSPSource.prototype = {
* Determines if this Source accepts a URI.
* @param aSource
* the URI, or CSPSource in question
* @param aContext
* the context of the resource being loaded
* @returns
* true if the URI matches a source in this source list.
*/
permits:
function(aSource) {
function(aSource, aContext) {
if (this._nonce && CSPPrefObserver.experimentalEnabled) {
if (aContext instanceof Ci.nsIDOMHTMLElement) {
return this._nonce === aContext.getAttribute('nonce');
} else if (typeof aContext === 'string') {
return this._nonce === aContext;
}
}
// We only use aContext for nonce checks. If it's otherwise provided,
// ignore it.
if (!CSPPrefObserver.experimentalEnabled && aContext) return false;
if (!aSource) return false;
if (!(aSource instanceof CSPSource))

View File

@ -31,6 +31,8 @@ const ERROR_FLAG = Ci.nsIScriptError.ERROR_FLAG;
const INLINE_STYLE_VIOLATION_OBSERVER_SUBJECT = 'violated base restriction: Inline Stylesheets will not apply';
const INLINE_SCRIPT_VIOLATION_OBSERVER_SUBJECT = 'violated base restriction: Inline Scripts will not execute';
const EVAL_VIOLATION_OBSERVER_SUBJECT = 'violated base restriction: Code will not be created from strings';
const SCRIPT_NONCE_VIOLATION_OBSERVER_SUBJECT = 'Inline Script had invalid nonce'
const STYLE_NONCE_VIOLATION_OBSERVER_SUBJECT = 'Inline Style had invalid nonce'
// The cutoff length of content location in creating CSP cache key.
const CSP_CACHE_URI_CUTOFF_SIZE = 512;
@ -189,6 +191,28 @@ ContentSecurityPolicy.prototype = {
});
},
getAllowsNonce: function(aNonce, aContentType, shouldReportViolation) {
if (!CSPPrefObserver.experimentalEnabled)
return false;
if (!(aContentType == Ci.nsIContentPolicy.TYPE_SCRIPT ||
aContentType == Ci.nsIContentPolicy.TYPE_STYLESHEET)) {
CSPdebug("Nonce check requested for an invalid content type (not script or style): " + aContentType);
return false;
}
let ct = ContentSecurityPolicy._MAPPINGS[aContentType];
// allow it to execute?
let policyAllowsNonce = [ policy.permits(null, ct, aNonce) for (policy of this._policies) ];
shouldReportViolation.value = policyAllowsNonce.some(function(a) { return !a; });
// allow it to execute? (Do all the policies allow it to execute)?
return this._policies.every(function(policy, i) {
return policy._reportOnlyMode || policyAllowsNonce[i];
});
},
/**
* For each policy, log any violation on the Error Console and send a report
* if a report-uri is present in the policy
@ -201,9 +225,13 @@ ContentSecurityPolicy.prototype = {
* sample of the violating content (to aid debugging)
* @param aLineNum
* source line number of the violation (if available)
* @param aNonce
* (optional) If this is a nonce violation, include the nonce should we
* can recheck to determine which policies were violated and send the
* appropriate reports.
*/
logViolationDetails:
function(aViolationType, aSourceFile, aScriptSample, aLineNum, violatedPolicyIndex) {
function(aViolationType, aSourceFile, aScriptSample, aLineNum, aNonce) {
for (let policyIndex=0; policyIndex < this._policies.length; policyIndex++) {
let policy = this._policies[policyIndex];
@ -237,6 +265,24 @@ ContentSecurityPolicy.prototype = {
aSourceFile, aScriptSample, aLineNum);
}
break;
case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_NONCE_SCRIPT:
let scriptType = ContentSecurityPolicy._MAPPINGS[Ci.nsIContentPolicy.TYPE_SCRIPT];
if (!policy.permits(null, scriptType, aNonce)) {
var violatedDirective = this._buildViolatedDirectiveString('SCRIPT_SRC', policy);
this._asyncReportViolation('self', null, violatedDirective, policyIndex,
SCRIPT_NONCE_VIOLATION_OBSERVER_SUBJECT,
aSourceFile, aScriptSample, aLineNum);
}
break;
case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_NONCE_STYLE:
let styleType = ContentSecurityPolicy._MAPPINGS[Ci.nsIContentPolicy.TYPE_STYLE];
if (!policy.permits(null, styleType, aNonce)) {
var violatedDirective = this._buildViolatedDirectiveString('STYLE_SRC', policy);
this._asyncReportViolation('self', null, violatedDirective, policyIndex,
STYLE_NONCE_VIOLATION_OBSERVER_SUBJECT,
aSourceFile, aScriptSample, aLineNum);
}
break;
}
}
},
@ -628,6 +674,14 @@ ContentSecurityPolicy.prototype = {
let cp = Ci.nsIContentPolicy;
// Infer if this is a preload for elements that use nonce-source. Since,
// for preloads, aContext is the document and not the element associated
// with the resource, we cannot determine the nonce. See Bug 612921 and
// Bug 855326.
var possiblePreloadNonceConflict =
(aContentType == cp.TYPE_SCRIPT || aContentType == cp.TYPE_STYLESHEET) &&
aContext instanceof Ci.nsIDOMHTMLDocument;
// 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.
@ -661,15 +715,18 @@ ContentSecurityPolicy.prototype = {
// otherwise, honor the translation
// var source = aContentLocation.scheme + "://" + aContentLocation.hostPort;
var res = policy.permits(aContentLocation, cspContext)
? cp.ACCEPT : cp.REJECT_SERVER;
let context = CSPPrefObserver.experimentalEnabled ? aContext : null;
var res = policy.permits(aContentLocation, cspContext, context) ?
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) {
// Do not send report if this is a nonce-source preload - the decision may
// be wrong and will incorrectly fail the unit tests.
if (res != Ci.nsIContentPolicy.ACCEPT && !possiblePreloadNonceConflict) {
CSPdebug("blocking request for " + aContentLocation.asciiSpec);
try {
let directive = "unknown directive",
@ -704,7 +761,8 @@ ContentSecurityPolicy.prototype = {
let ret = (policyAllowsLoadArray.some(function(a,b) { return !a; }) ?
cp.REJECT_SERVER : cp.ACCEPT);
if (key) {
// Do not cache the result if this is a nonce-source preload
if (key && !possiblePreloadNonceConflict) {
this._cache[key] = ret;
}
return ret;

View File

@ -608,6 +608,7 @@ GK_ATOM(nodeSet, "node-set")
GK_ATOM(noembed, "noembed")
GK_ATOM(noframes, "noframes")
GK_ATOM(nohref, "nohref")
GK_ATOM(nonce, "nonce")
GK_ATOM(none, "none")
GK_ATOM(noresize, "noresize")
GK_ATOM(normal, "normal")

View File

@ -624,13 +624,31 @@ nsScriptLoader::ProcessScriptElement(nsIScriptElement *aElement)
NS_ENSURE_SUCCESS(rv, false);
if (csp) {
PR_LOG(gCspPRLog, PR_LOG_DEBUG, ("New ScriptLoader i ****with CSP****"));
bool inlineOK = true;
bool reportViolations = false;
rv = csp->GetAllowsInlineScript(&reportViolations, &inlineOK);
PR_LOG(gCspPRLog, PR_LOG_DEBUG, ("New ScriptLoader ****with CSP****"));
bool reportViolation = false;
bool allowInlineScript = true;
rv = csp->GetAllowsInlineScript(&reportViolation, &allowInlineScript);
NS_ENSURE_SUCCESS(rv, false);
if (reportViolations) {
bool foundNonce = false;
nsAutoString nonce;
if (!allowInlineScript) {
foundNonce = scriptContent->GetAttr(kNameSpaceID_None, nsGkAtoms::nonce, nonce);
if (foundNonce) {
// We can overwrite the outparams from GetAllowsInlineScript because
// if the nonce is correct, then we don't want to report the original
// inline violation (it has been whitelisted by the nonce), and if
// the nonce is incorrect, then we want to return just the specific
// "nonce violation" rather than both a "nonce violation" and
// a generic "inline violation".
rv = csp->GetAllowsNonce(nonce, nsIContentPolicy::TYPE_SCRIPT,
&reportViolation, &allowInlineScript);
NS_ENSURE_SUCCESS(rv, false);
}
}
if (reportViolation) {
// gather information to log with violation report
nsIURI* uri = mDocument->GetDocumentURI();
nsAutoCString asciiSpec;
@ -641,17 +659,21 @@ nsScriptLoader::ProcessScriptElement(nsIScriptElement *aElement)
// cap the length of the script sample at 40 chars
if (scriptText.Length() > 40) {
scriptText.Truncate(40);
scriptText.Append(NS_LITERAL_STRING("..."));
scriptText.AppendLiteral("...");
}
csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_INLINE_SCRIPT,
NS_ConvertUTF8toUTF16(asciiSpec),
scriptText,
aElement->GetScriptLineNumber());
// The type of violation to report is determined by whether there was
// a nonce present.
unsigned short violationType = foundNonce ?
nsIContentSecurityPolicy::VIOLATION_TYPE_NONCE_SCRIPT :
nsIContentSecurityPolicy::VIOLATION_TYPE_INLINE_SCRIPT;
csp->LogViolationDetails(violationType, NS_ConvertUTF8toUTF16(asciiSpec),
scriptText, aElement->GetScriptLineNumber(), nonce);
}
if (!inlineOK) {
PR_LOG(gCspPRLog, PR_LOG_DEBUG, ("CSP blocked inline scripts (2)"));
if (!allowInlineScript) {
NS_ASSERTION(reportViolation,
"CSP blocked inline script but is not reporting a violation");
return false;
}
}

View File

@ -360,7 +360,10 @@ nsStyleLinkElement::DoUpdateStyleSheet(nsIDocument *aOldDocument,
nsAutoString text;
nsContentUtils::GetNodeTextContent(thisContent, false, text);
if (!nsStyleUtil::CSPAllowsInlineStyle(thisContent->NodePrincipal(),
MOZ_ASSERT(thisContent->Tag() != nsGkAtoms::link,
"<link> is not 'inline', and needs different CSP checks");
if (!nsStyleUtil::CSPAllowsInlineStyle(thisContent,
thisContent->NodePrincipal(),
doc->GetDocumentURI(),
mLineNumber, text, &rv))
return rv;

View File

@ -236,7 +236,7 @@ nsStyledElementNotElementCSSInlineStyle::ParseStyleAttribute(const nsAString& aV
{
nsIDocument* doc = OwnerDoc();
if (!nsStyleUtil::CSPAllowsInlineStyle(NodePrincipal(),
if (!nsStyleUtil::CSPAllowsInlineStyle(nullptr, NodePrincipal(),
doc->GetDocumentURI(), 0, aValue,
nullptr))
return;

View File

@ -0,0 +1,67 @@
<!doctype html>
<html>
<head>
<!-- external styles -->
<link rel='stylesheet' nonce="correctstylenonce" href="file_CSP.sjs?testid=external_style_correct_nonce_good&type=text/css" />
<link rel='stylesheet' nonce="incorrectstylenonce" href="file_CSP.sjs?testid=external_style_incorrect_nonce_bad&type=text/css" />
<link rel='stylesheet' nonce="correctscriptnonce" href="file_CSP.sjs?testid=external_style_correct_script_nonce_bad&type=text/css" />
<link rel='stylesheet' href="file_CSP.sjs?testid=external_style_no_nonce_bad&type=text/css" />
</head>
<body>
<!-- inline scripts -->
<script nonce="correctscriptnonce">
window.parent.inlineScriptTestResult("allowed", "allowed", "This script has a correct nonce for scripts");
</script>
<script nonce="incorrectscriptnonce">
window.parent.inlineScriptTestResult("allowed", "blocked", "This script has an incorrect nonce for scripts");
</script>
<script nonce="correctstylenonce">
window.parent.inlineScriptTestResult("allowed", "blocked", "This script has a correct nonce for styles (but not for scripts)");
</script>
<script>
window.parent.inlineScriptTestResult("allowed", "blocked", "This script has no nonce");
</script>
<!-- external scripts -->
<script nonce="correctscriptnonce" src="http://example.org/tests/content/base/test/csp/file_CSP.sjs?testid=external_script_correct_nonce_good&type=text/javascript"></script>
<script nonce="anothercorrectscriptnonce" src="http://example.org/tests/content/base/test/csp/file_CSP.sjs?testid=external_script_another_correct_nonce_good&type=text/javascript"></script>
<script nonce="incorrectscriptnonce" src="http://example.org/tests/content/base/test/csp/file_CSP.sjs?testid=external_script_incorrect_nonce_bad&type=text/javascript"></script>
<script nonce="correctstylenonce" src="http://example.org/tests/content/base/test/csp/file_CSP.sjs?testid=external_script_correct_style_nonce_bad&type=text/javascript"></script>
<script src="http://example.org/tests/content/base/test/csp/file_CSP.sjs?testid=external_script_no_nonce_bad&type=text/javascript"></script>
<!-- This external script has the correct nonce and comes from a whitelisted URI. It should be allowed. -->
<script nonce="correctscriptnonce" src="file_CSP.sjs?testid=external_script_correct_nonce_correct_uri_good&type=text/javascript"></script>
<!-- This external script has an incorrect nonce, but comes from a whitelisted URI. It should be allowed. -->
<script nonce="incorrectscriptnonce" src="file_CSP.sjs?testid=external_script_incorrect_nonce_correct_uri_good&type=text/javascript"></script>
<!-- This external script has no nonce and comes from a whitelisted URI. It should be allowed. -->
<script src="file_CSP.sjs?testid=external_script_no_nonce_correct_uri_good&type=text/javascript"></script>
<!-- inline styles -->
<ol>
<li id=inline-style-correct-nonce>
(inline style with correct nonce) This text should be green
</li>
<li id=inline-style-incorrect-nonce>
(inline style with incorrect nonce) This text should be black
</li>
<li id=inline-style-correct-script-nonce>
(inline style with correct script, not style, nonce) This text should be black
</li>
<li id=inline-style-no-nonce>
(inline style with no nonce) This text should be black
</li>
</ol>
<style nonce=correctstylenonce>
li#inline-style-correct-nonce { color: green; }
</style>
<style nonce=incorrectstylenonce>
li#inline-style-incorrect-nonce { color: red; }
</style>
<style nonce=correctscriptnonce>
li#inline-style-correct-script-nonce { color: red; }
</style>
<style>
li#inline-style-no-nonce { color: red; }
</style>
</body>
</html>

View File

@ -0,0 +1,2 @@
Content-Security-Policy: script-src 'self' 'nonce-correctscriptnonce' 'nonce-anothercorrectscriptnonce'; style-src 'nonce-correctstylenonce';
Cache-Control: no-cache

View File

@ -85,6 +85,8 @@ support-files =
file_policyuri_regression_from_multipolicy.html
file_policyuri_regression_from_multipolicy.html^headers^
file_policyuri_regression_from_multipolicy_policy
file_nonce_source.html
file_nonce_source.html^headers^
[test_CSP.html]
[test_CSP_bug663567.html]
@ -103,3 +105,4 @@ support-files =
[test_CSP_bug910139.html]
[test_CSP_bug909029.html]
[test_policyuri_regression_from_multipolicy.html]
[test_nonce_source.html]

View File

@ -0,0 +1,149 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test CSP 1.1 nonce-source for scripts and styles</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<p id="display"></p>
<div id="content" style="visibility:hidden">
<iframe style="width:100%;" id='cspframe'></iframe>
</div>
<script class="testbody" type="text/javascript">
var testsRun = 0;
var totalTests = 20;
var inlineScriptTestsRun = 0;
var totalInlineScriptTests = 4;
var scriptNonceViolations = 0;
var expectedScriptNonceViolations = 2;
var scriptInlineViolations = 0;
var expectedScriptInlineViolations = 1;
// This is used to watch the blocked data bounce off CSP
function examiner() {
SpecialPowers.addObserver(this, "http-on-modify-request", false);
SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
}
examiner.prototype = {
observe: function(subject, topic, data) {
if (!SpecialPowers.can_QI(subject))
return;
var testid_re = new RegExp("testid=([a-z0-9_]+)");
//_good things better be allowed!
//_bad things better be blocked!
if (topic === "http-on-modify-request") {
// these things were allowed by CSP
var allowed_uri = SpecialPowers.getPrivilegedProps(SpecialPowers.do_QueryInterface(subject, "nsIHttpChannel"), "URI.asciiSpec");
if (!testid_re.test(allowed_uri)) return;
var testid = testid_re.exec(allowed_uri)[1];
ok(/_good/.test(testid), "Allowed URI with testid " + testid);
ranTests(1);
}
if (topic === "csp-on-violate-policy") {
try {
// if it is an blocked external load, subject will be the URI of the resource
var blocked_uri = SpecialPowers.getPrivilegedProps(SpecialPowers.do_QueryInterface(subject, "nsIURI"), "asciiSpec");
if (!testid_re.test(blocked_uri)) return;
var testid = testid_re.exec(blocked_uri)[1];
ok(/_bad/.test(testid), "Blocked URI with testid " + testid);
ranTests(1);
} catch (e) {
// if the subject is blocked inline, data will be a violation msg (defined at the top of contentSecurityPolicy.js)
var violation_msg = SpecialPowers.getPrivilegedProps(SpecialPowers.do_QueryInterface(subject, "nsISupportsCString"), "data");
if (/Inline Script/.test(violation_msg)) {
if (/Inline Script had invalid nonce/.test(violation_msg))
scriptNonceViolations++;
if (/Inline Scripts will not execute/.test(violation_msg))
scriptInlineViolations++;
window.inlineScriptTestResult("blocked", "blocked",
"Blocked because " + violation_msg);
}
}
}
},
// must eventually call this to remove the listener, or mochitests might get borked.
remove: function() {
SpecialPowers.removeObserver(this, "http-on-modify-request");
SpecialPowers.removeObserver(this, "csp-on-violate-policy");
}
}
var inlineScriptTestResult = function(testIs, testShouldBe, description) {
if (testIs !== testShouldBe) {
ok(false, description);
} else {
ok(true, description);
}
ranTests(1)
inlineScriptTestsRun++;
if (inlineScriptTestsRun == totalInlineScriptTests) {
if (scriptNonceViolations != expectedScriptNonceViolations)
ok(false, "The number of reported script nonce violations does not match expected; got " + scriptNonceViolations + ", expected " + expectedScriptNonceViolations);
if (scriptInlineViolations != expectedScriptInlineViolations)
ok(false, "The number of reported inline script violations does not match expected; got " + scriptInlineViolations + ", expected " + expectedScriptInlineViolations);
ranTests(2);
}
}
function cleanup() {
// remove the observer so we don't bork other tests
window.examiner.remove();
// finish the tests
SimpleTest.finish();
}
function ranTests(num) {
testsRun += num;
if (testsRun < totalTests) {
return;
}
cleanup();
}
function checkStyles () {
var cspframe = document.getElementById('cspframe');
var getElementColorById = function (id) {
return window.getComputedStyle(cspframe.contentDocument.getElementById(id), null).color;
};
// Inline style tries to change an element's color to green. If blocked, the
// element's color will be the default black.
var green = "rgb(0, 128, 0)";
var black = "rgb(0, 0, 0)";
is(getElementColorById('inline-style-correct-nonce'), green, "Inline style with correct nonce allowed");
is(getElementColorById('inline-style-incorrect-nonce'), black, "Inline style with incorrect nonce blocked");
is(getElementColorById('inline-style-correct-script-nonce'), black, "Inline style with correct nonce for scripts (but incorrect nonce for styles) blocked");
is(getElementColorById('inline-style-no-nonce'), black, "Inline style with no nonce blocked");
ranTests(4);
}
//////////////////////////////////////////////////////////////////////
// set up and go
window.examiner = new examiner();
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv(
{'set':[["security.csp.speccompliant", true],
["security.csp.experimentalEnabled", true]]},
function() {
// save this for last so that our listeners are registered.
// ... this loads the testbed of good and bad requests.
document.getElementById('cspframe').src = 'file_nonce_source.html';
document.getElementById('cspframe').addEventListener('load', checkStyles, false);
});
</script>
</pre>
</body>
</html>

View File

@ -729,7 +729,8 @@ nsEventListenerManager::SetEventHandler(nsIAtom *aName,
csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_INLINE_SCRIPT,
NS_ConvertUTF8toUTF16(asciiSpec),
scriptSample,
0);
0,
EmptyString());
}
// return early if CSP wants us to block inline scripts

View File

@ -394,7 +394,8 @@ nsSMILCSSValueType::ValueFromString(nsCSSProperty aPropID,
}
nsIDocument* doc = aTargetElement->GetCurrentDoc();
if (doc && !nsStyleUtil::CSPAllowsInlineStyle(doc->NodePrincipal(),
if (doc && !nsStyleUtil::CSPAllowsInlineStyle(nullptr,
doc->NodePrincipal(),
doc->GetDocumentURI(),
0, aString, nullptr)) {
return;

View File

@ -181,7 +181,7 @@ CheckCSPForEval(JSContext* aCx, nsGlobalWindow* aWindow, ErrorResult& aError)
}
csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL,
fileNameString, scriptSample, lineNum);
fileNameString, scriptSample, lineNum, EmptyString());
}
return allowsEval;

View File

@ -179,7 +179,8 @@ nsresult nsJSThunk::EvaluateScript(nsIChannel *aChannel,
csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_INLINE_SCRIPT,
NS_ConvertUTF8toUTF16(asciiSpec),
NS_ConvertUTF8toUTF16(mURL),
0);
0,
EmptyString());
}
//return early if inline scripts are not allowed

View File

@ -707,7 +707,7 @@ public:
"Call to eval() or related function blocked by CSP.");
if (mWorkerPrivate->GetReportCSPViolations()) {
csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL,
mFileName, scriptSample, mLineNum);
mFileName, scriptSample, mLineNum, EmptyString());
}
}

View File

@ -10,6 +10,7 @@
#include "nsCSSProps.h"
#include "nsRuleNode.h"
#include "nsROCSSPrimitiveValue.h"
#include "nsIContentPolicy.h"
#include "nsIContentSecurityPolicy.h"
#include "nsIURI.h"
@ -441,7 +442,8 @@ nsStyleUtil::IsSignificantChild(nsIContent* aChild, bool aTextIsSignificant,
}
/* static */ bool
nsStyleUtil::CSPAllowsInlineStyle(nsIPrincipal* aPrincipal,
nsStyleUtil::CSPAllowsInlineStyle(nsIContent* aContent,
nsIPrincipal* aPrincipal,
nsIURI* aSourceURI,
uint32_t aLineNumber,
const nsSubstring& aStyleText,
@ -453,6 +455,10 @@ nsStyleUtil::CSPAllowsInlineStyle(nsIPrincipal* aPrincipal,
*aRv = NS_OK;
}
MOZ_ASSERT(!aContent || aContent->Tag() == nsGkAtoms::style,
"aContent passed to CSPAllowsInlineStyle "
"for an element that is not <style>");
nsCOMPtr<nsIContentSecurityPolicy> csp;
rv = aPrincipal->GetCsp(getter_AddRefs(csp));
@ -463,17 +469,42 @@ nsStyleUtil::CSPAllowsInlineStyle(nsIPrincipal* aPrincipal,
}
if (csp) {
bool inlineOK = true;
bool reportViolation;
rv = csp->GetAllowsInlineStyle(&reportViolation, &inlineOK);
bool allowInlineStyle = true;
rv = csp->GetAllowsInlineStyle(&reportViolation, &allowInlineStyle);
if (NS_FAILED(rv)) {
if (aRv)
*aRv = rv;
return false;
}
bool foundNonce = false;
nsAutoString nonce;
// If inline styles are allowed ('unsafe-inline'), skip the (irrelevant)
// nonce check
if (!allowInlineStyle) {
// We can only find a nonce if aContent is provided
foundNonce = !!aContent &&
aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::nonce, nonce);
if (foundNonce) {
// We can overwrite the outparams from GetAllowsInlineStyle because
// if the nonce is correct, then we don't want to report the original
// inline violation (it has been whitelisted by the nonce), and if
// the nonce is incorrect, then we want to return just the specific
// "nonce violation" rather than both a "nonce violation" and
// a generic "inline violation".
rv = csp->GetAllowsNonce(nonce, nsIContentPolicy::TYPE_STYLESHEET,
&reportViolation, &allowInlineStyle);
if (NS_FAILED(rv)) {
if (aRv)
*aRv = rv;
return false;
}
}
}
if (reportViolation) {
// Inline styles are not allowed by CSP, so report the violation
// This inline style is not allowed by CSP, so report the violation
nsAutoCString asciiSpec;
aSourceURI->GetAsciiSpec(asciiSpec);
nsAutoString styleText(aStyleText);
@ -481,18 +512,23 @@ nsStyleUtil::CSPAllowsInlineStyle(nsIPrincipal* aPrincipal,
// cap the length of the style sample at 40 chars.
if (styleText.Length() > 40) {
styleText.Truncate(40);
styleText.Append(NS_LITERAL_STRING("..."));
styleText.AppendLiteral("...");
}
csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_INLINE_STYLE,
NS_ConvertUTF8toUTF16(asciiSpec),
aStyleText,
aLineNumber);
// The type of violation to report is determined by whether there was
// a nonce present.
unsigned short violationType = foundNonce ?
nsIContentSecurityPolicy::VIOLATION_TYPE_NONCE_STYLE :
nsIContentSecurityPolicy::VIOLATION_TYPE_INLINE_STYLE;
csp->LogViolationDetails(violationType, NS_ConvertUTF8toUTF16(asciiSpec),
styleText, aLineNumber, nonce);
}
if (!inlineOK) {
// The inline style should be blocked.
return false;
if (!allowInlineStyle) {
NS_ASSERTION(reportViolation,
"CSP blocked inline style but is not reporting a violation");
// The inline style should be blocked.
return false;
}
}
// No CSP or a CSP that allows inline styles.

View File

@ -98,15 +98,32 @@ public:
bool aWhitespaceIsSignificant);
/*
* Does this principal have a CSP that blocks the application of
* inline styles ? Returns false if application of the style should
* inline styles? Returns false if application of the style should
* be blocked.
*
* Note that the principal passed in here needs to be the principal
* of the document, not of the style sheet. The document's principal
* is where any Content Security Policy that should be used to
* block or allow inline styles will be located.
* @param aContent
* The <style> element that the caller wants to know whether to honor.
* Included to check the nonce attribute if one is provided. Allowed to
* be null, if this is for something other than a <style> element (in
* which case nonces won't be checked).
* @param aPrincipal
* The principal of the of the document (*not* of the style sheet).
* The document's principal is where any Content Security Policy that
* should be used to block or allow inline styles will be located.
* @param aSourceURI
* URI of document containing inline style (for reporting violations)
* @param aLineNumber
* Line number of inline style element in the containing document (for
* reporting violations)
* @param aStyleText
* Contents of the inline style element (for reporting violations)
* @param aRv
* Return error code in case of failure
* @return
* Does CSP allow application of the specified inline style?
*/
static bool CSPAllowsInlineStyle(nsIPrincipal* aPrincipal,
static bool CSPAllowsInlineStyle(nsIContent* aContent,
nsIPrincipal* aPrincipal,
nsIURI* aSourceURI,
uint32_t aLineNumber,
const nsSubstring& aStyleText,

View File

@ -1606,6 +1606,7 @@ pref("security.notification_enable_delay", 500);
pref("security.csp.enable", true);
pref("security.csp.debug", false);
pref("security.csp.experimentalEnabled", false);
// Mixed content blocking
pref("security.mixed_content.block_active_content", false);

View File

@ -1926,7 +1926,8 @@ nsCrypto::GenerateCRMFRequest(JSContext* aContext,
csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL,
NS_ConvertASCIItoUTF16(fileName),
scriptSample,
lineNum);
lineNum,
EmptyString());
}
if (!evalAllowed) {

View File

@ -212,6 +212,7 @@
"content/base/test/csp/test_CSP_bug916446.html":"observer not working",
"content/base/test/csp/test_CSP_bug909029.html":"observer not working",
"content/base/test/csp/test_policyuri_regression_from_multipolicy.html":"observer not working",
"content/base/test/csp/test_nonce_source.html":"observer not working",
"content/base/test/test_CrossSiteXHR_origin.html":"https not working, bug 907770",
"content/base/test/test_plugin_freezing.html":"",