test_csp_reports.js (8778B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 const { NetUtil } = ChromeUtils.importESModule( 6 "resource://gre/modules/NetUtil.sys.mjs" 7 ); 8 const { HttpServer } = ChromeUtils.importESModule( 9 "resource://testing-common/httpd.sys.mjs" 10 ); 11 12 var httpServer = new HttpServer(); 13 httpServer.start(-1); 14 var testsToFinish = 0; 15 16 var principal; 17 18 const REPORT_SERVER_PORT = httpServer.identity.primaryPort; 19 const REPORT_SERVER_URI = "http://localhost"; 20 21 /** 22 * Construct a callback that listens to a report submission and either passes 23 * or fails a test based on what it gets. 24 */ 25 function makeReportHandler(testpath, message, expectedJSON) { 26 return function (request) { 27 // we only like "POST" submissions for reports! 28 if (request.method !== "POST") { 29 do_throw("violation report should be a POST request"); 30 return; 31 } 32 33 // check content-type of report is "application/csp-report" 34 var contentType = request.hasHeader("Content-Type") 35 ? request.getHeader("Content-Type") 36 : undefined; 37 if (contentType !== "application/csp-report") { 38 do_throw( 39 "violation report should have the 'application/csp-report' " + 40 "content-type, when in fact it is " + 41 contentType.toString() 42 ); 43 } 44 45 // obtain violation report 46 var reportObj = JSON.parse( 47 NetUtil.readInputStreamToString( 48 request.bodyInputStream, 49 request.bodyInputStream.available() 50 ) 51 ); 52 53 // dump("GOT REPORT:\n" + JSON.stringify(reportObj) + "\n"); 54 // dump("TESTPATH: " + testpath + "\n"); 55 // dump("EXPECTED: \n" + JSON.stringify(expectedJSON) + "\n\n"); 56 57 for (var i in expectedJSON) { 58 Assert.equal(expectedJSON[i], reportObj["csp-report"][i]); 59 } 60 61 testsToFinish--; 62 httpServer.registerPathHandler(testpath, null); 63 if (testsToFinish < 1) { 64 httpServer.stop(do_test_finished); 65 } else { 66 do_test_finished(); 67 } 68 }; 69 } 70 71 /** 72 * Everything created by this assumes it will cause a report. If you want to 73 * add a test here that will *not* cause a report to go out, you're gonna have 74 * to make sure the test cleans up after itself. 75 */ 76 function makeTest(id, expectedJSON, useReportOnlyPolicy, callback) { 77 testsToFinish++; 78 do_test_pending(); 79 80 // set up a new CSP instance for each test. 81 var csp = Cc["@mozilla.org/cspcontext;1"].createInstance( 82 Ci.nsIContentSecurityPolicy 83 ); 84 var policy = 85 "default-src 'none' 'report-sample'; " + 86 "report-uri " + 87 REPORT_SERVER_URI + 88 ":" + 89 REPORT_SERVER_PORT + 90 "/test" + 91 id; 92 var selfuri = NetUtil.newURI( 93 REPORT_SERVER_URI + ":" + REPORT_SERVER_PORT + "/foo/self" 94 ); 95 96 dump("Created test " + id + " : " + policy + "\n\n"); 97 98 principal = Services.scriptSecurityManager.createContentPrincipal( 99 selfuri, 100 {} 101 ); 102 csp.setRequestContextWithPrincipal(principal, selfuri, "", 0); 103 104 // Load up the policy 105 // set as report-only if that's the case 106 csp.appendPolicy(policy, useReportOnlyPolicy, false); 107 108 // prime the report server 109 var handler = makeReportHandler("/test" + id, "Test " + id, expectedJSON); 110 httpServer.registerPathHandler("/test" + id, handler); 111 112 // trigger the violation 113 callback(csp); 114 } 115 116 function run_test() { 117 do_get_profile(); 118 119 var selfuri = NetUtil.newURI( 120 REPORT_SERVER_URI + ":" + REPORT_SERVER_PORT + "/foo/self" 121 ); 122 123 // test that inline script violations cause a report. 124 makeTest( 125 0, 126 { 127 "blocked-uri": "inline", 128 "effective-directive": "script-src-elem", 129 disposition: "enforce", 130 }, 131 false, 132 function (csp) { 133 let inlineOK = true; 134 inlineOK = csp.getAllowsInline( 135 Ci.nsIContentSecurityPolicy.SCRIPT_SRC_ELEM_DIRECTIVE, 136 false, // aHasUnsafeHash 137 "", // aNonce 138 false, // aParserCreated 139 null, // aTriggeringElement 140 null, // nsICSPEventListener 141 "", // aContentOfPseudoScript 142 0, // aLineNumber 143 1 // aColumnNumber 144 ); 145 146 // this is not a report only policy, so it better block inline scripts 147 Assert.ok(!inlineOK); 148 } 149 ); 150 151 // test that eval violations cause a report. 152 makeTest( 153 1, 154 { 155 "blocked-uri": "eval", 156 // JSON script-sample is UTF8 encoded 157 "script-sample": "\xc2\xa3\xc2\xa5\xc2\xb5\xe5\x8c\x97\xf0\xa0\x9d\xb9", 158 "line-number": 1, 159 "column-number": 2, 160 }, 161 false, 162 function (csp) { 163 let evalOK = true, 164 oReportViolation = { value: false }; 165 evalOK = csp.getAllowsEval(oReportViolation); 166 167 // this is not a report only policy, so it better block eval 168 Assert.ok(!evalOK); 169 // ... and cause reports to go out 170 Assert.ok(oReportViolation.value); 171 172 if (oReportViolation.value) { 173 // force the logging, since the getter doesn't. 174 csp.logViolationDetails( 175 Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_EVAL, 176 null, // aTriggeringElement 177 null, // nsICSPEventListener 178 selfuri.asciiSpec, 179 // sending UTF-16 script sample to make sure 180 // csp report in JSON is not cut-off, please 181 // note that JSON is UTF8 encoded. 182 "\u00a3\u00a5\u00b5\u5317\ud841\udf79", 183 1, // line number 184 2 // column number 185 ); 186 } 187 } 188 ); 189 190 makeTest( 191 2, 192 { "blocked-uri": "http://blocked.test/foo.js" }, 193 false, 194 function (csp) { 195 // shouldLoad creates and sends out the report here. 196 csp.shouldLoad( 197 Ci.nsIContentPolicy.TYPE_SCRIPT, 198 null, // nsICSPEventListener 199 null, // aLoadInfo 200 NetUtil.newURI("http://blocked.test/foo.js"), 201 null, 202 true 203 ); 204 } 205 ); 206 207 // test that inline script violations cause a report in report-only policy 208 makeTest( 209 3, 210 { "blocked-uri": "inline", disposition: "report" }, 211 true, 212 function (csp) { 213 let inlineOK = true; 214 inlineOK = csp.getAllowsInline( 215 Ci.nsIContentSecurityPolicy.SCRIPT_SRC_ELEM_DIRECTIVE, 216 false, // aHasUnsafeHash 217 "", // aNonce 218 false, // aParserCreated 219 null, // aTriggeringElement 220 null, // nsICSPEventListener 221 "", // aContentOfPseudoScript 222 0, // aLineNumber 223 1 // aColumnNumber 224 ); 225 226 // this is a report only policy, so it better allow inline scripts 227 Assert.ok(inlineOK); 228 } 229 ); 230 231 // test that eval violations cause a report in report-only policy 232 makeTest(4, { "blocked-uri": "eval" }, true, function (csp) { 233 let evalOK = true, 234 oReportViolation = { value: false }; 235 evalOK = csp.getAllowsEval(oReportViolation); 236 237 // this is a report only policy, so it better allow eval 238 Assert.ok(evalOK); 239 // ... but still cause reports to go out 240 Assert.ok(oReportViolation.value); 241 242 if (oReportViolation.value) { 243 // force the logging, since the getter doesn't. 244 csp.logViolationDetails( 245 Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_EVAL, 246 null, // aTriggeringElement 247 null, // nsICSPEventListener 248 selfuri.asciiSpec, 249 "script sample", 250 4, // line number 251 5 // column number 252 ); 253 } 254 }); 255 256 // test that only the uri's scheme is reported for globally unique identifiers 257 makeTest(5, { "blocked-uri": "data" }, false, function (csp) { 258 var base64data = 259 "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12" + 260 "P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="; 261 // shouldLoad creates and sends out the report here. 262 csp.shouldLoad( 263 Ci.nsIContentPolicy.TYPE_IMAGE, 264 null, // nsICSPEventListener 265 null, // nsILoadInfo 266 NetUtil.newURI("data:image/png;base64," + base64data), 267 null, 268 true 269 ); 270 }); 271 272 // test that only the uri's scheme is reported for globally unique identifiers 273 makeTest(6, { "blocked-uri": "intent" }, false, function (csp) { 274 // shouldLoad creates and sends out the report here. 275 csp.shouldLoad( 276 Ci.nsIContentPolicy.TYPE_SUBDOCUMENT, 277 null, // nsICSPEventListener 278 null, // nsILoadInfo 279 NetUtil.newURI("intent://mymaps.com/maps?um=1&ie=UTF-8&fb=1&sll"), 280 null, 281 true 282 ); 283 }); 284 285 // test fragment removal 286 var selfSpec = 287 REPORT_SERVER_URI + ":" + REPORT_SERVER_PORT + "/foo/self/foo.js"; 288 makeTest(7, { "blocked-uri": selfSpec }, false, function (csp) { 289 // shouldLoad creates and sends out the report here. 290 csp.shouldLoad( 291 Ci.nsIContentPolicy.TYPE_SCRIPT, 292 null, // nsICSPEventListener 293 null, // nsILoadInfo 294 NetUtil.newURI(selfSpec + "#bar"), 295 null, 296 true 297 ); 298 }); 299 }