summaryrefslogtreecommitdiffstats
path: root/dom/ipc/ProcessIsolation.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/ipc/ProcessIsolation.cpp')
-rw-r--r--dom/ipc/ProcessIsolation.cpp956
1 files changed, 956 insertions, 0 deletions
diff --git a/dom/ipc/ProcessIsolation.cpp b/dom/ipc/ProcessIsolation.cpp
new file mode 100644
index 0000000000..cd223e647f
--- /dev/null
+++ b/dom/ipc/ProcessIsolation.cpp
@@ -0,0 +1,956 @@
+/* -*- 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<nsCString>& Get() {
+ if (!mValues) {
+ mValues = new nsTArray<nsCString>;
+ Preferences::RegisterCallbackAndCall(
+ [](const char*, void* aData) {
+ static_cast<CommaSeparatedPref*>(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<nsCString>* 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<nsIXULChromeRegistry> 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<nsIAboutModule> 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<nsIURI> inner;
+ if (nsCOMPtr<nsINestedURI> 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<nsIScriptSecurityManager> 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<BasePrincipal> 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<nsIURI> 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<PermissionManager> 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<NavigationIsolationOptions, nsresult> IsolationOptionsForNavigation(
+ CanonicalBrowsingContext* aTopBC, WindowGlobalParent* aParentWindow,
+ nsIURI* aChannelCreationURI, nsIChannel* aChannel,
+ const nsACString& aCurrentRemoteType, bool aHasCOOPMismatch,
+ bool aForNewTab, uint32_t aLoadStateLoadType,
+ const Maybe<uint64_t>& aChannelId,
+ const Maybe<nsCString>& aRemoteTypeOverride) {
+ // Get the final principal, used to select which process to load into.
+ nsCOMPtr<nsIPrincipal> 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<nsIPrincipal> resultOrPrecursor(resultPrincipal);
+ if (nsCOMPtr<nsIPrincipal> 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<BasePrincipal> 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<nsILoadInfo> 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<nsIURI> 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<nsIURI> 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<nsIPrincipal> documentPrincipal(aDocumentPrincipal);
+ if (nsCOMPtr<nsIPrincipal> 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<RefPtr<BrowsingContext>, 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<nsIHttpChannelInternal> 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<PermissionManager> 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<nsIPrincipal> 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<nsIPrincipal> 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<nsIPrincipal> 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