Bug 886164 - Enforce CSP in sandboxed iframe, r=grobinson

This commit is contained in:
Deian Stefan 2013-11-22 15:12:00 -08:00
parent 0bb6cc2058
commit d4fabf325e
18 changed files with 348 additions and 18 deletions

View File

@ -16,6 +16,7 @@
#include "nsJSPrincipals.h"
#include "nsCOMPtr.h"
#include "nsPrincipal.h"
#include "nsIContentSecurityPolicy.h"
class nsIURI;
@ -53,6 +54,7 @@ public:
virtual ~nsNullPrincipal();
nsCOMPtr<nsIURI> mURI;
nsCOMPtr<nsIContentSecurityPolicy> mCSP;
};
#endif // nsNullPrincipal_h__

View File

@ -149,8 +149,11 @@ nsNullPrincipal::GetHashValue(uint32_t *aResult)
NS_IMETHODIMP
nsNullPrincipal::GetSecurityPolicy(void** aSecurityPolicy)
{
// We don't actually do security policy caching. And it's not like anyone
// can set a security policy for us anyway.
// Leftover from old security model, a "security policy" is a set of
// rules for property access that can override the SOP. Policies are
// associated with origins and since nsNullPinricipals never get the
// same origin twice, it's not possible to specify a "security
// policy" for it. Hence, we do not cache the security policy.
*aSecurityPolicy = nullptr;
return NS_OK;
}
@ -158,8 +161,11 @@ nsNullPrincipal::GetSecurityPolicy(void** aSecurityPolicy)
NS_IMETHODIMP
nsNullPrincipal::SetSecurityPolicy(void* aSecurityPolicy)
{
// We don't actually do security policy caching. And it's not like anyone
// can set a security policy for us anyway.
// Leftover from old security model, a "security policy" is a set of
// rules for property access that can override the SOP. Policies are
// associated with origins and since nsNullPinricipals never get the
// same origin twice, it's not possible to specify a "security
// policy" for it. Hence, we do not cache the security policy.
return NS_OK;
}
@ -172,16 +178,20 @@ nsNullPrincipal::GetURI(nsIURI** aURI)
NS_IMETHODIMP
nsNullPrincipal::GetCsp(nsIContentSecurityPolicy** aCsp)
{
// CSP on a null principal makes no sense
*aCsp = nullptr;
NS_IF_ADDREF(*aCsp = mCSP);
return NS_OK;
}
NS_IMETHODIMP
nsNullPrincipal::SetCsp(nsIContentSecurityPolicy* aCsp)
{
// CSP on a null principal makes no sense
return NS_ERROR_NOT_AVAILABLE;
// If CSP was already set, it should not be destroyed! Instead, it should
// get set anew when a new principal is created.
if (mCSP)
return NS_ERROR_ALREADY_INITIALIZED;
mCSP = aCsp;
return NS_OK;
}
NS_IMETHODIMP

View File

