зеркало из https://github.com/mozilla/gecko-dev.git
946 строки
36 KiB
C++
946 строки
36 KiB
C++
/* -*- 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) {
|
|
return IsolationBehavior::WebContent;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
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
|