зеркало из https://github.com/mozilla/gecko-dev.git
Bug 558431 - Make fetching CSP policy-uri asyn, r=jst, a=blocking-betaN
This commit is contained in:
Родитель
6eda66dc66
Коммит
7c7be27025
|
@ -118,6 +118,57 @@ function CSPdebug(aMsg) {
|
|||
.logStringMessage(aMsg);
|
||||
}
|
||||
|
||||
// Callback to resume a request once the policy-uri has been fetched
|
||||
function CSPPolicyURIListener(policyURI, docRequest, csp) {
|
||||
this._policyURI = policyURI; // location of remote policy
|
||||
this._docRequest = docRequest; // the parent document request
|
||||
this._csp = csp; // parent document's CSP
|
||||
this._policy = ""; // contents fetched from policyURI
|
||||
this._wrapper = null; // nsIScriptableInputStream
|
||||
this._docURI = docRequest.QueryInterface(Components.interfaces.nsIChannel)
|
||||
.originalURI; // parent document URI (to be used as 'self')
|
||||
}
|
||||
|
||||
CSPPolicyURIListener.prototype = {
|
||||
|
||||
QueryInterface: function(iid) {
|
||||
if (iid.equals(Components.interfaces.nsIStreamListener) ||
|
||||
iid.equals(Components.interfaces.nsIRequestObserver) ||
|
||||
iid.equals(Components.interfaces.nsISupports))
|
||||
return this;
|
||||
throw Components.results.NS_ERROR_NO_INTERFACE;
|
||||
},
|
||||
|
||||
onStartRequest:
|
||||
function(request, context) {},
|
||||
|
||||
onDataAvailable:
|
||||
function(request, context, inputStream, offset, count) {
|
||||
if (this._wrapper == null) {
|
||||
this._wrapper = Components.classes["@mozilla.org/scriptableinputstream;1"]
|
||||
.createInstance(Components.interfaces.nsIScriptableInputStream);
|
||||
this._wrapper.init(inputStream);
|
||||
}
|
||||
// store the remote policy as it becomes available
|
||||
this._policy += this._wrapper.read(count);
|
||||
},
|
||||
|
||||
onStopRequest:
|
||||
function(request, context, status) {
|
||||
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._docRequest);
|
||||
}
|
||||
else {
|
||||
// problem fetching policy so fail closed
|
||||
this._csp.refinePolicy("allow 'none'", null, this._docURI, this._docRequest);
|
||||
}
|
||||
// resume the parent document request
|
||||
this._docRequest.resume();
|
||||
}
|
||||
};
|
||||
|
||||
//:::::::::::::::::::::::: CLASSES :::::::::::::::::::::::::://
|
||||
|
||||
/**
|
||||
|
@ -162,10 +213,15 @@ CSPRep.OPTIONS_DIRECTIVE = "options";
|
|||
* string rep of a CSP
|
||||
* @param self (optional)
|
||||
* string or CSPSource representing the "self" source
|
||||
* @param docRequest (optional)
|
||||
* request for the parent document which may need to be suspended
|
||||
* while the policy-uri is asynchronously fetched
|
||||
* @param csp (optional)
|
||||
* the CSP object to update once the policy has been fetched
|
||||
* @returns
|
||||
* an instance of CSPRep
|
||||
*/
|
||||
CSPRep.fromString = function(aStr, self) {
|
||||
CSPRep.fromString = function(aStr, self, docRequest, csp) {
|
||||
var SD = CSPRep.SRC_DIRECTIVES;
|
||||
var UD = CSPRep.URI_DIRECTIVES;
|
||||
var aCSPR = new CSPRep();
|
||||
|
@ -284,6 +340,12 @@ CSPRep.fromString = function(aStr, self) {
|
|||
CSPError("policy-uri directive can only appear alone");
|
||||
return CSPRep.fromString("allow 'none'");
|
||||
}
|
||||
// if we were called without a reference to the parent document request
|
||||
// we won't be able to suspend it while we fetch the policy -> fail closed
|
||||
if (!docRequest || !csp) {
|
||||
CSPError("The policy-uri cannot be fetched without a parent request and a CSP.");
|
||||
return CSPRep.fromString("allow 'none'");
|
||||
}
|
||||
|
||||
var uri = '';
|
||||
try {
|
||||
|
@ -309,31 +371,25 @@ CSPRep.fromString = function(aStr, self) {
|
|||
}
|
||||
}
|
||||
|
||||
var req = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
|
||||
.createInstance(Components.interfaces.nsIXMLHttpRequest);
|
||||
|
||||
// insert error hook
|
||||
req.onerror = CSPError;
|
||||
|
||||
// synchronous -- otherwise we need to architect a callback into the
|
||||
// xpcom component so that whomever creates the policy object gets
|
||||
// notified when it's loaded and ready to go.
|
||||
req.open("GET", uri.asciiSpec, false);
|
||||
|
||||
// make request anonymous
|
||||
// This prevents sending cookies with the request, in case the policy URI
|
||||
// is injected, it can't be abused for CSRF.
|
||||
req.channel.loadFlags |= Components.interfaces.nsIChannel.LOAD_ANONYMOUS;
|
||||
|
||||
req.send(null);
|
||||
if (req.status == 200) {
|
||||
aCSPR = CSPRep.fromString(req.responseText, self);
|
||||
// remember where we got the policy
|
||||
aCSPR._directives[UD.POLICY_URI] = dirvalue;
|
||||
return aCSPR;
|
||||
// suspend the parent document request while we fetch the policy-uri
|
||||
try {
|
||||
docRequest.suspend();
|
||||
var chan = gIoService.newChannel(uri.asciiSpec, null, null);
|
||||
// make request anonymous (no cookies, etc.) so the request for the
|
||||
// policy-uri can't be abused for CSRF
|
||||
chan.loadFlags |= Components.interfaces.nsIChannel.LOAD_ANONYMOUS;
|
||||
chan.asyncOpen(new CSPPolicyURIListener(uri, docRequest, csp), null);
|
||||
}
|
||||
CSPError("Error fetching policy URI: server response was " + req.status);
|
||||
return CSPRep.fromString("allow 'none'");
|
||||
catch (e) {
|
||||
// resume the document request and apply most restrictive policy
|
||||
docRequest.resume();
|
||||
CSPError("Error fetching policy-uri: " + e);
|
||||
return CSPRep.fromString("allow 'none'");
|
||||
}
|
||||
|
||||
// return a fully-open policy to be intersected with the contents of the
|
||||
// policy-uri when it returns
|
||||
return CSPRep.fromString("allow *");
|
||||
}
|
||||
|
||||
// UNIDENTIFIED DIRECTIVE /////////////////////////////////////////////
|
||||
|
|
|
@ -70,6 +70,7 @@ function ContentSecurityPolicy() {
|
|||
|
||||
this._requestHeaders = [];
|
||||
this._request = "";
|
||||
this._docRequest = null;
|
||||
CSPdebug("CSP POLICY INITED TO 'allow *'");
|
||||
}
|
||||
|
||||
|
@ -200,6 +201,7 @@ ContentSecurityPolicy.prototype = {
|
|||
}
|
||||
|
||||
this._request = aChannel.requestMethod + " " + aChannel.URI.asciiSpec;
|
||||
this._docRequest = aChannel;
|
||||
|
||||
// We will only be able to provide the HTTP version information if aChannel
|
||||
// implements nsIHttpChannelInternal
|
||||
|
@ -244,7 +246,9 @@ ContentSecurityPolicy.prototype = {
|
|||
// If there is a policy-uri, fetch the policy, then re-call this function.
|
||||
// (1) parse and create a CSPRep object
|
||||
var newpolicy = CSPRep.fromString(aPolicy,
|
||||
selfURI.scheme + "://" + selfURI.hostPort);
|
||||
selfURI.scheme + "://" + selfURI.hostPort,
|
||||
this._docRequest,
|
||||
this);
|
||||
|
||||
// (2) Intersect the currently installed CSPRep object with the new one
|
||||
var intersect = this._policy.intersectWith(newpolicy);
|
||||
|
|
|
@ -465,6 +465,9 @@ _TEST_FILES2 = \
|
|||
bug466080.sjs \
|
||||
test_bug625722.html \
|
||||
test_bug631615.html \
|
||||
test_bug558431.html \
|
||||
file_bug558431.html \
|
||||
file_bug558431.html^headers^ \
|
||||
$(NULL)
|
||||
|
||||
# This test fails on the Mac for some reason
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
<iframe id="inner"
|
||||
src="/tests/content/base/test/file_CSP.sjs?content=%3Cdiv%20id%3D%22test%22%3Etest%20558431%3C/div%3E"></iframe>
|
|
@ -0,0 +1 @@
|
|||
X-Content-Security-Policy: invalid-uri
|
|
@ -0,0 +1,32 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for CSP async policy-uri</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>
|
||||
<iframe id="cspframe"></iframe>
|
||||
<script type="text/javascript">
|
||||
// This tests that a policy is still attempted to be fetched
|
||||
// asynchronously (bug 558431) and that a default policy of
|
||||
// |allow 'none'| is applied when the fetching fails.
|
||||
|
||||
var f = document.getElementById("cspframe");
|
||||
// run our test functions when the inner frame is finished loading
|
||||
f.addEventListener('load', function() {
|
||||
var inner = this.contentDocument.getElementById("inner");
|
||||
var test = inner.contentDocument.getElementById("test");
|
||||
// the inner document should not exist because it has an invalid
|
||||
// policy-uri and should have been blocked by the default
|
||||
// |allow 'none'| policy that was applied
|
||||
is(test, null, "test inner document");
|
||||
SimpleTest.finish();
|
||||
}, false);
|
||||
// load the test frame
|
||||
f.src = "file_bug558431.html";
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,127 @@
|
|||
Components.utils.import('resource://gre/modules/CSPUtils.jsm');
|
||||
do_load_httpd_js();
|
||||
|
||||
var httpserv = null;
|
||||
|
||||
const POLICY_FROM_URI = "allow 'self'; img-src *";
|
||||
const POLICY_PORT = 9000;
|
||||
const POLICY_URI = "http://localhost:" + POLICY_PORT + "/policy";
|
||||
const POLICY_URI_RELATIVE = "/policy";
|
||||
const DOCUMENT_URI = "http://localhost:" + POLICY_PORT + "/document";
|
||||
const CSP_DOC_BODY = "CSP doc content";
|
||||
const SD = CSPRep.SRC_DIRECTIVES;
|
||||
const MAX_TESTS = 2;
|
||||
var TESTS_COMPLETED = 0;
|
||||
|
||||
var cspr, cspr_static;
|
||||
|
||||
// helper to use .equals on stuff
|
||||
function do_check_equivalent(foo, bar, stack) {
|
||||
if (!stack)
|
||||
stack = Components.stack.caller;
|
||||
|
||||
var text = foo + ".equals(" + bar + ")";
|
||||
|
||||
if (foo.equals && foo.equals(bar)) {
|
||||
dump("TEST-PASS | " + stack.filename + " | [" + stack.name + " : " +
|
||||
stack.lineNumber + "] " + text + "\n");
|
||||
return;
|
||||
}
|
||||
do_throw(text, stack);
|
||||
}
|
||||
|
||||
function listener() {
|
||||
this.buffer = "";
|
||||
}
|
||||
|
||||
listener.prototype = {
|
||||
onStartRequest: function (request, ctx) {
|
||||
},
|
||||
|
||||
onDataAvailable: function (request, ctx, stream, offset, count) {
|
||||
var sInputStream = Cc["@mozilla.org/scriptableinputstream;1"]
|
||||
.createInstance(Ci.nsIScriptableInputStream);
|
||||
sInputStream.init(stream);
|
||||
this.buffer = this.buffer.concat(sInputStream.read(count));
|
||||
},
|
||||
|
||||
onStopRequest: function (request, ctx, status) {
|
||||
// make sure that we have the full document content, guaranteeing that
|
||||
// the document channel has been resumed, before we do the comparisons
|
||||
if (this.buffer == CSP_DOC_BODY) {
|
||||
// "policy-uri failed to load"
|
||||
do_check_neq(null, cspr);
|
||||
|
||||
// other directives inherit self
|
||||
for (var i in SD) {
|
||||
do_check_equivalent(cspr._directives[SD[i]],
|
||||
cspr_static._directives[SD[i]]);
|
||||
}
|
||||
|
||||
do_test_finished();
|
||||
TESTS_COMPLETED++;
|
||||
// final teardown
|
||||
if (TESTS_COMPLETED == MAX_TESTS) {
|
||||
httpserv.stop(function(){});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function run_test() {
|
||||
httpserv = new nsHttpServer();
|
||||
httpserv.registerPathHandler("/document", csp_doc_response);
|
||||
httpserv.registerPathHandler("/policy", csp_policy_response);
|
||||
httpserv.start(POLICY_PORT);
|
||||
|
||||
var tests = [ test_CSPRep_fromPolicyURI, test_CSPRep_fromRelativePolicyURI];
|
||||
for (var i = 0 ; i < tests.length ; i++) {
|
||||
tests[i]();
|
||||
do_test_pending();
|
||||
}
|
||||
}
|
||||
|
||||
function makeChan(url) {
|
||||
var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
|
||||
var chan = ios.newChannel(url, null, null).QueryInterface(Ci.nsIHttpChannel);
|
||||
return chan;
|
||||
}
|
||||
|
||||
function csp_doc_response(metadata, response) {
|
||||
response.setStatusLine(metadata.httpVersion, 200, "OK");
|
||||
response.setHeader("Content-Type", "text/html", false);
|
||||
response.bodyOutputStream.write(CSP_DOC_BODY, CSP_DOC_BODY.length);
|
||||
}
|
||||
|
||||
function csp_policy_response(metadata, response) {
|
||||
response.setStatusLine(metadata.httpVersion, 200, "OK");
|
||||
response.setHeader("Content-Type", "text/csp", false);
|
||||
response.bodyOutputStream.write(POLICY_FROM_URI, POLICY_FROM_URI.length);
|
||||
}
|
||||
|
||||
///////////////////// TEST POLICY_URI //////////////////////
|
||||
function test_CSPRep_fromPolicyURI() {
|
||||
var csp = Components.classes["@mozilla.org/contentsecuritypolicy;1"]
|
||||
.createInstance[Components.interfaces.nsIContentSecurityPolicy];
|
||||
// once the policy-uri is returned we will compare our static CSPRep with one
|
||||
// we generated from the content we got back from the network to make sure
|
||||
// they are equivalent
|
||||
cspr_static = CSPRep.fromString(POLICY_FROM_URI, DOCUMENT_URI);
|
||||
// simulates the request for the parent document
|
||||
var docChan = makeChan(DOCUMENT_URI);
|
||||
docChan.asyncOpen(new listener(), null);
|
||||
cspr = CSPRep.fromString("policy-uri " + POLICY_URI, DOCUMENT_URI, docChan, csp);
|
||||
}
|
||||
|
||||
function test_CSPRep_fromRelativePolicyURI() {
|
||||
var csp = Components.classes["@mozilla.org/contentsecuritypolicy;1"]
|
||||
.createInstance[Components.interfaces.nsIContentSecurityPolicy];
|
||||
// once the policy-uri is returned we will compare our static CSPRep with one
|
||||
// we generated from the content we got back from the network to make sure
|
||||
// they are equivalent
|
||||
cspr_static = CSPRep.fromString(POLICY_FROM_URI, DOCUMENT_URI);
|
||||
// simulates the request for the parent document
|
||||
var docChan = makeChan(DOCUMENT_URI);
|
||||
docChan.asyncOpen(new listener(), null);
|
||||
cspr = CSPRep.fromString("policy-uri " + POLICY_URI_RELATIVE, DOCUMENT_URI, docChan, csp);
|
||||
}
|
|
@ -461,45 +461,6 @@ test(function test_CSPRep_fromString_withself() {
|
|||
do_check_true(cspr.permits("https://evil.com:100", SD.SCRIPT_SRC));
|
||||
});
|
||||
|
||||
///////////////////// TEST POLICY_URI //////////////////////
|
||||
test(function test_CSPRep_fromPolicyURI() {
|
||||
var cspr;
|
||||
var SD = CSPRep.SRC_DIRECTIVES;
|
||||
var self = "http://localhost:" + POLICY_PORT;
|
||||
|
||||
cspr = CSPRep.fromString("policy-uri " + POLICY_URI, self);
|
||||
cspr_static = CSPRep.fromString(POLICY_FROM_URI, self);
|
||||
|
||||
//"policy-uri failed to load"
|
||||
do_check_neq(null,cspr);
|
||||
|
||||
// other directives inherit self
|
||||
for(var i in SD) {
|
||||
//SD[i] + " parsed wrong from policy uri"
|
||||
do_check_equivalent(cspr._directives[SD[i]],
|
||||
cspr_static._directives[SD[i]]);
|
||||
}
|
||||
});
|
||||
|
||||
test(function test_CSPRep_fromRelativePolicyURI() {
|
||||
var cspr;
|
||||
var SD = CSPRep.SRC_DIRECTIVES;
|
||||
var self = "http://localhost:" + POLICY_PORT;
|
||||
|
||||
cspr = CSPRep.fromString("policy-uri " + POLICY_URI_RELATIVE, self);
|
||||
cspr_static = CSPRep.fromString(POLICY_FROM_URI, self);
|
||||
|
||||
//"policy-uri failed to load"
|
||||
do_check_neq(null,cspr);
|
||||
|
||||
// other directives inherit self
|
||||
for(var i in SD) {
|
||||
//SD[i] + " parsed wrong from policy uri"
|
||||
do_check_equivalent(cspr._directives[SD[i]],
|
||||
cspr_static._directives[SD[i]]);
|
||||
}
|
||||
});
|
||||
|
||||
//////////////// TEST FRAME ANCESTOR DEFAULTS /////////////////
|
||||
// (see bug 555068)
|
||||
test(function test_FrameAncestor_defaults() {
|
||||
|
|
Загрузка…
Ссылка в новой задаче