ProcessIsolation.cpp (51257B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set sw=2 ts=8 et 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 "mozilla/dom/ProcessIsolation.h" 8 9 #include "mozilla/AppShutdown.h" 10 #include "mozilla/Assertions.h" 11 #include "mozilla/BasePrincipal.h" 12 #include "mozilla/ClearOnShutdown.h" 13 #include "mozilla/ContentPrincipal.h" 14 #include "mozilla/ExtensionPolicyService.h" 15 #include "mozilla/Logging.h" 16 #include "mozilla/NullPrincipal.h" 17 #include "mozilla/PermissionManager.h" 18 #include "mozilla/Preferences.h" 19 #include "mozilla/RefPtr.h" 20 #include "mozilla/StaticPrefs_browser.h" 21 #include "mozilla/StaticPrefs_fission.h" 22 #include "mozilla/StaticPtr.h" 23 #include "mozilla/dom/BrowsingContextGroup.h" 24 #include "mozilla/dom/CanonicalBrowsingContext.h" 25 #include "mozilla/dom/ContentChild.h" 26 #include "mozilla/dom/ContentParent.h" 27 #include "mozilla/dom/Element.h" 28 #include "mozilla/dom/RemoteType.h" 29 #include "mozilla/dom/WindowGlobalParent.h" 30 #include "mozilla/extensions/WebExtensionPolicy.h" 31 #include "nsAboutProtocolUtils.h" 32 #include "nsDocShell.h" 33 #include "nsError.h" 34 #include "nsIChromeRegistry.h" 35 #include "nsIHttpChannel.h" 36 #include "nsIHttpChannelInternal.h" 37 #include "nsIProtocolHandler.h" 38 #include "nsIXULRuntime.h" 39 #include "nsNetUtil.h" 40 #include "nsSHistory.h" 41 #include "nsServiceManagerUtils.h" 42 #include "nsURLHelper.h" 43 44 namespace mozilla::dom { 45 46 mozilla::LazyLogModule gProcessIsolationLog{"ProcessIsolation"}; 47 48 namespace { 49 50 // Strategy used to determine whether or not a particular site should load into 51 // a webIsolated content process. The particular strategy chosen is controlled 52 // by the `fission.webContentIsolationStrategy` pref, which must hold one of the 53 // following values. 54 enum class WebContentIsolationStrategy : uint32_t { 55 // All web content is loaded into a shared `web` content process. This is 56 // similar to the non-Fission behaviour, however remote subframes may still 57 // be used for sites with special isolation behaviour, such as extension or 58 // mozillaweb content processes. 59 IsolateNothing = 0, 60 // Web content is always isolated into its own `webIsolated` content process 61 // based on site-origin, and will only load in a shared `web` content process 62 // if site-origin could not be determined. 63 IsolateEverything = 1, 64 // Only isolates web content loaded by sites which are considered "high 65 // value". A site is considered "high value" if it has been granted a 66 // `highValue*` permission by the permission manager, which is done in 67 // response to certain actions. 68 IsolateHighValue = 2, 69 }; 70 71 /** 72 * Helper class for caching the result of splitting prefs which are represented 73 * as a comma-separated list of strings. 74 */ 75 struct CommaSeparatedPref { 76 public: 77 explicit constexpr CommaSeparatedPref(nsLiteralCString aPrefName) 78 : mPrefName(aPrefName) {} 79 80 void OnChange() { 81 if (mValues) { 82 mValues->Clear(); 83 nsAutoCString prefValue; 84 if (NS_SUCCEEDED(Preferences::GetCString(mPrefName.get(), prefValue))) { 85 for (const auto& value : 86 nsCCharSeparatedTokenizer(prefValue, ',').ToRange()) { 87 mValues->EmplaceBack(value); 88 } 89 } 90 } 91 } 92 93 const nsTArray<nsCString>& Get() { 94 if (!mValues) { 95 mValues = new nsTArray<nsCString>; 96 Preferences::RegisterCallbackAndCall( 97 [](const char*, void* aData) { 98 static_cast<CommaSeparatedPref*>(aData)->OnChange(); 99 }, 100 mPrefName, this); 101 RunOnShutdown([this] { 102 delete this->mValues; 103 this->mValues = nullptr; 104 }); 105 } 106 return *mValues; 107 } 108 109 auto begin() { return Get().cbegin(); } 110 auto end() { return Get().cend(); } 111 112 private: 113 nsLiteralCString mPrefName; 114 nsTArray<nsCString>* MOZ_OWNING_REF mValues = nullptr; 115 }; 116 117 CommaSeparatedPref sSeparatedMozillaDomains{ 118 "browser.tabs.remote.separatedMozillaDomains"_ns}; 119 120 /** 121 * Certain URIs have special isolation behaviour, and need to be loaded within 122 * specific process types. 123 */ 124 enum class IsolationBehavior { 125 // This URI loads web content and should be treated as a content load, being 126 // isolated based on the response principal if enabled. 127 WebContent, 128 // Forcibly load in a process with the "web" remote type. This will ignore the 129 // response principal completely. 130 // This is generally reserved for internal documents which are loaded in 131 // content, but not in the privilegedabout content process. 132 ForceWebRemoteType, 133 // Load this URI in the privileged about content process. 134 PrivilegedAbout, 135 // Load this URI in the extension process. 136 Extension, 137 // Load this URI in the file content process. 138 File, 139 // Load this URI in the priviliged mozilla content process. 140 PrivilegedMozilla, 141 // Load this URI explicitly in the parent process. 142 Parent, 143 // Load this URI wherever the browsing context is currently loaded. This is 144 // generally used for error pages. 145 Anywhere, 146 // May only be returned for subframes. Inherits the remote type of the parent 147 // document which is embedding this document. 148 Inherit, 149 // Special case for the `about:reader` URI which should be loaded in the same 150 // process which would be used for the "url" query parameter. 151 AboutReader, 152 // There was a fatal error, and the load should be aborted. 153 Error, 154 }; 155 156 /** 157 * Returns a static string with the name of the given isolation behaviour. For 158 * use in logging code. 159 */ 160 static const char* IsolationBehaviorName(IsolationBehavior aBehavior) { 161 switch (aBehavior) { 162 case IsolationBehavior::WebContent: 163 return "WebContent"; 164 case IsolationBehavior::ForceWebRemoteType: 165 return "ForceWebRemoteType"; 166 case IsolationBehavior::PrivilegedAbout: 167 return "PrivilegedAbout"; 168 case IsolationBehavior::Extension: 169 return "Extension"; 170 case IsolationBehavior::File: 171 return "File"; 172 case IsolationBehavior::PrivilegedMozilla: 173 return "PrivilegedMozilla"; 174 case IsolationBehavior::Parent: 175 return "Parent"; 176 case IsolationBehavior::Anywhere: 177 return "Anywhere"; 178 case IsolationBehavior::Inherit: 179 return "Inherit"; 180 case IsolationBehavior::AboutReader: 181 return "AboutReader"; 182 case IsolationBehavior::Error: 183 return "Error"; 184 default: 185 return "Unknown"; 186 } 187 } 188 189 /** 190 * Returns a static string with the name of the given worker kind. For use in 191 * logging code. 192 */ 193 static const char* WorkerKindName(WorkerKind aWorkerKind) { 194 switch (aWorkerKind) { 195 case WorkerKindDedicated: 196 return "Dedicated"; 197 case WorkerKindShared: 198 return "Shared"; 199 case WorkerKindService: 200 return "Service"; 201 default: 202 return "Unknown"; 203 } 204 } 205 206 /** 207 * Check if a given URI has specialized process isolation behaviour, such as 208 * needing to be loaded within a specific type of content process. 209 * 210 * When handling a navigation, this method will be called twice: first with the 211 * channel's creation URI, and then it will be called with a result principal's 212 * URI. 213 */ 214 static IsolationBehavior IsolationBehaviorForURI(nsIURI* aURI, bool aIsSubframe, 215 bool aForChannelCreationURI) { 216 nsAutoCString scheme; 217 MOZ_ALWAYS_SUCCEEDS(aURI->GetScheme(scheme)); 218 219 if (scheme == "chrome"_ns) { 220 // `chrome://` URIs are always loaded in the parent process, unless they 221 // have opted in to loading in a content process. This is currently only 222 // done in tests. 223 // 224 // FIXME: These flags should be removed from `chrome` URIs at some point. 225 nsCOMPtr<nsIXULChromeRegistry> chromeReg = 226 do_GetService("@mozilla.org/chrome/chrome-registry;1"); 227 bool mustLoadRemotely = false; 228 if (NS_SUCCEEDED(chromeReg->MustLoadURLRemotely(aURI, &mustLoadRemotely)) && 229 mustLoadRemotely) { 230 return IsolationBehavior::ForceWebRemoteType; 231 } 232 bool canLoadRemotely = false; 233 if (NS_SUCCEEDED(chromeReg->CanLoadURLRemotely(aURI, &canLoadRemotely)) && 234 canLoadRemotely) { 235 return IsolationBehavior::Anywhere; 236 } 237 return IsolationBehavior::Parent; 238 } 239 240 if (scheme == "about"_ns) { 241 nsAutoCString path; 242 MOZ_ALWAYS_SUCCEEDS(NS_GetAboutModuleName(aURI, path)); 243 244 // The `about:blank` and `about:srcdoc` pages are loaded by normal web 245 // content, and should be allocated processes based on their simple content 246 // principals. 247 if (path == "blank"_ns || path == "srcdoc"_ns) { 248 MOZ_ASSERT(NS_IsContentAccessibleAboutURI(aURI)); 249 return IsolationBehavior::WebContent; 250 } 251 252 MOZ_ASSERT(!NS_IsContentAccessibleAboutURI(aURI)); 253 // If we're loading an `about:reader` URI, perform isolation based on the 254 // principal of the URI being loaded. 255 if (path == "reader"_ns && aForChannelCreationURI) { 256 return IsolationBehavior::AboutReader; 257 } 258 259 // Otherwise, we're going to be loading an about: page. Consult the module. 260 nsCOMPtr<nsIAboutModule> aboutModule; 261 if (NS_FAILED(NS_GetAboutModule(aURI, getter_AddRefs(aboutModule))) || 262 !aboutModule) { 263 // If we don't know of an about: module for this load, it's going to end 264 // up being a network error. Allow the load to finish as normal. 265 return IsolationBehavior::WebContent; 266 } 267 268 // NOTE: about modules can be implemented in JS, so this may run script, and 269 // therefore can spuriously fail. 270 uint32_t flags = 0; 271 if (NS_FAILED(aboutModule->GetURIFlags(aURI, &flags))) { 272 NS_WARNING( 273 "nsIAboutModule::GetURIFlags unexpectedly failed. Abort the load"); 274 return IsolationBehavior::Error; 275 } 276 277 if (flags & nsIAboutModule::URI_MUST_LOAD_IN_EXTENSION_PROCESS) { 278 return IsolationBehavior::Extension; 279 } 280 281 if (flags & nsIAboutModule::URI_MUST_LOAD_IN_CHILD) { 282 if (flags & nsIAboutModule::URI_CAN_LOAD_IN_PRIVILEGEDABOUT_PROCESS) { 283 return IsolationBehavior::PrivilegedAbout; 284 } 285 return IsolationBehavior::ForceWebRemoteType; 286 } 287 288 if (flags & nsIAboutModule::URI_CAN_LOAD_IN_CHILD) { 289 return IsolationBehavior::Anywhere; 290 } 291 292 return IsolationBehavior::Parent; 293 } 294 295 // If the test-only `dataUriInDefaultWebProcess` pref is enabled, dump all 296 // `data:` URIs in a "web" content process, rather than loading them in 297 // content processes based on their precursor origins. 298 if (StaticPrefs::browser_tabs_remote_dataUriInDefaultWebProcess() && 299 scheme == "data"_ns) { 300 return IsolationBehavior::ForceWebRemoteType; 301 } 302 303 // Make sure to unwrap nested URIs before we early return for channel creation 304 // URI. The checks past this point are intended to operate on the principal, 305 // which has it's origin constructed from the innermost URI. 306 nsCOMPtr<nsIURI> inner; 307 if (nsCOMPtr<nsINestedURI> nested = do_QueryInterface(aURI); 308 nested && NS_SUCCEEDED(nested->GetInnerURI(getter_AddRefs(inner)))) { 309 return IsolationBehaviorForURI(inner, aIsSubframe, aForChannelCreationURI); 310 } 311 312 // If we're doing the initial check based on the channel creation URI, stop 313 // here as we want to only perform the following checks on the true channel 314 // result principal. 315 if (aForChannelCreationURI) { 316 return IsolationBehavior::WebContent; 317 } 318 319 // Protocols used by Thunderbird to display email messages. 320 if (scheme == "imap"_ns || scheme == "mailbox"_ns || scheme == "news"_ns || 321 scheme == "nntp"_ns || scheme == "snews"_ns || scheme == "x-moz-ews"_ns) { 322 return IsolationBehavior::Parent; 323 } 324 325 // There is more handling for extension content processes in the caller, but 326 // they should load in an extension content process unless we're loading a 327 // subframe. 328 if (scheme == "moz-extension"_ns) { 329 if (aIsSubframe) { 330 // As a temporary measure, extension iframes must be loaded within the 331 // same process as their parent document. 332 return IsolationBehavior::Inherit; 333 } 334 return IsolationBehavior::Extension; 335 } 336 337 if (scheme == "file"_ns) { 338 return IsolationBehavior::File; 339 } 340 341 // Check if the URI is listed as a privileged mozilla content process. 342 if (scheme == "https"_ns && 343 StaticPrefs:: 344 browser_tabs_remote_separatePrivilegedMozillaWebContentProcess()) { 345 nsAutoCString host; 346 if (NS_SUCCEEDED(aURI->GetAsciiHost(host))) { 347 // This code is duplicated in E10SUtils.sys.mjs, please update both 348 for (const auto& separatedDomain : sSeparatedMozillaDomains) { 349 // If the domain exactly matches our host, or our host ends with "." + 350 // separatedDomain, we consider it matching. 351 if (separatedDomain == host || 352 (separatedDomain.Length() < host.Length() && 353 host.CharAt(host.Length() - separatedDomain.Length() - 1) == '.' && 354 StringEndsWith(host, separatedDomain))) { 355 return IsolationBehavior::PrivilegedMozilla; 356 } 357 } 358 } 359 } 360 361 nsCOMPtr<nsIScriptSecurityManager> secMan = 362 nsContentUtils::GetSecurityManager(); 363 bool inFileURIAllowList = false; 364 if (NS_SUCCEEDED(secMan->InFileURIAllowlist(aURI, &inFileURIAllowList)) && 365 inFileURIAllowList) { 366 return IsolationBehavior::File; 367 } 368 369 return IsolationBehavior::WebContent; 370 } 371 372 /** 373 * Helper method for logging the origin of a principal as a string. 374 */ 375 static nsAutoCString OriginString(nsIPrincipal* aPrincipal) { 376 nsAutoCString origin; 377 aPrincipal->GetOrigin(origin); 378 return origin; 379 } 380 381 /** 382 * Trim the OriginAttributes from aPrincipal, and use it to create a 383 * OriginSuffix string appropriate to use within a remoteType string. 384 */ 385 static nsAutoCString OriginSuffixForRemoteType(nsIPrincipal* aPrincipal) { 386 nsAutoCString originSuffix; 387 OriginAttributes attrs = aPrincipal->OriginAttributesRef(); 388 attrs.StripAttributes(OriginAttributes::STRIP_FIRST_PARTY_DOMAIN | 389 OriginAttributes::STRIP_PARITION_KEY); 390 attrs.CreateSuffix(originSuffix); 391 return originSuffix; 392 } 393 394 /** 395 * Given an about:reader URI, extract the "url" query parameter, and use it to 396 * construct a principal which should be used for process selection. 397 */ 398 static already_AddRefed<BasePrincipal> GetAboutReaderURLPrincipal( 399 nsIURI* aURI, const OriginAttributes& aAttrs) { 400 #ifdef DEBUG 401 MOZ_ASSERT(aURI->SchemeIs("about")); 402 nsAutoCString path; 403 MOZ_ALWAYS_SUCCEEDS(NS_GetAboutModuleName(aURI, path)); 404 MOZ_ASSERT(path == "reader"_ns); 405 #endif 406 407 nsAutoCString query; 408 MOZ_ALWAYS_SUCCEEDS(aURI->GetQuery(query)); 409 410 // Extract the "url" parameter from the `about:reader`'s query parameters, 411 // and recover a content principal from it. 412 nsAutoCString readerSpec; 413 if (URLParams::Extract(query, "url"_ns, readerSpec)) { 414 nsCOMPtr<nsIURI> readerUri; 415 if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(readerUri), readerSpec))) { 416 return BasePrincipal::CreateContentPrincipal(readerUri, aAttrs); 417 } 418 } 419 return nullptr; 420 } 421 422 /** 423 * Check the Cross-Origin-Opener-Policy of the given channel or ancestor 424 * BrowsingContext, checking if the response should be cross-origin isolated. 425 */ 426 static bool ShouldCrossOriginIsolate(nsIChannel* aChannel, 427 WindowGlobalParent* aParentWindow) { 428 nsILoadInfo::CrossOriginOpenerPolicy coop = 429 nsILoadInfo::OPENER_POLICY_UNSAFE_NONE; 430 if (aParentWindow) { 431 coop = aParentWindow->BrowsingContext()->Top()->GetOpenerPolicy(); 432 } else if (nsCOMPtr<nsIHttpChannelInternal> httpChannel = 433 do_QueryInterface(aChannel)) { 434 MOZ_ALWAYS_SUCCEEDS(httpChannel->GetCrossOriginOpenerPolicy(&coop)); 435 } 436 return coop == 437 nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP; 438 } 439 440 /** 441 * Returns `true` if loads for this site should be isolated on a per-site basis. 442 * If `aTopBC` is nullptr, this is being called to check if a shared or service 443 * worker should be isolated. 444 */ 445 static bool ShouldIsolateSite(nsIPrincipal* aPrincipal, 446 bool aUseRemoteSubframes) { 447 // If Fission is disabled, we never want to isolate. We check the toplevel BC 448 // if it's available, or the global pref if checking for shared or service 449 // workers. 450 if (!aUseRemoteSubframes) { 451 return false; 452 } 453 454 // non-content principals currently can't have webIsolated remote types 455 // assigned to them, so should not be isolated. 456 if (!aPrincipal->GetIsContentPrincipal()) { 457 return false; 458 } 459 460 switch (WebContentIsolationStrategy( 461 StaticPrefs::fission_webContentIsolationStrategy())) { 462 case WebContentIsolationStrategy::IsolateNothing: 463 MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, 464 ("Not isolating '%s' as isolation is disabled", 465 OriginString(aPrincipal).get())); 466 return false; 467 case WebContentIsolationStrategy::IsolateEverything: 468 MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, 469 ("Isolating '%s' as isolation is enabled for all sites", 470 OriginString(aPrincipal).get())); 471 return true; 472 case WebContentIsolationStrategy::IsolateHighValue: { 473 RefPtr<PermissionManager> perms = PermissionManager::GetInstance(); 474 if (NS_WARN_IF(!perms)) { 475 // If we somehow have no permission manager, fall back to the safest 476 // option, and try to isolate. 477 MOZ_ASSERT_UNREACHABLE("Permission manager is missing"); 478 return true; 479 } 480 481 static constexpr nsLiteralCString kHighValuePermissions[] = { 482 mozilla::dom::kHighValueCOOPPermission, 483 mozilla::dom::kHighValueHasSavedLoginPermission, 484 mozilla::dom::kHighValueIsLoggedInPermission, 485 }; 486 487 for (const auto& type : kHighValuePermissions) { 488 uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION; 489 if (NS_SUCCEEDED(perms->TestPermissionFromPrincipal(aPrincipal, type, 490 &permission)) && 491 permission == nsIPermissionManager::ALLOW_ACTION) { 492 MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, 493 ("Isolating '%s' due to high-value permission '%s'", 494 OriginString(aPrincipal).get(), type.get())); 495 return true; 496 } 497 } 498 MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, 499 ("Not isolating '%s' as it is not high-value", 500 OriginString(aPrincipal).get())); 501 return false; 502 } 503 default: 504 // An invalid pref value was used. Fall back to the safest option and 505 // isolate everything. 506 NS_WARNING("Invalid pref value for fission.webContentIsolationStrategy"); 507 MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, 508 ("Isolating '%s' due to unknown strategy pref value", 509 OriginString(aPrincipal).get())); 510 return true; 511 } 512 } 513 514 static Result<nsCString, nsresult> SpecialBehaviorRemoteType( 515 IsolationBehavior aBehavior, const nsACString& aCurrentRemoteType, 516 WindowGlobalParent* aParentWindow) { 517 switch (aBehavior) { 518 case IsolationBehavior::ForceWebRemoteType: 519 return {WEB_REMOTE_TYPE}; 520 case IsolationBehavior::PrivilegedAbout: 521 // The privileged about: content process cannot be disabled, as it 522 // causes various actors to break. 523 return {PRIVILEGEDABOUT_REMOTE_TYPE}; 524 case IsolationBehavior::Extension: 525 if (ExtensionPolicyService::GetSingleton().UseRemoteExtensions()) { 526 return {EXTENSION_REMOTE_TYPE}; 527 } 528 return {NOT_REMOTE_TYPE}; 529 case IsolationBehavior::File: 530 if (StaticPrefs::browser_tabs_remote_separateFileUriProcess()) { 531 return {FILE_REMOTE_TYPE}; 532 } 533 return {WEB_REMOTE_TYPE}; 534 case IsolationBehavior::PrivilegedMozilla: 535 return {PRIVILEGEDMOZILLA_REMOTE_TYPE}; 536 case IsolationBehavior::Parent: 537 return {NOT_REMOTE_TYPE}; 538 case IsolationBehavior::Anywhere: 539 return {nsCString(aCurrentRemoteType)}; 540 case IsolationBehavior::Inherit: 541 MOZ_DIAGNOSTIC_ASSERT(aParentWindow); 542 return {nsCString(aParentWindow->GetRemoteType())}; 543 544 case IsolationBehavior::Error: 545 return Err(NS_ERROR_UNEXPECTED); 546 547 default: 548 MOZ_ASSERT_UNREACHABLE(); 549 return Err(NS_ERROR_UNEXPECTED); 550 } 551 } 552 553 enum class WebProcessType { 554 Web, 555 WebIsolated, 556 WebCoopCoep, 557 }; 558 559 } // namespace 560 561 Result<NavigationIsolationOptions, nsresult> IsolationOptionsForNavigation( 562 CanonicalBrowsingContext* aTopBC, WindowGlobalParent* aParentWindow, 563 nsIURI* aChannelCreationURI, nsIChannel* aChannel, 564 const nsACString& aCurrentRemoteType, bool aHasCOOPMismatch, 565 bool aForNewTab, uint32_t aLoadStateLoadType, 566 const Maybe<uint64_t>& aChannelId, 567 const Maybe<nsCString>& aRemoteTypeOverride) { 568 // Get the final principal, used to select which process to load into. 569 nsCOMPtr<nsIPrincipal> resultPrincipal; 570 nsresult rv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal( 571 aChannel, getter_AddRefs(resultPrincipal)); 572 if (NS_FAILED(rv)) { 573 MOZ_LOG(gProcessIsolationLog, LogLevel::Error, 574 ("failed to get channel result principal")); 575 return Err(rv); 576 } 577 578 MOZ_LOG( 579 gProcessIsolationLog, LogLevel::Verbose, 580 ("IsolationOptionsForNavigation principal:%s, uri:%s, parentUri:%s", 581 OriginString(resultPrincipal).get(), 582 aChannelCreationURI->GetSpecOrDefault().get(), 583 aParentWindow ? aParentWindow->GetDocumentURI()->GetSpecOrDefault().get() 584 : "")); 585 586 // If we're loading a null principal, we can't easily make a process 587 // selection decision off ot it. Instead, we'll use our null principal's 588 // precursor principal to make process selection decisions. 589 bool isNullPrincipalPrecursor = false; 590 nsCOMPtr<nsIPrincipal> resultOrPrecursor(resultPrincipal); 591 if (nsCOMPtr<nsIPrincipal> precursor = 592 resultOrPrecursor->GetPrecursorPrincipal()) { 593 MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, 594 ("using null principal precursor origin %s", 595 OriginString(precursor).get())); 596 resultOrPrecursor = precursor; 597 isNullPrincipalPrecursor = true; 598 } 599 600 NavigationIsolationOptions options; 601 options.mReplaceBrowsingContext = aHasCOOPMismatch; 602 options.mShouldCrossOriginIsolate = 603 ShouldCrossOriginIsolate(aChannel, aParentWindow); 604 605 // Check if this load has an explicit remote type override. This is used to 606 // perform an about:blank load within a specific content process. 607 if (aRemoteTypeOverride) { 608 MOZ_DIAGNOSTIC_ASSERT( 609 NS_IsAboutBlank(aChannelCreationURI), 610 "Should only have aRemoteTypeOverride for about:blank URIs"); 611 if (NS_WARN_IF(!resultPrincipal->GetIsNullPrincipal())) { 612 MOZ_LOG(gProcessIsolationLog, LogLevel::Error, 613 ("invalid remote type override on non-null principal")); 614 return Err(NS_ERROR_DOM_SECURITY_ERR); 615 } 616 617 MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, 618 ("using remote type override (%s) for load", 619 aRemoteTypeOverride->get())); 620 options.mRemoteType = *aRemoteTypeOverride; 621 return options; 622 } 623 624 // First, check for any special cases which should be handled using the 625 // channel creation URI, and handle them. 626 auto behavior = IsolationBehaviorForURI(aChannelCreationURI, aParentWindow, 627 /* aForChannelCreationURI */ true); 628 MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, 629 ("Channel Creation Isolation Behavior: %s", 630 IsolationBehaviorName(behavior))); 631 632 // In the about:reader special case, we want to fetch the relevant information 633 // from the URI, an then treat it as a normal web content load. 634 if (behavior == IsolationBehavior::AboutReader) { 635 if (RefPtr<BasePrincipal> readerURIPrincipal = GetAboutReaderURLPrincipal( 636 aChannelCreationURI, resultOrPrecursor->OriginAttributesRef())) { 637 MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, 638 ("using about:reader's url origin %s", 639 OriginString(readerURIPrincipal).get())); 640 resultOrPrecursor = readerURIPrincipal; 641 } 642 behavior = IsolationBehavior::WebContent; 643 // If loading an about:reader page in a BrowsingContext which shares a 644 // BrowsingContextGroup with other toplevel documents, replace the 645 // BrowsingContext to destroy any references. 646 // 647 // With SHIP we can apply this to all about:reader loads, but otherwise 648 // do it at least where there are opener/group relationships. 649 if (mozilla::SessionHistoryInParent() || 650 aTopBC->Group()->Toplevels().Length() > 1) { 651 options.mReplaceBrowsingContext = true; 652 } 653 } 654 655 // If we're running in a test which is requesting that system-triggered 656 // about:blank documents load within the current process, override the 657 // behaviour for loads which meet the requirements. 658 if (StaticPrefs::browser_tabs_remote_systemTriggeredAboutBlankAnywhere() && 659 NS_IsAboutBlank(aChannelCreationURI)) { 660 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); 661 if (loadInfo->TriggeringPrincipal()->IsSystemPrincipal() && 662 resultOrPrecursor->GetIsNullPrincipal()) { 663 MOZ_LOG(gProcessIsolationLog, LogLevel::Warning, 664 ("Forcing system-principal triggered about:blank load to " 665 "complete in the current process")); 666 behavior = IsolationBehavior::Anywhere; 667 } 668 } 669 670 #ifdef MOZ_WIDGET_ANDROID 671 // If we're loading an error page on android, it must complete within the same 672 // process as the errored page load would complete in due to code expecting 673 // that behavior. See bug 1673763. 674 if (aLoadStateLoadType == LOAD_ERROR_PAGE) { 675 MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, 676 ("Forcing error page load to complete in the current process")); 677 behavior = IsolationBehavior::Anywhere; 678 } 679 #endif 680 681 // If we're loading for a specific extension, we'll need to perform a 682 // BCG-switching load to get our toplevel extension window in the correct 683 // BrowsingContextGroup. 684 if (auto* addonPolicy = 685 BasePrincipal::Cast(resultOrPrecursor)->AddonPolicy()) { 686 if (aParentWindow) { 687 // As a temporary measure, extension iframes must be loaded within the 688 // same process as their parent document. 689 MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, 690 ("Loading extension subframe in same process as parent")); 691 behavior = IsolationBehavior::Inherit; 692 } else { 693 MOZ_LOG( 694 gProcessIsolationLog, LogLevel::Verbose, 695 ("Found extension frame with addon policy. Will use group id %" PRIx64 696 " (currentId: %" PRIx64 ")", 697 addonPolicy->GetBrowsingContextGroupId(), aTopBC->Group()->Id())); 698 behavior = IsolationBehavior::Extension; 699 if (aTopBC->Group()->Id() != addonPolicy->GetBrowsingContextGroupId()) { 700 options.mReplaceBrowsingContext = true; 701 options.mSpecificGroupId = addonPolicy->GetBrowsingContextGroupId(); 702 } 703 } 704 } 705 706 // Do a second run of `GetIsolationBehavior`, this time using the 707 // principal's URI to handle additional special cases such as the file and 708 // privilegedmozilla content process. 709 if (behavior == IsolationBehavior::WebContent) { 710 if (resultOrPrecursor->IsSystemPrincipal()) { 711 // We're loading something with a system principal which isn't caught in 712 // one of our other edge-cases. If the load started in the parent process, 713 // and it's safe for it to end in the parent process, we should finish the 714 // load there. 715 bool isUIResource = false; 716 if (aCurrentRemoteType.IsEmpty() && 717 (aChannelCreationURI->SchemeIs("about") || 718 (NS_SUCCEEDED(NS_URIChainHasFlags( 719 aChannelCreationURI, nsIProtocolHandler::URI_IS_UI_RESOURCE, 720 &isUIResource)) && 721 isUIResource))) { 722 behavior = IsolationBehavior::Parent; 723 } else { 724 // In general, we don't want to load documents with a system principal 725 // in a content process, however we need to in some cases, such as when 726 // loading blob: URLs created by system code. We can force the load to 727 // finish in a content process instead. 728 behavior = IsolationBehavior::ForceWebRemoteType; 729 } 730 } else if (nsCOMPtr<nsIURI> principalURI = resultOrPrecursor->GetURI()) { 731 behavior = IsolationBehaviorForURI(principalURI, aParentWindow, 732 /* aForChannelCreationURI */ false); 733 } 734 } 735 736 // If we're currently loaded in the extension process, and are going to switch 737 // to some other remote type, make sure we leave the extension's BCG which we 738 // may have entered earlier to separate extension and non-extension BCGs from 739 // each-other. 740 if (!aParentWindow && aCurrentRemoteType == EXTENSION_REMOTE_TYPE && 741 behavior != IsolationBehavior::Extension && 742 behavior != IsolationBehavior::Anywhere) { 743 MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, 744 ("Forcing BC replacement to leave extension BrowsingContextGroup " 745 "%" PRIx64 " on navigation", 746 aTopBC->Group()->Id())); 747 options.mReplaceBrowsingContext = true; 748 } 749 750 // We don't want to load documents with sandboxed null principals, like 751 // `data:` URIs, in the parent process, even if they were created by a 752 // document which would otherwise be loaded in the parent process. 753 if (behavior == IsolationBehavior::Parent && isNullPrincipalPrecursor) { 754 MOZ_LOG(gProcessIsolationLog, LogLevel::Debug, 755 ("Ensuring sandboxed null-principal load doesn't occur in the " 756 "parent process")); 757 behavior = IsolationBehavior::ForceWebRemoteType; 758 } 759 760 MOZ_LOG( 761 gProcessIsolationLog, LogLevel::Debug, 762 ("Using IsolationBehavior %s for %s (original uri %s)", 763 IsolationBehaviorName(behavior), OriginString(resultOrPrecursor).get(), 764 aChannelCreationURI->GetSpecOrDefault().get())); 765 766 // Check if we can put the previous document into the BFCache. 767 if (mozilla::BFCacheInParent() && nsSHistory::GetMaxTotalViewers() > 0 && 768 !aForNewTab && !aParentWindow && !aTopBC->HadOriginalOpener() && 769 behavior != IsolationBehavior::Parent && 770 (ExtensionPolicyService::GetSingleton().UseRemoteExtensions() || 771 behavior != IsolationBehavior::Extension) && 772 !aCurrentRemoteType.IsEmpty() && 773 aTopBC->GetHasLoadedNonInitialDocument() && 774 (aLoadStateLoadType == LOAD_NORMAL || 775 aLoadStateLoadType == LOAD_HISTORY || aLoadStateLoadType == LOAD_LINK || 776 aLoadStateLoadType == LOAD_STOP_CONTENT || 777 aLoadStateLoadType == LOAD_STOP_CONTENT_AND_REPLACE) && 778 (!aTopBC->GetActiveSessionHistoryEntry() || 779 aTopBC->GetActiveSessionHistoryEntry()->GetSaveLayoutStateFlag())) { 780 if (nsCOMPtr<nsIURI> uri = aTopBC->GetCurrentURI()) { 781 MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, 782 ("current uri: %s", uri->GetSpecOrDefault().get())); 783 } 784 options.mTryUseBFCache = 785 aTopBC->AllowedInBFCache(aChannelId, aChannelCreationURI); 786 if (options.mTryUseBFCache) { 787 options.mReplaceBrowsingContext = true; 788 options.mActiveSessionHistoryEntry = 789 aTopBC->GetActiveSessionHistoryEntry(); 790 } 791 } 792 793 // If the load has any special remote type handling, do so at this point. 794 if (behavior != IsolationBehavior::WebContent) { 795 options.mRemoteType = MOZ_TRY( 796 SpecialBehaviorRemoteType(behavior, aCurrentRemoteType, aParentWindow)); 797 798 if (options.mRemoteType != aCurrentRemoteType && 799 (options.mRemoteType.IsEmpty() || aCurrentRemoteType.IsEmpty())) { 800 options.mReplaceBrowsingContext = true; 801 } 802 803 MOZ_LOG( 804 gProcessIsolationLog, LogLevel::Debug, 805 ("Selecting specific remote type (%s) due to a special case isolation " 806 "behavior %s", 807 options.mRemoteType.get(), IsolationBehaviorName(behavior))); 808 return options; 809 } 810 811 // At this point we're definitely not going to be loading in the parent 812 // process anymore, so we're definitely going to be replacing BrowsingContext 813 // if we're in the parent process. 814 if (aCurrentRemoteType.IsEmpty()) { 815 MOZ_ASSERT(!aParentWindow); 816 options.mReplaceBrowsingContext = true; 817 } 818 819 // NOTE: Currently we always perform process isolation based on the 820 // siteOrigin, not based on the full origin, even if the 821 // `Origin-Agent-Cluster` header is provided and we are keying DocGroups 822 // by-origin. 823 // 824 // If in the future we want to start keying based on full origin in some 825 // cases, the logic below will need to be updated to handle this. Note that 826 // the UseOriginAgentCluster bit may not have been set on the 827 // BrowsingContextGroup when this check is being evaluated (as it is set after 828 // process selection, which may cause a BrowsingContextGroup switch). 829 830 nsAutoCString siteOriginNoSuffix; 831 MOZ_TRY(resultOrPrecursor->GetSiteOriginNoSuffix(siteOriginNoSuffix)); 832 833 // Check if we've already loaded a document with the given principal in some 834 // content process. We want to finish the load in the same process in that 835 // case. 836 // 837 // The exception to that is with extension loads and the system principal, 838 // where we may have multiple documents with the same principal in different 839 // processes. Those have been handled above, and will not be reaching here. 840 // 841 // If we're doing a replace load or opening a new tab, we won't be staying in 842 // the same BrowsingContextGroup, so ignore this step. 843 if (!options.mReplaceBrowsingContext && !aForNewTab) { 844 // Helper for efficiently determining if a given origin is same-site. This 845 // will attempt to do a fast equality check, and will only fall back to 846 // computing the site-origin for content principals. 847 auto principalIsSameSite = [&](nsIPrincipal* aDocumentPrincipal) -> bool { 848 // If we're working with a null principal with a precursor, compare 849 // precursors, as `resultOrPrecursor` has already been stripped to its 850 // precursor. 851 nsCOMPtr<nsIPrincipal> documentPrincipal(aDocumentPrincipal); 852 if (nsCOMPtr<nsIPrincipal> precursor = 853 documentPrincipal->GetPrecursorPrincipal()) { 854 documentPrincipal = precursor; 855 } 856 857 // First, attempt to use `Equals` to compare principals, and if that 858 // fails compare siteOrigins. Only compare siteOrigin for content 859 // principals, as non-content principals will never have siteOrigin != 860 // origin. 861 nsAutoCString documentSiteOrigin; 862 return resultOrPrecursor->Equals(documentPrincipal) || 863 (documentPrincipal->GetIsContentPrincipal() && 864 resultOrPrecursor->GetIsContentPrincipal() && 865 NS_SUCCEEDED(documentPrincipal->GetSiteOriginNoSuffix( 866 documentSiteOrigin)) && 867 documentSiteOrigin == siteOriginNoSuffix); 868 }; 869 870 // XXX: Consider also checking in-flight process switches to see if any have 871 // matching principals? 872 AutoTArray<RefPtr<BrowsingContext>, 8> contexts; 873 aTopBC->Group()->GetToplevels(contexts); 874 while (!contexts.IsEmpty()) { 875 auto bc = contexts.PopLastElement(); 876 for (const auto& wc : bc->GetWindowContexts()) { 877 WindowGlobalParent* wgp = wc->Canonical(); 878 879 // Check if this WindowGlobalParent has the given resultPrincipal, and 880 // if it does, we need to load in that process. 881 if (!wgp->GetRemoteType().IsEmpty() && 882 principalIsSameSite(wgp->DocumentPrincipal())) { 883 MOZ_LOG(gProcessIsolationLog, LogLevel::Debug, 884 ("Found existing frame with matching principal " 885 "(remoteType:(%s), origin:%s)", 886 PromiseFlatCString(wgp->GetRemoteType()).get(), 887 OriginString(wgp->DocumentPrincipal()).get())); 888 options.mRemoteType = wgp->GetRemoteType(); 889 return options; 890 } 891 892 // Also enumerate over this WindowContexts' subframes. 893 contexts.AppendElements(wc->Children()); 894 } 895 } 896 } 897 898 nsAutoCString originSuffix = OriginSuffixForRemoteType(resultOrPrecursor); 899 900 WebProcessType webProcessType = WebProcessType::Web; 901 if (ShouldIsolateSite(resultOrPrecursor, aTopBC->UseRemoteSubframes())) { 902 webProcessType = WebProcessType::WebIsolated; 903 } 904 905 // Check if we should be cross-origin isolated. 906 if (options.mShouldCrossOriginIsolate) { 907 webProcessType = WebProcessType::WebCoopCoep; 908 } 909 910 switch (webProcessType) { 911 case WebProcessType::Web: 912 options.mRemoteType = WEB_REMOTE_TYPE; 913 break; 914 case WebProcessType::WebIsolated: 915 options.mRemoteType = 916 FISSION_WEB_REMOTE_TYPE "="_ns + siteOriginNoSuffix + originSuffix; 917 break; 918 case WebProcessType::WebCoopCoep: 919 options.mRemoteType = 920 WITH_COOP_COEP_REMOTE_TYPE "="_ns + siteOriginNoSuffix + originSuffix; 921 break; 922 } 923 return options; 924 } 925 926 Result<WorkerIsolationOptions, nsresult> IsolationOptionsForWorker( 927 nsIPrincipal* aPrincipal, WorkerKind aWorkerKind, 928 const nsACString& aCurrentRemoteType, bool aUseRemoteSubframes) { 929 MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, 930 ("IsolationOptionsForWorker principal:%s, kind:%s, current:%s", 931 OriginString(aPrincipal).get(), WorkerKindName(aWorkerKind), 932 PromiseFlatCString(aCurrentRemoteType).get())); 933 934 MOZ_ASSERT(NS_IsMainThread()); 935 MOZ_RELEASE_ASSERT( 936 aWorkerKind == WorkerKindService || aWorkerKind == WorkerKindShared, 937 "Unexpected remote worker kind"); 938 939 if (aWorkerKind == WorkerKindService && 940 !aPrincipal->GetIsContentPrincipal()) { 941 MOZ_LOG(gProcessIsolationLog, LogLevel::Warning, 942 ("Rejecting service worker with non-content principal")); 943 return Err(NS_ERROR_UNEXPECTED); 944 } 945 946 if (aPrincipal->GetIsExpandedPrincipal()) { 947 MOZ_LOG(gProcessIsolationLog, LogLevel::Warning, 948 ("Rejecting remote worker with expanded principal")); 949 return Err(NS_ERROR_UNEXPECTED); 950 } 951 952 // In some cases, such as for null principals without precursors, we will want 953 // to load a shared worker in a process based on the current process. This is 954 // not done for service workers - process selection for those should function 955 // the same in all processes. 956 // 957 // We only allow the current remote type to be used if it is not a COOP+COEP 958 // remote type, in order to avoid loading a shared worker in one of these 959 // processes. Currently process selection for workers occurs before response 960 // headers are available, so we will never select to load a shared worker in a 961 // COOP+COEP content process. 962 nsCString preferredRemoteType = DEFAULT_REMOTE_TYPE; 963 if (aWorkerKind == WorkerKind::WorkerKindShared && 964 !StringBeginsWith(aCurrentRemoteType, 965 WITH_COOP_COEP_REMOTE_TYPE_PREFIX)) { 966 preferredRemoteType = aCurrentRemoteType; 967 } 968 969 WorkerIsolationOptions options; 970 971 // If we're loading a null principal, we can't easily make a process 972 // selection decision off ot it. Instead, we'll use our null principal's 973 // precursor principal to make process selection decisions. 974 bool isNullPrincipalPrecursor = false; 975 nsCOMPtr<nsIPrincipal> resultOrPrecursor(aPrincipal); 976 if (nsCOMPtr<nsIPrincipal> precursor = 977 resultOrPrecursor->GetPrecursorPrincipal()) { 978 MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, 979 ("using null principal precursor origin %s", 980 OriginString(precursor).get())); 981 resultOrPrecursor = precursor; 982 isNullPrincipalPrecursor = true; 983 } 984 985 IsolationBehavior behavior = IsolationBehavior::WebContent; 986 if (resultOrPrecursor->GetIsContentPrincipal()) { 987 nsCOMPtr<nsIURI> uri = resultOrPrecursor->GetURI(); 988 behavior = IsolationBehaviorForURI(uri, /* aIsSubframe */ false, 989 /* aForChannelCreationURI */ false); 990 } else if (resultOrPrecursor->IsSystemPrincipal()) { 991 MOZ_ASSERT(aWorkerKind == WorkerKindShared); 992 993 // Allow system principal shared workers to load within either the 994 // parent process or privilegedabout process, depending on the 995 // responsible process. 996 if (preferredRemoteType == NOT_REMOTE_TYPE) { 997 MOZ_LOG(gProcessIsolationLog, LogLevel::Debug, 998 ("Loading system principal shared worker in parent process")); 999 behavior = IsolationBehavior::Parent; 1000 } else if (preferredRemoteType == PRIVILEGEDABOUT_REMOTE_TYPE) { 1001 MOZ_LOG(gProcessIsolationLog, LogLevel::Debug, 1002 ("Loading system principal shared worker in privilegedabout " 1003 "process")); 1004 behavior = IsolationBehavior::PrivilegedAbout; 1005 } else { 1006 MOZ_LOG(gProcessIsolationLog, LogLevel::Warning, 1007 ("Cannot load system-principal shared worker in " 1008 "non-privilegedabout content process")); 1009 return Err(NS_ERROR_UNEXPECTED); 1010 } 1011 } else { 1012 MOZ_ASSERT(resultOrPrecursor->GetIsNullPrincipal()); 1013 MOZ_ASSERT(aWorkerKind == WorkerKindShared); 1014 1015 if (preferredRemoteType == NOT_REMOTE_TYPE) { 1016 MOZ_LOG(gProcessIsolationLog, LogLevel::Debug, 1017 ("Ensuring precursorless null principal shared worker loads in a " 1018 "content process")); 1019 behavior = IsolationBehavior::ForceWebRemoteType; 1020 } else { 1021 MOZ_LOG(gProcessIsolationLog, LogLevel::Debug, 1022 ("Loading precursorless null principal shared worker within " 1023 "current remotetype: (%s)", 1024 preferredRemoteType.get())); 1025 behavior = IsolationBehavior::Anywhere; 1026 } 1027 } 1028 1029 if (behavior == IsolationBehavior::Parent && isNullPrincipalPrecursor) { 1030 MOZ_LOG(gProcessIsolationLog, LogLevel::Debug, 1031 ("Ensuring sandboxed null-principal shared worker doesn't load in " 1032 "the parent process")); 1033 behavior = IsolationBehavior::ForceWebRemoteType; 1034 } 1035 1036 if (behavior != IsolationBehavior::WebContent) { 1037 options.mRemoteType = MOZ_TRY( 1038 SpecialBehaviorRemoteType(behavior, preferredRemoteType, nullptr)); 1039 1040 MOZ_LOG( 1041 gProcessIsolationLog, LogLevel::Debug, 1042 ("Selecting specific %s worker remote type (%s) due to a special case " 1043 "isolation behavior %s", 1044 WorkerKindName(aWorkerKind), options.mRemoteType.get(), 1045 IsolationBehaviorName(behavior))); 1046 return options; 1047 } 1048 1049 // If we should be isolating this site, we can determine the correct fission 1050 // remote type from the principal's site-origin. 1051 if (ShouldIsolateSite(resultOrPrecursor, aUseRemoteSubframes)) { 1052 nsAutoCString siteOriginNoSuffix; 1053 MOZ_TRY(resultOrPrecursor->GetSiteOriginNoSuffix(siteOriginNoSuffix)); 1054 nsAutoCString originSuffix = OriginSuffixForRemoteType(resultOrPrecursor); 1055 1056 nsCString prefix = aWorkerKind == WorkerKindService 1057 ? SERVICEWORKER_REMOTE_TYPE 1058 : FISSION_WEB_REMOTE_TYPE; 1059 options.mRemoteType = prefix + "="_ns + siteOriginNoSuffix + originSuffix; 1060 1061 MOZ_LOG(gProcessIsolationLog, LogLevel::Debug, 1062 ("Isolating web content %s worker in remote type (%s)", 1063 WorkerKindName(aWorkerKind), options.mRemoteType.get())); 1064 } else { 1065 options.mRemoteType = WEB_REMOTE_TYPE; 1066 1067 MOZ_LOG(gProcessIsolationLog, LogLevel::Debug, 1068 ("Loading web content %s worker in shared web remote type", 1069 WorkerKindName(aWorkerKind))); 1070 } 1071 return options; 1072 } 1073 1074 void AddHighValuePermission(nsIPrincipal* aResultPrincipal, 1075 const nsACString& aPermissionType) { 1076 RefPtr<PermissionManager> perms = PermissionManager::GetInstance(); 1077 if (NS_WARN_IF(!perms)) { 1078 return; 1079 } 1080 1081 // We can't act on non-content principals, so if the load was sandboxed, try 1082 // to use the unsandboxed precursor principal to add the highValue permission. 1083 nsCOMPtr<nsIPrincipal> resultOrPrecursor(aResultPrincipal); 1084 if (!aResultPrincipal->GetIsContentPrincipal()) { 1085 resultOrPrecursor = aResultPrincipal->GetPrecursorPrincipal(); 1086 if (!resultOrPrecursor) { 1087 return; 1088 } 1089 } 1090 1091 // Use the site-origin principal as we want to add the permission for the 1092 // entire site, rather than a specific subdomain, as process isolation acts on 1093 // a site granularity. 1094 nsAutoCString siteOrigin; 1095 if (NS_FAILED(resultOrPrecursor->GetSiteOrigin(siteOrigin))) { 1096 return; 1097 } 1098 1099 nsCOMPtr<nsIPrincipal> sitePrincipal = 1100 BasePrincipal::CreateContentPrincipal(siteOrigin); 1101 if (!sitePrincipal || !sitePrincipal->GetIsContentPrincipal()) { 1102 return; 1103 } 1104 1105 MOZ_LOG(dom::gProcessIsolationLog, LogLevel::Verbose, 1106 ("Adding %s Permission for site '%s'", aPermissionType.BeginReading(), 1107 siteOrigin.get())); 1108 1109 uint32_t expiration = 0; 1110 if (aPermissionType.Equals(mozilla::dom::kHighValueCOOPPermission)) { 1111 expiration = StaticPrefs::fission_highValue_coop_expiration(); 1112 } else if (aPermissionType.Equals( 1113 mozilla::dom::kHighValueHasSavedLoginPermission) || 1114 aPermissionType.Equals( 1115 mozilla::dom::kHighValueIsLoggedInPermission)) { 1116 expiration = StaticPrefs::fission_highValue_login_expiration(); 1117 } else { 1118 MOZ_ASSERT_UNREACHABLE("Unknown permission type"); 1119 } 1120 1121 // XXX: Would be nice if we could use `TimeStamp` here, but there's 1122 // unfortunately no convenient way to recover a time in milliseconds since the 1123 // unix epoch from `TimeStamp`. 1124 int64_t expirationTime = 1125 (PR_Now() / PR_USEC_PER_MSEC) + (int64_t(expiration) * PR_MSEC_PER_SEC); 1126 (void)perms->AddFromPrincipal( 1127 sitePrincipal, aPermissionType, nsIPermissionManager::ALLOW_ACTION, 1128 nsIPermissionManager::EXPIRE_TIME, expirationTime); 1129 } 1130 1131 void AddHighValuePermission(const nsACString& aOrigin, 1132 const nsACString& aPermissionType) { 1133 nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); 1134 nsCOMPtr<nsIPrincipal> principal; 1135 nsresult rv = 1136 ssm->CreateContentPrincipalFromOrigin(aOrigin, getter_AddRefs(principal)); 1137 if (NS_WARN_IF(NS_FAILED(rv))) { 1138 return; 1139 } 1140 1141 AddHighValuePermission(principal, aPermissionType); 1142 } 1143 1144 bool IsIsolateHighValueSiteEnabled() { 1145 return mozilla::FissionAutostart() && 1146 WebContentIsolationStrategy( 1147 StaticPrefs::fission_webContentIsolationStrategy()) == 1148 WebContentIsolationStrategy::IsolateHighValue; 1149 } 1150 1151 bool ValidatePrincipalCouldPotentiallyBeLoadedBy( 1152 nsIPrincipal* aPrincipal, const nsACString& aRemoteType, 1153 const EnumSet<ValidatePrincipalOptions>& aOptions) { 1154 // Don't bother validating principals from the parent process. 1155 if (aRemoteType == NOT_REMOTE_TYPE) { 1156 return true; 1157 } 1158 1159 // If there is no principal, only allow it if AllowNullPtr is specified. 1160 if (!aPrincipal) { 1161 return aOptions.contains(ValidatePrincipalOptions::AllowNullPtr); 1162 } 1163 1164 // We currently do not track relationships between specific null principals 1165 // and content processes, so we can not validate much here. 1166 if (aPrincipal->GetIsNullPrincipal()) { 1167 return true; 1168 } 1169 1170 // If we have a system principal, only allow it if AllowSystem is passed. 1171 if (aPrincipal->IsSystemPrincipal()) { 1172 return aOptions.contains(ValidatePrincipalOptions::AllowSystem); 1173 } 1174 1175 // Performing checks against the remote type requires the IOService and 1176 // ThirdPartyService to be available, check we're not late in shutdown. 1177 if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownFinal)) { 1178 return true; 1179 } 1180 1181 // We can load a `resource://` URI in any process. This usually comes up due 1182 // to pdf.js and the JSON viewer. See bug 1686200. 1183 if (aPrincipal->SchemeIs("resource")) { 1184 return true; 1185 } 1186 1187 // Only allow expanded principals if AllowExpanded is passed. Each 1188 // sub-principal will be validated independently. 1189 if (aPrincipal->GetIsExpandedPrincipal()) { 1190 if (!aOptions.contains(ValidatePrincipalOptions::AllowExpanded)) { 1191 return false; 1192 } 1193 // FIXME: There are more constraints on expanded principals in-practice, 1194 // such as the structure of extension expanded principals. This may need 1195 // to be investigated more in the future. 1196 nsCOMPtr<nsIExpandedPrincipal> expandedPrincipal = 1197 do_QueryInterface(aPrincipal); 1198 const auto& allowList = expandedPrincipal->AllowList(); 1199 for (const auto& innerPrincipal : allowList) { 1200 if (!ValidatePrincipalCouldPotentiallyBeLoadedBy(innerPrincipal, 1201 aRemoteType, aOptions)) { 1202 return false; 1203 } 1204 } 1205 return true; 1206 } 1207 1208 // A URI with a file:// scheme can never load in a non-file content process 1209 // due to sandboxing. 1210 if (aPrincipal->SchemeIs("file")) { 1211 // If we don't support a separate 'file' process, then we can return here. 1212 if (!StaticPrefs::browser_tabs_remote_separateFileUriProcess()) { 1213 return true; 1214 } 1215 return aRemoteType == FILE_REMOTE_TYPE; 1216 } 1217 1218 if (aPrincipal->SchemeIs("about")) { 1219 uint32_t flags = 0; 1220 nsresult rv = aPrincipal->GetAboutModuleFlags(&flags); 1221 // In tests, we can race between about: pages being unregistered, and a 1222 // content process unregistering a Blob URL. To be safe here, we fail open 1223 // if no about module is present. 1224 if (NS_FAILED(rv)) { 1225 return false; 1226 } 1227 1228 // Block principals for about: URIs which can't load in this process. 1229 if (!(flags & (nsIAboutModule::URI_CAN_LOAD_IN_CHILD | 1230 nsIAboutModule::URI_MUST_LOAD_IN_CHILD))) { 1231 return false; 1232 } 1233 if (flags & nsIAboutModule::URI_MUST_LOAD_IN_EXTENSION_PROCESS) { 1234 return aRemoteType == EXTENSION_REMOTE_TYPE; 1235 } 1236 return true; 1237 } 1238 1239 // Web content can contain extension content frames, so any content process 1240 // may send us an extension's principal. 1241 // NOTE: We don't check AddonPolicy here, as that can disappear if the add-on 1242 // is disabled or uninstalled. As this is a lax check, looking at the scheme 1243 // should be sufficient. 1244 if (aPrincipal->SchemeIs("moz-extension")) { 1245 return true; 1246 } 1247 1248 // If the remote type doesn't have an origin suffix, we can do no further 1249 // principal validation with it. 1250 int32_t equalIdx = aRemoteType.FindChar('='); 1251 if (equalIdx == kNotFound) { 1252 return true; 1253 } 1254 1255 // Split out the remote type prefix and the origin suffix. 1256 nsDependentCSubstring typePrefix(aRemoteType, 0, equalIdx); 1257 nsDependentCSubstring typeOrigin(aRemoteType, equalIdx + 1); 1258 1259 // Only validate webIsolated and webServiceWorker remote types for now. This 1260 // should be expanded in the future. 1261 if (typePrefix != FISSION_WEB_REMOTE_TYPE && 1262 typePrefix != SERVICEWORKER_REMOTE_TYPE) { 1263 return true; 1264 } 1265 1266 // Trim any OriginAttributes from the origin, as those will not be validated. 1267 int32_t suffixIdx = typeOrigin.RFindChar('^'); 1268 nsDependentCSubstring typeOriginNoSuffix(typeOrigin, 0, suffixIdx); 1269 1270 // NOTE: Currently every webIsolated remote type is site-origin keyed, meaning 1271 // we can unconditionally compare site origins. If this changes in the future, 1272 // this logic will need to be updated to reflect that. 1273 nsAutoCString siteOriginNoSuffix; 1274 if (NS_FAILED(aPrincipal->GetSiteOriginNoSuffix(siteOriginNoSuffix))) { 1275 return false; 1276 } 1277 return siteOriginNoSuffix == typeOriginNoSuffix; 1278 } 1279 1280 } // namespace mozilla::dom