Bug 515458 - "(CSP) Implement frame ancestor check" [r=jst ui-r=johnath]

This commit is contained in:
Sid Stamm 2010-02-05 14:08:27 -06:00
parent 629bb5afcc
commit 26ad4e5a5b
13 changed files with 328 additions and 0 deletions

View file

@ -62,3 +62,4 @@ externalProtocolChkMsg=Remember my choice for all links of this type.
externalProtocolLaunchBtn=Launch application
malwareBlocked=The site at %S has been reported as an attack site and has been blocked based on your security preferences.
phishingBlocked=The web site at %S has been reported as a web forgery designed to trick users into sharing personal or financial information.
cspFrameAncestorBlocked=This page has a content security policy that prevents it from being embedded in this way.

View file

@ -163,6 +163,9 @@ be temporary, and you can try again later.</li>
<p>These types of web forgeries are used in scams known as phishing attacks, in which fraudulent web pages and emails are used to imitate sources you may trust.</p>
">
<!ENTITY cspFrameAncestorBlocked.title "Blocked by Content Security Policy">
<!ENTITY cspFrameAncestorBlocked.longDesc "<p>&brandShortName; prevented this page from loading in this way because the page has a content security policy that disallows it.</p>">
<!ENTITY securityOverride.linkText "Or you can add an exception…">
<!ENTITY securityOverride.getMeOutOfHereButton "Get me out of here!">
<!ENTITY securityOverride.exceptionButtonLabel "Add Exception…">

View file

@ -87,4 +87,8 @@
#define NS_FINDBROADCASTER_AWAIT_OVERLAYS \
NS_ERROR_GENERATE_SUCCESS(NS_ERROR_MODULE_CONTENT, 14)
/* Error codes for CSP */
#define NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION \
NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_SECURITY, 99)
#endif // nsContentErrors_h___

View file

@ -2345,6 +2345,25 @@ nsDocument::InitCSP()
#endif
}
// Check for frame-ancestor violation
nsCOMPtr<nsIDocShell> docShell = do_QueryReferent(mDocumentContainer);
if (docShell) {
PRBool safeAncestry = false;
// PermitsAncestry sends violation reports when necessary
rv = mCSP->PermitsAncestry(docShell, &safeAncestry);
NS_ENSURE_SUCCESS(rv, rv);
if (!safeAncestry) {
#ifdef PR_LOGGING
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
("CSP doesn't like frame's ancestry, not loading."));
#endif
// stop! ERROR page!
mChannel->Cancel(NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION);
}
}
//Copy into principal
nsIPrincipal* principal = GetPrincipal();

View file

@ -337,6 +337,10 @@ _TEST_FILES = test_bug5141.html \
file_CSP.sjs \
file_CSP_main.html \
file_CSP_main.js \
test_CSP_frameancestors.html \
file_CSP_frameancestors.sjs \
file_CSP_frameancestors_main.html \
file_CSP_frameancestors_main.js \
test_bug540854.html \
bug540854.sjs \
$(NULL)

View file

@ -0,0 +1,57 @@
// SJS file for CSP frame ancestor mochitests
function handleRequest(request, response)
{
var query = {};
request.queryString.split('&').forEach(function (val) {
var [name, value] = val.split('=');
query[name] = unescape(value);
});
var isPreflight = request.method == "OPTIONS";
//avoid confusing cache behaviors
response.setHeader("Cache-Control", "no-cache", false);
// grab the desired policy from the query, and then serve a page
if (query['csp'])
response.setHeader("X-Content-Security-Policy",
unescape(query['csp']),
false);
if (query['scriptedreport']) {
// spit back a script that records that the page loaded
response.setHeader("Content-Type", "text/javascript", false);
response.write('netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");');
if (query['double'])
response.write('window.parent.parent.parent.frameLoaded("' + query['scriptedreport'] + '", ' +
'window.location.toString());');
else
response.write('window.parent.parent.frameLoaded("' + query['scriptedreport'] + '", ' +
'window.location.toString());');
} else if (query['internalframe']) {
// spit back an internal iframe (one that might be blocked)
response.setHeader("Content-Type", "text/html", false);
response.write('<html><head>');
if (query['double'])
response.write('<script src="file_CSP_frameancestors.sjs?double=1&scriptedreport=' + query['testid'] + '"></script>');
else
response.write('<script src="file_CSP_frameancestors.sjs?scriptedreport=' + query['testid'] + '"></script>');
response.write('</head><body>');
response.write(unescape(query['internalframe']));
response.write('</body></html>');
} else if (query['externalframe']) {
// spit back an internal iframe (one that won't be blocked, and probably
// has no CSP)
response.setHeader("Content-Type", "text/html", false);
response.write('<html><head>');
response.write('</head><body>');
response.write(unescape(query['externalframe']));
response.write('</body></html>');
} else {
// default case: error.
response.setHeader("Content-Type", "text/html", false);
response.write('<html><body>');
response.write("ERROR: not sure what to serve.");
response.write('</body></html>');
}
}

