Bug 1223647: CSP erroneously inherited into dedicated workers. r=ckerschb

This commit is contained in:
Jonas Sicking 2015-11-10 21:16:12 -08:00
parent b38ab6a38f
commit ea6cf63b0f
14 changed files with 160 additions and 179 deletions

View File

@ -1690,8 +1690,10 @@ nsXMLHttpRequest::Open(const nsACString& inMethod, const nsACString& url,
nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
}
// If we have the document, use it
if (doc) {
// If we have the document, use it. Unfortunately, for dedicated workers
// 'doc' ends up being the parent document, which is not the document
// that we want to use. So make sure to avoid using 'doc' in that situation.
if (doc && doc->NodePrincipal() == mPrincipal) {
rv = NS_NewChannel(getter_AddRefs(mChannel),
uri,
doc,

View File

@ -1 +1 @@
Content-Security-Policy: default-src 'self' ; style-src 'unsafe-inline' 'self'
Content-Security-Policy: default-src 'self' blob: ; style-src 'unsafe-inline' 'self'

View File

@ -1,16 +1,28 @@
// some javascript for the CSP XHR tests
//
function doXHR(uri) {
try {
var xhr = new XMLHttpRequest();
xhr.open("GET", uri);
xhr.send();
} catch(ex) {}
}
doXHR("http://mochi.test:8888/tests/dom/security/test/csp/file_CSP.sjs?testid=xhr_good");
doXHR("http://example.com/tests/dom/security/test/csp/file_CSP.sjs?testid=xhr_bad");
fetch("http://mochi.test:8888/tests/dom/security/test/csp/file_CSP.sjs?testid=fetch_good");
fetch("http://example.com/tests/dom/security/test/csp/file_CSP.sjs?testid=fetch_bad");
navigator.sendBeacon("http://mochi.test:8888/tests/dom/security/test/csp/file_CSP.sjs?testid=beacon_good");
try {
var xhr_good = new XMLHttpRequest();
var xhr_good_uri ="http://mochi.test:8888/tests/dom/security/test/csp/file_CSP.sjs?testid=xhr_good";
xhr_good.open("GET", xhr_good_uri, true);
xhr_good.send(null);
} catch(e) {}
navigator.sendBeacon("http://example.com/tests/dom/security/test/csp/file_CSP.sjs?testid=beacon_bad");
} catch(ex) {}
try {
var xhr_bad = new XMLHttpRequest();
var xhr_bad_uri ="http://example.com/tests/dom/security/test/csp/file_CSP.sjs?testid=xhr_bad";
xhr_bad.open("GET", xhr_bad_uri, true);
xhr_bad.send(null);
} catch(e) {}
new Worker("file_main_worker.js").postMessage({inherited : false});
var blobxhr = new XMLHttpRequest();
blobxhr.open("GET", "file_main_worker.js")
blobxhr.responseType = "blob";
blobxhr.send();
blobxhr.onload = () => {
new Worker(URL.createObjectURL(blobxhr.response)).postMessage({inherited : true});
}

View File

@ -0,0 +1,28 @@
function doXHR(uri) {
try {
var xhr = new XMLHttpRequest();
xhr.open("GET", uri);
xhr.send();
} catch(ex) {}
}
var sameBase = "http://mochi.test:8888/tests/dom/security/test/csp/file_CSP.sjs?testid=";
var crossBase = "http://example.com/tests/dom/security/test/csp/file_CSP.sjs?testid=";
onmessage = (e) => {
for (base of [sameBase, crossBase]) {
var prefix;
var suffix;
if (e.data.inherited) {
prefix = base + "worker_inherited_"
suffix = base == sameBase ? "_good" : "_bad";
}
else {
prefix = base + "worker_"
suffix = base == sameBase ? "_same_good" : "_cross_good";
}
doXHR(prefix + "xhr" + suffix);
fetch(prefix + "fetch" + suffix);
try { importScripts(prefix + "script" + suffix); } catch(ex) {}
}
}

View File

@ -11,17 +11,18 @@ var thisSite = "http://mochi.test:8888";
var otherSite = "http://example.com";
var page = "/tests/dom/security/test/csp/file_redirects_page.sjs";
var tests = { "font-src": thisSite+page+"?testid=font-src&csp=1",
"frame-src": thisSite+page+"?testid=frame-src&csp=1",
"img-src": thisSite+page+"?testid=img-src&csp=1",
"media-src": thisSite+page+"?testid=media-src&csp=1",
"object-src": thisSite+page+"?testid=object-src&csp=1",
"script-src": thisSite+page+"?testid=script-src&csp=1",
"style-src": thisSite+page+"?testid=style-src&csp=1",
"worker": thisSite+page+"?testid=worker&csp=1",
"xhr-src": thisSite+page+"?testid=xhr-src&csp=1",
"script-src-from-worker": thisSite+page+"?testid=script-src-from-worker&csp=1",
"img-src-from-css": thisSite+page+"?testid=img-src-from-css&csp=1",
var tests = { "font-src": thisSite+page+"?testid=font-src",
"frame-src": thisSite+page+"?testid=frame-src",
"img-src": thisSite+page+"?testid=img-src",
"media-src": thisSite+page+"?testid=media-src",
"object-src": thisSite+page+"?testid=object-src",
"script-src": thisSite+page+"?testid=script-src",
"style-src": thisSite+page+"?testid=style-src",
"worker": thisSite+page+"?testid=worker",
"xhr-src": thisSite+page+"?testid=xhr-src",
"from-worker": thisSite+page+"?testid=from-worker",
"from-blob-worker": thisSite+page+"?testid=from-blob-worker",
"img-src-from-css": thisSite+page+"?testid=img-src-from-css",
};
var container = document.getElementById("container");

View File

@ -14,15 +14,13 @@ function handleRequest(request, response)
var resource = "/tests/dom/security/test/csp/file_redirects_resource.sjs";
// CSP header value
if (query["csp"] == 1) {
var additional = ""
if (query['testid'] == "worker") {
additional = "; script-src 'self' 'unsafe-inline'";
}
response.setHeader("Content-Security-Policy",
"default-src 'self' ; style-src 'self' 'unsafe-inline'" + additional,
false);
var additional = ""
if (query['testid'] == "worker") {
additional = "; script-src 'self' 'unsafe-inline'";
}
response.setHeader("Content-Security-Policy",
"default-src 'self' blob: ; style-src 'self' 'unsafe-inline'" + additional,
false);
// downloadable font that redirects to another site
if (query["testid"] == "font-src") {
@ -90,13 +88,27 @@ function handleRequest(request, response)
return;
}
if (query["testid"] == "script-src-from-worker") {
if (query["testid"] == "from-worker") {
// loads a script; launches a worker; that worker uses importscript; which then gets redirected
// So it's:
// <script "res=loadWorkerThatImports">
// .. loads Worker("res=importScriptWorker")
// <script src="res=loadWorkerThatMakesRequests">
// .. loads Worker("res=makeRequestsWorker")
// .. calls importScript("res=script")
response.write('<script src="'+resource+'?res=loadWorkerThatImports&id=script-src-redir-from-worker"></script>');
// .. calls xhr("res=xhr-resp")
// .. calls fetch("res=xhr-resp")
response.write('<script src="'+resource+'?res=loadWorkerThatMakesRequests&id=from-worker"></script>');
return;
}
if (query["testid"] == "from-blob-worker") {
// loads a script; launches a worker; that worker uses importscript; which then gets redirected
// So it's:
// <script src="res=loadBlobWorkerThatMakesRequests">
// .. loads Worker("res=makeRequestsWorker")
// .. calls importScript("res=script")
// .. calls xhr("res=xhr-resp")
// .. calls fetch("res=xhr-resp")
response.write('<script src="'+resource+'?res=loadBlobWorkerThatMakesRequests&id=from-blob-worker"></script>');
return;
}
}

View File

@ -102,29 +102,45 @@ function handleRequest(request, response)
// script that loads an internal worker that uses importScripts on a redirect
// to an external script.
if (query["res"] == "loadWorkerThatImports") {
if (query["res"] == "loadWorkerThatMakesRequests") {
// this creates a worker (same origin) that imports a redirecting script.
let workerURL = thisSite + resource + '?res=importScriptWorker&id=' + query["id"];
let workerURL = thisSite + resource + '?res=makeRequestsWorker&id=' + query["id"];
response.setHeader("Content-Type", "application/javascript", false);
response.write("var w=new Worker('" + workerURL + "'); w.onmessage=function(event){ alert(event.data); }");
response.write("new Worker('" + workerURL + "');");
return;
}
// script that loads an internal worker that uses importScripts on a redirect
// to an external script.
if (query["res"] == "loadBlobWorkerThatMakesRequests") {
// this creates a worker (same origin) that imports a redirecting script.
let workerURL = thisSite + resource + '?res=makeRequestsWorker&id=' + query["id"];
response.setHeader("Content-Type", "application/javascript", false);
response.write("var x = new XMLHttpRequest(); x.open('GET', '" + workerURL + "'); ");
response.write("x.responseType = 'blob'; x.send(); ");
response.write("x.onload = () => { new Worker(URL.createObjectURL(x.response)); };");
return;
}
// source for a worker that simply calls importScripts on a script that
// redirects.
if (query["res"] == "importScriptWorker") {
if (query["res"] == "makeRequestsWorker") {
// this is code for a worker that imports a redirected script.
let scriptURL = thisSite + resource + "?redir=other&res=script&id=" + query["id"];
let scriptURL = thisSite + resource + "?redir=other&res=script&id=script-src-redir-" + query["id"];
let xhrURL = thisSite + resource + "?redir=other&res=xhr-resp&id=xhr-src-redir-" + query["id"];
let fetchURL = thisSite + resource + "?redir=other&res=xhr-resp&id=fetch-src-redir-" + query["id"];
response.setHeader("Content-Type", "application/javascript", false);
response.write("importScripts('" + scriptURL + "');");
response.write("try { importScripts('" + scriptURL + "'); } catch(ex) {} ");
response.write("var x = new XMLHttpRequest(); x.open('GET', '" + xhrURL + "'); x.send();");
response.write("fetch('" + fetchURL + "');");
return;
}
// script that invokes XHR
if (query["res"] == "xhr") {
response.setHeader("Content-Type", "application/javascript", false);
var resp = 'var x = new XMLHttpRequest();x.open("GET", "' + otherSite +
resource+'?res=xhr-resp&testid=xhr-src-redir", false);\n' +
var resp = 'var x = new XMLHttpRequest();x.open("GET", "' + thisSite +
resource+'?redir=other&res=xhr-resp&id=xhr-src-redir", false);\n' +
'x.send(null);';
response.write(resp);
return;

View File

@ -1,9 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Bug 949706 - CSP: Correct handling of web workers importing scripts that get redirected</title>
</head>
<body>
<script src="file_worker_redirect.sjs?stage_0_script_loads_worker"></script>
</body>
</html>

View File

@ -1,37 +0,0 @@
// testserver customized for the needs of:
// Bug 949706 - CSP: Correct handling of web workers importing scripts that get redirected
function handleRequest(request, response)
{
response.setHeader("Cache-Control", "no-cache", false);
response.setHeader("Content-Type", "text/html", false);
var query = request.queryString;
if (query === "stage_0_script_loads_worker") {
var newWorker =
"var myWorker = new Worker(\"file_worker_redirect.sjs?stage_1_worker_import_scripts\");" +
"myWorker.onmessage = function (event) { parent.checkResult(\"allowed\"); };" +
"myWorker.onerror = function (event) { parent.checkResult(\"blocked\"); };";
response.write(newWorker);
return;
}
if (query === "stage_1_worker_import_scripts") {
response.write("importScripts(\"file_worker_redirect.sjs?stage_2_redirect_imported_script\");");
return;
}
if (query === "stage_2_redirect_imported_script") {
var newLocation =
"http://test1.example.com/tests/dom/security/test/csp/file_worker_redirect.sjs?stage_3_target_script";
response.setStatusLine("1.1", 302, "Found");
response.setHeader("Location", newLocation, false);
return;
}
if (query === "stage_3_target_script") {
response.write("postMessage(\"imported script loaded\");");
return;
}
}

View File

@ -40,6 +40,7 @@ support-files =
file_main.html
file_main.html^headers^
file_main.js
file_main_worker.js
file_web_manifest.html
file_web_manifest_remote.html
file_web_manifest_https.html
@ -115,8 +116,6 @@ support-files =
file_multi_policy_injection_bypass_2.html^headers^
file_null_baseuri.html
file_form-action.html
file_worker_redirect.html
file_worker_redirect.sjs
file_referrerdirective.html
referrerdirective.sjs
file_upgrade_insecure.html
@ -199,7 +198,6 @@ skip-if = buildapp == 'b2g' # intermittent orange (bug 1028490)
[test_referrerdirective.html]
skip-if = buildapp == 'b2g' #no ssl support
[test_dual_header.html]
[test_worker_redirect.html]
[test_upgrade_insecure.html]
# no ssl support as well as websocket tests do not work (see test_websocket.html)
skip-if = buildapp == 'b2g' || buildapp == 'mulet' || toolkit == 'gonk' || toolkit == 'android'

View File

@ -25,6 +25,22 @@ window.tests = {
script_bad: -1,
xhr_good: -1,
xhr_bad: -1,
fetch_good: -1,
fetch_bad: -1,
beacon_good: -1,
beacon_bad: -1,
worker_xhr_same_good: -1,
worker_xhr_cross_good: -1,
worker_fetch_same_good: -1,
worker_fetch_cross_good: -1,
worker_script_same_good: -1,
worker_script_cross_good: -1,
worker_inherited_xhr_good: -1,
worker_inherited_xhr_bad: -1,
worker_inherited_fetch_good: -1,
worker_inherited_fetch_bad: -1,
worker_inherited_script_good: -1,
worker_inherited_script_bad: -1,
media_good: -1,
media_bad: -1,
font_good: -1,
@ -81,10 +97,11 @@ examiner.prototype = {
window.examiner = new examiner();
window.testResult = function(testname, result, msg) {
//test already complete.... forget it... remember the first result.
// test already complete.... forget it... remember the first result.
if (window.tests[testname] != -1)
return;
ok(testname in window.tests, "It's a real test");
window.tests[testname] = result;
is(result, true, testname + ' test: ' + msg);

View File

@ -86,10 +86,16 @@ var testExpectedResults = { "font-src": true,
"worker-redir": false,
"xhr-src": true,
"xhr-src-redir": false,
"script-src-from-worker": true, /* test runs */
"script-src-redir-from-worker": false, /* redir is blocked */
"img-src-from-css": true, /* test runs */
"img-src-redir-from-css": false, /* redir is blocked */
"from-worker": true,
"script-src-redir-from-worker": true, /* redir is allowed since policy isn't inherited */
"xhr-src-redir-from-worker": true, /* redir is allowed since policy isn't inherited */
"fetch-src-redir-from-worker": true, /* redir is allowed since policy isn't inherited */
"from-blob-worker": true,
"script-src-redir-from-blob-worker": false,
"xhr-src-redir-from-blob-worker": false,
"fetch-src-redir-from-blob-worker": false,
"img-src-from-css": true,
"img-src-redir-from-css": false,
};
// takes the name of the test, the URL that was tested, and whether the

View File

@ -1,76 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Bug 949706 - CSP: Correct handling of web workers importing scripts that get redirected</title>
<!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<div id="content" style="visibility: hidden">
<iframe style="width:100%;" id="testframe"></iframe>
</div>
<script class="testbody" type="text/javascript">
/* Description of the test:
* We load a page that loads a script which then instantiates a web worker,
* where that web worker then imports a script which gets redirected.
* We verify that the CSP applies correctly after the imported script of
* the worker gets redirected. More specifically, the test works as follows:
*
* test_worker_redirect.html
* -> loads file_worker_redirect.html file into iframe
* -> loads worker file_worker_redirect.sjs?stage_0_script_loads_worker
* -> creates script file_worker_redirect.sjs?stage_1_worker_import_scripts
* -> redirects script file_worker_redirect.sjs?stage_2_redirect_imported_script
* -> loads target script file_worker_redirect.sjs?stage_3_target_script
*
* Please note that we have to use 'unsafe-eval' in the policy
* so that workers are actually permitted by the CSP.
*
* The main test is loaded using:
* http://mochi.test:8888
* where the imported script gets redirected to:
* http://test1.example.com
*/
var tests = [
{
policy: "default-src 'self'; script-src 'self' 'unsafe-eval'; child-src 'self' http://test1.example.com;",
expected: "allowed"
},
{
policy: "default-src 'self'; script-src 'self' 'unsafe-eval'; child-src 'self';",
expected: "blocked",
},
];
var counter = 0;
var curTest;
function checkResult(aResult) {
is(aResult, curTest.expected, "Should be (" + curTest.expected + ") in Test " + counter + "!");
loadNextTest();
}
function loadNextTest() {
if (counter == tests.length) {
SimpleTest.finish();
return;
}
curTest = tests[counter++];
var src = "file_testserver.sjs";
// append the file that should be served
src += "?file=" + escape("tests/dom/security/test/csp/file_worker_redirect.html");
// append the CSP that should be used to serve the file
src += "&csp=" + escape(curTest.policy);
document.getElementById("testframe").src = src;
}
SimpleTest.waitForExplicitFinish();
loadNextTest();
</script>
</body>
</html>

View File

@ -122,6 +122,14 @@ ChannelFromScriptURL(nsIPrincipal* principal,
return NS_ERROR_DOM_SYNTAX_ERR;
}
// If we have the document, use it. Unfortunately, for dedicated workers
// 'parentDoc' ends up being the parent document, which is not the document
// that we want to use. So make sure to avoid using 'parentDoc' in that
// situation.
if (parentDoc && parentDoc->NodePrincipal() != principal) {
parentDoc = nullptr;
}
int16_t shouldLoad = nsIContentPolicy::ACCEPT;
rv = NS_CheckContentLoadPolicy(aContentPolicyType, uri,
principal, parentDoc,
@ -164,8 +172,11 @@ ChannelFromScriptURL(nsIPrincipal* principal,
aLoadFlags |= nsIChannel::LOAD_CLASSIFY_URI;
nsCOMPtr<nsIChannel> channel;
// If we have the document, use it
if (parentDoc) {
// If we have the document, use it. Unfortunately, for dedicated workers
// 'parentDoc' ends up being the parent document, which is not the document
// that we want to use. So make sure to avoid using 'parentDoc' in that
// situation.
if (parentDoc && parentDoc->NodePrincipal() == principal) {
rv = NS_NewChannel(getter_AddRefs(channel),
uri,
parentDoc,