nsCSPUtils.cpp (68251B)
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 "nsCSPUtils.h" 8 9 #include "mozilla/Assertions.h" 10 #include "mozilla/Components.h" 11 #include "mozilla/StaticPrefs_security.h" 12 #include "mozilla/dom/CSPDictionariesBinding.h" 13 #include "mozilla/dom/Document.h" 14 #include "mozilla/dom/PolicyContainer.h" 15 #include "mozilla/dom/SRIMetadata.h" 16 #include "mozilla/dom/TrustedTypesConstants.h" 17 #include "nsAboutProtocolUtils.h" 18 #include "nsAttrValue.h" 19 #include "nsCSPParser.h" 20 #include "nsCharSeparatedTokenizer.h" 21 #include "nsComponentManagerUtils.h" 22 #include "nsContentUtils.h" 23 #include "nsDebug.h" 24 #include "nsIChannel.h" 25 #include "nsIConsoleService.h" 26 #include "nsIContentSecurityPolicy.h" 27 #include "nsICryptoHash.h" 28 #include "nsIScriptError.h" 29 #include "nsIStringBundle.h" 30 #include "nsIURL.h" 31 #include "nsNetUtil.h" 32 #include "nsReadableUtils.h" 33 #include "nsSandboxFlags.h" 34 #include "nsServiceManagerUtils.h" 35 #include "nsWhitespaceTokenizer.h" 36 37 using namespace mozilla; 38 using mozilla::dom::SRIMetadata; 39 40 #define DEFAULT_PORT -1 41 42 static mozilla::LogModule* GetCspUtilsLog() { 43 static mozilla::LazyLogModule gCspUtilsPRLog("CSPUtils"); 44 return gCspUtilsPRLog; 45 } 46 47 #define CSPUTILSLOG(args) \ 48 MOZ_LOG(GetCspUtilsLog(), mozilla::LogLevel::Debug, args) 49 #define CSPUTILSLOGENABLED() \ 50 MOZ_LOG_TEST(GetCspUtilsLog(), mozilla::LogLevel::Debug) 51 52 void CSP_PercentDecodeStr(const nsAString& aEncStr, nsAString& outDecStr) { 53 outDecStr.Truncate(); 54 55 // helper function that should not be visible outside this methods scope 56 struct local { 57 static inline char16_t convertHexDig(char16_t aHexDig) { 58 if (isNumberToken(aHexDig)) { 59 return aHexDig - '0'; 60 } 61 if (aHexDig >= 'A' && aHexDig <= 'F') { 62 return aHexDig - 'A' + 10; 63 } 64 // must be a lower case character 65 // (aHexDig >= 'a' && aHexDig <= 'f') 66 return aHexDig - 'a' + 10; 67 } 68 }; 69 70 const char16_t *cur, *end, *hexDig1, *hexDig2; 71 cur = aEncStr.BeginReading(); 72 end = aEncStr.EndReading(); 73 74 while (cur != end) { 75 // if it's not a percent sign then there is 76 // nothing to do for that character 77 if (*cur != PERCENT_SIGN) { 78 outDecStr.Append(*cur); 79 cur++; 80 continue; 81 } 82 83 // get the two hexDigs following the '%'-sign 84 hexDig1 = cur + 1; 85 hexDig2 = cur + 2; 86 87 // if there are no hexdigs after the '%' then 88 // there is nothing to do for us. 89 if (hexDig1 == end || hexDig2 == end || !isValidHexDig(*hexDig1) || 90 !isValidHexDig(*hexDig2)) { 91 outDecStr.Append(PERCENT_SIGN); 92 cur++; 93 continue; 94 } 95 96 // decode "% hexDig1 hexDig2" into a character. 97 char16_t decChar = 98 (local::convertHexDig(*hexDig1) << 4) + local::convertHexDig(*hexDig2); 99 outDecStr.Append(decChar); 100 101 // increment 'cur' to after the second hexDig 102 cur = ++hexDig2; 103 } 104 } 105 106 // The Content Security Policy should be inherited for 107 // local schemes like: "about", "blob", "data", or "filesystem". 108 // see: https://w3c.github.io/webappsec-csp/#initialize-document-csp 109 bool CSP_ShouldResponseInheritCSP(nsIChannel* aChannel) { 110 if (!aChannel) { 111 return false; 112 } 113 114 nsCOMPtr<nsIURI> uri; 115 nsresult rv = aChannel->GetURI(getter_AddRefs(uri)); 116 NS_ENSURE_SUCCESS(rv, false); 117 118 return CSP_ShouldURIInheritCSP(uri); 119 } 120 121 bool CSP_ShouldURIInheritCSP(nsIURI* aURI) { 122 if (!aURI) { 123 return false; 124 } 125 // about:blank and about:srcdoc 126 if ((aURI->SchemeIs("about")) && (NS_IsContentAccessibleAboutURI(aURI))) { 127 return true; 128 } 129 return aURI->SchemeIs("blob") || aURI->SchemeIs("data") || 130 aURI->SchemeIs("filesystem") || aURI->SchemeIs("javascript"); 131 } 132 133 void CSP_ApplyMetaCSPToDoc(mozilla::dom::Document& aDoc, 134 const nsAString& aPolicyStr) { 135 if (aDoc.IsLoadedAsData()) { 136 return; 137 } 138 139 nsAutoString policyStr( 140 nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>( 141 aPolicyStr)); 142 143 if (policyStr.IsEmpty()) { 144 return; 145 } 146 147 nsCOMPtr<nsIContentSecurityPolicy> csp = 148 PolicyContainer::GetCSP(aDoc.GetPolicyContainer()); 149 if (!csp) { 150 MOZ_ASSERT(false, "how come there is no CSP"); 151 return; 152 } 153 154 if (nsIURI* uri = aDoc.GetDocumentURI(); CSP_IsBrowserXHTML(uri)) { 155 // Make the <meta> policy in browser.xhtml toggleable. 156 if (!StaticPrefs::security_browser_xhtml_csp_enabled()) { 157 return; 158 } 159 } 160 161 // Multiple CSPs (delivered through either header of meta tag) need to 162 // be joined together, see: 163 // https://w3c.github.io/webappsec/specs/content-security-policy/#delivery-html-meta-element 164 nsresult rv = csp->AppendPolicy( 165 policyStr, 166 false, // CSPs delivered via a <meta> tag can not be report-only. 167 true); // delivered through the meta tag 168 NS_ENSURE_SUCCESS_VOID(rv); 169 if (nsPIDOMWindowInner* inner = aDoc.GetInnerWindow()) { 170 if (nsIPolicyContainer* policyContainer = inner->GetPolicyContainer()) { 171 inner->SetPolicyContainer(policyContainer); 172 } else { 173 RefPtr<PolicyContainer> newPolicyContainer = new PolicyContainer(); 174 inner->SetPolicyContainer(newPolicyContainer); 175 } 176 } 177 aDoc.ApplySettingsFromCSP(false); 178 } 179 180 bool CSP_IsBrowserXHTML(nsIURI* aURI) { 181 if (!aURI->SchemeIs("chrome")) { 182 return false; 183 } 184 185 nsAutoCString spec; 186 aURI->GetSpec(spec); 187 return spec.EqualsLiteral("chrome://browser/content/browser.xhtml"); 188 } 189 190 void CSP_GetLocalizedStr(const char* aName, const nsTArray<nsString>& aParams, 191 nsAString& outResult) { 192 nsCOMPtr<nsIStringBundle> keyStringBundle; 193 nsCOMPtr<nsIStringBundleService> stringBundleService = 194 mozilla::components::StringBundle::Service(); 195 196 NS_ASSERTION(stringBundleService, "String bundle service must be present!"); 197 stringBundleService->CreateBundle( 198 "chrome://global/locale/security/csp.properties", 199 getter_AddRefs(keyStringBundle)); 200 201 NS_ASSERTION(keyStringBundle, "Key string bundle must be available!"); 202 203 if (!keyStringBundle) { 204 return; 205 } 206 207 if (aParams.IsEmpty()) { 208 keyStringBundle->GetStringFromName(aName, outResult); 209 } else { 210 keyStringBundle->FormatStringFromName(aName, aParams, outResult); 211 } 212 } 213 214 void CSP_LogStrMessage(const nsAString& aMsg) { 215 nsCOMPtr<nsIConsoleService> console( 216 do_GetService("@mozilla.org/consoleservice;1")); 217 218 if (!console) { 219 return; 220 } 221 nsString msg(aMsg); 222 console->LogStringMessage(msg.get()); 223 } 224 225 void CSP_LogMessage(const nsAString& aMessage, const nsACString& aSourceName, 226 const nsAString& aSourceLine, uint32_t aLineNumber, 227 uint32_t aColumnNumber, uint32_t aFlags, 228 const nsACString& aCategory, uint64_t aInnerWindowID, 229 bool aFromPrivateWindow) { 230 nsCOMPtr<nsIConsoleService> console( 231 do_GetService(NS_CONSOLESERVICE_CONTRACTID)); 232 233 nsCOMPtr<nsIScriptError> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID)); 234 235 if (!console || !error) { 236 return; 237 } 238 239 // Prepending CSP to the outgoing console message 240 nsString cspMsg; 241 CSP_GetLocalizedStr("CSPMessagePrefix", 242 AutoTArray<nsString, 1>{nsString(aMessage)}, cspMsg); 243 244 // Currently 'aSourceLine' is not logged to the console, because similar 245 // information is already included within the source link of the message. 246 // For inline violations however, the line and column number are 0 and 247 // information contained within 'aSourceLine' can be really useful for devs. 248 // E.g. 'aSourceLine' might be: 'onclick attribute on DIV element'. 249 // In such cases we append 'aSourceLine' directly to the error message. 250 if (!aSourceLine.IsEmpty() && aLineNumber == 0) { 251 cspMsg.AppendLiteral(u"\nSource: "); 252 cspMsg.Append(aSourceLine); 253 } 254 255 // Since we are leveraging csp errors as the category names which 256 // we pass to devtools, we should prepend them with "CSP_" to 257 // allow easy distincution in devtools code. e.g. 258 // upgradeInsecureRequest -> CSP_upgradeInsecureRequest 259 nsCString category("CSP_"); 260 category.Append(aCategory); 261 262 nsresult rv; 263 if (aInnerWindowID > 0) { 264 rv = 265 error->InitWithWindowID(cspMsg, aSourceName, aLineNumber, aColumnNumber, 266 aFlags, category, aInnerWindowID); 267 } else { 268 rv = error->Init(cspMsg, aSourceName, aLineNumber, aColumnNumber, aFlags, 269 category, aFromPrivateWindow, 270 true /* from chrome context */); 271 } 272 if (NS_FAILED(rv)) { 273 return; 274 } 275 console->LogMessage(error); 276 } 277 278 CSPDirective CSP_StringToCSPDirective(const nsAString& aDir) { 279 nsString lowerDir = PromiseFlatString(aDir); 280 ToLowerCase(lowerDir); 281 282 uint32_t numDirs = (sizeof(CSPStrDirectives) / sizeof(CSPStrDirectives[0])); 283 284 for (uint32_t i = 1; i < numDirs; i++) { 285 if (lowerDir.EqualsASCII(CSPStrDirectives[i])) { 286 return static_cast<CSPDirective>(i); 287 } 288 } 289 return nsIContentSecurityPolicy::NO_DIRECTIVE; 290 } 291 292 /** 293 * Combines CSP_LogMessage and CSP_GetLocalizedStr into one call. 294 */ 295 void CSP_LogLocalizedStr(const char* aName, const nsTArray<nsString>& aParams, 296 const nsACString& aSourceName, 297 const nsAString& aSourceLine, uint32_t aLineNumber, 298 uint32_t aColumnNumber, uint32_t aFlags, 299 const nsACString& aCategory, uint64_t aInnerWindowID, 300 bool aFromPrivateWindow) { 301 nsAutoString logMsg; 302 CSP_GetLocalizedStr(aName, aParams, logMsg); 303 CSP_LogMessage(logMsg, aSourceName, aSourceLine, aLineNumber, aColumnNumber, 304 aFlags, aCategory, aInnerWindowID, aFromPrivateWindow); 305 } 306 307 /* ===== Helpers ============================ */ 308 // This implements 309 // https://w3c.github.io/webappsec-csp/#effective-directive-for-a-request. 310 // However the spec doesn't currently cover all request destinations, which 311 // we roughly represent using nsContentPolicyType. 312 CSPDirective CSP_ContentTypeToDirective(nsContentPolicyType aType) { 313 switch (aType) { 314 case nsIContentPolicy::TYPE_IMAGE: 315 case nsIContentPolicy::TYPE_IMAGESET: 316 case nsIContentPolicy::TYPE_INTERNAL_IMAGE: 317 case nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD: 318 case nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON: 319 case nsIContentPolicy::TYPE_INTERNAL_EXTERNAL_RESOURCE: 320 return nsIContentSecurityPolicy::IMG_SRC_DIRECTIVE; 321 322 // BLock XSLT as script, see bug 910139 323 case nsIContentPolicy::TYPE_XSLT: 324 case nsIContentPolicy::TYPE_SCRIPT: 325 case nsIContentPolicy::TYPE_INTERNAL_SCRIPT: 326 case nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD: 327 case nsIContentPolicy::TYPE_INTERNAL_MODULE: 328 case nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD: 329 case nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS: 330 case nsIContentPolicy::TYPE_INTERNAL_AUDIOWORKLET: 331 case nsIContentPolicy::TYPE_INTERNAL_PAINTWORKLET: 332 case nsIContentPolicy::TYPE_INTERNAL_CHROMEUTILS_COMPILED_SCRIPT: 333 case nsIContentPolicy::TYPE_INTERNAL_FRAME_MESSAGEMANAGER_SCRIPT: 334 // (https://github.com/w3c/webappsec-csp/issues/554) 335 // Some of these types are not explicitly defined in the spec. 336 // 337 // Chrome seems to use script-src-elem for worklet! 338 return nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE; 339 340 case nsIContentPolicy::TYPE_STYLESHEET: 341 case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET: 342 case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD: 343 return nsIContentSecurityPolicy::STYLE_SRC_ELEM_DIRECTIVE; 344 345 case nsIContentPolicy::TYPE_FONT: 346 case nsIContentPolicy::TYPE_INTERNAL_FONT_PRELOAD: 347 return nsIContentSecurityPolicy::FONT_SRC_DIRECTIVE; 348 349 case nsIContentPolicy::TYPE_MEDIA: 350 case nsIContentPolicy::TYPE_INTERNAL_AUDIO: 351 case nsIContentPolicy::TYPE_INTERNAL_VIDEO: 352 case nsIContentPolicy::TYPE_INTERNAL_TRACK: 353 return nsIContentSecurityPolicy::MEDIA_SRC_DIRECTIVE; 354 355 case nsIContentPolicy::TYPE_WEB_MANIFEST: 356 return nsIContentSecurityPolicy::WEB_MANIFEST_SRC_DIRECTIVE; 357 358 case nsIContentPolicy::TYPE_INTERNAL_WORKER: 359 case nsIContentPolicy::TYPE_INTERNAL_WORKER_STATIC_MODULE: 360 case nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER: 361 case nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER: 362 return nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE; 363 364 case nsIContentPolicy::TYPE_SUBDOCUMENT: 365 case nsIContentPolicy::TYPE_INTERNAL_FRAME: 366 case nsIContentPolicy::TYPE_INTERNAL_IFRAME: 367 return nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE; 368 369 case nsIContentPolicy::TYPE_WEBSOCKET: 370 case nsIContentPolicy::TYPE_XMLHTTPREQUEST: 371 case nsIContentPolicy::TYPE_BEACON: 372 case nsIContentPolicy::TYPE_PING: 373 case nsIContentPolicy::TYPE_FETCH: 374 case nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST_ASYNC: 375 case nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST_SYNC: 376 case nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE: 377 case nsIContentPolicy::TYPE_INTERNAL_FETCH_PRELOAD: 378 case nsIContentPolicy::TYPE_WEB_IDENTITY: 379 case nsIContentPolicy::TYPE_WEB_TRANSPORT: 380 case nsIContentPolicy::TYPE_JSON: 381 case nsIContentPolicy::TYPE_INTERNAL_JSON_PRELOAD: 382 return nsIContentSecurityPolicy::CONNECT_SRC_DIRECTIVE; 383 384 case nsIContentPolicy::TYPE_OBJECT: 385 case nsIContentPolicy::TYPE_INTERNAL_EMBED: 386 case nsIContentPolicy::TYPE_INTERNAL_OBJECT: 387 return nsIContentSecurityPolicy::OBJECT_SRC_DIRECTIVE; 388 389 case nsIContentPolicy::TYPE_DTD: 390 case nsIContentPolicy::TYPE_OTHER: 391 case nsIContentPolicy::TYPE_SPECULATIVE: 392 case nsIContentPolicy::TYPE_INTERNAL_DTD: 393 case nsIContentPolicy::TYPE_INTERNAL_FORCE_ALLOWED_DTD: 394 return nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE; 395 396 // CSP does not apply to webrtc connections 397 case nsIContentPolicy::TYPE_PROXIED_WEBRTC_MEDIA: 398 // csp shold not block top level loads, e.g. in case 399 // of a redirect. 400 case nsIContentPolicy::TYPE_DOCUMENT: 401 // CSP can not block csp reports 402 case nsIContentPolicy::TYPE_CSP_REPORT: 403 return nsIContentSecurityPolicy::NO_DIRECTIVE; 404 405 case nsIContentPolicy::TYPE_SAVEAS_DOWNLOAD: 406 case nsIContentPolicy::TYPE_UA_FONT: 407 return nsIContentSecurityPolicy::NO_DIRECTIVE; 408 409 // Fall through to error for all other directives 410 case nsIContentPolicy::TYPE_INVALID: 411 case nsIContentPolicy::TYPE_END: 412 MOZ_ASSERT(false, "Can not map nsContentPolicyType to CSPDirective"); 413 // Do not add default: so that compilers can catch the missing case. 414 } 415 return nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE; 416 } 417 418 already_AddRefed<nsIContentSecurityPolicy> CSP_CreateFromHeader( 419 const nsAString& aHeaderValue, nsIURI* aSelfURI, 420 nsIPrincipal* aLoadingPrincipal, ErrorResult& aRv) { 421 RefPtr<nsCSPContext> csp = new nsCSPContext(); 422 // Hard code some default values until we have a use case where we can provide 423 // something else. 424 // When inheriting from this CSP, these values will be overwritten anyway. 425 aRv = csp->SetRequestContextWithPrincipal(aLoadingPrincipal, aSelfURI, 426 /* aReferrer */ ""_ns, 427 /* aInnerWindowId */ 0); 428 if (aRv.Failed()) { 429 return nullptr; 430 } 431 432 aRv = CSP_AppendCSPFromHeader(csp, aHeaderValue, /* aReportOnly */ false); 433 if (aRv.Failed()) { 434 return nullptr; 435 } 436 437 return csp.forget(); 438 } 439 440 nsCSPHostSrc* CSP_CreateHostSrcFromSelfURI(nsIURI* aSelfURI) { 441 // Create the host first 442 nsCString host; 443 aSelfURI->GetAsciiHost(host); 444 nsCSPHostSrc* hostsrc = new nsCSPHostSrc(NS_ConvertUTF8toUTF16(host)); 445 hostsrc->setGeneratedFromSelfKeyword(); 446 447 // Add the scheme. 448 nsCString scheme; 449 aSelfURI->GetScheme(scheme); 450 hostsrc->setScheme(NS_ConvertUTF8toUTF16(scheme)); 451 452 // An empty host (e.g. for data:) indicates it's effectively a unique origin. 453 // Please note that we still need to set the scheme on hostsrc (see above), 454 // because it's used for reporting. 455 if (host.EqualsLiteral("")) { 456 hostsrc->setIsUniqueOrigin(); 457 // no need to query the port in that case. 458 return hostsrc; 459 } 460 461 int32_t port; 462 aSelfURI->GetPort(&port); 463 // Only add port if it's not default port. 464 if (port > 0) { 465 nsAutoString portStr; 466 portStr.AppendInt(port); 467 hostsrc->setPort(portStr); 468 } 469 return hostsrc; 470 } 471 472 bool CSP_IsEmptyDirective(const nsAString& aValue, const nsAString& aDir) { 473 return (aDir.Length() == 0 && aValue.Length() == 0); 474 } 475 476 bool CSP_IsInvalidDirectiveValue(mozilla::Span<const char16_t> aValue) { 477 for (char16_t c : aValue) { 478 if (!(c >= 0x21 && c <= 0x7E)) { 479 return true; 480 } 481 } 482 return false; 483 } 484 485 bool CSP_IsDirective(const nsAString& aValue, CSPDirective aDir) { 486 return aValue.LowerCaseEqualsASCII(CSP_CSPDirectiveToString(aDir)); 487 } 488 489 bool CSP_IsKeyword(const nsAString& aValue, enum CSPKeyword aKey) { 490 return aValue.LowerCaseEqualsASCII(CSP_EnumToUTF8Keyword(aKey)); 491 } 492 493 bool CSP_IsQuotelessKeyword(const nsAString& aKey) { 494 nsString lowerKey; 495 ToLowerCase(aKey, lowerKey); 496 497 nsAutoString keyword; 498 for (uint32_t i = 0; i < CSP_LAST_KEYWORD_VALUE; i++) { 499 // skipping the leading ' and trimming the trailing ' 500 keyword.AssignASCII(gCSPUTF8Keywords[i] + 1); 501 keyword.Trim("'", false, true); 502 if (lowerKey.Equals(keyword)) { 503 return true; 504 } 505 } 506 return false; 507 } 508 509 /* 510 * Checks whether the current directive permits a specific 511 * scheme. This function is called from nsCSPSchemeSrc() and 512 * also nsCSPHostSrc. 513 * @param aEnforcementScheme 514 * The scheme that this directive allows 515 * @param aUri 516 * The uri of the subresource load. 517 * @param aReportOnly 518 * Whether the enforced policy is report only or not. 519 * @param aUpgradeInsecure 520 * Whether the policy makes use of the directive 521 * 'upgrade-insecure-requests'. 522 * @param aFromSelfURI 523 * Whether a scheme was generated from the keyword 'self' 524 * which then allows schemeless sources to match ws and wss. 525 */ 526 527 bool permitsScheme(const nsAString& aEnforcementScheme, nsIURI* aUri, 528 bool aReportOnly, bool aUpgradeInsecure, bool aFromSelfURI) { 529 nsAutoCString scheme; 530 nsresult rv = aUri->GetScheme(scheme); 531 NS_ENSURE_SUCCESS(rv, false); 532 533 // no scheme to enforce, let's allow the load (e.g. script-src *) 534 if (aEnforcementScheme.IsEmpty()) { 535 return true; 536 } 537 538 // if the scheme matches, all good - allow the load 539 if (aEnforcementScheme.EqualsASCII(scheme.get())) { 540 return true; 541 } 542 543 // allow scheme-less sources where the protected resource is http 544 // and the load is https, see: 545 // http://www.w3.org/TR/CSP2/#match-source-expression 546 if (aEnforcementScheme.EqualsASCII("http")) { 547 if (scheme.EqualsASCII("https")) { 548 return true; 549 } 550 if ((scheme.EqualsASCII("ws") || scheme.EqualsASCII("wss")) && 551 aFromSelfURI) { 552 return true; 553 } 554 } 555 if (aEnforcementScheme.EqualsASCII("https")) { 556 if (scheme.EqualsLiteral("wss") && aFromSelfURI) { 557 return true; 558 } 559 } 560 if (aEnforcementScheme.EqualsASCII("ws") && scheme.EqualsASCII("wss")) { 561 return true; 562 } 563 564 // Allow the load when enforcing upgrade-insecure-requests with the 565 // promise the request gets upgraded from http to https and ws to wss. 566 // See nsHttpChannel::Connect() and also WebSocket.cpp. Please note, 567 // the report only policies should not allow the load and report 568 // the error back to the page. 569 return ( 570 (aUpgradeInsecure && !aReportOnly) && 571 ((scheme.EqualsASCII("http") && 572 aEnforcementScheme.EqualsASCII("https")) || 573 (scheme.EqualsASCII("ws") && aEnforcementScheme.EqualsASCII("wss")))); 574 } 575 576 /* 577 * A helper function for appending a CSP header to an existing CSP 578 * policy. 579 * 580 * @param aCsp the CSP policy 581 * @param aHeaderValue the header 582 * @param aReportOnly is this a report-only header? 583 */ 584 585 nsresult CSP_AppendCSPFromHeader(nsIContentSecurityPolicy* aCsp, 586 const nsAString& aHeaderValue, 587 bool aReportOnly) { 588 NS_ENSURE_ARG(aCsp); 589 590 // Need to tokenize the header value since multiple headers could be 591 // concatenated into one comma-separated list of policies. 592 // See RFC2616 section 4.2 (last paragraph) 593 nsresult rv = NS_OK; 594 for (const nsAString& policy : 595 nsCharSeparatedTokenizer(aHeaderValue, ',').ToRange()) { 596 rv = aCsp->AppendPolicy(policy, aReportOnly, false); 597 NS_ENSURE_SUCCESS(rv, rv); 598 { 599 CSPUTILSLOG(("CSP refined with policy: \"%s\"", 600 NS_ConvertUTF16toUTF8(policy).get())); 601 } 602 } 603 return NS_OK; 604 } 605 606 /* ===== nsCSPSrc ============================ */ 607 608 nsCSPBaseSrc::nsCSPBaseSrc() {} 609 610 nsCSPBaseSrc::~nsCSPBaseSrc() = default; 611 612 // ::permits is only called for external load requests, therefore: 613 // nsCSPKeywordSrc and nsCSPHashSource fall back to this base class 614 // implementation which will never allow the load. 615 bool nsCSPBaseSrc::permits(nsIURI* aUri, bool aWasRedirected, bool aReportOnly, 616 bool aUpgradeInsecure) const { 617 if (CSPUTILSLOGENABLED()) { 618 CSPUTILSLOG( 619 ("nsCSPBaseSrc::permits, aUri: %s", aUri->GetSpecOrDefault().get())); 620 } 621 return false; 622 } 623 624 // ::allows is only called for inlined loads, therefore: 625 // nsCSPSchemeSrc, nsCSPHostSrc fall back 626 // to this base class implementation which will never allow the load. 627 bool nsCSPBaseSrc::allows(enum CSPKeyword aKeyword, 628 const nsAString& aHashOrNonce) const { 629 CSPUTILSLOG(("nsCSPBaseSrc::allows, aKeyWord: %s, a HashOrNonce: %s", 630 aKeyword == CSP_HASH ? "hash" : CSP_EnumToUTF8Keyword(aKeyword), 631 NS_ConvertUTF16toUTF8(aHashOrNonce).get())); 632 return false; 633 } 634 635 /* ====== nsCSPSchemeSrc ===================== */ 636 637 nsCSPSchemeSrc::nsCSPSchemeSrc(const nsAString& aScheme) : mScheme(aScheme) { 638 ToLowerCase(mScheme); 639 } 640 641 nsCSPSchemeSrc::~nsCSPSchemeSrc() = default; 642 643 bool nsCSPSchemeSrc::permits(nsIURI* aUri, bool aWasRedirected, 644 bool aReportOnly, bool aUpgradeInsecure) const { 645 if (CSPUTILSLOGENABLED()) { 646 CSPUTILSLOG( 647 ("nsCSPSchemeSrc::permits, aUri: %s", aUri->GetSpecOrDefault().get())); 648 } 649 MOZ_ASSERT((!mScheme.EqualsASCII("")), "scheme can not be the empty string"); 650 return permitsScheme(mScheme, aUri, aReportOnly, aUpgradeInsecure, false); 651 } 652 653 bool nsCSPSchemeSrc::visit(nsCSPSrcVisitor* aVisitor) const { 654 return aVisitor->visitSchemeSrc(*this); 655 } 656 657 void nsCSPSchemeSrc::toString(nsAString& outStr) const { 658 outStr.Append(mScheme); 659 outStr.AppendLiteral(":"); 660 } 661 662 /* ===== nsCSPHostSrc ======================== */ 663 664 nsCSPHostSrc::nsCSPHostSrc(const nsAString& aHost) 665 : mHost(aHost), 666 mGeneratedFromSelfKeyword(false), 667 mIsUniqueOrigin(false), 668 mWithinFrameAncstorsDir(false) { 669 ToLowerCase(mHost); 670 } 671 672 nsCSPHostSrc::~nsCSPHostSrc() = default; 673 674 /* 675 * Checks whether the current directive permits a specific port. 676 * @param aEnforcementScheme 677 * The scheme that this directive allows 678 * (used to query the default port for that scheme) 679 * @param aEnforcementPort 680 * The port that this directive allows 681 * @param aResourceURI 682 * The uri of the subresource load 683 */ 684 bool permitsPort(const nsAString& aEnforcementScheme, 685 const nsAString& aEnforcementPort, nsIURI* aResourceURI) { 686 // If enforcement port is the wildcard, don't block the load. 687 if (aEnforcementPort.EqualsASCII("*")) { 688 return true; 689 } 690 691 int32_t resourcePort; 692 nsresult rv = aResourceURI->GetPort(&resourcePort); 693 if (NS_FAILED(rv) && aEnforcementPort.IsEmpty()) { 694 // If we cannot get a Port (e.g. because of an Custom Protocol handler) 695 // We need to check if a default port is associated with the Scheme 696 if (aEnforcementScheme.IsEmpty()) { 697 return false; 698 } 699 int defaultPortforScheme = 700 NS_GetDefaultPort(NS_ConvertUTF16toUTF8(aEnforcementScheme).get()); 701 702 // If there is no default port associated with the Scheme ( 703 // defaultPortforScheme == -1) or it is an externally handled protocol ( 704 // defaultPortforScheme == 0 ) and the csp does not enforce a port - we can 705 // allow not having a port 706 return (defaultPortforScheme == -1 || defaultPortforScheme == -0); 707 } 708 // Avoid unnecessary string creation/manipulation and don't block the 709 // load if the resource to be loaded uses the default port for that 710 // scheme and there is no port to be enforced. 711 // Note, this optimization relies on scheme checks within permitsScheme(). 712 if (resourcePort == DEFAULT_PORT && aEnforcementPort.IsEmpty()) { 713 return true; 714 } 715 716 // By now we know at that either the resourcePort does not use the default 717 // port or there is a port restriction to be enforced. A port value of -1 718 // corresponds to the protocol's default port (eg. -1 implies port 80 for 719 // http URIs), in such a case we have to query the default port of the 720 // resource to be loaded. 721 if (resourcePort == DEFAULT_PORT) { 722 nsAutoCString resourceScheme; 723 rv = aResourceURI->GetScheme(resourceScheme); 724 NS_ENSURE_SUCCESS(rv, false); 725 resourcePort = NS_GetDefaultPort(resourceScheme.get()); 726 } 727 728 // If there is a port to be enforced and the ports match, then 729 // don't block the load. 730 nsString resourcePortStr; 731 resourcePortStr.AppendInt(resourcePort); 732 if (aEnforcementPort.Equals(resourcePortStr)) { 733 return true; 734 } 735 736 // If there is no port to be enforced, query the default port for the load. 737 nsString enforcementPort(aEnforcementPort); 738 if (enforcementPort.IsEmpty()) { 739 // For scheme less sources, our parser always generates a scheme 740 // which is the scheme of the protected resource. 741 MOZ_ASSERT(!aEnforcementScheme.IsEmpty(), 742 "need a scheme to query default port"); 743 int32_t defaultEnforcementPort = 744 NS_GetDefaultPort(NS_ConvertUTF16toUTF8(aEnforcementScheme).get()); 745 enforcementPort.Truncate(); 746 enforcementPort.AppendInt(defaultEnforcementPort); 747 } 748 749 // If default ports match, don't block the load 750 if (enforcementPort.Equals(resourcePortStr)) { 751 return true; 752 } 753 754 // Additional port matching where the regular URL matching algorithm 755 // treats insecure ports as matching their secure variants. 756 // default port for http is :80 757 // default port for https is :443 758 if (enforcementPort.EqualsLiteral("80") && 759 resourcePortStr.EqualsLiteral("443")) { 760 return true; 761 } 762 763 // ports do not match, block the load. 764 return false; 765 } 766 767 bool nsCSPHostSrc::permits(nsIURI* aUri, bool aWasRedirected, bool aReportOnly, 768 bool aUpgradeInsecure) const { 769 if (CSPUTILSLOGENABLED()) { 770 CSPUTILSLOG( 771 ("nsCSPHostSrc::permits, aUri: %s", aUri->GetSpecOrDefault().get())); 772 } 773 774 if (mIsUniqueOrigin) { 775 return false; 776 } 777 778 // we are following the enforcement rules from the spec, see: 779 // http://www.w3.org/TR/CSP11/#match-source-expression 780 781 // 4.3) scheme matching: Check if the scheme matches. 782 if (!permitsScheme(mScheme, aUri, aReportOnly, aUpgradeInsecure, 783 mGeneratedFromSelfKeyword)) { 784 return false; 785 } 786 787 // The host in nsCSpHostSrc should never be empty. In case we are enforcing 788 // just a specific scheme, the parser should generate a nsCSPSchemeSource. 789 NS_ASSERTION((!mHost.IsEmpty()), "host can not be the empty string"); 790 791 // Before we can check if the host matches, we have to 792 // extract the host part from aUri. 793 nsAutoCString uriHost; 794 nsresult rv = aUri->GetAsciiHost(uriHost); 795 NS_ENSURE_SUCCESS(rv, false); 796 797 nsString decodedUriHost; 798 CSP_PercentDecodeStr(NS_ConvertUTF8toUTF16(uriHost), decodedUriHost); 799 800 // 2) host matching: Enforce a single * 801 if (mHost.EqualsASCII("*")) { 802 // The single ASTERISK character (*) does not match a URI's scheme of a type 803 // designating a globally unique identifier (such as blob:, data:, or 804 // filesystem:) At the moment firefox does not support filesystem; but for 805 // future compatibility we support it in CSP according to the spec, 806 // see: 4.2.2 Matching Source Expressions Note, that allowlisting any of 807 // these schemes would call nsCSPSchemeSrc::permits(). 808 if (aUri->SchemeIs("blob") || aUri->SchemeIs("data") || 809 aUri->SchemeIs("filesystem")) { 810 return false; 811 } 812 813 // If no scheme is present there also wont be a port and folder to check 814 // which means we can return early 815 if (mScheme.IsEmpty()) { 816 return true; 817 } 818 } 819 // 4.5) host matching: Check if the allowed host starts with a wilcard. 820 else if (mHost.First() == '*') { 821 NS_ASSERTION( 822 mHost[1] == '.', 823 "Second character needs to be '.' whenever host starts with '*'"); 824 825 // Eliminate leading "*", but keeping the FULL STOP (.) thereafter before 826 // checking if the remaining characters match 827 nsString wildCardHost = mHost; 828 wildCardHost = Substring(wildCardHost, 1, wildCardHost.Length() - 1); 829 if (!StringEndsWith(decodedUriHost, wildCardHost)) { 830 return false; 831 } 832 } 833 // 4.6) host matching: Check if hosts match. 834 else if (!mHost.Equals(decodedUriHost)) { 835 return false; 836 } 837 838 // Port matching: Check if the ports match. 839 if (!permitsPort(mScheme, mPort, aUri)) { 840 return false; 841 } 842 843 // 4.9) Path matching: If there is a path, we have to enforce 844 // path-level matching, unless the channel got redirected, see: 845 // http://www.w3.org/TR/CSP11/#source-list-paths-and-redirects 846 if (!aWasRedirected && !mPath.IsEmpty()) { 847 // converting aUri into nsIURL so we can strip query and ref 848 // example.com/test#foo -> example.com/test 849 // example.com/test?val=foo -> example.com/test 850 nsCOMPtr<nsIURL> url = do_QueryInterface(aUri); 851 if (!url) { 852 NS_ASSERTION(false, "can't QI into nsIURI"); 853 return false; 854 } 855 nsAutoCString uriPath; 856 rv = url->GetFilePath(uriPath); 857 NS_ENSURE_SUCCESS(rv, false); 858 859 if (mWithinFrameAncstorsDir) { 860 // no path matching for frame-ancestors to not leak any path information. 861 return true; 862 } 863 864 nsString decodedUriPath; 865 CSP_PercentDecodeStr(NS_ConvertUTF8toUTF16(uriPath), decodedUriPath); 866 867 // check if the last character of mPath is '/'; if so 868 // we just have to check loading resource is within 869 // the allowed path. 870 if (mPath.Last() == '/') { 871 if (!StringBeginsWith(decodedUriPath, mPath)) { 872 return false; 873 } 874 } 875 // otherwise mPath refers to a specific file, and we have to 876 // check if the loading resource matches the file. 877 else { 878 if (!mPath.Equals(decodedUriPath)) { 879 return false; 880 } 881 } 882 } 883 884 // At the end: scheme, host, port and path match -> allow the load. 885 return true; 886 } 887 888 bool nsCSPHostSrc::visit(nsCSPSrcVisitor* aVisitor) const { 889 return aVisitor->visitHostSrc(*this); 890 } 891 892 void nsCSPHostSrc::toString(nsAString& outStr) const { 893 if (mGeneratedFromSelfKeyword) { 894 outStr.AppendLiteral("'self'"); 895 return; 896 } 897 898 // If mHost is a single "*", we append the wildcard and return. 899 if (mHost.EqualsASCII("*") && mScheme.IsEmpty() && mPort.IsEmpty()) { 900 outStr.Append(mHost); 901 return; 902 } 903 904 // append scheme 905 outStr.Append(mScheme); 906 907 // append host 908 outStr.AppendLiteral("://"); 909 outStr.Append(mHost); 910 911 // append port 912 if (!mPort.IsEmpty()) { 913 outStr.AppendLiteral(":"); 914 outStr.Append(mPort); 915 } 916 917 // append path 918 outStr.Append(mPath); 919 } 920 921 void nsCSPHostSrc::setScheme(const nsAString& aScheme) { 922 mScheme = aScheme; 923 ToLowerCase(mScheme); 924 } 925 926 void nsCSPHostSrc::setPort(const nsAString& aPort) { mPort = aPort; } 927 928 void nsCSPHostSrc::appendPath(const nsAString& aPath) { mPath.Append(aPath); } 929 930 /* ===== nsCSPKeywordSrc ===================== */ 931 932 nsCSPKeywordSrc::nsCSPKeywordSrc(enum CSPKeyword aKeyword) 933 : mKeyword(aKeyword) { 934 NS_ASSERTION((aKeyword != CSP_SELF), 935 "'self' should have been replaced in the parser"); 936 } 937 938 nsCSPKeywordSrc::~nsCSPKeywordSrc() = default; 939 940 bool nsCSPKeywordSrc::allows(enum CSPKeyword aKeyword, 941 const nsAString& aHashOrNonce) const { 942 CSPUTILSLOG(("nsCSPKeywordSrc::allows, aKeyWord: %s, aHashOrNonce: %s", 943 CSP_EnumToUTF8Keyword(aKeyword), 944 NS_ConvertUTF16toUTF8(aHashOrNonce).get())); 945 return mKeyword == aKeyword; 946 } 947 948 bool nsCSPKeywordSrc::visit(nsCSPSrcVisitor* aVisitor) const { 949 return aVisitor->visitKeywordSrc(*this); 950 } 951 952 void nsCSPKeywordSrc::toString(nsAString& outStr) const { 953 outStr.Append(CSP_EnumToUTF16Keyword(mKeyword)); 954 } 955 956 /* ===== nsCSPNonceSrc ==================== */ 957 958 nsCSPNonceSrc::nsCSPNonceSrc(const nsAString& aNonce) : mNonce(aNonce) {} 959 960 nsCSPNonceSrc::~nsCSPNonceSrc() = default; 961 962 bool nsCSPNonceSrc::allows(enum CSPKeyword aKeyword, 963 const nsAString& aHashOrNonce) const { 964 CSPUTILSLOG(("nsCSPNonceSrc::allows, aKeyWord: %s, a HashOrNonce: %s", 965 CSP_EnumToUTF8Keyword(aKeyword), 966 NS_ConvertUTF16toUTF8(aHashOrNonce).get())); 967 968 if (aKeyword != CSP_NONCE) { 969 return false; 970 } 971 // nonces can not be invalidated by strict-dynamic 972 return mNonce.Equals(aHashOrNonce); 973 } 974 975 bool nsCSPNonceSrc::visit(nsCSPSrcVisitor* aVisitor) const { 976 return aVisitor->visitNonceSrc(*this); 977 } 978 979 void nsCSPNonceSrc::toString(nsAString& outStr) const { 980 outStr.Append(CSP_EnumToUTF16Keyword(CSP_NONCE)); 981 outStr.Append(mNonce); 982 outStr.AppendLiteral("'"); 983 } 984 985 /* ===== nsCSPHashSrc ===================== */ 986 987 nsCSPHashSrc::nsCSPHashSrc(const nsAString& aAlgo, const nsAString& aHash) 988 : mAlgorithm(aAlgo), mHash(aHash) { 989 // Only the algo should be rewritten to lowercase, the hash must remain the 990 // same. 991 ToLowerCase(mAlgorithm); 992 // Normalize the base64url encoding to base64 encoding: 993 char16_t* cur = mHash.BeginWriting(); 994 char16_t* end = mHash.EndWriting(); 995 996 for (; cur < end; ++cur) { 997 if (char16_t('-') == *cur) { 998 *cur = char16_t('+'); 999 } 1000 if (char16_t('_') == *cur) { 1001 *cur = char16_t('/'); 1002 } 1003 } 1004 } 1005 1006 nsCSPHashSrc::~nsCSPHashSrc() = default; 1007 1008 bool nsCSPHashSrc::allows(enum CSPKeyword aKeyword, 1009 const nsAString& aHashOrNonce) const { 1010 CSPUTILSLOG(("nsCSPHashSrc::allows, aKeyWord: %s, a HashOrNonce: %s", 1011 CSP_EnumToUTF8Keyword(aKeyword), 1012 NS_ConvertUTF16toUTF8(aHashOrNonce).get())); 1013 1014 if (aKeyword != CSP_HASH) { 1015 return false; 1016 } 1017 1018 // hashes can not be invalidated by strict-dynamic 1019 1020 // Convert aHashOrNonce to UTF-8 1021 NS_ConvertUTF16toUTF8 utf8_hash(aHashOrNonce); 1022 1023 nsCOMPtr<nsICryptoHash> hasher; 1024 nsresult rv = NS_NewCryptoHash(NS_ConvertUTF16toUTF8(mAlgorithm), 1025 getter_AddRefs(hasher)); 1026 NS_ENSURE_SUCCESS(rv, false); 1027 1028 rv = hasher->Update((uint8_t*)utf8_hash.get(), utf8_hash.Length()); 1029 NS_ENSURE_SUCCESS(rv, false); 1030 1031 nsAutoCString hash; 1032 rv = hasher->Finish(true, hash); 1033 NS_ENSURE_SUCCESS(rv, false); 1034 1035 return NS_ConvertUTF16toUTF8(mHash).Equals(hash); 1036 } 1037 1038 bool nsCSPHashSrc::visit(nsCSPSrcVisitor* aVisitor) const { 1039 return aVisitor->visitHashSrc(*this); 1040 } 1041 1042 void nsCSPHashSrc::toString(nsAString& outStr) const { 1043 outStr.AppendLiteral("'"); 1044 outStr.Append(mAlgorithm); 1045 outStr.AppendLiteral("-"); 1046 outStr.Append(mHash); 1047 outStr.AppendLiteral("'"); 1048 } 1049 1050 /* ===== nsCSPReportURI ===================== */ 1051 1052 nsCSPReportURI::nsCSPReportURI(nsIURI* aURI) : mReportURI(aURI) {} 1053 1054 nsCSPReportURI::~nsCSPReportURI() = default; 1055 1056 bool nsCSPReportURI::visit(nsCSPSrcVisitor* aVisitor) const { return false; } 1057 1058 void nsCSPReportURI::toString(nsAString& outStr) const { 1059 nsAutoCString spec; 1060 nsresult rv = mReportURI->GetSpec(spec); 1061 if (NS_FAILED(rv)) { 1062 return; 1063 } 1064 outStr.AppendASCII(spec.get()); 1065 } 1066 1067 /* ===== nsCSPReportGroup ===================== */ 1068 1069 nsCSPGroup::nsCSPGroup(const nsAString& aGroup) : mGroup(aGroup) {} 1070 1071 nsCSPGroup::~nsCSPGroup() = default; 1072 1073 bool nsCSPGroup::visit(nsCSPSrcVisitor* aVisitor) const { return false; } 1074 1075 void nsCSPGroup::toString(nsAString& aOutStr) const { aOutStr.Append(mGroup); } 1076 1077 /* ===== nsCSPSandboxFlags ===================== */ 1078 1079 nsCSPSandboxFlags::nsCSPSandboxFlags(const nsAString& aFlags) : mFlags(aFlags) { 1080 ToLowerCase(mFlags); 1081 } 1082 1083 nsCSPSandboxFlags::~nsCSPSandboxFlags() = default; 1084 1085 bool nsCSPSandboxFlags::visit(nsCSPSrcVisitor* aVisitor) const { return false; } 1086 1087 void nsCSPSandboxFlags::toString(nsAString& outStr) const { 1088 outStr.Append(mFlags); 1089 } 1090 1091 /* ===== nsCSPRequireTrustedTypesForDirectiveValue ===================== */ 1092 1093 nsCSPRequireTrustedTypesForDirectiveValue:: 1094 nsCSPRequireTrustedTypesForDirectiveValue(const nsAString& aValue) 1095 : mValue{aValue} {} 1096 1097 bool nsCSPRequireTrustedTypesForDirectiveValue::visit( 1098 nsCSPSrcVisitor* aVisitor) const { 1099 MOZ_ASSERT_UNREACHABLE( 1100 "This method should only be called for other overloads of this method."); 1101 return false; 1102 } 1103 1104 void nsCSPRequireTrustedTypesForDirectiveValue::toString( 1105 nsAString& aOutStr) const { 1106 aOutStr.Append(mValue); 1107 } 1108 1109 /* =============== nsCSPTrustedTypesDirectivePolicyName =============== */ 1110 1111 nsCSPTrustedTypesDirectivePolicyName::nsCSPTrustedTypesDirectivePolicyName( 1112 const nsAString& aName) 1113 : mName{aName} {} 1114 1115 bool nsCSPTrustedTypesDirectivePolicyName::visit( 1116 nsCSPSrcVisitor* aVisitor) const { 1117 MOZ_ASSERT_UNREACHABLE( 1118 "Should only be called for other overloads of this method."); 1119 return false; 1120 } 1121 1122 void nsCSPTrustedTypesDirectivePolicyName::toString(nsAString& aOutStr) const { 1123 aOutStr.Append(mName); 1124 } 1125 1126 /* =============== nsCSPTrustedTypesDirectiveInvalidToken =============== */ 1127 1128 nsCSPTrustedTypesDirectiveInvalidToken::nsCSPTrustedTypesDirectiveInvalidToken( 1129 const nsAString& aInvalidToken) 1130 : mInvalidToken{aInvalidToken} {} 1131 1132 bool nsCSPTrustedTypesDirectiveInvalidToken::visit( 1133 nsCSPSrcVisitor* aVisitor) const { 1134 MOZ_ASSERT_UNREACHABLE( 1135 "Should only be called for other overloads of this method."); 1136 return false; 1137 } 1138 1139 void nsCSPTrustedTypesDirectiveInvalidToken::toString( 1140 nsAString& aOutStr) const { 1141 aOutStr.Append(mInvalidToken); 1142 } 1143 1144 /* ===== nsCSPDirective ====================== */ 1145 1146 nsCSPDirective::nsCSPDirective(CSPDirective aDirective) { 1147 mDirective = aDirective; 1148 } 1149 1150 nsCSPDirective::~nsCSPDirective() { 1151 for (uint32_t i = 0; i < mSrcs.Length(); i++) { 1152 delete mSrcs[i]; 1153 } 1154 } 1155 1156 // https://w3c.github.io/webappsec-csp/#match-nonce-to-source-list 1157 static bool DoesNonceMatchSourceList(nsILoadInfo* aLoadInfo, 1158 const nsTArray<nsCSPBaseSrc*>& aSrcs) { 1159 // Step 1. Assert: source list is not null. (implicit) 1160 1161 // Note: For code-reuse we do "request’s cryptographic nonce metadata" here 1162 // instead of the caller. 1163 nsAutoString nonce; 1164 MOZ_ALWAYS_SUCCEEDS(aLoadInfo->GetCspNonce(nonce)); 1165 1166 // Step 2. If nonce is the empty string, return "Does Not Match". 1167 if (nonce.IsEmpty()) { 1168 return false; 1169 } 1170 1171 // Step 3. For each expression of source list: 1172 for (nsCSPBaseSrc* src : aSrcs) { 1173 // Step 3.1. If expression matches the nonce-source grammar, and nonce is 1174 // identical to expression’s base64-value part, return "Matches". 1175 if (src->isNonce()) { 1176 nsAutoString srcNonce; 1177 static_cast<nsCSPNonceSrc*>(src)->getNonce(srcNonce); 1178 if (srcNonce == nonce) { 1179 return true; 1180 } 1181 } 1182 } 1183 1184 // Step 4. Return "Does Not Match". 1185 return false; 1186 } 1187 1188 // https://www.w3.org/TR/SRI/#parse-metadata 1189 // This function is similar to SRICheck::IntegrityMetadata, but also keeps 1190 // SRI metadata with weaker hashes. 1191 // CSP treats "no metadata" and empty results the same way. 1192 static nsTArray<SRIMetadata> ParseSRIMetadata(const nsAString& aMetadata) { 1193 // Step 1. Let result be the empty set. 1194 // Step 2. Let empty be equal to true. 1195 nsTArray<SRIMetadata> result; 1196 1197 NS_ConvertUTF16toUTF8 metadataList(aMetadata); 1198 nsAutoCString token; 1199 1200 // Step 3. For each token returned by splitting metadata on spaces: 1201 nsCWhitespaceTokenizer tokenizer(metadataList); 1202 while (tokenizer.hasMoreTokens()) { 1203 token = tokenizer.nextToken(); 1204 // Step 3.1. Set empty to false. 1205 // Step 3.3. Parse token per the grammar in integrity metadata. 1206 SRIMetadata metadata(token); 1207 // Step 3.2. If token is not a valid metadata, skip the remaining steps, and 1208 // proceed to the next token. 1209 if (metadata.IsMalformed()) { 1210 continue; 1211 } 1212 1213 // Step 3.4. Let algorithm be the alg component of token. 1214 // Step 3.5. If algorithm is a hash function recognized by the user agent, 1215 // add the 1216 // parsed token to result. 1217 if (metadata.IsAlgorithmSupported()) { 1218 result.AppendElement(metadata); 1219 } 1220 } 1221 1222 // Step 4. Return no metadata if empty is true, otherwise return result. 1223 return result; 1224 } 1225 1226 bool nsCSPDirective::permits(CSPDirective aDirective, nsILoadInfo* aLoadInfo, 1227 nsIURI* aUri, bool aWasRedirected, 1228 bool aReportOnly, bool aUpgradeInsecure) const { 1229 MOZ_ASSERT(equals(aDirective) || isDefaultDirective()); 1230 1231 if (CSPUTILSLOGENABLED()) { 1232 CSPUTILSLOG(("nsCSPDirective::permits, aUri: %s, aDirective: %s", 1233 aUri->GetSpecOrDefault().get(), 1234 CSP_CSPDirectiveToString(aDirective))); 1235 } 1236 1237 if (aLoadInfo) { 1238 // https://w3c.github.io/webappsec-csp/#style-src-elem-pre-request 1239 if (aDirective == CSPDirective::STYLE_SRC_ELEM_DIRECTIVE) { 1240 // Step 3. If the result of executing §6.7.2.3 Does nonce match source 1241 // list? on request’s cryptographic nonce metadata and this directive’s 1242 // value is "Matches", return "Allowed". 1243 if (DoesNonceMatchSourceList(aLoadInfo, mSrcs)) { 1244 CSPUTILSLOG((" Allowed by matching nonce (style)")); 1245 return true; 1246 } 1247 } 1248 1249 // https://w3c.github.io/webappsec-csp/#script-pre-request 1250 // Step 1. If request’s destination is script-like: 1251 else if (aDirective == CSPDirective::SCRIPT_SRC_ELEM_DIRECTIVE || 1252 aDirective == CSPDirective::WORKER_SRC_DIRECTIVE) { 1253 // Step 1.1. If the result of executing §6.7.2.3 Does nonce match source 1254 // list? on request’s cryptographic nonce metadata and this directive’s 1255 // value is "Matches", return "Allowed". 1256 if (DoesNonceMatchSourceList(aLoadInfo, mSrcs)) { 1257 CSPUTILSLOG((" Allowed by matching nonce (script-like)")); 1258 return true; 1259 } 1260 1261 // Step 1.2. Let integrity expressions be the set of source expressions in 1262 // directive’s value that match the hash-source grammar. 1263 nsTArray<nsCSPHashSrc*> integrityExpressions; 1264 bool hasStrictDynamicKeyword = 1265 false; // Optimization to reduce number of iterations. 1266 for (uint32_t i = 0; i < mSrcs.Length(); i++) { 1267 if (mSrcs[i]->isHash()) { 1268 integrityExpressions.AppendElement( 1269 static_cast<nsCSPHashSrc*>(mSrcs[i])); 1270 } else if (mSrcs[i]->isKeyword(CSP_STRICT_DYNAMIC)) { 1271 hasStrictDynamicKeyword = true; 1272 } 1273 } 1274 1275 // Step 1.3. If integrity expressions is not empty: 1276 if (!integrityExpressions.IsEmpty()) { 1277 // Step 1.3.1. Let integrity sources be the result of executing the 1278 // algorithm defined in [SRI 3.3.3 Parse metadata] on request’s 1279 // integrity metadata. 1280 nsAutoString integrityMetadata; 1281 aLoadInfo->GetIntegrityMetadata(integrityMetadata); 1282 1283 nsTArray<SRIMetadata> integritySources = 1284 ParseSRIMetadata(integrityMetadata); 1285 1286 // Step 1.3.2. If integrity sources is "no metadata" or an empty set, 1287 // skip the remaining substeps. 1288 if (!integritySources.IsEmpty()) { 1289 // Step 1.3.3. Let bypass due to integrity match be true. 1290 bool bypass = true; 1291 1292 nsAutoCString sourceAlgorithmUTF8; 1293 nsAutoCString sourceHashUTF8; 1294 nsAutoString sourceAlgorithm; 1295 nsAutoString sourceHash; 1296 nsAutoString algorithm; 1297 nsAutoString hash; 1298 1299 // Step 1.3.4. For each source of integrity sources: 1300 for (const SRIMetadata& source : integritySources) { 1301 source.GetAlgorithm(&sourceAlgorithmUTF8); 1302 sourceAlgorithm = NS_ConvertUTF8toUTF16(sourceAlgorithmUTF8); 1303 source.GetHash(0, &sourceHashUTF8); 1304 sourceHash = NS_ConvertUTF8toUTF16(sourceHashUTF8); 1305 1306 // Step 1.3.4.1 If directive’s value does not contain a source 1307 // expression whose hash-algorithm is an ASCII case-insensitive 1308 // match for source’s hash-algorithm, and whose base64-value is 1309 // identical to source’s base64-value, then set bypass due to 1310 // integrity match to false. 1311 bool found = false; 1312 for (const nsCSPHashSrc* hashSrc : integrityExpressions) { 1313 hashSrc->getAlgorithm(algorithm); 1314 hashSrc->getHash(hash); 1315 1316 // The nsCSPHashSrc constructor lowercases algorithm, so this 1317 // is case-insensitive. 1318 if (sourceAlgorithm == algorithm && sourceHash == hash) { 1319 found = true; 1320 break; 1321 } 1322 } 1323 1324 if (!found) { 1325 bypass = false; 1326 break; 1327 } 1328 } 1329 1330 // Step 1.3.5. If bypass due to integrity match is true, return 1331 // "Allowed". 1332 if (bypass) { 1333 CSPUTILSLOG( 1334 (" Allowed by matching integrity metadata (script-like)")); 1335 return true; 1336 } 1337 } 1338 } 1339 1340 // Step 1.4. If directive’s value contains a source expression that is an 1341 // ASCII case-insensitive match for the "'strict-dynamic'" keyword-source: 1342 1343 // XXX I don't think we should apply strict-dynamic to XSLT. 1344 if (hasStrictDynamicKeyword && aLoadInfo->InternalContentPolicyType() != 1345 nsIContentPolicy::TYPE_XSLT) { 1346 // Step 1.4.1 If the request’s parser metadata is "parser-inserted", 1347 // return "Blocked". Otherwise, return "Allowed". 1348 if (aLoadInfo->GetParserCreatedScript()) { 1349 CSPUTILSLOG( 1350 (" Blocked by 'strict-dynamic' because parser-inserted")); 1351 return false; 1352 } 1353 1354 CSPUTILSLOG( 1355 (" Allowed by 'strict-dynamic' because not-parser-inserted")); 1356 return true; 1357 } 1358 } 1359 } 1360 1361 for (uint32_t i = 0; i < mSrcs.Length(); i++) { 1362 if (mSrcs[i]->permits(aUri, aWasRedirected, aReportOnly, 1363 aUpgradeInsecure)) { 1364 return true; 1365 } 1366 } 1367 return false; 1368 } 1369 1370 bool nsCSPDirective::allows(enum CSPKeyword aKeyword, 1371 const nsAString& aHashOrNonce) const { 1372 CSPUTILSLOG(("nsCSPDirective::allows, aKeyWord: %s, aHashOrNonce: %s", 1373 CSP_EnumToUTF8Keyword(aKeyword), 1374 NS_ConvertUTF16toUTF8(aHashOrNonce).get())); 1375 1376 for (uint32_t i = 0; i < mSrcs.Length(); i++) { 1377 if (mSrcs[i]->allows(aKeyword, aHashOrNonce)) { 1378 return true; 1379 } 1380 } 1381 return false; 1382 } 1383 1384 // https://w3c.github.io/webappsec-csp/#allow-all-inline 1385 bool nsCSPDirective::allowsAllInlineBehavior(CSPDirective aDir) const { 1386 // Step 1. Let allow all inline be false. 1387 bool allowAll = false; 1388 1389 // Step 2. For each expression of list: 1390 for (nsCSPBaseSrc* src : mSrcs) { 1391 // Step 2.1. If expression matches the nonce-source or hash-source grammar, 1392 // return "Does Not Allow". 1393 if (src->isNonce() || src->isHash()) { 1394 return false; 1395 } 1396 1397 // Step 2.2. If type is "script", "script attribute" or "navigation" and 1398 // expression matches the keyword-source "'strict-dynamic'", return "Does 1399 // Not Allow". 1400 if ((aDir == nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE || 1401 aDir == nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE) && 1402 src->isKeyword(CSP_STRICT_DYNAMIC)) { 1403 return false; 1404 } 1405 1406 // Step 2.3. If expression is an ASCII case-insensitive match for the 1407 // keyword-source "'unsafe-inline'", set allow all inline to true. 1408 if (src->isKeyword(CSP_UNSAFE_INLINE)) { 1409 allowAll = true; 1410 } 1411 } 1412 1413 // Step 3. If allow all inline is true, return "Allows". Otherwise, return 1414 // "Does Not Allow". 1415 return allowAll; 1416 } 1417 1418 static constexpr auto kWildcard = u"*"_ns; 1419 1420 bool nsCSPDirective::ShouldCreateViolationForNewTrustedTypesPolicy( 1421 const nsAString& aPolicyName, 1422 const nsTArray<nsString>& aCreatedPolicyNames) const { 1423 MOZ_ASSERT(mDirective == nsIContentSecurityPolicy::TRUSTED_TYPES_DIRECTIVE); 1424 1425 if (mDirective == nsIContentSecurityPolicy::TRUSTED_TYPES_DIRECTIVE) { 1426 if (allows(CSP_NONE, EmptyString())) { 1427 // Step 2.4: if directive’s value only contains a tt-keyword which is a 1428 // match for a value 'none', set createViolation to true. 1429 // `nsCSPParser` ignores the 'none' keyword if other keywords or policy 1430 // names are present. Hence no additional checks required here. 1431 return true; 1432 } 1433 1434 if (aCreatedPolicyNames.Contains(aPolicyName) && 1435 !allows(CSP_ALLOW_DUPLICATES, EmptyString())) { 1436 // Step 2.5: if createdPolicyNames contains policyName and directive’s 1437 // value does not contain a tt-keyword which is a match for a value 1438 // 'allow-duplicates', set createViolation to true. 1439 return true; 1440 } 1441 1442 if (!ContainsTrustedTypesDirectivePolicyName(aPolicyName) && 1443 !ContainsTrustedTypesDirectivePolicyName(kWildcard)) { 1444 // Step 2.6: if directive’s value does not contain a tt-policy-name, which 1445 // value is policyName, and directive’s value does not contain a 1446 // tt-wildcard, set createViolation to true. 1447 return true; 1448 } 1449 } 1450 1451 return false; 1452 } 1453 1454 void nsCSPDirective::toString(nsAString& outStr) const { 1455 // Append directive name 1456 outStr.AppendASCII(CSP_CSPDirectiveToString(mDirective)); 1457 1458 MOZ_ASSERT(!mSrcs.IsEmpty()); 1459 1460 outStr.AppendLiteral(" "); 1461 1462 // Append srcs 1463 StringJoinAppend(outStr, u" "_ns, mSrcs, 1464 [](nsAString& dest, nsCSPBaseSrc* cspBaseSrc) { 1465 cspBaseSrc->toString(dest); 1466 }); 1467 } 1468 1469 void nsCSPDirective::toDomCSPStruct(mozilla::dom::CSP& outCSP) const { 1470 mozilla::dom::Sequence<nsString> srcs; 1471 nsString src; 1472 if (NS_WARN_IF(!srcs.SetCapacity(mSrcs.Length(), mozilla::fallible))) { 1473 MOZ_ASSERT(false, 1474 "Not enough memory for 'sources' sequence in " 1475 "nsCSPDirective::toDomCSPStruct()."); 1476 return; 1477 } 1478 for (uint32_t i = 0; i < mSrcs.Length(); i++) { 1479 src.Truncate(); 1480 mSrcs[i]->toString(src); 1481 if (!srcs.AppendElement(src, mozilla::fallible)) { 1482 MOZ_ASSERT(false, 1483 "Failed to append to 'sources' sequence in " 1484 "nsCSPDirective::toDomCSPStruct()."); 1485 } 1486 } 1487 1488 switch (mDirective) { 1489 case nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE: 1490 outCSP.mDefault_src.Construct(); 1491 outCSP.mDefault_src.Value() = std::move(srcs); 1492 return; 1493 1494 case nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE: 1495 outCSP.mScript_src.Construct(); 1496 outCSP.mScript_src.Value() = std::move(srcs); 1497 return; 1498 1499 case nsIContentSecurityPolicy::OBJECT_SRC_DIRECTIVE: 1500 outCSP.mObject_src.Construct(); 1501 outCSP.mObject_src.Value() = std::move(srcs); 1502 return; 1503 1504 case nsIContentSecurityPolicy::STYLE_SRC_DIRECTIVE: 1505 outCSP.mStyle_src.Construct(); 1506 outCSP.mStyle_src.Value() = std::move(srcs); 1507 return; 1508 1509 case nsIContentSecurityPolicy::IMG_SRC_DIRECTIVE: 1510 outCSP.mImg_src.Construct(); 1511 outCSP.mImg_src.Value() = std::move(srcs); 1512 return; 1513 1514 case nsIContentSecurityPolicy::MEDIA_SRC_DIRECTIVE: 1515 outCSP.mMedia_src.Construct(); 1516 outCSP.mMedia_src.Value() = std::move(srcs); 1517 return; 1518 1519 case nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE: 1520 outCSP.mFrame_src.Construct(); 1521 outCSP.mFrame_src.Value() = std::move(srcs); 1522 return; 1523 1524 case nsIContentSecurityPolicy::FONT_SRC_DIRECTIVE: 1525 outCSP.mFont_src.Construct(); 1526 outCSP.mFont_src.Value() = std::move(srcs); 1527 return; 1528 1529 case nsIContentSecurityPolicy::CONNECT_SRC_DIRECTIVE: 1530 outCSP.mConnect_src.Construct(); 1531 outCSP.mConnect_src.Value() = std::move(srcs); 1532 return; 1533 1534 case nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE: 1535 outCSP.mReport_uri.Construct(); 1536 outCSP.mReport_uri.Value() = std::move(srcs); 1537 return; 1538 1539 case nsIContentSecurityPolicy::FRAME_ANCESTORS_DIRECTIVE: 1540 outCSP.mFrame_ancestors.Construct(); 1541 outCSP.mFrame_ancestors.Value() = std::move(srcs); 1542 return; 1543 1544 case nsIContentSecurityPolicy::WEB_MANIFEST_SRC_DIRECTIVE: 1545 outCSP.mManifest_src.Construct(); 1546 outCSP.mManifest_src.Value() = std::move(srcs); 1547 return; 1548 // not supporting REFLECTED_XSS_DIRECTIVE 1549 1550 case nsIContentSecurityPolicy::BASE_URI_DIRECTIVE: 1551 outCSP.mBase_uri.Construct(); 1552 outCSP.mBase_uri.Value() = std::move(srcs); 1553 return; 1554 1555 case nsIContentSecurityPolicy::FORM_ACTION_DIRECTIVE: 1556 outCSP.mForm_action.Construct(); 1557 outCSP.mForm_action.Value() = std::move(srcs); 1558 return; 1559 1560 case nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT: 1561 outCSP.mBlock_all_mixed_content.Construct(); 1562 // does not have any srcs 1563 return; 1564 1565 case nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE: 1566 outCSP.mUpgrade_insecure_requests.Construct(); 1567 // does not have any srcs 1568 return; 1569 1570 case nsIContentSecurityPolicy::CHILD_SRC_DIRECTIVE: 1571 outCSP.mChild_src.Construct(); 1572 outCSP.mChild_src.Value() = std::move(srcs); 1573 return; 1574 1575 case nsIContentSecurityPolicy::SANDBOX_DIRECTIVE: 1576 outCSP.mSandbox.Construct(); 1577 outCSP.mSandbox.Value() = std::move(srcs); 1578 return; 1579 1580 case nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE: 1581 outCSP.mWorker_src.Construct(); 1582 outCSP.mWorker_src.Value() = std::move(srcs); 1583 return; 1584 1585 case nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE: 1586 outCSP.mScript_src_elem.Construct(); 1587 outCSP.mScript_src_elem.Value() = std::move(srcs); 1588 return; 1589 1590 case nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE: 1591 outCSP.mScript_src_attr.Construct(); 1592 outCSP.mScript_src_attr.Value() = std::move(srcs); 1593 return; 1594 1595 case nsIContentSecurityPolicy::REQUIRE_TRUSTED_TYPES_FOR_DIRECTIVE: 1596 outCSP.mRequire_trusted_types_for.Construct(); 1597 1598 // Here, the srcs represent the sink group 1599 // (https://w3c.github.io/trusted-types/dist/spec/#integration-with-content-security-policy). 1600 outCSP.mRequire_trusted_types_for.Value() = std::move(srcs); 1601 return; 1602 1603 case nsIContentSecurityPolicy::TRUSTED_TYPES_DIRECTIVE: 1604 outCSP.mTrusted_types.Construct(); 1605 // Here, "srcs" represents tt-expressions 1606 // (https://w3c.github.io/trusted-types/dist/spec/#trusted-types-csp-directive). 1607 outCSP.mTrusted_types.Value() = std::move(srcs); 1608 return; 1609 1610 case nsIContentSecurityPolicy::REPORT_TO_DIRECTIVE: 1611 outCSP.mReport_to.Construct(); 1612 outCSP.mReport_to.Value() = std::move(srcs); 1613 return; 1614 1615 default: 1616 NS_ASSERTION(false, "cannot find directive to convert CSP to JSON"); 1617 } 1618 } 1619 1620 bool nsCSPDirective::isDefaultDirective() const { 1621 return mDirective == nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE; 1622 } 1623 1624 void nsCSPDirective::getReportURIs(nsTArray<nsString>& outReportURIs) const { 1625 NS_ASSERTION((mDirective == nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE), 1626 "not a report-uri directive"); 1627 1628 // append uris 1629 nsString tmpReportURI; 1630 for (uint32_t i = 0; i < mSrcs.Length(); i++) { 1631 tmpReportURI.Truncate(); 1632 mSrcs[i]->toString(tmpReportURI); 1633 outReportURIs.AppendElement(tmpReportURI); 1634 } 1635 } 1636 1637 void nsCSPDirective::getReportGroup(nsAString& outReportGroup) const { 1638 NS_ASSERTION((mDirective == nsIContentSecurityPolicy::REPORT_TO_DIRECTIVE), 1639 "not a report-to directive"); 1640 1641 MOZ_ASSERT(mSrcs.Length() <= 1); 1642 mSrcs[0]->toString(outReportGroup); 1643 } 1644 1645 bool nsCSPDirective::visitSrcs(nsCSPSrcVisitor* aVisitor) const { 1646 for (uint32_t i = 0; i < mSrcs.Length(); i++) { 1647 if (!mSrcs[i]->visit(aVisitor)) { 1648 return false; 1649 } 1650 } 1651 return true; 1652 } 1653 1654 bool nsCSPDirective::equals(CSPDirective aDirective) const { 1655 return (mDirective == aDirective); 1656 } 1657 1658 void nsCSPDirective::getDirName(nsAString& outStr) const { 1659 outStr.AppendASCII(CSP_CSPDirectiveToString(mDirective)); 1660 } 1661 1662 bool nsCSPDirective::hasReportSampleKeyword() const { 1663 for (nsCSPBaseSrc* src : mSrcs) { 1664 if (src->isReportSample()) { 1665 return true; 1666 } 1667 } 1668 1669 return false; 1670 } 1671 1672 bool nsCSPDirective::ContainsTrustedTypesDirectivePolicyName( 1673 const nsAString& aPolicyName) const { 1674 MOZ_ASSERT(mDirective == nsIContentSecurityPolicy::TRUSTED_TYPES_DIRECTIVE); 1675 1676 if (mDirective == nsIContentSecurityPolicy::TRUSTED_TYPES_DIRECTIVE) { 1677 for (const auto* src : mSrcs) { 1678 if (src->isTrustedTypesDirectivePolicyName()) { 1679 const auto& name = 1680 static_cast<const nsCSPTrustedTypesDirectivePolicyName*>(src) 1681 ->GetName(); 1682 if (name.Equals(aPolicyName)) { 1683 return true; 1684 } 1685 } 1686 } 1687 } 1688 1689 return false; 1690 } 1691 1692 /* =============== nsCSPChildSrcDirective ============= */ 1693 1694 nsCSPChildSrcDirective::nsCSPChildSrcDirective(CSPDirective aDirective) 1695 : nsCSPDirective(aDirective), 1696 mRestrictFrames(false), 1697 mRestrictWorkers(false) {} 1698 1699 nsCSPChildSrcDirective::~nsCSPChildSrcDirective() = default; 1700 1701 bool nsCSPChildSrcDirective::equals(CSPDirective aDirective) const { 1702 if (aDirective == nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE) { 1703 return mRestrictFrames; 1704 } 1705 if (aDirective == nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE) { 1706 return mRestrictWorkers; 1707 } 1708 return (mDirective == aDirective); 1709 } 1710 1711 /* =============== nsCSPScriptSrcDirective ============= */ 1712 1713 nsCSPScriptSrcDirective::nsCSPScriptSrcDirective(CSPDirective aDirective) 1714 : nsCSPDirective(aDirective) {} 1715 1716 nsCSPScriptSrcDirective::~nsCSPScriptSrcDirective() = default; 1717 1718 bool nsCSPScriptSrcDirective::equals(CSPDirective aDirective) const { 1719 if (aDirective == nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE) { 1720 return mRestrictWorkers; 1721 } 1722 if (aDirective == nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE) { 1723 return mRestrictScriptElem; 1724 } 1725 if (aDirective == nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE) { 1726 return mRestrictScriptAttr; 1727 } 1728 return mDirective == aDirective; 1729 } 1730 1731 /* =============== nsCSPStyleSrcDirective ============= */ 1732 1733 nsCSPStyleSrcDirective::nsCSPStyleSrcDirective(CSPDirective aDirective) 1734 : nsCSPDirective(aDirective) {} 1735 1736 nsCSPStyleSrcDirective::~nsCSPStyleSrcDirective() = default; 1737 1738 bool nsCSPStyleSrcDirective::equals(CSPDirective aDirective) const { 1739 if (aDirective == nsIContentSecurityPolicy::STYLE_SRC_ELEM_DIRECTIVE) { 1740 return mRestrictStyleElem; 1741 } 1742 if (aDirective == nsIContentSecurityPolicy::STYLE_SRC_ATTR_DIRECTIVE) { 1743 return mRestrictStyleAttr; 1744 } 1745 return mDirective == aDirective; 1746 } 1747 1748 /* =============== nsBlockAllMixedContentDirective ============= */ 1749 1750 nsBlockAllMixedContentDirective::nsBlockAllMixedContentDirective( 1751 CSPDirective aDirective) 1752 : nsCSPDirective(aDirective) {} 1753 1754 nsBlockAllMixedContentDirective::~nsBlockAllMixedContentDirective() = default; 1755 1756 void nsBlockAllMixedContentDirective::toString(nsAString& outStr) const { 1757 outStr.AppendASCII(CSP_CSPDirectiveToString( 1758 nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT)); 1759 } 1760 1761 void nsBlockAllMixedContentDirective::getDirName(nsAString& outStr) const { 1762 outStr.AppendASCII(CSP_CSPDirectiveToString( 1763 nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT)); 1764 } 1765 1766 /* =============== nsUpgradeInsecureDirective ============= */ 1767 1768 nsUpgradeInsecureDirective::nsUpgradeInsecureDirective(CSPDirective aDirective) 1769 : nsCSPDirective(aDirective) {} 1770 1771 nsUpgradeInsecureDirective::~nsUpgradeInsecureDirective() = default; 1772 1773 void nsUpgradeInsecureDirective::toString(nsAString& outStr) const { 1774 outStr.AppendASCII(CSP_CSPDirectiveToString( 1775 nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE)); 1776 } 1777 1778 void nsUpgradeInsecureDirective::getDirName(nsAString& outStr) const { 1779 outStr.AppendASCII(CSP_CSPDirectiveToString( 1780 nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE)); 1781 } 1782 1783 /* ===== nsCSPPolicy ========================= */ 1784 1785 nsCSPPolicy::nsCSPPolicy() 1786 : mUpgradeInsecDir(nullptr), 1787 mReportOnly(false), 1788 mDeliveredViaMetaTag(false) { 1789 CSPUTILSLOG(("nsCSPPolicy::nsCSPPolicy")); 1790 } 1791 1792 nsCSPPolicy::~nsCSPPolicy() { 1793 CSPUTILSLOG(("nsCSPPolicy::~nsCSPPolicy")); 1794 1795 for (uint32_t i = 0; i < mDirectives.Length(); i++) { 1796 delete mDirectives[i]; 1797 } 1798 } 1799 1800 bool nsCSPPolicy::permits(CSPDirective aDir, nsILoadInfo* aLoadInfo, 1801 nsIURI* aUri, bool aWasRedirected, bool aSpecific, 1802 nsAString& outViolatedDirective, 1803 nsAString& outViolatedDirectiveString) const { 1804 if (CSPUTILSLOGENABLED()) { 1805 CSPUTILSLOG(("nsCSPPolicy::permits, aUri: %s, aDir: %s, aSpecific: %s", 1806 aUri->GetSpecOrDefault().get(), CSP_CSPDirectiveToString(aDir), 1807 aSpecific ? "true" : "false")); 1808 } 1809 1810 NS_ASSERTION(aUri, "permits needs an uri to perform the check!"); 1811 outViolatedDirective.Truncate(); 1812 outViolatedDirectiveString.Truncate(); 1813 1814 nsCSPDirective* defaultDir = nullptr; 1815 1816 // Try to find a relevant directive 1817 // These directive arrays are short (1-5 elements), not worth using a 1818 // hashtable. 1819 for (uint32_t i = 0; i < mDirectives.Length(); i++) { 1820 if (mDirectives[i]->equals(aDir)) { 1821 if (!mDirectives[i]->permits(aDir, aLoadInfo, aUri, aWasRedirected, 1822 mReportOnly, mUpgradeInsecDir)) { 1823 mDirectives[i]->getDirName(outViolatedDirective); 1824 mDirectives[i]->toString(outViolatedDirectiveString); 1825 return false; 1826 } 1827 return true; 1828 } 1829 if (mDirectives[i]->isDefaultDirective()) { 1830 defaultDir = mDirectives[i]; 1831 } 1832 } 1833 1834 // If the above loop runs through, we haven't found a matching directive. 1835 // Avoid relooping, just store the result of default-src while looping. 1836 if (!aSpecific && defaultDir) { 1837 if (!defaultDir->permits(aDir, aLoadInfo, aUri, aWasRedirected, mReportOnly, 1838 mUpgradeInsecDir)) { 1839 defaultDir->getDirName(outViolatedDirective); 1840 defaultDir->toString(outViolatedDirectiveString); 1841 return false; 1842 } 1843 return true; 1844 } 1845 1846 // Nothing restricts this, so we're allowing the load 1847 // See bug 764937 1848 return true; 1849 } 1850 1851 bool nsCSPPolicy::allows(CSPDirective aDirective, enum CSPKeyword aKeyword, 1852 const nsAString& aHashOrNonce) const { 1853 CSPUTILSLOG(("nsCSPPolicy::allows, aKeyWord: %s, a HashOrNonce: %s", 1854 CSP_EnumToUTF8Keyword(aKeyword), 1855 NS_ConvertUTF16toUTF8(aHashOrNonce).get())); 1856 1857 if (nsCSPDirective* directive = matchingOrDefaultDirective(aDirective)) { 1858 return directive->allows(aKeyword, aHashOrNonce); 1859 } 1860 1861 // No matching directive or default directive as fallback found, thus 1862 // allowing the load; see Bug 885433 1863 // a) inline scripts (also unsafe eval) should only be blocked 1864 // if there is a [script-src] or [default-src] 1865 // b) inline styles should only be blocked 1866 // if there is a [style-src] or [default-src] 1867 return true; 1868 } 1869 1870 nsCSPDirective* nsCSPPolicy::matchingOrDefaultDirective( 1871 CSPDirective aDirective) const { 1872 nsCSPDirective* defaultDir = nullptr; 1873 1874 // Try to find a matching directive 1875 for (uint32_t i = 0; i < mDirectives.Length(); i++) { 1876 if (mDirectives[i]->isDefaultDirective()) { 1877 defaultDir = mDirectives[i]; 1878 continue; 1879 } 1880 if (mDirectives[i]->equals(aDirective)) { 1881 return mDirectives[i]; 1882 } 1883 } 1884 1885 return defaultDir; 1886 } 1887 1888 void nsCSPPolicy::toString(nsAString& outStr) const { 1889 StringJoinAppend(outStr, u"; "_ns, mDirectives, 1890 [](nsAString& dest, nsCSPDirective* cspDirective) { 1891 cspDirective->toString(dest); 1892 }); 1893 } 1894 1895 void nsCSPPolicy::toDomCSPStruct(mozilla::dom::CSP& outCSP) const { 1896 outCSP.mReport_only = mReportOnly; 1897 1898 for (uint32_t i = 0; i < mDirectives.Length(); ++i) { 1899 mDirectives[i]->toDomCSPStruct(outCSP); 1900 } 1901 } 1902 1903 bool nsCSPPolicy::hasDirective(CSPDirective aDir) const { 1904 for (uint32_t i = 0; i < mDirectives.Length(); i++) { 1905 if (mDirectives[i]->equals(aDir)) { 1906 return true; 1907 } 1908 } 1909 return false; 1910 } 1911 1912 bool nsCSPPolicy::allowsAllInlineBehavior(CSPDirective aDir) const { 1913 nsCSPDirective* directive = matchingOrDefaultDirective(aDir); 1914 if (!directive) { 1915 // No matching or default directive found thus allow the all inline 1916 // scripts or styles. (See nsCSPPolicy::allows) 1917 return true; 1918 } 1919 1920 return directive->allowsAllInlineBehavior(aDir); 1921 } 1922 1923 bool nsCSPPolicy::ShouldCreateViolationForNewTrustedTypesPolicy( 1924 const nsAString& aPolicyName, 1925 const nsTArray<nsString>& aCreatedPolicyNames) const { 1926 for (const auto* directive : mDirectives) { 1927 if (directive->equals(nsIContentSecurityPolicy::TRUSTED_TYPES_DIRECTIVE)) { 1928 return directive->ShouldCreateViolationForNewTrustedTypesPolicy( 1929 aPolicyName, aCreatedPolicyNames); 1930 } 1931 } 1932 1933 return false; 1934 } 1935 1936 bool nsCSPPolicy::AreTrustedTypesForSinkGroupRequired( 1937 const nsAString& aSinkGroup) const { 1938 MOZ_ASSERT(aSinkGroup == dom::kTrustedTypesOnlySinkGroup); 1939 return mHasRequireTrustedTypesForDirective; 1940 } 1941 1942 /* 1943 * Use this function only after ::allows() returned 'false' or if ensured by 1944 * other means that the directive is violated. First and foremost it's used to 1945 * get the violated directive before sending reports. The parameter 1946 * aDirectiveName is the equivalent of 'outViolatedDirective' for the 1947 * ::permits() function family. 1948 */ 1949 void nsCSPPolicy::getViolatedDirectiveInformation( 1950 CSPDirective aDirective, nsAString& aDirectiveName, 1951 nsAString& aDirectiveNameAndValue, bool* aReportSample) const { 1952 *aReportSample = false; 1953 nsCSPDirective* directive = matchingOrDefaultDirective(aDirective); 1954 if (!directive) { 1955 MOZ_ASSERT_UNREACHABLE("Can not query violated directive"); 1956 aDirectiveName.Truncate(); 1957 aDirectiveNameAndValue.Truncate(); 1958 return; 1959 } 1960 1961 directive->getDirName(aDirectiveName); 1962 directive->toString(aDirectiveNameAndValue); 1963 *aReportSample = directive->hasReportSampleKeyword(); 1964 } 1965 1966 /* 1967 * Helper function that returns the underlying bit representation of sandbox 1968 * flags. The function returns SANDBOXED_NONE if there are no sandbox 1969 * directives. 1970 */ 1971 uint32_t nsCSPPolicy::getSandboxFlags() const { 1972 for (uint32_t i = 0; i < mDirectives.Length(); i++) { 1973 if (mDirectives[i]->equals(nsIContentSecurityPolicy::SANDBOX_DIRECTIVE)) { 1974 nsAutoString flags; 1975 mDirectives[i]->toString(flags); 1976 1977 if (flags.IsEmpty()) { 1978 return SANDBOX_ALL_FLAGS; 1979 } 1980 1981 nsAttrValue attr; 1982 attr.ParseAtomArray(flags); 1983 1984 return nsContentUtils::ParseSandboxAttributeToFlags(&attr); 1985 } 1986 } 1987 1988 return SANDBOXED_NONE; 1989 } 1990 1991 void nsCSPPolicy::getReportURIs(nsTArray<nsString>& outReportURIs) const { 1992 for (uint32_t i = 0; i < mDirectives.Length(); i++) { 1993 if (mDirectives[i]->equals( 1994 nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE)) { 1995 mDirectives[i]->getReportURIs(outReportURIs); 1996 return; 1997 } 1998 } 1999 } 2000 2001 void nsCSPPolicy::getReportGroup(nsAString& outReportGroup) const { 2002 for (uint32_t i = 0; i < mDirectives.Length(); i++) { 2003 if (mDirectives[i]->equals(nsIContentSecurityPolicy::REPORT_TO_DIRECTIVE)) { 2004 mDirectives[i]->getReportGroup(outReportGroup); 2005 return; 2006 } 2007 } 2008 } 2009 2010 void nsCSPPolicy::getDirectiveNames(nsTArray<nsString>& outDirectives) const { 2011 for (uint32_t i = 0; i < mDirectives.Length(); i++) { 2012 nsAutoString name; 2013 mDirectives[i]->getDirName(name); 2014 outDirectives.AppendElement(name); 2015 } 2016 } 2017 2018 bool nsCSPPolicy::visitDirectiveSrcs(CSPDirective aDir, 2019 nsCSPSrcVisitor* aVisitor) const { 2020 for (uint32_t i = 0; i < mDirectives.Length(); i++) { 2021 if (mDirectives[i]->equals(aDir)) { 2022 return mDirectives[i]->visitSrcs(aVisitor); 2023 } 2024 } 2025 return false; 2026 }