nsCSPContext.cpp (86605B)
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 "nsCSPContext.h" 8 9 #include <string> 10 #include <unordered_set> 11 #include <utility> 12 13 #include "mozilla/Logging.h" 14 #include "mozilla/Preferences.h" 15 #include "mozilla/StaticPrefs_security.h" 16 #include "mozilla/dom/CSPDictionariesBinding.h" 17 #include "mozilla/dom/CSPReportBinding.h" 18 #include "mozilla/dom/CSPViolationReportBody.h" 19 #include "mozilla/dom/DocGroup.h" 20 #include "mozilla/dom/Document.h" 21 #include "mozilla/dom/Element.h" 22 #include "mozilla/dom/ReportingUtils.h" 23 #include "mozilla/dom/WindowGlobalParent.h" 24 #include "mozilla/glean/DomSecurityMetrics.h" 25 #include "mozilla/ipc/PBackgroundSharedTypes.h" 26 #include "nsCOMPtr.h" 27 #include "nsCSPParser.h" 28 #include "nsCSPService.h" 29 #include "nsCSPUtils.h" 30 #include "nsContentPolicyUtils.h" 31 #include "nsContentSecurityUtils.h" 32 #include "nsContentUtils.h" 33 #include "nsError.h" 34 #include "nsGlobalWindowOuter.h" 35 #include "nsIAsyncVerifyRedirectCallback.h" 36 #include "nsIClassInfoImpl.h" 37 #include "nsIContentPolicy.h" 38 #include "nsIEventTarget.h" 39 #include "nsIHttpChannel.h" 40 #include "nsIInterfaceRequestor.h" 41 #include "nsIInterfaceRequestorUtils.h" 42 #include "nsINetworkInterceptController.h" 43 #include "nsIObjectInputStream.h" 44 #include "nsIObjectOutputStream.h" 45 #include "nsIObserver.h" 46 #include "nsIObserverService.h" 47 #include "nsIScriptElement.h" 48 #include "nsIScriptError.h" 49 #include "nsIStringStream.h" 50 #include "nsISupportsPrimitives.h" 51 #include "nsIURIMutator.h" 52 #include "nsIUploadChannel.h" 53 #include "nsJSUtils.h" 54 #include "nsMimeTypes.h" 55 #include "nsNetUtil.h" 56 #include "nsSandboxFlags.h" 57 #include "nsScriptSecurityManager.h" 58 #include "nsStreamUtils.h" 59 #include "nsString.h" 60 #include "nsStringStream.h" 61 #include "nsSupportsPrimitives.h" 62 #include "nsThreadUtils.h" 63 #include "nsXULAppAPI.h" 64 65 using namespace mozilla; 66 using namespace mozilla::dom; 67 using namespace mozilla::ipc; 68 69 static LogModule* GetCspContextLog() { 70 static LazyLogModule gCspContextPRLog("CSPContext"); 71 return gCspContextPRLog; 72 } 73 74 #define CSPCONTEXTLOG(args) \ 75 MOZ_LOG(GetCspContextLog(), mozilla::LogLevel::Debug, args) 76 #define CSPCONTEXTLOGENABLED() \ 77 MOZ_LOG_TEST(GetCspContextLog(), mozilla::LogLevel::Debug) 78 79 static LogModule* GetCspOriginLogLog() { 80 static LazyLogModule gCspOriginPRLog("CSPOrigin"); 81 return gCspOriginPRLog; 82 } 83 84 #define CSPORIGINLOG(args) \ 85 MOZ_LOG(GetCspOriginLogLog(), mozilla::LogLevel::Debug, args) 86 #define CSPORIGINLOGENABLED() \ 87 MOZ_LOG_TEST(GetCspOriginLogLog(), mozilla::LogLevel::Debug) 88 89 #ifdef DEBUG 90 /** 91 * This function is only used for verification purposes within 92 * GatherSecurityPolicyViolationEventData. 93 */ 94 static bool ValidateDirectiveName(const nsAString& aDirective) { 95 static const auto directives = []() { 96 std::unordered_set<std::string> directives; 97 constexpr size_t dirLen = 98 sizeof(CSPStrDirectives) / sizeof(CSPStrDirectives[0]); 99 for (size_t i = 0; i < dirLen; ++i) { 100 directives.insert(CSPStrDirectives[i]); 101 } 102 return directives; 103 }(); 104 105 nsAutoString directive(aDirective); 106 auto itr = directives.find(NS_ConvertUTF16toUTF8(directive).get()); 107 return itr != directives.end(); 108 } 109 #endif // DEBUG 110 111 static void BlockedContentSourceToString( 112 CSPViolationData::BlockedContentSource aSource, nsACString& aString) { 113 switch (aSource) { 114 case CSPViolationData::BlockedContentSource::Unknown: 115 aString.Truncate(); 116 break; 117 118 case CSPViolationData::BlockedContentSource::Inline: 119 aString.AssignLiteral("inline"); 120 break; 121 122 case CSPViolationData::BlockedContentSource::Eval: 123 aString.AssignLiteral("eval"); 124 break; 125 126 case CSPViolationData::BlockedContentSource::Self: 127 aString.AssignLiteral("self"); 128 break; 129 130 case CSPViolationData::BlockedContentSource::WasmEval: 131 aString.AssignLiteral("wasm-eval"); 132 break; 133 case CSPViolationData::BlockedContentSource::TrustedTypesPolicy: 134 aString.AssignLiteral("trusted-types-policy"); 135 break; 136 case CSPViolationData::BlockedContentSource::TrustedTypesSink: 137 aString.AssignLiteral("trusted-types-sink"); 138 break; 139 } 140 } 141 142 /* ===== nsIContentSecurityPolicy impl ====== */ 143 144 NS_IMETHODIMP 145 nsCSPContext::ShouldLoad(nsContentPolicyType aContentType, 146 nsICSPEventListener* aCSPEventListener, 147 nsILoadInfo* aLoadInfo, nsIURI* aContentLocation, 148 nsIURI* aOriginalURIIfRedirect, 149 bool aSendViolationReports, int16_t* outDecision) { 150 if (CSPCONTEXTLOGENABLED()) { 151 CSPCONTEXTLOG(("nsCSPContext::ShouldLoad, aContentLocation: %s", 152 aContentLocation->GetSpecOrDefault().get())); 153 CSPCONTEXTLOG((">>>> aContentType: %s", 154 NS_CP_ContentTypeName(aContentType))); 155 } 156 157 // This ShouldLoad function is called from nsCSPService::ShouldLoad, 158 // which already checked a number of things, including: 159 // * aContentLocation is not null; we can consume this without further checks 160 // * scheme is not a allowlisted scheme (about: chrome:, etc). 161 // * CSP is enabled 162 // * Content Type is not allowlisted (CSP Reports, TYPE_DOCUMENT, etc). 163 // * Fast Path for Apps 164 165 // Default decision, CSP can revise it if there's a policy to enforce 166 *outDecision = nsIContentPolicy::ACCEPT; 167 168 // If the content type doesn't map to a CSP directive, there's nothing for 169 // CSP to do. 170 CSPDirective dir = CSP_ContentTypeToDirective(aContentType); 171 if (dir == nsIContentSecurityPolicy::NO_DIRECTIVE) { 172 return NS_OK; 173 } 174 175 bool permitted = permitsInternal( 176 dir, 177 nullptr, // aTriggeringElement 178 aCSPEventListener, aLoadInfo, aContentLocation, aOriginalURIIfRedirect, 179 false, // allow fallback to default-src 180 aSendViolationReports, 181 true); // send blocked URI in violation reports 182 183 *outDecision = 184 permitted ? nsIContentPolicy::ACCEPT : nsIContentPolicy::REJECT_SERVER; 185 186 if (CSPCONTEXTLOGENABLED()) { 187 CSPCONTEXTLOG( 188 ("nsCSPContext::ShouldLoad, decision: %s, " 189 "aContentLocation: %s", 190 *outDecision > 0 ? "load" : "deny", 191 aContentLocation->GetSpecOrDefault().get())); 192 } 193 return NS_OK; 194 } 195 196 bool nsCSPContext::permitsInternal( 197 CSPDirective aDir, Element* aTriggeringElement, 198 nsICSPEventListener* aCSPEventListener, nsILoadInfo* aLoadInfo, 199 nsIURI* aContentLocation, nsIURI* aOriginalURIIfRedirect, bool aSpecific, 200 bool aSendViolationReports, bool aSendContentLocationInViolationReports) { 201 EnsureIPCPoliciesRead(); 202 bool permits = true; 203 204 nsAutoString violatedDirective; 205 nsAutoString violatedDirectiveString; 206 for (uint32_t p = 0; p < mPolicies.Length(); p++) { 207 if (!mPolicies[p]->permits(aDir, aLoadInfo, aContentLocation, 208 !!aOriginalURIIfRedirect, aSpecific, 209 violatedDirective, violatedDirectiveString)) { 210 // If the policy is violated and not report-only, reject the load and 211 // report to the console 212 if (!mPolicies[p]->getReportOnlyFlag()) { 213 CSPCONTEXTLOG(("nsCSPContext::permitsInternal, false")); 214 permits = false; 215 } 216 217 // Callers should set |aSendViolationReports| to false if this is a 218 // preload - the decision may be wrong due to the inability to get the 219 // nonce, and will incorrectly fail the unit tests. 220 if (aSendViolationReports) { 221 auto loc = JSCallingLocation::Get(); 222 223 using Resource = CSPViolationData::Resource; 224 Resource resource = 225 aSendContentLocationInViolationReports 226 ? Resource{nsCOMPtr<nsIURI>{aContentLocation}} 227 : Resource{CSPViolationData::BlockedContentSource::Unknown}; 228 229 CSPViolationData cspViolationData{p, 230 std::move(resource), 231 aDir, 232 loc.FileName(), 233 loc.mLine, 234 loc.mColumn, 235 aTriggeringElement, 236 /* aSample */ u""_ns}; 237 238 AsyncReportViolation( 239 aCSPEventListener, std::move(cspViolationData), 240 aOriginalURIIfRedirect, /* in case of redirect originalURI is not 241 null */ 242 violatedDirective, violatedDirectiveString, 243 u""_ns, // no observer subject 244 false); // aReportSample (no sample) 245 } 246 } 247 } 248 249 return permits; 250 } 251 252 /* ===== nsISupports implementation ========== */ 253 254 NS_IMPL_CLASSINFO(nsCSPContext, nullptr, 0, NS_CSPCONTEXT_CID) 255 256 NS_IMPL_ISUPPORTS_CI(nsCSPContext, nsIContentSecurityPolicy, nsISerializable) 257 258 nsCSPContext::nsCSPContext() 259 : mInnerWindowID(0), 260 mSkipAllowInlineStyleCheck(false), 261 mLoadingContext(nullptr), 262 mLoadingPrincipal(nullptr), 263 mQueueUpMessages(true) { 264 CSPCONTEXTLOG(("nsCSPContext::nsCSPContext")); 265 } 266 267 nsCSPContext::~nsCSPContext() { 268 CSPCONTEXTLOG(("nsCSPContext::~nsCSPContext")); 269 for (uint32_t i = 0; i < mPolicies.Length(); i++) { 270 delete mPolicies[i]; 271 } 272 } 273 274 /* static */ 275 bool nsCSPContext::Equals(nsIContentSecurityPolicy* aCSP, 276 nsIContentSecurityPolicy* aOtherCSP) { 277 if (aCSP == aOtherCSP) { 278 // fast path for pointer equality 279 return true; 280 } 281 282 uint32_t policyCount = 0; 283 if (aCSP) { 284 aCSP->GetPolicyCount(&policyCount); 285 } 286 287 uint32_t otherPolicyCount = 0; 288 if (aOtherCSP) { 289 aOtherCSP->GetPolicyCount(&otherPolicyCount); 290 } 291 292 if (policyCount != otherPolicyCount) { 293 return false; 294 } 295 296 nsAutoString policyStr, otherPolicyStr; 297 for (uint32_t i = 0; i < policyCount; ++i) { 298 aCSP->GetPolicyString(i, policyStr); 299 aOtherCSP->GetPolicyString(i, otherPolicyStr); 300 if (!policyStr.Equals(otherPolicyStr)) { 301 return false; 302 } 303 } 304 305 return true; 306 } 307 308 nsresult nsCSPContext::InitFromOther(nsCSPContext* aOtherContext) { 309 NS_ENSURE_ARG(aOtherContext); 310 311 nsresult rv = NS_OK; 312 nsCOMPtr<Document> doc = do_QueryReferent(aOtherContext->mLoadingContext); 313 if (doc) { 314 rv = SetRequestContextWithDocument(doc); 315 } else { 316 rv = SetRequestContextWithPrincipal( 317 aOtherContext->mLoadingPrincipal, aOtherContext->mSelfURI, 318 aOtherContext->mReferrer, aOtherContext->mInnerWindowID); 319 } 320 NS_ENSURE_SUCCESS(rv, rv); 321 322 mSkipAllowInlineStyleCheck = aOtherContext->mSkipAllowInlineStyleCheck; 323 324 // This policy was already parsed somewhere else, don't emit parsing errors. 325 mSuppressParserLogMessages = true; 326 for (auto policy : aOtherContext->mPolicies) { 327 nsAutoString policyStr; 328 policy->toString(policyStr); 329 AppendPolicy(policyStr, policy->getReportOnlyFlag(), 330 policy->getDeliveredViaMetaTagFlag()); 331 } 332 333 mSuppressParserLogMessages = aOtherContext->mSuppressParserLogMessages; 334 335 mIPCPolicies = aOtherContext->mIPCPolicies.Clone(); 336 return NS_OK; 337 } 338 339 NS_IMETHODIMP 340 nsCSPContext::EnsureIPCPoliciesRead() { 341 // Most likely the parser errors already happened before serializing 342 // the policy for IPC. 343 bool previous = mSuppressParserLogMessages; 344 mSuppressParserLogMessages = true; 345 346 if (mIPCPolicies.Length() > 0) { 347 nsresult rv; 348 for (auto& policy : mIPCPolicies) { 349 rv = AppendPolicy(policy.policy(), policy.reportOnlyFlag(), 350 policy.deliveredViaMetaTagFlag()); 351 (void)NS_WARN_IF(NS_FAILED(rv)); 352 } 353 mIPCPolicies.Clear(); 354 } 355 356 mSuppressParserLogMessages = previous; 357 return NS_OK; 358 } 359 360 NS_IMETHODIMP 361 nsCSPContext::GetPolicyString(uint32_t aIndex, nsAString& outStr) { 362 outStr.Truncate(); 363 EnsureIPCPoliciesRead(); 364 if (aIndex >= mPolicies.Length()) { 365 return NS_ERROR_ILLEGAL_VALUE; 366 } 367 mPolicies[aIndex]->toString(outStr); 368 return NS_OK; 369 } 370 371 const nsCSPPolicy* nsCSPContext::GetPolicy(uint32_t aIndex) { 372 EnsureIPCPoliciesRead(); 373 if (aIndex >= mPolicies.Length()) { 374 return nullptr; 375 } 376 return mPolicies[aIndex]; 377 } 378 379 NS_IMETHODIMP 380 nsCSPContext::GetPolicyCount(uint32_t* outPolicyCount) { 381 EnsureIPCPoliciesRead(); 382 *outPolicyCount = mPolicies.Length(); 383 return NS_OK; 384 } 385 386 NS_IMETHODIMP 387 nsCSPContext::GetUpgradeInsecureRequests(bool* outUpgradeRequest) { 388 EnsureIPCPoliciesRead(); 389 *outUpgradeRequest = false; 390 for (uint32_t i = 0; i < mPolicies.Length(); i++) { 391 if (mPolicies[i]->hasDirective( 392 nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE) && 393 !mPolicies[i]->getReportOnlyFlag()) { 394 *outUpgradeRequest = true; 395 return NS_OK; 396 } 397 } 398 return NS_OK; 399 } 400 401 NS_IMETHODIMP 402 nsCSPContext::GetBlockAllMixedContent(bool* outBlockAllMixedContent) { 403 EnsureIPCPoliciesRead(); 404 *outBlockAllMixedContent = false; 405 for (uint32_t i = 0; i < mPolicies.Length(); i++) { 406 if (!mPolicies[i]->getReportOnlyFlag() && 407 mPolicies[i]->hasDirective( 408 nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT)) { 409 *outBlockAllMixedContent = true; 410 return NS_OK; 411 } 412 } 413 return NS_OK; 414 } 415 416 NS_IMETHODIMP 417 nsCSPContext::GetEnforcesFrameAncestors(bool* outEnforcesFrameAncestors) { 418 EnsureIPCPoliciesRead(); 419 *outEnforcesFrameAncestors = false; 420 for (uint32_t i = 0; i < mPolicies.Length(); i++) { 421 if (!mPolicies[i]->getReportOnlyFlag() && 422 mPolicies[i]->hasDirective( 423 nsIContentSecurityPolicy::FRAME_ANCESTORS_DIRECTIVE)) { 424 *outEnforcesFrameAncestors = true; 425 return NS_OK; 426 } 427 } 428 return NS_OK; 429 } 430 431 NS_IMETHODIMP 432 nsCSPContext::AppendPolicy(const nsAString& aPolicyString, bool aReportOnly, 433 bool aDeliveredViaMetaTag) { 434 CSPCONTEXTLOG(("nsCSPContext::AppendPolicy: %s", 435 NS_ConvertUTF16toUTF8(aPolicyString).get())); 436 437 // Use mSelfURI from setRequestContextWith{Document,Principal} (bug 991474) 438 MOZ_ASSERT( 439 mLoadingPrincipal, 440 "did you forget to call setRequestContextWith{Document,Principal}?"); 441 MOZ_ASSERT( 442 mSelfURI, 443 "did you forget to call setRequestContextWith{Document,Principal}?"); 444 NS_ENSURE_TRUE(mLoadingPrincipal, NS_ERROR_UNEXPECTED); 445 NS_ENSURE_TRUE(mSelfURI, NS_ERROR_UNEXPECTED); 446 447 if (CSPORIGINLOGENABLED()) { 448 nsAutoCString selfURISpec; 449 mSelfURI->GetSpec(selfURISpec); 450 CSPORIGINLOG(("CSP - AppendPolicy")); 451 CSPORIGINLOG((" * selfURI: %s", selfURISpec.get())); 452 CSPORIGINLOG((" * reportOnly: %s", aReportOnly ? "yes" : "no")); 453 CSPORIGINLOG( 454 (" * deliveredViaMetaTag: %s", aDeliveredViaMetaTag ? "yes" : "no")); 455 CSPORIGINLOG( 456 (" * policy: %s\n", NS_ConvertUTF16toUTF8(aPolicyString).get())); 457 } 458 459 nsCSPPolicy* policy = nsCSPParser::parseContentSecurityPolicy( 460 aPolicyString, mSelfURI, aReportOnly, this, aDeliveredViaMetaTag, 461 mSuppressParserLogMessages); 462 if (policy) { 463 if (policy->hasDirective( 464 nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE)) { 465 nsAutoCString selfURIspec; 466 if (mSelfURI) { 467 mSelfURI->GetAsciiSpec(selfURIspec); 468 } 469 CSPCONTEXTLOG( 470 ("nsCSPContext::AppendPolicy added UPGRADE_IF_INSECURE_DIRECTIVE " 471 "self-uri=%s referrer=%s", 472 selfURIspec.get(), mReferrer.get())); 473 } 474 if (policy->hasDirective( 475 nsIContentSecurityPolicy::REQUIRE_TRUSTED_TYPES_FOR_DIRECTIVE)) { 476 if (mRequireTrustedTypesForDirectiveState != 477 RequireTrustedTypesForDirectiveState::ENFORCE) { 478 mRequireTrustedTypesForDirectiveState = 479 policy->getReportOnlyFlag() 480 ? RequireTrustedTypesForDirectiveState::REPORT_ONLY 481 : RequireTrustedTypesForDirectiveState::ENFORCE; 482 } 483 if (nsCOMPtr<Document> doc = do_QueryReferent(mLoadingContext)) { 484 doc->SetHasPolicyWithRequireTrustedTypesForDirective(true); 485 } 486 } 487 488 mPolicies.AppendElement(policy); 489 } 490 491 return NS_OK; 492 } 493 494 NS_IMETHODIMP 495 nsCSPContext::GetRequireTrustedTypesForDirectiveState( 496 RequireTrustedTypesForDirectiveState* 497 aRequireTrustedTypesForDirectiveState) { 498 *aRequireTrustedTypesForDirectiveState = 499 mRequireTrustedTypesForDirectiveState; 500 return NS_OK; 501 } 502 503 NS_IMETHODIMP 504 nsCSPContext::GetAllowsEval(bool* outShouldReportViolation, 505 bool* outAllowsEval) { 506 EnsureIPCPoliciesRead(); 507 *outShouldReportViolation = false; 508 *outAllowsEval = true; 509 510 if (CSP_IsBrowserXHTML(mSelfURI)) { 511 // Allow eval in browser.xhtml, just like 512 // nsContentSecurityUtils::IsEvalAllowed allows it for other privileged 513 // contexts. 514 if (StaticPrefs:: 515 security_allow_unsafe_dangerous_privileged_evil_eval_AtStartup()) { 516 return NS_OK; 517 } 518 } 519 520 bool trustedTypesRequired = (mRequireTrustedTypesForDirectiveState == 521 RequireTrustedTypesForDirectiveState::ENFORCE); 522 523 for (uint32_t i = 0; i < mPolicies.Length(); i++) { 524 if (!(trustedTypesRequired && 525 mPolicies[i]->allows(SCRIPT_SRC_DIRECTIVE, CSP_TRUSTED_TYPES_EVAL, 526 u""_ns)) && 527 !mPolicies[i]->allows(SCRIPT_SRC_DIRECTIVE, CSP_UNSAFE_EVAL, u""_ns)) { 528 // policy is violated: must report the violation and allow the inline 529 // script if the policy is report-only. 530 *outShouldReportViolation = true; 531 if (!mPolicies[i]->getReportOnlyFlag()) { 532 *outAllowsEval = false; 533 } 534 } 535 } 536 return NS_OK; 537 } 538 539 NS_IMETHODIMP 540 nsCSPContext::GetAllowsWasmEval(bool* outShouldReportViolation, 541 bool* outAllowsWasmEval) { 542 EnsureIPCPoliciesRead(); 543 *outShouldReportViolation = false; 544 *outAllowsWasmEval = true; 545 546 for (uint32_t i = 0; i < mPolicies.Length(); i++) { 547 // Either 'unsafe-eval' or 'wasm-unsafe-eval' can allow this 548 if (!mPolicies[i]->allows(SCRIPT_SRC_DIRECTIVE, CSP_WASM_UNSAFE_EVAL, 549 u""_ns) && 550 !mPolicies[i]->allows(SCRIPT_SRC_DIRECTIVE, CSP_UNSAFE_EVAL, u""_ns)) { 551 // policy is violated: must report the violation and allow the inline 552 // script if the policy is report-only. 553 *outShouldReportViolation = true; 554 if (!mPolicies[i]->getReportOnlyFlag()) { 555 *outAllowsWasmEval = false; 556 } 557 } 558 } 559 560 return NS_OK; 561 } 562 563 // Helper function to report inline violations 564 void nsCSPContext::ReportInlineViolation( 565 CSPDirective aDirective, Element* aTriggeringElement, 566 nsICSPEventListener* aCSPEventListener, const nsAString& aNonce, 567 bool aReportSample, const nsAString& aSourceCode, 568 const nsAString& aViolatedDirective, 569 const nsAString& aViolatedDirectiveString, CSPDirective aEffectiveDirective, 570 uint32_t aViolatedPolicyIndex, // TODO, use report only flag for that 571 uint32_t aLineNumber, uint32_t aColumnNumber) { 572 nsString observerSubject; 573 // if the nonce is non empty, then we report the nonce error, otherwise 574 // let's report the hash error; no need to report the unsafe-inline error 575 // anymore. 576 if (!aNonce.IsEmpty()) { 577 observerSubject = (aDirective == SCRIPT_SRC_ELEM_DIRECTIVE || 578 aDirective == SCRIPT_SRC_ATTR_DIRECTIVE) 579 ? NS_LITERAL_STRING_FROM_CSTRING( 580 SCRIPT_NONCE_VIOLATION_OBSERVER_TOPIC) 581 : NS_LITERAL_STRING_FROM_CSTRING( 582 STYLE_NONCE_VIOLATION_OBSERVER_TOPIC); 583 } else { 584 observerSubject = (aDirective == SCRIPT_SRC_ELEM_DIRECTIVE || 585 aDirective == SCRIPT_SRC_ATTR_DIRECTIVE) 586 ? NS_LITERAL_STRING_FROM_CSTRING( 587 SCRIPT_HASH_VIOLATION_OBSERVER_TOPIC) 588 : NS_LITERAL_STRING_FROM_CSTRING( 589 STYLE_HASH_VIOLATION_OBSERVER_TOPIC); 590 } 591 592 auto loc = JSCallingLocation::Get(); 593 if (!loc) { 594 nsCString sourceFile; 595 // use selfURI as the source 596 if (mSelfURI) { 597 mSelfURI->GetSpec(sourceFile); 598 loc.mResource = AsVariant(std::move(sourceFile)); 599 } 600 loc.mLine = aLineNumber; 601 loc.mColumn = aColumnNumber; 602 } 603 604 nsAutoCString hashSHA256; 605 // We optionally include the hash to create more helpful error messages. 606 nsCOMPtr<nsICryptoHash> hasher; 607 if (NS_SUCCEEDED( 608 NS_NewCryptoHash(nsICryptoHash::SHA256, getter_AddRefs(hasher)))) { 609 NS_ConvertUTF16toUTF8 source(aSourceCode); 610 if (NS_SUCCEEDED(hasher->Update( 611 reinterpret_cast<const uint8_t*>(source.get()), source.Length()))) { 612 (void)hasher->Finish(true, hashSHA256); 613 } 614 } 615 616 CSPViolationData cspViolationData{ 617 aViolatedPolicyIndex, 618 CSPViolationData::Resource{ 619 CSPViolationData::BlockedContentSource::Inline}, 620 aEffectiveDirective, 621 loc.FileName(), 622 loc.mLine, 623 loc.mColumn, 624 aTriggeringElement, 625 aSourceCode, 626 hashSHA256}; 627 628 AsyncReportViolation(aCSPEventListener, std::move(cspViolationData), 629 mSelfURI, // aOriginalURI 630 aViolatedDirective, // aViolatedDirective 631 aViolatedDirectiveString, 632 observerSubject, // aObserverSubject 633 aReportSample); // aReportSample 634 } 635 636 NS_IMETHODIMP 637 nsCSPContext::GetAllowsInline(CSPDirective aDirective, bool aHasUnsafeHash, 638 const nsAString& aNonce, bool aParserCreated, 639 Element* aTriggeringElement, 640 nsICSPEventListener* aCSPEventListener, 641 const nsAString& aSourceText, 642 uint32_t aLineNumber, uint32_t aColumnNumber, 643 bool* outAllowsInline) { 644 *outAllowsInline = true; 645 646 if (aDirective != SCRIPT_SRC_ELEM_DIRECTIVE && 647 aDirective != SCRIPT_SRC_ATTR_DIRECTIVE && 648 aDirective != STYLE_SRC_ELEM_DIRECTIVE && 649 aDirective != STYLE_SRC_ATTR_DIRECTIVE) { 650 MOZ_ASSERT(false, 651 "can only allow inline for (script/style)-src-(attr/elem)"); 652 return NS_OK; 653 } 654 655 EnsureIPCPoliciesRead(); 656 nsAutoString content; 657 658 // always iterate all policies, otherwise we might not send out all reports 659 for (uint32_t i = 0; i < mPolicies.Length(); i++) { 660 // https://w3c.github.io/webappsec-csp/#match-element-to-source-list 661 662 // Step 1. If §6.7.3.2 Does a source list allow all inline behavior for 663 // type? returns "Allows" given list and type, return "Matches". 664 if (mPolicies[i]->allowsAllInlineBehavior(aDirective)) { 665 continue; 666 } 667 668 // Step 2. If type is "script" or "style", and §6.7.3.1 Is element 669 // nonceable? returns "Nonceable" when executed upon element: 670 if ((aDirective == SCRIPT_SRC_ELEM_DIRECTIVE || 671 aDirective == STYLE_SRC_ELEM_DIRECTIVE) && 672 aTriggeringElement && !aNonce.IsEmpty()) { 673 #ifdef DEBUG 674 // NOTE: Folllowing Chrome "Is element nonceable?" doesn't apply to 675 // <style>. 676 if (aDirective == SCRIPT_SRC_ELEM_DIRECTIVE) { 677 // Our callers should have checked this. 678 MOZ_ASSERT(nsContentSecurityUtils::GetIsElementNonceableNonce( 679 *aTriggeringElement) == aNonce); 680 } 681 #endif 682 683 // Step 2.1. For each expression of list: [...] 684 if (mPolicies[i]->allows(aDirective, CSP_NONCE, aNonce)) { 685 continue; 686 } 687 } 688 689 // Check the content length to ensure the content is not allocated more than 690 // once. Even though we are in a for loop, it is probable that there is only 691 // one policy, so this check may be unnecessary. 692 if (content.IsEmpty()) { 693 if (aSourceText.IsVoid()) { 694 // Lazily retrieve the text of inline script, see bug 1376651. 695 nsCOMPtr<nsIScriptElement> element = 696 do_QueryInterface(aTriggeringElement); 697 MOZ_ASSERT(element); 698 element->GetScriptText(content); 699 } else { 700 content = aSourceText; 701 } 702 } 703 704 // Step 3. Let unsafe-hashes flag be false. 705 // Step 4. For each expression of list: [...] 706 bool unsafeHashesFlag = 707 mPolicies[i]->allows(aDirective, CSP_UNSAFE_HASHES, u""_ns); 708 709 // Step 5. If type is "script" or "style", or unsafe-hashes flag is true: 710 // 711 // aHasUnsafeHash is true for event handlers (type "script attribute"), 712 // style= attributes (type "style attribute") and the javascript: protocol. 713 if (!aHasUnsafeHash || unsafeHashesFlag) { 714 if (mPolicies[i]->allows(aDirective, CSP_HASH, content)) { 715 continue; 716 } 717 } 718 719 // TODO(Bug 1844290): Figure out how/if strict-dynamic for inline scripts is 720 // specified 721 bool allowed = false; 722 if ((aDirective == SCRIPT_SRC_ELEM_DIRECTIVE || 723 aDirective == SCRIPT_SRC_ATTR_DIRECTIVE) && 724 mPolicies[i]->allows(aDirective, CSP_STRICT_DYNAMIC, u""_ns)) { 725 allowed = !aParserCreated; 726 } 727 728 if (!allowed) { 729 // policy is violoated: deny the load unless policy is report only and 730 // report the violation. 731 if (!mPolicies[i]->getReportOnlyFlag()) { 732 *outAllowsInline = false; 733 } 734 nsAutoString violatedDirective; 735 nsAutoString violatedDirectiveString; 736 bool reportSample = false; 737 mPolicies[i]->getViolatedDirectiveInformation( 738 aDirective, violatedDirective, violatedDirectiveString, 739 &reportSample); 740 741 ReportInlineViolation(aDirective, aTriggeringElement, aCSPEventListener, 742 aNonce, reportSample, content, violatedDirective, 743 violatedDirectiveString, aDirective, i, aLineNumber, 744 aColumnNumber); 745 } 746 } 747 748 return NS_OK; 749 } 750 751 /** 752 * For each policy, log any violation on the Error Console and send a report 753 * if a report-uri is present in the policy 754 * 755 * @param aViolationType 756 * one of the VIOLATION_TYPE_* constants, e.g. inline-script or eval 757 * @param aSourceFile 758 * name of the source file containing the violation (if available) 759 * @param aContentSample 760 * sample of the violating content (to aid debugging) 761 * @param aLineNum 762 * source line number of the violation (if available) 763 * @param aColumnNum 764 * source column number of the violation (if available) 765 * @param aNonce 766 * (optional) If this is a nonce violation, include the nonce so we can 767 * recheck to determine which policies were violated and send the 768 * appropriate reports. 769 * @param aContent 770 * (optional) If this is a hash violation, include contents of the inline 771 * resource in the question so we can recheck the hash in order to 772 * determine which policies were violated and send the appropriate 773 * reports. 774 */ 775 NS_IMETHODIMP 776 nsCSPContext::LogViolationDetails( 777 uint16_t aViolationType, Element* aTriggeringElement, 778 nsICSPEventListener* aCSPEventListener, const nsACString& aSourceFile, 779 const nsAString& aScriptSample, int32_t aLineNum, int32_t aColumnNum, 780 const nsAString& aNonce, const nsAString& aContent) { 781 EnsureIPCPoliciesRead(); 782 783 CSPViolationData::BlockedContentSource blockedContentSource; 784 enum CSPKeyword keyword; 785 nsAutoString observerSubject; 786 if (aViolationType == nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL) { 787 blockedContentSource = CSPViolationData::BlockedContentSource::Eval; 788 keyword = CSP_UNSAFE_EVAL; 789 observerSubject.AssignLiteral(EVAL_VIOLATION_OBSERVER_TOPIC); 790 } else { 791 NS_ASSERTION( 792 aViolationType == nsIContentSecurityPolicy::VIOLATION_TYPE_WASM_EVAL, 793 "unexpected aViolationType"); 794 blockedContentSource = CSPViolationData::BlockedContentSource::WasmEval; 795 keyword = CSP_WASM_UNSAFE_EVAL; 796 observerSubject.AssignLiteral(WASM_EVAL_VIOLATION_OBSERVER_TOPIC); 797 } 798 799 for (uint32_t p = 0; p < mPolicies.Length(); p++) { 800 NS_ASSERTION(mPolicies[p], "null pointer in nsTArray<nsCSPPolicy>"); 801 802 if (mPolicies[p]->allows(SCRIPT_SRC_DIRECTIVE, keyword, u""_ns)) { 803 continue; 804 } 805 806 CSPViolationData cspViolationData{ 807 p, 808 CSPViolationData::Resource{blockedContentSource}, 809 /* aEffectiveDirective */ CSPDirective::SCRIPT_SRC_DIRECTIVE, 810 aSourceFile, 811 static_cast<uint32_t>(aLineNum), 812 static_cast<uint32_t>(aColumnNum), 813 aTriggeringElement, 814 aScriptSample}; 815 816 LogViolationDetailsUnchecked(aCSPEventListener, std::move(cspViolationData), 817 observerSubject, ForceReportSample::No); 818 } 819 return NS_OK; 820 } 821 822 void nsCSPContext::LogViolationDetailsUnchecked( 823 nsICSPEventListener* aCSPEventListener, 824 mozilla::dom::CSPViolationData&& aCSPViolationData, 825 const nsAString& aObserverSubject, ForceReportSample aForceReportSample) { 826 EnsureIPCPoliciesRead(); 827 828 nsAutoString violatedDirectiveName; 829 nsAutoString violatedDirectiveNameAndValue; 830 bool reportSample = false; 831 mPolicies[aCSPViolationData.mViolatedPolicyIndex] 832 ->getViolatedDirectiveInformation( 833 aCSPViolationData.mEffectiveDirective, violatedDirectiveName, 834 violatedDirectiveNameAndValue, &reportSample); 835 836 if (aForceReportSample == ForceReportSample::Yes) { 837 reportSample = true; 838 } 839 840 AsyncReportViolation(aCSPEventListener, std::move(aCSPViolationData), nullptr, 841 violatedDirectiveName, violatedDirectiveNameAndValue, 842 aObserverSubject, reportSample); 843 } 844 845 NS_IMETHODIMP nsCSPContext::LogTrustedTypesViolationDetailsUnchecked( 846 CSPViolationData&& aCSPViolationData, const nsAString& aObserverSubject, 847 nsICSPEventListener* aCSPEventListener) { 848 EnsureIPCPoliciesRead(); 849 850 // Trusted types don't support the "report-sample" keyword 851 // (https://github.com/w3c/trusted-types/issues/531#issuecomment-2194166146). 852 LogViolationDetailsUnchecked(aCSPEventListener, std::move(aCSPViolationData), 853 aObserverSubject, ForceReportSample::Yes); 854 return NS_OK; 855 } 856 857 #undef CASE_CHECK_AND_REPORT 858 859 NS_IMETHODIMP 860 nsCSPContext::SetRequestContextWithDocument(Document* aDocument) { 861 MOZ_ASSERT(aDocument, "Can't set context without doc"); 862 NS_ENSURE_ARG(aDocument); 863 864 mLoadingContext = do_GetWeakReference(aDocument); 865 mSelfURI = aDocument->GetDocumentURI(); 866 mLoadingPrincipal = aDocument->NodePrincipal(); 867 aDocument->GetReferrer(mReferrer); 868 mInnerWindowID = aDocument->InnerWindowID(); 869 // the innerWindowID is not available for CSPs delivered through the 870 // header at the time setReqeustContext is called - let's queue up 871 // console messages until it becomes available, see flushConsoleMessages 872 mQueueUpMessages = !mInnerWindowID; 873 mCallingChannelLoadGroup = aDocument->GetDocumentLoadGroup(); 874 // set the flag on the document for CSP telemetry 875 mEventTarget = GetMainThreadSerialEventTarget(); 876 877 MOZ_ASSERT(mLoadingPrincipal, "need a valid requestPrincipal"); 878 MOZ_ASSERT(mSelfURI, "need mSelfURI to translate 'self' into actual URI"); 879 return NS_OK; 880 } 881 882 NS_IMETHODIMP 883 nsCSPContext::SetRequestContextWithPrincipal(nsIPrincipal* aRequestPrincipal, 884 nsIURI* aSelfURI, 885 const nsACString& aReferrer, 886 uint64_t aInnerWindowId) { 887 NS_ENSURE_ARG(aRequestPrincipal); 888 889 mLoadingPrincipal = aRequestPrincipal; 890 mSelfURI = aSelfURI; 891 mReferrer = aReferrer; 892 mInnerWindowID = aInnerWindowId; 893 // if no document is available, then it also does not make sense to queue 894 // console messages sending messages to the browser console instead of the web 895 // console in that case. 896 mQueueUpMessages = false; 897 mCallingChannelLoadGroup = nullptr; 898 mEventTarget = nullptr; 899 900 MOZ_ASSERT(mLoadingPrincipal, "need a valid requestPrincipal"); 901 MOZ_ASSERT(mSelfURI, "need mSelfURI to translate 'self' into actual URI"); 902 return NS_OK; 903 } 904 905 nsIPrincipal* nsCSPContext::GetRequestPrincipal() { return mLoadingPrincipal; } 906 907 nsIURI* nsCSPContext::GetSelfURI() { return mSelfURI; } 908 909 NS_IMETHODIMP 910 nsCSPContext::GetReferrer(nsACString& outReferrer) { 911 outReferrer.Assign(mReferrer); 912 return NS_OK; 913 } 914 915 uint64_t nsCSPContext::GetInnerWindowID() { return mInnerWindowID; } 916 917 bool nsCSPContext::GetSkipAllowInlineStyleCheck() { 918 return mSkipAllowInlineStyleCheck; 919 } 920 921 void nsCSPContext::SetSkipAllowInlineStyleCheck( 922 bool aSkipAllowInlineStyleCheck) { 923 mSkipAllowInlineStyleCheck = aSkipAllowInlineStyleCheck; 924 } 925 926 NS_IMETHODIMP 927 nsCSPContext::EnsureEventTarget(nsIEventTarget* aEventTarget) { 928 NS_ENSURE_ARG(aEventTarget); 929 // Don't bother if we did have a valid event target (if the csp object is 930 // tied to a document in SetRequestContextWithDocument) 931 if (mEventTarget) { 932 return NS_OK; 933 } 934 935 mEventTarget = aEventTarget; 936 return NS_OK; 937 } 938 939 struct ConsoleMsgQueueElem { 940 nsString mMsg; 941 nsCString mSourceName; 942 nsString mSourceLine; 943 uint32_t mLineNumber; 944 uint32_t mColumnNumber; 945 uint32_t mSeverityFlag; 946 nsCString mCategory; 947 }; 948 949 void nsCSPContext::flushConsoleMessages() { 950 bool privateWindow = false; 951 952 // should flush messages even if doc is not available 953 nsCOMPtr<Document> doc = do_QueryReferent(mLoadingContext); 954 if (doc) { 955 mInnerWindowID = doc->InnerWindowID(); 956 privateWindow = 957 doc->NodePrincipal()->OriginAttributesRef().IsPrivateBrowsing(); 958 } 959 960 mQueueUpMessages = false; 961 962 for (uint32_t i = 0; i < mConsoleMsgQueue.Length(); i++) { 963 ConsoleMsgQueueElem& elem = mConsoleMsgQueue[i]; 964 CSP_LogMessage(elem.mMsg, elem.mSourceName, elem.mSourceLine, 965 elem.mLineNumber, elem.mColumnNumber, elem.mSeverityFlag, 966 elem.mCategory, mInnerWindowID, privateWindow); 967 } 968 mConsoleMsgQueue.Clear(); 969 } 970 971 void nsCSPContext::logToConsole(const char* aName, 972 const nsTArray<nsString>& aParams, 973 const nsACString& aSourceName, 974 const nsAString& aSourceLine, 975 uint32_t aLineNumber, uint32_t aColumnNumber, 976 uint32_t aSeverityFlag) { 977 // we are passing aName as the category so we can link to the 978 // appropriate MDN docs depending on the specific error. 979 nsDependentCString category(aName); 980 981 // Fallback 982 nsAutoCString spec; 983 if (aSourceName.IsEmpty() && mSelfURI) { 984 mSelfURI->GetSpec(spec); 985 } 986 987 const auto& sourceName = aSourceName.IsEmpty() ? spec : aSourceName; 988 989 // let's check if we have to queue up console messages 990 if (mQueueUpMessages) { 991 nsAutoString msg; 992 CSP_GetLocalizedStr(aName, aParams, msg); 993 ConsoleMsgQueueElem& elem = *mConsoleMsgQueue.AppendElement(); 994 elem.mMsg = msg; 995 elem.mSourceName = sourceName; 996 elem.mSourceLine = PromiseFlatString(aSourceLine); 997 elem.mLineNumber = aLineNumber; 998 elem.mColumnNumber = aColumnNumber; 999 elem.mSeverityFlag = aSeverityFlag; 1000 elem.mCategory = category; 1001 return; 1002 } 1003 1004 bool privateWindow = false; 1005 if (nsCOMPtr<Document> doc = do_QueryReferent(mLoadingContext)) { 1006 privateWindow = 1007 doc->NodePrincipal()->OriginAttributesRef().IsPrivateBrowsing(); 1008 } 1009 1010 CSP_LogLocalizedStr(aName, aParams, sourceName, aSourceLine, aLineNumber, 1011 aColumnNumber, aSeverityFlag, category, mInnerWindowID, 1012 privateWindow); 1013 } 1014 1015 /** 1016 * Strip URI for reporting according to: 1017 * https://w3c.github.io/webappsec-csp/#strip-url-for-use-in-reports 1018 * 1019 * @param aSelfURI 1020 * The URI of the CSP policy. Used for cross-origin checks. 1021 * @param aURI 1022 * The URI of the blocked resource. In case of a redirect, this it the 1023 * initial URI the request started out with, not the redirected URI. 1024 * @param aEffectiveDirective 1025 * The effective directive that triggered this report 1026 * @return The ASCII serialization of the uri to be reported ignoring 1027 * the ref part of the URI. 1028 */ 1029 void StripURIForReporting(nsIURI* aSelfURI, nsIURI* aURI, 1030 const nsAString& aEffectiveDirective, 1031 nsACString& outStrippedURI) { 1032 // Non-standard: For reports going to internal chrome: documents include the 1033 // whole URI. 1034 if (aSelfURI->SchemeIs("chrome")) { 1035 aURI->GetSpecIgnoringRef(outStrippedURI); 1036 return; 1037 } 1038 1039 // Step 1. If url’s scheme is not an HTTP(S) scheme, then return url’s scheme. 1040 // https://github.com/w3c/webappsec-csp/issues/735: We also allow WS(S) 1041 // schemes. 1042 if (!net::SchemeIsHttpOrHttps(aURI) && 1043 !(aURI->SchemeIs("ws") || aURI->SchemeIs("wss"))) { 1044 aURI->GetScheme(outStrippedURI); 1045 return; 1046 } 1047 1048 // Step 2. Set url’s fragment to the empty string. 1049 // Step 3. Set url’s username to the empty string. 1050 // Step 3. Set url’s password to the empty string. 1051 nsCOMPtr<nsIURI> stripped; 1052 if (NS_FAILED(NS_MutateURI(aURI).SetRef(""_ns).SetUserPass(""_ns).Finalize( 1053 stripped))) { 1054 // Mutating the URI failed for some reason, just return the scheme. 1055 aURI->GetScheme(outStrippedURI); 1056 return; 1057 } 1058 1059 // Non-standard: https://github.com/w3c/webappsec-csp/issues/735 1060 // For cross-origin URIs in frame-src also strip the path. 1061 // This prevents detailed tracking of pages loaded into an iframe 1062 // by the embedding page using a report-only policy. 1063 if (aEffectiveDirective.EqualsLiteral("frame-src") || 1064 aEffectiveDirective.EqualsLiteral("object-src")) { 1065 nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); 1066 if (NS_FAILED(ssm->CheckSameOriginURI(aSelfURI, stripped, false, false))) { 1067 stripped->GetPrePath(outStrippedURI); 1068 return; 1069 } 1070 } 1071 1072 // Step 4. Return the result of executing the URL serializer on url. 1073 stripped->GetSpec(outStrippedURI); 1074 } 1075 1076 nsresult nsCSPContext::GatherSecurityPolicyViolationEventData( 1077 nsIURI* aOriginalURI, const nsAString& aEffectiveDirective, 1078 const mozilla::dom::CSPViolationData& aCSPViolationData, bool aReportSample, 1079 mozilla::dom::SecurityPolicyViolationEventInit& aViolationEventInit) { 1080 EnsureIPCPoliciesRead(); 1081 NS_ENSURE_ARG_MAX(aCSPViolationData.mViolatedPolicyIndex, 1082 mPolicies.Length() - 1); 1083 1084 MOZ_ASSERT(ValidateDirectiveName(aEffectiveDirective), 1085 "Invalid directive name"); 1086 1087 nsresult rv; 1088 1089 // document-uri 1090 nsAutoCString reportDocumentURI; 1091 StripURIForReporting(mSelfURI, mSelfURI, aEffectiveDirective, 1092 reportDocumentURI); 1093 CopyUTF8toUTF16(reportDocumentURI, aViolationEventInit.mDocumentURI); 1094 1095 // referrer 1096 CopyUTF8toUTF16(mReferrer, aViolationEventInit.mReferrer); 1097 1098 // blocked-uri 1099 // Corresponds to 1100 // <https://w3c.github.io/webappsec-csp/#obtain-violation-blocked-uri>. 1101 if (aCSPViolationData.mResource.is<nsCOMPtr<nsIURI>>()) { 1102 nsAutoCString reportBlockedURI; 1103 StripURIForReporting( 1104 mSelfURI, 1105 aOriginalURI ? aOriginalURI 1106 : aCSPViolationData.mResource.as<nsCOMPtr<nsIURI>>().get(), 1107 aEffectiveDirective, reportBlockedURI); 1108 CopyUTF8toUTF16(reportBlockedURI, aViolationEventInit.mBlockedURI); 1109 } else { 1110 nsAutoCString blockedContentSource; 1111 BlockedContentSourceToString( 1112 aCSPViolationData.mResource 1113 .as<CSPViolationData::BlockedContentSource>(), 1114 blockedContentSource); 1115 CopyUTF8toUTF16(blockedContentSource, aViolationEventInit.mBlockedURI); 1116 } 1117 1118 // effective-directive 1119 // The name of the policy directive that was violated. 1120 aViolationEventInit.mEffectiveDirective = aEffectiveDirective; 1121 1122 // violated-directive 1123 // In CSP2, the policy directive that was violated, as it appears in the 1124 // policy. In CSP3, the same as effective-directive. 1125 aViolationEventInit.mViolatedDirective = aEffectiveDirective; 1126 1127 // original-policy 1128 nsAutoString originalPolicy; 1129 rv = this->GetPolicyString(aCSPViolationData.mViolatedPolicyIndex, 1130 originalPolicy); 1131 NS_ENSURE_SUCCESS(rv, rv); 1132 aViolationEventInit.mOriginalPolicy = originalPolicy; 1133 1134 // source-file 1135 if (!aCSPViolationData.mSourceFile.IsEmpty()) { 1136 // if aSourceFile is a URI, we have to make sure to strip fragments 1137 nsCOMPtr<nsIURI> sourceURI; 1138 NS_NewURI(getter_AddRefs(sourceURI), aCSPViolationData.mSourceFile); 1139 if (sourceURI) { 1140 nsAutoCString stripped; 1141 StripURIForReporting(mSelfURI, sourceURI, aEffectiveDirective, stripped); 1142 CopyUTF8toUTF16(stripped, aViolationEventInit.mSourceFile); 1143 } else { 1144 CopyUTF8toUTF16(aCSPViolationData.mSourceFile, 1145 aViolationEventInit.mSourceFile); 1146 } 1147 } 1148 1149 // sample (already truncated) 1150 aViolationEventInit.mSample = 1151 aReportSample ? aCSPViolationData.mSample : EmptyString(); 1152 1153 // disposition 1154 aViolationEventInit.mDisposition = 1155 mPolicies[aCSPViolationData.mViolatedPolicyIndex]->getReportOnlyFlag() 1156 ? mozilla::dom::SecurityPolicyViolationEventDisposition::Report 1157 : mozilla::dom::SecurityPolicyViolationEventDisposition::Enforce; 1158 1159 // status-code 1160 uint16_t statusCode = 0; 1161 { 1162 nsCOMPtr<Document> doc = do_QueryReferent(mLoadingContext); 1163 if (doc) { 1164 nsCOMPtr<nsIHttpChannel> channel = do_QueryInterface(doc->GetChannel()); 1165 if (channel) { 1166 uint32_t responseStatus = 0; 1167 nsresult rv = channel->GetResponseStatus(&responseStatus); 1168 if (NS_SUCCEEDED(rv) && (responseStatus <= UINT16_MAX)) { 1169 statusCode = static_cast<uint16_t>(responseStatus); 1170 } 1171 } 1172 } 1173 } 1174 aViolationEventInit.mStatusCode = statusCode; 1175 1176 // line-number 1177 aViolationEventInit.mLineNumber = aCSPViolationData.mLineNumber; 1178 1179 // column-number 1180 aViolationEventInit.mColumnNumber = aCSPViolationData.mColumnNumber; 1181 1182 aViolationEventInit.mBubbles = true; 1183 aViolationEventInit.mComposed = true; 1184 1185 return NS_OK; 1186 } 1187 1188 bool nsCSPContext::ShouldThrottleReport( 1189 const mozilla::dom::SecurityPolicyViolationEventInit& aViolationEventInit) { 1190 // Fetch the rate limit preference. 1191 const uint32_t kLimitCount = 1192 StaticPrefs::security_csp_reporting_limit_count(); 1193 1194 // Disable throttling if the preference is set to 0. 1195 if (kLimitCount == 0) { 1196 return false; 1197 } 1198 1199 const uint32_t kTimeSpanSeconds = 2; 1200 TimeDuration throttleSpan = TimeDuration::FromSeconds(kTimeSpanSeconds); 1201 if (mSendReportLimitSpanStart.IsNull() || 1202 ((TimeStamp::Now() - mSendReportLimitSpanStart) > throttleSpan)) { 1203 // Initial call or timespan exceeded, reset counter and timespan. 1204 mSendReportLimitSpanStart = TimeStamp::Now(); 1205 mSendReportLimitCount = 1; 1206 // Also make sure we warn about omitted messages. (XXX or only do this once 1207 // per context?) 1208 mWarnedAboutTooManyReports = false; 1209 return false; 1210 } 1211 1212 if (mSendReportLimitCount < kLimitCount) { 1213 mSendReportLimitCount++; 1214 return false; 1215 } 1216 1217 // Rate limit reached 1218 if (!mWarnedAboutTooManyReports) { 1219 logToConsole("tooManyReports", {}, 1220 NS_ConvertUTF16toUTF8(aViolationEventInit.mSourceFile), 1221 aViolationEventInit.mSample, aViolationEventInit.mLineNumber, 1222 aViolationEventInit.mColumnNumber, nsIScriptError::errorFlag); 1223 mWarnedAboutTooManyReports = true; 1224 } 1225 return true; 1226 } 1227 1228 nsresult nsCSPContext::SendReports( 1229 const mozilla::dom::SecurityPolicyViolationEventInit& aViolationEventInit, 1230 uint32_t aViolatedPolicyIndex) { 1231 EnsureIPCPoliciesRead(); 1232 NS_ENSURE_ARG_MAX(aViolatedPolicyIndex, mPolicies.Length() - 1); 1233 1234 if (!StaticPrefs::security_csp_reporting_enabled() || 1235 ShouldThrottleReport(aViolationEventInit)) { 1236 return NS_OK; 1237 } 1238 1239 nsAutoString reportGroup; 1240 mPolicies[aViolatedPolicyIndex]->getReportGroup(reportGroup); 1241 1242 // CSP Level 3 Reporting 1243 if (StaticPrefs::dom_reporting_enabled() && !reportGroup.IsEmpty()) { 1244 return SendReportsToEndpoints(reportGroup, aViolationEventInit); 1245 } 1246 1247 nsTArray<nsString> reportURIs; 1248 mPolicies[aViolatedPolicyIndex]->getReportURIs(reportURIs); 1249 1250 // [Deprecated] CSP Level 2 Reporting 1251 if (!reportURIs.IsEmpty()) { 1252 return SendReportsToURIs(reportURIs, aViolationEventInit); 1253 } 1254 1255 return NS_OK; 1256 } 1257 1258 nsresult nsCSPContext::SendReportsToEndpoints( 1259 nsAutoString& reportGroup, 1260 const mozilla::dom::SecurityPolicyViolationEventInit& aViolationEventInit) { 1261 nsCOMPtr<Document> doc = do_QueryReferent(mLoadingContext); 1262 if (!doc) { 1263 return NS_ERROR_FAILURE; 1264 } 1265 nsPIDOMWindowInner* window = doc->GetInnerWindow(); 1266 if (NS_WARN_IF(!window)) { 1267 return NS_ERROR_FAILURE; 1268 } 1269 1270 RefPtr<CSPViolationReportBody> body = 1271 new CSPViolationReportBody(window->AsGlobal(), aViolationEventInit); 1272 1273 ReportingUtils::Report(window->AsGlobal(), nsGkAtoms::cspViolation, 1274 reportGroup, aViolationEventInit.mDocumentURI, body); 1275 return NS_OK; 1276 } 1277 1278 nsresult nsCSPContext::SendReportsToURIs( 1279 const nsTArray<nsString>& reportURIs, 1280 const mozilla::dom::SecurityPolicyViolationEventInit& aViolationEventInit) { 1281 dom::CSPReport report; 1282 1283 // blocked-uri 1284 report.mCsp_report.mBlocked_uri = aViolationEventInit.mBlockedURI; 1285 1286 // document-uri 1287 report.mCsp_report.mDocument_uri = aViolationEventInit.mDocumentURI; 1288 1289 // original-policy 1290 report.mCsp_report.mOriginal_policy = aViolationEventInit.mOriginalPolicy; 1291 1292 // referrer 1293 report.mCsp_report.mReferrer = aViolationEventInit.mReferrer; 1294 1295 // effective-directive 1296 report.mCsp_report.mEffective_directive = 1297 aViolationEventInit.mEffectiveDirective; 1298 1299 // violated-directive 1300 report.mCsp_report.mViolated_directive = 1301 aViolationEventInit.mEffectiveDirective; 1302 1303 // disposition 1304 report.mCsp_report.mDisposition = aViolationEventInit.mDisposition; 1305 1306 // status-code 1307 report.mCsp_report.mStatus_code = aViolationEventInit.mStatusCode; 1308 1309 // source-file 1310 if (!aViolationEventInit.mSourceFile.IsEmpty()) { 1311 report.mCsp_report.mSource_file.Construct(); 1312 CopyUTF16toUTF8(aViolationEventInit.mSourceFile, 1313 report.mCsp_report.mSource_file.Value()); 1314 } 1315 1316 // script-sample 1317 if (!aViolationEventInit.mSample.IsEmpty()) { 1318 report.mCsp_report.mScript_sample.Construct(); 1319 report.mCsp_report.mScript_sample.Value() = aViolationEventInit.mSample; 1320 } 1321 1322 // line-number 1323 if (aViolationEventInit.mLineNumber != 0) { 1324 report.mCsp_report.mLine_number.Construct(); 1325 report.mCsp_report.mLine_number.Value() = aViolationEventInit.mLineNumber; 1326 } 1327 1328 if (aViolationEventInit.mColumnNumber != 0) { 1329 report.mCsp_report.mColumn_number.Construct(); 1330 report.mCsp_report.mColumn_number.Value() = 1331 aViolationEventInit.mColumnNumber; 1332 } 1333 1334 nsString csp_report; 1335 if (!report.ToJSON(csp_report)) { 1336 return NS_ERROR_FAILURE; 1337 } 1338 1339 // ---------- Assembled, now send it to all the report URIs ----------- // 1340 nsCOMPtr<Document> doc = do_QueryReferent(mLoadingContext); 1341 nsCOMPtr<nsIURI> reportURI; 1342 nsCOMPtr<nsIChannel> reportChannel; 1343 1344 nsresult rv; 1345 for (uint32_t r = 0; r < reportURIs.Length(); r++) { 1346 NS_ConvertUTF16toUTF8 reportURICstring(reportURIs[r]); 1347 // try to create a new uri from every report-uri string 1348 rv = NS_NewURI(getter_AddRefs(reportURI), reportURIs[r]); 1349 if (NS_FAILED(rv)) { 1350 AutoTArray<nsString, 1> params = {reportURIs[r]}; 1351 CSPCONTEXTLOG(("Could not create nsIURI for report URI %s", 1352 reportURICstring.get())); 1353 logToConsole("triedToSendReport", params, 1354 NS_ConvertUTF16toUTF8(aViolationEventInit.mSourceFile), 1355 aViolationEventInit.mSample, aViolationEventInit.mLineNumber, 1356 aViolationEventInit.mColumnNumber, 1357 nsIScriptError::errorFlag); 1358 continue; // don't return yet, there may be more URIs 1359 } 1360 1361 // try to create a new channel for every report-uri 1362 if (doc) { 1363 rv = 1364 NS_NewChannel(getter_AddRefs(reportChannel), reportURI, doc, 1365 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, 1366 nsIContentPolicy::TYPE_CSP_REPORT); 1367 } else { 1368 rv = NS_NewChannel( 1369 getter_AddRefs(reportChannel), reportURI, mLoadingPrincipal, 1370 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, 1371 nsIContentPolicy::TYPE_CSP_REPORT); 1372 } 1373 1374 if (NS_FAILED(rv)) { 1375 CSPCONTEXTLOG(("Could not create new channel for report URI %s", 1376 reportURICstring.get())); 1377 continue; // don't return yet, there may be more URIs 1378 } 1379 1380 // log a warning to console if scheme is not http or https 1381 if (!net::SchemeIsHttpOrHttps(reportURI)) { 1382 AutoTArray<nsString, 1> params = {reportURIs[r]}; 1383 logToConsole("reportURInotHttpsOrHttp2", params, 1384 NS_ConvertUTF16toUTF8(aViolationEventInit.mSourceFile), 1385 aViolationEventInit.mSample, aViolationEventInit.mLineNumber, 1386 aViolationEventInit.mColumnNumber, 1387 nsIScriptError::errorFlag); 1388 continue; 1389 } 1390 1391 // make sure this is an anonymous request (no cookies) so in case the 1392 // policy URI is injected, it can't be abused for CSRF. 1393 nsLoadFlags flags; 1394 rv = reportChannel->GetLoadFlags(&flags); 1395 NS_ENSURE_SUCCESS(rv, rv); 1396 flags |= nsIRequest::LOAD_ANONYMOUS | nsIChannel::LOAD_BACKGROUND | 1397 nsIChannel::LOAD_BYPASS_SERVICE_WORKER; 1398 rv = reportChannel->SetLoadFlags(flags); 1399 NS_ENSURE_SUCCESS(rv, rv); 1400 1401 // we need to set an nsIChannelEventSink on the channel object 1402 // so we can tell it to not follow redirects when posting the reports 1403 RefPtr<CSPReportRedirectSink> reportSink = new CSPReportRedirectSink(); 1404 if (doc && doc->GetDocShell()) { 1405 nsCOMPtr<nsINetworkInterceptController> interceptController = 1406 do_QueryInterface(doc->GetDocShell()); 1407 reportSink->SetInterceptController(interceptController); 1408 } 1409 reportChannel->SetNotificationCallbacks(reportSink); 1410 1411 // apply the loadgroup taken by setRequestContextWithDocument. If there's 1412 // no loadgroup, AsyncOpen will fail on process-split necko (since the 1413 // channel cannot query the iBrowserChild). 1414 rv = reportChannel->SetLoadGroup(mCallingChannelLoadGroup); 1415 NS_ENSURE_SUCCESS(rv, rv); 1416 1417 // wire in the string input stream to send the report 1418 nsCOMPtr<nsIStringInputStream> sis( 1419 do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID)); 1420 NS_ASSERTION(sis, 1421 "nsIStringInputStream is needed but not available to send CSP " 1422 "violation reports"); 1423 rv = sis->SetUTF8Data(NS_ConvertUTF16toUTF8(csp_report)); 1424 NS_ENSURE_SUCCESS(rv, rv); 1425 1426 nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(reportChannel)); 1427 if (!uploadChannel) { 1428 // It's possible the URI provided can't be uploaded to, in which case 1429 // we skip this one. We'll already have warned about a non-HTTP URI 1430 // earlier. 1431 continue; 1432 } 1433 1434 rv = uploadChannel->SetUploadStream(sis, "application/csp-report"_ns, -1); 1435 NS_ENSURE_SUCCESS(rv, rv); 1436 1437 // if this is an HTTP channel, set the request method to post 1438 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(reportChannel)); 1439 if (httpChannel) { 1440 rv = httpChannel->SetRequestMethod("POST"_ns); 1441 MOZ_ASSERT(NS_SUCCEEDED(rv)); 1442 } 1443 1444 RefPtr<CSPViolationReportListener> listener = 1445 new CSPViolationReportListener(); 1446 rv = reportChannel->AsyncOpen(listener); 1447 1448 // AsyncOpen should not fail, but could if there's no load group (like if 1449 // SetRequestContextWith{Document,Principal} is not given a channel). This 1450 // should fail quietly and not return an error since it's really ok if 1451 // reports don't go out, but it's good to log the error locally. 1452 1453 if (NS_FAILED(rv)) { 1454 AutoTArray<nsString, 1> params = {reportURIs[r]}; 1455 CSPCONTEXTLOG(("AsyncOpen failed for report URI %s", 1456 NS_ConvertUTF16toUTF8(params[0]).get())); 1457 logToConsole("triedToSendReport", params, 1458 NS_ConvertUTF16toUTF8(aViolationEventInit.mSourceFile), 1459 aViolationEventInit.mSample, aViolationEventInit.mLineNumber, 1460 aViolationEventInit.mColumnNumber, 1461 nsIScriptError::errorFlag); 1462 } else { 1463 CSPCONTEXTLOG( 1464 ("Sent violation report to URI %s", reportURICstring.get())); 1465 } 1466 } 1467 return NS_OK; 1468 } 1469 1470 void nsCSPContext::HandleInternalPageViolation( 1471 const CSPViolationData& aCSPViolationData, 1472 const SecurityPolicyViolationEventInit& aInit, 1473 const nsAString& aViolatedDirectiveNameAndValue) { 1474 if (!mSelfURI || !mSelfURI->SchemeIs("chrome")) { 1475 return; 1476 } 1477 1478 nsAutoCString selfURISpec; 1479 mSelfURI->GetSpec(selfURISpec); 1480 1481 glean::security::CspViolationInternalPageExtra extra; 1482 extra.directive = Some(NS_ConvertUTF16toUTF8(aInit.mEffectiveDirective)); 1483 1484 FilenameTypeAndDetails self = 1485 nsContentSecurityUtils::FilenameToFilenameType(selfURISpec, true); 1486 extra.selftype = Some(self.first); 1487 extra.selfdetails = self.second; 1488 1489 FilenameTypeAndDetails source = 1490 nsContentSecurityUtils::FilenameToFilenameType( 1491 NS_ConvertUTF16toUTF8(aInit.mSourceFile), true); 1492 extra.sourcetype = Some(source.first); 1493 extra.sourcedetails = source.second; 1494 1495 extra.linenumber = Some(aInit.mLineNumber); 1496 extra.columnnumber = Some(aInit.mColumnNumber); 1497 1498 // Don't collect samples for code that is probably not shipped by us. 1499 if (source.first.EqualsLiteral("chromeuri") || 1500 source.first.EqualsLiteral("resourceuri") || 1501 source.first.EqualsLiteral("abouturi")) { 1502 // aInit's sample requires the 'report-sample' keyword. 1503 extra.sample = Some(NS_ConvertUTF16toUTF8(aCSPViolationData.mSample)); 1504 } 1505 1506 if (aInit.mBlockedURI.EqualsLiteral("inline")) { 1507 extra.blockeduritype = Some("inline"_ns); 1508 } else { 1509 FilenameTypeAndDetails blocked = 1510 nsContentSecurityUtils::FilenameToFilenameType( 1511 NS_ConvertUTF16toUTF8(aInit.mBlockedURI), true); 1512 extra.blockeduritype = Some(blocked.first); 1513 extra.blockeduridetails = blocked.second; 1514 } 1515 1516 glean::security::csp_violation_internal_page.Record(Some(extra)); 1517 1518 #ifdef DEBUG 1519 if (!StaticPrefs::security_csp_testing_allow_internal_csp_violation()) { 1520 NS_ConvertUTF16toUTF8 directive(aViolatedDirectiveNameAndValue); 1521 nsAutoCString effectiveDirective; 1522 effectiveDirective.Assign( 1523 CSP_CSPDirectiveToString(aCSPViolationData.mEffectiveDirective)); 1524 nsFmtCString s( 1525 FMT_STRING("Unexpected CSP violation on page {} caused by {} (URL: {}, " 1526 "Source: {}) violating the directive: \"{}\" (file: {} " 1527 "line: {}). For debugging you can set the pref " 1528 "security.csp.testing.allow_internal_csp_violation=true."), 1529 selfURISpec.get(), effectiveDirective.get(), 1530 NS_ConvertUTF16toUTF8(aInit.mBlockedURI).get(), 1531 NS_ConvertUTF16toUTF8(aCSPViolationData.mSample).get(), directive.get(), 1532 aCSPViolationData.mSourceFile.get(), aCSPViolationData.mLineNumber); 1533 MOZ_CRASH_UNSAFE(s.get()); 1534 } 1535 #endif 1536 } 1537 1538 nsresult nsCSPContext::FireViolationEvent( 1539 Element* aTriggeringElement, nsICSPEventListener* aCSPEventListener, 1540 const mozilla::dom::SecurityPolicyViolationEventInit& aViolationEventInit) { 1541 if (aCSPEventListener) { 1542 nsAutoString json; 1543 if (aViolationEventInit.ToJSON(json)) { 1544 aCSPEventListener->OnCSPViolationEvent(json); 1545 } 1546 1547 return NS_OK; 1548 } 1549 1550 // 1. If target is not null, and global is a Window, and target’s 1551 // shadow-including root is not global’s associated Document, set target to 1552 // null. 1553 RefPtr<EventTarget> eventTarget = aTriggeringElement; 1554 1555 nsCOMPtr<Document> doc = do_QueryReferent(mLoadingContext); 1556 if (doc && aTriggeringElement && 1557 aTriggeringElement->GetComposedDoc() != doc) { 1558 eventTarget = nullptr; 1559 } 1560 1561 if (!eventTarget) { 1562 // If target is a Window, set target to target’s associated Document. 1563 eventTarget = doc; 1564 } 1565 1566 if (!eventTarget && mInnerWindowID && XRE_IsParentProcess()) { 1567 if (RefPtr<WindowGlobalParent> parent = 1568 WindowGlobalParent::GetByInnerWindowId(mInnerWindowID)) { 1569 nsAutoString json; 1570 if (aViolationEventInit.ToJSON(json)) { 1571 (void)parent->SendDispatchSecurityPolicyViolation(json); 1572 } 1573 } 1574 return NS_OK; 1575 } 1576 1577 if (!eventTarget) { 1578 // If we are here, we are probably dealing with workers. Those are handled 1579 // via nsICSPEventListener. Nothing to do here. 1580 return NS_OK; 1581 } 1582 1583 RefPtr<mozilla::dom::Event> event = 1584 mozilla::dom::SecurityPolicyViolationEvent::Constructor( 1585 eventTarget, u"securitypolicyviolation"_ns, aViolationEventInit); 1586 event->SetTrusted(true); 1587 1588 ErrorResult rv; 1589 eventTarget->DispatchEvent(*event, rv); 1590 return rv.StealNSResult(); 1591 } 1592 1593 /** 1594 * Dispatched from the main thread to send reports for one CSP violation. 1595 */ 1596 class CSPReportSenderRunnable final : public Runnable { 1597 public: 1598 CSPReportSenderRunnable(nsICSPEventListener* aCSPEventListener, 1599 CSPViolationData&& aCSPViolationData, 1600 nsIURI* aOriginalURI, bool aReportOnlyFlag, 1601 const nsAString& aViolatedDirectiveName, 1602 const nsAString& aViolatedDirectiveNameAndValue, 1603 const nsAString& aObserverSubject, bool aReportSample, 1604 nsCSPContext* aCSPContext) 1605 : mozilla::Runnable("CSPReportSenderRunnable"), 1606 mCSPEventListener(aCSPEventListener), 1607 mCSPViolationData(std::move(aCSPViolationData)), 1608 mOriginalURI(aOriginalURI), 1609 mReportOnlyFlag(aReportOnlyFlag), 1610 mReportSample(aReportSample), 1611 mViolatedDirectiveName(aViolatedDirectiveName), 1612 mViolatedDirectiveNameAndValue(aViolatedDirectiveNameAndValue), 1613 mCSPContext(aCSPContext) { 1614 NS_ASSERTION(!aViolatedDirectiveName.IsEmpty(), 1615 "Can not send reports without a violated directive"); 1616 // the observer subject is an nsISupports: either an nsISupportsCString 1617 // from the arg passed in directly, or if that's empty, it's the blocked 1618 // source. 1619 if (aObserverSubject.IsEmpty() && 1620 mCSPViolationData.mResource.is<nsCOMPtr<nsIURI>>()) { 1621 mObserverSubject = mCSPViolationData.mResource.as<nsCOMPtr<nsIURI>>(); 1622 return; 1623 } 1624 1625 nsAutoCString subject; 1626 if (aObserverSubject.IsEmpty()) { 1627 BlockedContentSourceToString( 1628 mCSPViolationData.BlockedContentSourceOrUnknown(), subject); 1629 } else { 1630 CopyUTF16toUTF8(aObserverSubject, subject); 1631 } 1632 1633 nsCOMPtr<nsISupportsCString> supportscstr = 1634 do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID); 1635 if (supportscstr) { 1636 supportscstr->SetData(subject); 1637 mObserverSubject = do_QueryInterface(supportscstr); 1638 } 1639 } 1640 1641 NS_IMETHOD Run() override { 1642 MOZ_ASSERT(NS_IsMainThread()); 1643 1644 // 0) prepare violation data 1645 mozilla::dom::SecurityPolicyViolationEventInit init; 1646 1647 nsAutoString effectiveDirective; 1648 effectiveDirective.AssignASCII( 1649 CSP_CSPDirectiveToString(mCSPViolationData.mEffectiveDirective)); 1650 1651 nsresult rv = mCSPContext->GatherSecurityPolicyViolationEventData( 1652 mOriginalURI, effectiveDirective, mCSPViolationData, mReportSample, 1653 init); 1654 NS_ENSURE_SUCCESS(rv, rv); 1655 1656 // 1) notify observers 1657 nsCOMPtr<nsIObserverService> observerService = 1658 mozilla::services::GetObserverService(); 1659 if (mObserverSubject && observerService) { 1660 rv = observerService->NotifyObservers( 1661 mObserverSubject, CSP_VIOLATION_TOPIC, mViolatedDirectiveName.get()); 1662 NS_ENSURE_SUCCESS(rv, rv); 1663 } 1664 1665 // 2) send reports for the policy that was violated 1666 mCSPContext->SendReports(init, mCSPViolationData.mViolatedPolicyIndex); 1667 1668 // 3) log to console (one per policy violation) 1669 ReportToConsole(); 1670 1671 // 4) For internal pages we might send the failure to telemetry or crash. 1672 mCSPContext->HandleInternalPageViolation(mCSPViolationData, init, 1673 mViolatedDirectiveNameAndValue); 1674 1675 // 5) fire violation event 1676 // A frame-ancestors violation has occurred, but we should not dispatch 1677 // the violation event to a potentially cross-origin ancestor. 1678 if (!mViolatedDirectiveName.EqualsLiteral("frame-ancestors")) { 1679 mCSPContext->FireViolationEvent(mCSPViolationData.mElement, 1680 mCSPEventListener, init); 1681 } 1682 1683 return NS_OK; 1684 } 1685 1686 private: 1687 void ReportToConsole() const { 1688 NS_ConvertUTF8toUTF16 effectiveDirective( 1689 CSP_CSPDirectiveToString(mCSPViolationData.mEffectiveDirective)); 1690 1691 const auto blockedContentSource = 1692 mCSPViolationData.BlockedContentSourceOrUnknown(); 1693 1694 switch (blockedContentSource) { 1695 case CSPViolationData::BlockedContentSource::Inline: { 1696 const char* errorName = nullptr; 1697 if (mCSPViolationData.mEffectiveDirective == 1698 CSPDirective::STYLE_SRC_ATTR_DIRECTIVE || 1699 mCSPViolationData.mEffectiveDirective == 1700 CSPDirective::STYLE_SRC_ELEM_DIRECTIVE) { 1701 errorName = mReportOnlyFlag ? "CSPROInlineStyleViolation2" 1702 : "CSPInlineStyleViolation2"; 1703 } else if (mCSPViolationData.mEffectiveDirective == 1704 CSPDirective::SCRIPT_SRC_ATTR_DIRECTIVE) { 1705 errorName = mReportOnlyFlag ? "CSPROEventHandlerScriptViolation2" 1706 : "CSPEventHandlerScriptViolation2"; 1707 } else { 1708 MOZ_ASSERT(mCSPViolationData.mEffectiveDirective == 1709 CSPDirective::SCRIPT_SRC_ELEM_DIRECTIVE); 1710 errorName = mReportOnlyFlag ? "CSPROInlineScriptViolation2" 1711 : "CSPInlineScriptViolation2"; 1712 } 1713 1714 AutoTArray<nsString, 3> params = { 1715 mViolatedDirectiveNameAndValue, effectiveDirective, 1716 NS_ConvertUTF8toUTF16(mCSPViolationData.mHashSHA256)}; 1717 mCSPContext->logToConsole( 1718 errorName, params, mCSPViolationData.mSourceFile, 1719 mCSPViolationData.mSample, mCSPViolationData.mLineNumber, 1720 mCSPViolationData.mColumnNumber, nsIScriptError::errorFlag); 1721 break; 1722 } 1723 1724 case CSPViolationData::BlockedContentSource::Eval: { 1725 AutoTArray<nsString, 2> params = {mViolatedDirectiveNameAndValue, 1726 effectiveDirective}; 1727 mCSPContext->logToConsole( 1728 mReportOnlyFlag ? "CSPROEvalScriptViolation" 1729 : "CSPEvalScriptViolation", 1730 params, mCSPViolationData.mSourceFile, mCSPViolationData.mSample, 1731 mCSPViolationData.mLineNumber, mCSPViolationData.mColumnNumber, 1732 nsIScriptError::errorFlag); 1733 break; 1734 } 1735 1736 case CSPViolationData::BlockedContentSource::WasmEval: { 1737 AutoTArray<nsString, 2> params = {mViolatedDirectiveNameAndValue, 1738 effectiveDirective}; 1739 mCSPContext->logToConsole( 1740 mReportOnlyFlag ? "CSPROWasmEvalScriptViolation" 1741 : "CSPWasmEvalScriptViolation", 1742 params, mCSPViolationData.mSourceFile, mCSPViolationData.mSample, 1743 mCSPViolationData.mLineNumber, mCSPViolationData.mColumnNumber, 1744 nsIScriptError::errorFlag); 1745 break; 1746 } 1747 1748 case CSPViolationData::BlockedContentSource::TrustedTypesPolicy: { 1749 AutoTArray<nsString, 1> params = {mViolatedDirectiveNameAndValue}; 1750 1751 mCSPContext->logToConsole( 1752 mReportOnlyFlag ? "CSPROTrustedTypesPolicyViolation" 1753 : "CSPTrustedTypesPolicyViolation", 1754 params, mCSPViolationData.mSourceFile, mCSPViolationData.mSample, 1755 mCSPViolationData.mLineNumber, mCSPViolationData.mColumnNumber, 1756 nsIScriptError::errorFlag); 1757 break; 1758 } 1759 1760 case CSPViolationData::BlockedContentSource::TrustedTypesSink: { 1761 mCSPContext->logToConsole( 1762 mReportOnlyFlag ? "CSPROTrustedTypesSinkViolation" 1763 : "CSPTrustedTypesSinkViolation", 1764 {}, mCSPViolationData.mSourceFile, mCSPViolationData.mSample, 1765 mCSPViolationData.mLineNumber, mCSPViolationData.mColumnNumber, 1766 nsIScriptError::errorFlag); 1767 break; 1768 } 1769 1770 case CSPViolationData::BlockedContentSource::Self: 1771 case CSPViolationData::BlockedContentSource::Unknown: { 1772 nsAutoString source(u"<unknown>"_ns); 1773 if (mCSPViolationData.mResource.is<nsCOMPtr<nsIURI>>()) { 1774 nsAutoCString uri; 1775 auto blockedURI = mCSPViolationData.mResource.as<nsCOMPtr<nsIURI>>(); 1776 blockedURI->GetSpec(uri); 1777 1778 if (blockedURI->SchemeIs("data") && 1779 uri.Length() > nsCSPContext::ScriptSampleMaxLength()) { 1780 uri.Truncate(nsCSPContext::ScriptSampleMaxLength()); 1781 uri.Append( 1782 NS_ConvertUTF16toUTF8(nsContentUtils::GetLocalizedEllipsis())); 1783 } 1784 1785 if (!uri.IsEmpty()) { 1786 CopyUTF8toUTF16(uri, source); 1787 } 1788 } 1789 1790 const char* errorName = nullptr; 1791 switch (mCSPViolationData.mEffectiveDirective) { 1792 case CSPDirective::STYLE_SRC_ELEM_DIRECTIVE: 1793 errorName = 1794 mReportOnlyFlag ? "CSPROStyleViolation" : "CSPStyleViolation"; 1795 break; 1796 case CSPDirective::SCRIPT_SRC_ELEM_DIRECTIVE: 1797 errorName = 1798 mReportOnlyFlag ? "CSPROScriptViolation" : "CSPScriptViolation"; 1799 break; 1800 case CSPDirective::WORKER_SRC_DIRECTIVE: 1801 errorName = 1802 mReportOnlyFlag ? "CSPROWorkerViolation" : "CSPWorkerViolation"; 1803 break; 1804 default: 1805 errorName = mReportOnlyFlag ? "CSPROGenericViolation" 1806 : "CSPGenericViolation"; 1807 } 1808 1809 AutoTArray<nsString, 3> params = {mViolatedDirectiveNameAndValue, 1810 source, effectiveDirective}; 1811 mCSPContext->logToConsole( 1812 errorName, params, mCSPViolationData.mSourceFile, 1813 mCSPViolationData.mSample, mCSPViolationData.mLineNumber, 1814 mCSPViolationData.mColumnNumber, nsIScriptError::errorFlag); 1815 } 1816 } 1817 } 1818 1819 nsCOMPtr<nsICSPEventListener> mCSPEventListener; 1820 CSPViolationData mCSPViolationData; 1821 nsCOMPtr<nsIURI> mOriginalURI; 1822 bool mReportOnlyFlag; 1823 bool mReportSample; 1824 nsString mViolatedDirectiveName; 1825 nsString mViolatedDirectiveNameAndValue; 1826 nsCOMPtr<nsISupports> mObserverSubject; 1827 RefPtr<nsCSPContext> mCSPContext; 1828 }; 1829 1830 nsresult nsCSPContext::AsyncReportViolation( 1831 nsICSPEventListener* aCSPEventListener, 1832 mozilla::dom::CSPViolationData&& aCSPViolationData, nsIURI* aOriginalURI, 1833 const nsAString& aViolatedDirectiveName, 1834 const nsAString& aViolatedDirectiveNameAndValue, 1835 const nsAString& aObserverSubject, bool aReportSample) { 1836 EnsureIPCPoliciesRead(); 1837 NS_ENSURE_ARG_MAX(aCSPViolationData.mViolatedPolicyIndex, 1838 mPolicies.Length() - 1); 1839 1840 nsCOMPtr<nsIRunnable> task = new CSPReportSenderRunnable( 1841 aCSPEventListener, std::move(aCSPViolationData), aOriginalURI, 1842 mPolicies[aCSPViolationData.mViolatedPolicyIndex]->getReportOnlyFlag(), 1843 aViolatedDirectiveName, aViolatedDirectiveNameAndValue, aObserverSubject, 1844 aReportSample, this); 1845 1846 if (XRE_IsContentProcess()) { 1847 if (mEventTarget) { 1848 mEventTarget->Dispatch(task.forget(), NS_DISPATCH_NORMAL); 1849 return NS_OK; 1850 } 1851 } 1852 1853 NS_DispatchToMainThread(task.forget()); 1854 return NS_OK; 1855 } 1856 1857 /** 1858 * Based on the given loadinfo, determines if this CSP context allows the 1859 * ancestry. 1860 * 1861 * In order to determine the URI of the parent document (one causing the load 1862 * of this protected document), this function traverses all Browsing Contexts 1863 * until it reaches the top level browsing context. 1864 */ 1865 NS_IMETHODIMP 1866 nsCSPContext::PermitsAncestry(nsILoadInfo* aLoadInfo, 1867 bool* outPermitsAncestry) { 1868 nsresult rv; 1869 1870 *outPermitsAncestry = true; 1871 1872 RefPtr<mozilla::dom::BrowsingContext> ctx; 1873 aLoadInfo->GetBrowsingContext(getter_AddRefs(ctx)); 1874 1875 // extract the ancestry as an array 1876 nsCOMArray<nsIURI> ancestorsArray; 1877 nsCOMPtr<nsIURI> uriClone; 1878 1879 while (ctx) { 1880 nsCOMPtr<nsIPrincipal> currentPrincipal; 1881 // Generally permitsAncestry is consulted from within the 1882 // DocumentLoadListener in the parent process. For loads of type object 1883 // and embed it's called from the Document in the content process. 1884 // After Bug 1646899 we should be able to remove that branching code for 1885 // querying the currentURI. 1886 if (XRE_IsParentProcess()) { 1887 WindowGlobalParent* window = ctx->Canonical()->GetCurrentWindowGlobal(); 1888 if (window) { 1889 // Using the URI of the Principal and not the document because e.g. 1890 // about:blank inherits the principal and hence the URI of the 1891 // document does not reflect the security context of the document. 1892 currentPrincipal = window->DocumentPrincipal(); 1893 } 1894 } else if (nsPIDOMWindowOuter* windowOuter = ctx->GetDOMWindow()) { 1895 currentPrincipal = nsGlobalWindowOuter::Cast(windowOuter)->GetPrincipal(); 1896 } 1897 1898 if (currentPrincipal) { 1899 nsCOMPtr<nsIURI> currentURI; 1900 auto* currentBasePrincipal = BasePrincipal::Cast(currentPrincipal); 1901 currentBasePrincipal->GetURI(getter_AddRefs(currentURI)); 1902 1903 if (currentURI) { 1904 nsAutoCString spec; 1905 currentURI->GetSpec(spec); 1906 // delete the userpass from the URI. 1907 rv = NS_MutateURI(currentURI) 1908 .SetRef(""_ns) 1909 .SetUserPass(""_ns) 1910 .Finalize(uriClone); 1911 1912 // If setUserPass fails for some reason, just return a clone of the 1913 // current URI 1914 if (NS_FAILED(rv)) { 1915 rv = NS_GetURIWithoutRef(currentURI, getter_AddRefs(uriClone)); 1916 NS_ENSURE_SUCCESS(rv, rv); 1917 } 1918 ancestorsArray.AppendElement(uriClone); 1919 } 1920 } 1921 ctx = ctx->GetParent(); 1922 } 1923 1924 nsAutoString violatedDirective; 1925 1926 // Now that we've got the ancestry chain in ancestorsArray, time to check 1927 // them against any CSP. 1928 // NOTE: the ancestors are not allowed to be sent cross origin; this is a 1929 // restriction not placed on subresource loads. 1930 1931 for (uint32_t a = 0; a < ancestorsArray.Length(); a++) { 1932 if (CSPCONTEXTLOGENABLED()) { 1933 CSPCONTEXTLOG(("nsCSPContext::PermitsAncestry, checking ancestor: %s", 1934 ancestorsArray[a]->GetSpecOrDefault().get())); 1935 } 1936 // omit the ancestor URI in violation reports if cross-origin as per spec 1937 // (it is a violation of the same-origin policy). 1938 bool okToSendAncestor = 1939 NS_SecurityCompareURIs(ancestorsArray[a], mSelfURI, true); 1940 1941 bool permits = 1942 permitsInternal(nsIContentSecurityPolicy::FRAME_ANCESTORS_DIRECTIVE, 1943 nullptr, // triggering element 1944 nullptr, // nsICSPEventListener 1945 nullptr, // nsILoadInfo 1946 ancestorsArray[a], 1947 nullptr, // no redirect here. 1948 true, // specific, do not use default-src 1949 true, // send violation reports 1950 okToSendAncestor); 1951 if (!permits) { 1952 *outPermitsAncestry = false; 1953 } 1954 } 1955 return NS_OK; 1956 } 1957 1958 NS_IMETHODIMP 1959 nsCSPContext::Permits(Element* aTriggeringElement, 1960 nsICSPEventListener* aCSPEventListener, nsIURI* aURI, 1961 CSPDirective aDir, bool aSpecific, 1962 bool aSendViolationReports, bool* outPermits) { 1963 // Can't perform check without aURI 1964 if (aURI == nullptr) { 1965 return NS_ERROR_FAILURE; 1966 } 1967 1968 if (aURI->SchemeIs("resource")) { 1969 // XXX Ideally we would call SubjectToCSP() here but that would also 1970 // allowlist e.g. javascript: URIs which should not be allowlisted here. 1971 // As a hotfix we just allowlist pdf.js internals here explicitly. 1972 nsAutoCString uriSpec; 1973 aURI->GetSpec(uriSpec); 1974 if (StringBeginsWith(uriSpec, "resource://pdf.js/"_ns)) { 1975 *outPermits = true; 1976 return NS_OK; 1977 } 1978 } 1979 1980 *outPermits = permitsInternal(aDir, aTriggeringElement, aCSPEventListener, 1981 nullptr, // no nsILoadInfo 1982 aURI, 1983 nullptr, // no original (pre-redirect) URI 1984 aSpecific, aSendViolationReports, 1985 true); // send blocked URI in violation reports 1986 1987 if (CSPCONTEXTLOGENABLED()) { 1988 CSPCONTEXTLOG(("nsCSPContext::Permits, aUri: %s, aDir: %s, isAllowed: %s", 1989 aURI->GetSpecOrDefault().get(), 1990 CSP_CSPDirectiveToString(aDir), 1991 *outPermits ? "allow" : "deny")); 1992 } 1993 1994 return NS_OK; 1995 } 1996 1997 NS_IMETHODIMP 1998 nsCSPContext::ToJSON(nsAString& outCSPinJSON) { 1999 outCSPinJSON.Truncate(); 2000 dom::CSPPolicies jsonPolicies; 2001 jsonPolicies.mCsp_policies.Construct(); 2002 EnsureIPCPoliciesRead(); 2003 2004 for (uint32_t p = 0; p < mPolicies.Length(); p++) { 2005 dom::CSP jsonCSP; 2006 mPolicies[p]->toDomCSPStruct(jsonCSP); 2007 if (!jsonPolicies.mCsp_policies.Value().AppendElement(jsonCSP, fallible)) { 2008 return NS_ERROR_OUT_OF_MEMORY; 2009 } 2010 } 2011 2012 // convert the gathered information to JSON 2013 if (!jsonPolicies.ToJSON(outCSPinJSON)) { 2014 return NS_ERROR_FAILURE; 2015 } 2016 return NS_OK; 2017 } 2018 2019 NS_IMETHODIMP 2020 nsCSPContext::GetCSPSandboxFlags(uint32_t* aOutSandboxFlags) { 2021 if (!aOutSandboxFlags) { 2022 return NS_ERROR_FAILURE; 2023 } 2024 *aOutSandboxFlags = SANDBOXED_NONE; 2025 2026 EnsureIPCPoliciesRead(); 2027 for (uint32_t i = 0; i < mPolicies.Length(); i++) { 2028 uint32_t flags = mPolicies[i]->getSandboxFlags(); 2029 2030 // current policy doesn't have sandbox flag, check next policy 2031 if (!flags) { 2032 continue; 2033 } 2034 2035 // current policy has sandbox flags, if the policy is in enforcement-mode 2036 // (i.e. not report-only) set these flags and check for policies with more 2037 // restrictions 2038 if (!mPolicies[i]->getReportOnlyFlag()) { 2039 *aOutSandboxFlags |= flags; 2040 } else { 2041 // sandbox directive is ignored in report-only mode, warn about it and 2042 // continue the loop checking for an enforcement policy. 2043 nsAutoString policy; 2044 mPolicies[i]->toString(policy); 2045 2046 CSPCONTEXTLOG( 2047 ("nsCSPContext::GetCSPSandboxFlags, report only policy, ignoring " 2048 "sandbox in: %s", 2049 NS_ConvertUTF16toUTF8(policy).get())); 2050 2051 AutoTArray<nsString, 1> params = {policy}; 2052 logToConsole("ignoringReportOnlyDirective", params, ""_ns, u""_ns, 0, 1, 2053 nsIScriptError::warningFlag); 2054 } 2055 } 2056 2057 return NS_OK; 2058 } 2059 2060 /* ========== CSPViolationReportListener implementation ========== */ 2061 2062 NS_IMPL_ISUPPORTS(CSPViolationReportListener, nsIStreamListener, 2063 nsIRequestObserver, nsISupports); 2064 2065 CSPViolationReportListener::CSPViolationReportListener() = default; 2066 2067 CSPViolationReportListener::~CSPViolationReportListener() = default; 2068 2069 nsresult AppendSegmentToString(nsIInputStream* aInputStream, void* aClosure, 2070 const char* aRawSegment, uint32_t aToOffset, 2071 uint32_t aCount, uint32_t* outWrittenCount) { 2072 nsCString* decodedData = static_cast<nsCString*>(aClosure); 2073 decodedData->Append(aRawSegment, aCount); 2074 *outWrittenCount = aCount; 2075 return NS_OK; 2076 } 2077 2078 NS_IMETHODIMP 2079 CSPViolationReportListener::OnDataAvailable(nsIRequest* aRequest, 2080 nsIInputStream* aInputStream, 2081 uint64_t aOffset, uint32_t aCount) { 2082 uint32_t read; 2083 nsCString decodedData; 2084 return aInputStream->ReadSegments(AppendSegmentToString, &decodedData, aCount, 2085 &read); 2086 } 2087 2088 NS_IMETHODIMP 2089 CSPViolationReportListener::OnStopRequest(nsIRequest* aRequest, 2090 nsresult aStatus) { 2091 return NS_OK; 2092 } 2093 2094 NS_IMETHODIMP 2095 CSPViolationReportListener::OnStartRequest(nsIRequest* aRequest) { 2096 return NS_OK; 2097 } 2098 2099 /* ========== CSPReportRedirectSink implementation ========== */ 2100 2101 NS_IMPL_ISUPPORTS(CSPReportRedirectSink, nsIChannelEventSink, 2102 nsIInterfaceRequestor); 2103 2104 CSPReportRedirectSink::CSPReportRedirectSink() = default; 2105 2106 CSPReportRedirectSink::~CSPReportRedirectSink() = default; 2107 2108 NS_IMETHODIMP 2109 CSPReportRedirectSink::AsyncOnChannelRedirect( 2110 nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aRedirFlags, 2111 nsIAsyncVerifyRedirectCallback* aCallback) { 2112 if (aRedirFlags & nsIChannelEventSink::REDIRECT_INTERNAL) { 2113 aCallback->OnRedirectVerifyCallback(NS_OK); 2114 return NS_OK; 2115 } 2116 2117 // cancel the old channel so XHR failure callback happens 2118 nsresult rv = aOldChannel->Cancel(NS_ERROR_ABORT); 2119 NS_ENSURE_SUCCESS(rv, rv); 2120 2121 // notify an observer that we have blocked the report POST due to a 2122 // redirect, used in testing, do this async since we're in an async call now 2123 // to begin with 2124 nsCOMPtr<nsIURI> uri; 2125 rv = aOldChannel->GetURI(getter_AddRefs(uri)); 2126 NS_ENSURE_SUCCESS(rv, rv); 2127 2128 nsCOMPtr<nsIObserverService> observerService = 2129 mozilla::services::GetObserverService(); 2130 NS_ASSERTION(observerService, 2131 "Observer service required to log CSP violations"); 2132 observerService->NotifyObservers( 2133 uri, CSP_VIOLATION_TOPIC, 2134 u"denied redirect while sending violation report"); 2135 2136 return NS_BINDING_REDIRECTED; 2137 } 2138 2139 NS_IMETHODIMP 2140 CSPReportRedirectSink::GetInterface(const nsIID& aIID, void** aResult) { 2141 if (aIID.Equals(NS_GET_IID(nsINetworkInterceptController)) && 2142 mInterceptController) { 2143 nsCOMPtr<nsINetworkInterceptController> copy(mInterceptController); 2144 *aResult = copy.forget().take(); 2145 2146 return NS_OK; 2147 } 2148 2149 return QueryInterface(aIID, aResult); 2150 } 2151 2152 void CSPReportRedirectSink::SetInterceptController( 2153 nsINetworkInterceptController* aInterceptController) { 2154 mInterceptController = aInterceptController; 2155 } 2156 2157 /* ===== nsISerializable implementation ====== */ 2158 2159 NS_IMETHODIMP 2160 nsCSPContext::Read(nsIObjectInputStream* aStream) { 2161 return ReadImpl(aStream, false); 2162 } 2163 2164 nsresult nsCSPContext::PolicyContainerRead(nsIObjectInputStream* aInputStream) { 2165 return ReadImpl(aInputStream, true); 2166 } 2167 2168 nsresult nsCSPContext::ReadImpl(nsIObjectInputStream* aStream, 2169 bool aForPolicyContainer) { 2170 CSPCONTEXTLOG(("nsCSPContext::Read")); 2171 2172 nsresult rv; 2173 nsCOMPtr<nsISupports> supports; 2174 2175 rv = NS_ReadOptionalObject(aStream, true, getter_AddRefs(supports)); 2176 NS_ENSURE_SUCCESS(rv, rv); 2177 2178 mSelfURI = do_QueryInterface(supports); 2179 MOZ_ASSERT(mSelfURI, "need a self URI to de-serialize"); 2180 2181 nsAutoCString JSON; 2182 rv = aStream->ReadCString(JSON); 2183 NS_ENSURE_SUCCESS(rv, rv); 2184 2185 nsCOMPtr<nsIPrincipal> principal = BasePrincipal::FromJSON(JSON); 2186 mLoadingPrincipal = principal; 2187 MOZ_ASSERT(mLoadingPrincipal, "need a loadingPrincipal to de-serialize"); 2188 2189 uint32_t numPolicies; 2190 rv = aStream->Read32(&numPolicies); 2191 NS_ENSURE_SUCCESS(rv, rv); 2192 2193 if (numPolicies == 0) { 2194 return NS_OK; 2195 } 2196 2197 if (aForPolicyContainer) { 2198 return TryReadPolicies(PolicyDataVersion::Post136, aStream, numPolicies, 2199 true); 2200 } 2201 2202 // Note: This assume that there is no other data following the CSP! 2203 // E10SUtils.deserializeCSP is the only user of this logic. 2204 nsTArray<uint8_t> data; 2205 rv = NS_ConsumeStream(aStream, UINT32_MAX, data); 2206 NS_ENSURE_SUCCESS(rv, rv); 2207 2208 auto createStreamFromData = 2209 [&data]() -> already_AddRefed<nsIObjectInputStream> { 2210 nsCOMPtr<nsIInputStream> binaryStream; 2211 nsresult rv = NS_NewByteInputStream( 2212 getter_AddRefs(binaryStream), 2213 Span(reinterpret_cast<const char*>(data.Elements()), data.Length()), 2214 NS_ASSIGNMENT_DEPEND); 2215 NS_ENSURE_SUCCESS(rv, nullptr); 2216 2217 nsCOMPtr<nsIObjectInputStream> stream = 2218 NS_NewObjectInputStream(binaryStream); 2219 2220 return stream.forget(); 2221 }; 2222 2223 // Because of accidental backwards incompatible changes we have to try and 2224 // parse multiple different versions of the CSP data. Starting with the 2225 // current data format. 2226 2227 nsCOMPtr<nsIObjectInputStream> stream = createStreamFromData(); 2228 NS_ENSURE_TRUE(stream, NS_ERROR_FAILURE); 2229 2230 if (NS_SUCCEEDED(TryReadPolicies(PolicyDataVersion::Post136, stream, 2231 numPolicies, false))) { 2232 CSPCONTEXTLOG(("nsCSPContext::Read: Data was in version ::Post136.")); 2233 return NS_OK; 2234 } 2235 2236 stream = createStreamFromData(); 2237 NS_ENSURE_TRUE(stream, NS_ERROR_FAILURE); 2238 if (NS_SUCCEEDED(TryReadPolicies(PolicyDataVersion::Pre136, stream, 2239 numPolicies, false))) { 2240 CSPCONTEXTLOG(("nsCSPContext::Read: Data was in version ::Pre136.")); 2241 return NS_OK; 2242 } 2243 2244 stream = createStreamFromData(); 2245 NS_ENSURE_TRUE(stream, NS_ERROR_FAILURE); 2246 if (NS_SUCCEEDED(TryReadPolicies(PolicyDataVersion::V138_9PreRelease, stream, 2247 numPolicies, false))) { 2248 CSPCONTEXTLOG( 2249 ("nsCSPContext::Read: Data was in version ::V138_9PreRelease.")); 2250 return NS_OK; 2251 } 2252 2253 CSPCONTEXTLOG(("nsCSPContext::Read: Failed to read data!")); 2254 return NS_ERROR_FAILURE; 2255 } 2256 2257 nsresult nsCSPContext::TryReadPolicies(PolicyDataVersion aVersion, 2258 nsIObjectInputStream* aStream, 2259 uint32_t aNumPolicies, 2260 bool aForPolicyContainer) { 2261 // Like ReadBoolean, but ensures the byte is actually 0 or 1. 2262 auto ReadBooleanSafe = [aStream](bool* aBoolean) { 2263 uint8_t raw = 0; 2264 nsresult rv = aStream->Read8(&raw); 2265 NS_ENSURE_SUCCESS(rv, rv); 2266 if (!(raw == 0 || raw == 1)) { 2267 CSPCONTEXTLOG(("nsCSPContext::TryReadPolicies: Bad boolean value")); 2268 return NS_ERROR_FAILURE; 2269 } 2270 2271 *aBoolean = !!raw; 2272 return NS_OK; 2273 }; 2274 2275 nsTArray<mozilla::ipc::ContentSecurityPolicy> policies; 2276 nsAutoString policyString; 2277 while (aNumPolicies > 0) { 2278 aNumPolicies--; 2279 2280 nsresult rv = aStream->ReadString(policyString); 2281 NS_ENSURE_SUCCESS(rv, rv); 2282 2283 // nsCSPParser::policy removed all non-ASCII tokens while parsing the CSP 2284 // that was serialized, so we shouldn't have any in this string. A non-ASCII 2285 // character is thus a strong indicator for some kind of deserialization 2286 // error. 2287 if (!IsAscii(Span(policyString))) { 2288 CSPCONTEXTLOG( 2289 ("nsCSPContext::TryReadPolicies: Unexpected non-ASCII policy " 2290 "string")); 2291 return NS_ERROR_FAILURE; 2292 } 2293 2294 bool reportOnly = false; 2295 rv = ReadBooleanSafe(&reportOnly); 2296 NS_ENSURE_SUCCESS(rv, rv); 2297 2298 bool deliveredViaMetaTag = false; 2299 rv = ReadBooleanSafe(&deliveredViaMetaTag); 2300 NS_ENSURE_SUCCESS(rv, rv); 2301 2302 bool hasRequireTrustedTypesForDirective = false; 2303 if (aVersion == PolicyDataVersion::Post136 || 2304 aVersion == PolicyDataVersion::V138_9PreRelease) { 2305 // Added in bug 1901492. 2306 rv = ReadBooleanSafe(&hasRequireTrustedTypesForDirective); 2307 NS_ENSURE_SUCCESS(rv, rv); 2308 } 2309 2310 if (aVersion == PolicyDataVersion::V138_9PreRelease) { 2311 // This was added in bug 1942306, but wasn't really necessary. 2312 // Removed again in bug 1958259. 2313 uint32_t numExpressions; 2314 rv = aStream->Read32(&numExpressions); 2315 NS_ENSURE_SUCCESS(rv, rv); 2316 // We assume that because Trusted Types was disabled by default 2317 // that no "trusted type expressions" were written during that time. 2318 if (numExpressions != 0) { 2319 return NS_ERROR_FAILURE; 2320 } 2321 } 2322 2323 policies.AppendElement( 2324 ContentSecurityPolicy(policyString, reportOnly, deliveredViaMetaTag, 2325 hasRequireTrustedTypesForDirective)); 2326 } 2327 2328 // PolicyContainer may contain extra stuff. 2329 if (!aForPolicyContainer) { 2330 // Make sure all data was consumed. 2331 uint64_t available = 0; 2332 nsresult rv = aStream->Available(&available); 2333 NS_ENSURE_SUCCESS(rv, rv); 2334 if (available) { 2335 return NS_ERROR_FAILURE; 2336 } 2337 } 2338 2339 // Success! Add the policies now. 2340 for (auto policy : policies) { 2341 AddIPCPolicy(policy); 2342 } 2343 return NS_OK; 2344 } 2345 2346 NS_IMETHODIMP 2347 nsCSPContext::Write(nsIObjectOutputStream* aStream) { 2348 nsresult rv = NS_WriteOptionalCompoundObject(aStream, mSelfURI, 2349 NS_GET_IID(nsIURI), true); 2350 NS_ENSURE_SUCCESS(rv, rv); 2351 2352 nsAutoCString JSON; 2353 BasePrincipal::Cast(mLoadingPrincipal)->ToJSON(JSON); 2354 rv = aStream->WriteStringZ(JSON.get()); 2355 NS_ENSURE_SUCCESS(rv, rv); 2356 2357 // Serialize all the policies. 2358 aStream->Write32(mPolicies.Length() + mIPCPolicies.Length()); 2359 2360 // WARNING: Any change here needs to be backwards compatible because 2361 // the serialized CSP data is used across different Firefox versions. 2362 // Better just don't touch this. 2363 2364 // This writes data in the PolicyDataVersion::Post136 format. 2365 nsAutoString polStr; 2366 for (uint32_t p = 0; p < mPolicies.Length(); p++) { 2367 polStr.Truncate(); 2368 mPolicies[p]->toString(polStr); 2369 aStream->WriteWStringZ(polStr.get()); 2370 aStream->WriteBoolean(mPolicies[p]->getReportOnlyFlag()); 2371 aStream->WriteBoolean(mPolicies[p]->getDeliveredViaMetaTagFlag()); 2372 aStream->WriteBoolean(mPolicies[p]->hasRequireTrustedTypesForDirective()); 2373 } 2374 for (auto& policy : mIPCPolicies) { 2375 aStream->WriteWStringZ(policy.policy().get()); 2376 aStream->WriteBoolean(policy.reportOnlyFlag()); 2377 aStream->WriteBoolean(policy.deliveredViaMetaTagFlag()); 2378 aStream->WriteBoolean(policy.hasRequireTrustedTypesForDirective()); 2379 } 2380 2381 return NS_OK; 2382 } 2383 2384 void nsCSPContext::AddIPCPolicy(const ContentSecurityPolicy& aPolicy) { 2385 mIPCPolicies.AppendElement(aPolicy); 2386 if (aPolicy.hasRequireTrustedTypesForDirective()) { 2387 if (mRequireTrustedTypesForDirectiveState != 2388 RequireTrustedTypesForDirectiveState::ENFORCE) { 2389 mRequireTrustedTypesForDirectiveState = 2390 aPolicy.reportOnlyFlag() 2391 ? RequireTrustedTypesForDirectiveState::REPORT_ONLY 2392 : RequireTrustedTypesForDirectiveState::ENFORCE; 2393 } 2394 } 2395 } 2396 2397 void nsCSPContext::SerializePolicies( 2398 nsTArray<ContentSecurityPolicy>& aPolicies) { 2399 for (auto* policy : mPolicies) { 2400 nsAutoString policyString; 2401 policy->toString(policyString); 2402 aPolicies.AppendElement( 2403 ContentSecurityPolicy(policyString, policy->getReportOnlyFlag(), 2404 policy->getDeliveredViaMetaTagFlag(), 2405 policy->hasRequireTrustedTypesForDirective())); 2406 } 2407 2408 aPolicies.AppendElements(mIPCPolicies); 2409 }