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 "nsAttrValue.h"
8 #include "nsCharSeparatedTokenizer.h"
9 #include "nsContentUtils.h"
10 #include "nsCSPUtils.h"
11 #include "nsDebug.h"
12 #include "nsCSPParser.h"
13 #include "nsComponentManagerUtils.h"
14 #include "nsIConsoleService.h"
15 #include "nsIChannel.h"
16 #include "nsICryptoHash.h"
17 #include "nsIScriptError.h"
18 #include "nsIStringBundle.h"
19 #include "nsIURL.h"
20 #include "nsNetUtil.h"
21 #include "nsReadableUtils.h"
22 #include "nsSandboxFlags.h"
23 #include "nsServiceManagerUtils.h"
24
25 #include "mozilla/Components.h"
26 #include "mozilla/dom/CSPDictionariesBinding.h"
27 #include "mozilla/dom/Document.h"
28 #include "mozilla/StaticPrefs_security.h"
29
30 #define DEFAULT_PORT -1
31
GetCspUtilsLog()32 static mozilla::LogModule* GetCspUtilsLog() {
33 static mozilla::LazyLogModule gCspUtilsPRLog("CSPUtils");
34 return gCspUtilsPRLog;
35 }
36
37 #define CSPUTILSLOG(args) \
38 MOZ_LOG(GetCspUtilsLog(), mozilla::LogLevel::Debug, args)
39 #define CSPUTILSLOGENABLED() \
40 MOZ_LOG_TEST(GetCspUtilsLog(), mozilla::LogLevel::Debug)
41
CSP_PercentDecodeStr(const nsAString & aEncStr,nsAString & outDecStr)42 void CSP_PercentDecodeStr(const nsAString& aEncStr, nsAString& outDecStr) {
43 outDecStr.Truncate();
44
45 // helper function that should not be visible outside this methods scope
46 struct local {
47 static inline char16_t convertHexDig(char16_t aHexDig) {
48 if (isNumberToken(aHexDig)) {
49 return aHexDig - '0';
50 }
51 if (aHexDig >= 'A' && aHexDig <= 'F') {
52 return aHexDig - 'A' + 10;
53 }
54 // must be a lower case character
55 // (aHexDig >= 'a' && aHexDig <= 'f')
56 return aHexDig - 'a' + 10;
57 }
58 };
59
60 const char16_t *cur, *end, *hexDig1, *hexDig2;
61 cur = aEncStr.BeginReading();
62 end = aEncStr.EndReading();
63
64 while (cur != end) {
65 // if it's not a percent sign then there is
66 // nothing to do for that character
67 if (*cur != PERCENT_SIGN) {
68 outDecStr.Append(*cur);
69 cur++;
70 continue;
71 }
72
73 // get the two hexDigs following the '%'-sign
74 hexDig1 = cur + 1;
75 hexDig2 = cur + 2;
76
77 // if there are no hexdigs after the '%' then
78 // there is nothing to do for us.
79 if (hexDig1 == end || hexDig2 == end || !isValidHexDig(*hexDig1) ||
80 !isValidHexDig(*hexDig2)) {
81 outDecStr.Append(PERCENT_SIGN);
82 cur++;
83 continue;
84 }
85
86 // decode "% hexDig1 hexDig2" into a character.
87 char16_t decChar =
88 (local::convertHexDig(*hexDig1) << 4) + local::convertHexDig(*hexDig2);
89 outDecStr.Append(decChar);
90
91 // increment 'cur' to after the second hexDig
92 cur = ++hexDig2;
93 }
94 }
95
96 // The Content Security Policy should be inherited for
97 // local schemes like: "about", "blob", "data", or "filesystem".
98 // see: https://w3c.github.io/webappsec-csp/#initialize-document-csp
CSP_ShouldResponseInheritCSP(nsIChannel * aChannel)99 bool CSP_ShouldResponseInheritCSP(nsIChannel* aChannel) {
100 if (!aChannel) {
101 return false;
102 }
103
104 nsCOMPtr<nsIURI> uri;
105 nsresult rv = aChannel->GetURI(getter_AddRefs(uri));
106 NS_ENSURE_SUCCESS(rv, false);
107
108 bool isAbout = uri->SchemeIs("about");
109 if (isAbout) {
110 nsAutoCString aboutSpec;
111 rv = uri->GetSpec(aboutSpec);
112 NS_ENSURE_SUCCESS(rv, false);
113 // also allow about:blank#foo
114 if (StringBeginsWith(aboutSpec, "about:blank"_ns) ||
115 StringBeginsWith(aboutSpec, "about:srcdoc"_ns)) {
116 return true;
117 }
118 }
119
120 return uri->SchemeIs("blob") || uri->SchemeIs("data") ||
121 uri->SchemeIs("filesystem") || uri->SchemeIs("javascript");
122 }
123
CSP_ApplyMetaCSPToDoc(mozilla::dom::Document & aDoc,const nsAString & aPolicyStr)124 void CSP_ApplyMetaCSPToDoc(mozilla::dom::Document& aDoc,
125 const nsAString& aPolicyStr) {
126 if (!mozilla::StaticPrefs::security_csp_enable() || aDoc.IsLoadedAsData()) {
127 return;
128 }
129
130 nsAutoString policyStr(
131 nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>(
132 aPolicyStr));
133
134 if (policyStr.IsEmpty()) {
135 return;
136 }
137
138 nsCOMPtr<nsIContentSecurityPolicy> csp = aDoc.GetCsp();
139 if (!csp) {
140 MOZ_ASSERT(false, "how come there is no CSP");
141 return;
142 }
143
144 // Multiple CSPs (delivered through either header of meta tag) need to
145 // be joined together, see:
146 // https://w3c.github.io/webappsec/specs/content-security-policy/#delivery-html-meta-element
147 nsresult rv =
148 csp->AppendPolicy(policyStr,
149 false, // csp via meta tag can not be report only
150 true); // delivered through the meta tag
151 NS_ENSURE_SUCCESS_VOID(rv);
152 if (nsPIDOMWindowInner* inner = aDoc.GetInnerWindow()) {
153 inner->SetCsp(csp);
154 }
155 aDoc.ApplySettingsFromCSP(false);
156 }
157
CSP_GetLocalizedStr(const char * aName,const nsTArray<nsString> & aParams,nsAString & outResult)158 void CSP_GetLocalizedStr(const char* aName, const nsTArray<nsString>& aParams,
159 nsAString& outResult) {
160 nsCOMPtr<nsIStringBundle> keyStringBundle;
161 nsCOMPtr<nsIStringBundleService> stringBundleService =
162 mozilla::components::StringBundle::Service();
163
164 NS_ASSERTION(stringBundleService, "String bundle service must be present!");
165 stringBundleService->CreateBundle(
166 "chrome://global/locale/security/csp.properties",
167 getter_AddRefs(keyStringBundle));
168
169 NS_ASSERTION(keyStringBundle, "Key string bundle must be available!");
170
171 if (!keyStringBundle) {
172 return;
173 }
174 keyStringBundle->FormatStringFromName(aName, aParams, outResult);
175 }
176
CSP_LogStrMessage(const nsAString & aMsg)177 void CSP_LogStrMessage(const nsAString& aMsg) {
178 nsCOMPtr<nsIConsoleService> console(
179 do_GetService("@mozilla.org/consoleservice;1"));
180
181 if (!console) {
182 return;
183 }
184 nsString msg(aMsg);
185 console->LogStringMessage(msg.get());
186 }
187
CSP_LogMessage(const nsAString & aMessage,const nsAString & aSourceName,const nsAString & aSourceLine,uint32_t aLineNumber,uint32_t aColumnNumber,uint32_t aFlags,const nsACString & aCategory,uint64_t aInnerWindowID,bool aFromPrivateWindow)188 void CSP_LogMessage(const nsAString& aMessage, const nsAString& aSourceName,
189 const nsAString& aSourceLine, uint32_t aLineNumber,
190 uint32_t aColumnNumber, uint32_t aFlags,
191 const nsACString& aCategory, uint64_t aInnerWindowID,
192 bool aFromPrivateWindow) {
193 nsCOMPtr<nsIConsoleService> console(
194 do_GetService(NS_CONSOLESERVICE_CONTRACTID));
195
196 nsCOMPtr<nsIScriptError> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID));
197
198 if (!console || !error) {
199 return;
200 }
201
202 // Prepending CSP to the outgoing console message
203 nsString cspMsg;
204 cspMsg.AppendLiteral(u"Content Security Policy: ");
205 cspMsg.Append(aMessage);
206
207 // Currently 'aSourceLine' is not logged to the console, because similar
208 // information is already included within the source link of the message.
209 // For inline violations however, the line and column number are 0 and
210 // information contained within 'aSourceLine' can be really useful for devs.
211 // E.g. 'aSourceLine' might be: 'onclick attribute on DIV element'.
212 // In such cases we append 'aSourceLine' directly to the error message.
213 if (!aSourceLine.IsEmpty()) {
214 cspMsg.AppendLiteral(u" Source: ");
215 cspMsg.Append(aSourceLine);
216 cspMsg.AppendLiteral(u".");
217 }
218
219 // Since we are leveraging csp errors as the category names which
220 // we pass to devtools, we should prepend them with "CSP_" to
221 // allow easy distincution in devtools code. e.g.
222 // upgradeInsecureRequest -> CSP_upgradeInsecureRequest
223 nsCString category("CSP_");
224 category.Append(aCategory);
225
226 nsresult rv;
227 if (aInnerWindowID > 0) {
228 rv = error->InitWithWindowID(cspMsg, aSourceName, aSourceLine, aLineNumber,
229 aColumnNumber, aFlags, category,
230 aInnerWindowID);
231 } else {
232 rv = error->Init(cspMsg, aSourceName, aSourceLine, aLineNumber,
233 aColumnNumber, aFlags, category.get(), aFromPrivateWindow,
234 true /* from chrome context */);
235 }
236 if (NS_FAILED(rv)) {
237 return;
238 }
239 console->LogMessage(error);
240 }
241
242 /**
243 * Combines CSP_LogMessage and CSP_GetLocalizedStr into one call.
244 */
CSP_LogLocalizedStr(const char * aName,const nsTArray<nsString> & aParams,const nsAString & aSourceName,const nsAString & aSourceLine,uint32_t aLineNumber,uint32_t aColumnNumber,uint32_t aFlags,const nsACString & aCategory,uint64_t aInnerWindowID,bool aFromPrivateWindow)245 void CSP_LogLocalizedStr(const char* aName, const nsTArray<nsString>& aParams,
246 const nsAString& aSourceName,
247 const nsAString& aSourceLine, uint32_t aLineNumber,
248 uint32_t aColumnNumber, uint32_t aFlags,
249 const nsACString& aCategory, uint64_t aInnerWindowID,
250 bool aFromPrivateWindow) {
251 nsAutoString logMsg;
252 CSP_GetLocalizedStr(aName, aParams, logMsg);
253 CSP_LogMessage(logMsg, aSourceName, aSourceLine, aLineNumber, aColumnNumber,
254 aFlags, aCategory, aInnerWindowID, aFromPrivateWindow);
255 }
256
257 /* ===== Helpers ============================ */
CSP_ContentTypeToDirective(nsContentPolicyType aType)258 CSPDirective CSP_ContentTypeToDirective(nsContentPolicyType aType) {
259 // We need to know if this is a worker so child-src can handle that case
260 // correctly.
261 switch (aType) {
262 case nsIContentPolicy::TYPE_IMAGE:
263 case nsIContentPolicy::TYPE_IMAGESET:
264 case nsIContentPolicy::TYPE_INTERNAL_IMAGE:
265 case nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD:
266 case nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON:
267 return nsIContentSecurityPolicy::IMG_SRC_DIRECTIVE;
268
269 // BLock XSLT as script, see bug 910139
270 case nsIContentPolicy::TYPE_XSLT:
271 case nsIContentPolicy::TYPE_SCRIPT:
272 case nsIContentPolicy::TYPE_INTERNAL_SCRIPT:
273 case nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD:
274 case nsIContentPolicy::TYPE_INTERNAL_MODULE:
275 case nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD:
276 case nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS:
277 case nsIContentPolicy::TYPE_INTERNAL_AUDIOWORKLET:
278 case nsIContentPolicy::TYPE_INTERNAL_PAINTWORKLET:
279 case nsIContentPolicy::TYPE_INTERNAL_CHROMEUTILS_COMPILED_SCRIPT:
280 case nsIContentPolicy::TYPE_INTERNAL_FRAME_MESSAGEMANAGER_SCRIPT:
281 return nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE;
282
283 case nsIContentPolicy::TYPE_STYLESHEET:
284 case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET:
285 case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD:
286 return nsIContentSecurityPolicy::STYLE_SRC_DIRECTIVE;
287
288 case nsIContentPolicy::TYPE_FONT:
289 case nsIContentPolicy::TYPE_INTERNAL_FONT_PRELOAD:
290 return nsIContentSecurityPolicy::FONT_SRC_DIRECTIVE;
291
292 case nsIContentPolicy::TYPE_MEDIA:
293 case nsIContentPolicy::TYPE_INTERNAL_AUDIO:
294 case nsIContentPolicy::TYPE_INTERNAL_VIDEO:
295 case nsIContentPolicy::TYPE_INTERNAL_TRACK:
296 return nsIContentSecurityPolicy::MEDIA_SRC_DIRECTIVE;
297
298 case nsIContentPolicy::TYPE_WEB_MANIFEST:
299 return nsIContentSecurityPolicy::WEB_MANIFEST_SRC_DIRECTIVE;
300
301 case nsIContentPolicy::TYPE_INTERNAL_WORKER:
302 case nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER:
303 case nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER:
304 return nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE;
305
306 case nsIContentPolicy::TYPE_SUBDOCUMENT:
307 case nsIContentPolicy::TYPE_INTERNAL_FRAME:
308 case nsIContentPolicy::TYPE_INTERNAL_IFRAME:
309 return nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE;
310
311 case nsIContentPolicy::TYPE_WEBSOCKET:
312 case nsIContentPolicy::TYPE_XMLHTTPREQUEST:
313 case nsIContentPolicy::TYPE_BEACON:
314 case nsIContentPolicy::TYPE_PING:
315 case nsIContentPolicy::TYPE_FETCH:
316 case nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST:
317 case nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE:
318 case nsIContentPolicy::TYPE_INTERNAL_FETCH_PRELOAD:
319 return nsIContentSecurityPolicy::CONNECT_SRC_DIRECTIVE;
320
321 case nsIContentPolicy::TYPE_OBJECT:
322 case nsIContentPolicy::TYPE_OBJECT_SUBREQUEST:
323 case nsIContentPolicy::TYPE_INTERNAL_EMBED:
324 case nsIContentPolicy::TYPE_INTERNAL_OBJECT:
325 return nsIContentSecurityPolicy::OBJECT_SRC_DIRECTIVE;
326
327 case nsIContentPolicy::TYPE_DTD:
328 case nsIContentPolicy::TYPE_OTHER:
329 case nsIContentPolicy::TYPE_SPECULATIVE:
330 case nsIContentPolicy::TYPE_INTERNAL_DTD:
331 case nsIContentPolicy::TYPE_INTERNAL_FORCE_ALLOWED_DTD:
332 return nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE;
333
334 // csp shold not block top level loads, e.g. in case
335 // of a redirect.
336 case nsIContentPolicy::TYPE_DOCUMENT:
337 // CSP can not block csp reports
338 case nsIContentPolicy::TYPE_CSP_REPORT:
339 return nsIContentSecurityPolicy::NO_DIRECTIVE;
340
341 case nsIContentPolicy::TYPE_SAVEAS_DOWNLOAD:
342 case nsIContentPolicy::TYPE_UA_FONT:
343 return nsIContentSecurityPolicy::NO_DIRECTIVE;
344
345 // Fall through to error for all other directives
346 // Note that we should never end up here for navigate-to
347 case nsIContentPolicy::TYPE_INVALID:
348 MOZ_ASSERT(false, "Can not map nsContentPolicyType to CSPDirective");
349 // Do not add default: so that compilers can catch the missing case.
350 }
351 return nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE;
352 }
353
CSP_CreateHostSrcFromSelfURI(nsIURI * aSelfURI)354 nsCSPHostSrc* CSP_CreateHostSrcFromSelfURI(nsIURI* aSelfURI) {
355 // Create the host first
356 nsCString host;
357 aSelfURI->GetAsciiHost(host);
358 nsCSPHostSrc* hostsrc = new nsCSPHostSrc(NS_ConvertUTF8toUTF16(host));
359 hostsrc->setGeneratedFromSelfKeyword();
360
361 // Add the scheme.
362 nsCString scheme;
363 aSelfURI->GetScheme(scheme);
364 hostsrc->setScheme(NS_ConvertUTF8toUTF16(scheme));
365
366 // An empty host (e.g. for data:) indicates it's effectively a unique origin.
367 // Please note that we still need to set the scheme on hostsrc (see above),
368 // because it's used for reporting.
369 if (host.EqualsLiteral("")) {
370 hostsrc->setIsUniqueOrigin();
371 // no need to query the port in that case.
372 return hostsrc;
373 }
374
375 int32_t port;
376 aSelfURI->GetPort(&port);
377 // Only add port if it's not default port.
378 if (port > 0) {
379 nsAutoString portStr;
380 portStr.AppendInt(port);
381 hostsrc->setPort(portStr);
382 }
383 return hostsrc;
384 }
385
CSP_IsEmptyDirective(const nsAString & aValue,const nsAString & aDir)386 bool CSP_IsEmptyDirective(const nsAString& aValue, const nsAString& aDir) {
387 return (aDir.Length() == 0 && aValue.Length() == 0);
388 }
CSP_IsDirective(const nsAString & aValue,CSPDirective aDir)389 bool CSP_IsDirective(const nsAString& aValue, CSPDirective aDir) {
390 return aValue.LowerCaseEqualsASCII(CSP_CSPDirectiveToString(aDir));
391 }
392
CSP_IsKeyword(const nsAString & aValue,enum CSPKeyword aKey)393 bool CSP_IsKeyword(const nsAString& aValue, enum CSPKeyword aKey) {
394 return aValue.LowerCaseEqualsASCII(CSP_EnumToUTF8Keyword(aKey));
395 }
396
CSP_IsQuotelessKeyword(const nsAString & aKey)397 bool CSP_IsQuotelessKeyword(const nsAString& aKey) {
398 nsString lowerKey;
399 ToLowerCase(aKey, lowerKey);
400
401 nsAutoString keyword;
402 for (uint32_t i = 0; i < CSP_LAST_KEYWORD_VALUE; i++) {
403 // skipping the leading ' and trimming the trailing '
404 keyword.AssignASCII(gCSPUTF8Keywords[i] + 1);
405 keyword.Trim("'", false, true);
406 if (lowerKey.Equals(keyword)) {
407 return true;
408 }
409 }
410 return false;
411 }
412
413 /*
414 * Checks whether the current directive permits a specific
415 * scheme. This function is called from nsCSPSchemeSrc() and
416 * also nsCSPHostSrc.
417 * @param aEnforcementScheme
418 * The scheme that this directive allows
419 * @param aUri
420 * The uri of the subresource load.
421 * @param aReportOnly
422 * Whether the enforced policy is report only or not.
423 * @param aUpgradeInsecure
424 * Whether the policy makes use of the directive
425 * 'upgrade-insecure-requests'.
426 * @param aFromSelfURI
427 * Whether a scheme was generated from the keyword 'self'
428 * which then allows schemeless sources to match ws and wss.
429 */
430
permitsScheme(const nsAString & aEnforcementScheme,nsIURI * aUri,bool aReportOnly,bool aUpgradeInsecure,bool aFromSelfURI)431 bool permitsScheme(const nsAString& aEnforcementScheme, nsIURI* aUri,
432 bool aReportOnly, bool aUpgradeInsecure, bool aFromSelfURI) {
433 nsAutoCString scheme;
434 nsresult rv = aUri->GetScheme(scheme);
435 NS_ENSURE_SUCCESS(rv, false);
436
437 // no scheme to enforce, let's allow the load (e.g. script-src *)
438 if (aEnforcementScheme.IsEmpty()) {
439 return true;
440 }
441
442 // if the scheme matches, all good - allow the load
443 if (aEnforcementScheme.EqualsASCII(scheme.get())) {
444 return true;
445 }
446
447 // allow scheme-less sources where the protected resource is http
448 // and the load is https, see:
449 // http://www.w3.org/TR/CSP2/#match-source-expression
450 if (aEnforcementScheme.EqualsASCII("http")) {
451 if (scheme.EqualsASCII("https")) {
452 return true;
453 }
454 if ((scheme.EqualsASCII("ws") || scheme.EqualsASCII("wss")) &&
455 aFromSelfURI) {
456 return true;
457 }
458 }
459 if (aEnforcementScheme.EqualsASCII("https")) {
460 if (scheme.EqualsLiteral("wss") && aFromSelfURI) {
461 return true;
462 }
463 }
464 if (aEnforcementScheme.EqualsASCII("ws") && scheme.EqualsASCII("wss")) {
465 return true;
466 }
467
468 // Allow the load when enforcing upgrade-insecure-requests with the
469 // promise the request gets upgraded from http to https and ws to wss.
470 // See nsHttpChannel::Connect() and also WebSocket.cpp. Please note,
471 // the report only policies should not allow the load and report
472 // the error back to the page.
473 return (
474 (aUpgradeInsecure && !aReportOnly) &&
475 ((scheme.EqualsASCII("http") &&
476 aEnforcementScheme.EqualsASCII("https")) ||
477 (scheme.EqualsASCII("ws") && aEnforcementScheme.EqualsASCII("wss"))));
478 }
479
480 /*
481 * A helper function for appending a CSP header to an existing CSP
482 * policy.
483 *
484 * @param aCsp the CSP policy
485 * @param aHeaderValue the header
486 * @param aReportOnly is this a report-only header?
487 */
488
CSP_AppendCSPFromHeader(nsIContentSecurityPolicy * aCsp,const nsAString & aHeaderValue,bool aReportOnly)489 nsresult CSP_AppendCSPFromHeader(nsIContentSecurityPolicy* aCsp,
490 const nsAString& aHeaderValue,
491 bool aReportOnly) {
492 NS_ENSURE_ARG(aCsp);
493
494 // Need to tokenize the header value since multiple headers could be
495 // concatenated into one comma-separated list of policies.
496 // See RFC2616 section 4.2 (last paragraph)
497 nsresult rv = NS_OK;
498 for (const nsAString& policy :
499 nsCharSeparatedTokenizer(aHeaderValue, ',').ToRange()) {
500 rv = aCsp->AppendPolicy(policy, aReportOnly, false);
501 NS_ENSURE_SUCCESS(rv, rv);
502 {
503 CSPUTILSLOG(("CSP refined with policy: \"%s\"",
504 NS_ConvertUTF16toUTF8(policy).get()));
505 }
506 }
507 return NS_OK;
508 }
509
510 /* ===== nsCSPSrc ============================ */
511
nsCSPBaseSrc()512 nsCSPBaseSrc::nsCSPBaseSrc() : mInvalidated(false) {}
513
514 nsCSPBaseSrc::~nsCSPBaseSrc() = default;
515
516 // ::permits is only called for external load requests, therefore:
517 // nsCSPKeywordSrc and nsCSPHashSource fall back to this base class
518 // implementation which will never allow the load.
permits(nsIURI * aUri,const nsAString & aNonce,bool aWasRedirected,bool aReportOnly,bool aUpgradeInsecure,bool aParserCreated) const519 bool nsCSPBaseSrc::permits(nsIURI* aUri, const nsAString& aNonce,
520 bool aWasRedirected, bool aReportOnly,
521 bool aUpgradeInsecure, bool aParserCreated) const {
522 if (CSPUTILSLOGENABLED()) {
523 CSPUTILSLOG(
524 ("nsCSPBaseSrc::permits, aUri: %s", aUri->GetSpecOrDefault().get()));
525 }
526 return false;
527 }
528
529 // ::allows is only called for inlined loads, therefore:
530 // nsCSPSchemeSrc, nsCSPHostSrc fall back
531 // to this base class implementation which will never allow the load.
allows(enum CSPKeyword aKeyword,const nsAString & aHashOrNonce,bool aParserCreated) const532 bool nsCSPBaseSrc::allows(enum CSPKeyword aKeyword,
533 const nsAString& aHashOrNonce,
534 bool aParserCreated) const {
535 CSPUTILSLOG(("nsCSPBaseSrc::allows, aKeyWord: %s, a HashOrNonce: %s",
536 aKeyword == CSP_HASH ? "hash" : CSP_EnumToUTF8Keyword(aKeyword),
537 NS_ConvertUTF16toUTF8(aHashOrNonce).get()));
538 return false;
539 }
540
541 /* ====== nsCSPSchemeSrc ===================== */
542
nsCSPSchemeSrc(const nsAString & aScheme)543 nsCSPSchemeSrc::nsCSPSchemeSrc(const nsAString& aScheme) : mScheme(aScheme) {
544 ToLowerCase(mScheme);
545 }
546
547 nsCSPSchemeSrc::~nsCSPSchemeSrc() = default;
548
permits(nsIURI * aUri,const nsAString & aNonce,bool aWasRedirected,bool aReportOnly,bool aUpgradeInsecure,bool aParserCreated) const549 bool nsCSPSchemeSrc::permits(nsIURI* aUri, const nsAString& aNonce,
550 bool aWasRedirected, bool aReportOnly,
551 bool aUpgradeInsecure, bool aParserCreated) const {
552 if (CSPUTILSLOGENABLED()) {
553 CSPUTILSLOG(
554 ("nsCSPSchemeSrc::permits, aUri: %s", aUri->GetSpecOrDefault().get()));
555 }
556 MOZ_ASSERT((!mScheme.EqualsASCII("")), "scheme can not be the empty string");
557 if (mInvalidated) {
558 return false;
559 }
560 return permitsScheme(mScheme, aUri, aReportOnly, aUpgradeInsecure, false);
561 }
562
visit(nsCSPSrcVisitor * aVisitor) const563 bool nsCSPSchemeSrc::visit(nsCSPSrcVisitor* aVisitor) const {
564 return aVisitor->visitSchemeSrc(*this);
565 }
566
toString(nsAString & outStr) const567 void nsCSPSchemeSrc::toString(nsAString& outStr) const {
568 outStr.Append(mScheme);
569 outStr.AppendLiteral(":");
570 }
571
572 /* ===== nsCSPHostSrc ======================== */
573
nsCSPHostSrc(const nsAString & aHost)574 nsCSPHostSrc::nsCSPHostSrc(const nsAString& aHost)
575 : mHost(aHost),
576 mGeneratedFromSelfKeyword(false),
577 mIsUniqueOrigin(false),
578 mWithinFrameAncstorsDir(false) {
579 ToLowerCase(mHost);
580 }
581
582 nsCSPHostSrc::~nsCSPHostSrc() = default;
583
584 /*
585 * Checks whether the current directive permits a specific port.
586 * @param aEnforcementScheme
587 * The scheme that this directive allows
588 * (used to query the default port for that scheme)
589 * @param aEnforcementPort
590 * The port that this directive allows
591 * @param aResourceURI
592 * The uri of the subresource load
593 */
permitsPort(const nsAString & aEnforcementScheme,const nsAString & aEnforcementPort,nsIURI * aResourceURI)594 bool permitsPort(const nsAString& aEnforcementScheme,
595 const nsAString& aEnforcementPort, nsIURI* aResourceURI) {
596 // If enforcement port is the wildcard, don't block the load.
597 if (aEnforcementPort.EqualsASCII("*")) {
598 return true;
599 }
600
601 int32_t resourcePort;
602 nsresult rv = aResourceURI->GetPort(&resourcePort);
603 if (NS_FAILED(rv) && aEnforcementPort.IsEmpty()) {
604 // If we cannot get a Port (e.g. because of an Custom Protocol handler)
605 // We need to check if a default port is associated with the Scheme
606 if (aEnforcementScheme.IsEmpty()) {
607 return false;
608 }
609 int defaultPortforScheme =
610 NS_GetDefaultPort(NS_ConvertUTF16toUTF8(aEnforcementScheme).get());
611
612 // If there is no default port associated with the Scheme (
613 // defaultPortforScheme == -1) or it is an externally handled protocol (
614 // defaultPortforScheme == 0 ) and the csp does not enforce a port - we can
615 // allow not having a port
616 return (defaultPortforScheme == -1 || defaultPortforScheme == -0);
617 }
618 // Avoid unnecessary string creation/manipulation and don't block the
619 // load if the resource to be loaded uses the default port for that
620 // scheme and there is no port to be enforced.
621 // Note, this optimization relies on scheme checks within permitsScheme().
622 if (resourcePort == DEFAULT_PORT && aEnforcementPort.IsEmpty()) {
623 return true;
624 }
625
626 // By now we know at that either the resourcePort does not use the default
627 // port or there is a port restriction to be enforced. A port value of -1
628 // corresponds to the protocol's default port (eg. -1 implies port 80 for
629 // http URIs), in such a case we have to query the default port of the
630 // resource to be loaded.
631 if (resourcePort == DEFAULT_PORT) {
632 nsAutoCString resourceScheme;
633 rv = aResourceURI->GetScheme(resourceScheme);
634 NS_ENSURE_SUCCESS(rv, false);
635 resourcePort = NS_GetDefaultPort(resourceScheme.get());
636 }
637
638 // If there is a port to be enforced and the ports match, then
639 // don't block the load.
640 nsString resourcePortStr;
641 resourcePortStr.AppendInt(resourcePort);
642 if (aEnforcementPort.Equals(resourcePortStr)) {
643 return true;
644 }
645
646 // If there is no port to be enforced, query the default port for the load.
647 nsString enforcementPort(aEnforcementPort);
648 if (enforcementPort.IsEmpty()) {
649 // For scheme less sources, our parser always generates a scheme
650 // which is the scheme of the protected resource.
651 MOZ_ASSERT(!aEnforcementScheme.IsEmpty(),
652 "need a scheme to query default port");
653 int32_t defaultEnforcementPort =
654 NS_GetDefaultPort(NS_ConvertUTF16toUTF8(aEnforcementScheme).get());
655 enforcementPort.Truncate();
656 enforcementPort.AppendInt(defaultEnforcementPort);
657 }
658
659 // If default ports match, don't block the load
660 if (enforcementPort.Equals(resourcePortStr)) {
661 return true;
662 }
663
664 // Additional port matching where the regular URL matching algorithm
665 // treats insecure ports as matching their secure variants.
666 // default port for http is :80
667 // default port for https is :443
668 if (enforcementPort.EqualsLiteral("80") &&
669 resourcePortStr.EqualsLiteral("443")) {
670 return true;
671 }
672
673 // ports do not match, block the load.
674 return false;
675 }
676
permits(nsIURI * aUri,const nsAString & aNonce,bool aWasRedirected,bool aReportOnly,bool aUpgradeInsecure,bool aParserCreated) const677 bool nsCSPHostSrc::permits(nsIURI* aUri, const nsAString& aNonce,
678 bool aWasRedirected, bool aReportOnly,
679 bool aUpgradeInsecure, bool aParserCreated) const {
680 if (CSPUTILSLOGENABLED()) {
681 CSPUTILSLOG(
682 ("nsCSPHostSrc::permits, aUri: %s", aUri->GetSpecOrDefault().get()));
683 }
684
685 if (mInvalidated || mIsUniqueOrigin) {
686 return false;
687 }
688
689 // we are following the enforcement rules from the spec, see:
690 // http://www.w3.org/TR/CSP11/#match-source-expression
691
692 // 4.3) scheme matching: Check if the scheme matches.
693 if (!permitsScheme(mScheme, aUri, aReportOnly, aUpgradeInsecure,
694 mGeneratedFromSelfKeyword)) {
695 return false;
696 }
697
698 // The host in nsCSpHostSrc should never be empty. In case we are enforcing
699 // just a specific scheme, the parser should generate a nsCSPSchemeSource.
700 NS_ASSERTION((!mHost.IsEmpty()), "host can not be the empty string");
701
702 // Before we can check if the host matches, we have to
703 // extract the host part from aUri.
704 nsAutoCString uriHost;
705 nsresult rv = aUri->GetAsciiHost(uriHost);
706 NS_ENSURE_SUCCESS(rv, false);
707
708 nsString decodedUriHost;
709 CSP_PercentDecodeStr(NS_ConvertUTF8toUTF16(uriHost), decodedUriHost);
710
711 // 2) host matching: Enforce a single *
712 if (mHost.EqualsASCII("*")) {
713 // The single ASTERISK character (*) does not match a URI's scheme of a type
714 // designating a globally unique identifier (such as blob:, data:, or
715 // filesystem:) At the moment firefox does not support filesystem; but for
716 // future compatibility we support it in CSP according to the spec,
717 // see: 4.2.2 Matching Source Expressions Note, that allowlisting any of
718 // these schemes would call nsCSPSchemeSrc::permits().
719 if (aUri->SchemeIs("blob") || aUri->SchemeIs("data") ||
720 aUri->SchemeIs("filesystem")) {
721 return false;
722 }
723
724 // If no scheme is present there also wont be a port and folder to check
725 // which means we can return early
726 if (mScheme.IsEmpty()) {
727 return true;
728 }
729 }
730 // 4.5) host matching: Check if the allowed host starts with a wilcard.
731 else if (mHost.First() == '*') {
732 NS_ASSERTION(
733 mHost[1] == '.',
734 "Second character needs to be '.' whenever host starts with '*'");
735
736 // Eliminate leading "*", but keeping the FULL STOP (.) thereafter before
737 // checking if the remaining characters match
738 nsString wildCardHost = mHost;
739 wildCardHost = Substring(wildCardHost, 1, wildCardHost.Length() - 1);
740 if (!StringEndsWith(decodedUriHost, wildCardHost)) {
741 return false;
742 }
743 }
744 // 4.6) host matching: Check if hosts match.
745 else if (!mHost.Equals(decodedUriHost)) {
746 return false;
747 }
748
749 // Port matching: Check if the ports match.
750 if (!permitsPort(mScheme, mPort, aUri)) {
751 return false;
752 }
753
754 // 4.9) Path matching: If there is a path, we have to enforce
755 // path-level matching, unless the channel got redirected, see:
756 // http://www.w3.org/TR/CSP11/#source-list-paths-and-redirects
757 if (!aWasRedirected && !mPath.IsEmpty()) {
758 // converting aUri into nsIURL so we can strip query and ref
759 // example.com/test#foo -> example.com/test
760 // example.com/test?val=foo -> example.com/test
761 nsCOMPtr<nsIURL> url = do_QueryInterface(aUri);
762 if (!url) {
763 NS_ASSERTION(false, "can't QI into nsIURI");
764 return false;
765 }
766 nsAutoCString uriPath;
767 rv = url->GetFilePath(uriPath);
768 NS_ENSURE_SUCCESS(rv, false);
769
770 if (mWithinFrameAncstorsDir) {
771 // no path matching for frame-ancestors to not leak any path information.
772 return true;
773 }
774
775 nsString decodedUriPath;
776 CSP_PercentDecodeStr(NS_ConvertUTF8toUTF16(uriPath), decodedUriPath);
777
778 // check if the last character of mPath is '/'; if so
779 // we just have to check loading resource is within
780 // the allowed path.
781 if (mPath.Last() == '/') {
782 if (!StringBeginsWith(decodedUriPath, mPath)) {
783 return false;
784 }
785 }
786 // otherwise mPath refers to a specific file, and we have to
787 // check if the loading resource matches the file.
788 else {
789 if (!mPath.Equals(decodedUriPath)) {
790 return false;
791 }
792 }
793 }
794
795 // At the end: scheme, host, port and path match -> allow the load.
796 return true;
797 }
798
visit(nsCSPSrcVisitor * aVisitor) const799 bool nsCSPHostSrc::visit(nsCSPSrcVisitor* aVisitor) const {
800 return aVisitor->visitHostSrc(*this);
801 }
802
toString(nsAString & outStr) const803 void nsCSPHostSrc::toString(nsAString& outStr) const {
804 if (mGeneratedFromSelfKeyword) {
805 outStr.AppendLiteral("'self'");
806 return;
807 }
808
809 // If mHost is a single "*", we append the wildcard and return.
810 if (mHost.EqualsASCII("*") && mScheme.IsEmpty() && mPort.IsEmpty()) {
811 outStr.Append(mHost);
812 return;
813 }
814
815 // append scheme
816 outStr.Append(mScheme);
817
818 // append host
819 outStr.AppendLiteral("://");
820 outStr.Append(mHost);
821
822 // append port
823 if (!mPort.IsEmpty()) {
824 outStr.AppendLiteral(":");
825 outStr.Append(mPort);
826 }
827
828 // append path
829 outStr.Append(mPath);
830 }
831
setScheme(const nsAString & aScheme)832 void nsCSPHostSrc::setScheme(const nsAString& aScheme) {
833 mScheme = aScheme;
834 ToLowerCase(mScheme);
835 }
836
setPort(const nsAString & aPort)837 void nsCSPHostSrc::setPort(const nsAString& aPort) { mPort = aPort; }
838
appendPath(const nsAString & aPath)839 void nsCSPHostSrc::appendPath(const nsAString& aPath) { mPath.Append(aPath); }
840
841 /* ===== nsCSPKeywordSrc ===================== */
842
nsCSPKeywordSrc(enum CSPKeyword aKeyword)843 nsCSPKeywordSrc::nsCSPKeywordSrc(enum CSPKeyword aKeyword)
844 : mKeyword(aKeyword) {
845 NS_ASSERTION((aKeyword != CSP_SELF),
846 "'self' should have been replaced in the parser");
847 }
848
849 nsCSPKeywordSrc::~nsCSPKeywordSrc() = default;
850
permits(nsIURI * aUri,const nsAString & aNonce,bool aWasRedirected,bool aReportOnly,bool aUpgradeInsecure,bool aParserCreated) const851 bool nsCSPKeywordSrc::permits(nsIURI* aUri, const nsAString& aNonce,
852 bool aWasRedirected, bool aReportOnly,
853 bool aUpgradeInsecure,
854 bool aParserCreated) const {
855 // no need to check for invalidated, this will always return false unless
856 // it is an nsCSPKeywordSrc for 'strict-dynamic', which should allow non
857 // parser created scripts.
858 return ((mKeyword == CSP_STRICT_DYNAMIC) && !aParserCreated);
859 }
860
allows(enum CSPKeyword aKeyword,const nsAString & aHashOrNonce,bool aParserCreated) const861 bool nsCSPKeywordSrc::allows(enum CSPKeyword aKeyword,
862 const nsAString& aHashOrNonce,
863 bool aParserCreated) const {
864 CSPUTILSLOG(
865 ("nsCSPKeywordSrc::allows, aKeyWord: %s, aHashOrNonce: %s, mInvalidated: "
866 "%s",
867 CSP_EnumToUTF8Keyword(aKeyword),
868 NS_ConvertUTF16toUTF8(aHashOrNonce).get(),
869 mInvalidated ? "yes" : "false"));
870
871 if (mInvalidated) {
872 // only 'self', 'report-sample' and 'unsafe-inline' are keywords that can be
873 // ignored. Please note that the parser already translates 'self' into a uri
874 // (see assertion in constructor).
875 MOZ_ASSERT(mKeyword == CSP_UNSAFE_INLINE || mKeyword == CSP_REPORT_SAMPLE,
876 "should only invalidate unsafe-inline");
877 return false;
878 }
879 // either the keyword allows the load or the policy contains 'strict-dynamic',
880 // in which case we have to make sure the script is not parser created before
881 // allowing the load and also eval should be blocked even if 'strict-dynamic'
882 // is present. Should be allowed only if 'unsafe-eval' is present.
883 return ((mKeyword == aKeyword) ||
884 ((mKeyword == CSP_STRICT_DYNAMIC) && !aParserCreated &&
885 aKeyword != CSP_UNSAFE_EVAL));
886 }
887
visit(nsCSPSrcVisitor * aVisitor) const888 bool nsCSPKeywordSrc::visit(nsCSPSrcVisitor* aVisitor) const {
889 return aVisitor->visitKeywordSrc(*this);
890 }
891
toString(nsAString & outStr) const892 void nsCSPKeywordSrc::toString(nsAString& outStr) const {
893 outStr.Append(CSP_EnumToUTF16Keyword(mKeyword));
894 }
895
896 /* ===== nsCSPNonceSrc ==================== */
897
nsCSPNonceSrc(const nsAString & aNonce)898 nsCSPNonceSrc::nsCSPNonceSrc(const nsAString& aNonce) : mNonce(aNonce) {}
899
900 nsCSPNonceSrc::~nsCSPNonceSrc() = default;
901
permits(nsIURI * aUri,const nsAString & aNonce,bool aWasRedirected,bool aReportOnly,bool aUpgradeInsecure,bool aParserCreated) const902 bool nsCSPNonceSrc::permits(nsIURI* aUri, const nsAString& aNonce,
903 bool aWasRedirected, bool aReportOnly,
904 bool aUpgradeInsecure, bool aParserCreated) const {
905 if (CSPUTILSLOGENABLED()) {
906 CSPUTILSLOG(("nsCSPNonceSrc::permits, aUri: %s, aNonce: %s",
907 aUri->GetSpecOrDefault().get(),
908 NS_ConvertUTF16toUTF8(aNonce).get()));
909 }
910
911 if (aReportOnly && aWasRedirected && aNonce.IsEmpty()) {
912 /* Fix for Bug 1505412
913 * If we land here, we're currently handling a script-preload which got
914 * redirected. Preloads do not have any info about the nonce assiociated.
915 * Because of Report-Only the preload passes the 1st CSP-check so the
916 * preload does not get retried with a nonce attached.
917 * Currently we're relying on the script-manager to
918 * provide a fake loadinfo to check the preloads against csp.
919 * So during HTTPChannel->OnRedirect we cant check csp for this case.
920 * But as the script-manager already checked the csp,
921 * a report would already have been send,
922 * if the nonce didnt match.
923 * So we can pass the check here for Report-Only Cases.
924 */
925 MOZ_ASSERT(aParserCreated == false,
926 "Skipping nonce-check is only allowed for Preloads");
927 return true;
928 }
929
930 // nonces can not be invalidated by strict-dynamic
931 return mNonce.Equals(aNonce);
932 }
933
allows(enum CSPKeyword aKeyword,const nsAString & aHashOrNonce,bool aParserCreated) const934 bool nsCSPNonceSrc::allows(enum CSPKeyword aKeyword,
935 const nsAString& aHashOrNonce,
936 bool aParserCreated) const {
937 CSPUTILSLOG(("nsCSPNonceSrc::allows, aKeyWord: %s, a HashOrNonce: %s",
938 CSP_EnumToUTF8Keyword(aKeyword),
939 NS_ConvertUTF16toUTF8(aHashOrNonce).get()));
940
941 if (aKeyword != CSP_NONCE) {
942 return false;
943 }
944 // nonces can not be invalidated by strict-dynamic
945 return mNonce.Equals(aHashOrNonce);
946 }
947
visit(nsCSPSrcVisitor * aVisitor) const948 bool nsCSPNonceSrc::visit(nsCSPSrcVisitor* aVisitor) const {
949 return aVisitor->visitNonceSrc(*this);
950 }
951
toString(nsAString & outStr) const952 void nsCSPNonceSrc::toString(nsAString& outStr) const {
953 outStr.Append(CSP_EnumToUTF16Keyword(CSP_NONCE));
954 outStr.Append(mNonce);
955 outStr.AppendLiteral("'");
956 }
957
958 /* ===== nsCSPHashSrc ===================== */
959
nsCSPHashSrc(const nsAString & aAlgo,const nsAString & aHash)960 nsCSPHashSrc::nsCSPHashSrc(const nsAString& aAlgo, const nsAString& aHash)
961 : mAlgorithm(aAlgo), mHash(aHash) {
962 // Only the algo should be rewritten to lowercase, the hash must remain the
963 // same.
964 ToLowerCase(mAlgorithm);
965 }
966
967 nsCSPHashSrc::~nsCSPHashSrc() = default;
968
allows(enum CSPKeyword aKeyword,const nsAString & aHashOrNonce,bool aParserCreated) const969 bool nsCSPHashSrc::allows(enum CSPKeyword aKeyword,
970 const nsAString& aHashOrNonce,
971 bool aParserCreated) const {
972 CSPUTILSLOG(("nsCSPHashSrc::allows, aKeyWord: %s, a HashOrNonce: %s",
973 CSP_EnumToUTF8Keyword(aKeyword),
974 NS_ConvertUTF16toUTF8(aHashOrNonce).get()));
975
976 if (aKeyword != CSP_HASH) {
977 return false;
978 }
979
980 // hashes can not be invalidated by strict-dynamic
981
982 // Convert aHashOrNonce to UTF-8
983 NS_ConvertUTF16toUTF8 utf8_hash(aHashOrNonce);
984
985 nsresult rv;
986 nsCOMPtr<nsICryptoHash> hasher;
987 hasher = do_CreateInstance("@mozilla.org/security/hash;1", &rv);
988 NS_ENSURE_SUCCESS(rv, false);
989
990 rv = hasher->InitWithString(NS_ConvertUTF16toUTF8(mAlgorithm));
991 NS_ENSURE_SUCCESS(rv, false);
992
993 rv = hasher->Update((uint8_t*)utf8_hash.get(), utf8_hash.Length());
994 NS_ENSURE_SUCCESS(rv, false);
995
996 nsAutoCString hash;
997 rv = hasher->Finish(true, hash);
998 NS_ENSURE_SUCCESS(rv, false);
999
1000 return NS_ConvertUTF16toUTF8(mHash).Equals(hash);
1001 }
1002
visit(nsCSPSrcVisitor * aVisitor) const1003 bool nsCSPHashSrc::visit(nsCSPSrcVisitor* aVisitor) const {
1004 return aVisitor->visitHashSrc(*this);
1005 }
1006
toString(nsAString & outStr) const1007 void nsCSPHashSrc::toString(nsAString& outStr) const {
1008 outStr.AppendLiteral("'");
1009 outStr.Append(mAlgorithm);
1010 outStr.AppendLiteral("-");
1011 outStr.Append(mHash);
1012 outStr.AppendLiteral("'");
1013 }
1014
1015 /* ===== nsCSPReportURI ===================== */
1016
nsCSPReportURI(nsIURI * aURI)1017 nsCSPReportURI::nsCSPReportURI(nsIURI* aURI) : mReportURI(aURI) {}
1018
1019 nsCSPReportURI::~nsCSPReportURI() = default;
1020
visit(nsCSPSrcVisitor * aVisitor) const1021 bool nsCSPReportURI::visit(nsCSPSrcVisitor* aVisitor) const { return false; }
1022
toString(nsAString & outStr) const1023 void nsCSPReportURI::toString(nsAString& outStr) const {
1024 nsAutoCString spec;
1025 nsresult rv = mReportURI->GetSpec(spec);
1026 if (NS_FAILED(rv)) {
1027 return;
1028 }
1029 outStr.AppendASCII(spec.get());
1030 }
1031
1032 /* ===== nsCSPSandboxFlags ===================== */
1033
nsCSPSandboxFlags(const nsAString & aFlags)1034 nsCSPSandboxFlags::nsCSPSandboxFlags(const nsAString& aFlags) : mFlags(aFlags) {
1035 ToLowerCase(mFlags);
1036 }
1037
1038 nsCSPSandboxFlags::~nsCSPSandboxFlags() = default;
1039
visit(nsCSPSrcVisitor * aVisitor) const1040 bool nsCSPSandboxFlags::visit(nsCSPSrcVisitor* aVisitor) const { return false; }
1041
toString(nsAString & outStr) const1042 void nsCSPSandboxFlags::toString(nsAString& outStr) const {
1043 outStr.Append(mFlags);
1044 }
1045
1046 /* ===== nsCSPDirective ====================== */
1047
nsCSPDirective(CSPDirective aDirective)1048 nsCSPDirective::nsCSPDirective(CSPDirective aDirective) {
1049 mDirective = aDirective;
1050 }
1051
~nsCSPDirective()1052 nsCSPDirective::~nsCSPDirective() {
1053 for (uint32_t i = 0; i < mSrcs.Length(); i++) {
1054 delete mSrcs[i];
1055 }
1056 }
1057
permits(nsIURI * aUri,const nsAString & aNonce,bool aWasRedirected,bool aReportOnly,bool aUpgradeInsecure,bool aParserCreated) const1058 bool nsCSPDirective::permits(nsIURI* aUri, const nsAString& aNonce,
1059 bool aWasRedirected, bool aReportOnly,
1060 bool aUpgradeInsecure, bool aParserCreated) const {
1061 if (CSPUTILSLOGENABLED()) {
1062 CSPUTILSLOG(
1063 ("nsCSPDirective::permits, aUri: %s", aUri->GetSpecOrDefault().get()));
1064 }
1065
1066 for (uint32_t i = 0; i < mSrcs.Length(); i++) {
1067 if (mSrcs[i]->permits(aUri, aNonce, aWasRedirected, aReportOnly,
1068 aUpgradeInsecure, aParserCreated)) {
1069 return true;
1070 }
1071 }
1072 return false;
1073 }
1074
allows(enum CSPKeyword aKeyword,const nsAString & aHashOrNonce,bool aParserCreated) const1075 bool nsCSPDirective::allows(enum CSPKeyword aKeyword,
1076 const nsAString& aHashOrNonce,
1077 bool aParserCreated) const {
1078 CSPUTILSLOG(("nsCSPDirective::allows, aKeyWord: %s, a HashOrNonce: %s",
1079 CSP_EnumToUTF8Keyword(aKeyword),
1080 NS_ConvertUTF16toUTF8(aHashOrNonce).get()));
1081
1082 for (uint32_t i = 0; i < mSrcs.Length(); i++) {
1083 if (mSrcs[i]->allows(aKeyword, aHashOrNonce, aParserCreated)) {
1084 return true;
1085 }
1086 }
1087 return false;
1088 }
1089
toString(nsAString & outStr) const1090 void nsCSPDirective::toString(nsAString& outStr) const {
1091 // Append directive name
1092 outStr.AppendASCII(CSP_CSPDirectiveToString(mDirective));
1093 outStr.AppendLiteral(" ");
1094
1095 // Append srcs
1096 StringJoinAppend(outStr, u" "_ns, mSrcs,
1097 [](nsAString& dest, nsCSPBaseSrc* cspBaseSrc) {
1098 cspBaseSrc->toString(dest);
1099 });
1100 }
1101
toDomCSPStruct(mozilla::dom::CSP & outCSP) const1102 void nsCSPDirective::toDomCSPStruct(mozilla::dom::CSP& outCSP) const {
1103 mozilla::dom::Sequence<nsString> srcs;
1104 nsString src;
1105 for (uint32_t i = 0; i < mSrcs.Length(); i++) {
1106 src.Truncate();
1107 mSrcs[i]->toString(src);
1108 if (!srcs.AppendElement(src, mozilla::fallible)) {
1109 // XXX(Bug 1632090) Instead of extending the array 1-by-1 (which might
1110 // involve multiple reallocations) and potentially crashing here,
1111 // SetCapacity could be called outside the loop once.
1112 mozalloc_handle_oom(0);
1113 }
1114 }
1115
1116 switch (mDirective) {
1117 case nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE:
1118 outCSP.mDefault_src.Construct();
1119 outCSP.mDefault_src.Value() = std::move(srcs);
1120 return;
1121
1122 case nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE:
1123 outCSP.mScript_src.Construct();
1124 outCSP.mScript_src.Value() = std::move(srcs);
1125 return;
1126
1127 case nsIContentSecurityPolicy::OBJECT_SRC_DIRECTIVE:
1128 outCSP.mObject_src.Construct();
1129 outCSP.mObject_src.Value() = std::move(srcs);
1130 return;
1131
1132 case nsIContentSecurityPolicy::STYLE_SRC_DIRECTIVE:
1133 outCSP.mStyle_src.Construct();
1134 outCSP.mStyle_src.Value() = std::move(srcs);
1135 return;
1136
1137 case nsIContentSecurityPolicy::IMG_SRC_DIRECTIVE:
1138 outCSP.mImg_src.Construct();
1139 outCSP.mImg_src.Value() = std::move(srcs);
1140 return;
1141
1142 case nsIContentSecurityPolicy::MEDIA_SRC_DIRECTIVE:
1143 outCSP.mMedia_src.Construct();
1144 outCSP.mMedia_src.Value() = std::move(srcs);
1145 return;
1146
1147 case nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE:
1148 outCSP.mFrame_src.Construct();
1149 outCSP.mFrame_src.Value() = std::move(srcs);
1150 return;
1151
1152 case nsIContentSecurityPolicy::FONT_SRC_DIRECTIVE:
1153 outCSP.mFont_src.Construct();
1154 outCSP.mFont_src.Value() = std::move(srcs);
1155 return;
1156
1157 case nsIContentSecurityPolicy::CONNECT_SRC_DIRECTIVE:
1158 outCSP.mConnect_src.Construct();
1159 outCSP.mConnect_src.Value() = std::move(srcs);
1160 return;
1161
1162 case nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE:
1163 outCSP.mReport_uri.Construct();
1164 outCSP.mReport_uri.Value() = std::move(srcs);
1165 return;
1166
1167 case nsIContentSecurityPolicy::FRAME_ANCESTORS_DIRECTIVE:
1168 outCSP.mFrame_ancestors.Construct();
1169 outCSP.mFrame_ancestors.Value() = std::move(srcs);
1170 return;
1171
1172 case nsIContentSecurityPolicy::WEB_MANIFEST_SRC_DIRECTIVE:
1173 outCSP.mManifest_src.Construct();
1174 outCSP.mManifest_src.Value() = std::move(srcs);
1175 return;
1176 // not supporting REFLECTED_XSS_DIRECTIVE
1177
1178 case nsIContentSecurityPolicy::BASE_URI_DIRECTIVE:
1179 outCSP.mBase_uri.Construct();
1180 outCSP.mBase_uri.Value() = std::move(srcs);
1181 return;
1182
1183 case nsIContentSecurityPolicy::FORM_ACTION_DIRECTIVE:
1184 outCSP.mForm_action.Construct();
1185 outCSP.mForm_action.Value() = std::move(srcs);
1186 return;
1187
1188 case nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT:
1189 outCSP.mBlock_all_mixed_content.Construct();
1190 // does not have any srcs
1191 return;
1192
1193 case nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE:
1194 outCSP.mUpgrade_insecure_requests.Construct();
1195 // does not have any srcs
1196 return;
1197
1198 case nsIContentSecurityPolicy::CHILD_SRC_DIRECTIVE:
1199 outCSP.mChild_src.Construct();
1200 outCSP.mChild_src.Value() = std::move(srcs);
1201 return;
1202
1203 case nsIContentSecurityPolicy::SANDBOX_DIRECTIVE:
1204 outCSP.mSandbox.Construct();
1205 outCSP.mSandbox.Value() = std::move(srcs);
1206 return;
1207
1208 case nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE:
1209 outCSP.mWorker_src.Construct();
1210 outCSP.mWorker_src.Value() = std::move(srcs);
1211 return;
1212
1213 default:
1214 NS_ASSERTION(false, "cannot find directive to convert CSP to JSON");
1215 }
1216 }
1217
getReportURIs(nsTArray<nsString> & outReportURIs) const1218 void nsCSPDirective::getReportURIs(nsTArray<nsString>& outReportURIs) const {
1219 NS_ASSERTION((mDirective == nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE),
1220 "not a report-uri directive");
1221
1222 // append uris
1223 nsString tmpReportURI;
1224 for (uint32_t i = 0; i < mSrcs.Length(); i++) {
1225 tmpReportURI.Truncate();
1226 mSrcs[i]->toString(tmpReportURI);
1227 outReportURIs.AppendElement(tmpReportURI);
1228 }
1229 }
1230
visitSrcs(nsCSPSrcVisitor * aVisitor) const1231 bool nsCSPDirective::visitSrcs(nsCSPSrcVisitor* aVisitor) const {
1232 for (uint32_t i = 0; i < mSrcs.Length(); i++) {
1233 if (!mSrcs[i]->visit(aVisitor)) {
1234 return false;
1235 }
1236 }
1237 return true;
1238 }
1239
equals(CSPDirective aDirective) const1240 bool nsCSPDirective::equals(CSPDirective aDirective) const {
1241 return (mDirective == aDirective);
1242 }
1243
getDirName(nsAString & outStr) const1244 void nsCSPDirective::getDirName(nsAString& outStr) const {
1245 outStr.AppendASCII(CSP_CSPDirectiveToString(mDirective));
1246 }
1247
hasReportSampleKeyword() const1248 bool nsCSPDirective::hasReportSampleKeyword() const {
1249 for (nsCSPBaseSrc* src : mSrcs) {
1250 if (src->isReportSample()) {
1251 return true;
1252 }
1253 }
1254
1255 return false;
1256 }
1257
1258 /* =============== nsCSPChildSrcDirective ============= */
1259
nsCSPChildSrcDirective(CSPDirective aDirective)1260 nsCSPChildSrcDirective::nsCSPChildSrcDirective(CSPDirective aDirective)
1261 : nsCSPDirective(aDirective),
1262 mRestrictFrames(false),
1263 mRestrictWorkers(false) {}
1264
1265 nsCSPChildSrcDirective::~nsCSPChildSrcDirective() = default;
1266
equals(CSPDirective aDirective) const1267 bool nsCSPChildSrcDirective::equals(CSPDirective aDirective) const {
1268 if (aDirective == nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE) {
1269 return mRestrictFrames;
1270 }
1271 if (aDirective == nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE) {
1272 return mRestrictWorkers;
1273 }
1274 return (mDirective == aDirective);
1275 }
1276
1277 /* =============== nsCSPScriptSrcDirective ============= */
1278
nsCSPScriptSrcDirective(CSPDirective aDirective)1279 nsCSPScriptSrcDirective::nsCSPScriptSrcDirective(CSPDirective aDirective)
1280 : nsCSPDirective(aDirective), mRestrictWorkers(false) {}
1281
1282 nsCSPScriptSrcDirective::~nsCSPScriptSrcDirective() = default;
1283
equals(CSPDirective aDirective) const1284 bool nsCSPScriptSrcDirective::equals(CSPDirective aDirective) const {
1285 if (aDirective == nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE) {
1286 return mRestrictWorkers;
1287 }
1288 return (mDirective == aDirective);
1289 }
1290
1291 /* =============== nsBlockAllMixedContentDirective ============= */
1292
nsBlockAllMixedContentDirective(CSPDirective aDirective)1293 nsBlockAllMixedContentDirective::nsBlockAllMixedContentDirective(
1294 CSPDirective aDirective)
1295 : nsCSPDirective(aDirective) {}
1296
1297 nsBlockAllMixedContentDirective::~nsBlockAllMixedContentDirective() = default;
1298
toString(nsAString & outStr) const1299 void nsBlockAllMixedContentDirective::toString(nsAString& outStr) const {
1300 outStr.AppendASCII(CSP_CSPDirectiveToString(
1301 nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT));
1302 }
1303
getDirName(nsAString & outStr) const1304 void nsBlockAllMixedContentDirective::getDirName(nsAString& outStr) const {
1305 outStr.AppendASCII(CSP_CSPDirectiveToString(
1306 nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT));
1307 }
1308
1309 /* =============== nsUpgradeInsecureDirective ============= */
1310
nsUpgradeInsecureDirective(CSPDirective aDirective)1311 nsUpgradeInsecureDirective::nsUpgradeInsecureDirective(CSPDirective aDirective)
1312 : nsCSPDirective(aDirective) {}
1313
1314 nsUpgradeInsecureDirective::~nsUpgradeInsecureDirective() = default;
1315
toString(nsAString & outStr) const1316 void nsUpgradeInsecureDirective::toString(nsAString& outStr) const {
1317 outStr.AppendASCII(CSP_CSPDirectiveToString(
1318 nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE));
1319 }
1320
getDirName(nsAString & outStr) const1321 void nsUpgradeInsecureDirective::getDirName(nsAString& outStr) const {
1322 outStr.AppendASCII(CSP_CSPDirectiveToString(
1323 nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE));
1324 }
1325
1326 /* ===== nsCSPPolicy ========================= */
1327
nsCSPPolicy()1328 nsCSPPolicy::nsCSPPolicy()
1329 : mUpgradeInsecDir(nullptr),
1330 mReportOnly(false),
1331 mDeliveredViaMetaTag(false) {
1332 CSPUTILSLOG(("nsCSPPolicy::nsCSPPolicy"));
1333 }
1334
~nsCSPPolicy()1335 nsCSPPolicy::~nsCSPPolicy() {
1336 CSPUTILSLOG(("nsCSPPolicy::~nsCSPPolicy"));
1337
1338 for (uint32_t i = 0; i < mDirectives.Length(); i++) {
1339 delete mDirectives[i];
1340 }
1341 }
1342
permits(CSPDirective aDir,nsIURI * aUri,const nsAString & aNonce,bool aWasRedirected,bool aSpecific,bool aParserCreated,nsAString & outViolatedDirective) const1343 bool nsCSPPolicy::permits(CSPDirective aDir, nsIURI* aUri,
1344 const nsAString& aNonce, bool aWasRedirected,
1345 bool aSpecific, bool aParserCreated,
1346 nsAString& outViolatedDirective) const {
1347 if (CSPUTILSLOGENABLED()) {
1348 CSPUTILSLOG(("nsCSPPolicy::permits, aUri: %s, aDir: %d, aSpecific: %s",
1349 aUri->GetSpecOrDefault().get(), aDir,
1350 aSpecific ? "true" : "false"));
1351 }
1352
1353 NS_ASSERTION(aUri, "permits needs an uri to perform the check!");
1354 outViolatedDirective.Truncate();
1355
1356 nsCSPDirective* defaultDir = nullptr;
1357
1358 // Try to find a relevant directive
1359 // These directive arrays are short (1-5 elements), not worth using a
1360 // hashtable.
1361 for (uint32_t i = 0; i < mDirectives.Length(); i++) {
1362 if (mDirectives[i]->equals(aDir)) {
1363 if (!mDirectives[i]->permits(aUri, aNonce, aWasRedirected, mReportOnly,
1364 mUpgradeInsecDir, aParserCreated)) {
1365 mDirectives[i]->getDirName(outViolatedDirective);
1366 return false;
1367 }
1368 return true;
1369 }
1370 if (mDirectives[i]->isDefaultDirective()) {
1371 defaultDir = mDirectives[i];
1372 }
1373 }
1374
1375 // If the above loop runs through, we haven't found a matching directive.
1376 // Avoid relooping, just store the result of default-src while looping.
1377 if (!aSpecific && defaultDir) {
1378 if (!defaultDir->permits(aUri, aNonce, aWasRedirected, mReportOnly,
1379 mUpgradeInsecDir, aParserCreated)) {
1380 defaultDir->getDirName(outViolatedDirective);
1381 return false;
1382 }
1383 return true;
1384 }
1385
1386 // Nothing restricts this, so we're allowing the load
1387 // See bug 764937
1388 return true;
1389 }
1390
allows(CSPDirective aDirective,enum CSPKeyword aKeyword,const nsAString & aHashOrNonce,bool aParserCreated) const1391 bool nsCSPPolicy::allows(CSPDirective aDirective, enum CSPKeyword aKeyword,
1392 const nsAString& aHashOrNonce,
1393 bool aParserCreated) const {
1394 CSPUTILSLOG(("nsCSPPolicy::allows, aKeyWord: %s, a HashOrNonce: %s",
1395 CSP_EnumToUTF8Keyword(aKeyword),
1396 NS_ConvertUTF16toUTF8(aHashOrNonce).get()));
1397
1398 nsCSPDirective* defaultDir = nullptr;
1399
1400 // Try to find a matching directive
1401 for (uint32_t i = 0; i < mDirectives.Length(); i++) {
1402 if (mDirectives[i]->isDefaultDirective()) {
1403 defaultDir = mDirectives[i];
1404 continue;
1405 }
1406 if (mDirectives[i]->equals(aDirective)) {
1407 if (mDirectives[i]->allows(aKeyword, aHashOrNonce, aParserCreated)) {
1408 return true;
1409 }
1410 return false;
1411 }
1412 }
1413
1414 // {nonce,hash}-source should not consult default-src:
1415 // * return false if default-src is specified
1416 // * but allow the load if default-src is *not* specified (Bug 1198422)
1417 if (aKeyword == CSP_NONCE || aKeyword == CSP_HASH) {
1418 if (!defaultDir) {
1419 return true;
1420 }
1421 return false;
1422 }
1423
1424 // If the above loop runs through, we haven't found a matching directive.
1425 // Avoid relooping, just store the result of default-src while looping.
1426 if (defaultDir) {
1427 return defaultDir->allows(aKeyword, aHashOrNonce, aParserCreated);
1428 }
1429
1430 // Allowing the load; see Bug 885433
1431 // a) inline scripts (also unsafe eval) should only be blocked
1432 // if there is a [script-src] or [default-src]
1433 // b) inline styles should only be blocked
1434 // if there is a [style-src] or [default-src]
1435 return true;
1436 }
1437
toString(nsAString & outStr) const1438 void nsCSPPolicy::toString(nsAString& outStr) const {
1439 StringJoinAppend(outStr, u"; "_ns, mDirectives,
1440 [](nsAString& dest, nsCSPDirective* cspDirective) {
1441 cspDirective->toString(dest);
1442 });
1443 }
1444
toDomCSPStruct(mozilla::dom::CSP & outCSP) const1445 void nsCSPPolicy::toDomCSPStruct(mozilla::dom::CSP& outCSP) const {
1446 outCSP.mReport_only = mReportOnly;
1447
1448 for (uint32_t i = 0; i < mDirectives.Length(); ++i) {
1449 mDirectives[i]->toDomCSPStruct(outCSP);
1450 }
1451 }
1452
hasDirective(CSPDirective aDir) const1453 bool nsCSPPolicy::hasDirective(CSPDirective aDir) const {
1454 for (uint32_t i = 0; i < mDirectives.Length(); i++) {
1455 if (mDirectives[i]->equals(aDir)) {
1456 return true;
1457 }
1458 }
1459 return false;
1460 }
1461
allowsNavigateTo(nsIURI * aURI,bool aWasRedirected,bool aEnforceAllowlist) const1462 bool nsCSPPolicy::allowsNavigateTo(nsIURI* aURI, bool aWasRedirected,
1463 bool aEnforceAllowlist) const {
1464 bool allowsNavigateTo = true;
1465
1466 for (unsigned long i = 0; i < mDirectives.Length(); i++) {
1467 if (mDirectives[i]->equals(
1468 nsIContentSecurityPolicy::NAVIGATE_TO_DIRECTIVE)) {
1469 // Early return if we can skip the allowlist AND 'unsafe-allow-redirects'
1470 // is present.
1471 if (!aEnforceAllowlist &&
1472 mDirectives[i]->allows(CSP_UNSAFE_ALLOW_REDIRECTS, u""_ns, false)) {
1473 return true;
1474 }
1475 // Otherwise, check against the allowlist.
1476 if (!mDirectives[i]->permits(aURI, u""_ns, aWasRedirected, false, false,
1477 false)) {
1478 allowsNavigateTo = false;
1479 }
1480 }
1481 }
1482
1483 return allowsNavigateTo;
1484 }
1485
1486 /*
1487 * Use this function only after ::allows() returned 'false'. Most and
1488 * foremost it's used to get the violated directive before sending reports.
1489 * The parameter outDirective is the equivalent of 'outViolatedDirective'
1490 * for the ::permits() function family.
1491 */
getDirectiveStringAndReportSampleForContentType(CSPDirective aDirective,nsAString & outDirective,bool * aReportSample) const1492 void nsCSPPolicy::getDirectiveStringAndReportSampleForContentType(
1493 CSPDirective aDirective, nsAString& outDirective,
1494 bool* aReportSample) const {
1495 MOZ_ASSERT(aReportSample);
1496 *aReportSample = false;
1497
1498 nsCSPDirective* defaultDir = nullptr;
1499 for (uint32_t i = 0; i < mDirectives.Length(); i++) {
1500 if (mDirectives[i]->isDefaultDirective()) {
1501 defaultDir = mDirectives[i];
1502 continue;
1503 }
1504 if (mDirectives[i]->equals(aDirective)) {
1505 mDirectives[i]->getDirName(outDirective);
1506 *aReportSample = mDirectives[i]->hasReportSampleKeyword();
1507 return;
1508 }
1509 }
1510 // if we haven't found a matching directive yet,
1511 // the contentType must be restricted by the default directive
1512 if (defaultDir) {
1513 defaultDir->getDirName(outDirective);
1514 *aReportSample = defaultDir->hasReportSampleKeyword();
1515 return;
1516 }
1517 NS_ASSERTION(false, "Can not query directive string for contentType!");
1518 outDirective.AppendLiteral("couldNotQueryViolatedDirective");
1519 }
1520
getDirectiveAsString(CSPDirective aDir,nsAString & outDirective) const1521 void nsCSPPolicy::getDirectiveAsString(CSPDirective aDir,
1522 nsAString& outDirective) const {
1523 for (uint32_t i = 0; i < mDirectives.Length(); i++) {
1524 if (mDirectives[i]->equals(aDir)) {
1525 mDirectives[i]->toString(outDirective);
1526 return;
1527 }
1528 }
1529 }
1530
1531 /*
1532 * Helper function that returns the underlying bit representation of sandbox
1533 * flags. The function returns SANDBOXED_NONE if there are no sandbox
1534 * directives.
1535 */
getSandboxFlags() const1536 uint32_t nsCSPPolicy::getSandboxFlags() const {
1537 for (uint32_t i = 0; i < mDirectives.Length(); i++) {
1538 if (mDirectives[i]->equals(nsIContentSecurityPolicy::SANDBOX_DIRECTIVE)) {
1539 nsAutoString flags;
1540 mDirectives[i]->toString(flags);
1541
1542 if (flags.IsEmpty()) {
1543 return SANDBOX_ALL_FLAGS;
1544 }
1545
1546 nsAttrValue attr;
1547 attr.ParseAtomArray(flags);
1548
1549 return nsContentUtils::ParseSandboxAttributeToFlags(&attr);
1550 }
1551 }
1552
1553 return SANDBOXED_NONE;
1554 }
1555
getReportURIs(nsTArray<nsString> & outReportURIs) const1556 void nsCSPPolicy::getReportURIs(nsTArray<nsString>& outReportURIs) const {
1557 for (uint32_t i = 0; i < mDirectives.Length(); i++) {
1558 if (mDirectives[i]->equals(
1559 nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE)) {
1560 mDirectives[i]->getReportURIs(outReportURIs);
1561 return;
1562 }
1563 }
1564 }
1565
visitDirectiveSrcs(CSPDirective aDir,nsCSPSrcVisitor * aVisitor) const1566 bool nsCSPPolicy::visitDirectiveSrcs(CSPDirective aDir,
1567 nsCSPSrcVisitor* aVisitor) const {
1568 for (uint32_t i = 0; i < mDirectives.Length(); i++) {
1569 if (mDirectives[i]->equals(aDir)) {
1570 return mDirectives[i]->visitSrcs(aVisitor);
1571 }
1572 }
1573 return false;
1574 }
1575