1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "nsCOMPtr.h"
8 #include "nsContentPolicyUtils.h"
9 #include "nsContentUtils.h"
10 #include "nsCSPContext.h"
11 #include "nsCSPParser.h"
12 #include "nsCSPService.h"
13 #include "nsError.h"
14 #include "nsIAsyncVerifyRedirectCallback.h"
15 #include "nsIClassInfoImpl.h"
16 #include "nsIDocShell.h"
17 #include "nsIDocShellTreeItem.h"
18 #include "nsIDOMHTMLDocument.h"
19 #include "nsIDOMHTMLElement.h"
20 #include "nsIDOMNode.h"
21 #include "nsIHttpChannel.h"
22 #include "nsIInterfaceRequestor.h"
23 #include "nsIInterfaceRequestorUtils.h"
24 #include "nsIObjectInputStream.h"
25 #include "nsIObjectOutputStream.h"
26 #include "nsIObserver.h"
27 #include "nsIObserverService.h"
28 #include "nsIStringStream.h"
29 #include "nsIUploadChannel.h"
30 #include "nsIScriptError.h"
31 #include "nsIWebNavigation.h"
32 #include "nsMimeTypes.h"
33 #include "nsNetUtil.h"
34 #include "nsIContentPolicy.h"
35 #include "nsSupportsPrimitives.h"
36 #include "nsThreadUtils.h"
37 #include "nsString.h"
38 #include "nsScriptSecurityManager.h"
39 #include "nsStringStream.h"
40 #include "mozilla/Logging.h"
41 #include "mozilla/dom/CSPReportBinding.h"
42 #include "mozilla/dom/CSPDictionariesBinding.h"
43 #include "mozilla/net/ReferrerPolicy.h"
44 #include "nsINetworkInterceptController.h"
45 #include "nsSandboxFlags.h"
46 #include "nsIScriptElement.h"
47
48 using namespace mozilla;
49
50 static LogModule*
GetCspContextLog()51 GetCspContextLog()
52 {
53 static LazyLogModule gCspContextPRLog("CSPContext");
54 return gCspContextPRLog;
55 }
56
57 #define CSPCONTEXTLOG(args) MOZ_LOG(GetCspContextLog(), mozilla::LogLevel::Debug, args)
58 #define CSPCONTEXTLOGENABLED() MOZ_LOG_TEST(GetCspContextLog(), mozilla::LogLevel::Debug)
59
60 static const uint32_t CSP_CACHE_URI_CUTOFF_SIZE = 512;
61
62 /**
63 * Creates a key for use in the ShouldLoad cache.
64 * Looks like: <uri>!<nsIContentPolicy::LOAD_TYPE>
65 */
66 nsresult
CreateCacheKey_Internal(nsIURI * aContentLocation,nsContentPolicyType aContentType,nsACString & outCacheKey)67 CreateCacheKey_Internal(nsIURI* aContentLocation,
68 nsContentPolicyType aContentType,
69 nsACString& outCacheKey)
70 {
71 if (!aContentLocation) {
72 return NS_ERROR_FAILURE;
73 }
74
75 bool isDataScheme = false;
76 nsresult rv = aContentLocation->SchemeIs("data", &isDataScheme);
77 NS_ENSURE_SUCCESS(rv, rv);
78
79 outCacheKey.Truncate();
80 if (aContentType != nsIContentPolicy::TYPE_SCRIPT && isDataScheme) {
81 // For non-script data: URI, use ("data:", aContentType) as the cache key.
82 outCacheKey.Append(NS_LITERAL_CSTRING("data:"));
83 outCacheKey.AppendInt(aContentType);
84 return NS_OK;
85 }
86
87 nsAutoCString spec;
88 rv = aContentLocation->GetSpec(spec);
89 NS_ENSURE_SUCCESS(rv, rv);
90
91 // Don't cache for a URI longer than the cutoff size.
92 if (spec.Length() <= CSP_CACHE_URI_CUTOFF_SIZE) {
93 outCacheKey.Append(spec);
94 outCacheKey.Append(NS_LITERAL_CSTRING("!"));
95 outCacheKey.AppendInt(aContentType);
96 }
97
98 return NS_OK;
99 }
100
101 /* ===== nsIContentSecurityPolicy impl ====== */
102
103 NS_IMETHODIMP
ShouldLoad(nsContentPolicyType aContentType,nsIURI * aContentLocation,nsIURI * aRequestOrigin,nsISupports * aRequestContext,const nsACString & aMimeTypeGuess,nsISupports * aExtra,int16_t * outDecision)104 nsCSPContext::ShouldLoad(nsContentPolicyType aContentType,
105 nsIURI* aContentLocation,
106 nsIURI* aRequestOrigin,
107 nsISupports* aRequestContext,
108 const nsACString& aMimeTypeGuess,
109 nsISupports* aExtra,
110 int16_t* outDecision)
111 {
112 if (CSPCONTEXTLOGENABLED()) {
113 CSPCONTEXTLOG(("nsCSPContext::ShouldLoad, aContentLocation: %s",
114 aContentLocation->GetSpecOrDefault().get()));
115 CSPCONTEXTLOG((">>>> aContentType: %d", aContentType));
116 }
117
118 bool isPreload = nsContentUtils::IsPreloadType(aContentType);
119
120 // Since we know whether we are dealing with a preload, we have to convert
121 // the internal policytype ot the external policy type before moving on.
122 // We still need to know if this is a worker so child-src can handle that
123 // case correctly.
124 aContentType = nsContentUtils::InternalContentPolicyTypeToExternalOrWorker(aContentType);
125
126 nsresult rv = NS_OK;
127
128 // This ShouldLoad function is called from nsCSPService::ShouldLoad,
129 // which already checked a number of things, including:
130 // * aContentLocation is not null; we can consume this without further checks
131 // * scheme is not a whitelisted scheme (about: chrome:, etc).
132 // * CSP is enabled
133 // * Content Type is not whitelisted (CSP Reports, TYPE_DOCUMENT, etc).
134 // * Fast Path for Apps
135
136 nsAutoCString cacheKey;
137 rv = CreateCacheKey_Internal(aContentLocation, aContentType, cacheKey);
138 NS_ENSURE_SUCCESS(rv, rv);
139
140 bool isCached = mShouldLoadCache.Get(cacheKey, outDecision);
141 if (isCached && cacheKey.Length() > 0) {
142 // this is cached, use the cached value.
143 return NS_OK;
144 }
145
146 // Default decision, CSP can revise it if there's a policy to enforce
147 *outDecision = nsIContentPolicy::ACCEPT;
148
149 // If the content type doesn't map to a CSP directive, there's nothing for
150 // CSP to do.
151 CSPDirective dir = CSP_ContentTypeToDirective(aContentType);
152 if (dir == nsIContentSecurityPolicy::NO_DIRECTIVE) {
153 return NS_OK;
154 }
155
156 nsAutoString nonce;
157 bool parserCreated = false;
158 if (!isPreload) {
159 nsCOMPtr<nsIDOMHTMLElement> htmlElement = do_QueryInterface(aRequestContext);
160 if (htmlElement) {
161 rv = htmlElement->GetAttribute(NS_LITERAL_STRING("nonce"), nonce);
162 NS_ENSURE_SUCCESS(rv, rv);
163 }
164
165 nsCOMPtr<nsIScriptElement> script = do_QueryInterface(aRequestContext);
166 if (script && script->GetParserCreated() != mozilla::dom::NOT_FROM_PARSER) {
167 parserCreated = true;
168 }
169 }
170
171 // aExtra is only non-null if the channel got redirected.
172 bool wasRedirected = (aExtra != nullptr);
173 nsCOMPtr<nsIURI> originalURI = do_QueryInterface(aExtra);
174
175 bool permitted = permitsInternal(dir,
176 aContentLocation,
177 originalURI,
178 nonce,
179 wasRedirected,
180 isPreload,
181 false, // allow fallback to default-src
182 true, // send violation reports
183 true, // send blocked URI in violation reports
184 parserCreated);
185
186 *outDecision = permitted ? nsIContentPolicy::ACCEPT
187 : nsIContentPolicy::REJECT_SERVER;
188
189 // Done looping, cache any relevant result
190 if (cacheKey.Length() > 0 && !isPreload) {
191 mShouldLoadCache.Put(cacheKey, *outDecision);
192 }
193
194 if (CSPCONTEXTLOGENABLED()) {
195 CSPCONTEXTLOG(("nsCSPContext::ShouldLoad, decision: %s, "
196 "aContentLocation: %s",
197 *outDecision > 0 ? "load" : "deny",
198 aContentLocation->GetSpecOrDefault().get()));
199 }
200 return NS_OK;
201 }
202
203 bool
permitsInternal(CSPDirective aDir,nsIURI * aContentLocation,nsIURI * aOriginalURI,const nsAString & aNonce,bool aWasRedirected,bool aIsPreload,bool aSpecific,bool aSendViolationReports,bool aSendContentLocationInViolationReports,bool aParserCreated)204 nsCSPContext::permitsInternal(CSPDirective aDir,
205 nsIURI* aContentLocation,
206 nsIURI* aOriginalURI,
207 const nsAString& aNonce,
208 bool aWasRedirected,
209 bool aIsPreload,
210 bool aSpecific,
211 bool aSendViolationReports,
212 bool aSendContentLocationInViolationReports,
213 bool aParserCreated)
214 {
215 bool permits = true;
216
217 nsAutoString violatedDirective;
218 for (uint32_t p = 0; p < mPolicies.Length(); p++) {
219
220 // According to the W3C CSP spec, frame-ancestors checks are ignored for
221 // report-only policies (when "monitoring").
222 if (aDir == nsIContentSecurityPolicy::FRAME_ANCESTORS_DIRECTIVE &&
223 mPolicies[p]->getReportOnlyFlag()) {
224 continue;
225 }
226
227 if (!mPolicies[p]->permits(aDir,
228 aContentLocation,
229 aNonce,
230 aWasRedirected,
231 aSpecific,
232 aParserCreated,
233 violatedDirective)) {
234 // If the policy is violated and not report-only, reject the load and
235 // report to the console
236 if (!mPolicies[p]->getReportOnlyFlag()) {
237 CSPCONTEXTLOG(("nsCSPContext::permitsInternal, false"));
238 permits = false;
239 }
240
241 // Do not send a report or notify observers if this is a preload - the
242 // decision may be wrong due to the inability to get the nonce, and will
243 // incorrectly fail the unit tests.
244 if (!aIsPreload && aSendViolationReports) {
245 this->AsyncReportViolation((aSendContentLocationInViolationReports ?
246 aContentLocation : nullptr),
247 aOriginalURI, /* in case of redirect originalURI is not null */
248 violatedDirective,
249 p, /* policy index */
250 EmptyString(), /* no observer subject */
251 EmptyString(), /* no source file */
252 EmptyString(), /* no script sample */
253 0); /* no line number */
254 }
255 }
256 }
257
258 return permits;
259 }
260
261
262
263 /* ===== nsISupports implementation ========== */
264
NS_IMPL_CLASSINFO(nsCSPContext,nullptr,nsIClassInfo::MAIN_THREAD_ONLY,NS_CSPCONTEXT_CID)265 NS_IMPL_CLASSINFO(nsCSPContext,
266 nullptr,
267 nsIClassInfo::MAIN_THREAD_ONLY,
268 NS_CSPCONTEXT_CID)
269
270 NS_IMPL_ISUPPORTS_CI(nsCSPContext,
271 nsIContentSecurityPolicy,
272 nsISerializable)
273
274 nsCSPContext::nsCSPContext()
275 : mInnerWindowID(0)
276 , mLoadingContext(nullptr)
277 , mLoadingPrincipal(nullptr)
278 , mQueueUpMessages(true)
279 {
280 CSPCONTEXTLOG(("nsCSPContext::nsCSPContext"));
281 }
282
~nsCSPContext()283 nsCSPContext::~nsCSPContext()
284 {
285 CSPCONTEXTLOG(("nsCSPContext::~nsCSPContext"));
286 for (uint32_t i = 0; i < mPolicies.Length(); i++) {
287 delete mPolicies[i];
288 }
289 mShouldLoadCache.Clear();
290 }
291
292 NS_IMETHODIMP
GetPolicyString(uint32_t aIndex,nsAString & outStr)293 nsCSPContext::GetPolicyString(uint32_t aIndex, nsAString& outStr)
294 {
295 if (aIndex >= mPolicies.Length()) {
296 return NS_ERROR_ILLEGAL_VALUE;
297 }
298 mPolicies[aIndex]->toString(outStr);
299 return NS_OK;
300 }
301
302 const nsCSPPolicy*
GetPolicy(uint32_t aIndex)303 nsCSPContext::GetPolicy(uint32_t aIndex)
304 {
305 if (aIndex >= mPolicies.Length()) {
306 return nullptr;
307 }
308 return mPolicies[aIndex];
309 }
310
311 NS_IMETHODIMP
GetPolicyCount(uint32_t * outPolicyCount)312 nsCSPContext::GetPolicyCount(uint32_t *outPolicyCount)
313 {
314 *outPolicyCount = mPolicies.Length();
315 return NS_OK;
316 }
317
318 NS_IMETHODIMP
GetUpgradeInsecureRequests(bool * outUpgradeRequest)319 nsCSPContext::GetUpgradeInsecureRequests(bool *outUpgradeRequest)
320 {
321 *outUpgradeRequest = false;
322 for (uint32_t i = 0; i < mPolicies.Length(); i++) {
323 if (mPolicies[i]->hasDirective(nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE)) {
324 *outUpgradeRequest = true;
325 return NS_OK;
326 }
327 }
328 return NS_OK;
329 }
330
331 NS_IMETHODIMP
GetBlockAllMixedContent(bool * outBlockAllMixedContent)332 nsCSPContext::GetBlockAllMixedContent(bool *outBlockAllMixedContent)
333 {
334 *outBlockAllMixedContent = false;
335 for (uint32_t i = 0; i < mPolicies.Length(); i++) {
336 if (!mPolicies[i]->getReportOnlyFlag() &&
337 mPolicies[i]->hasDirective(nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT)) {
338 *outBlockAllMixedContent = true;
339 return NS_OK;
340 }
341 }
342 return NS_OK;
343 }
344
345 NS_IMETHODIMP
GetReferrerPolicy(uint32_t * outPolicy,bool * outIsSet)346 nsCSPContext::GetReferrerPolicy(uint32_t* outPolicy, bool* outIsSet)
347 {
348 *outIsSet = false;
349 *outPolicy = mozilla::net::RP_Default;
350 nsAutoString refpol;
351 mozilla::net::ReferrerPolicy previousPolicy = mozilla::net::RP_Default;
352 for (uint32_t i = 0; i < mPolicies.Length(); i++) {
353 mPolicies[i]->getReferrerPolicy(refpol);
354 // only set the referrer policy if not delievered through a CSPRO and
355 // note that and an empty string in refpol means it wasn't set
356 // (that's the default in nsCSPPolicy).
357 if (!mPolicies[i]->getReportOnlyFlag() && !refpol.IsEmpty()) {
358 // Referrer Directive in CSP is no more used and going to be replaced by
359 // Referrer-Policy HTTP header. But we still keep using referrer directive,
360 // and would remove it later.
361 // Referrer Directive specs is not fully compliant with new referrer policy
362 // specs. What we are using here:
363 // - If the value of the referrer directive is invalid, the user agent
364 // should set the referrer policy to no-referrer.
365 // - If there are two policies that specify a referrer policy, then they
366 // must agree or the employed policy is no-referrer.
367 if (!mozilla::net::IsValidReferrerPolicy(refpol)) {
368 *outPolicy = mozilla::net::RP_No_Referrer;
369 *outIsSet = true;
370 return NS_OK;
371 }
372
373 uint32_t currentPolicy = mozilla::net::ReferrerPolicyFromString(refpol);
374 if (*outIsSet && previousPolicy != currentPolicy) {
375 *outPolicy = mozilla::net::RP_No_Referrer;
376 return NS_OK;
377 }
378
379 *outPolicy = currentPolicy;
380 *outIsSet = true;
381 }
382 }
383
384 return NS_OK;
385 }
386
387 NS_IMETHODIMP
AppendPolicy(const nsAString & aPolicyString,bool aReportOnly,bool aDeliveredViaMetaTag)388 nsCSPContext::AppendPolicy(const nsAString& aPolicyString,
389 bool aReportOnly,
390 bool aDeliveredViaMetaTag)
391 {
392 CSPCONTEXTLOG(("nsCSPContext::AppendPolicy: %s",
393 NS_ConvertUTF16toUTF8(aPolicyString).get()));
394
395 // Use the mSelfURI from setRequestContext, see bug 991474
396 NS_ASSERTION(mSelfURI, "mSelfURI required for AppendPolicy, but not set");
397 nsCSPPolicy* policy = nsCSPParser::parseContentSecurityPolicy(aPolicyString, mSelfURI,
398 aReportOnly, this,
399 aDeliveredViaMetaTag);
400 if (policy) {
401 mPolicies.AppendElement(policy);
402 // reset cache since effective policy changes
403 mShouldLoadCache.Clear();
404 }
405 return NS_OK;
406 }
407
408 NS_IMETHODIMP
GetAllowsEval(bool * outShouldReportViolation,bool * outAllowsEval)409 nsCSPContext::GetAllowsEval(bool* outShouldReportViolation,
410 bool* outAllowsEval)
411 {
412 *outShouldReportViolation = false;
413 *outAllowsEval = true;
414
415 for (uint32_t i = 0; i < mPolicies.Length(); i++) {
416 if (!mPolicies[i]->allows(nsIContentPolicy::TYPE_SCRIPT,
417 CSP_UNSAFE_EVAL,
418 EmptyString(),
419 false)) {
420 // policy is violated: must report the violation and allow the inline
421 // script if the policy is report-only.
422 *outShouldReportViolation = true;
423 if (!mPolicies[i]->getReportOnlyFlag()) {
424 *outAllowsEval = false;
425 }
426 }
427 }
428 return NS_OK;
429 }
430
431 // Helper function to report inline violations
432 void
reportInlineViolation(nsContentPolicyType aContentType,const nsAString & aNonce,const nsAString & aContent,const nsAString & aViolatedDirective,uint32_t aViolatedPolicyIndex,uint32_t aLineNumber)433 nsCSPContext::reportInlineViolation(nsContentPolicyType aContentType,
434 const nsAString& aNonce,
435 const nsAString& aContent,
436 const nsAString& aViolatedDirective,
437 uint32_t aViolatedPolicyIndex, // TODO, use report only flag for that
438 uint32_t aLineNumber)
439 {
440 nsString observerSubject;
441 // if the nonce is non empty, then we report the nonce error, otherwise
442 // let's report the hash error; no need to report the unsafe-inline error
443 // anymore.
444 if (!aNonce.IsEmpty()) {
445 observerSubject = (aContentType == nsIContentPolicy::TYPE_SCRIPT)
446 ? NS_LITERAL_STRING(SCRIPT_NONCE_VIOLATION_OBSERVER_TOPIC)
447 : NS_LITERAL_STRING(STYLE_NONCE_VIOLATION_OBSERVER_TOPIC);
448 }
449 else {
450 observerSubject = (aContentType == nsIContentPolicy::TYPE_SCRIPT)
451 ? NS_LITERAL_STRING(SCRIPT_HASH_VIOLATION_OBSERVER_TOPIC)
452 : NS_LITERAL_STRING(STYLE_HASH_VIOLATION_OBSERVER_TOPIC);
453 }
454
455 nsCOMPtr<nsISupportsCString> selfICString(do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID));
456 if (selfICString) {
457 selfICString->SetData(nsDependentCString("self"));
458 }
459 nsCOMPtr<nsISupports> selfISupports(do_QueryInterface(selfICString));
460
461 // use selfURI as the sourceFile
462 nsAutoCString sourceFile;
463 if (mSelfURI) {
464 mSelfURI->GetSpec(sourceFile);
465 }
466
467 nsAutoString codeSample(aContent);
468 // cap the length of the script sample at 40 chars
469 if (codeSample.Length() > 40) {
470 codeSample.Truncate(40);
471 codeSample.AppendLiteral("...");
472 }
473 AsyncReportViolation(selfISupports, // aBlockedContentSource
474 mSelfURI, // aOriginalURI
475 aViolatedDirective, // aViolatedDirective
476 aViolatedPolicyIndex, // aViolatedPolicyIndex
477 observerSubject, // aObserverSubject
478 NS_ConvertUTF8toUTF16(sourceFile), // aSourceFile
479 codeSample, // aScriptSample
480 aLineNumber); // aLineNum
481 }
482
483 NS_IMETHODIMP
GetAllowsInline(nsContentPolicyType aContentType,const nsAString & aNonce,bool aParserCreated,const nsAString & aContent,uint32_t aLineNumber,bool * outAllowsInline)484 nsCSPContext::GetAllowsInline(nsContentPolicyType aContentType,
485 const nsAString& aNonce,
486 bool aParserCreated,
487 const nsAString& aContent,
488 uint32_t aLineNumber,
489 bool* outAllowsInline)
490 {
491 *outAllowsInline = true;
492
493 MOZ_ASSERT(aContentType == nsContentUtils::InternalContentPolicyTypeToExternal(aContentType),
494 "We should only see external content policy types here.");
495
496 if (aContentType != nsIContentPolicy::TYPE_SCRIPT &&
497 aContentType != nsIContentPolicy::TYPE_STYLESHEET) {
498 MOZ_ASSERT(false, "can only allow inline for script or style");
499 return NS_OK;
500 }
501
502 // always iterate all policies, otherwise we might not send out all reports
503 for (uint32_t i = 0; i < mPolicies.Length(); i++) {
504 bool allowed =
505 mPolicies[i]->allows(aContentType, CSP_UNSAFE_INLINE, EmptyString(), aParserCreated) ||
506 mPolicies[i]->allows(aContentType, CSP_NONCE, aNonce, aParserCreated) ||
507 mPolicies[i]->allows(aContentType, CSP_HASH, aContent, aParserCreated);
508
509 if (!allowed) {
510 // policy is violoated: deny the load unless policy is report only and
511 // report the violation.
512 if (!mPolicies[i]->getReportOnlyFlag()) {
513 *outAllowsInline = false;
514 }
515 nsAutoString violatedDirective;
516 mPolicies[i]->getDirectiveStringForContentType(aContentType, violatedDirective);
517 reportInlineViolation(aContentType,
518 aNonce,
519 aContent,
520 violatedDirective,
521 i,
522 aLineNumber);
523 }
524 }
525 return NS_OK;
526 }
527
528
529 /**
530 * Reduces some code repetition for the various logging situations in
531 * LogViolationDetails.
532 *
533 * Call-sites for the eval/inline checks recieve two return values: allows
534 * and violates. Based on those, they must choose whether to call
535 * LogViolationDetails or not. Policies that are report-only allow the
536 * loads/compilations but violations should still be reported. Not all
537 * policies in this nsIContentSecurityPolicy instance will be violated,
538 * which is why we must check allows() again here.
539 *
540 * Note: This macro uses some parameters from its caller's context:
541 * p, mPolicies, this, aSourceFile, aScriptSample, aLineNum, selfISupports
542 *
543 * @param violationType: the VIOLATION_TYPE_* constant (partial symbol)
544 * such as INLINE_SCRIPT
545 * @param contentPolicyType: a constant from nsIContentPolicy such as TYPE_STYLESHEET
546 * @param nonceOrHash: for NONCE and HASH violations, it's the nonce or content
547 * string. For other violations, it is an empty string.
548 * @param keyword: the keyword corresponding to violation (UNSAFE_INLINE for most)
549 * @param observerTopic: the observer topic string to send with the CSP
550 * observer notifications.
551 *
552 * Please note that inline violations for scripts are reported within
553 * GetAllowsInline() and do not call this macro, hence we can pass 'false'
554 * as the argument _aParserCreated_ to allows().
555 */
556 #define CASE_CHECK_AND_REPORT(violationType, contentPolicyType, nonceOrHash, \
557 keyword, observerTopic) \
558 case nsIContentSecurityPolicy::VIOLATION_TYPE_ ## violationType : \
559 PR_BEGIN_MACRO \
560 if (!mPolicies[p]->allows(nsIContentPolicy::TYPE_ ## contentPolicyType, \
561 keyword, nonceOrHash, false)) \
562 { \
563 nsAutoString violatedDirective; \
564 mPolicies[p]->getDirectiveStringForContentType( \
565 nsIContentPolicy::TYPE_ ## contentPolicyType, \
566 violatedDirective); \
567 this->AsyncReportViolation(selfISupports, nullptr, violatedDirective, p, \
568 NS_LITERAL_STRING(observerTopic), \
569 aSourceFile, aScriptSample, aLineNum); \
570 } \
571 PR_END_MACRO; \
572 break
573
574 /**
575 * For each policy, log any violation on the Error Console and send a report
576 * if a report-uri is present in the policy
577 *
578 * @param aViolationType
579 * one of the VIOLATION_TYPE_* constants, e.g. inline-script or eval
580 * @param aSourceFile
581 * name of the source file containing the violation (if available)
582 * @param aContentSample
583 * sample of the violating content (to aid debugging)
584 * @param aLineNum
585 * source line number of the violation (if available)
586 * @param aNonce
587 * (optional) If this is a nonce violation, include the nonce so we can
588 * recheck to determine which policies were violated and send the
589 * appropriate reports.
590 * @param aContent
591 * (optional) If this is a hash violation, include contents of the inline
592 * resource in the question so we can recheck the hash in order to
593 * determine which policies were violated and send the appropriate
594 * reports.
595 */
596 NS_IMETHODIMP
LogViolationDetails(uint16_t aViolationType,const nsAString & aSourceFile,const nsAString & aScriptSample,int32_t aLineNum,const nsAString & aNonce,const nsAString & aContent)597 nsCSPContext::LogViolationDetails(uint16_t aViolationType,
598 const nsAString& aSourceFile,
599 const nsAString& aScriptSample,
600 int32_t aLineNum,
601 const nsAString& aNonce,
602 const nsAString& aContent)
603 {
604 for (uint32_t p = 0; p < mPolicies.Length(); p++) {
605 NS_ASSERTION(mPolicies[p], "null pointer in nsTArray<nsCSPPolicy>");
606
607 nsCOMPtr<nsISupportsCString> selfICString(do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID));
608 if (selfICString) {
609 selfICString->SetData(nsDependentCString("self"));
610 }
611 nsCOMPtr<nsISupports> selfISupports(do_QueryInterface(selfICString));
612
613 switch (aViolationType) {
614 CASE_CHECK_AND_REPORT(EVAL, SCRIPT, NS_LITERAL_STRING(""),
615 CSP_UNSAFE_EVAL, EVAL_VIOLATION_OBSERVER_TOPIC);
616 CASE_CHECK_AND_REPORT(INLINE_STYLE, STYLESHEET, NS_LITERAL_STRING(""),
617 CSP_UNSAFE_INLINE, INLINE_STYLE_VIOLATION_OBSERVER_TOPIC);
618 CASE_CHECK_AND_REPORT(INLINE_SCRIPT, SCRIPT, NS_LITERAL_STRING(""),
619 CSP_UNSAFE_INLINE, INLINE_SCRIPT_VIOLATION_OBSERVER_TOPIC);
620 CASE_CHECK_AND_REPORT(NONCE_SCRIPT, SCRIPT, aNonce,
621 CSP_UNSAFE_INLINE, SCRIPT_NONCE_VIOLATION_OBSERVER_TOPIC);
622 CASE_CHECK_AND_REPORT(NONCE_STYLE, STYLESHEET, aNonce,
623 CSP_UNSAFE_INLINE, STYLE_NONCE_VIOLATION_OBSERVER_TOPIC);
624 CASE_CHECK_AND_REPORT(HASH_SCRIPT, SCRIPT, aContent,
625 CSP_UNSAFE_INLINE, SCRIPT_HASH_VIOLATION_OBSERVER_TOPIC);
626 CASE_CHECK_AND_REPORT(HASH_STYLE, STYLESHEET, aContent,
627 CSP_UNSAFE_INLINE, STYLE_HASH_VIOLATION_OBSERVER_TOPIC);
628 CASE_CHECK_AND_REPORT(REQUIRE_SRI_FOR_STYLE, STYLESHEET, NS_LITERAL_STRING(""),
629 CSP_REQUIRE_SRI_FOR, REQUIRE_SRI_STYLE_VIOLATION_OBSERVER_TOPIC);
630 CASE_CHECK_AND_REPORT(REQUIRE_SRI_FOR_SCRIPT, SCRIPT, NS_LITERAL_STRING(""),
631 CSP_REQUIRE_SRI_FOR, REQUIRE_SRI_SCRIPT_VIOLATION_OBSERVER_TOPIC);
632
633
634 default:
635 NS_ASSERTION(false, "LogViolationDetails with invalid type");
636 break;
637 }
638 }
639 return NS_OK;
640 }
641
642 #undef CASE_CHECK_AND_REPORT
643
644 NS_IMETHODIMP
SetRequestContext(nsIDOMDocument * aDOMDocument,nsIPrincipal * aPrincipal)645 nsCSPContext::SetRequestContext(nsIDOMDocument* aDOMDocument,
646 nsIPrincipal* aPrincipal)
647 {
648 NS_PRECONDITION(aDOMDocument || aPrincipal,
649 "Can't set context without doc or principal");
650 NS_ENSURE_ARG(aDOMDocument || aPrincipal);
651
652 if (aDOMDocument) {
653 nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDOMDocument);
654 mLoadingContext = do_GetWeakReference(doc);
655 mSelfURI = doc->GetDocumentURI();
656 mLoadingPrincipal = doc->NodePrincipal();
657 doc->GetReferrer(mReferrer);
658 mInnerWindowID = doc->InnerWindowID();
659 // the innerWindowID is not available for CSPs delivered through the
660 // header at the time setReqeustContext is called - let's queue up
661 // console messages until it becomes available, see flushConsoleMessages
662 mQueueUpMessages = !mInnerWindowID;
663 mCallingChannelLoadGroup = doc->GetDocumentLoadGroup();
664
665 // set the flag on the document for CSP telemetry
666 doc->SetHasCSP(true);
667 }
668 else {
669 CSPCONTEXTLOG(("No Document in SetRequestContext; can not query loadgroup; sending reports may fail."));
670 mLoadingPrincipal = aPrincipal;
671 mLoadingPrincipal->GetURI(getter_AddRefs(mSelfURI));
672 // if no document is available, then it also does not make sense to queue console messages
673 // sending messages to the browser conolse instead of the web console in that case.
674 mQueueUpMessages = false;
675 }
676
677 NS_ASSERTION(mSelfURI, "mSelfURI not available, can not translate 'self' into actual URI");
678 return NS_OK;
679 }
680
681 struct ConsoleMsgQueueElem {
682 nsXPIDLString mMsg;
683 nsString mSourceName;
684 nsString mSourceLine;
685 uint32_t mLineNumber;
686 uint32_t mColumnNumber;
687 uint32_t mSeverityFlag;
688 };
689
690 void
flushConsoleMessages()691 nsCSPContext::flushConsoleMessages()
692 {
693 // should flush messages even if doc is not available
694 nsCOMPtr<nsIDocument> doc = do_QueryReferent(mLoadingContext);
695 if (doc) {
696 mInnerWindowID = doc->InnerWindowID();
697 }
698 mQueueUpMessages = false;
699
700 for (uint32_t i = 0; i < mConsoleMsgQueue.Length(); i++) {
701 ConsoleMsgQueueElem &elem = mConsoleMsgQueue[i];
702 CSP_LogMessage(elem.mMsg, elem.mSourceName, elem.mSourceLine,
703 elem.mLineNumber, elem.mColumnNumber,
704 elem.mSeverityFlag, "CSP", mInnerWindowID);
705 }
706 mConsoleMsgQueue.Clear();
707 }
708
709 void
logToConsole(const char16_t * aName,const char16_t ** aParams,uint32_t aParamsLength,const nsAString & aSourceName,const nsAString & aSourceLine,uint32_t aLineNumber,uint32_t aColumnNumber,uint32_t aSeverityFlag)710 nsCSPContext::logToConsole(const char16_t* aName,
711 const char16_t** aParams,
712 uint32_t aParamsLength,
713 const nsAString& aSourceName,
714 const nsAString& aSourceLine,
715 uint32_t aLineNumber,
716 uint32_t aColumnNumber,
717 uint32_t aSeverityFlag)
718 {
719 // let's check if we have to queue up console messages
720 if (mQueueUpMessages) {
721 nsXPIDLString msg;
722 CSP_GetLocalizedStr(aName, aParams, aParamsLength, getter_Copies(msg));
723 ConsoleMsgQueueElem &elem = *mConsoleMsgQueue.AppendElement();
724 elem.mMsg = msg;
725 elem.mSourceName = PromiseFlatString(aSourceName);
726 elem.mSourceLine = PromiseFlatString(aSourceLine);
727 elem.mLineNumber = aLineNumber;
728 elem.mColumnNumber = aColumnNumber;
729 elem.mSeverityFlag = aSeverityFlag;
730 return;
731 }
732 CSP_LogLocalizedStr(aName, aParams, aParamsLength, aSourceName,
733 aSourceLine, aLineNumber, aColumnNumber,
734 aSeverityFlag, "CSP", mInnerWindowID);
735 }
736
737 /**
738 * Strip URI for reporting according to:
739 * http://www.w3.org/TR/CSP/#violation-reports
740 *
741 * @param aURI
742 * The uri to be stripped for reporting
743 * @param aSelfURI
744 * The uri of the protected resource
745 * which is needed to enforce the SOP.
746 * @return ASCII serialization of the uri to be reported.
747 */
748 void
StripURIForReporting(nsIURI * aURI,nsIURI * aSelfURI,nsACString & outStrippedURI)749 StripURIForReporting(nsIURI* aURI,
750 nsIURI* aSelfURI,
751 nsACString& outStrippedURI)
752 {
753 // 1) If the origin of uri is a globally unique identifier (for example,
754 // aURI has a scheme of data, blob, or filesystem), then return the
755 // ASCII serialization of uri’s scheme.
756 bool isHttpOrFtp =
757 (NS_SUCCEEDED(aURI->SchemeIs("http", &isHttpOrFtp)) && isHttpOrFtp) ||
758 (NS_SUCCEEDED(aURI->SchemeIs("https", &isHttpOrFtp)) && isHttpOrFtp) ||
759 (NS_SUCCEEDED(aURI->SchemeIs("ftp", &isHttpOrFtp)) && isHttpOrFtp);
760
761 if (!isHttpOrFtp) {
762 // not strictly spec compliant, but what we really care about is
763 // http/https and also ftp. If it's not http/https or ftp, then treat aURI
764 // as if it's a globally unique identifier and just return the scheme.
765 aURI->GetScheme(outStrippedURI);
766 return;
767 }
768
769 // 2) If the origin of uri is not the same as the origin of the protected
770 // resource, then return the ASCII serialization of uri’s origin.
771 if (!NS_SecurityCompareURIs(aSelfURI, aURI, false)) {
772 // cross origin redirects also fall into this category, see:
773 // http://www.w3.org/TR/CSP/#violation-reports
774 aURI->GetPrePath(outStrippedURI);
775 return;
776 }
777
778 // 3) Return uri, with any fragment component removed.
779 aURI->GetSpecIgnoringRef(outStrippedURI);
780 }
781
782 /**
783 * Sends CSP violation reports to all sources listed under report-uri.
784 *
785 * @param aBlockedContentSource
786 * Either a CSP Source (like 'self', as string) or nsIURI: the source
787 * of the violation.
788 * @param aOriginalUri
789 * The original URI if the blocked content is a redirect, else null
790 * @param aViolatedDirective
791 * the directive that was violated (string).
792 * @param aSourceFile
793 * name of the file containing the inline script violation
794 * @param aScriptSample
795 * a sample of the violating inline script
796 * @param aLineNum
797 * source line number of the violation (if available)
798 */
799 nsresult
SendReports(nsISupports * aBlockedContentSource,nsIURI * aOriginalURI,nsAString & aViolatedDirective,uint32_t aViolatedPolicyIndex,nsAString & aSourceFile,nsAString & aScriptSample,uint32_t aLineNum)800 nsCSPContext::SendReports(nsISupports* aBlockedContentSource,
801 nsIURI* aOriginalURI,
802 nsAString& aViolatedDirective,
803 uint32_t aViolatedPolicyIndex,
804 nsAString& aSourceFile,
805 nsAString& aScriptSample,
806 uint32_t aLineNum)
807 {
808 NS_ENSURE_ARG_MAX(aViolatedPolicyIndex, mPolicies.Length() - 1);
809
810 #ifdef MOZ_B2G
811 // load group information (on process-split necko implementations like b2g).
812 // (fix this in bug 1011086)
813 if (!mCallingChannelLoadGroup) {
814 NS_WARNING("Load group required but not present for report sending; cannot send CSP violation reports");
815 return NS_ERROR_FAILURE;
816 }
817 #endif
818
819 dom::CSPReport report;
820 nsresult rv;
821
822 // blocked-uri
823 if (aBlockedContentSource) {
824 nsAutoCString reportBlockedURI;
825 nsCOMPtr<nsIURI> uri = do_QueryInterface(aBlockedContentSource);
826 // could be a string or URI
827 if (uri) {
828 StripURIForReporting(uri, mSelfURI, reportBlockedURI);
829 } else {
830 nsCOMPtr<nsISupportsCString> cstr = do_QueryInterface(aBlockedContentSource);
831 if (cstr) {
832 cstr->GetData(reportBlockedURI);
833 }
834 }
835 if (reportBlockedURI.IsEmpty()) {
836 // this can happen for frame-ancestors violation where the violating
837 // ancestor is cross-origin.
838 NS_WARNING("No blocked URI (null aBlockedContentSource) for CSP violation report.");
839 }
840 report.mCsp_report.mBlocked_uri = NS_ConvertUTF8toUTF16(reportBlockedURI);
841 }
842
843 // document-uri
844 nsAutoCString reportDocumentURI;
845 StripURIForReporting(mSelfURI, mSelfURI, reportDocumentURI);
846 report.mCsp_report.mDocument_uri = NS_ConvertUTF8toUTF16(reportDocumentURI);
847
848 // original-policy
849 nsAutoString originalPolicy;
850 rv = this->GetPolicyString(aViolatedPolicyIndex, originalPolicy);
851 NS_ENSURE_SUCCESS(rv, rv);
852 report.mCsp_report.mOriginal_policy = originalPolicy;
853
854 // referrer
855 if (!mReferrer.IsEmpty()) {
856 report.mCsp_report.mReferrer = mReferrer;
857 }
858
859 // violated-directive
860 report.mCsp_report.mViolated_directive = aViolatedDirective;
861
862 // source-file
863 if (!aSourceFile.IsEmpty()) {
864 // if aSourceFile is a URI, we have to make sure to strip fragments
865 nsCOMPtr<nsIURI> sourceURI;
866 NS_NewURI(getter_AddRefs(sourceURI), aSourceFile);
867 if (sourceURI) {
868 nsAutoCString spec;
869 sourceURI->GetSpecIgnoringRef(spec);
870 aSourceFile = NS_ConvertUTF8toUTF16(spec);
871 }
872 report.mCsp_report.mSource_file.Construct();
873 report.mCsp_report.mSource_file.Value() = aSourceFile;
874 }
875
876 // script-sample
877 if (!aScriptSample.IsEmpty()) {
878 report.mCsp_report.mScript_sample.Construct();
879 report.mCsp_report.mScript_sample.Value() = aScriptSample;
880 }
881
882 // line-number
883 if (aLineNum != 0) {
884 report.mCsp_report.mLine_number.Construct();
885 report.mCsp_report.mLine_number.Value() = aLineNum;
886 }
887
888 nsString csp_report;
889 if (!report.ToJSON(csp_report)) {
890 return NS_ERROR_FAILURE;
891 }
892
893 // ---------- Assembled, now send it to all the report URIs ----------- //
894
895 nsTArray<nsString> reportURIs;
896 mPolicies[aViolatedPolicyIndex]->getReportURIs(reportURIs);
897
898
899 nsCOMPtr<nsIDocument> doc = do_QueryReferent(mLoadingContext);
900 nsCOMPtr<nsIURI> reportURI;
901 nsCOMPtr<nsIChannel> reportChannel;
902
903 for (uint32_t r = 0; r < reportURIs.Length(); r++) {
904 nsAutoCString reportURICstring = NS_ConvertUTF16toUTF8(reportURIs[r]);
905 // try to create a new uri from every report-uri string
906 rv = NS_NewURI(getter_AddRefs(reportURI), reportURIs[r]);
907 if (NS_FAILED(rv)) {
908 const char16_t* params[] = { reportURIs[r].get() };
909 CSPCONTEXTLOG(("Could not create nsIURI for report URI %s",
910 reportURICstring.get()));
911 logToConsole(u"triedToSendReport", params, ArrayLength(params),
912 aSourceFile, aScriptSample, aLineNum, 0, nsIScriptError::errorFlag);
913 continue; // don't return yet, there may be more URIs
914 }
915
916 // try to create a new channel for every report-uri
917 nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL | nsIChannel::LOAD_CLASSIFY_URI;
918 if (doc) {
919 rv = NS_NewChannel(getter_AddRefs(reportChannel),
920 reportURI,
921 doc,
922 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
923 nsIContentPolicy::TYPE_CSP_REPORT,
924 nullptr, // aLoadGroup
925 nullptr, // aCallbacks
926 loadFlags);
927 }
928 else {
929 rv = NS_NewChannel(getter_AddRefs(reportChannel),
930 reportURI,
931 mLoadingPrincipal,
932 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
933 nsIContentPolicy::TYPE_CSP_REPORT,
934 nullptr, // aLoadGroup
935 nullptr, // aCallbacks
936 loadFlags);
937 }
938
939 if (NS_FAILED(rv)) {
940 CSPCONTEXTLOG(("Could not create new channel for report URI %s",
941 reportURICstring.get()));
942 continue; // don't return yet, there may be more URIs
943 }
944
945 // log a warning to console if scheme is not http or https
946 bool isHttpScheme =
947 (NS_SUCCEEDED(reportURI->SchemeIs("http", &isHttpScheme)) && isHttpScheme) ||
948 (NS_SUCCEEDED(reportURI->SchemeIs("https", &isHttpScheme)) && isHttpScheme);
949
950 if (!isHttpScheme) {
951 const char16_t* params[] = { reportURIs[r].get() };
952 logToConsole(u"reportURInotHttpsOrHttp2", params, ArrayLength(params),
953 aSourceFile, aScriptSample, aLineNum, 0, nsIScriptError::errorFlag);
954 continue;
955 }
956
957 // make sure this is an anonymous request (no cookies) so in case the
958 // policy URI is injected, it can't be abused for CSRF.
959 nsLoadFlags flags;
960 rv = reportChannel->GetLoadFlags(&flags);
961 NS_ENSURE_SUCCESS(rv, rv);
962 flags |= nsIRequest::LOAD_ANONYMOUS;
963 rv = reportChannel->SetLoadFlags(flags);
964 NS_ENSURE_SUCCESS(rv, rv);
965
966 // we need to set an nsIChannelEventSink on the channel object
967 // so we can tell it to not follow redirects when posting the reports
968 RefPtr<CSPReportRedirectSink> reportSink = new CSPReportRedirectSink();
969 if (doc && doc->GetDocShell()) {
970 nsCOMPtr<nsINetworkInterceptController> interceptController =
971 do_QueryInterface(doc->GetDocShell());
972 reportSink->SetInterceptController(interceptController);
973 }
974 reportChannel->SetNotificationCallbacks(reportSink);
975
976 // apply the loadgroup from the channel taken by setRequestContext. If
977 // there's no loadgroup, AsyncOpen will fail on process-split necko (since
978 // the channel cannot query the iTabChild).
979 rv = reportChannel->SetLoadGroup(mCallingChannelLoadGroup);
980 NS_ENSURE_SUCCESS(rv, rv);
981
982 // wire in the string input stream to send the report
983 nsCOMPtr<nsIStringInputStream> sis(do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID));
984 NS_ASSERTION(sis, "nsIStringInputStream is needed but not available to send CSP violation reports");
985 nsAutoCString utf8CSPReport = NS_ConvertUTF16toUTF8(csp_report);
986 rv = sis->SetData(utf8CSPReport.get(), utf8CSPReport.Length());
987 NS_ENSURE_SUCCESS(rv, rv);
988
989 nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(reportChannel));
990 if (!uploadChannel) {
991 // It's possible the URI provided can't be uploaded to, in which case
992 // we skip this one. We'll already have warned about a non-HTTP URI earlier.
993 continue;
994 }
995
996 rv = uploadChannel->SetUploadStream(sis, NS_LITERAL_CSTRING("application/csp-report"), -1);
997 NS_ENSURE_SUCCESS(rv, rv);
998
999 // if this is an HTTP channel, set the request method to post
1000 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(reportChannel));
1001 if (httpChannel) {
1002 httpChannel->SetRequestMethod(NS_LITERAL_CSTRING("POST"));
1003 }
1004
1005 RefPtr<CSPViolationReportListener> listener = new CSPViolationReportListener();
1006 rv = reportChannel->AsyncOpen2(listener);
1007
1008 // AsyncOpen should not fail, but could if there's no load group (like if
1009 // SetRequestContext is not given a channel). This should fail quietly and
1010 // not return an error since it's really ok if reports don't go out, but
1011 // it's good to log the error locally.
1012
1013 if (NS_FAILED(rv)) {
1014 const char16_t* params[] = { reportURIs[r].get() };
1015 CSPCONTEXTLOG(("AsyncOpen failed for report URI %s", params[0]));
1016 logToConsole(u"triedToSendReport", params, ArrayLength(params),
1017 aSourceFile, aScriptSample, aLineNum, 0, nsIScriptError::errorFlag);
1018 } else {
1019 CSPCONTEXTLOG(("Sent violation report to URI %s", reportURICstring.get()));
1020 }
1021 }
1022 return NS_OK;
1023 }
1024
1025 /**
1026 * Dispatched from the main thread to send reports for one CSP violation.
1027 */
1028 class CSPReportSenderRunnable final : public Runnable
1029 {
1030 public:
CSPReportSenderRunnable(nsISupports * aBlockedContentSource,nsIURI * aOriginalURI,uint32_t aViolatedPolicyIndex,bool aReportOnlyFlag,const nsAString & aViolatedDirective,const nsAString & aObserverSubject,const nsAString & aSourceFile,const nsAString & aScriptSample,uint32_t aLineNum,nsCSPContext * aCSPContext)1031 CSPReportSenderRunnable(nsISupports* aBlockedContentSource,
1032 nsIURI* aOriginalURI,
1033 uint32_t aViolatedPolicyIndex,
1034 bool aReportOnlyFlag,
1035 const nsAString& aViolatedDirective,
1036 const nsAString& aObserverSubject,
1037 const nsAString& aSourceFile,
1038 const nsAString& aScriptSample,
1039 uint32_t aLineNum,
1040 nsCSPContext* aCSPContext)
1041 : mBlockedContentSource(aBlockedContentSource)
1042 , mOriginalURI(aOriginalURI)
1043 , mViolatedPolicyIndex(aViolatedPolicyIndex)
1044 , mReportOnlyFlag(aReportOnlyFlag)
1045 , mViolatedDirective(aViolatedDirective)
1046 , mSourceFile(aSourceFile)
1047 , mScriptSample(aScriptSample)
1048 , mLineNum(aLineNum)
1049 , mCSPContext(aCSPContext)
1050 {
1051 NS_ASSERTION(!aViolatedDirective.IsEmpty(), "Can not send reports without a violated directive");
1052 // the observer subject is an nsISupports: either an nsISupportsCString
1053 // from the arg passed in directly, or if that's empty, it's the blocked
1054 // source.
1055 if (aObserverSubject.IsEmpty()) {
1056 mObserverSubject = aBlockedContentSource;
1057 } else {
1058 nsCOMPtr<nsISupportsCString> supportscstr =
1059 do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID);
1060 NS_ASSERTION(supportscstr, "Couldn't allocate nsISupportsCString");
1061 supportscstr->SetData(NS_ConvertUTF16toUTF8(aObserverSubject));
1062 mObserverSubject = do_QueryInterface(supportscstr);
1063 }
1064 }
1065
Run()1066 NS_IMETHOD Run() override
1067 {
1068 MOZ_ASSERT(NS_IsMainThread());
1069
1070 // 1) notify observers
1071 nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
1072 NS_ASSERTION(observerService, "needs observer service");
1073 nsresult rv = observerService->NotifyObservers(mObserverSubject,
1074 CSP_VIOLATION_TOPIC,
1075 mViolatedDirective.get());
1076 NS_ENSURE_SUCCESS(rv, rv);
1077
1078 // 2) send reports for the policy that was violated
1079 mCSPContext->SendReports(mBlockedContentSource, mOriginalURI,
1080 mViolatedDirective, mViolatedPolicyIndex,
1081 mSourceFile, mScriptSample, mLineNum);
1082
1083 // 3) log to console (one per policy violation)
1084 // mBlockedContentSource could be a URI or a string.
1085 nsCOMPtr<nsIURI> blockedURI = do_QueryInterface(mBlockedContentSource);
1086 // if mBlockedContentSource is not a URI, it could be a string
1087 nsCOMPtr<nsISupportsCString> blockedString = do_QueryInterface(mBlockedContentSource);
1088
1089 nsCString blockedDataStr;
1090
1091 if (blockedURI) {
1092 blockedURI->GetSpec(blockedDataStr);
1093 bool isData = false;
1094 rv = blockedURI->SchemeIs("data", &isData);
1095 if (NS_SUCCEEDED(rv) && isData) {
1096 blockedDataStr.Truncate(40);
1097 blockedDataStr.AppendASCII("...");
1098 }
1099 } else if (blockedString) {
1100 blockedString->GetData(blockedDataStr);
1101 }
1102
1103 if (blockedDataStr.Length() > 0) {
1104 nsString blockedDataChar16 = NS_ConvertUTF8toUTF16(blockedDataStr);
1105 const char16_t* params[] = { mViolatedDirective.get(),
1106 blockedDataChar16.get() };
1107 mCSPContext->logToConsole(mReportOnlyFlag ? u"CSPROViolationWithURI" :
1108 u"CSPViolationWithURI",
1109 params, ArrayLength(params), mSourceFile, mScriptSample,
1110 mLineNum, 0, nsIScriptError::errorFlag);
1111 }
1112 return NS_OK;
1113 }
1114
1115 private:
1116 nsCOMPtr<nsISupports> mBlockedContentSource;
1117 nsCOMPtr<nsIURI> mOriginalURI;
1118 uint32_t mViolatedPolicyIndex;
1119 bool mReportOnlyFlag;
1120 nsString mViolatedDirective;
1121 nsCOMPtr<nsISupports> mObserverSubject;
1122 nsString mSourceFile;
1123 nsString mScriptSample;
1124 uint32_t mLineNum;
1125 RefPtr<nsCSPContext> mCSPContext;
1126 };
1127
1128 /**
1129 * Asynchronously notifies any nsIObservers listening to the CSP violation
1130 * topic that a violation occurred. Also triggers report sending and console
1131 * logging. All asynchronous on the main thread.
1132 *
1133 * @param aBlockedContentSource
1134 * Either a CSP Source (like 'self', as string) or nsIURI: the source
1135 * of the violation.
1136 * @param aOriginalUri
1137 * The original URI if the blocked content is a redirect, else null
1138 * @param aViolatedDirective
1139 * the directive that was violated (string).
1140 * @param aViolatedPolicyIndex
1141 * the index of the policy that was violated (so we know where to send
1142 * the reports).
1143 * @param aObserverSubject
1144 * optional, subject sent to the nsIObservers listening to the CSP
1145 * violation topic.
1146 * @param aSourceFile
1147 * name of the file containing the inline script violation
1148 * @param aScriptSample
1149 * a sample of the violating inline script
1150 * @param aLineNum
1151 * source line number of the violation (if available)
1152 */
1153 nsresult
AsyncReportViolation(nsISupports * aBlockedContentSource,nsIURI * aOriginalURI,const nsAString & aViolatedDirective,uint32_t aViolatedPolicyIndex,const nsAString & aObserverSubject,const nsAString & aSourceFile,const nsAString & aScriptSample,uint32_t aLineNum)1154 nsCSPContext::AsyncReportViolation(nsISupports* aBlockedContentSource,
1155 nsIURI* aOriginalURI,
1156 const nsAString& aViolatedDirective,
1157 uint32_t aViolatedPolicyIndex,
1158 const nsAString& aObserverSubject,
1159 const nsAString& aSourceFile,
1160 const nsAString& aScriptSample,
1161 uint32_t aLineNum)
1162 {
1163 NS_ENSURE_ARG_MAX(aViolatedPolicyIndex, mPolicies.Length() - 1);
1164
1165 NS_DispatchToMainThread(new CSPReportSenderRunnable(aBlockedContentSource,
1166 aOriginalURI,
1167 aViolatedPolicyIndex,
1168 mPolicies[aViolatedPolicyIndex]->getReportOnlyFlag(),
1169 aViolatedDirective,
1170 aObserverSubject,
1171 aSourceFile,
1172 aScriptSample,
1173 aLineNum,
1174 this));
1175 return NS_OK;
1176 }
1177
1178 NS_IMETHODIMP
RequireSRIForType(nsContentPolicyType aContentType,bool * outRequiresSRIForType)1179 nsCSPContext::RequireSRIForType(nsContentPolicyType aContentType, bool* outRequiresSRIForType)
1180 {
1181 *outRequiresSRIForType = false;
1182 for (uint32_t i = 0; i < mPolicies.Length(); i++) {
1183 if (mPolicies[i]->hasDirective(REQUIRE_SRI_FOR)) {
1184 if (mPolicies[i]->requireSRIForType(aContentType)) {
1185 *outRequiresSRIForType = true;
1186 return NS_OK;
1187 }
1188 }
1189 }
1190 return NS_OK;
1191 }
1192
1193 /**
1194 * Based on the given docshell, determines if this CSP context allows the
1195 * ancestry.
1196 *
1197 * In order to determine the URI of the parent document (one causing the load
1198 * of this protected document), this function obtains the docShellTreeItem,
1199 * then walks up the hierarchy until it finds a privileged (chrome) tree item.
1200 * Getting the a tree item's URI looks like this in pseudocode:
1201 *
1202 * nsIDocShellTreeItem->GetDocument()->GetDocumentURI();
1203 *
1204 * aDocShell is the docShell for the protected document.
1205 */
1206 NS_IMETHODIMP
PermitsAncestry(nsIDocShell * aDocShell,bool * outPermitsAncestry)1207 nsCSPContext::PermitsAncestry(nsIDocShell* aDocShell, bool* outPermitsAncestry)
1208 {
1209 nsresult rv;
1210
1211 // Can't check ancestry without a docShell.
1212 if (aDocShell == nullptr) {
1213 return NS_ERROR_FAILURE;
1214 }
1215
1216 *outPermitsAncestry = true;
1217
1218 // extract the ancestry as an array
1219 nsCOMArray<nsIURI> ancestorsArray;
1220
1221 nsCOMPtr<nsIInterfaceRequestor> ir(do_QueryInterface(aDocShell));
1222 nsCOMPtr<nsIDocShellTreeItem> treeItem(do_GetInterface(ir));
1223 nsCOMPtr<nsIDocShellTreeItem> parentTreeItem;
1224 nsCOMPtr<nsIURI> currentURI;
1225 nsCOMPtr<nsIURI> uriClone;
1226
1227 // iterate through each docShell parent item
1228 while (NS_SUCCEEDED(treeItem->GetParent(getter_AddRefs(parentTreeItem))) &&
1229 parentTreeItem != nullptr) {
1230
1231 nsIDocument* doc = parentTreeItem->GetDocument();
1232 NS_ASSERTION(doc, "Could not get nsIDocument from nsIDocShellTreeItem in nsCSPContext::PermitsAncestry");
1233 NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
1234
1235 currentURI = doc->GetDocumentURI();
1236
1237 if (currentURI) {
1238 // stop when reaching chrome
1239 bool isChrome = false;
1240 rv = currentURI->SchemeIs("chrome", &isChrome);
1241 NS_ENSURE_SUCCESS(rv, rv);
1242 if (isChrome) { break; }
1243
1244 // delete the userpass from the URI.
1245 rv = currentURI->CloneIgnoringRef(getter_AddRefs(uriClone));
1246 NS_ENSURE_SUCCESS(rv, rv);
1247
1248 // We don't care if this succeeds, just want to delete a userpass if
1249 // there was one.
1250 uriClone->SetUserPass(EmptyCString());
1251
1252 if (CSPCONTEXTLOGENABLED()) {
1253 CSPCONTEXTLOG(("nsCSPContext::PermitsAncestry, found ancestor: %s",
1254 uriClone->GetSpecOrDefault().get()));
1255 }
1256 ancestorsArray.AppendElement(uriClone);
1257 }
1258
1259 // next ancestor
1260 treeItem = parentTreeItem;
1261 }
1262
1263 nsAutoString violatedDirective;
1264
1265 // Now that we've got the ancestry chain in ancestorsArray, time to check
1266 // them against any CSP.
1267 // NOTE: the ancestors are not allowed to be sent cross origin; this is a
1268 // restriction not placed on subresource loads.
1269
1270 for (uint32_t a = 0; a < ancestorsArray.Length(); a++) {
1271 if (CSPCONTEXTLOGENABLED()) {
1272 CSPCONTEXTLOG(("nsCSPContext::PermitsAncestry, checking ancestor: %s",
1273 ancestorsArray[a]->GetSpecOrDefault().get()));
1274 }
1275 // omit the ancestor URI in violation reports if cross-origin as per spec
1276 // (it is a violation of the same-origin policy).
1277 bool okToSendAncestor = NS_SecurityCompareURIs(ancestorsArray[a], mSelfURI, true);
1278
1279
1280 bool permits = permitsInternal(nsIContentSecurityPolicy::FRAME_ANCESTORS_DIRECTIVE,
1281 ancestorsArray[a],
1282 nullptr, // no redirect here.
1283 EmptyString(), // no nonce
1284 false, // no redirect here.
1285 false, // not a preload.
1286 true, // specific, do not use default-src
1287 true, // send violation reports
1288 okToSendAncestor,
1289 false); // not parser created
1290 if (!permits) {
1291 *outPermitsAncestry = false;
1292 }
1293 }
1294 return NS_OK;
1295 }
1296
1297 NS_IMETHODIMP
Permits(nsIURI * aURI,CSPDirective aDir,bool aSpecific,bool * outPermits)1298 nsCSPContext::Permits(nsIURI* aURI,
1299 CSPDirective aDir,
1300 bool aSpecific,
1301 bool* outPermits)
1302 {
1303 // Can't perform check without aURI
1304 if (aURI == nullptr) {
1305 return NS_ERROR_FAILURE;
1306 }
1307
1308 *outPermits = permitsInternal(aDir,
1309 aURI,
1310 nullptr, // no original (pre-redirect) URI
1311 EmptyString(), // no nonce
1312 false, // not redirected.
1313 false, // not a preload.
1314 aSpecific,
1315 true, // send violation reports
1316 true, // send blocked URI in violation reports
1317 false); // not parser created
1318
1319 if (CSPCONTEXTLOGENABLED()) {
1320 CSPCONTEXTLOG(("nsCSPContext::Permits, aUri: %s, aDir: %d, isAllowed: %s",
1321 aURI->GetSpecOrDefault().get(), aDir,
1322 *outPermits ? "allow" : "deny"));
1323 }
1324
1325 return NS_OK;
1326 }
1327
1328 NS_IMETHODIMP
ToJSON(nsAString & outCSPinJSON)1329 nsCSPContext::ToJSON(nsAString& outCSPinJSON)
1330 {
1331 outCSPinJSON.Truncate();
1332 dom::CSPPolicies jsonPolicies;
1333 jsonPolicies.mCsp_policies.Construct();
1334
1335 for (uint32_t p = 0; p < mPolicies.Length(); p++) {
1336 dom::CSP jsonCSP;
1337 mPolicies[p]->toDomCSPStruct(jsonCSP);
1338 jsonPolicies.mCsp_policies.Value().AppendElement(jsonCSP, fallible);
1339 }
1340
1341 // convert the gathered information to JSON
1342 if (!jsonPolicies.ToJSON(outCSPinJSON)) {
1343 return NS_ERROR_FAILURE;
1344 }
1345 return NS_OK;
1346 }
1347
1348 NS_IMETHODIMP
GetCSPSandboxFlags(uint32_t * aOutSandboxFlags)1349 nsCSPContext::GetCSPSandboxFlags(uint32_t* aOutSandboxFlags)
1350 {
1351 if (!aOutSandboxFlags) {
1352 return NS_ERROR_FAILURE;
1353 }
1354 *aOutSandboxFlags = SANDBOXED_NONE;
1355
1356 for (uint32_t i = 0; i < mPolicies.Length(); i++) {
1357 uint32_t flags = mPolicies[i]->getSandboxFlags();
1358
1359 // current policy doesn't have sandbox flag, check next policy
1360 if (!flags) {
1361 continue;
1362 }
1363
1364 // current policy has sandbox flags, if the policy is in enforcement-mode
1365 // (i.e. not report-only) set these flags and check for policies with more
1366 // restrictions
1367 if (!mPolicies[i]->getReportOnlyFlag()) {
1368 *aOutSandboxFlags |= flags;
1369 } else {
1370 // sandbox directive is ignored in report-only mode, warn about it and
1371 // continue the loop checking for an enforcement policy.
1372 nsAutoString policy;
1373 mPolicies[i]->toString(policy);
1374
1375 CSPCONTEXTLOG(("nsCSPContext::GetCSPSandboxFlags, report only policy, ignoring sandbox in: %s",
1376 policy.get()));
1377
1378 const char16_t* params[] = { policy.get() };
1379 logToConsole(u"ignoringReportOnlyDirective", params, ArrayLength(params),
1380 EmptyString(), EmptyString(), 0, 0, nsIScriptError::warningFlag);
1381 }
1382 }
1383
1384 return NS_OK;
1385 }
1386
1387 /* ========== CSPViolationReportListener implementation ========== */
1388
1389 NS_IMPL_ISUPPORTS(CSPViolationReportListener, nsIStreamListener, nsIRequestObserver, nsISupports);
1390
CSPViolationReportListener()1391 CSPViolationReportListener::CSPViolationReportListener()
1392 {
1393 }
1394
~CSPViolationReportListener()1395 CSPViolationReportListener::~CSPViolationReportListener()
1396 {
1397 }
1398
1399 nsresult
AppendSegmentToString(nsIInputStream * aInputStream,void * aClosure,const char * aRawSegment,uint32_t aToOffset,uint32_t aCount,uint32_t * outWrittenCount)1400 AppendSegmentToString(nsIInputStream* aInputStream,
1401 void* aClosure,
1402 const char* aRawSegment,
1403 uint32_t aToOffset,
1404 uint32_t aCount,
1405 uint32_t* outWrittenCount)
1406 {
1407 nsCString* decodedData = static_cast<nsCString*>(aClosure);
1408 decodedData->Append(aRawSegment, aCount);
1409 *outWrittenCount = aCount;
1410 return NS_OK;
1411 }
1412
1413 NS_IMETHODIMP
OnDataAvailable(nsIRequest * aRequest,nsISupports * aContext,nsIInputStream * aInputStream,uint64_t aOffset,uint32_t aCount)1414 CSPViolationReportListener::OnDataAvailable(nsIRequest* aRequest,
1415 nsISupports* aContext,
1416 nsIInputStream* aInputStream,
1417 uint64_t aOffset,
1418 uint32_t aCount)
1419 {
1420 uint32_t read;
1421 nsCString decodedData;
1422 return aInputStream->ReadSegments(AppendSegmentToString,
1423 &decodedData,
1424 aCount,
1425 &read);
1426 }
1427
1428 NS_IMETHODIMP
OnStopRequest(nsIRequest * aRequest,nsISupports * aContext,nsresult aStatus)1429 CSPViolationReportListener::OnStopRequest(nsIRequest* aRequest,
1430 nsISupports* aContext,
1431 nsresult aStatus)
1432 {
1433 return NS_OK;
1434 }
1435
1436 NS_IMETHODIMP
OnStartRequest(nsIRequest * aRequest,nsISupports * aContext)1437 CSPViolationReportListener::OnStartRequest(nsIRequest* aRequest,
1438 nsISupports* aContext)
1439 {
1440 return NS_OK;
1441 }
1442
1443 /* ========== CSPReportRedirectSink implementation ========== */
1444
1445 NS_IMPL_ISUPPORTS(CSPReportRedirectSink, nsIChannelEventSink, nsIInterfaceRequestor);
1446
CSPReportRedirectSink()1447 CSPReportRedirectSink::CSPReportRedirectSink()
1448 {
1449 }
1450
~CSPReportRedirectSink()1451 CSPReportRedirectSink::~CSPReportRedirectSink()
1452 {
1453 }
1454
1455 NS_IMETHODIMP
AsyncOnChannelRedirect(nsIChannel * aOldChannel,nsIChannel * aNewChannel,uint32_t aRedirFlags,nsIAsyncVerifyRedirectCallback * aCallback)1456 CSPReportRedirectSink::AsyncOnChannelRedirect(nsIChannel* aOldChannel,
1457 nsIChannel* aNewChannel,
1458 uint32_t aRedirFlags,
1459 nsIAsyncVerifyRedirectCallback* aCallback)
1460 {
1461 // cancel the old channel so XHR failure callback happens
1462 nsresult rv = aOldChannel->Cancel(NS_ERROR_ABORT);
1463 NS_ENSURE_SUCCESS(rv, rv);
1464
1465 // notify an observer that we have blocked the report POST due to a redirect,
1466 // used in testing, do this async since we're in an async call now to begin with
1467 nsCOMPtr<nsIURI> uri;
1468 rv = aOldChannel->GetURI(getter_AddRefs(uri));
1469 NS_ENSURE_SUCCESS(rv, rv);
1470
1471 nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
1472 NS_ASSERTION(observerService, "Observer service required to log CSP violations");
1473 observerService->NotifyObservers(uri,
1474 CSP_VIOLATION_TOPIC,
1475 u"denied redirect while sending violation report");
1476
1477 return NS_BINDING_REDIRECTED;
1478 }
1479
1480 NS_IMETHODIMP
GetInterface(const nsIID & aIID,void ** aResult)1481 CSPReportRedirectSink::GetInterface(const nsIID& aIID, void** aResult)
1482 {
1483 if (aIID.Equals(NS_GET_IID(nsINetworkInterceptController)) &&
1484 mInterceptController) {
1485 nsCOMPtr<nsINetworkInterceptController> copy(mInterceptController);
1486 *aResult = copy.forget().take();
1487
1488 return NS_OK;
1489 }
1490
1491 return QueryInterface(aIID, aResult);
1492 }
1493
1494 void
SetInterceptController(nsINetworkInterceptController * aInterceptController)1495 CSPReportRedirectSink::SetInterceptController(nsINetworkInterceptController* aInterceptController)
1496 {
1497 mInterceptController = aInterceptController;
1498 }
1499
1500 /* ===== nsISerializable implementation ====== */
1501
1502 NS_IMETHODIMP
Read(nsIObjectInputStream * aStream)1503 nsCSPContext::Read(nsIObjectInputStream* aStream)
1504 {
1505 nsresult rv;
1506 nsCOMPtr<nsISupports> supports;
1507
1508 rv = NS_ReadOptionalObject(aStream, true, getter_AddRefs(supports));
1509 NS_ENSURE_SUCCESS(rv, rv);
1510
1511 mSelfURI = do_QueryInterface(supports);
1512 NS_ASSERTION(mSelfURI, "need a self URI to de-serialize");
1513
1514 uint32_t numPolicies;
1515 rv = aStream->Read32(&numPolicies);
1516 NS_ENSURE_SUCCESS(rv, rv);
1517
1518 nsAutoString policyString;
1519
1520 while (numPolicies > 0) {
1521 numPolicies--;
1522
1523 rv = aStream->ReadString(policyString);
1524 NS_ENSURE_SUCCESS(rv, rv);
1525
1526 bool reportOnly = false;
1527 rv = aStream->ReadBoolean(&reportOnly);
1528 NS_ENSURE_SUCCESS(rv, rv);
1529
1530 // @param deliveredViaMetaTag:
1531 // when parsing the CSP policy string initially we already remove directives
1532 // that should not be processed when delivered via the meta tag. Such directives
1533 // will not be present at this point anymore.
1534 nsCSPPolicy* policy = nsCSPParser::parseContentSecurityPolicy(policyString,
1535 mSelfURI,
1536 reportOnly,
1537 this,
1538 false);
1539 if (policy) {
1540 mPolicies.AppendElement(policy);
1541 }
1542 }
1543
1544 return NS_OK;
1545 }
1546
1547 NS_IMETHODIMP
Write(nsIObjectOutputStream * aStream)1548 nsCSPContext::Write(nsIObjectOutputStream* aStream)
1549 {
1550 nsresult rv = NS_WriteOptionalCompoundObject(aStream,
1551 mSelfURI,
1552 NS_GET_IID(nsIURI),
1553 true);
1554 NS_ENSURE_SUCCESS(rv, rv);
1555
1556 // Serialize all the policies.
1557 aStream->Write32(mPolicies.Length());
1558
1559 nsAutoString polStr;
1560 for (uint32_t p = 0; p < mPolicies.Length(); p++) {
1561 polStr.Truncate();
1562 mPolicies[p]->toString(polStr);
1563 aStream->WriteWStringZ(polStr.get());
1564 aStream->WriteBoolean(mPolicies[p]->getReportOnlyFlag());
1565 }
1566 return NS_OK;
1567 }
1568