Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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/. */
6 /**
7 * Content Security Policy
8 *
9 * Overview
10 * This is a stub component that will be fleshed out to do all the fancy stuff
11 * that ContentSecurityPolicy has to do.
12 */
14 // these identifiers are also defined in contentSecurityPolicy.manifest.
15 const CSP_CLASS_ID = Components.ID("{d1680bb4-1ac0-4772-9437-1188375e44f2}");
16 const CSP_CONTRACT_ID = "@mozilla.org/contentsecuritypolicy;1";
18 /* :::::::: Constants and Helpers ::::::::::::::: */
20 const Cc = Components.classes;
21 const Ci = Components.interfaces;
22 const Cr = Components.results;
23 const Cu = Components.utils;
25 const CSP_VIOLATION_TOPIC = "csp-on-violate-policy";
27 // Needed to support CSP 1.0 spec and our original CSP implementation - should
28 // be removed when our original implementation is deprecated.
29 const CSP_TYPE_XMLHTTPREQUEST_SPEC_COMPLIANT = "csp_type_xmlhttprequest_spec_compliant";
30 const CSP_TYPE_WEBSOCKET_SPEC_COMPLIANT = "csp_type_websocket_spec_compliant";
32 const WARN_FLAG = Ci.nsIScriptError.warningFlag;
33 const ERROR_FLAG = Ci.nsIScriptError.ERROR_FLAG;
35 const INLINE_STYLE_VIOLATION_OBSERVER_SUBJECT = 'violated base restriction: Inline Stylesheets will not apply';
36 const INLINE_SCRIPT_VIOLATION_OBSERVER_SUBJECT = 'violated base restriction: Inline Scripts will not execute';
37 const EVAL_VIOLATION_OBSERVER_SUBJECT = 'violated base restriction: Code will not be created from strings';
38 const SCRIPT_NONCE_VIOLATION_OBSERVER_SUBJECT = 'Inline Script had invalid nonce'
39 const STYLE_NONCE_VIOLATION_OBSERVER_SUBJECT = 'Inline Style had invalid nonce'
40 const SCRIPT_HASH_VIOLATION_OBSERVER_SUBJECT = 'Inline Script had invalid hash';
41 const STYLE_HASH_VIOLATION_OBSERVER_SUBJECT = 'Inline Style had invalid hash';
43 // The cutoff length of content location in creating CSP cache key.
44 const CSP_CACHE_URI_CUTOFF_SIZE = 512;
46 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
47 Cu.import("resource://gre/modules/Services.jsm");
48 Cu.import("resource://gre/modules/CSPUtils.jsm");
50 /* ::::: Policy Parsing & Data structures :::::: */
52 function ContentSecurityPolicy() {
53 CSPdebug("CSP CREATED");
54 this._isInitialized = false;
56 this._policies = [];
58 this._request = "";
59 this._requestOrigin = "";
60 this._weakRequestPrincipal = { get : function() { return null; } };
61 this._referrer = "";
62 this._weakDocRequest = { get : function() { return null; } };
63 CSPdebug("CSP object initialized, no policies to enforce yet");
65 this._cache = new Map();
66 }
68 /*
69 * Set up mappings from nsIContentPolicy content types to CSP directives.
70 */
71 {
72 let cp = Ci.nsIContentPolicy;
73 let csp = ContentSecurityPolicy;
74 let cspr_sd_old = CSPRep.SRC_DIRECTIVES_OLD;
75 let cspr_sd_new = CSPRep.SRC_DIRECTIVES_NEW;
77 csp._MAPPINGS=[];
79 /* default, catch-all case */
80 // This is the same in old and new CSP so use the new mapping.
81 csp._MAPPINGS[cp.TYPE_OTHER] = cspr_sd_new.DEFAULT_SRC;
83 /* self */
84 csp._MAPPINGS[cp.TYPE_DOCUMENT] = null;
86 /* shouldn't see this one */
87 csp._MAPPINGS[cp.TYPE_REFRESH] = null;
89 /* categorized content types */
90 // These are the same in old and new CSP's so just use the new mappings.
91 csp._MAPPINGS[cp.TYPE_SCRIPT] = cspr_sd_new.SCRIPT_SRC;
92 csp._MAPPINGS[cp.TYPE_IMAGE] = cspr_sd_new.IMG_SRC;
93 csp._MAPPINGS[cp.TYPE_STYLESHEET] = cspr_sd_new.STYLE_SRC;
94 csp._MAPPINGS[cp.TYPE_OBJECT] = cspr_sd_new.OBJECT_SRC;
95 csp._MAPPINGS[cp.TYPE_OBJECT_SUBREQUEST] = cspr_sd_new.OBJECT_SRC;
96 csp._MAPPINGS[cp.TYPE_SUBDOCUMENT] = cspr_sd_new.FRAME_SRC;
97 csp._MAPPINGS[cp.TYPE_MEDIA] = cspr_sd_new.MEDIA_SRC;
98 csp._MAPPINGS[cp.TYPE_FONT] = cspr_sd_new.FONT_SRC;
99 csp._MAPPINGS[cp.TYPE_XSLT] = cspr_sd_new.SCRIPT_SRC;
100 csp._MAPPINGS[cp.TYPE_BEACON] = cspr_sd_new.CONNECT_SRC;
102 /* Our original CSP implementation's mappings for XHR and websocket
103 * These should be changed to be = cspr_sd.CONNECT_SRC when we remove
104 * the original implementation - NOTE: order in this array is important !!!
105 */
106 csp._MAPPINGS[cp.TYPE_XMLHTTPREQUEST] = cspr_sd_old.XHR_SRC;
107 csp._MAPPINGS[cp.TYPE_WEBSOCKET] = cspr_sd_old.XHR_SRC;
109 /* CSP cannot block CSP reports */
110 csp._MAPPINGS[cp.TYPE_CSP_REPORT] = null;
112 /* These must go through the catch-all */
113 csp._MAPPINGS[cp.TYPE_XBL] = cspr_sd_new.DEFAULT_SRC;
114 csp._MAPPINGS[cp.TYPE_PING] = cspr_sd_new.DEFAULT_SRC;
115 csp._MAPPINGS[cp.TYPE_DTD] = cspr_sd_new.DEFAULT_SRC;
117 /* CSP 1.0 spec compliant mappings for XHR and websocket */
118 // The directive name for XHR, websocket, and EventSource is different
119 // in the 1.0 spec than in our original implementation, these mappings
120 // address this. These won't be needed when we deprecate our original
121 // implementation.
122 csp._MAPPINGS[CSP_TYPE_XMLHTTPREQUEST_SPEC_COMPLIANT] = cspr_sd_new.CONNECT_SRC;
123 csp._MAPPINGS[CSP_TYPE_WEBSOCKET_SPEC_COMPLIANT] = cspr_sd_new.CONNECT_SRC;
124 // TODO : EventSource will be here and also will use connect-src
125 // after we fix Bug 802872 - CSP should restrict EventSource using the connect-src
126 // directive. For background see Bug 667490 - EventSource should use the same
127 // nsIContentPolicy type as XHR (which is fixed)
128 }
130 ContentSecurityPolicy.prototype = {
131 classID: CSP_CLASS_ID,
132 QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentSecurityPolicy,
133 Ci.nsISerializable,
134 Ci.nsISupports]),
136 // Class info is required to be able to serialize
137 classInfo: XPCOMUtils.generateCI({classID: CSP_CLASS_ID,
138 contractID: CSP_CONTRACT_ID,
139 interfaces: [Ci.nsIContentSecurityPolicy,
140 Ci.nsISerializable],
141 flags: Ci.nsIClassInfo.MAIN_THREAD_ONLY}),
143 get isInitialized() {
144 return this._isInitialized;
145 },
147 set isInitialized (foo) {
148 this._isInitialized = foo;
149 },
151 _getPolicyInternal: function(index) {
152 if (index < 0 || index >= this._policies.length) {
153 throw Cr.NS_ERROR_FAILURE;
154 }
155 return this._policies[index];
156 },
158 _buildViolatedDirectiveString:
159 function(aDirectiveName, aPolicy) {
160 var SD = CSPRep.SRC_DIRECTIVES_NEW;
161 var cspContext = (SD[aDirectiveName] in aPolicy._directives) ? SD[aDirectiveName] : SD.DEFAULT_SRC;
162 var directive = aPolicy._directives[cspContext];
163 return cspContext + ' ' + directive.toString();
164 },
166 /**
167 * Returns policy string representing the policy at "index".
168 */
169 getPolicy: function(index) {
170 return this._getPolicyInternal(index).toString();
171 },
173 /**
174 * Returns count of policies.
175 */
176 get numPolicies() {
177 return this._policies.length;
178 },
180 getAllowsInlineScript: function(shouldReportViolations) {
181 // report it? (for each policy, is it violated?)
182 shouldReportViolations.value = this._policies.some(function(a) { return !a.allowsInlineScripts; });
184 // allow it to execute? (Do all the policies allow it to execute)?
185 return this._policies.every(function(a) {
186 return a._reportOnlyMode || a.allowsInlineScripts;
187 });
188 },
190 getAllowsEval: function(shouldReportViolations) {
191 // report it? (for each policy, is it violated?)
192 shouldReportViolations.value = this._policies.some(function(a) { return !a.allowsEvalInScripts; });
194 // allow it to execute? (Do all the policies allow it to execute)?
195 return this._policies.every(function(a) {
196 return a._reportOnlyMode || a.allowsEvalInScripts;
197 });
198 },
200 getAllowsInlineStyle: function(shouldReportViolations) {
201 // report it? (for each policy, is it violated?)
202 shouldReportViolations.value = this._policies.some(function(a) { return !a.allowsInlineStyles; });
204 // allow it to execute? (Do all the policies allow it to execute)?
205 return this._policies.every(function(a) {
206 return a._reportOnlyMode || a.allowsInlineStyles;
207 });
208 },
210 getAllowsNonce: function(aNonce, aContentType, shouldReportViolation) {
211 if (!(aContentType == Ci.nsIContentPolicy.TYPE_SCRIPT ||
212 aContentType == Ci.nsIContentPolicy.TYPE_STYLESHEET)) {
213 CSPdebug("Nonce check requested for an invalid content type (not script or style): " + aContentType);
214 return false;
215 }
217 let directive = ContentSecurityPolicy._MAPPINGS[aContentType];
218 let policyAllowsNonce = [ policy.permitsNonce(aNonce, directive)
219 for (policy of this._policies) ];
221 shouldReportViolation.value = this._policies.some(function(policy, i) {
222 // Don't report a violation if the policy didn't use nonce-source
223 return policy._directives.hasOwnProperty(directive) &&
224 policy._directives[directive]._hasNonceSource &&
225 !policyAllowsNonce[i];
226 });
228 // allow it to execute? (Do all the policies allow it to execute)?
229 return this._policies.every(function(policy, i) {
230 return policy._reportOnlyMode || policyAllowsNonce[i];
231 });
232 },
234 getAllowsHash: function(aContent, aContentType, shouldReportViolation) {
235 if (!(aContentType == Ci.nsIContentPolicy.TYPE_SCRIPT ||
236 aContentType == Ci.nsIContentPolicy.TYPE_STYLESHEET)) {
237 CSPdebug("Hash check requested for an invalid content type (not script or style): " + aContentType);
238 return false;
239 }
241 let directive = ContentSecurityPolicy._MAPPINGS[aContentType];
242 let policyAllowsHash = [ policy.permitsHash(aContent, directive)
243 for (policy of this._policies) ];
245 shouldReportViolation.value = this._policies.some(function(policy, i) {
246 // Don't report a violation if the policy didn't use hash-source
247 return policy._directives.hasOwnProperty(directive) &&
248 policy._directives[directive]._hasHashSource &&
249 !policyAllowsHash[i];
250 });
252 // allow it to execute? (Do all the policies allow it to execute)?
253 return this._policies.every(function(policy, i) {
254 return policy._reportOnlyMode || policyAllowsHash[i];
255 });
256 },
258 /**
259 * For each policy, log any violation on the Error Console and send a report
260 * if a report-uri is present in the policy
261 *
262 * @param aViolationType
263 * one of the VIOLATION_TYPE_* constants, e.g. inline-script or eval
264 * @param aSourceFile
265 * name of the source file containing the violation (if available)
266 * @param aContentSample
267 * sample of the violating content (to aid debugging)
268 * @param aLineNum
269 * source line number of the violation (if available)
270 * @param aNonce
271 * (optional) If this is a nonce violation, include the nonce so we can
272 * recheck to determine which policies were violated and send the
273 * appropriate reports.
274 * @param aContent
275 * (optional) If this is a hash violation, include contents of the inline
276 * resource in the question so we can recheck the hash in order to
277 * determine which policies were violated and send the appropriate
278 * reports.
279 */
280 logViolationDetails:
281 function(aViolationType, aSourceFile, aScriptSample, aLineNum, aNonce, aContent) {
282 for (let policyIndex=0; policyIndex < this._policies.length; policyIndex++) {
283 let policy = this._policies[policyIndex];
285 // call-sites to the eval/inline checks recieve two return values: allows
286 // and violates. Policies that are report-only allow the
287 // loads/compilations but violations should still be reported. Not all
288 // policies in this nsIContentSecurityPolicy instance will be violated,
289 // which is why we must check again here.
290 switch (aViolationType) {
291 case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_STYLE:
292 if (!policy.allowsInlineStyles) {
293 var violatedDirective = this._buildViolatedDirectiveString('STYLE_SRC', policy);
294 this._asyncReportViolation('self', null, violatedDirective, policyIndex,
295 INLINE_STYLE_VIOLATION_OBSERVER_SUBJECT,
296 aSourceFile, aScriptSample, aLineNum);
297 }
298 break;
299 case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_SCRIPT:
300 if (!policy.allowsInlineScripts) {
301 var violatedDirective = this._buildViolatedDirectiveString('SCRIPT_SRC', policy);
302 this._asyncReportViolation('self', null, violatedDirective, policyIndex,
303 INLINE_SCRIPT_VIOLATION_OBSERVER_SUBJECT,
304 aSourceFile, aScriptSample, aLineNum);
305 }
306 break;
307 case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_EVAL:
308 if (!policy.allowsEvalInScripts) {
309 var violatedDirective = this._buildViolatedDirectiveString('SCRIPT_SRC', policy);
310 this._asyncReportViolation('self', null, violatedDirective, policyIndex,
311 EVAL_VIOLATION_OBSERVER_SUBJECT,
312 aSourceFile, aScriptSample, aLineNum);
313 }
314 break;
315 case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_NONCE_SCRIPT:
316 var scriptType = ContentSecurityPolicy._MAPPINGS[Ci.nsIContentPolicy.TYPE_SCRIPT];
317 if (!policy.permitsNonce(aNonce, scriptType)) {
318 var violatedDirective = this._buildViolatedDirectiveString('SCRIPT_SRC', policy);
319 this._asyncReportViolation('self', null, violatedDirective, policyIndex,
320 SCRIPT_NONCE_VIOLATION_OBSERVER_SUBJECT,
321 aSourceFile, aScriptSample, aLineNum);
322 }
323 break;
324 case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_NONCE_STYLE:
325 var styleType = ContentSecurityPolicy._MAPPINGS[Ci.nsIContentPolicy.TYPE_STYLE];
326 if (!policy.permitsNonce(aNonce, styleType)) {
327 var violatedDirective = this._buildViolatedDirectiveString('STYLE_SRC', policy);
328 this._asyncReportViolation('self', null, violatedDirective, policyIndex,
329 STYLE_NONCE_VIOLATION_OBSERVER_SUBJECT,
330 aSourceFile, aScriptSample, aLineNum);
331 }
332 break;
333 case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_HASH_SCRIPT:
334 var scriptType = ContentSecurityPolicy._MAPPINGS[Ci.nsIContentPolicy.TYPE_SCRIPT];
335 if (!policy.permitsHash(aContent, scriptType)) {
336 var violatedDirective = this._buildViolatedDirectiveString('SCRIPT_SRC', policy);
337 this._asyncReportViolation('self', null, violatedDirective, policyIndex,
338 SCRIPT_HASH_VIOLATION_OBSERVER_SUBJECT,
339 aSourceFile, aScriptSample, aLineNum);
340 }
341 break;
342 case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_HASH_STYLE:
343 var styleType = ContentSecurityPolicy._MAPPINGS[Ci.nsIContentPolicy.TYPE_STYLE];
344 if (!policy.permitsHash(aContent, styleType)) {
345 var violatedDirective = this._buildViolatedDirectiveString('STYLE_SRC', policy);
346 this._asyncReportViolation('self', null, violatedDirective, policyIndex,
347 STYLE_HASH_VIOLATION_OBSERVER_SUBJECT,
348 aSourceFile, aScriptSample, aLineNum);
349 }
350 break;
351 }
352 }
353 },
355 /**
356 * Given an nsIHttpChannel, fill out the appropriate data.
357 */
358 setRequestContext:
359 function(aSelfURI, aReferrerURI, aPrincipal, aChannel) {
361 // this requires either a self URI or a http channel.
362 if (!aSelfURI && !aChannel)
363 return;
365 if (aChannel) {
366 // Save the docRequest for fetching a policy-uri
367 this._weakDocRequest = Cu.getWeakReference(aChannel);
368 }
370 // save the document URI (minus <fragment>) and referrer for reporting
371 let uri = aSelfURI ? aSelfURI.cloneIgnoringRef() : aChannel.URI.cloneIgnoringRef();
372 try { // GetUserPass throws for some protocols without userPass
373 uri.userPass = '';
374 } catch (ex) {}
375 this._request = uri.asciiSpec;
376 this._requestOrigin = uri;
378 // store a reference to the principal, that can later be used in shouldLoad
379 if (aPrincipal) {
380 this._weakRequestPrincipal = Cu.getWeakReference(aPrincipal);
381 } else if (aChannel) {
382 this._weakRequestPrincipal = Cu.getWeakReference(Cc["@mozilla.org/scriptsecuritymanager;1"]
383 .getService(Ci.nsIScriptSecurityManager)
384 .getChannelPrincipal(aChannel));
385 } else {
386 CSPdebug("No principal or channel for document context; violation reports may not work.");
387 }
389 // pick one: referrerURI, channel's referrer, or null (first available)
390 let ref = null;
391 if (aReferrerURI)
392 ref = aReferrerURI;
393 else if (aChannel instanceof Ci.nsIHttpChannel)
394 ref = aChannel.referrer;
396 if (ref) {
397 let referrer = aChannel.referrer.cloneIgnoringRef();
398 try { // GetUserPass throws for some protocols without userPass
399 referrer.userPass = '';
400 } catch (ex) {}
401 this._referrer = referrer.asciiSpec;
402 }
403 },
405 /* ........ Methods .............. */
407 /**
408 * Adds a new policy to our list of policies for this CSP context.
409 * @returns the count of policies.
410 */
411 appendPolicy:
412 function csp_appendPolicy(aPolicy, selfURI, aReportOnly, aSpecCompliant) {
413 return this._appendPolicyInternal(aPolicy, selfURI, aReportOnly,
414 aSpecCompliant, true);
415 },
417 /**
418 * Adds a new policy to our list of policies for this CSP context.
419 * Only to be called from this module (not exported)
420 * @returns the count of policies.
421 */
422 _appendPolicyInternal:
423 function csp_appendPolicy(aPolicy, selfURI, aReportOnly, aSpecCompliant,
424 aEnforceSelfChecks) {
425 #ifndef MOZ_B2G
426 CSPdebug("APPENDING POLICY: " + aPolicy);
427 CSPdebug(" SELF: " + (selfURI ? selfURI.asciiSpec : " null"));
428 CSPdebug("CSP 1.0 COMPLIANT : " + aSpecCompliant);
429 #endif
431 // For nested schemes such as view-source: make sure we are taking the
432 // innermost URI to use as 'self' since that's where we will extract the
433 // scheme, host and port from
434 if (selfURI instanceof Ci.nsINestedURI) {
435 #ifndef MOZ_B2G
436 CSPdebug(" INNER: " + selfURI.innermostURI.asciiSpec);
437 #endif
438 selfURI = selfURI.innermostURI;
439 }
441 // stay uninitialized until policy setup is done
442 var newpolicy;
444 // If there is a policy-uri, fetch the policy, then re-call this function.
445 // (1) parse and create a CSPRep object
446 // Note that we pass the full URI since when it's parsed as 'self' to construct a
447 // CSPSource only the scheme, host, and port are kept.
449 // If we want to be CSP 1.0 spec compliant, use the new parser.
450 // The old one will be deprecated in the future and will be
451 // removed at that time.
452 if (aSpecCompliant) {
453 newpolicy = CSPRep.fromStringSpecCompliant(aPolicy,
454 selfURI,
455 aReportOnly,
456 this._weakDocRequest.get(),
457 this,
458 aEnforceSelfChecks);
459 } else {
460 newpolicy = CSPRep.fromString(aPolicy,
461 selfURI,
462 aReportOnly,
463 this._weakDocRequest.get(),
464 this,
465 aEnforceSelfChecks);
466 }
468 newpolicy._specCompliant = !!aSpecCompliant;
469 newpolicy._isInitialized = true;
470 this._policies.push(newpolicy);
471 this._cache.clear(); // reset cache since effective policy changes
472 },
474 /**
475 * Removes a policy from the array of policies.
476 */
477 removePolicy:
478 function csp_removePolicy(index) {
479 if (index < 0 || index >= this._policies.length) {
480 CSPdebug("Cannot remove policy " + index + "; not enough policies.");
481 return;
482 }
483 this._policies.splice(index, 1);
484 this._cache.clear(); // reset cache since effective policy changes
485 },
487 /**
488 * Generates and sends a violation report to the specified report URIs.
489 */
490 sendReports:
491 function(blockedUri, originalUri, violatedDirective,
492 violatedPolicyIndex, aSourceFile,
493 aScriptSample, aLineNum) {
495 let policy = this._getPolicyInternal(violatedPolicyIndex);
496 if (!policy) {
497 CSPdebug("ERROR in SendReports: policy " + violatedPolicyIndex + " is not defined.");
498 return;
499 }
501 var uriString = policy.getReportURIs();
502 var uris = uriString.split(/\s+/);
503 if (uris.length > 0) {
504 // see if we need to sanitize the blocked-uri
505 let blocked = '';
506 if (originalUri) {
507 // We've redirected, only report the blocked origin
508 try {
509 let clone = blockedUri.clone();
510 clone.path = '';
511 blocked = clone.asciiSpec;
512 } catch(e) {
513 CSPdebug(".... blockedUri can't be cloned: " + blockedUri);
514 }
515 }
516 else if (blockedUri instanceof Ci.nsIURI) {
517 blocked = blockedUri.cloneIgnoringRef().asciiSpec;
518 }
519 else {
520 // blockedUri is a string for eval/inline-script violations
521 blocked = blockedUri;
522 }
524 // Generate report to send composed of
525 // {
526 // csp-report: {
527 // document-uri: "http://example.com/file.html?params",
528 // referrer: "...",
529 // blocked-uri: "...",
530 // violated-directive: "..."
531 // }
532 // }
533 var report = {
534 'csp-report': {
535 'document-uri': this._request,
536 'referrer': this._referrer,
537 'blocked-uri': blocked,
538 'violated-directive': violatedDirective
539 }
540 }
542 // extra report fields for script errors (if available)
543 if (originalUri)
544 report["csp-report"]["original-uri"] = originalUri.cloneIgnoringRef().asciiSpec;
545 if (aSourceFile)
546 report["csp-report"]["source-file"] = aSourceFile;
547 if (aScriptSample)
548 report["csp-report"]["script-sample"] = aScriptSample;
549 if (aLineNum)
550 report["csp-report"]["line-number"] = aLineNum;
552 var reportString = JSON.stringify(report);
553 CSPdebug("Constructed violation report:\n" + reportString);
555 // For each URI in the report list, send out a report.
556 // We make the assumption that all of the URIs are absolute URIs; this
557 // should be taken care of in CSPRep.fromString (where it converts any
558 // relative URIs into absolute ones based on "self").
559 for (let i in uris) {
560 if (uris[i] === "")
561 continue;
563 try {
564 var chan = Services.io.newChannel(uris[i], null, null);
565 if (!chan) {
566 CSPdebug("Error creating channel for " + uris[i]);
567 continue;
568 }
570 var content = Cc["@mozilla.org/io/string-input-stream;1"]
571 .createInstance(Ci.nsIStringInputStream);
572 content.data = reportString + "\n\n";
574 // make sure this is an anonymous request (no cookies) so in case the
575 // policy URI is injected, it can't be abused for CSRF.
576 chan.loadFlags |= Ci.nsIChannel.LOAD_ANONYMOUS;
578 // we need to set an nsIChannelEventSink on the channel object
579 // so we can tell it to not follow redirects when posting the reports
580 chan.notificationCallbacks = new CSPReportRedirectSink(policy);
581 if (this._weakDocRequest.get()) {
582 chan.loadGroup = this._weakDocRequest.get().loadGroup;
583 }
585 chan.QueryInterface(Ci.nsIUploadChannel)
586 .setUploadStream(content, "application/json", content.available());
588 try {
589 // if this is an HTTP channel, set the request method to post
590 chan.QueryInterface(Ci.nsIHttpChannel);
591 chan.requestMethod = "POST";
592 } catch(e) {} // throws only if chan is not an nsIHttpChannel.
594 // check with the content policy service to see if we're allowed to
595 // send this request.
596 try {
597 var contentPolicy = Cc["@mozilla.org/layout/content-policy;1"]
598 .getService(Ci.nsIContentPolicy);
599 if (contentPolicy.shouldLoad(Ci.nsIContentPolicy.TYPE_CSP_REPORT,
600 chan.URI, this._requestOrigin,
601 null, null, null, this._weakRequestPrincipal.get())
602 != Ci.nsIContentPolicy.ACCEPT) {
603 continue; // skip unauthorized URIs
604 }
605 } catch(e) {
606 continue; // refuse to load if we can't do a security check.
607 }
609 //send data (and set up error notifications)
610 chan.asyncOpen(new CSPViolationReportListener(uris[i]), null);
611 CSPdebug("Sent violation report to " + uris[i]);
612 } catch(e) {
613 // it's possible that the URI was invalid, just log a
614 // warning and skip over that.
615 policy.log(WARN_FLAG, CSPLocalizer.getFormatStr("triedToSendReport", [uris[i]]));
616 policy.log(WARN_FLAG, CSPLocalizer.getFormatStr("errorWas", [e.toString()]));
617 }
618 }
619 }
620 },
622 /**
623 * Logs a meaningful CSP warning to the developer console.
624 */
625 logToConsole:
626 function(blockedUri, originalUri, violatedDirective, aViolatedPolicyIndex,
627 aSourceFile, aScriptSample, aLineNum, aObserverSubject) {
628 let policy = this._policies[aViolatedPolicyIndex];
629 switch(aObserverSubject.data) {
630 case INLINE_STYLE_VIOLATION_OBSERVER_SUBJECT:
631 violatedDirective = CSPLocalizer.getStr("inlineStyleBlocked");
632 break;
633 case INLINE_SCRIPT_VIOLATION_OBSERVER_SUBJECT:
634 violatedDirective = CSPLocalizer.getStr("inlineScriptBlocked");
635 break;
636 case EVAL_VIOLATION_OBSERVER_SUBJECT:
637 violatedDirective = CSPLocalizer.getStr("scriptFromStringBlocked");
638 break;
639 }
640 var violationMessage = null;
641 if (blockedUri["asciiSpec"]) {
642 violationMessage = CSPLocalizer.getFormatStr("CSPViolationWithURI", [violatedDirective, blockedUri.asciiSpec]);
643 } else {
644 violationMessage = CSPLocalizer.getFormatStr("CSPViolation", [violatedDirective]);
645 }
646 policy.log(WARN_FLAG, violationMessage,
647 (aSourceFile) ? aSourceFile : null,
648 (aScriptSample) ? decodeURIComponent(aScriptSample) : null,
649 (aLineNum) ? aLineNum : null);
650 },
652 /**
653 * Exposed Method to analyze docShell for approved frame ancestry.
654 * NOTE: Also sends violation reports if necessary.
655 * @param docShell
656 * the docShell for this policy's resource.
657 * @return
658 * true if the frame ancestry is allowed by this policy and the load
659 * should progress.
660 */
661 permitsAncestry:
662 function(docShell) {
663 // Cannot shortcut checking all the policies since violation reports have
664 // to be triggered if any policy wants it.
665 var permitted = true;
666 for (let i = 0; i < this._policies.length; i++) {
667 if (!this._permitsAncestryInternal(docShell, this._policies[i], i)) {
668 permitted = false;
669 }
670 }
671 return permitted;
672 },
674 _permitsAncestryInternal:
675 function(docShell, policy, policyIndex) {
676 if (!docShell) { return false; }
678 // walk up this docShell tree until we hit chrome
679 var dst = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
680 .getInterface(Ci.nsIDocShellTreeItem);
682 // collect ancestors and make sure they're allowed.
683 var ancestors = [];
684 while (dst.parent) {
685 dst = dst.parent;
686 let it = dst.QueryInterface(Ci.nsIInterfaceRequestor)
687 .getInterface(Ci.nsIWebNavigation);
688 if (it.currentURI) {
689 if (it.currentURI.scheme === "chrome") {
690 break;
691 }
692 // delete any userpass
693 let ancestor = it.currentURI.cloneIgnoringRef();
694 try { // GetUserPass throws for some protocols without userPass
695 ancestor.userPass = '';
696 } catch (ex) {}
698 #ifndef MOZ_B2G
699 CSPdebug(" found frame ancestor " + ancestor.asciiSpec);
700 #endif
701 ancestors.push(ancestor);
702 }
703 }
705 // scan the discovered ancestors
706 // frame-ancestors is the same in both old and new source directives,
707 // so don't need to differentiate here.
708 let cspContext = CSPRep.SRC_DIRECTIVES_NEW.FRAME_ANCESTORS;
709 for (let i in ancestors) {
710 let ancestor = ancestors[i];
711 if (!policy.permits(ancestor, cspContext)) {
712 // report the frame-ancestor violation
713 let directive = policy._directives[cspContext];
714 let violatedPolicy = 'frame-ancestors ' + directive.toString();
716 this._asyncReportViolation(ancestors[i], null, violatedPolicy,
717 policyIndex);
719 // need to lie if we are testing in report-only mode
720 return policy._reportOnlyMode;
721 }
722 }
723 return true;
724 },
726 /**
727 * Creates a cache key from content location and content type.
728 */
729 _createCacheKey:
730 function (aContentLocation, aContentType) {
731 if (aContentType != Ci.nsIContentPolicy.TYPE_SCRIPT &&
732 aContentLocation.scheme == "data") {
733 // For non-script data: URI, use ("data:", aContentType) as the cache key.
734 return aContentLocation.scheme + ":" + aContentType;
735 }
737 let uri = aContentLocation.spec;
738 if (uri.length > CSP_CACHE_URI_CUTOFF_SIZE) {
739 // Don't cache for a URI longer than the cutoff size.
740 return null;
741 }
742 return uri + "!" + aContentType;
743 },
745 /**
746 * Delegate method called by the service when sub-elements of the protected
747 * document are being loaded. Given a bit of information about the request,
748 * decides whether or not the policy is satisfied.
749 */
750 shouldLoad:
751 function csp_shouldLoad(aContentType,
752 aContentLocation,
753 aRequestOrigin,
754 aContext,
755 aMimeTypeGuess,
756 aOriginalUri) {
757 let key = this._createCacheKey(aContentLocation, aContentType);
758 if (key && this._cache.has(key)) {
759 return this._cache.get(key);
760 }
762 #ifndef MOZ_B2G
763 // Try to remove as much as possible from the hot path on b2g.
764 CSPdebug("shouldLoad location = " + aContentLocation.asciiSpec);
765 CSPdebug("shouldLoad content type = " + aContentType);
766 #endif
768 // The mapping for XHR and websockets is different between our original
769 // implementation and the 1.0 spec, we handle this here.
770 var cspContext;
772 let cp = Ci.nsIContentPolicy;
774 // Infer if this is a preload for elements that use nonce-source. Since,
775 // for preloads, aContext is the document and not the element associated
776 // with the resource, we cannot determine the nonce. See Bug 612921 and
777 // Bug 855326.
778 let nonceSourceValid = aContentType == cp.TYPE_SCRIPT ||
779 aContentType == cp.TYPE_STYLESHEET;
780 var possiblePreloadNonceConflict = nonceSourceValid &&
781 aContext instanceof Ci.nsIDOMHTMLDocument;
783 // iterate through all the _policies and send reports where a policy is
784 // violated. After the check, determine the overall effect (blocked or
785 // loaded?) and cache it.
786 let policyAllowsLoadArray = [];
787 for (let policyIndex=0; policyIndex < this._policies.length; policyIndex++) {
788 let policy = this._policies[policyIndex];
790 #ifndef MOZ_B2G
791 CSPdebug("policy is " + (policy._specCompliant ?
792 "1.0 compliant" : "pre-1.0"));
793 CSPdebug("policy is " + (policy._reportOnlyMode ?
794 "report-only" : "blocking"));
795 #endif
797 if (aContentType == cp.TYPE_XMLHTTPREQUEST && this._policies[policyIndex]._specCompliant) {
798 cspContext = ContentSecurityPolicy._MAPPINGS[CSP_TYPE_XMLHTTPREQUEST_SPEC_COMPLIANT];
799 } else if (aContentType == cp.TYPE_WEBSOCKET && this._policies[policyIndex]._specCompliant) {
800 cspContext = ContentSecurityPolicy._MAPPINGS[CSP_TYPE_WEBSOCKET_SPEC_COMPLIANT];
801 } else {
802 cspContext = ContentSecurityPolicy._MAPPINGS[aContentType];
803 }
805 #ifndef MOZ_B2G
806 CSPdebug("shouldLoad cspContext = " + cspContext);
807 #endif
809 // if the mapping is null, there's no policy, let it through.
810 if (!cspContext) {
811 return Ci.nsIContentPolicy.ACCEPT;
812 }
814 // check if location is permitted
815 let permitted = policy.permits(aContentLocation, cspContext);
817 // check any valid content type for nonce if location is not permitted
818 if (!permitted && nonceSourceValid &&
819 aContext instanceof Ci.nsIDOMHTMLElement &&
820 aContext.hasAttribute('nonce')) {
821 permitted = policy.permitsNonce(aContext.getAttribute('nonce'),
822 cspContext);
823 }
825 // record whether the thing should be blocked or just reported.
826 policyAllowsLoadArray.push(permitted || policy._reportOnlyMode);
827 let res = permitted ? cp.ACCEPT : cp.REJECT_SERVER;
829 // frame-ancestors is taken care of early on (as this document is loaded)
831 // If the result is *NOT* ACCEPT, then send report
832 // Do not send report if this is a nonce-source preload - the decision may
833 // be wrong and will incorrectly fail the unit tests.
834 if (res != cp.ACCEPT && !possiblePreloadNonceConflict) {
835 CSPdebug("blocking request for " + aContentLocation.asciiSpec);
836 try {
837 let directive = "unknown directive",
838 violatedPolicy = "unknown policy";
840 // The policy might not explicitly declare each source directive (so
841 // the cspContext may be implicit). If so, we have to report
842 // violations as appropriate: specific or the default-src directive.
843 if (policy._directives.hasOwnProperty(cspContext)) {
844 directive = policy._directives[cspContext];
845 violatedPolicy = cspContext + ' ' + directive.toString();
846 } else if (policy._directives.hasOwnProperty("default-src")) {
847 directive = policy._directives["default-src"];
848 violatedPolicy = "default-src " + directive.toString();
849 } else {
850 violatedPolicy = "unknown directive";
851 CSPdebug('ERROR in blocking content: ' +
852 'CSP is not sure which part of the policy caused this block');
853 }
855 this._asyncReportViolation(aContentLocation,
856 aOriginalUri,
857 violatedPolicy,
858 policyIndex);
859 } catch(e) {
860 CSPdebug('---------------- ERROR: ' + e);
861 }
862 }
863 } // end for-each loop over policies
865 // the ultimate decision is based on whether any policies want to reject
866 // the load. The array keeps track of whether the policies allowed the
867 // loads. If any doesn't, we'll reject the load (and cache the result).
868 let ret = (policyAllowsLoadArray.some(function(a,b) { return !a; }) ?
869 cp.REJECT_SERVER : cp.ACCEPT);
871 // Do not cache the result if this is a nonce-source preload
872 if (key && !possiblePreloadNonceConflict) {
873 this._cache.set(key, ret);
874 }
875 return ret;
876 },
878 shouldProcess:
879 function csp_shouldProcess(aContentType,
880 aContentLocation,
881 aRequestOrigin,
882 aContext,
883 aMimeType,
884 aExtra) {
885 // frame-ancestors check is done outside the ContentPolicy
886 var res = Ci.nsIContentPolicy.ACCEPT;
887 CSPdebug("shouldProcess aContext=" + aContext);
888 return res;
889 },
891 /**
892 * Asynchronously notifies any nsIObservers listening to the CSP violation
893 * topic that a violation occurred. Also triggers report sending. All
894 * asynchronous on the main thread.
895 *
896 * @param aBlockedContentSource
897 * Either a CSP Source (like 'self', as string) or nsIURI: the source
898 * of the violation.
899 * @param aOriginalUri
900 * The original URI if the blocked content is a redirect, else null
901 * @param aViolatedDirective
902 * the directive that was violated (string).
903 * @param aViolatedPolicyIndex
904 * the index of the policy that was violated (so we know where to send
905 * the reports).
906 * @param aObserverSubject
907 * optional, subject sent to the nsIObservers listening to the CSP
908 * violation topic.
909 * @param aSourceFile
910 * name of the file containing the inline script violation
911 * @param aScriptSample
912 * a sample of the violating inline script
913 * @param aLineNum
914 * source line number of the violation (if available)
915 */
916 _asyncReportViolation:
917 function(aBlockedContentSource, aOriginalUri, aViolatedDirective,
918 aViolatedPolicyIndex, aObserverSubject,
919 aSourceFile, aScriptSample, aLineNum) {
920 // if optional observerSubject isn't specified, default to the source of
921 // the violation.
922 if (!aObserverSubject)
923 aObserverSubject = aBlockedContentSource;
925 // gotta wrap things that aren't nsISupports, since it's sent out to
926 // observers as such. Objects that are not nsISupports are converted to
927 // strings and then wrapped into a nsISupportsCString.
928 if (!(aObserverSubject instanceof Ci.nsISupports)) {
929 let d = aObserverSubject;
930 aObserverSubject = Cc["@mozilla.org/supports-cstring;1"]
931 .createInstance(Ci.nsISupportsCString);
932 aObserverSubject.data = d;
933 }
935 var reportSender = this;
936 Services.tm.mainThread.dispatch(
937 function() {
938 Services.obs.notifyObservers(aObserverSubject,
939 CSP_VIOLATION_TOPIC,
940 aViolatedDirective);
941 reportSender.sendReports(aBlockedContentSource, aOriginalUri,
942 aViolatedDirective, aViolatedPolicyIndex,
943 aSourceFile, aScriptSample, aLineNum);
944 reportSender.logToConsole(aBlockedContentSource, aOriginalUri,
945 aViolatedDirective, aViolatedPolicyIndex,
946 aSourceFile, aScriptSample,
947 aLineNum, aObserverSubject);
949 }, Ci.nsIThread.DISPATCH_NORMAL);
950 },
952 /* ........ nsISerializable Methods: .............. */
954 read:
955 function(aStream) {
957 this._requestOrigin = aStream.readObject(true);
958 this._requestOrigin.QueryInterface(Ci.nsIURI);
960 for (let pCount = aStream.read32(); pCount > 0; pCount--) {
961 let polStr = aStream.readString();
962 let reportOnly = aStream.readBoolean();
963 let specCompliant = aStream.readBoolean();
964 // don't need self info because when the policy is turned back into a
965 // string, 'self' is replaced with the explicit source expression.
966 this._appendPolicyInternal(polStr, null, reportOnly, specCompliant,
967 false);
968 }
970 // NOTE: the document instance that's deserializing this object (via its
971 // principal) should hook itself into this._principal manually. If they
972 // don't, the CSP reports will likely be blocked by nsMixedContentBlocker.
973 },
975 write:
976 function(aStream) {
977 // need to serialize the context: request origin and such. They are
978 // used when sending reports. Since _request and _requestOrigin are just
979 // different representations of the same thing, only save _requestOrigin
980 // (an nsIURI).
981 aStream.writeCompoundObject(this._requestOrigin, Ci.nsIURI, true);
983 // we can't serialize a reference to the principal that triggered this
984 // instance to serialize, so when this is deserialized by the principal the
985 // caller must hook it up manually by calling setRequestContext on this
986 // instance with the appropriate nsIChannel.
988 // Finally, serialize all the policies.
989 aStream.write32(this._policies.length);
991 for each (var policy in this._policies) {
992 aStream.writeWStringZ(policy.toString());
993 aStream.writeBoolean(policy._reportOnlyMode);
994 aStream.writeBoolean(policy._specCompliant);
995 }
996 },
997 };
999 // The POST of the violation report (if it happens) should not follow
1000 // redirects, per the spec. hence, we implement an nsIChannelEventSink
1001 // with an object so we can tell XHR to abort if a redirect happens.
1002 function CSPReportRedirectSink(policy) {
1003 this._policy = policy;
1004 }
1006 CSPReportRedirectSink.prototype = {
1007 QueryInterface: function requestor_qi(iid) {
1008 if (iid.equals(Ci.nsISupports) ||
1009 iid.equals(Ci.nsIInterfaceRequestor) ||
1010 iid.equals(Ci.nsIChannelEventSink))
1011 return this;
1012 throw Cr.NS_ERROR_NO_INTERFACE;
1013 },
1015 // nsIInterfaceRequestor
1016 getInterface: function requestor_gi(iid) {
1017 if (iid.equals(Ci.nsIChannelEventSink))
1018 return this;
1020 throw Components.results.NS_ERROR_NO_INTERFACE;
1021 },
1023 // nsIChannelEventSink
1024 asyncOnChannelRedirect: function channel_redirect(oldChannel, newChannel,
1025 flags, callback) {
1026 this._policy.log(WARN_FLAG, CSPLocalizer.getFormatStr("reportPostRedirect", [oldChannel.URI.asciiSpec]));
1028 // cancel the old channel so XHR failure callback happens
1029 oldChannel.cancel(Cr.NS_ERROR_ABORT);
1031 // notify an observer that we have blocked the report POST due to a redirect,
1032 // used in testing, do this async since we're in an async call now to begin with
1033 Services.tm.mainThread.dispatch(
1034 function() {
1035 observerSubject = Cc["@mozilla.org/supports-cstring;1"]
1036 .createInstance(Ci.nsISupportsCString);
1037 observerSubject.data = oldChannel.URI.asciiSpec;
1039 Services.obs.notifyObservers(observerSubject,
1040 CSP_VIOLATION_TOPIC,
1041 "denied redirect while sending violation report");
1042 }, Ci.nsIThread.DISPATCH_NORMAL);
1044 // throw to stop the redirect happening
1045 throw Cr.NS_BINDING_REDIRECTED;
1046 }
1047 };
1049 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ContentSecurityPolicy]);