@ -51,9 +51,9 @@ function ContentSecurityPolicy() {
this._request = "";
this._requestOrigin = "";
this._requestPrincipal = "";
this._weakRequestPrincipal = { get : function() { return null; } };
this._referrer = "";
this._docRequest = null;
this._weakDocRequest = { get : function() { return null; } };
CSPdebug("CSP object initialized, no policies to enforce yet");
this._cache = { };
@ -296,7 +296,7 @@ ContentSecurityPolicy.prototype = {
return;
// Save the docRequest for fetching a policy-uri
this._docRequest = aChannel;
this._weakDocRequest = Cu.getWeakReference(aChannel);
// save the document URI (minus <fragment>) and referrer for reporting
let uri = aChannel.URI.cloneIgnoringRef();
@ -307,8 +307,9 @@ ContentSecurityPolicy.prototype = {
this._requestOrigin = uri;
//store a reference to the principal, that can later be used in shouldLoad
this._requestPrincipal = Components.classes["@mozilla.org/scriptsecuritymanager;1"].
getService(Components.interfaces.nsIScriptSecurityManager).getChannelPrincipal(aChannel);
this._weakRequestPrincipal = Cu.getWeakReference(Cc["@mozilla.org/scriptsecuritymanager;1"]
.getService(Ci.nsIScriptSecurityManager)
.getChannelPrincipal(aChannel));
if (aChannel.referrer) {
let referrer = aChannel.referrer.cloneIgnoringRef();
@ -358,13 +359,13 @@ ContentSecurityPolicy.prototype = {
newpolicy = CSPRep.fromStringSpecCompliant(aPolicy,
selfURI,
aReportOnly,
this._docRequest,
this._weakDocRequest.get(),
this);
} else {
newpolicy = CSPRep.fromString(aPolicy,
selfURI,
aReportOnly,
this._docRequest,
this._weakDocRequest.get(),
this);
}
@ -481,8 +482,8 @@ 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(policy);
if (this._docRequest) {
chan.loadGroup = this._docRequest.loadGroup;
if (this._weakDocRequest.get()) {
chan.loadGroup = this._weakDocRequest.get().loadGroup;
}
chan.QueryInterface(Ci.nsIUploadChannel)
@ -501,7 +502,7 @@ ContentSecurityPolicy.prototype = {
.getService(Ci.nsIContentPolicy);
if (contentPolicy.shouldLoad(Ci.nsIContentPolicy.TYPE_CSP_REPORT,
chan.URI, this._requestOrigin,
null, null, null, this._requestPrincipal)
null, null, null, this._weakRequestPrincipal.get())
!= Ci.nsIContentPolicy.ACCEPT) {
continue; // skip unauthorized URIs
}

View File

@ -0,0 +1,15 @@
<html>
<head> <meta charset="utf-8"> </head>
<body>
<!-- sandbox="allow-same-origin" -->
<!-- Content-Security-Policy: default-src 'self' -->
<!-- these should be stopped by CSP -->
<img src="http://example.org/tests/content/base/test/csp/file_CSP.sjs?testid=img_bad&type=img/png"> </img>
<!-- these should load ok -->
<img src="/tests/content/base/test/csp/file_CSP.sjs?testid=img_good&type=img/png" />
<script src='/tests/content/base/test/csp/file_CSP.sjs?testid=scripta_bad&type=text/javascript'></script>
</body>
</html>

View File

@ -0,0 +1 @@
Content-Security-Policy: default-src 'self'

View File

@ -0,0 +1,14 @@
<html>
<head> <meta charset="utf-8"> </head>
<body>
<!-- sandbox -->
<!-- Content-Security-Policy: default-src 'self' -->
<!-- these should be stopped by CSP -->
<img src="http://example.org/tests/content/base/test/csp/file_CSP.sjs?testid=img2_bad&type=img/png"> </img>
<!-- these should load ok -->
<img src="/tests/content/base/test/csp/file_CSP.sjs?testid=img2a_good&type=img/png" />
</body>
</html>

View File

@ -0,0 +1 @@
Content-Security-Policy: default-src 'self'

View File

@ -0,0 +1,12 @@
<html>
<head> <meta charset="utf-8"> </head>
<body>
<!-- sandbox -->
<!-- Content-Security-Policy: default-src 'none' -->
<!-- these should be stopped by CSP -->
<img src="http://example.org/tests/content/base/test/csp/file_CSP.sjs?testid=img3_bad&type=img/png"> </img>
<img src="/tests/content/base/test/csp/file_CSP.sjs?testid=img3a_bad&type=img/png" />
</body>
</html>

View File

@ -0,0 +1 @@
Content-Security-Policy: default-src 'none'

View File

@ -0,0 +1,12 @@
<html>
<head> <meta charset="utf-8"> </head>
<body>
<!-- sandbox -->
<!-- Content-Security-Policy: default-src 'none' -->
<!-- these should be stopped by CSP -->
<img src="http://example.org/tests/content/base/test/csp/file_CSP.sjs?testid=img4_bad&type=img/png"> </img>
<img src="/tests/content/base/test/csp/file_CSP.sjs?testid=img4a_bad&type=img/png" />
</body>
</html>

View File

@ -0,0 +1 @@
Content-Security-Policy: default-src 'none'

View File

@ -0,0 +1,26 @@
<!DOCTYPE HTML>
<html>
<head> <meta charset="utf-8"> </head>
<script type="text/javascript">
function ok(result, desc) {
window.parent.postMessage({ok: result, desc: desc}, "*");
}
function doStuff() {
ok(true, "documents sandboxed with allow-scripts should be able to run inline scripts");
}
</script>
<script src='file_iframe_sandbox_pass.js'></script>
<body onLoad='ok(true, "documents sandboxed with allow-scripts should be able to run script from event listeners");doStuff();'>
I am sandboxed but with only inline "allow-scripts"
<!-- sandbox="allow-scripts" -->
<!-- Content-Security-Policy: default-src 'none' 'unsafe-inline'-->
<!-- these should be stopped by CSP -->
<img src="/tests/content/base/test/csp/file_CSP.sjs?testid=img5_bad&type=img/png" />
<img src="http://example.org/tests/content/base/test/csp/file_CSP.sjs?testid=img5a_bad&type=img/png"> </img>
<script src='/tests/content/base/test/csp/file_CSP.sjs?testid=script5_bad&type=text/javascript'></script>
<script src='http://example.org/tests/content/base/test/csp/file_CSP.sjs?testid=script5a_bad&type=text/javascript'></script>
</body>
</html>

View File

@ -0,0 +1 @@
Content-Security-Policy: default-src 'none' 'unsafe-inline';

View File

@ -0,0 +1,35 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
</head>
<script type="text/javascript">
function ok(result, desc) {
window.parent.postMessage({ok: result, desc: desc}, "*");
}
function doStuff() {
ok(true, "documents sandboxed with allow-scripts should be able to run inline scripts");
document.getElementById('a_form').submit();
// trigger the javascript: url test
sendMouseEvent({type:'click'}, 'a_link');
}
</script>
<script src='file_iframe_sandbox_pass.js'></script>
<body onLoad='ok(true, "documents sandboxed with allow-scripts should be able to run script from event listeners");doStuff();'>
I am sandboxed but with "allow-scripts"
<img src="http://example.org/tests/content/base/test/csp/file_CSP.sjs?testid=img6_bad&type=img/png"> </img>
<script src='http://example.org/tests/content/base/test/csp/file_CSP.sjs?testid=script6_bad&type=text/javascript'></script>
<form method="get" action="file_iframe_sandbox_form_fail.html" id="a_form">
First name: <input type="text" name="firstname">
Last name: <input type="text" name="lastname">
<input type="submit" onclick="doSubmit()" id="a_button">
</form>
<a href = 'javascript:ok(true, "documents sandboxed with allow-scripts should be able to run script from javascript: URLs");' id='a_link'>click me</a>
</body>
</html>

View File

@ -0,0 +1 @@
Content-Security-Policy: default-src 'self' 'unsafe-inline';

View File

@ -68,6 +68,18 @@ support-files =
file_bug836922_npolicies.html^headers^
file_bug836922_npolicies_ro_violation.sjs
file_bug836922_npolicies_violation.sjs
file_bug886164.html
file_bug886164.html^headers^
file_bug886164_2.html
file_bug886164_2.html^headers^
file_bug886164_3.html
file_bug886164_3.html^headers^
file_bug886164_4.html
file_bug886164_4.html^headers^
file_bug886164_5.html
file_bug886164_5.html^headers^
file_bug886164_6.html
file_bug886164_6.html^headers^
file_csp_bug768029.html
file_csp_bug768029.sjs
file_csp_bug773891.html
@ -101,6 +113,7 @@ support-files =
[test_CSP_inlinestyle.html]
[test_bothCSPheaders.html]
[test_bug836922_npolicies.html]
[test_bug886164.html]
[test_csp_redirects.html]
[test_CSP_bug910139.html]
[test_CSP_bug909029.html]

View File

@ -0,0 +1,183 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Bug 886164 - Enforce CSP in sandboxed iframe</title>
<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:200px;height:200px;" id='cspframe' sandbox="allow-same-origin"></iframe>
<iframe style="width:200px;height:200px;" id='cspframe2' sandbox></iframe>
<iframe style="width:200px;height:200px;" id='cspframe3' sandbox="allow-same-origin"></iframe>
<iframe style="width:200px;height:200px;" id='cspframe4' sandbox></iframe>
<iframe style="width:200px;height:200px;" id='cspframe5' sandbox="allow-scripts"></iframe>
<iframe style="width:200px;height:200px;" id='cspframe6' sandbox="allow-same-origin allow-scripts"></iframe>
<script class="testbody" type="text/javascript">
var path = "/tests/content/base/test/csp/";
// These are test results: -1 means it hasn't run,
// true/false is the pass/fail result.
window.tests = {
// sandbox allow-same-origin; 'self'
img_good: -1, // same origin
img_bad: -1, //example.com
// sandbox; 'self'
img2_bad: -1, //example.com
img2a_good: -1, // same origin & is image
// sandbox allow-same-origin; 'none'
img3_bad: -1,
img3a_bad: -1,
// sandbox; 'none'
img4_bad: -1,
img4a_bad: -1,
// sandbox allow-scripts; 'none' 'unsafe-inline'
img5_bad: -1,
img5a_bad: -1,
script5_bad: -1,
script5a_bad: -1,
// sandbox allow-same-origin allow-scripts; 'self' 'unsafe-inline'
img6_bad: -1,
script6_bad: -1,
};
// a postMessage handler that is used by sandboxed iframes without
// 'allow-same-origin' to communicate pass/fail back to this main page.
// it expects to be called with an object like {ok: true/false, desc:
// <description of the test> which it then forwards to ok()
window.addEventListener("message", receiveMessage, false);
function receiveMessage(event)
{
ok_wrapper(event.data.ok, event.data.desc);
}
var cspTestsDone = false;
var iframeSandboxTestsDone = false;
// iframe related
var completedTests = 0;
var passedTests = 0;
function ok_wrapper(result, desc) {
ok(result, desc);
completedTests++;
if (result) {
passedTests++;
}
if (completedTests === 5) {
iframeSandboxTestsDone = true;
if (cspTestsDone) {
SimpleTest.finish();
}
}
}
//csp related
// This is used to watch the blocked data bounce off CSP and allowed data
// get sent out to the wire.
function examiner() {
SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
SpecialPowers.addObserver(this, "http-on-modify-request", false);
}
examiner.prototype = {
observe: function(subject, topic, data) {
// subject should be an nsURI, and should be either allowed or blocked.
if (!SpecialPowers.can_QI(subject))
return;
var testpat = new RegExp("testid=([a-z0-9_]+)");
//_good things better be allowed!
//_bad things better be stopped!
if (topic === "http-on-modify-request") {
//these things were allowed by CSP
var asciiSpec = SpecialPowers.getPrivilegedProps(SpecialPowers.do_QueryInterface(subject, "nsIHttpChannel"), "URI.asciiSpec");
if (!testpat.test(asciiSpec)) return;
var testid = testpat.exec(asciiSpec)[1];
window.testResult(testid,
/_good/.test(testid),
asciiSpec + " allowed by csp");
}
if(topic === "csp-on-violate-policy") {
//these were blocked... record that they were blocked
var asciiSpec = SpecialPowers.getPrivilegedProps(SpecialPowers.do_QueryInterface(subject, "nsIURI"), "asciiSpec");
if (!testpat.test(asciiSpec)) return;
var testid = testpat.exec(asciiSpec)[1];
window.testResult(testid,
/_bad/.test(testid),
asciiSpec + " blocked by \"" + data + "\"");
}
},
// must eventually call this to remove the listener,
// or mochitests might get borked.
remove: function() {
SpecialPowers.removeObserver(this, "csp-on-violate-policy");
SpecialPowers.removeObserver(this, "http-on-modify-request");
}
}
window.examiner = new examiner();
window.testResult = function(testname, result, msg) {
//test already complete.... forget it... remember the first result.
if (window.tests[testname] != -1)
return;
window.tests[testname] = result;
ok(result, testname + ' test: ' + msg);
// if any test is incomplete, keep waiting
for (var v in window.tests)
if(tests[v] == -1)
return;
// ... otherwise, finish
window.examiner.remove();
cspTestsDone = true;
if (iframeSandboxTestsDone) {
SimpleTest.finish();
}
}
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv(
{'set':[["security.csp.speccompliant", 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_bug886164.html';
document.getElementById('cspframe2').src = 'file_bug886164_2.html';
document.getElementById('cspframe3').src = 'file_bug886164_3.html';
document.getElementById('cspframe4').src = 'file_bug886164_4.html';
document.getElementById('cspframe5').src = 'file_bug886164_5.html';
document.getElementById('cspframe6').src = 'file_bug886164_6.html';
});
</script>
</pre>
</body>
</html>

View File

@ -209,6 +209,7 @@
"content/base/test/csp/test_CSP_frameancestors.html":"observer not working",
"content/base/test/csp/test_CSP.html":"observer not working",
"content/base/test/csp/test_bug836922_npolicies.html":"observer not working",
"content/base/test/csp/test_bug886164.html":"observer not working",
"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",