View file

@ -0,0 +1,44 @@
<html>
<head>
<title>CSP frame ancestors tests</title>
<!-- this page shouldn't have a CSP, just the sub-pages. -->
<script src='file_CSP_frameancestors_main.js'></script>
</head>
<body>
<!-- These iframes will get populated by the attached javascript. -->
<tt> aa_allow: /* innermost frame allows a */</tt><br/>
<iframe id='aa_allow'></iframe><br/>
<tt> aa_block: /* innermost frame denies a */</tt><br/>
<iframe id='aa_block'></iframe><br/>
<tt> ab_allow: /* innermost frame allows a */</tt><br/>
<iframe id='ab_allow'></iframe><br/>
<tt> ab_block: /* innermost frame denies a */</tt><br/>
<iframe id='ab_block'></iframe><br/>
<tt> aba_allow: /* innermost frame allows b,a */</tt><br/>
<iframe id='aba_allow'></iframe><br/>
<tt> aba_block: /* innermost frame denies b */</tt><br/>
<iframe id='aba_block'></iframe><br/>
<tt> aba2_block: /* innermost frame denies a */</tt><br/>
<iframe id='aba2_block'></iframe><br/>
<tt> abb_allow: /* innermost frame allows b,a */</tt><br/>
<iframe id='abb_allow'></iframe><br/>
<tt> abb_block: /* innermost frame denies b */</tt><br/>
<iframe id='abb_block'></iframe><br/>
<tt> abb2_block: /* innermost frame denies a */</tt><br/>
<iframe id='abb2_block'></iframe><br/>
</body>
</html>

View file

