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