Bug 612391 - Convert CSP violation reporting from XHR to nsIUploadChannel. r=jonas

This commit is contained in:
Sid Stamm 2012-05-23 16:00:42 -07:00
Родитель ab0e45872a
Коммит 7321d1a414
4 изменённых файлов: 238 добавлений и 18 удалений

Просмотреть файл

@ -11,8 +11,9 @@
*/
// Module stuff
var EXPORTED_SYMBOLS = ["CSPRep", "CSPSourceList", "CSPSource",
"CSPHost", "CSPWarning", "CSPError", "CSPdebug"];
var EXPORTED_SYMBOLS = ["CSPRep", "CSPSourceList", "CSPSource", "CSPHost",
"CSPWarning", "CSPError", "CSPdebug",
"CSPViolationReportListener"];
// these are not exported
@ -1442,3 +1443,41 @@ CSPHost.prototype = {
return true;
}
};
//////////////////////////////////////////////////////////////////////
/**
* Class that listens to violation report transmission and logs errors.
*/
function CSPViolationReportListener(reportURI) {
this._reportURI = reportURI;
}
CSPViolationReportListener.prototype = {
_reportURI: null,
QueryInterface: function(iid) {
if(iid.equals(Ci.nsIStreamListener) ||
iid.equals(Ci.nsIRequestObserver) ||
iid.equals(Ci.nsISupports))
return this;
throw Components.results.NS_ERROR_NO_INTERFACE;
},
onStopRequest:
function(request, context, status) {
if (!Components.isSuccessCode(status)) {
CSPdebug("error " + status.toString(16) +
" while sending violation report to " +
this._reportURI);
}
},
onStartRequest:
function(request, context) { },
onDataAvailable:
function(request, context, inputStream, offset, count) { },
};

Просмотреть файл