@ -0,0 +1,65 @@
// Script to populate the test frames in the frame ancestors mochitest.
//
function setupFrames() {
var $ = function(v) { return document.getElementById(v); }
var base = {
self: '/tests/content/base/test/file_CSP_frameancestors.sjs',
a: 'http://localhost:8888/tests/content/base/test/file_CSP_frameancestors.sjs',
b: 'http://example.com/tests/content/base/test/file_CSP_frameancestors.sjs'
};
var host = { a: 'http://localhost:8888', b: 'http://example.com:80' };
var innerframeuri = null;
var elt = null;
elt = $('aa_allow');
elt.src = base.a + "?testid=aa_allow&internalframe=aa_a&csp=" +
escape("allow 'none'; frame-ancestors " + host.a + "; script-src 'self'");
elt = $('aa_block');
elt.src = base.a + "?testid=aa_block&internalframe=aa_b&csp=" +
escape("allow 'none'; frame-ancestors 'none'; script-src 'self'");
elt = $('ab_allow');
elt.src = base.b + "?testid=ab_allow&internalframe=ab_a&csp=" +
escape("allow 'none'; frame-ancestors " + host.a + "; script-src 'self'");
elt = $('ab_block');
elt.src = base.b + "?testid=ab_block&internalframe=ab_b&csp=" +
escape("allow 'none'; frame-ancestors 'none'; script-src 'self'");
/* .... two-level framing */
elt = $('aba_allow');
innerframeuri = base.a + "?testid=aba_allow&double=1&internalframe=aba_a&csp=" +
escape("allow 'none'; frame-ancestors " + host.a + " " + host.b + "; script-src 'self'");
elt.src = base.b + "?externalframe=" + escape('<iframe src="' + innerframeuri + '"></iframe>');
elt = $('aba_block');
innerframeuri = base.a + "?testid=aba_allow&double=1&internalframe=aba_b&csp=" +
escape("allow 'none'; frame-ancestors " + host.a + "; script-src 'self'");
elt.src = base.b + "?externalframe=" + escape('<iframe src="' + innerframeuri + '"></iframe>');
elt = $('aba2_block');
innerframeuri = base.a + "?testid=aba_allow&double=1&internalframe=aba2_b&csp=" +
escape("allow 'none'; frame-ancestors " + host.b + "; script-src 'self'");
elt.src = base.b + "?externalframe=" + escape('<iframe src="' + innerframeuri + '"></iframe>');
elt = $('abb_allow');
innerframeuri = base.b + "?testid=abb_allow&double=1&internalframe=abb_a&csp=" +
escape("allow 'none'; frame-ancestors " + host.a + " " + host.b + "; script-src 'self'");
elt.src = base.b + "?externalframe=" + escape('<iframe src="' + innerframeuri + '"></iframe>');
elt = $('abb_block');
innerframeuri = base.b + "?testid=abb_allow&double=1&internalframe=abb_b&csp=" +
escape("allow 'none'; frame-ancestors " + host.a + "; script-src 'self'");
elt.src = base.b + "?externalframe=" + escape('<iframe src="' + innerframeuri + '"></iframe>');
elt = $('abb2_block');
innerframeuri = base.b + "?testid=abb_allow&double=1&internalframe=abb2_b&csp=" +
escape("allow 'none'; frame-ancestors " + host.b + "; script-src 'self'");
elt.src = base.b + "?externalframe=" + escape('<iframe src="' + innerframeuri + '"></iframe>');
}
window.addEventListener('load', setupFrames, false);

View file

@ -0,0 +1,120 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for Content Security Policy Frame Ancestors directive</title>
<script type="text/javascript" src="/MochiKit/packed.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<iframe style="width:100%;height:300px;" id='cspframe'></iframe>
<script class="testbody" type="text/javascript">
var path = "/tests/content/base/test/";
// These are test results: -1 means it hasn't run,
// true/false is the pass/fail result.
var framesThatShouldLoad = {
aa_allow: -1, /* innermost frame allows a */
//aa_block: -1, /* innermost frame denies a */
ab_allow: -1, /* innermost frame allows a */
//ab_block: -1, /* innermost frame denies a */
aba_allow: -1, /* innermost frame allows b,a */
//aba_block: -1, /* innermost frame denies b */
//aba2_block: -1, /* innermost frame denies a */
abb_allow: -1, /* innermost frame allows b,a */
//abb_block: -1, /* innermost frame denies b */
//abb2_block: -1, /* innermost frame denies a */
};
var expectedViolationsLeft = 6;
// This is used to watch the blocked data bounce off CSP and allowed data
// get sent out to the wire.
function examiner() {
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
this.obsvc = Components.classes['@mozilla.org/observer-service;1']
.getService(Components.interfaces.nsIObserverService);
this.obsvc.addObserver(this, "csp-on-violate-policy", false);
}
examiner.prototype = {
observe: function(subject, topic, data) {
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
// subject should be an nsURI, and should be either allowed or blocked.
if(!subject.QueryInterface) return;
if (topic === "csp-on-violate-policy") {
//these were blocked... record that they were blocked
var uri = subject.QueryInterface(Components.interfaces.nsIURI);
window.frameBlocked(uri.asciiSpec, data);
}
},
// must eventually call this to remove the listener,
// or mochitests might get borked.
remove: function() {
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
this.obsvc.removeObserver(this, "csp-on-violate-policy");
}
}
// called when a frame is loaded
// -- if it's not enumerated above, it should not load!
var frameLoaded = function(testname, uri) {
//test already complete.... forget it... remember the first result.
if (window.framesThatShouldLoad[testname] != -1)
return;
if (typeof window.framesThatShouldLoad[testname] === 'undefined') {
// uh-oh, we're not expecting this frame to load!
ok(false, testname + ' framed site should not have loaded: ' + uri);
} else {
framesThatShouldLoad[testname] = true;
ok(true, testname + ' framed site when allowed by csp (' + uri + ')');
}
checkTestResults();
}
// called when a frame is blocked
// -- we can't determine *which* frame was blocked, but at least we can count them
var frameBlocked = function(uri, policy) {
ok(true, 'a CSP policy blocked frame from being loaded: ' + policy);
expectedViolationsLeft--;
checkTestResults();
}
// Check to see if all the tests have run
var checkTestResults = function() {
// if any test is incomplete, keep waiting
for (var v in framesThatShouldLoad)
if(framesThatShouldLoad[v] == -1)
return;
if (expectedViolationsLeft > 0)
return;
// ... otherwise, finish
window.examiner.remove();
SimpleTest.finish();
}
//////////////////////////////////////////////////////////////////////
// set up and go
window.examiner = new examiner();
SimpleTest.waitForExplicitFinish();
// save this for last so that our listeners are registered.
// ... this loads the testbed of good and bad requests.
document.getElementById('cspframe').src = 'file_CSP_frameancestors_main.html';
</script>
</pre>
</body>
</html>

