/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set sw=2 ts=8 et tw=80 : */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/dom/ProcessIsolation.h" #include "mozilla/Assertions.h" #include "mozilla/dom/BrowsingContextGroup.h" #include "mozilla/dom/CanonicalBrowsingContext.h" #include "mozilla/dom/ContentChild.h" #include "mozilla/dom/ContentParent.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/RemoteType.h" #include "mozilla/dom/WindowGlobalParent.h" #include "mozilla/extensions/WebExtensionPolicy.h" #include "mozilla/BasePrincipal.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/ContentPrincipal.h" #include "mozilla/ExtensionPolicyService.h" #include "mozilla/Logging.h" #include "mozilla/NullPrincipal.h" #include "mozilla/PermissionManager.h" #include "mozilla/Preferences.h" #include "mozilla/RefPtr.h" #include "mozilla/StaticPrefs_browser.h" #include "mozilla/StaticPrefs_fission.h" #include "mozilla/StaticPtr.h" #include "nsAboutProtocolUtils.h" #include "nsDocShell.h" #include "nsError.h" #include "nsIChromeRegistry.h" #include "nsIHttpChannel.h" #include "nsIHttpChannelInternal.h" #include "nsIProtocolHandler.h" #include "nsIXULRuntime.h" #include "nsNetUtil.h" #include "nsServiceManagerUtils.h" #include "nsSHistory.h" #include "nsURLHelper.h" namespace mozilla::dom { mozilla::LazyLogModule gProcessIsolationLog{"ProcessIsolation"}; namespace { // Strategy used to determine whether or not a particular site should load into // a webIsolated content process. The particular strategy chosen is controlled // by the `fission.webContentIsolationStrategy` pref, which must hold one of the // following values. enum class WebContentIsolationStrategy : uint32_t { // All web content is loaded into a shared `web` content process. This is // similar to the non-Fission behaviour, however remote subframes may still // be used for sites with special isolation behaviour, such as extension or // mozillaweb content processes. IsolateNothing = 0, // Web content is always isolated into its own `webIsolated` content process // based on site-origin, and will only load in a shared `web` content process // if site-origin could not be determined. IsolateEverything = 1, // Only isolates web content loaded by sites which are considered "high // value". A site is considered "high value" if it has been granted a // `highValue*` permission by the permission manager, which is done in // response to certain actions. IsolateHighValue = 2, }; /** * Helper class for caching the result of splitting prefs which are represented * as a comma-separated list of strings. */ struct CommaSeparatedPref { public: explicit constexpr CommaSeparatedPref(nsLiteralCString aPrefName) : mPrefName(aPrefName) {} void OnChange() { if (mValues) { mValues->Clear(); nsAutoCString prefValue; if (NS_SUCCEEDED(Preferences::GetCString(mPrefName.get(), prefValue))) { for (const auto& value : nsCCharSeparatedTokenizer(prefValue, ',').ToRange()) { mValues->EmplaceBack(value); } } } } const nsTArray& Get() { if (!mValues) { mValues = new nsTArray; Preferences::RegisterCallbackAndCall( [](const char*, void* aData) { static_cast(aData)->OnChange(); }, mPrefName, this); RunOnShutdown([this] { delete this->mValues; this->mValues = nullptr; }); } return *mValues; } auto begin() { return Get().cbegin(); } auto end() { return Get().cend(); } private: nsLiteralCString mPrefName; nsTArray* MOZ_OWNING_REF mValues = nullptr; }; CommaSeparatedPref sSeparatedMozillaDomains{ "browser.tabs.remote.separatedMozillaDomains"_ns}; /** * Certain URIs have special isolation behaviour, and need to be loaded within * specific process types. */ enum class IsolationBehavior { // This URI loads web content and should be treated as a content load, being // isolated based on the response principal. WebContent, // Forcibly load in a process with the "web" remote type. ForceWebRemoteType, // Load this URI in the privileged about content process. PrivilegedAbout, // Load this URI in the extension process. Extension, // Load this URI in the file content process. File, // Load this URI in the priviliged mozilla content process. PrivilegedMozilla, // Load this URI explicitly in the parent process. Parent, // Load this URI wherever the browsing context is currently loaded. This is // generally used for error pages. Anywhere, // May only be returned for subframes. Inherits the remote type of the parent // document which is embedding this document. Inherit, // Special case for the `about:reader` URI which should be loaded in the same // process which would be used for the "url" query parameter. AboutReader, // There was a fatal error, and the load should be aborted. Error, }; /** * Returns a static string with the name of the given isolation behaviour. For * use in logging code. */ static const char* IsolationBehaviorName(IsolationBehavior aBehavior) { switch (aBehavior) { case IsolationBehavior::WebContent: return "WebContent"; case IsolationBehavior::ForceWebRemoteType: return "ForceWebRemoteType"; case IsolationBehavior::PrivilegedAbout: return "PrivilegedAbout"; case IsolationBehavior::Extension: return "Extension"; case IsolationBehavior::File: return "File"; case IsolationBehavior::PrivilegedMozilla: return "PrivilegedMozilla"; case IsolationBehavior::Parent: return "Parent"; case IsolationBehavior::Anywhere: return "Anywhere"; case IsolationBehavior::Inherit: return "Inherit"; case IsolationBehavior::AboutReader: return "AboutReader"; case IsolationBehavior::Error: return "Error"; default: return "Unknown"; } } /** * Check if a given URI has specialized process isolation behaviour, such as * needing to be loaded within a specific type of content process. * * When handling a navigation, this method will be called twice: first with the * channel's creation URI, and then it will be called with a result principal's * URI. */ static IsolationBehavior IsolationBehaviorForURI(nsIURI* aURI, bool aIsSubframe, bool aForChannelCreationURI) { nsAutoCString scheme; MOZ_ALWAYS_SUCCEEDS(aURI->GetScheme(scheme)); if (scheme == "chrome"_ns) { // `chrome://` URIs are always loaded in the parent process, unless they // have opted in to loading in a content process. This is currently only // done in tests. // // FIXME: These flags should be removed from `chrome` URIs at some point. nsCOMPtr chromeReg = do_GetService("@mozilla.org/chrome/chrome-registry;1"); bool mustLoadRemotely = false; if (NS_SUCCEEDED(chromeReg->MustLoadURLRemotely(aURI, &mustLoadRemotely)) && mustLoadRemotely) { return IsolationBehavior::ForceWebRemoteType; } bool canLoadRemotely = false; if (NS_SUCCEEDED(chromeReg->CanLoadURLRemotely(aURI, &canLoadRemotely)) && canLoadRemotely) { return IsolationBehavior::Anywhere; } return IsolationBehavior::Parent; } if (scheme == "about"_ns) { nsAutoCString path; MOZ_ALWAYS_SUCCEEDS(NS_GetAboutModuleName(aURI, path)); // The `about:blank` and `about:srcdoc` pages are loaded by normal web // content, and should be allocated processes based on their simple content // principals. if (path == "blank"_ns || path == "srcdoc"_ns) { MOZ_ASSERT(NS_IsContentAccessibleAboutURI(aURI)); return IsolationBehavior::WebContent; } MOZ_ASSERT(!NS_IsContentAccessibleAboutURI(aURI)); // If we're loading an `about:reader` URI, perform isolation based on the // principal of the URI being loaded. if (path == "reader"_ns && aForChannelCreationURI) { return IsolationBehavior::AboutReader; } // Otherwise, we're going to be loading an about: page. Consult the module. nsCOMPtr aboutModule; if (NS_FAILED(NS_GetAboutModule(aURI, getter_AddRefs(aboutModule))) || !aboutModule) { // If we don't know of an about: module for this load, it's going to end // up being a network error. Allow the load to finish as normal. return IsolationBehavior::WebContent; } // NOTE: about modules can be implemented in JS, so this may run script, and // therefore can spuriously fail. uint32_t flags = 0; if (NS_FAILED(aboutModule->GetURIFlags(aURI, &flags))) { NS_WARNING( "nsIAboutModule::GetURIFlags unexpectedly failed. Abort the load"); return IsolationBehavior::Error; } if (flags & nsIAboutModule::URI_MUST_LOAD_IN_EXTENSION_PROCESS) { return IsolationBehavior::Extension; } if (flags & nsIAboutModule::URI_MUST_LOAD_IN_CHILD) { if (flags & nsIAboutModule::URI_CAN_LOAD_IN_PRIVILEGEDABOUT_PROCESS) { return IsolationBehavior::PrivilegedAbout; } return IsolationBehavior::ForceWebRemoteType; } if (flags & nsIAboutModule::URI_CAN_LOAD_IN_CHILD) { return IsolationBehavior::Anywhere; } return IsolationBehavior::Parent; } // If the test-only `dataUriInDefaultWebProcess` pref is enabled, dump all // `data:` URIs in a "web" content process, rather than loading them in // content processes based on their precursor origins. if (StaticPrefs::browser_tabs_remote_dataUriInDefaultWebProcess() && scheme == "data"_ns) { return IsolationBehavior::ForceWebRemoteType; } // Make sure to unwrap nested URIs before we early return for channel creation // URI. The checks past this point are intended to operate on the principal, // which has it's origin constructed from the innermost URI. nsCOMPtr inner; if (nsCOMPtr nested = do_QueryInterface(aURI); nested && NS_SUCCEEDED(nested->GetInnerURI(getter_AddRefs(inner)))) { return IsolationBehaviorForURI(inner, aIsSubframe, aForChannelCreationURI); } // If we're doing the initial check based on the channel creation URI, stop // here as we want to only perform the following checks on the true channel // result principal. if (aForChannelCreationURI) { return IsolationBehavior::WebContent; } // Protocols used by Thunderbird to display email messages. if (scheme == "imap"_ns || scheme == "mailbox"_ns || scheme == "news"_ns || scheme == "nntp"_ns || scheme == "snews"_ns) { return IsolationBehavior::Parent; } // There is more handling for extension content processes in the caller, but // they should load in an extension content process unless we're loading a // subframe. if (scheme == "moz-extension"_ns) { if (aIsSubframe) { // As a temporary measure, extension iframes must be loaded within the // same process as their parent document. return IsolationBehavior::Inherit; } return IsolationBehavior::Extension; } if (scheme == "file"_ns) { return IsolationBehavior::File; } // Check if the URI is listed as a privileged mozilla content process. if (scheme == "https"_ns && StaticPrefs:: browser_tabs_remote_separatePrivilegedMozillaWebContentProcess()) { nsAutoCString host; if (NS_SUCCEEDED(aURI->GetAsciiHost(host))) { for (const auto& separatedDomain : sSeparatedMozillaDomains) { // If the domain exactly matches our host, or our host ends with "." + // separatedDomain, we consider it matching. if (separatedDomain == host || (separatedDomain.Length() < host.Length() && host.CharAt(host.Length() - separatedDomain.Length() - 1) == '.' && StringEndsWith(host, separatedDomain))) { return IsolationBehavior::PrivilegedMozilla; } } } } nsCOMPtr secMan = nsContentUtils::GetSecurityManager(); bool inFileURIAllowList = false; if (NS_SUCCEEDED(secMan->InFileURIAllowlist(aURI, &inFileURIAllowList)) && inFileURIAllowList) { return IsolationBehavior::File; } return IsolationBehavior::WebContent; } /** * Helper method for logging the origin of a principal as a string. */ static nsAutoCString OriginString(nsIPrincipal* aPrincipal) { nsAutoCString origin; aPrincipal->GetOrigin(origin); return origin; } /** * Given an about:reader URI, extract the "url" query parameter, and use it to * construct a principal which should be used for process selection. */ static already_AddRefed GetAboutReaderURLPrincipal( nsIURI* aURI, const OriginAttributes& aAttrs) { #ifdef DEBUG MOZ_ASSERT(aURI->SchemeIs("about")); nsAutoCString path; MOZ_ALWAYS_SUCCEEDS(NS_GetAboutModuleName(aURI, path)); MOZ_ASSERT(path == "reader"_ns); #endif nsAutoCString query; MOZ_ALWAYS_SUCCEEDS(aURI->GetQuery(query)); // Extract the "url" parameter from the `about:reader`'s query parameters, // and recover a content principal from it. nsAutoString readerSpec; if (URLParams::Extract(query, u"url"_ns, readerSpec)) { nsCOMPtr readerUri; if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(readerUri), readerSpec))) { return BasePrincipal::CreateContentPrincipal(readerUri, aAttrs); } } return nullptr; } /** * Returns `true` if loads for this site should be isolated on a per-site basis. * If `aTopBC` is nullptr, this is being called to check if a shared or service * worker should be isolated. */ static bool ShouldIsolateSite(nsIPrincipal* aPrincipal, CanonicalBrowsingContext* aTopBC) { // If Fission is disabled, we never want to isolate. We check the toplevel BC // if it's available, or the global pref if checking for shared or service // workers. if (aTopBC && !aTopBC->UseRemoteSubframes()) { return false; } if (!aTopBC && !mozilla::FissionAutostart()) { return false; } // non-content principals currently can't have webIsolated remote types // assigned to them, so should not be isolated. if (!aPrincipal->GetIsContentPrincipal()) { return false; } switch (WebContentIsolationStrategy( StaticPrefs::fission_webContentIsolationStrategy())) { case WebContentIsolationStrategy::IsolateNothing: MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, ("Not isolating '%s' as isolation is disabled", OriginString(aPrincipal).get())); return false; case WebContentIsolationStrategy::IsolateEverything: MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, ("Isolating '%s' as isolation is enabled for all sites", OriginString(aPrincipal).get())); return true; case WebContentIsolationStrategy::IsolateHighValue: { RefPtr perms = PermissionManager::GetInstance(); if (NS_WARN_IF(!perms)) { // If we somehow have no permission manager, fall back to the safest // option, and try to isolate. MOZ_ASSERT_UNREACHABLE("Permission manager is missing"); return true; } static constexpr nsLiteralCString kHighValuePermissions[] = { mozilla::dom::kHighValueCOOPPermission, mozilla::dom::kHighValueHasSavedLoginPermission, mozilla::dom::kHighValueIsLoggedInPermission, }; for (const auto& type : kHighValuePermissions) { uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION; if (NS_SUCCEEDED(perms->TestPermissionFromPrincipal(aPrincipal, type, &permission)) && permission == nsIPermissionManager::ALLOW_ACTION) { MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, ("Isolating '%s' due to high-value permission '%s'", OriginString(aPrincipal).get(), type.get())); return true; } } MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, ("Not isolating '%s' as it is not high-value", OriginString(aPrincipal).get())); return false; } default: // An invalid pref value was used. Fall back to the safest option and // isolate everything. NS_WARNING("Invalid pref value for fission.webContentIsolationStrategy"); MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, ("Isolating '%s' due to unknown strategy pref value", OriginString(aPrincipal).get())); return true; } } enum class WebProcessType { Web, WebIsolated, WebCoopCoep, }; } // namespace Result IsolationOptionsForNavigation( CanonicalBrowsingContext* aTopBC, WindowGlobalParent* aParentWindow, nsIURI* aChannelCreationURI, nsIChannel* aChannel, const nsACString& aCurrentRemoteType, bool aHasCOOPMismatch, bool aForNewTab, uint32_t aLoadStateLoadType, const Maybe& aChannelId, const Maybe& aRemoteTypeOverride) { // Get the final principal, used to select which process to load into. nsCOMPtr resultPrincipal; nsresult rv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal( aChannel, getter_AddRefs(resultPrincipal)); if (NS_FAILED(rv)) { MOZ_LOG(gProcessIsolationLog, LogLevel::Error, ("failed to get channel result principal")); return Err(rv); } MOZ_LOG( gProcessIsolationLog, LogLevel::Verbose, ("IsolationOptionsForNavigation principal:%s, uri:%s, parentUri:%s", OriginString(resultPrincipal).get(), aChannelCreationURI->GetSpecOrDefault().get(), aParentWindow ? aParentWindow->GetDocumentURI()->GetSpecOrDefault().get() : "")); // If we're loading a null principal, we can't easily make a process // selection decision off ot it. Instead, we'll use our null principal's // precursor principal to make process selection decisions. bool principalIsSandboxed = false; nsCOMPtr resultOrPrecursor(resultPrincipal); if (nsCOMPtr precursor = resultOrPrecursor->GetPrecursorPrincipal()) { MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, ("using null principal precursor origin %s", OriginString(precursor).get())); resultOrPrecursor = precursor; principalIsSandboxed = true; } NavigationIsolationOptions options; options.mReplaceBrowsingContext = aHasCOOPMismatch; // Check if this load has an explicit remote type override. This is used to // perform an about:blank load within a specific content process. if (aRemoteTypeOverride) { MOZ_DIAGNOSTIC_ASSERT( NS_IsAboutBlank(aChannelCreationURI), "Should only have aRemoteTypeOverride for about:blank URIs"); if (NS_WARN_IF(!resultPrincipal->GetIsNullPrincipal())) { MOZ_LOG(gProcessIsolationLog, LogLevel::Error, ("invalid remote type override on non-null principal")); return Err(NS_ERROR_DOM_SECURITY_ERR); } MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, ("using remote type override (%s) for load", aRemoteTypeOverride->get())); options.mRemoteType = *aRemoteTypeOverride; return options; } // First, check for any special cases which should be handled using the // channel creation URI, and handle them. auto behavior = IsolationBehaviorForURI(aChannelCreationURI, aParentWindow, /* aForChannelCreationURI */ true); MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, ("Channel Creation Isolation Behavior: %s", IsolationBehaviorName(behavior))); // In the about:reader special case, we want to fetch the relevant information // from the URI, an then treat it as a normal web content load. if (behavior == IsolationBehavior::AboutReader) { if (RefPtr readerURIPrincipal = GetAboutReaderURLPrincipal( aChannelCreationURI, resultOrPrecursor->OriginAttributesRef())) { MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, ("using about:reader's url origin %s", OriginString(readerURIPrincipal).get())); resultOrPrecursor = readerURIPrincipal; } behavior = IsolationBehavior::WebContent; // If loading an about:reader page in a BrowsingContext which shares a // BrowsingContextGroup with other toplevel documents, replace the // BrowsingContext to destroy any references. // // With SHIP we can apply this to all about:reader loads, but otherwise // do it at least where there are opener/group relationships. if (mozilla::SessionHistoryInParent() || aTopBC->Group()->Toplevels().Length() > 1) { options.mReplaceBrowsingContext = true; } } // If we're running in a test which is requesting that system-triggered // about:blank documents load within the current process, override the // behaviour for loads which meet the requirements. if (StaticPrefs::browser_tabs_remote_systemTriggeredAboutBlankAnywhere() && NS_IsAboutBlank(aChannelCreationURI)) { nsCOMPtr loadInfo = aChannel->LoadInfo(); if (loadInfo->TriggeringPrincipal()->IsSystemPrincipal() && resultOrPrecursor->GetIsNullPrincipal()) { MOZ_LOG(gProcessIsolationLog, LogLevel::Warning, ("Forcing system-principal triggered about:blank load to " "complete in the current process")); behavior = IsolationBehavior::Anywhere; } } // If we're loading for a specific extension, we'll need to perform a // BCG-switching load to get our toplevel extension window in the correct // BrowsingContextGroup. if (auto* addonPolicy = BasePrincipal::Cast(resultOrPrecursor)->AddonPolicy()) { if (aParentWindow) { // As a temporary measure, extension iframes must be loaded within the // same process as their parent document. MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, ("Loading extension subframe in same process as parent")); behavior = IsolationBehavior::Inherit; } else { MOZ_LOG( gProcessIsolationLog, LogLevel::Verbose, ("Found extension frame with addon policy. Will use group id %" PRIx64 " (currentId: %" PRIx64 ")", addonPolicy->GetBrowsingContextGroupId(), aTopBC->Group()->Id())); behavior = IsolationBehavior::Extension; if (aTopBC->Group()->Id() != addonPolicy->GetBrowsingContextGroupId()) { options.mReplaceBrowsingContext = true; options.mSpecificGroupId = addonPolicy->GetBrowsingContextGroupId(); } } } // Do a second run of `GetIsolationBehavior`, this time using the // principal's URI to handle additional special cases such as the file and // privilegedmozilla content process. if (behavior == IsolationBehavior::WebContent) { if (resultOrPrecursor->IsSystemPrincipal()) { // We're loading something with a system principal which isn't caught in // one of our other edge-cases. If the load started in the parent process, // and it's safe for it to end in the parent process, we should finish the // load there. bool isUIResource = false; if (aCurrentRemoteType.IsEmpty() && (aChannelCreationURI->SchemeIs("about") || (NS_SUCCEEDED(NS_URIChainHasFlags( aChannelCreationURI, nsIProtocolHandler::URI_IS_UI_RESOURCE, &isUIResource)) && isUIResource))) { behavior = IsolationBehavior::Parent; } else { // In general, we don't want to load documents with a system principal // in a content process, however we need to in some cases, such as when // loading blob: URLs created by system code. We can force the load to // finish in a content process instead. behavior = IsolationBehavior::ForceWebRemoteType; } } else if (nsCOMPtr principalURI = resultOrPrecursor->GetURI()) { behavior = IsolationBehaviorForURI(principalURI, aParentWindow, /* aForChannelCreationURI */ false); } } // If we're currently loaded in the extension process, and are going to switch // to some other remote type, make sure we leave the extension's BCG which we // may have entered earlier to separate extension and non-extension BCGs from // each-other. if (!aParentWindow && aCurrentRemoteType == EXTENSION_REMOTE_TYPE && behavior != IsolationBehavior::Extension && behavior != IsolationBehavior::Anywhere) { MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, ("Forcing BC replacement to leave extension BrowsingContextGroup " "%" PRIx64 " on navigation", aTopBC->Group()->Id())); options.mReplaceBrowsingContext = true; } // We don't want to load documents with sandboxed null principals, like // `data:` URIs, in the parent process, even if they were created by a // document which would otherwise be loaded in the parent process. if (behavior == IsolationBehavior::Parent && principalIsSandboxed) { MOZ_LOG(gProcessIsolationLog, LogLevel::Debug, ("Ensuring sandboxed null-principal load doesn't occur in the " "parent process")); behavior = IsolationBehavior::ForceWebRemoteType; } MOZ_LOG( gProcessIsolationLog, LogLevel::Debug, ("Using IsolationBehavior %s for %s (original uri %s)", IsolationBehaviorName(behavior), OriginString(resultOrPrecursor).get(), aChannelCreationURI->GetSpecOrDefault().get())); // Check if we can put the previous document into the BFCache. if (mozilla::BFCacheInParent() && nsSHistory::GetMaxTotalViewers() > 0 && !aForNewTab && !aParentWindow && !aTopBC->HadOriginalOpener() && behavior != IsolationBehavior::Parent && (ExtensionPolicyService::GetSingleton().UseRemoteExtensions() || behavior != IsolationBehavior::Extension) && !aCurrentRemoteType.IsEmpty() && aTopBC->GetHasLoadedNonInitialDocument() && (aLoadStateLoadType == LOAD_NORMAL || aLoadStateLoadType == LOAD_HISTORY || aLoadStateLoadType == LOAD_LINK || aLoadStateLoadType == LOAD_STOP_CONTENT || aLoadStateLoadType == LOAD_STOP_CONTENT_AND_REPLACE) && (!aTopBC->GetActiveSessionHistoryEntry() || aTopBC->GetActiveSessionHistoryEntry()->GetSaveLayoutStateFlag())) { if (nsCOMPtr uri = aTopBC->GetCurrentURI()) { MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, ("current uri: %s", uri->GetSpecOrDefault().get())); } options.mTryUseBFCache = aTopBC->AllowedInBFCache(aChannelId, aChannelCreationURI); if (options.mTryUseBFCache) { options.mReplaceBrowsingContext = true; options.mActiveSessionHistoryEntry = aTopBC->GetActiveSessionHistoryEntry(); } } // If the load has any special remote type handling, do so at this point. if (behavior != IsolationBehavior::WebContent) { switch (behavior) { case IsolationBehavior::ForceWebRemoteType: options.mRemoteType = WEB_REMOTE_TYPE; break; case IsolationBehavior::PrivilegedAbout: // The privileged about: content process cannot be disabled, as it // causes various actors to break. options.mRemoteType = PRIVILEGEDABOUT_REMOTE_TYPE; break; case IsolationBehavior::Extension: if (ExtensionPolicyService::GetSingleton().UseRemoteExtensions()) { options.mRemoteType = EXTENSION_REMOTE_TYPE; } else { options.mRemoteType = NOT_REMOTE_TYPE; } break; case IsolationBehavior::File: if (StaticPrefs::browser_tabs_remote_separateFileUriProcess()) { options.mRemoteType = FILE_REMOTE_TYPE; } else { options.mRemoteType = WEB_REMOTE_TYPE; } break; case IsolationBehavior::PrivilegedMozilla: options.mRemoteType = PRIVILEGEDMOZILLA_REMOTE_TYPE; break; case IsolationBehavior::Parent: options.mRemoteType = NOT_REMOTE_TYPE; break; case IsolationBehavior::Anywhere: options.mRemoteType = aCurrentRemoteType; break; case IsolationBehavior::Inherit: MOZ_DIAGNOSTIC_ASSERT(aParentWindow); options.mRemoteType = aParentWindow->GetRemoteType(); break; case IsolationBehavior::WebContent: case IsolationBehavior::AboutReader: MOZ_ASSERT_UNREACHABLE(); return Err(NS_ERROR_UNEXPECTED); case IsolationBehavior::Error: return Err(NS_ERROR_UNEXPECTED); } if (options.mRemoteType != aCurrentRemoteType && (options.mRemoteType.IsEmpty() || aCurrentRemoteType.IsEmpty())) { options.mReplaceBrowsingContext = true; } MOZ_LOG( gProcessIsolationLog, LogLevel::Debug, ("Selecting specific remote type (%s) due to a special case isolation " "behavior %s", options.mRemoteType.get(), IsolationBehaviorName(behavior))); return options; } // At this point we're definitely not going to be loading in the parent // process anymore, so we're definitely going to be replacing BrowsingContext // if we're in the parent process. if (aCurrentRemoteType.IsEmpty()) { MOZ_ASSERT(!aParentWindow); options.mReplaceBrowsingContext = true; } nsAutoCString siteOriginNoSuffix; MOZ_TRY(resultOrPrecursor->GetSiteOriginNoSuffix(siteOriginNoSuffix)); // Check if we've already loaded a document with the given principal in some // content process. We want to finish the load in the same process in that // case. // // The exception to that is with extension loads and the system principal, // where we may have multiple documents with the same principal in different // processes. Those have been handled above, and will not be reaching here. // // If we're doing a replace load or opening a new tab, we won't be staying in // the same BrowsingContextGroup, so ignore this step. if (!options.mReplaceBrowsingContext && !aForNewTab) { // Helper for efficiently determining if a given origin is same-site. This // will attempt to do a fast equality check, and will only fall back to // computing the site-origin for content principals. auto principalIsSameSite = [&](nsIPrincipal* aDocumentPrincipal) -> bool { // If we're working with a null principal with a precursor, compare // precursors, as `resultOrPrecursor` has already been stripped to its // precursor. nsCOMPtr documentPrincipal(aDocumentPrincipal); if (nsCOMPtr precursor = documentPrincipal->GetPrecursorPrincipal()) { documentPrincipal = precursor; } // First, attempt to use `Equals` to compare principals, and if that // fails compare siteOrigins. Only compare siteOrigin for content // principals, as non-content principals will never have siteOrigin != // origin. nsAutoCString documentSiteOrigin; return resultOrPrecursor->Equals(documentPrincipal) || (documentPrincipal->GetIsContentPrincipal() && resultOrPrecursor->GetIsContentPrincipal() && NS_SUCCEEDED(documentPrincipal->GetSiteOriginNoSuffix( documentSiteOrigin)) && documentSiteOrigin == siteOriginNoSuffix); }; // XXX: Consider also checking in-flight process switches to see if any have // matching principals? AutoTArray, 8> contexts; aTopBC->Group()->GetToplevels(contexts); while (!contexts.IsEmpty()) { auto bc = contexts.PopLastElement(); for (const auto& wc : bc->GetWindowContexts()) { WindowGlobalParent* wgp = wc->Canonical(); // Check if this WindowGlobalParent has the given resultPrincipal, and // if it does, we need to load in that process. if (!wgp->GetRemoteType().IsEmpty() && principalIsSameSite(wgp->DocumentPrincipal())) { MOZ_LOG(gProcessIsolationLog, LogLevel::Debug, ("Found existing frame with matching principal " "(remoteType:(%s), origin:%s)", PromiseFlatCString(wgp->GetRemoteType()).get(), OriginString(wgp->DocumentPrincipal()).get())); options.mRemoteType = wgp->GetRemoteType(); return options; } // Also enumerate over this WindowContexts' subframes. contexts.AppendElements(wc->Children()); } } } nsAutoCString originSuffix; OriginAttributes attrs = resultOrPrecursor->OriginAttributesRef(); attrs.StripAttributes(OriginAttributes::STRIP_FIRST_PARTY_DOMAIN | OriginAttributes::STRIP_PARITION_KEY); attrs.CreateSuffix(originSuffix); WebProcessType webProcessType = WebProcessType::Web; if (ShouldIsolateSite(resultOrPrecursor, aTopBC)) { webProcessType = WebProcessType::WebIsolated; } // Check if we should be loading in a webCOOP+COEP remote type due to our COOP // status. nsILoadInfo::CrossOriginOpenerPolicy coop = nsILoadInfo::OPENER_POLICY_UNSAFE_NONE; if (aParentWindow) { coop = aTopBC->GetOpenerPolicy(); } else if (nsCOMPtr httpChannel = do_QueryInterface(aChannel)) { MOZ_ALWAYS_SUCCEEDS(httpChannel->GetCrossOriginOpenerPolicy(&coop)); } if (coop == nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP) { webProcessType = WebProcessType::WebCoopCoep; // If we're changing BrowsingContext, and are going to end up within a // webCOOP+COEP group, ensure we use a cross-origin isolated BCG ID. if (options.mReplaceBrowsingContext) { MOZ_ASSERT(!options.mSpecificGroupId, "overriding previously-specified BCG ID"); options.mSpecificGroupId = BrowsingContextGroup::CreateId( /* aPotentiallyCrossOriginIsolated */ true); } } switch (webProcessType) { case WebProcessType::Web: options.mRemoteType = WEB_REMOTE_TYPE; break; case WebProcessType::WebIsolated: options.mRemoteType = FISSION_WEB_REMOTE_TYPE "="_ns + siteOriginNoSuffix + originSuffix; break; case WebProcessType::WebCoopCoep: options.mRemoteType = WITH_COOP_COEP_REMOTE_TYPE "="_ns + siteOriginNoSuffix + originSuffix; break; } return options; } void AddHighValuePermission(nsIPrincipal* aResultPrincipal, const nsACString& aPermissionType) { RefPtr perms = PermissionManager::GetInstance(); if (NS_WARN_IF(!perms)) { return; } // We can't act on non-content principals, so if the load was sandboxed, try // to use the unsandboxed precursor principal to add the highValue permission. nsCOMPtr resultOrPrecursor(aResultPrincipal); if (!aResultPrincipal->GetIsContentPrincipal()) { resultOrPrecursor = aResultPrincipal->GetPrecursorPrincipal(); if (!resultOrPrecursor) { return; } } // Use the site-origin principal as we want to add the permission for the // entire site, rather than a specific subdomain, as process isolation acts on // a site granularity. nsAutoCString siteOrigin; if (NS_FAILED(resultOrPrecursor->GetSiteOrigin(siteOrigin))) { return; } nsCOMPtr sitePrincipal = BasePrincipal::CreateContentPrincipal(siteOrigin); if (!sitePrincipal || !sitePrincipal->GetIsContentPrincipal()) { return; } MOZ_LOG(dom::gProcessIsolationLog, LogLevel::Verbose, ("Adding %s Permission for site '%s'", aPermissionType.BeginReading(), siteOrigin.get())); uint32_t expiration = 0; if (aPermissionType.Equals(mozilla::dom::kHighValueCOOPPermission)) { expiration = StaticPrefs::fission_highValue_coop_expiration(); } else if (aPermissionType.Equals( mozilla::dom::kHighValueHasSavedLoginPermission) || aPermissionType.Equals( mozilla::dom::kHighValueIsLoggedInPermission)) { expiration = StaticPrefs::fission_highValue_login_expiration(); } else { MOZ_ASSERT_UNREACHABLE("Unknown permission type"); } // XXX: Would be nice if we could use `TimeStamp` here, but there's // unfortunately no convenient way to recover a time in milliseconds since the // unix epoch from `TimeStamp`. int64_t expirationTime = (PR_Now() / PR_USEC_PER_MSEC) + (int64_t(expiration) * PR_MSEC_PER_SEC); Unused << perms->AddFromPrincipal( sitePrincipal, aPermissionType, nsIPermissionManager::ALLOW_ACTION, nsIPermissionManager::EXPIRE_TIME, expirationTime); } void AddHighValuePermission(const nsACString& aOrigin, const nsACString& aPermissionType) { nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); nsCOMPtr principal; nsresult rv = ssm->CreateContentPrincipalFromOrigin(aOrigin, getter_AddRefs(principal)); if (NS_WARN_IF(NS_FAILED(rv))) { return; } AddHighValuePermission(principal, aPermissionType); } bool IsIsolateHighValueSiteEnabled() { return mozilla::FissionAutostart() && WebContentIsolationStrategy( StaticPrefs::fission_webContentIsolationStrategy()) == WebContentIsolationStrategy::IsolateHighValue; } } // namespace mozilla::dom