@ -253,7 +253,8 @@ ContentSecurityPolicy.prototype = {
if (aLineNum)
report["csp-report"]["line-number"] = aLineNum;
CSPdebug("Constructed violation report:\n" + JSON.stringify(report));
var reportString = JSON.stringify(report);
CSPdebug("Constructed violation report:\n" + reportString);
CSPWarning("Directive \"" + violatedDirective + "\" violated"
+ (blockedUri['asciiSpec'] ? " by " + blockedUri.asciiSpec : ""),
@ -269,30 +270,56 @@ ContentSecurityPolicy.prototype = {
if (uris[i] === "")
continue;
var failure = function(aEvt) {
if (req.readyState == 4 && req.status != 200) {
CSPError("Failed to send report to " + uris[i]);
}
};
var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance(Ci.nsIXMLHttpRequest);
try {
req.open("POST", uris[i], true);
req.setRequestHeader('Content-Type', 'application/json');
req.upload.addEventListener("error", failure, false);
req.upload.addEventListener("abort", failure, false);
var chan = Services.io.newChannel(uris[i], null, null);
if(!chan) {
CSPdebug("Error creating channel for " + uris[i]);
continue;
}
// we need to set an nsIChannelEventSink on the XHR object
var content = Cc["@mozilla.org/io/string-input-stream;1"]
.createInstance(Ci.nsIStringInputStream);
content.data = reportString + "\n\n";
// make sure this is an anonymous request (no cookies) so in case the
// policy URI is injected, it can't be abused for CSRF.
chan.loadFlags |= Ci.nsIChannel.LOAD_ANONYMOUS;
// we need to set an nsIChannelEventSink on the channel object
// so we can tell it to not follow redirects when posting the reports
req.channel.notificationCallbacks = new CSPReportRedirectSink();
chan.notificationCallbacks = new CSPReportRedirectSink();
req.send(JSON.stringify(report));
chan.QueryInterface(Ci.nsIUploadChannel)
.setUploadStream(content, "application/json", content.available());
try {
// if this is an HTTP channel, set the request method to post
chan.QueryInterface(Ci.nsIHttpChannel);
chan.requestMethod = "POST";
} catch(e) {} // throws only if chan is not an nsIHttpChannel.
// check with the content policy service to see if we're allowed to
// send this request.
try {
var contentPolicy = Cc["@mozilla.org/layout/content-policy;1"]
.getService(Ci.nsIContentPolicy);
if (contentPolicy.shouldLoad(Ci.nsIContentPolicy.TYPE_OTHER,
chan.URI, null, null, null, null)
!= Ci.nsIContentPolicy.ACCEPT) {
continue; // skip unauthorized URIs
}
} catch(e) {
continue; // refuse to load if we can't do a security check.
}
//send data (and set up error notifications)
chan.asyncOpen(new CSPViolationReportListener(uris[i]), null);
CSPdebug("Sent violation report to " + uris[i]);
} catch(e) {
// it's possible that the URI was invalid, just log a
// warning and skip over that.
CSPWarning("Tried to send report to invalid URI: \"" + uris[i] + "\"");
CSPWarning("error was: \"" + e + "\"");
}
}
}

Просмотреть файл

@ -0,0 +1,153 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is the Content Security Policy Data Structures testing code.
*
* The Initial Developer of the Original Code is
* Mozilla Corporation
*
* Contributor(s):
* Sid Stamm <sid@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
Components.utils.import('resource://gre/modules/CSPUtils.jsm');
Components.utils.import('resource://gre/modules/NetUtil.jsm');
// load the HTTP server
do_load_httpd_js();
const REPORT_SERVER_PORT = 9000;
const REPORT_SERVER_URI = "http://localhost";
const REPORT_SERVER_PATH = "/report";
var httpServer = null;
var testsToFinish = 0;
/**
* Construct a callback that listens to a report submission and either passes
* or fails a test based on what it gets.
*/
function makeReportHandler(testpath, message, expectedJSON) {
return function(request, response) {
// we only like "POST" submissions for reports!
if (request.method !== "POST") {
do_throw("violation report should be a POST request");
return;
}
// obtain violation report
var reportObj = JSON.parse(
NetUtil.readInputStreamToString(
request.bodyInputStream,
request.bodyInputStream.available()));
dump("GOT REPORT:\n" + JSON.stringify(reportObj) + "\n");
dump("TESTPATH: " + testpath + "\n");
dump("EXPECTED: \n" + JSON.stringify(expectedJSON) + "\n\n");
for (var i in expectedJSON)
do_check_eq(expectedJSON[i], reportObj['csp-report'][i]);
// self-destroy
testsToFinish--;
httpServer.registerPathHandler(testpath, null);
if (testsToFinish < 1)
httpServer.stop(do_test_finished);
else
do_test_finished();
};
}
function makeTest(id, expectedJSON, callback) {
testsToFinish++;
do_test_pending();
// set up a new CSP instance for each test.
var csp = Cc["@mozilla.org/contentsecuritypolicy;1"]
.createInstance(Ci.nsIContentSecurityPolicy);
var policy = "allow 'none'; " +
"report-uri " + REPORT_SERVER_URI +
":" + REPORT_SERVER_PORT +
"/test" + id;
var selfuri = NetUtil.newURI(REPORT_SERVER_URI +
":" + REPORT_SERVER_PORT +
"/foo/self");
var selfchan = NetUtil.newChannel(selfuri);
dump("Created test " + id + " : " + policy + "\n\n");
// make the reports seem authentic by "binding" them to a channel.
csp.scanRequestData(selfchan);
// Load up the policy
csp.refinePolicy(policy, selfuri);
// prime the report server
var handler = makeReportHandler("/test" + id, "Test " + id, expectedJSON);
httpServer.registerPathHandler("/test" + id, handler);
//trigger the violation
callback(csp);
}
function run_test() {
var selfuri = NetUtil.newURI(REPORT_SERVER_URI +
":" + REPORT_SERVER_PORT +
"/foo/self");
httpServer = new nsHttpServer();
httpServer.start(REPORT_SERVER_PORT);
// test that inline script violations cause a report.
makeTest(0, {"blocked-uri": "self"},
function(csp) {
if(!csp.allowsInlineScript) {
// force the logging, since the getter doesn't.
csp.logViolationDetails(Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_SCRIPT,
selfuri.asciiSpec,
"script sample",
0);
}
});
makeTest(1, {"blocked-uri": "self"},
function(csp) {
if(!csp.allowsEval) {
// force the logging, since the getter doesn't.
csp.logViolationDetails(Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_SCRIPT,
selfuri.asciiSpec,
"script sample",
1);
}
});
makeTest(2, {"blocked-uri": "http://blocked.test/foo.js"},
function(csp) {
csp.shouldLoad(Ci.nsIContentPolicy.TYPE_SCRIPT,
NetUtil.newURI("http://blocked.test/foo.js"),
null, null, null, null);
});
}

Просмотреть файл

@ -6,6 +6,7 @@ tail =
[test_bug558431.js]
[test_bug737966.js]
[test_csputils.js]
[test_cspreports.js]
[test_error_codes.js]
[test_thirdpartyutil.js]
[test_xhr_standalone.js]