View file

@ -3622,6 +3622,11 @@ nsDocShell::DisplayLoadError(nsresult aError, nsIURI *aURI,
formatStrCount = 1;
error.AssignLiteral("netTimeout");
}
else if (NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION == aError) {
// CSP error
cssClass.AssignLiteral("neterror");
error.AssignLiteral("cspFrameAncestorBlocked");
}
else if (NS_ERROR_GET_MODULE(aError) == NS_ERROR_MODULE_SECURITY) {
nsCOMPtr<nsINSSErrorsService> nsserr =
do_GetService(NS_NSS_ERRORS_SERVICE_CONTRACTID);

View file

@ -313,6 +313,7 @@
<h1 id="et_nssFailure2">&nssFailure2.title;</h1>
<h1 id="et_nssBadCert">&nssBadCert.title;</h1>
<h1 id="et_malwareBlocked">&malwareBlocked.title;</h1>
<h1 id="et_cspFrameAncestorBlocked">&cspFrameAncestorBlocked.title;</h1>
</div>
<div id="errorDescriptionsContainer">
<div id="ed_generic">&generic.longDesc;</div>
@ -335,6 +336,7 @@
<div id="ed_nssFailure2">&nssFailure2.longDesc;</div>
<div id="ed_nssBadCert">&nssBadCert.longDesc2;</div>
<div id="ed_malwareBlocked">&malwareBlocked.longDesc;</div>
<div id="ed_cspFrameAncestorBlocked">&cspFrameAncestorBlocked.longDesc;</div>
</div>
</div>

View file

@ -61,3 +61,4 @@ externalProtocolChkMsg=Remember my choice for all links of this type.
externalProtocolLaunchBtn=Launch application
malwareBlocked=The site at %S has been reported as an attack site and has been blocked based on your security preferences.
phishingBlocked=The web site at %S has been reported as a web forgery designed to trick users into sharing personal or financial information.
cspFrameAncestorBlocked=This page has a content security policy that prevents it from being embedded in this way.

View file

@ -80,6 +80,9 @@
<p>These types of web forgeries are used in scams known as phishing attacks, in which fraudulent web pages and emails are used to imitate sources you may trust.</p>
">
<!ENTITY cspFrameAncestorBlocked.title "Blocked by Content Security Policy">
<!ENTITY cspFrameAncestorBlocked.longDesc "<p>The browser prevented this page from loading in this way because the page has a content security policy that disallows it.</p>">
<!-- Include app-specific error messages - do not change this in localization!
Some applications might override netErrorApp.dtd with their specific version,
this inclusion needs to be intact for that approach to work correctly. -->