nsContentSecurityUtils.cpp (82746B)
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 /* A namespace class for static content security utilities. */ 8 9 #include "nsContentSecurityUtils.h" 10 11 #include "mozilla/Components.h" 12 #include "mozilla/dom/PolicyContainer.h" 13 #include "mozilla/dom/ScriptSettings.h" 14 #include "mozilla/dom/WorkerCommon.h" 15 #include "mozilla/dom/WorkerPrivate.h" 16 #include "mozilla/dom/nsMixedContentBlocker.h" 17 #include "nsComponentManagerUtils.h" 18 #include "nsIChannel.h" 19 #include "nsIContentSecurityPolicy.h" 20 #include "nsIHttpChannel.h" 21 #include "nsIMultiPartChannel.h" 22 #include "nsITransfer.h" 23 #include "nsIURI.h" 24 #include "nsNetUtil.h" 25 #include "nsSandboxFlags.h" 26 #if defined(XP_WIN) 27 # include <wininet.h> 28 29 # include "WinUtils.h" 30 # include "mozilla/WinHeaderOnlyUtils.h" 31 #endif 32 33 #include "FramingChecker.h" 34 #include "LoadInfo.h" 35 #include "js/Array.h" // JS::GetArrayLength 36 #include "js/ContextOptions.h" 37 #include "js/PropertyAndElement.h" // JS_GetElement 38 #include "js/RegExp.h" 39 #include "js/RegExpFlags.h" // JS::RegExpFlags 40 #include "js/friend/ErrorMessages.h" // JSMSG_UNSAFE_FILENAME 41 #include "mozilla/ExtensionPolicyService.h" 42 #include "mozilla/Logging.h" 43 #include "mozilla/Preferences.h" 44 #include "mozilla/StaticPrefs_dom.h" 45 #include "mozilla/StaticPrefs_extensions.h" 46 #include "mozilla/StaticPrefs_security.h" 47 #include "mozilla/dom/Document.h" 48 #include "mozilla/dom/nsCSPContext.h" 49 #include "mozilla/glean/DomSecurityMetrics.h" 50 #include "nsIConsoleService.h" 51 #include "nsIStringBundle.h" 52 53 using namespace mozilla; 54 using namespace mozilla::dom; 55 56 extern mozilla::LazyLogModule sCSMLog; 57 extern Atomic<bool, mozilla::Relaxed> sJSHacksChecked; 58 extern Atomic<bool, mozilla::Relaxed> sJSHacksPresent; 59 extern Atomic<bool, mozilla::Relaxed> sCSSHacksChecked; 60 extern Atomic<bool, mozilla::Relaxed> sCSSHacksPresent; 61 62 // Helper function for IsConsideredSameOriginForUIR which makes 63 // Principals of scheme 'http' return Principals of scheme 'https'. 64 static already_AddRefed<nsIPrincipal> MakeHTTPPrincipalHTTPS( 65 nsIPrincipal* aPrincipal) { 66 nsCOMPtr<nsIPrincipal> principal = aPrincipal; 67 // if the principal is not http, then it can also not be upgraded 68 // to https. 69 if (!principal->SchemeIs("http")) { 70 return principal.forget(); 71 } 72 73 nsAutoCString spec; 74 aPrincipal->GetAsciiSpec(spec); 75 // replace http with https 76 spec.ReplaceLiteral(0, 4, "https"); 77 78 nsCOMPtr<nsIURI> newURI; 79 nsresult rv = NS_NewURI(getter_AddRefs(newURI), spec); 80 if (NS_WARN_IF(NS_FAILED(rv))) { 81 return nullptr; 82 } 83 84 mozilla::OriginAttributes OA = 85 BasePrincipal::Cast(aPrincipal)->OriginAttributesRef(); 86 87 principal = BasePrincipal::CreateContentPrincipal(newURI, OA); 88 return principal.forget(); 89 } 90 91 /* static */ 92 bool nsContentSecurityUtils::IsConsideredSameOriginForUIR( 93 nsIPrincipal* aTriggeringPrincipal, nsIPrincipal* aResultPrincipal) { 94 MOZ_ASSERT(aTriggeringPrincipal); 95 MOZ_ASSERT(aResultPrincipal); 96 // we only have to make sure that the following truth table holds: 97 // aTriggeringPrincipal | aResultPrincipal | Result 98 // ---------------------------------------------------------------- 99 // http://example.com/foo.html | http://example.com/bar.html | true 100 // http://example.com/foo.html | https://example.com/bar.html | true 101 // https://example.com/foo.html | https://example.com/bar.html | true 102 // https://example.com/foo.html | http://example.com/bar.html | true 103 104 // fast path if both principals are same-origin 105 if (aTriggeringPrincipal->Equals(aResultPrincipal)) { 106 return true; 107 } 108 109 // in case a principal uses a scheme of 'http' then we just upgrade to 110 // 'https' and use the principal equals comparison operator to check 111 // for same-origin. 112 nsCOMPtr<nsIPrincipal> compareTriggeringPrincipal = 113 MakeHTTPPrincipalHTTPS(aTriggeringPrincipal); 114 115 nsCOMPtr<nsIPrincipal> compareResultPrincipal = 116 MakeHTTPPrincipalHTTPS(aResultPrincipal); 117 118 return compareTriggeringPrincipal->Equals(compareResultPrincipal); 119 } 120 121 /* static */ 122 bool nsContentSecurityUtils::IsTrustedScheme(nsIURI* aURI) { 123 return aURI->SchemeIs("resource") || aURI->SchemeIs("chrome") || 124 aURI->SchemeIs("moz-src"); 125 } 126 127 /* 128 * Performs a Regular Expression match, optionally returning the results. 129 * This function is not safe to use OMT. 130 * 131 * @param aPattern The regex pattern 132 * @param aString The string to compare against 133 * @param aOnlyMatch Whether we want match results or only a true/false for 134 * the match 135 * @param aMatchResult Out param for whether or not the pattern matched 136 * @param aRegexResults Out param for the matches of the regex, if requested 137 * @returns nsresult indicating correct function operation or error 138 */ 139 nsresult RegexEval(const nsAString& aPattern, const nsAString& aString, 140 bool aOnlyMatch, bool& aMatchResult, 141 nsTArray<nsString>* aRegexResults = nullptr) { 142 MOZ_ASSERT(NS_IsMainThread()); 143 aMatchResult = false; 144 145 mozilla::dom::AutoJSAPI jsapi; 146 jsapi.Init(); 147 148 JSContext* cx = jsapi.cx(); 149 mozilla::AutoDisableJSInterruptCallback disabler(cx); 150 151 // We can use the junk scope here, because we're just using it for regexp 152 // evaluation, not actual script execution, and we disable statics so that the 153 // evaluation does not interact with the execution global. 154 JSAutoRealm ar(cx, xpc::PrivilegedJunkScope()); 155 156 JS::Rooted<JSObject*> regexp( 157 cx, JS::NewUCRegExpObject(cx, aPattern.BeginReading(), aPattern.Length(), 158 JS::RegExpFlag::Unicode)); 159 if (!regexp) { 160 return NS_ERROR_ILLEGAL_VALUE; 161 } 162 163 JS::Rooted<JS::Value> regexResult(cx, JS::NullValue()); 164 165 size_t index = 0; 166 if (!JS::ExecuteRegExpNoStatics(cx, regexp, aString.BeginReading(), 167 aString.Length(), &index, aOnlyMatch, 168 ®exResult)) { 169 return NS_ERROR_FAILURE; 170 } 171 172 if (regexResult.isNull()) { 173 // On no match, ExecuteRegExpNoStatics returns Null 174 return NS_OK; 175 } 176 if (aOnlyMatch) { 177 // On match, with aOnlyMatch = true, ExecuteRegExpNoStatics returns boolean 178 // true. 179 MOZ_ASSERT(regexResult.isBoolean() && regexResult.toBoolean()); 180 aMatchResult = true; 181 return NS_OK; 182 } 183 if (aRegexResults == nullptr) { 184 return NS_ERROR_INVALID_ARG; 185 } 186 187 // Now we know we have a result, and we need to extract it so we can read it. 188 uint32_t length; 189 JS::Rooted<JSObject*> regexResultObj(cx, ®exResult.toObject()); 190 if (!JS::GetArrayLength(cx, regexResultObj, &length)) { 191 return NS_ERROR_NOT_AVAILABLE; 192 } 193 MOZ_LOG(sCSMLog, LogLevel::Verbose, ("Regex Matched %i strings", length)); 194 195 for (uint32_t i = 0; i < length; i++) { 196 JS::Rooted<JS::Value> element(cx); 197 if (!JS_GetElement(cx, regexResultObj, i, &element)) { 198 return NS_ERROR_NO_CONTENT; 199 } 200 201 nsAutoJSString value; 202 if (!value.init(cx, element)) { 203 return NS_ERROR_NO_CONTENT; 204 } 205 206 MOZ_LOG(sCSMLog, LogLevel::Verbose, 207 ("Regex Matching: %i: %s", i, NS_ConvertUTF16toUTF8(value).get())); 208 aRegexResults->AppendElement(value); 209 } 210 211 aMatchResult = true; 212 return NS_OK; 213 } 214 215 /* 216 * MOZ_CRASH_UNSAFE_PRINTF has a sPrintfCrashReasonSize-sized buffer. We need 217 * to make sure we don't exceed it. These functions perform this check and 218 * munge things for us. 219 * 220 */ 221 222 /* 223 * Destructively truncates a string to fit within the limit 224 */ 225 char* nsContentSecurityUtils::SmartFormatCrashString(const char* str) { 226 return nsContentSecurityUtils::SmartFormatCrashString(strdup(str)); 227 } 228 229 char* nsContentSecurityUtils::SmartFormatCrashString(char* str) { 230 auto str_len = strlen(str); 231 232 if (str_len > sPrintfCrashReasonSize) { 233 str[sPrintfCrashReasonSize - 1] = '\0'; 234 str_len = strlen(str); 235 } 236 MOZ_RELEASE_ASSERT(sPrintfCrashReasonSize > str_len); 237 238 return str; 239 } 240 241 /* 242 * Destructively truncates two strings to fit within the limit. 243 * format_string is a format string containing two %s entries 244 * The second string will be truncated to the _last_ 25 characters 245 * The first string will be truncated to the remaining limit. 246 */ 247 nsCString nsContentSecurityUtils::SmartFormatCrashString( 248 const char* part1, const char* part2, const char* format_string) { 249 return SmartFormatCrashString(strdup(part1), strdup(part2), format_string); 250 } 251 252 nsCString nsContentSecurityUtils::SmartFormatCrashString( 253 char* part1, char* part2, const char* format_string) { 254 auto part1_len = strlen(part1); 255 auto part2_len = strlen(part2); 256 257 auto constant_len = strlen(format_string) - 4; 258 259 if (part1_len + part2_len + constant_len > sPrintfCrashReasonSize) { 260 if (part2_len > 25) { 261 part2 += (part2_len - 25); 262 } 263 part2_len = strlen(part2); 264 265 part1[sPrintfCrashReasonSize - (constant_len + part2_len + 1)] = '\0'; 266 part1_len = strlen(part1); 267 } 268 MOZ_RELEASE_ASSERT(sPrintfCrashReasonSize > 269 constant_len + part1_len + part2_len); 270 271 auto parts = nsPrintfCString(format_string, part1, part2); 272 return std::move(parts); 273 } 274 275 /* 276 * Telemetry Events extra data only supports 80 characters, so we optimize the 277 * filename to be smaller and collect more data. 278 */ 279 nsCString OptimizeFileName(const nsAString& aFileName) { 280 nsCString optimizedName; 281 CopyUTF16toUTF8(aFileName, optimizedName); 282 283 MOZ_LOG(sCSMLog, LogLevel::Verbose, 284 ("Optimizing FileName: %s", optimizedName.get())); 285 286 optimizedName.ReplaceSubstring(".xpi!"_ns, "!"_ns); 287 optimizedName.ReplaceSubstring("shield.mozilla.org!"_ns, "s!"_ns); 288 optimizedName.ReplaceSubstring("mozilla.org!"_ns, "m!"_ns); 289 if (optimizedName.Length() > 80) { 290 optimizedName.Truncate(80); 291 } 292 293 MOZ_LOG(sCSMLog, LogLevel::Verbose, 294 ("Optimized FileName: %s", optimizedName.get())); 295 return optimizedName; 296 } 297 298 static nsCString StripQueryRef(const nsACString& aFileName) { 299 nsCString stripped(aFileName); 300 int32_t i = stripped.FindCharInSet("#?"_ns); 301 if (i != kNotFound) { 302 stripped.Truncate(i); 303 } 304 return stripped; 305 } 306 307 /* 308 * FilenameToFilenameType takes a fileName and returns a Pair of strings. 309 * The First entry is a string indicating the type of fileName 310 * The Second entry is a Maybe<string> that can contain additional details to 311 * report. 312 * 313 * The reason we use strings (instead of an int/enum) is because the Telemetry 314 * Events API only accepts strings. 315 * 316 * Function is a static member of the class to enable gtests. 317 */ 318 /* static */ 319 FilenameTypeAndDetails nsContentSecurityUtils::FilenameToFilenameType( 320 const nsACString& fileName, bool collectAdditionalExtensionData) { 321 // These are strings because the Telemetry Events API only accepts strings 322 static constexpr auto kChromeURI = "chromeuri"_ns; 323 static constexpr auto kResourceURI = "resourceuri"_ns; 324 static constexpr auto kMozSrcURI = "mozsrcuri"_ns; 325 static constexpr auto kBlobUri = "bloburi"_ns; 326 static constexpr auto kDataUri = "dataurl"_ns; 327 static constexpr auto kAboutUri = "abouturi"_ns; 328 static constexpr auto kDataUriWebExtCStyle = 329 "dataurl-extension-contentstyle"_ns; 330 static constexpr auto kSingleString = "singlestring"_ns; 331 static constexpr auto kMozillaExtensionFile = "mozillaextension_file"_ns; 332 static constexpr auto kOtherExtensionFile = "otherextension_file"_ns; 333 static constexpr auto kExtensionURI = "extension_uri"_ns; 334 static constexpr auto kSuspectedUserChromeJS = "suspectedUserChromeJS"_ns; 335 #if defined(XP_WIN) 336 static constexpr auto kSanitizedWindowsURL = "sanitizedWindowsURL"_ns; 337 static constexpr auto kSanitizedWindowsPath = "sanitizedWindowsPath"_ns; 338 #endif 339 static constexpr auto kOther = "other"_ns; 340 static constexpr auto kOtherWorker = "other-on-worker"_ns; 341 static constexpr auto kRegexFailure = "regexfailure"_ns; 342 343 static constexpr auto kUCJSRegex = u"(.+).uc.js\\?*[0-9]*$"_ns; 344 static constexpr auto kExtensionRegex = u"extensions/(.+)@(.+)!(.+)$"_ns; 345 static constexpr auto kSingleFileRegex = u"^[a-zA-Z0-9.?]+$"_ns; 346 347 if (fileName.IsEmpty()) { 348 return FilenameTypeAndDetails(kOther, Nothing()); 349 } 350 351 // resource:// and chrome:// 352 // These don't contain any user (profile) paths. 353 if (StringBeginsWith(fileName, "chrome://"_ns)) { 354 if (StringBeginsWith(fileName, "chrome://userscripts/"_ns) || 355 StringBeginsWith(fileName, "chrome://userchromejs/"_ns) || 356 StringBeginsWith(fileName, "chrome://user_chrome_files/"_ns) || 357 StringBeginsWith(fileName, "chrome://tabmix"_ns) || 358 StringBeginsWith(fileName, "chrome://searchwp/"_ns) || 359 StringBeginsWith(fileName, "chrome://custombuttons"_ns) || 360 StringBeginsWith(fileName, "chrome://tabgroups-resource/"_ns)) { 361 return FilenameTypeAndDetails(kSuspectedUserChromeJS, 362 Some(StripQueryRef(fileName))); 363 } 364 return FilenameTypeAndDetails(kChromeURI, Some(StripQueryRef(fileName))); 365 } 366 if (StringBeginsWith(fileName, "resource://"_ns)) { 367 if (StringBeginsWith(fileName, "resource://usl-ucjs/"_ns) || 368 StringBeginsWith(fileName, "resource://sfm-ucjs/"_ns) || 369 StringBeginsWith(fileName, "resource://cpmanager-legacy/"_ns) || 370 StringBeginsWith(fileName, "resource://pwa/utils/"_ns)) { 371 return FilenameTypeAndDetails(kSuspectedUserChromeJS, 372 Some(StripQueryRef(fileName))); 373 } 374 return FilenameTypeAndDetails(kResourceURI, Some(StripQueryRef(fileName))); 375 } 376 if (StringBeginsWith(fileName, "moz-src://"_ns)) { 377 return FilenameTypeAndDetails(kMozSrcURI, Some(StripQueryRef(fileName))); 378 } 379 380 // blob: and data: 381 if (StringBeginsWith(fileName, "blob:"_ns)) { 382 return FilenameTypeAndDetails(kBlobUri, Nothing()); 383 } 384 if (StringBeginsWith(fileName, "data:text/css;extension=style;"_ns)) { 385 return FilenameTypeAndDetails(kDataUriWebExtCStyle, Nothing()); 386 } 387 if (StringBeginsWith(fileName, "data:"_ns)) { 388 return FilenameTypeAndDetails(kDataUri, Nothing()); 389 } 390 391 // Can't do regex matching off-main-thread 392 if (NS_IsMainThread()) { 393 NS_ConvertUTF8toUTF16 fileNameA(fileName); 394 // Extension as loaded via a file:// 395 bool regexMatch; 396 nsTArray<nsString> regexResults; 397 nsresult rv = 398 RegexEval(kExtensionRegex, fileNameA, 399 /* aOnlyMatch = */ false, regexMatch, ®exResults); 400 if (NS_FAILED(rv)) { 401 return FilenameTypeAndDetails(kRegexFailure, Nothing()); 402 } 403 if (regexMatch) { 404 nsCString type = StringEndsWith(regexResults[2], u"mozilla.org.xpi"_ns) 405 ? kMozillaExtensionFile 406 : kOtherExtensionFile; 407 const auto& extensionNameAndPath = 408 Substring(regexResults[0], std::size("extensions/") - 1); 409 return FilenameTypeAndDetails( 410 type, Some(OptimizeFileName(extensionNameAndPath))); 411 } 412 413 // Single File 414 rv = RegexEval(kSingleFileRegex, fileNameA, /* aOnlyMatch = */ true, 415 regexMatch); 416 if (NS_FAILED(rv)) { 417 return FilenameTypeAndDetails(kRegexFailure, Nothing()); 418 } 419 if (regexMatch) { 420 return FilenameTypeAndDetails(kSingleString, Some(nsCString(fileName))); 421 } 422 423 // Suspected userChromeJS script 424 rv = RegexEval(kUCJSRegex, fileNameA, /* aOnlyMatch = */ true, regexMatch); 425 if (NS_FAILED(rv)) { 426 return FilenameTypeAndDetails(kRegexFailure, Nothing()); 427 } 428 if (regexMatch) { 429 return FilenameTypeAndDetails(kSuspectedUserChromeJS, Nothing()); 430 } 431 } 432 433 // Something loaded via an about:// URI. 434 if (StringBeginsWith(fileName, "about:"_ns)) { 435 return FilenameTypeAndDetails(kAboutUri, Some(StripQueryRef(fileName))); 436 } 437 438 // Something loaded via a moz-extension:// URI. 439 if (StringBeginsWith(fileName, "moz-extension://"_ns)) { 440 if (!collectAdditionalExtensionData) { 441 return FilenameTypeAndDetails(kExtensionURI, Nothing()); 442 } 443 444 nsAutoCString sanitizedPathAndScheme; 445 sanitizedPathAndScheme.Append("moz-extension://["_ns); 446 447 nsCOMPtr<nsIURI> uri; 448 nsresult rv = NS_NewURI(getter_AddRefs(uri), fileName); 449 if (NS_FAILED(rv)) { 450 // Return after adding ://[ so we know we failed here. 451 return FilenameTypeAndDetails(kExtensionURI, 452 Some(sanitizedPathAndScheme)); 453 } 454 455 mozilla::extensions::URLInfo url(uri); 456 if (NS_IsMainThread()) { 457 // EPS is only usable on main thread 458 auto* policy = 459 ExtensionPolicyService::GetSingleton().GetByHost(url.Host()); 460 if (policy) { 461 nsString addOnId; 462 policy->GetId(addOnId); 463 464 sanitizedPathAndScheme.Append(NS_ConvertUTF16toUTF8(addOnId)); 465 sanitizedPathAndScheme.Append(": "_ns); 466 sanitizedPathAndScheme.Append(NS_ConvertUTF16toUTF8(policy->Name())); 467 sanitizedPathAndScheme.Append("]"_ns); 468 469 if (policy->IsPrivileged()) { 470 sanitizedPathAndScheme.Append("P=1"_ns); 471 } else { 472 sanitizedPathAndScheme.Append("P=0"_ns); 473 } 474 } else { 475 sanitizedPathAndScheme.Append("failed finding addon by host]"_ns); 476 } 477 } else { 478 sanitizedPathAndScheme.Append("can't get addon off main thread]"_ns); 479 } 480 481 sanitizedPathAndScheme.Append(url.FilePath()); 482 return FilenameTypeAndDetails(kExtensionURI, Some(sanitizedPathAndScheme)); 483 } 484 485 #if defined(XP_WIN) 486 auto flags = mozilla::widget::WinUtils::PathTransformFlags::Default | 487 mozilla::widget::WinUtils::PathTransformFlags::RequireFilePath; 488 const NS_ConvertUTF8toUTF16 fileNameA(fileName); 489 nsAutoString strSanitizedPath(fileNameA); 490 if (widget::WinUtils::PreparePathForTelemetry(strSanitizedPath, flags)) { 491 DWORD cchDecodedUrl = INTERNET_MAX_URL_LENGTH; 492 WCHAR szOut[INTERNET_MAX_URL_LENGTH]; 493 HRESULT hr; 494 SAFECALL_URLMON_FUNC(CoInternetParseUrl, fileNameA.get(), PARSE_SCHEMA, 0, 495 szOut, INTERNET_MAX_URL_LENGTH, &cchDecodedUrl, 0); 496 if (hr == S_OK && cchDecodedUrl) { 497 nsAutoString sanitizedPathAndScheme; 498 sanitizedPathAndScheme.Append(szOut); 499 if (sanitizedPathAndScheme == u"file"_ns) { 500 sanitizedPathAndScheme.Append(u"://.../"_ns); 501 sanitizedPathAndScheme.Append(strSanitizedPath); 502 } 503 return FilenameTypeAndDetails( 504 kSanitizedWindowsURL, 505 Some(NS_ConvertUTF16toUTF8(sanitizedPathAndScheme))); 506 } else { 507 return FilenameTypeAndDetails( 508 kSanitizedWindowsPath, Some(NS_ConvertUTF16toUTF8(strSanitizedPath))); 509 } 510 } 511 #endif 512 513 if (!NS_IsMainThread()) { 514 return FilenameTypeAndDetails(kOtherWorker, Nothing()); 515 } 516 return FilenameTypeAndDetails(kOther, Nothing()); 517 } 518 519 #if defined(EARLY_BETA_OR_EARLIER) 520 // Crash String must be safe from a telemetry point of view. 521 // This will be ensured when this function is used. 522 void PossiblyCrash(const char* aPrefSuffix, const char* aUnsafeCrashString, 523 const nsCString& aSafeCrashString) { 524 if (MOZ_UNLIKELY(!XRE_IsParentProcess())) { 525 // We only crash in the parent (unfortunately) because it's 526 // the only place we can be sure that our only-crash-once 527 // pref-writing works. 528 return; 529 } 530 if (!NS_IsMainThread()) { 531 // Setting a pref off the main thread causes ContentParent to observe the 532 // pref set, resulting in a Release Assertion when it tries to update the 533 // child off main thread. So don't do any of this off main thread. (Which 534 // is a bit of a blind spot for this purpose...) 535 return; 536 } 537 538 nsCString previous_crashes("security.crash_tracking."); 539 previous_crashes.Append(aPrefSuffix); 540 previous_crashes.Append(".prevCrashes"); 541 542 nsCString max_crashes("security.crash_tracking."); 543 max_crashes.Append(aPrefSuffix); 544 max_crashes.Append(".maxCrashes"); 545 546 int32_t numberOfPreviousCrashes = 0; 547 numberOfPreviousCrashes = Preferences::GetInt(previous_crashes.get(), 0); 548 549 int32_t maxAllowableCrashes = 0; 550 maxAllowableCrashes = Preferences::GetInt(max_crashes.get(), 0); 551 552 if (numberOfPreviousCrashes >= maxAllowableCrashes) { 553 return; 554 } 555 556 nsresult rv = 557 Preferences::SetInt(previous_crashes.get(), ++numberOfPreviousCrashes); 558 if (NS_FAILED(rv)) { 559 return; 560 } 561 562 nsCOMPtr<nsIPrefService> prefsCom = Preferences::GetService(); 563 Preferences* prefs = static_cast<Preferences*>(prefsCom.get()); 564 565 if (!prefs->AllowOffMainThreadSave()) { 566 // Do not crash if we can't save prefs off the main thread 567 return; 568 } 569 570 rv = prefs->SavePrefFileBlocking(); 571 if (!NS_FAILED(rv)) { 572 // We can only use this in local builds where we don't send stuff up to the 573 // crash reporter because it has user private data. 574 // MOZ_CRASH_UNSAFE_PRINTF("%s", 575 // nsContentSecurityUtils::SmartFormatCrashString(aUnsafeCrashString)); 576 MOZ_CRASH_UNSAFE_PRINTF( 577 "%s", 578 nsContentSecurityUtils::SmartFormatCrashString(aSafeCrashString.get())); 579 } 580 } 581 #endif 582 583 class EvalUsageNotificationRunnable final : public Runnable { 584 public: 585 EvalUsageNotificationRunnable(bool aIsSystemPrincipal, 586 const nsACString& aFileName, uint64_t aWindowID, 587 uint32_t aLineNumber, uint32_t aColumnNumber) 588 : mozilla::Runnable("EvalUsageNotificationRunnable"), 589 mIsSystemPrincipal(aIsSystemPrincipal), 590 mFileName(aFileName), 591 mWindowID(aWindowID), 592 mLineNumber(aLineNumber), 593 mColumnNumber(aColumnNumber) {} 594 595 NS_IMETHOD Run() override { 596 nsContentSecurityUtils::NotifyEvalUsage( 597 mIsSystemPrincipal, mFileName, mWindowID, mLineNumber, mColumnNumber); 598 return NS_OK; 599 } 600 601 void Revoke() {} 602 603 private: 604 bool mIsSystemPrincipal; 605 nsCString mFileName; 606 uint64_t mWindowID; 607 uint32_t mLineNumber; 608 uint32_t mColumnNumber; 609 }; 610 611 /* static */ 612 bool nsContentSecurityUtils::IsEvalAllowed(JSContext* cx, 613 bool aIsSystemPrincipal, 614 const nsAString& aScript) { 615 // This allowlist contains files that are permanently allowed to use 616 // eval()-like functions. It will ideally be restricted to files that are 617 // exclusively used in testing contexts. 618 static nsLiteralCString evalAllowlist[] = { 619 // Test-only third-party library 620 "resource://testing-common/sinon-7.2.7.js"_ns, 621 // Test-only utility 622 "resource://testing-common/content-task.js"_ns, 623 624 // Tracked by Bug 1584605 625 "resource://gre/modules/translations/cld-worker.js"_ns, 626 627 // require.js implements a script loader for workers. It uses eval 628 // to load the script; but injection is only possible in situations 629 // that you could otherwise control script that gets executed, so 630 // it is okay to allow eval() as it adds no additional attack surface. 631 // Bug 1584564 tracks requiring safe usage of require.js 632 "resource://gre/modules/workers/require.js"_ns, 633 634 // The profiler's symbolication code uses a wasm module to extract symbols 635 // from the binary files result of local builds. 636 // See bug 1777479 637 "resource://devtools/shared/performance-new/symbolication.sys.mjs"_ns, 638 639 // The devtools use a WASM module to optimize source-map mapping. 640 "resource://devtools/client/shared/vendor/source-map/lib/wasm.js"_ns, 641 642 // The Browser Toolbox/Console 643 "debugger"_ns, 644 645 // Tor Browser's Lox wasm integration 646 "resource://gre/modules/lox_wasm.jsm"_ns, 647 }; 648 649 // We also permit two specific idioms in eval()-like contexts. We'd like to 650 // elminate these too; but there are in-the-wild Mozilla privileged extensions 651 // that use them. 652 static constexpr auto sAllowedEval1 = u"this"_ns; 653 static constexpr auto sAllowedEval2 = 654 u"function anonymous(\n) {\nreturn this\n}"_ns; 655 656 if (MOZ_LIKELY(!aIsSystemPrincipal && !XRE_IsE10sParentProcess())) { 657 // We restrict eval in the system principal and parent process. 658 // Other uses (like web content and null principal) are allowed. 659 return true; 660 } 661 662 if (JS::ContextOptionsRef(cx).disableEvalSecurityChecks()) { 663 MOZ_LOG(sCSMLog, LogLevel::Debug, 664 ("Allowing eval() because this JSContext was set to allow it")); 665 return true; 666 } 667 668 if (StaticPrefs:: 669 security_allow_unsafe_dangerous_privileged_evil_eval_AtStartup()) { 670 MOZ_LOG( 671 sCSMLog, LogLevel::Debug, 672 ("Allowing eval() because " 673 "security.allow_unsafe_dangerous_priviliged_evil_eval is enabled.")); 674 return true; 675 } 676 677 if (aIsSystemPrincipal && 678 StaticPrefs::security_allow_eval_with_system_principal()) { 679 MOZ_LOG(sCSMLog, LogLevel::Debug, 680 ("Allowing eval() with System Principal because allowing pref is " 681 "enabled")); 682 return true; 683 } 684 685 if (XRE_IsE10sParentProcess() && 686 StaticPrefs::security_allow_eval_in_parent_process()) { 687 MOZ_LOG(sCSMLog, LogLevel::Debug, 688 ("Allowing eval() in parent process because allowing pref is " 689 "enabled")); 690 return true; 691 } 692 693 if (XRE_IsE10sParentProcess() && 694 !StaticPrefs::extensions_webextensions_remote()) { 695 MOZ_LOG(sCSMLog, LogLevel::Debug, 696 ("Allowing eval() in parent process because the web extension " 697 "process is disabled")); 698 return true; 699 } 700 701 // We permit these two common idioms to get access to the global JS object 702 if (!aScript.IsEmpty() && 703 (aScript == sAllowedEval1 || aScript == sAllowedEval2)) { 704 MOZ_LOG( 705 sCSMLog, LogLevel::Debug, 706 ("Allowing eval() %s because a key string is " 707 "provided", 708 (aIsSystemPrincipal ? "with System Principal" : "in parent process"))); 709 return true; 710 } 711 712 // Check the allowlist for the provided filename. getFilename is a helper 713 // function 714 auto location = JSCallingLocation::Get(cx); 715 const nsCString& fileName = location.FileName(); 716 for (const nsLiteralCString& allowlistEntry : evalAllowlist) { 717 // checking if current filename begins with entry, because JS Engine 718 // gives us additional stuff for code inside eval or Function ctor 719 // e.g., "require.js > Function" 720 if (StringBeginsWith(fileName, allowlistEntry)) { 721 MOZ_LOG(sCSMLog, LogLevel::Debug, 722 ("Allowing eval() %s because the containing " 723 "file is in the allowlist", 724 (aIsSystemPrincipal ? "with System Principal" 725 : "in parent process"))); 726 return true; 727 } 728 } 729 730 // Send Telemetry and Log to the Console 731 uint64_t windowID = nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(cx); 732 if (NS_IsMainThread()) { 733 nsContentSecurityUtils::NotifyEvalUsage(aIsSystemPrincipal, fileName, 734 windowID, location.mLine, 735 location.mColumn); 736 } else { 737 auto runnable = new EvalUsageNotificationRunnable( 738 aIsSystemPrincipal, fileName, windowID, location.mLine, 739 location.mColumn); 740 NS_DispatchToMainThread(runnable); 741 } 742 743 // Log to MOZ_LOG 744 MOZ_LOG(sCSMLog, LogLevel::Error, 745 ("Blocking eval() %s from file %s and script " 746 "provided %s", 747 (aIsSystemPrincipal ? "with System Principal" : "in parent process"), 748 fileName.get(), NS_ConvertUTF16toUTF8(aScript).get())); 749 750 // Maybe Crash 751 #if defined(DEBUG) || defined(FUZZING) 752 auto crashString = nsContentSecurityUtils::SmartFormatCrashString( 753 NS_ConvertUTF16toUTF8(aScript).get(), fileName.get(), 754 (aIsSystemPrincipal 755 ? "Blocking eval() with System Principal with script %s from file %s" 756 : "Blocking eval() in parent process with script %s from file %s")); 757 MOZ_CRASH_UNSAFE_PRINTF("%s", crashString.get()); 758 #endif 759 760 #if defined(ANDROID) && !defined(NIGHTLY_BUILD) 761 return true; 762 #else 763 return false; 764 #endif 765 } 766 767 /* static */ 768 void nsContentSecurityUtils::NotifyEvalUsage(bool aIsSystemPrincipal, 769 const nsACString& aFileName, 770 uint64_t aWindowID, 771 uint32_t aLineNumber, 772 uint32_t aColumnNumber) { 773 FilenameTypeAndDetails fileNameTypeAndDetails = 774 FilenameToFilenameType(aFileName, false); 775 auto fileinfo = fileNameTypeAndDetails.second; 776 auto value = Some(fileNameTypeAndDetails.first); 777 if (aIsSystemPrincipal) { 778 glean::security::EvalUsageSystemContextExtra extra = { 779 .fileinfo = fileinfo, 780 .value = value, 781 }; 782 glean::security::eval_usage_system_context.Record(Some(extra)); 783 } else { 784 glean::security::EvalUsageParentProcessExtra extra = { 785 .fileinfo = fileinfo, 786 .value = value, 787 }; 788 glean::security::eval_usage_parent_process.Record(Some(extra)); 789 } 790 791 // Report an error to console 792 nsCOMPtr<nsIConsoleService> console( 793 do_GetService(NS_CONSOLESERVICE_CONTRACTID)); 794 if (!console) { 795 return; 796 } 797 nsCOMPtr<nsIScriptError> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID)); 798 if (!error) { 799 return; 800 } 801 nsCOMPtr<nsIStringBundle> bundle; 802 nsCOMPtr<nsIStringBundleService> stringService = 803 mozilla::components::StringBundle::Service(); 804 if (!stringService) { 805 return; 806 } 807 stringService->CreateBundle( 808 "chrome://global/locale/security/security.properties", 809 getter_AddRefs(bundle)); 810 if (!bundle) { 811 return; 812 } 813 nsAutoString message; 814 NS_ConvertUTF8toUTF16 fileNameA(aFileName); 815 AutoTArray<nsString, 1> formatStrings = {fileNameA}; 816 nsresult rv = bundle->FormatStringFromName("RestrictBrowserEvalUsage", 817 formatStrings, message); 818 if (NS_FAILED(rv)) { 819 return; 820 } 821 822 rv = error->InitWithWindowID(message, aFileName, aLineNumber, aColumnNumber, 823 nsIScriptError::errorFlag, "BrowserEvalUsage", 824 aWindowID, true /* From chrome context */); 825 if (NS_FAILED(rv)) { 826 return; 827 } 828 console->LogMessage(error); 829 } 830 831 // If we detect that one of the relevant prefs has been changed, reset 832 // sJSHacksChecked to cause us to re-evaluate all the pref values. 833 // This will stop us from crashing because a user enabled one of these 834 // prefs during a session and then triggered the JavaScript load mitigation 835 // (which can cause a crash). 836 class JSHackPrefObserver final { 837 public: 838 JSHackPrefObserver() = default; 839 static void PrefChanged(const char* aPref, void* aData); 840 841 protected: 842 ~JSHackPrefObserver() = default; 843 }; 844 845 // static 846 void JSHackPrefObserver::PrefChanged(const char* aPref, void* aData) { 847 sJSHacksChecked = false; 848 } 849 850 static bool sJSHackObserverAdded = false; 851 852 /* static */ 853 void nsContentSecurityUtils::DetectJsHacks() { 854 // We can only perform the check of this preference on the Main Thread 855 // (because a String-based preference check is only safe on Main Thread.) 856 // In theory, it would be possible that a separate thread could get here 857 // before the main thread, resulting in the other thread not being able to 858 // perform this check, but the odds of that are small (and probably zero.) 859 if (!NS_IsMainThread()) { 860 return; 861 } 862 863 // If the pref service isn't available, do nothing and re-do this later. 864 if (!Preferences::IsServiceAvailable()) { 865 return; 866 } 867 868 // No need to check again. 869 if (MOZ_LIKELY(sJSHacksChecked || sJSHacksPresent)) { 870 return; 871 } 872 873 static const char* kObservedPrefs[] = { 874 "xpinstall.signatures.required", "general.config.filename", 875 "autoadmin.global_config_url", "autoadmin.failover_to_cached", nullptr}; 876 if (MOZ_UNLIKELY(!sJSHackObserverAdded)) { 877 Preferences::RegisterCallbacks(JSHackPrefObserver::PrefChanged, 878 kObservedPrefs); 879 sJSHackObserverAdded = true; 880 } 881 882 nsresult rv; 883 sJSHacksChecked = true; 884 885 // This preference is required by bootstrapLoader.xpi, which is an 886 // alternate way to load legacy-style extensions. It only works on 887 // DevEdition/Nightly. 888 bool xpinstallSignatures; 889 rv = Preferences::GetBool("xpinstall.signatures.required", 890 &xpinstallSignatures, PrefValueKind::Default); 891 if (!NS_FAILED(rv) && !xpinstallSignatures) { 892 sJSHacksPresent = true; 893 return; 894 } 895 rv = Preferences::GetBool("xpinstall.signatures.required", 896 &xpinstallSignatures, PrefValueKind::User); 897 if (!NS_FAILED(rv) && !xpinstallSignatures) { 898 sJSHacksPresent = true; 899 return; 900 } 901 902 if (Preferences::HasDefaultValue("general.config.filename")) { 903 sJSHacksPresent = true; 904 return; 905 } 906 if (Preferences::HasUserValue("general.config.filename")) { 907 sJSHacksPresent = true; 908 return; 909 } 910 if (Preferences::HasDefaultValue("autoadmin.global_config_url")) { 911 sJSHacksPresent = true; 912 return; 913 } 914 if (Preferences::HasUserValue("autoadmin.global_config_url")) { 915 sJSHacksPresent = true; 916 return; 917 } 918 919 bool failOverToCache; 920 rv = Preferences::GetBool("autoadmin.failover_to_cached", &failOverToCache, 921 PrefValueKind::Default); 922 if (!NS_FAILED(rv) && failOverToCache) { 923 sJSHacksPresent = true; 924 return; 925 } 926 rv = Preferences::GetBool("autoadmin.failover_to_cached", &failOverToCache, 927 PrefValueKind::User); 928 if (!NS_FAILED(rv) && failOverToCache) { 929 sJSHacksPresent = true; 930 } 931 } 932 933 /* static */ 934 void nsContentSecurityUtils::DetectCssHacks() { 935 // We can only perform the check of this preference on the Main Thread 936 // It's possible that this function may therefore race and we expect the 937 // caller to ensure that the checks have actually happened. 938 if (!NS_IsMainThread()) { 939 return; 940 } 941 942 // If the pref service isn't available, do nothing and re-do this later. 943 if (!Preferences::IsServiceAvailable()) { 944 return; 945 } 946 947 // No need to check again. 948 if (MOZ_LIKELY(sCSSHacksChecked || sCSSHacksPresent)) { 949 return; 950 } 951 952 // This preference is a bool to see if userChrome css is loaded 953 bool customStylesPresent = Preferences::GetBool( 954 "toolkit.legacyUserProfileCustomizations.stylesheets", false); 955 if (customStylesPresent) { 956 sCSSHacksPresent = true; 957 } 958 959 sCSSHacksChecked = true; 960 } 961 962 /* static */ 963 nsresult nsContentSecurityUtils::GetHttpChannelFromPotentialMultiPart( 964 nsIChannel* aChannel, nsIHttpChannel** aHttpChannel) { 965 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel); 966 if (httpChannel) { 967 httpChannel.forget(aHttpChannel); 968 return NS_OK; 969 } 970 971 nsCOMPtr<nsIMultiPartChannel> multipart = do_QueryInterface(aChannel); 972 if (!multipart) { 973 *aHttpChannel = nullptr; 974 return NS_OK; 975 } 976 977 nsCOMPtr<nsIChannel> baseChannel; 978 nsresult rv = multipart->GetBaseChannel(getter_AddRefs(baseChannel)); 979 if (NS_WARN_IF(NS_FAILED(rv))) { 980 return rv; 981 } 982 983 httpChannel = do_QueryInterface(baseChannel); 984 httpChannel.forget(aHttpChannel); 985 986 return NS_OK; 987 } 988 989 nsresult CheckCSPFrameAncestorPolicy(nsIChannel* aChannel, 990 nsIContentSecurityPolicy** aOutCSP) { 991 MOZ_ASSERT(aChannel); 992 993 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); 994 ExtContentPolicyType contentType = loadInfo->GetExternalContentPolicyType(); 995 // frame-ancestor check only makes sense for subdocument and object loads, 996 // if this is not a load of such type, there is nothing to do here. 997 if (contentType != ExtContentPolicy::TYPE_SUBDOCUMENT && 998 contentType != ExtContentPolicy::TYPE_OBJECT) { 999 return NS_OK; 1000 } 1001 1002 // CSP can only hang off an http channel, if this channel is not 1003 // an http channel then there is nothing to do here, 1004 // except with add-ons, where the CSP is stored in a WebExtensionPolicy. 1005 nsCOMPtr<nsIHttpChannel> httpChannel; 1006 nsresult rv = nsContentSecurityUtils::GetHttpChannelFromPotentialMultiPart( 1007 aChannel, getter_AddRefs(httpChannel)); 1008 if (NS_WARN_IF(NS_FAILED(rv))) { 1009 return rv; 1010 } 1011 1012 nsAutoCString tCspHeaderValue, tCspROHeaderValue; 1013 if (httpChannel) { 1014 (void)httpChannel->GetResponseHeader("content-security-policy"_ns, 1015 tCspHeaderValue); 1016 1017 (void)httpChannel->GetResponseHeader( 1018 "content-security-policy-report-only"_ns, tCspROHeaderValue); 1019 1020 // if there are no CSP values, then there is nothing to do here. 1021 if (tCspHeaderValue.IsEmpty() && tCspROHeaderValue.IsEmpty()) { 1022 return NS_OK; 1023 } 1024 } 1025 1026 nsCOMPtr<nsIPrincipal> resultPrincipal; 1027 rv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal( 1028 aChannel, getter_AddRefs(resultPrincipal)); 1029 NS_ENSURE_SUCCESS(rv, rv); 1030 1031 RefPtr<extensions::WebExtensionPolicy> addonPolicy; 1032 if (!httpChannel) { 1033 addonPolicy = BasePrincipal::Cast(resultPrincipal)->AddonPolicy(); 1034 if (!addonPolicy) { 1035 // Neither a HTTP channel, nor a moz-extension:-resource. 1036 // CSP is not supported. 1037 return NS_OK; 1038 } 1039 } 1040 1041 RefPtr<nsCSPContext> csp = new nsCSPContext(); 1042 // This CSPContext is only used for checking frame-ancestors, we 1043 // will parse the CSP again anyway. (Unless this blocks the load, but 1044 // parser warnings aren't really important in that case) 1045 csp->SuppressParserLogMessages(); 1046 1047 nsCOMPtr<nsIURI> selfURI; 1048 nsAutoCString referrerSpec; 1049 if (httpChannel) { 1050 aChannel->GetURI(getter_AddRefs(selfURI)); 1051 nsCOMPtr<nsIReferrerInfo> referrerInfo = httpChannel->GetReferrerInfo(); 1052 if (referrerInfo) { 1053 referrerInfo->GetComputedReferrerSpec(referrerSpec); 1054 } 1055 } else { 1056 // aChannel::GetURI would return the jar: or file:-URI for extensions. 1057 // Use the "final" URI to get the actual moz-extension:-URL. 1058 NS_GetFinalChannelURI(aChannel, getter_AddRefs(selfURI)); 1059 } 1060 1061 uint64_t innerWindowID = loadInfo->GetInnerWindowID(); 1062 1063 rv = csp->SetRequestContextWithPrincipal(resultPrincipal, selfURI, 1064 referrerSpec, innerWindowID); 1065 if (NS_WARN_IF(NS_FAILED(rv))) { 1066 return rv; 1067 } 1068 1069 if (addonPolicy) { 1070 csp->AppendPolicy(addonPolicy->BaseCSP(), false, false); 1071 csp->AppendPolicy(addonPolicy->ExtensionPageCSP(), false, false); 1072 } else { 1073 NS_ConvertASCIItoUTF16 cspHeaderValue(tCspHeaderValue); 1074 NS_ConvertASCIItoUTF16 cspROHeaderValue(tCspROHeaderValue); 1075 1076 // ----- if there's a full-strength CSP header, apply it. 1077 if (!cspHeaderValue.IsEmpty()) { 1078 rv = CSP_AppendCSPFromHeader(csp, cspHeaderValue, false); 1079 NS_ENSURE_SUCCESS(rv, rv); 1080 } 1081 1082 // ----- if there's a report-only CSP header, apply it. 1083 if (!cspROHeaderValue.IsEmpty()) { 1084 rv = CSP_AppendCSPFromHeader(csp, cspROHeaderValue, true); 1085 NS_ENSURE_SUCCESS(rv, rv); 1086 } 1087 } 1088 1089 // ----- Enforce frame-ancestor policy on any applied policies 1090 bool safeAncestry = false; 1091 // PermitsAncestry sends violation reports when necessary 1092 rv = csp->PermitsAncestry(loadInfo, &safeAncestry); 1093 1094 if (NS_FAILED(rv) || !safeAncestry) { 1095 // stop! ERROR page! 1096 return NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION; 1097 } 1098 1099 // return the CSP for x-frame-options check 1100 csp.forget(aOutCSP); 1101 1102 return NS_OK; 1103 } 1104 1105 void EnforceCSPFrameAncestorPolicy(nsIChannel* aChannel, 1106 const nsresult& aError) { 1107 if (aError == NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION) { 1108 aChannel->Cancel(NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION); 1109 } 1110 } 1111 1112 void EnforceXFrameOptionsCheck(nsIChannel* aChannel, 1113 nsIContentSecurityPolicy* aCsp) { 1114 MOZ_ASSERT(aChannel); 1115 bool isFrameOptionsIgnored = false; 1116 // check for XFO options 1117 // XFO checks can be skipped if there are frame ancestors 1118 if (!FramingChecker::CheckFrameOptions(aChannel, aCsp, 1119 isFrameOptionsIgnored)) { 1120 // stop! ERROR page! 1121 aChannel->Cancel(NS_ERROR_XFO_VIOLATION); 1122 } 1123 1124 if (isFrameOptionsIgnored) { 1125 // log warning to console that xfo is ignored because of CSP 1126 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); 1127 uint64_t innerWindowID = loadInfo->GetInnerWindowID(); 1128 bool privateWindow = loadInfo->GetOriginAttributes().IsPrivateBrowsing(); 1129 AutoTArray<nsString, 2> params = {u"x-frame-options"_ns, 1130 u"frame-ancestors"_ns}; 1131 CSP_LogLocalizedStr("IgnoringSrcBecauseOfDirective", params, 1132 ""_ns, // no sourcefile 1133 u""_ns, // no scriptsample 1134 0, // no linenumber 1135 1, // no columnnumber 1136 nsIScriptError::warningFlag, 1137 "IgnoringSrcBecauseOfDirective"_ns, innerWindowID, 1138 privateWindow); 1139 } 1140 } 1141 1142 /* static */ 1143 void nsContentSecurityUtils::PerformCSPFrameAncestorAndXFOCheck( 1144 nsIChannel* aChannel) { 1145 nsCOMPtr<nsIContentSecurityPolicy> csp; 1146 nsresult rv = CheckCSPFrameAncestorPolicy(aChannel, getter_AddRefs(csp)); 1147 1148 if (NS_FAILED(rv)) { 1149 EnforceCSPFrameAncestorPolicy(aChannel, rv); 1150 return; 1151 } 1152 1153 // X-Frame-Options needs to be enforced after CSP frame-ancestors 1154 // checks because if frame-ancestors is present, then x-frame-options 1155 // will be discarded 1156 EnforceXFrameOptionsCheck(aChannel, csp); 1157 } 1158 /* static */ 1159 bool nsContentSecurityUtils::CheckCSPFrameAncestorAndXFO(nsIChannel* aChannel) { 1160 nsCOMPtr<nsIContentSecurityPolicy> csp; 1161 nsresult rv = CheckCSPFrameAncestorPolicy(aChannel, getter_AddRefs(csp)); 1162 1163 if (NS_FAILED(rv)) { 1164 return false; 1165 } 1166 1167 bool isFrameOptionsIgnored = false; 1168 1169 return FramingChecker::CheckFrameOptions(aChannel, csp, 1170 isFrameOptionsIgnored); 1171 } 1172 1173 // https://w3c.github.io/webappsec-csp/#is-element-nonceable 1174 /* static */ 1175 nsString nsContentSecurityUtils::GetIsElementNonceableNonce( 1176 const Element& aElement) { 1177 // Step 1. If element does not have an attribute named "nonce", return "Not 1178 // Nonceable". 1179 nsString nonce; 1180 if (nsString* cspNonce = 1181 static_cast<nsString*>(aElement.GetProperty(nsGkAtoms::nonce))) { 1182 nonce = *cspNonce; 1183 } 1184 if (nonce.IsEmpty()) { 1185 return nonce; 1186 } 1187 1188 // Step 2. If element is a script element, then for each attribute of 1189 // element’s attribute list: 1190 if (nsCOMPtr<nsIScriptElement> script = 1191 do_QueryInterface(const_cast<Element*>(&aElement))) { 1192 auto containsScriptOrStyle = [](const nsAString& aStr) { 1193 return aStr.LowerCaseFindASCII("<script") != kNotFound || 1194 aStr.LowerCaseFindASCII("<style") != kNotFound; 1195 }; 1196 1197 nsString value; 1198 uint32_t i = 0; 1199 while (BorrowedAttrInfo info = aElement.GetAttrInfoAt(i++)) { 1200 // Step 2.1. If attribute’s name contains an ASCII case-insensitive match 1201 // for "<script" or "<style", return "Not Nonceable". 1202 const nsAttrName* name = info.mName; 1203 if (nsAtom* prefix = name->GetPrefix()) { 1204 if (containsScriptOrStyle(nsDependentAtomString(prefix))) { 1205 return EmptyString(); 1206 } 1207 } 1208 if (containsScriptOrStyle(nsDependentAtomString(name->LocalName()))) { 1209 return EmptyString(); 1210 } 1211 1212 // Step 2.2. If attribute’s value contains an ASCII case-insensitive match 1213 // for "<script" or "<style", return "Not Nonceable". 1214 info.mValue->ToString(value); 1215 if (containsScriptOrStyle(value)) { 1216 return EmptyString(); 1217 } 1218 } 1219 } 1220 1221 // Step 3. If element had a duplicate-attribute parse error during 1222 // tokenization, return "Not Nonceable". 1223 if (aElement.HasFlag(ELEMENT_PARSER_HAD_DUPLICATE_ATTR_ERROR)) { 1224 return EmptyString(); 1225 } 1226 1227 // Step 4. Return "Nonceable". 1228 return nonce; 1229 } 1230 1231 #if defined(DEBUG) 1232 1233 # include "mozilla/dom/nsCSPContext.h" 1234 1235 // The follow lists define the exceptions to the usual default list 1236 // of allowed CSP sources for internal pages. The default list 1237 // allows chrome: and resource: URLs for everything, with the exception 1238 // of object-src. 1239 // 1240 // Generally adding something to these lists should be seen as a bad 1241 // sign, but it is obviously impossible for some pages, e.g. 1242 // those that are meant to include content from the web. 1243 // 1244 // Do note: We will _never_ allow any additional source for scripts 1245 // (script-src, script-src-elem, script-src-attr, worker-src) 1246 1247 // style-src data: 1248 // This is more or less the same as allowing arbitrary inline styles. 1249 static nsLiteralCString sStyleSrcDataAllowList[] = { 1250 "about:preferences"_ns, "about:settings"_ns, 1251 // STOP! Do not add anything to this list. 1252 }; 1253 // style-src 'unsafe-inline' 1254 static nsLiteralCString sStyleSrcUnsafeInlineAllowList[] = { 1255 // Bug 1579160: Remove 'unsafe-inline' from style-src within 1256 // about:preferences 1257 "about:preferences"_ns, 1258 "about:settings"_ns, 1259 // Bug 1571346: Remove 'unsafe-inline' from style-src within about:addons 1260 "about:addons"_ns, 1261 // Bug 1584485: Remove 'unsafe-inline' from style-src within: 1262 // * about:newtab 1263 // * about:welcome 1264 // * about:home 1265 "about:newtab"_ns, 1266 "about:welcome"_ns, 1267 "about:home"_ns, 1268 "chrome://browser/content/pageinfo/pageInfo.xhtml"_ns, 1269 "chrome://browser/content/places/bookmarkProperties.xhtml"_ns, 1270 "chrome://browser/content/places/bookmarksSidebar.xhtml"_ns, 1271 "chrome://browser/content/places/historySidebar.xhtml"_ns, 1272 "chrome://browser/content/places/interactionsViewer.html"_ns, 1273 "chrome://browser/content/places/places.xhtml"_ns, 1274 "chrome://browser/content/preferences/dialogs/applicationManager.xhtml"_ns, 1275 "chrome://browser/content/preferences/dialogs/browserLanguages.xhtml"_ns, 1276 "chrome://browser/content/preferences/dialogs/clearSiteData.xhtml"_ns, 1277 "chrome://browser/content/preferences/dialogs/colors.xhtml"_ns, 1278 "chrome://browser/content/preferences/dialogs/connection.xhtml"_ns, 1279 "chrome://browser/content/preferences/dialogs/containers.xhtml"_ns, 1280 "chrome://browser/content/preferences/dialogs/dohExceptions.xhtml"_ns, 1281 "chrome://browser/content/preferences/dialogs/fonts.xhtml"_ns, 1282 "chrome://browser/content/preferences/dialogs/languages.xhtml"_ns, 1283 "chrome://browser/content/preferences/dialogs/permissions.xhtml"_ns, 1284 "chrome://browser/content/preferences/dialogs/selectBookmark.xhtml"_ns, 1285 "chrome://browser/content/preferences/dialogs/siteDataSettings.xhtml"_ns, 1286 "chrome://browser/content/preferences/dialogs/sitePermissions.xhtml"_ns, 1287 "chrome://browser/content/preferences/dialogs/syncChooseWhatToSync.xhtml"_ns, 1288 "chrome://browser/content/preferences/dialogs/translations.xhtml"_ns, 1289 "chrome://browser/content/preferences/fxaPairDevice.xhtml"_ns, 1290 "chrome://browser/content/safeMode.xhtml"_ns, 1291 "chrome://browser/content/sanitize.xhtml"_ns, 1292 "chrome://browser/content/sanitize_v2.xhtml"_ns, 1293 "chrome://browser/content/search/addEngine.xhtml"_ns, 1294 "chrome://browser/content/setDesktopBackground.xhtml"_ns, 1295 "chrome://browser/content/spotlight.html"_ns, 1296 "chrome://devtools/content/debugger/index.html"_ns, 1297 "chrome://devtools/content/framework/browser-toolbox/window.html"_ns, 1298 "chrome://devtools/content/framework/toolbox-options.html"_ns, 1299 "chrome://devtools/content/framework/toolbox-window.xhtml"_ns, 1300 "chrome://devtools/content/inspector/index.xhtml"_ns, 1301 "chrome://devtools/content/inspector/markup/markup.xhtml"_ns, 1302 "chrome://devtools/content/netmonitor/index.html"_ns, 1303 "chrome://devtools/content/memory/index.xhtml"_ns, 1304 "chrome://devtools/content/shared/sourceeditor/codemirror/cmiframe.html"_ns, 1305 "chrome://devtools/content/webconsole/index.html"_ns, 1306 "chrome://formautofill/content/manageAddresses.xhtml"_ns, 1307 "chrome://formautofill/content/manageCreditCards.xhtml"_ns, 1308 "chrome://gfxsanity/content/sanityparent.html"_ns, 1309 "chrome://gfxsanity/content/sanitytest.html"_ns, 1310 "chrome://global/content/commonDialog.xhtml"_ns, 1311 "chrome://global/content/resetProfileProgress.xhtml"_ns, 1312 "chrome://layoutdebug/content/layoutdebug.xhtml"_ns, 1313 "chrome://mozapps/content/downloads/unknownContentType.xhtml"_ns, 1314 "chrome://mozapps/content/handling/appChooser.xhtml"_ns, 1315 "chrome://mozapps/content/preferences/changemp.xhtml"_ns, 1316 "chrome://mozapps/content/preferences/removemp.xhtml"_ns, 1317 "chrome://mozapps/content/profile/profileDowngrade.xhtml"_ns, 1318 "chrome://mozapps/content/profile/profileSelection.xhtml"_ns, 1319 "chrome://mozapps/content/profile/createProfileWizard.xhtml"_ns, 1320 "chrome://mozapps/content/update/history.xhtml"_ns, 1321 "chrome://mozapps/content/update/updateElevation.xhtml"_ns, 1322 "chrome://pippki/content/certManager.xhtml"_ns, 1323 "chrome://pippki/content/changepassword.xhtml"_ns, 1324 "chrome://pippki/content/deletecert.xhtml"_ns, 1325 "chrome://pippki/content/device_manager.xhtml"_ns, 1326 "chrome://pippki/content/downloadcert.xhtml"_ns, 1327 "chrome://pippki/content/editcacert.xhtml"_ns, 1328 "chrome://pippki/content/load_device.xhtml"_ns, 1329 "chrome://pippki/content/setp12password.xhtml"_ns, 1330 }; 1331 // img-src moz-remote-image: 1332 static nsLiteralCString sImgSrcMozRemoteImageAllowList[] = { 1333 "about:preferences"_ns, 1334 "about:settings"_ns, 1335 "chrome://browser/content/preferences/dialogs/applicationManager.xhtml"_ns, 1336 "chrome://mozapps/content/handling/appChooser.xhtml"_ns, 1337 }; 1338 // img-src data: blob: 1339 static nsLiteralCString sImgSrcDataBlobAllowList[] = { 1340 "about:addons"_ns, 1341 "about:debugging"_ns, 1342 "about:deleteprofile"_ns, 1343 "about:devtools-toolbox"_ns, 1344 "about:editprofile"_ns, 1345 "about:firefoxview"_ns, 1346 "about:home"_ns, 1347 "about:inference"_ns, 1348 "about:logins"_ns, 1349 "about:newprofile"_ns, 1350 "about:newtab"_ns, 1351 "about:opentabs"_ns, 1352 "about:preferences"_ns, 1353 "about:privatebrowsing"_ns, 1354 "about:processes"_ns, 1355 "about:profilemanager"_ns, 1356 "about:protections"_ns, 1357 "about:reader"_ns, 1358 "about:sessionrestore"_ns, 1359 "about:settings"_ns, 1360 "about:test-about-content-search-ui"_ns, 1361 "about:welcome"_ns, 1362 "chrome://browser/content/aboutDialog.xhtml"_ns, 1363 "chrome://browser/content/aboutlogins/aboutLogins.html"_ns, 1364 "chrome://browser/content/genai/chat.html"_ns, 1365 "chrome://browser/content/places/bookmarksSidebar.xhtml"_ns, 1366 "chrome://browser/content/places/places.xhtml"_ns, 1367 "chrome://browser/content/preferences/dialogs/permissions.xhtml"_ns, 1368 "chrome://browser/content/preferences/fxaPairDevice.xhtml"_ns, 1369 "chrome://browser/content/screenshots/screenshots-preview.html"_ns, 1370 "chrome://browser/content/sidebar/sidebar-customize.html"_ns, 1371 "chrome://browser/content/sidebar/sidebar-history.html"_ns, 1372 "chrome://browser/content/sidebar/sidebar-syncedtabs.html"_ns, 1373 "chrome://browser/content/spotlight.html"_ns, 1374 "chrome://browser/content/syncedtabs/sidebar.xhtml"_ns, 1375 "chrome://browser/content/webext-panels.xhtml"_ns, 1376 "chrome://devtools/content/application/index.html"_ns, 1377 "chrome://devtools/content/framework/browser-toolbox/window.html"_ns, 1378 "chrome://devtools/content/framework/toolbox-window.xhtml"_ns, 1379 "chrome://devtools/content/inspector/index.xhtml"_ns, 1380 "chrome://devtools/content/inspector/markup/markup.xhtml"_ns, 1381 "chrome://devtools/content/netmonitor/index.html"_ns, 1382 "chrome://devtools/content/responsive/toolbar.xhtml"_ns, 1383 "chrome://devtools/content/shared/sourceeditor/codemirror/cmiframe.html"_ns, 1384 "chrome://devtools/content/webconsole/index.html"_ns, 1385 "chrome://global/content/alerts/alert.xhtml"_ns, 1386 "chrome://global/content/print.html"_ns, 1387 "chrome://browser/content/torpreferences/requestBridgeDialog.xhtml"_ns, 1388 }; 1389 // img-src https: 1390 static nsLiteralCString sImgSrcHttpsAllowList[] = { 1391 "about:addons"_ns, 1392 "about:debugging"_ns, 1393 "about:home"_ns, 1394 "about:newtab"_ns, 1395 "about:preferences"_ns, 1396 "about:settings"_ns, 1397 "about:welcome"_ns, 1398 "chrome://devtools/content/application/index.html"_ns, 1399 "chrome://devtools/content/framework/browser-toolbox/window.html"_ns, 1400 "chrome://devtools/content/framework/toolbox-window.xhtml"_ns, 1401 "chrome://global/content/alerts/alert.xhtml"_ns, 1402 }; 1403 // img-src http: 1404 // UNSAFE! Do not use. 1405 static nsLiteralCString sImgSrcHttpAllowList[] = { 1406 "about:addons"_ns, 1407 "chrome://devtools/content/application/index.html"_ns, 1408 "chrome://devtools/content/framework/browser-toolbox/window.html"_ns, 1409 "chrome://devtools/content/framework/toolbox-window.xhtml"_ns, 1410 "chrome://global/content/alerts/alert.xhtml"_ns, 1411 // STOP! Do not add anything to this list. 1412 }; 1413 // img-src jar: file: 1414 // UNSAFE! Do not use. 1415 static nsLiteralCString sImgSrcAddonsAllowList[] = { 1416 "about:addons"_ns, 1417 // STOP! Do not add anything to this list. 1418 }; 1419 // img-src * 1420 // UNSAFE! Allows loading everything. 1421 static nsLiteralCString sImgSrcWildcardAllowList[] = { 1422 "about:reader"_ns, "chrome://browser/content/syncedtabs/sidebar.xhtml"_ns, 1423 // STOP! Do not add anything to this list. 1424 }; 1425 // img-src https://example.org 1426 // Any https host source. 1427 static nsLiteralCString sImgSrcHttpsHostAllowList[] = { 1428 "about:logins"_ns, 1429 "chrome://browser/content/aboutlogins/aboutLogins.html"_ns, 1430 "chrome://browser/content/spotlight.html"_ns, 1431 }; 1432 // media-src * 1433 // UNSAFE! Allows loading everything. 1434 static nsLiteralCString sMediaSrcWildcardAllowList[] = { 1435 "about:reader"_ns, 1436 // STOP! Do not add anything to this list. 1437 }; 1438 // media-src https://example.org 1439 // Any https host source. 1440 static nsLiteralCString sMediaSrcHttpsHostAllowList[] = {"about:welcome"_ns}; 1441 // connect-src https: 1442 static nsLiteralCString sConnectSrcHttpsAllowList[] = { 1443 "about:addons"_ns, 1444 "about:home"_ns, 1445 "about:newtab"_ns, 1446 "about:welcome"_ns, 1447 }; 1448 // connect-src data: http: 1449 // UNSAFE! Do not use. 1450 static nsLiteralCString sConnectSrcAddonsAllowList[] = { 1451 "about:addons"_ns, 1452 // STOP! Do not add anything to this list. 1453 }; 1454 // connect-src https://example.org 1455 // Any https host source. 1456 static nsLiteralCString sConnectSrcHttpsHostAllowList[] = {"about:logging"_ns}; 1457 1458 class DisallowingVisitor : public nsCSPSrcVisitor { 1459 public: 1460 DisallowingVisitor(CSPDirective aDirective, nsACString& aURL) 1461 : mDirective(aDirective), mURL(aURL) {} 1462 1463 bool visit(const nsCSPPolicy* aPolicy) { 1464 return aPolicy->visitDirectiveSrcs(mDirective, this); 1465 } 1466 1467 bool visitSchemeSrc(const nsCSPSchemeSrc& src) override { 1468 Assert(src); 1469 return false; 1470 }; 1471 1472 bool visitHostSrc(const nsCSPHostSrc& src) override { 1473 Assert(src); 1474 return false; 1475 }; 1476 1477 bool visitKeywordSrc(const nsCSPKeywordSrc& src) override { 1478 // Using the 'none' keyword doesn't allow anything. 1479 if (src.isKeyword(CSPKeyword::CSP_NONE)) { 1480 return true; 1481 } 1482 1483 Assert(src); 1484 return false; 1485 } 1486 1487 bool visitNonceSrc(const nsCSPNonceSrc& src) override { 1488 Assert(src); 1489 return false; 1490 }; 1491 1492 bool visitHashSrc(const nsCSPHashSrc& src) override { 1493 Assert(src); 1494 return false; 1495 }; 1496 1497 protected: 1498 bool CheckAllowList(Span<nsLiteralCString> aList) { 1499 for (const nsLiteralCString& entry : aList) { 1500 // please note that we perform a substring match here on purpose, 1501 // so we don't have to deal and parse out all the query arguments 1502 // the various about pages rely on. 1503 if (StringBeginsWith(mURL, entry)) { 1504 return true; 1505 } 1506 } 1507 1508 return false; 1509 } 1510 1511 void Assert(const nsCSPBaseSrc& aSrc) { 1512 nsAutoString srcStr; 1513 aSrc.toString(srcStr); 1514 NS_ConvertUTF16toUTF8 srcStrUtf8(srcStr); 1515 1516 MOZ_CRASH_UNSAFE_PRINTF( 1517 "Page %s must not contain a CSP with the " 1518 "directive %s that includes %s", 1519 mURL.get(), CSP_CSPDirectiveToString(mDirective), srcStrUtf8.get()); 1520 } 1521 1522 CSPDirective mDirective; 1523 nsCString mURL; 1524 }; 1525 1526 // Only allows loads from chrome:, moz-src: and resource: URLs: 1527 class AllowBuiltinSrcVisitor : public DisallowingVisitor { 1528 public: 1529 AllowBuiltinSrcVisitor(CSPDirective aDirective, nsACString& aURL) 1530 : DisallowingVisitor(aDirective, aURL) {} 1531 1532 bool visitSchemeSrc(const nsCSPSchemeSrc& src) override { 1533 nsAutoString scheme; 1534 src.getScheme(scheme); 1535 if (scheme == u"chrome"_ns || scheme == u"moz-src" || 1536 scheme == u"resource"_ns) { 1537 return true; 1538 } 1539 1540 return DisallowingVisitor::visitSchemeSrc(src); 1541 } 1542 1543 protected: 1544 bool VisitHostSrcWithWildcardAndHttpsHostAllowLists( 1545 const nsCSPHostSrc& aSrc, const Span<nsLiteralCString> aWildcard, 1546 const Span<nsLiteralCString> aHttpsHost) { 1547 nsAutoString str; 1548 aSrc.toString(str); 1549 1550 if (str.EqualsLiteral("*")) { 1551 if (CheckAllowList(aWildcard)) { 1552 return true; 1553 } 1554 } else { 1555 MOZ_ASSERT(StringBeginsWith(str, u"https://"_ns), 1556 "Must use https: for host sources!"); 1557 MOZ_ASSERT(!FindInReadable(u"*"_ns, str), 1558 "Can not include wildcard in host sources!"); 1559 if (CheckAllowList(aHttpsHost)) { 1560 return true; 1561 } 1562 } 1563 1564 return DisallowingVisitor::visitHostSrc(aSrc); 1565 } 1566 }; 1567 1568 class StyleSrcVisitor : public AllowBuiltinSrcVisitor { 1569 public: 1570 StyleSrcVisitor(CSPDirective aDirective, nsACString& aURL) 1571 : AllowBuiltinSrcVisitor(aDirective, aURL) { 1572 MOZ_ASSERT(aDirective == CSPDirective::STYLE_SRC_DIRECTIVE); 1573 } 1574 1575 bool visitSchemeSrc(const nsCSPSchemeSrc& src) override { 1576 nsAutoString scheme; 1577 src.getScheme(scheme); 1578 1579 if (scheme == u"data"_ns) { 1580 if (CheckAllowList(Span(sStyleSrcDataAllowList))) { 1581 return true; 1582 } 1583 } 1584 1585 return AllowBuiltinSrcVisitor::visitSchemeSrc(src); 1586 } 1587 1588 bool visitKeywordSrc(const nsCSPKeywordSrc& src) override { 1589 if (src.isKeyword(CSPKeyword::CSP_UNSAFE_INLINE)) { 1590 if (CheckAllowList(Span(sStyleSrcUnsafeInlineAllowList))) { 1591 return true; 1592 } 1593 } 1594 1595 return AllowBuiltinSrcVisitor::visitKeywordSrc(src); 1596 } 1597 }; 1598 1599 class ImgSrcVisitor : public AllowBuiltinSrcVisitor { 1600 public: 1601 ImgSrcVisitor(CSPDirective aDirective, nsACString& aURL) 1602 : AllowBuiltinSrcVisitor(aDirective, aURL) { 1603 MOZ_ASSERT(aDirective == CSPDirective::IMG_SRC_DIRECTIVE); 1604 } 1605 1606 bool visitSchemeSrc(const nsCSPSchemeSrc& src) override { 1607 nsAutoString scheme; 1608 src.getScheme(scheme); 1609 1610 // moz-icon is used for loading known favicons. 1611 if (scheme == u"moz-icon"_ns) { 1612 return true; 1613 } 1614 1615 // moz-remote-image: safely re-encodes the image, but can still be used for 1616 // arbitrary network requests. 1617 if (scheme == u"moz-remote-image"_ns) { 1618 if (CheckAllowList(sImgSrcMozRemoteImageAllowList)) { 1619 return true; 1620 } 1621 } 1622 1623 // data: and blob: can be used to decode arbitrary images. 1624 if (scheme == u"data"_ns || scheme == u"blob") { 1625 if (CheckAllowList(sImgSrcDataBlobAllowList)) { 1626 return true; 1627 } 1628 } 1629 1630 if (scheme == u"https"_ns) { 1631 if (CheckAllowList(Span(sImgSrcHttpsAllowList))) { 1632 return true; 1633 } 1634 } 1635 1636 if (scheme == u"http"_ns) { 1637 if (CheckAllowList(Span(sImgSrcHttpAllowList))) { 1638 return true; 1639 } 1640 } 1641 1642 if (scheme == u"jar"_ns || scheme == u"file"_ns) { 1643 if (CheckAllowList(Span(sImgSrcAddonsAllowList))) { 1644 return true; 1645 } 1646 } 1647 1648 return AllowBuiltinSrcVisitor::visitSchemeSrc(src); 1649 } 1650 1651 bool visitHostSrc(const nsCSPHostSrc& src) override { 1652 return VisitHostSrcWithWildcardAndHttpsHostAllowLists( 1653 src, sImgSrcWildcardAllowList, sImgSrcHttpsHostAllowList); 1654 } 1655 }; 1656 1657 class MediaSrcVisitor : public AllowBuiltinSrcVisitor { 1658 public: 1659 MediaSrcVisitor(CSPDirective aDirective, nsACString& aURL) 1660 : AllowBuiltinSrcVisitor(aDirective, aURL) { 1661 MOZ_ASSERT(aDirective == CSPDirective::MEDIA_SRC_DIRECTIVE); 1662 } 1663 1664 bool visitHostSrc(const nsCSPHostSrc& src) override { 1665 return VisitHostSrcWithWildcardAndHttpsHostAllowLists( 1666 src, sMediaSrcWildcardAllowList, sMediaSrcHttpsHostAllowList); 1667 } 1668 }; 1669 1670 class ConnectSrcVisitor : public AllowBuiltinSrcVisitor { 1671 public: 1672 ConnectSrcVisitor(CSPDirective aDirective, nsACString& aURL) 1673 : AllowBuiltinSrcVisitor(aDirective, aURL) { 1674 MOZ_ASSERT(aDirective == CSPDirective::CONNECT_SRC_DIRECTIVE); 1675 } 1676 1677 bool visitSchemeSrc(const nsCSPSchemeSrc& src) override { 1678 nsAutoString scheme; 1679 src.getScheme(scheme); 1680 1681 if (scheme == u"https"_ns) { 1682 if (CheckAllowList(Span(sConnectSrcHttpsAllowList))) { 1683 return true; 1684 } 1685 } 1686 1687 if (scheme == u"data"_ns || scheme == u"http") { 1688 if (CheckAllowList(Span(sConnectSrcAddonsAllowList))) { 1689 return true; 1690 } 1691 } 1692 1693 return AllowBuiltinSrcVisitor::visitSchemeSrc(src); 1694 } 1695 1696 bool visitHostSrc(const nsCSPHostSrc& src) override { 1697 return VisitHostSrcWithWildcardAndHttpsHostAllowLists( 1698 src, nullptr, sConnectSrcHttpsHostAllowList); 1699 } 1700 }; 1701 1702 class AddonSrcVisitor : public AllowBuiltinSrcVisitor { 1703 public: 1704 AddonSrcVisitor(CSPDirective aDirective, nsACString& aURL) 1705 : AllowBuiltinSrcVisitor(aDirective, aURL) { 1706 MOZ_ASSERT(aDirective == CSPDirective::DEFAULT_SRC_DIRECTIVE || 1707 aDirective == CSPDirective::SCRIPT_SRC_DIRECTIVE); 1708 } 1709 1710 bool visitHostSrc(const nsCSPHostSrc& src) override { 1711 nsAutoString str; 1712 src.toString(str); 1713 if (str == u"'self'"_ns) { 1714 return true; 1715 } 1716 return AllowBuiltinSrcVisitor::visitHostSrc(src); 1717 } 1718 1719 bool visitHashSrc(const nsCSPHashSrc& src) override { 1720 if (mDirective == CSPDirective::SCRIPT_SRC_DIRECTIVE) { 1721 return true; 1722 } 1723 return AllowBuiltinSrcVisitor::visitHashSrc(src); 1724 } 1725 }; 1726 1727 # define CHECK_DIR(DIR, VISITOR) \ 1728 do { \ 1729 VISITOR visitor(CSPDirective::DIR, spec); \ 1730 /* We don't assert here, because we know that the default fallback is \ 1731 * secure. */ \ 1732 visitor.visit(policy); \ 1733 } while (false) 1734 1735 /* static */ 1736 void nsContentSecurityUtils::AssertAboutPageHasCSP(Document* aDocument) { 1737 // We want to get to a point where all about: pages ship with a CSP. This 1738 // assertion ensures that we can not deploy new about: pages without a CSP. 1739 // Please note that any about: page should not use inline JS or inline CSS, 1740 // and instead should load JS and CSS from an external file (*.js, *.css) 1741 // which allows us to apply a strong CSP omitting 'unsafe-inline'. Ideally, 1742 // the CSP allows precisely the resources that need to be loaded; but it 1743 // should at least be as strong as: 1744 // <meta http-equiv="Content-Security-Policy" content="default-src chrome:; 1745 // object-src 'none'"/> 1746 1747 // This is a data document, created using DOMParser or 1748 // document.implementation.createDocument() or such, not an about: page which 1749 // is loaded as a web page. 1750 if (aDocument->IsLoadedAsData()) { 1751 return; 1752 } 1753 1754 // Check if we should skip the assertion 1755 if (StaticPrefs::dom_security_skip_about_page_has_csp_assert()) { 1756 return; 1757 } 1758 1759 // Check if we are loading an about: URI at all 1760 nsCOMPtr<nsIURI> documentURI = aDocument->GetDocumentURI(); 1761 if (!documentURI->SchemeIs("about")) { 1762 return; 1763 } 1764 1765 nsCSPContext* csp = nsCSPContext::Cast( 1766 PolicyContainer::GetCSP(aDocument->GetPolicyContainer())); 1767 bool foundDefaultSrc = false; 1768 uint32_t policyCount = 0; 1769 if (csp) { 1770 csp->GetPolicyCount(&policyCount); 1771 for (uint32_t i = 0; i < policyCount; i++) { 1772 const nsCSPPolicy* policy = csp->GetPolicy(i); 1773 1774 foundDefaultSrc = 1775 policy->hasDirective(CSPDirective::DEFAULT_SRC_DIRECTIVE); 1776 if (foundDefaultSrc) { 1777 break; 1778 } 1779 } 1780 } 1781 1782 // Check if we should skip the allowlist and assert right away. Please note 1783 // that this pref can and should only be set for automated testing. 1784 if (StaticPrefs::dom_security_skip_about_page_csp_allowlist_and_assert()) { 1785 NS_ASSERTION(foundDefaultSrc, "about: page must have a CSP"); 1786 return; 1787 } 1788 1789 nsAutoCString spec; 1790 documentURI->GetSpec(spec); 1791 ToLowerCase(spec); 1792 1793 // This allowlist contains about: pages that are permanently allowed to 1794 // render without a CSP applied. 1795 static nsLiteralCString sAllowedAboutPagesWithNoCSP[] = { 1796 // about:blank is a special about page -> no CSP 1797 "about:blank"_ns, 1798 // about:srcdoc is a special about page -> no CSP 1799 "about:srcdoc"_ns, 1800 // about:sync-log displays plain text only -> no CSP 1801 "about:sync-log"_ns, 1802 // about:logo just displays the firefox logo -> no CSP 1803 "about:logo"_ns, 1804 // about:sync is a special mozilla-signed developer addon with low usage 1805 // -> 1806 // no CSP 1807 "about:sync"_ns, 1808 # if defined(ANDROID) 1809 "about:config"_ns, 1810 # endif 1811 }; 1812 1813 for (const nsLiteralCString& allowlistEntry : sAllowedAboutPagesWithNoCSP) { 1814 // please note that we perform a substring match here on purpose, 1815 // so we don't have to deal and parse out all the query arguments 1816 // the various about pages rely on. 1817 if (StringBeginsWith(spec, allowlistEntry)) { 1818 return; 1819 } 1820 } 1821 1822 if (aDocument->IsExtensionPage()) { 1823 // Extensions have two CSP policies applied where the baseline CSP 1824 // includes 'unsafe-eval' and 'unsafe-inline', hence we only 1825 // make sure the second CSP is more restrictive. 1826 // 1827 // Extension CSPs look quite different to other pages, so for now we just 1828 // assert some basic security properties. 1829 MOZ_ASSERT(policyCount == 2, 1830 "about: page from extension should have two CSP"); 1831 const nsCSPPolicy* policy = csp->GetPolicy(1); 1832 1833 { 1834 AddonSrcVisitor visitor(CSPDirective::DEFAULT_SRC_DIRECTIVE, spec); 1835 if (!visitor.visit(policy)) { 1836 MOZ_ASSERT(false, "about: page must contain a secure default-src"); 1837 } 1838 } 1839 1840 { 1841 DisallowingVisitor visitor(CSPDirective::OBJECT_SRC_DIRECTIVE, spec); 1842 if (!visitor.visit(policy)) { 1843 MOZ_ASSERT( 1844 false, 1845 "about: page must contain a secure object-src 'none'; directive"); 1846 } 1847 } 1848 1849 CHECK_DIR(SCRIPT_SRC_DIRECTIVE, AddonSrcVisitor); 1850 1851 nsTArray<nsString> directiveNames; 1852 policy->getDirectiveNames(directiveNames); 1853 for (nsString dir : directiveNames) { 1854 MOZ_ASSERT(!dir.EqualsLiteral("script-src-elem") && 1855 !dir.EqualsLiteral("script-src-attr")); 1856 } 1857 1858 return; 1859 } 1860 1861 MOZ_ASSERT(policyCount == 1, "about: page should have exactly one CSP"); 1862 1863 const nsCSPPolicy* policy = csp->GetPolicy(0); 1864 { 1865 AllowBuiltinSrcVisitor visitor(CSPDirective::DEFAULT_SRC_DIRECTIVE, spec); 1866 if (!visitor.visit(policy)) { 1867 MOZ_ASSERT(false, "about: page must contain a secure default-src"); 1868 } 1869 } 1870 1871 { 1872 DisallowingVisitor visitor(CSPDirective::OBJECT_SRC_DIRECTIVE, spec); 1873 if (!visitor.visit(policy)) { 1874 MOZ_ASSERT( 1875 false, 1876 "about: page must contain a secure object-src 'none'; directive"); 1877 } 1878 } 1879 1880 CHECK_DIR(SCRIPT_SRC_DIRECTIVE, AllowBuiltinSrcVisitor); 1881 CHECK_DIR(STYLE_SRC_DIRECTIVE, StyleSrcVisitor); 1882 CHECK_DIR(IMG_SRC_DIRECTIVE, ImgSrcVisitor); 1883 CHECK_DIR(MEDIA_SRC_DIRECTIVE, MediaSrcVisitor); 1884 CHECK_DIR(CONNECT_SRC_DIRECTIVE, ConnectSrcVisitor); 1885 1886 // Make sure we have a checker for all the directives that are being used. 1887 nsTArray<nsString> directiveNames; 1888 policy->getDirectiveNames(directiveNames); 1889 for (nsString dir : directiveNames) { 1890 if (dir.EqualsLiteral("default-src") || dir.EqualsLiteral("object-src") || 1891 dir.EqualsLiteral("script-src") || dir.EqualsLiteral("style-src") || 1892 dir.EqualsLiteral("img-src") || dir.EqualsLiteral("media-src") || 1893 dir.EqualsLiteral("connect-src")) { 1894 continue; 1895 } 1896 1897 NS_WARNING( 1898 nsPrintfCString( 1899 "Page %s must not contain a CSP with the unchecked directive %s", 1900 spec.get(), NS_ConvertUTF16toUTF8(dir).get()) 1901 .get()); 1902 MOZ_ASSERT(false, "Unchecked CSP directive found on internal page."); 1903 } 1904 } 1905 1906 /* static */ 1907 void nsContentSecurityUtils::AssertChromePageHasCSP(Document* aDocument) { 1908 nsCOMPtr<nsIURI> documentURI = aDocument->GetDocumentURI(); 1909 if (!documentURI->SchemeIs("chrome")) { 1910 return; 1911 } 1912 1913 // We load a lot of SVG images from chrome:. 1914 if (aDocument->IsBeingUsedAsImage() || aDocument->IsLoadedAsData()) { 1915 return; 1916 } 1917 1918 nsAutoCString spec; 1919 documentURI->GetSpec(spec); 1920 1921 nsCOMPtr<nsIContentSecurityPolicy> csp = 1922 PolicyContainer::GetCSP(aDocument->GetPolicyContainer()); 1923 uint32_t count = 0; 1924 if (csp) { 1925 static_cast<nsCSPContext*>(csp.get())->GetPolicyCount(&count); 1926 } 1927 if (count != 0) { 1928 MOZ_ASSERT(count == 1, "chrome: pages should have exactly one CSP"); 1929 1930 // Both of these have a known weaker policy that differs 1931 // from all other chrome: pages. 1932 if (StringBeginsWith(spec, "chrome://browser/content/browser.xhtml"_ns) || 1933 StringBeginsWith(spec, 1934 "chrome://browser/content/hiddenWindowMac.xhtml"_ns)) { 1935 return; 1936 } 1937 1938 // Thunderbird's CSP does not pass these checks. 1939 # ifndef MOZ_THUNDERBIRD 1940 const nsCSPPolicy* policy = 1941 static_cast<nsCSPContext*>(csp.get())->GetPolicy(0); 1942 { 1943 AllowBuiltinSrcVisitor visitor(CSPDirective::DEFAULT_SRC_DIRECTIVE, spec); 1944 if (!visitor.visit(policy)) { 1945 MOZ_CRASH_UNSAFE_PRINTF( 1946 "Document (%s) CSP does not have a default-src!", spec.get()); 1947 } 1948 } 1949 1950 CHECK_DIR(SCRIPT_SRC_DIRECTIVE, AllowBuiltinSrcVisitor); 1951 // If the policy being checked does not have an explicit |script-src-attr| 1952 // directive, nsCSPPolicy::visitDirectiveSrcs will fallback to using the 1953 // |script-src| directive, but not default-src. 1954 // This means we can't use DisallowingVisitor here, because the script-src 1955 // fallback will usually contain at least a chrome: source. 1956 // This is not a problem from a security perspective, because inline scripts 1957 // are not loaded from an URL and thus still disallowed. 1958 CHECK_DIR(SCRIPT_SRC_ATTR_DIRECTIVE, AllowBuiltinSrcVisitor); 1959 CHECK_DIR(STYLE_SRC_DIRECTIVE, StyleSrcVisitor); 1960 CHECK_DIR(IMG_SRC_DIRECTIVE, ImgSrcVisitor); 1961 CHECK_DIR(MEDIA_SRC_DIRECTIVE, MediaSrcVisitor); 1962 // For now we don't require chrome: pages to have a `object-src 'none'` 1963 // directive. 1964 CHECK_DIR(OBJECT_SRC_DIRECTIVE, DisallowingVisitor); 1965 1966 nsTArray<nsString> directiveNames; 1967 policy->getDirectiveNames(directiveNames); 1968 for (nsString dir : directiveNames) { 1969 if (dir.EqualsLiteral("default-src") || dir.EqualsLiteral("script-src") || 1970 dir.EqualsLiteral("script-src-attr") || 1971 dir.EqualsLiteral("style-src") || dir.EqualsLiteral("img-src") || 1972 dir.EqualsLiteral("media-src") || dir.EqualsLiteral("object-src")) { 1973 continue; 1974 } 1975 1976 MOZ_CRASH_UNSAFE_PRINTF( 1977 "Document (%s) must not contain a CSP with the unchecked directive " 1978 "%s", 1979 spec.get(), NS_ConvertUTF16toUTF8(dir).get()); 1980 } 1981 # endif 1982 return; 1983 } 1984 1985 if (xpc::IsInAutomation()) { 1986 // Test files 1987 static nsLiteralCString sAllowedTestPathsWithNoCSP[] = { 1988 "chrome://mochikit/"_ns, 1989 "chrome://mochitests/"_ns, 1990 "chrome://pageloader/content/pageloader.xhtml"_ns, 1991 "chrome://reftest/"_ns, 1992 }; 1993 1994 for (const nsLiteralCString& entry : sAllowedTestPathsWithNoCSP) { 1995 if (StringBeginsWith(spec, entry)) { 1996 return; 1997 } 1998 } 1999 } 2000 2001 // CSP for browser.xhtml has been disabled 2002 if (spec.EqualsLiteral("chrome://browser/content/browser.xhtml") && 2003 !StaticPrefs::security_browser_xhtml_csp_enabled()) { 2004 return; 2005 } 2006 2007 MOZ_CRASH_UNSAFE_PRINTF("Document (%s) does not have a CSP!", spec.get()); 2008 } 2009 2010 # undef CHECK_DIR 2011 2012 #endif 2013 2014 // Add a lock for the gVeryFirstUnexpectedJavascriptLoadFilename variable 2015 static StaticMutex gVeryFirstUnexpectedJavascriptLoadFilenameMutex; 2016 static StaticAutoPtr<nsCString> gVeryFirstUnexpectedJavascriptLoadFilename 2017 MOZ_GUARDED_BY(gVeryFirstUnexpectedJavascriptLoadFilenameMutex); 2018 2019 /* static */ 2020 nsresult nsContentSecurityUtils::GetVeryFirstUnexpectedScriptFilename( 2021 nsACString& aFilename) { 2022 StaticMutexAutoLock lock(gVeryFirstUnexpectedJavascriptLoadFilenameMutex); 2023 if (gVeryFirstUnexpectedJavascriptLoadFilename) { 2024 aFilename = *gVeryFirstUnexpectedJavascriptLoadFilename; 2025 } 2026 return NS_OK; 2027 } 2028 2029 /* static */ 2030 bool nsContentSecurityUtils::ValidateScriptFilename(JSContext* cx, 2031 const char* aFilename) { 2032 // If the pref is permissive, allow everything 2033 if (StaticPrefs::security_allow_parent_unrestricted_js_loads()) { 2034 return true; 2035 } 2036 2037 // If we're not in the parent process allow everything (presently) 2038 if (!XRE_IsE10sParentProcess()) { 2039 return true; 2040 } 2041 2042 // If we have allowed eval (because of a user configuration or more 2043 // likely a test has requested it), and the script is an eval, allow it. 2044 nsDependentCString filename(aFilename); 2045 if (StaticPrefs::security_allow_eval_with_system_principal() || 2046 StaticPrefs::security_allow_eval_in_parent_process()) { 2047 if (StringEndsWith(filename, "> eval"_ns)) { 2048 return true; 2049 } 2050 } 2051 2052 DetectJsHacks(); 2053 2054 if (!StaticPrefs::security_parent_unrestricted_js_loads_skip_jshacks() && 2055 MOZ_UNLIKELY(!sJSHacksChecked)) { 2056 MOZ_LOG( 2057 sCSMLog, LogLevel::Debug, 2058 ("Allowing a javascript load of %s because " 2059 "we have not yet been able to determine if JS hacks may be present", 2060 aFilename)); 2061 return true; 2062 } 2063 2064 if (!StaticPrefs::security_parent_unrestricted_js_loads_skip_jshacks() && 2065 MOZ_UNLIKELY(sJSHacksPresent)) { 2066 MOZ_LOG(sCSMLog, LogLevel::Debug, 2067 ("Allowing a javascript load of %s because " 2068 "some JS hacks may be present", 2069 aFilename)); 2070 return true; 2071 } 2072 2073 if (XRE_IsE10sParentProcess() && 2074 !StaticPrefs::extensions_webextensions_remote()) { 2075 MOZ_LOG(sCSMLog, LogLevel::Debug, 2076 ("Allowing a javascript load of %s because the web extension " 2077 "process is disabled.", 2078 aFilename)); 2079 return true; 2080 } 2081 2082 if (StringBeginsWith(filename, "chrome://"_ns)) { 2083 // If it's a chrome:// url, allow it 2084 return true; 2085 } 2086 if (StringBeginsWith(filename, "resource://"_ns)) { 2087 // If it's a resource:// url, allow it 2088 return true; 2089 } 2090 if (StringBeginsWith(filename, "moz-src://"_ns)) { 2091 // If it's a moz-src:// url, allow it 2092 return true; 2093 } 2094 if (StringBeginsWith(filename, "file://"_ns)) { 2095 // We will temporarily allow all file:// URIs through for now 2096 return true; 2097 } 2098 if (StringBeginsWith(filename, "jar:file://"_ns)) { 2099 // We will temporarily allow all jar URIs through for now 2100 return true; 2101 } 2102 if (filename.Equals("about:sync-log"_ns)) { 2103 // about:sync-log runs in the parent process and displays a directory 2104 // listing. The listing has inline javascript that executes on load. 2105 return true; 2106 } 2107 2108 if (StringBeginsWith(filename, "moz-extension://"_ns)) { 2109 nsCOMPtr<nsIURI> uri; 2110 nsresult rv = NS_NewURI(getter_AddRefs(uri), aFilename); 2111 if (!NS_FAILED(rv) && NS_IsMainThread()) { 2112 mozilla::extensions::URLInfo url(uri); 2113 auto* policy = 2114 ExtensionPolicyService::GetSingleton().GetByHost(url.Host()); 2115 2116 if (policy && policy->IsPrivileged()) { 2117 MOZ_LOG(sCSMLog, LogLevel::Debug, 2118 ("Allowing a javascript load of %s because the web extension " 2119 "it is associated with is privileged.", 2120 aFilename)); 2121 return true; 2122 } 2123 } 2124 } else if (!NS_IsMainThread()) { 2125 WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx); 2126 if (workerPrivate && workerPrivate->IsPrivilegedAddonGlobal()) { 2127 MOZ_LOG(sCSMLog, LogLevel::Debug, 2128 ("Allowing a javascript load of %s because the web extension " 2129 "it is associated with is privileged.", 2130 aFilename)); 2131 return true; 2132 } 2133 } 2134 2135 auto kAllowedFilenamesPrefix = { 2136 // Until 371900 is fixed, we need to do something about about:downloads 2137 // and this is the most reasonable. See 1727770 2138 "about:downloads"_ns, 2139 // We think this is the same problem as about:downloads 2140 "about:preferences"_ns, "about:settings"_ns, 2141 // Browser console will give a filename of 'debugger' See 1763943 2142 // Sometimes it's 'debugger eager eval code', other times just 'debugger 2143 // eval code' 2144 "debugger"_ns}; 2145 2146 for (auto allowedFilenamePrefix : kAllowedFilenamesPrefix) { 2147 if (StringBeginsWith(filename, allowedFilenamePrefix)) { 2148 return true; 2149 } 2150 } 2151 2152 FilenameTypeAndDetails fileNameTypeAndDetails = 2153 FilenameToFilenameType(filename, true); 2154 glean::security::JavascriptLoadParentProcessExtra extra = { 2155 .fileinfo = fileNameTypeAndDetails.second, 2156 .value = Some(fileNameTypeAndDetails.first)}; 2157 2158 if (StaticPrefs::security_block_parent_unrestricted_js_loads_temporary()) { 2159 // Log to MOZ_LOG 2160 MOZ_LOG(sCSMLog, LogLevel::Error, 2161 ("ValidateScriptFilename Failed, But Blocking: %s\n", aFilename)); 2162 2163 extra.blocked = Some(true); 2164 glean::security::javascript_load_parent_process.Record(Some(extra)); 2165 2166 return false; 2167 } 2168 MOZ_LOG(sCSMLog, LogLevel::Error, 2169 ("ValidateScriptFilename Failed: %s\n", aFilename)); 2170 2171 glean::security::javascript_load_parent_process.Record(Some(extra)); 2172 2173 #if defined(DEBUG) || defined(FUZZING) 2174 auto crashString = nsContentSecurityUtils::SmartFormatCrashString( 2175 aFilename, 2176 fileNameTypeAndDetails.second.isSome() 2177 ? fileNameTypeAndDetails.second.value().get() 2178 : "(None)", 2179 "Blocking a script load %s from file %s"); 2180 MOZ_CRASH_UNSAFE_PRINTF("%s", crashString.get()); 2181 #elif defined(EARLY_BETA_OR_EARLIER) 2182 // Cause a crash (if we've never crashed before and we can ensure we won't do 2183 // it again.) 2184 // The details in the second arg, passed to UNSAFE_PRINTF, are also included 2185 // in Event Telemetry and have received data review. 2186 if (fileNameTypeAndDetails.second.isSome()) { 2187 PossiblyCrash("js_load_1", aFilename, 2188 fileNameTypeAndDetails.second.value()); 2189 } else { 2190 PossiblyCrash("js_load_1", aFilename, "(None)"_ns); 2191 } 2192 #endif 2193 2194 { 2195 StaticMutexAutoLock lock(gVeryFirstUnexpectedJavascriptLoadFilenameMutex); 2196 if (gVeryFirstUnexpectedJavascriptLoadFilename == nullptr) { 2197 gVeryFirstUnexpectedJavascriptLoadFilename = new nsCString(aFilename); 2198 } 2199 } 2200 2201 if (NS_IsMainThread()) { 2202 nsCOMPtr<nsIObserverService> observerService = 2203 mozilla::services::GetObserverService(); 2204 if (observerService) { 2205 observerService->NotifyObservers(nullptr, "UnexpectedJavaScriptLoad-Live", 2206 NS_ConvertUTF8toUTF16(filename).get()); 2207 } 2208 } else { 2209 NS_DispatchToMainThread( 2210 NS_NewRunnableFunction("NotifyObserversRunnable", [filename]() { 2211 nsCOMPtr<nsIObserverService> observerService = 2212 mozilla::services::GetObserverService(); 2213 if (observerService) { 2214 observerService->NotifyObservers( 2215 nullptr, "UnexpectedJavaScriptLoad-Live", 2216 NS_ConvertUTF8toUTF16(filename).get()); 2217 } 2218 })); 2219 } 2220 2221 // Presently we are only enforcing restrictions for the script filename 2222 // on Nightly. On all channels we are reporting Telemetry. In the future we 2223 // will assert in debug builds and return false to prevent execution in 2224 // non-debug builds. 2225 #ifdef NIGHTLY_BUILD 2226 return false; 2227 #else 2228 return true; 2229 #endif 2230 } 2231 2232 /* static */ 2233 void nsContentSecurityUtils::LogMessageToConsole(nsIHttpChannel* aChannel, 2234 const char* aMsg) { 2235 nsCOMPtr<nsIURI> uri; 2236 nsresult rv = aChannel->GetURI(getter_AddRefs(uri)); 2237 if (NS_FAILED(rv)) { 2238 return; 2239 } 2240 2241 uint64_t windowID = 0; 2242 rv = aChannel->GetTopLevelContentWindowId(&windowID); 2243 if (NS_WARN_IF(NS_FAILED(rv))) { 2244 return; 2245 } 2246 if (!windowID) { 2247 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); 2248 loadInfo->GetInnerWindowID(&windowID); 2249 } 2250 2251 nsAutoString localizedMsg; 2252 nsAutoCString spec; 2253 uri->GetSpec(spec); 2254 AutoTArray<nsString, 1> params = {NS_ConvertUTF8toUTF16(spec)}; 2255 rv = nsContentUtils::FormatLocalizedString( 2256 nsContentUtils::eSECURITY_PROPERTIES, aMsg, params, localizedMsg); 2257 if (NS_WARN_IF(NS_FAILED(rv))) { 2258 return; 2259 } 2260 2261 nsContentUtils::ReportToConsoleByWindowID( 2262 localizedMsg, nsIScriptError::warningFlag, "Security"_ns, windowID, 2263 SourceLocation{uri.get()}); 2264 } 2265 2266 /* static */ 2267 long nsContentSecurityUtils::ClassifyDownload(nsIChannel* aChannel) { 2268 MOZ_ASSERT(aChannel, "IsDownloadAllowed without channel?"); 2269 2270 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); 2271 if ((loadInfo->GetTriggeringSandboxFlags() & SANDBOXED_DOWNLOADS) || 2272 (loadInfo->GetSandboxFlags() & SANDBOXED_DOWNLOADS)) { 2273 if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel)) { 2274 LogMessageToConsole(httpChannel, "IframeSandboxBlockedDownload"); 2275 } 2276 return nsITransfer::DOWNLOAD_FORBIDDEN; 2277 } 2278 2279 nsCOMPtr<nsIURI> contentLocation; 2280 aChannel->GetURI(getter_AddRefs(contentLocation)); 2281 2282 nsCOMPtr<nsIPrincipal> loadingPrincipal = loadInfo->GetLoadingPrincipal(); 2283 if (!loadingPrincipal) { 2284 loadingPrincipal = loadInfo->TriggeringPrincipal(); 2285 } 2286 // Creating a fake Loadinfo that is just used for the MCB check. 2287 Result<RefPtr<net::LoadInfo>, nsresult> maybeLoadInfo = net::LoadInfo::Create( 2288 loadingPrincipal, loadInfo->TriggeringPrincipal(), nullptr, 2289 nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK, 2290 nsIContentPolicy::TYPE_FETCH); 2291 if (maybeLoadInfo.isErr()) { 2292 return nsITransfer::DOWNLOAD_FORBIDDEN; 2293 } 2294 RefPtr<net::LoadInfo> secCheckLoadInfo = maybeLoadInfo.unwrap(); 2295 // Disable HTTPS-Only checks for that loadinfo. This is required because 2296 // otherwise nsMixedContentBlocker::ShouldLoad would assume that the request 2297 // is safe, because HTTPS-Only is handling it. 2298 secCheckLoadInfo->SetHttpsOnlyStatus(nsILoadInfo::HTTPS_ONLY_EXEMPT); 2299 2300 int16_t decission = nsIContentPolicy::ACCEPT; 2301 nsMixedContentBlocker::ShouldLoad(false, // aHadInsecureImageRedirect 2302 contentLocation, // aContentLocation, 2303 secCheckLoadInfo, // aLoadinfo 2304 false, // aReportError 2305 &decission // aDecision 2306 ); 2307 2308 if (StaticPrefs::dom_block_download_insecure() && 2309 decission != nsIContentPolicy::ACCEPT) { 2310 if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel)) { 2311 LogMessageToConsole(httpChannel, "MixedContentBlockedDownload"); 2312 } 2313 return nsITransfer::DOWNLOAD_POTENTIALLY_UNSAFE; 2314 } 2315 2316 return nsITransfer::DOWNLOAD_ACCEPTABLE; 2317 }