/* 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/AppShutdown.h"
#include "mozilla/Assertions.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 "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 "nsAboutProtocolUtils.h"
#include "nsCExternalHandlerService.h"
#include "nsDocShell.h"
#include "nsError.h"
#include "nsEscape.h"
#include "nsIChromeRegistry.h"
#include "nsIEnterprisePolicies.h"
#include "nsIExternalProtocolHandler.h"
#include "nsIExternalProtocolService.h"
#include "nsIHttpChannel.h"
#include "nsIHttpChannelInternal.h"
#include "nsIMIMEInfo.h"
#include "nsIProtocolHandler.h"
#include "nsIXULRuntime.h"
#include "nsNetUtil.h"
#include "nsSHistory.h"
#include "nsServiceManagerUtils.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};
bool AllowJITForSiteOrigin(
const nsACString& aSiteOriginNoSuffix,
WindowGlobalParent* aParentWindow) {
nsresult rv;
nsCOMPtr<nsIEnterprisePolicies> policyService =
do_GetService(
"@mozilla.org/enterprisepolicies;1");
if (!policyService) {
return true;
}
nsAutoCString topSiteOriginNoSuffix(aSiteOriginNoSuffix);
// If this is a subframe then use the principal of the top window.
if (aParentWindow) {
rv = aParentWindow->TopWindowContext()
->DocumentPrincipal()
->GetSiteOriginNoSuffix(topSiteOriginNoSuffix);
if (NS_FAILED(rv)) {
topSiteOriginNoSuffix = aSiteOriginNoSuffix;
}
}
nsCOMPtr<nsIURI> topSite;
rv = NS_NewURI(getter_AddRefs(topSite), topSiteOriginNoSuffix);
NS_ENSURE_SUCCESS(rv,
true);
bool isJitAllowed =
true;
if (NS_FAILED(
policyService->IsAllowedForURI(
"jit"_ns, topSite, &isJitAllowed))) {
return true;
}
if (!isJitAllowed) {
MOZ_LOG(gProcessIsolationLog, LogLevel::Debug,
(
"JIT is disabled for site %s by enterprise policy",
topSiteOriginNoSuffix.get()));
}
return isJitAllowed;
}
/**
* 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 if enabled.
WebContent,
// Forcibly load in a process with the "web" remote type. This will ignore the
// response principal completely.
// This is generally reserved for internal documents which are loaded in
// content, but not in the privilegedabout content process.
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";
}
}
/**
* Returns a static string with the name of the given worker kind. For use in
* logging code.
*/
static const char* WorkerKindName(WorkerKind aWorkerKind) {
switch (aWorkerKind) {
case WorkerKindDedicated:
return "Dedicated";
case WorkerKindShared:
return "Shared";
case WorkerKindService:
return "Service";
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) {
MOZ_ASSERT(NS_IsMainThread());
nsAutoCString scheme;
MOZ_ALWAYS_SUCCEEDS(aURI->GetScheme(scheme));
if (scheme ==
"chrome"_ns) {
// `chrome://` URIs are always loaded in the parent process.
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 || scheme ==
"x-moz-ews"_ns ||
scheme ==
"x-moz-graph"_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;
}
/**
* Trim the OriginAttributes, and use it to create a OriginSuffix string
* appropriate to use within a remoteType string.
*/
static nsAutoCString OriginSuffixForRemoteType(OriginAttributes aAttrs,
bool aDisableJit) {
nsAutoCString originSuffix;
aAttrs.StripAttributes(OriginAttributes::STRIP_FIRST_PARTY_DOMAIN |
OriginAttributes::STRIP_PARITION_KEY);
aAttrs.CreateSuffix(originSuffix);
if (aDisableJit) {
if (originSuffix.IsEmpty()) {
originSuffix =
"^"_ns + DISABLE_JIT_REMOTE_TYPE_SUFFIX;
}
else {
originSuffix +=
"&"_ns + DISABLE_JIT_REMOTE_TYPE_SUFFIX;
}
}
return originSuffix;
}
/**
* 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<nsIURI> GetAboutReaderURL(nsIURI* aURI) {
#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.
nsAutoCString readerSpec;
if (URLParams::Extract(query,
"url"_ns, readerSpec)) {
nsCOMPtr<nsIURI> readerUri;
if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(readerUri), readerSpec))) {
return readerUri.forget();
}
}
return nullptr;
}
static already_AddRefed<BasePrincipal> GetAboutReaderURLPrincipal(
nsIURI* aURI,
const OriginAttributes& aAttrs) {
if (nsCOMPtr<nsIURI> readerUri = GetAboutReaderURL(aURI)) {
return BasePrincipal::CreateContentPrincipal(readerUri, aAttrs);
}
return nullptr;
}
/**
* Check the Cross-Origin-Opener-Policy of the given channel or ancestor
* BrowsingContext, checking if the response should be cross-origin isolated.
*/
static bool ShouldCrossOriginIsolate(nsIChannel* aChannel,
WindowGlobalParent* aParentWindow) {
nsILoadInfo::CrossOriginOpenerPolicy coop =
nsILoadInfo::OPENER_POLICY_UNSAFE_NONE;
if (aParentWindow) {
coop = aParentWindow->BrowsingContext()->Top()->GetOpenerPolicy();
}
else if (nsCOMPtr<nsIHttpChannelInternal> httpChannel =
do_QueryInterface(aChannel)) {
MOZ_ALWAYS_SUCCEEDS(httpChannel->GetCrossOriginOpenerPolicy(&coop));
}
return coop ==
nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP;
}
/**
* 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,
bool aUseRemoteSubframes) {
// 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 (!aUseRemoteSubframes) {
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;
}
}
static Result<nsCString, nsresult> SpecialBehaviorRemoteType(
IsolationBehavior aBehavior,
const nsACString& aCurrentRemoteType,
WindowGlobalParent* aParentWindow,
const OriginAttributes& aAttrs) {
switch (aBehavior) {
case IsolationBehavior::ForceWebRemoteType:
return {SharedWebRemoteType(aAttrs)};
case IsolationBehavior::PrivilegedAbout:
// The privileged about: content process cannot be disabled, as it
// causes various actors to break.
return {PRIVILEGEDABOUT_REMOTE_TYPE};
case IsolationBehavior::Extension:
if (ExtensionPolicyService::GetSingleton().UseRemoteExtensions()) {
return {EXTENSION_REMOTE_TYPE};
}
return {NOT_REMOTE_TYPE};
case IsolationBehavior::File:
if (StaticPrefs::browser_tabs_remote_separateFileUriProcess()) {
return {FILE_REMOTE_TYPE};
}
return {SharedWebRemoteType(aAttrs)};
case IsolationBehavior::PrivilegedMozilla:
return {PRIVILEGEDMOZILLA_REMOTE_TYPE};
case IsolationBehavior::Parent:
return {NOT_REMOTE_TYPE};
case IsolationBehavior::Anywhere:
return {nsCString(aCurrentRemoteType)};
case IsolationBehavior::Inherit:
MOZ_DIAGNOSTIC_ASSERT(aParentWindow);
return {nsCString(aParentWindow->GetRemoteType())};
case IsolationBehavior::Error:
return Err(NS_ERROR_UNEXPECTED);
default:
MOZ_ASSERT_UNREACHABLE();
return Err(NS_ERROR_UNEXPECTED);
}
}
enum class WebProcessType {
Web,
WebIsolated,
WebCoopCoep,
};
}
// namespace
nsCString SharedWebRemoteType(
const OriginAttributes& aAttrs,
bool aDisableJit) {
nsAutoCString suffix = OriginSuffixForRemoteType(aAttrs, aDisableJit);
if (suffix.IsEmpty()) {
return WEB_REMOTE_TYPE;
}
return WEB_REMOTE_TYPE
"="_ns + suffix;
}
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 isNullPrincipalPrecursor =
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;
isNullPrincipalPrecursor =
true;
}
NavigationIsolationOptions options;
options.mReplaceBrowsingContext = aHasCOOPMismatch;
options.mShouldCrossOriginIsolate =
ShouldCrossOriginIsolate(aChannel, aParentWindow);
// 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.
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;
}
}
#ifdef MOZ_WIDGET_ANDROID
// If we're loading an error page on android, it must complete within the same
// process as the errored page load would complete in due to code expecting
// that behavior. See bug 1673763.
if (aLoadStateLoadType == LOAD_ERROR_PAGE) {
MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
(
"Forcing error page load to complete in the current process"));
behavior = IsolationBehavior::Anywhere;
}
#endif
// 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 && isNullPrincipalPrecursor) {
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) {
options.mRemoteType = MOZ_TRY(
SpecialBehaviorRemoteType(behavior, aCurrentRemoteType, aParentWindow,
aTopBC->OriginAttributesRef()));
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;
}
// NOTE: Currently we always perform process isolation based on the
// siteOrigin, not based on the full origin, even if the
// `Origin-Agent-Cluster` header is provided and we are keying DocGroups
// by-origin.
//
// If in the future we want to start keying based on full origin in some
// cases, the logic below will need to be updated to handle this. Note that
// the UseOriginAgentCluster bit may not have been set on the
// BrowsingContextGroup when this check is being evaluated (as it is set after
// process selection, which may cause a BrowsingContextGroup switch).
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());
}
}
}
bool isJitAllowed = AllowJITForSiteOrigin(siteOriginNoSuffix, aParentWindow);
nsAutoCString originSuffix = OriginSuffixForRemoteType(
resultOrPrecursor->OriginAttributesRef(), !isJitAllowed);
WebProcessType webProcessType = WebProcessType::Web;
if (ShouldIsolateSite(resultOrPrecursor, aTopBC->UseRemoteSubframes())) {
webProcessType = WebProcessType::WebIsolated;
}
// Check if we should be cross-origin isolated.
if (options.mShouldCrossOriginIsolate) {
webProcessType = WebProcessType::WebCoopCoep;
}
switch (webProcessType) {
case WebProcessType::Web:
options.mRemoteType =
SharedWebRemoteType(aTopBC->OriginAttributesRef(), !isJitAllowed);
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;
}
Result<WorkerIsolationOptions, nsresult> IsolationOptionsForWorker(
nsIPrincipal* aPrincipal, WorkerKind aWorkerKind,
const nsACString& aCurrentRemoteType,
bool aUseRemoteSubframes) {
MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
(
"IsolationOptionsForWorker principal:%s, kind:%s, current:%s",
OriginString(aPrincipal).get(), WorkerKindName(aWorkerKind),
PromiseFlatCString(aCurrentRemoteType).get()));
MOZ_ASSERT(NS_IsMainThread());
MOZ_RELEASE_ASSERT(
aWorkerKind == WorkerKindService || aWorkerKind == WorkerKindShared,
"Unexpected remote worker kind");
if (aWorkerKind == WorkerKindService &&
!aPrincipal->GetIsContentPrincipal()) {
MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
(
"Rejecting service worker with non-content principal"));
return Err(NS_ERROR_UNEXPECTED);
}
if (aPrincipal->GetIsExpandedPrincipal()) {
MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
(
"Rejecting remote worker with expanded principal"));
return Err(NS_ERROR_UNEXPECTED);
}
// In some cases, such as for null principals without precursors, we will want
// to load a shared worker in a process based on the current process. This is
// not done for service workers - process selection for those should function
// the same in all processes.
//
// We only allow the current remote type to be used if it is not a COOP+COEP
// remote type, in order to avoid loading a shared worker in one of these
// processes. Currently process selection for workers occurs before response
// headers are available, so we will never select to load a shared worker in a
// COOP+COEP content process.
nsCString preferredRemoteType =
SharedWebRemoteType(aPrincipal->OriginAttributesRef());
if (aWorkerKind == WorkerKind::WorkerKindShared &&
!StringBeginsWith(aCurrentRemoteType,
WITH_COOP_COEP_REMOTE_TYPE_PREFIX)) {
preferredRemoteType = aCurrentRemoteType;
}
WorkerIsolationOptions options;
// 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 isNullPrincipalPrecursor =
false;
nsCOMPtr<nsIPrincipal> resultOrPrecursor(aPrincipal);
if (nsCOMPtr<nsIPrincipal> precursor =
resultOrPrecursor->GetPrecursorPrincipal()) {
MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
(
"using null principal precursor origin %s",
OriginString(precursor).get()));
resultOrPrecursor = precursor;
isNullPrincipalPrecursor =
true;
}
IsolationBehavior behavior = IsolationBehavior::WebContent;
if (resultOrPrecursor->GetIsContentPrincipal()) {
nsCOMPtr<nsIURI> uri = resultOrPrecursor->GetURI();
behavior = IsolationBehaviorForURI(uri,
/* aIsSubframe */ false,
/* aForChannelCreationURI */ false);
}
else if (resultOrPrecursor->IsSystemPrincipal()) {
MOZ_ASSERT(aWorkerKind == WorkerKindShared);
// Only allow system principal shared workers to load within the parent
// process, and only if that process is responsible for the load.
if (preferredRemoteType == NOT_REMOTE_TYPE) {
MOZ_LOG(gProcessIsolationLog, LogLevel::Debug,
(
"Loading system principal shared worker in parent process"));
behavior = IsolationBehavior::Parent;
}
else {
MOZ_LOG(
gProcessIsolationLog, LogLevel::Warning,
(
"Cannot load system-principal shared worker in content process"));
return Err(NS_ERROR_UNEXPECTED);
}
}
else {
MOZ_ASSERT(resultOrPrecursor->GetIsNullPrincipal());
MOZ_ASSERT(aWorkerKind == WorkerKindShared);
if (preferredRemoteType == NOT_REMOTE_TYPE) {
MOZ_LOG(gProcessIsolationLog, LogLevel::Debug,
(
"Ensuring precursorless null principal shared worker loads in a "
"content process"));
behavior = IsolationBehavior::ForceWebRemoteType;
}
else {
MOZ_LOG(gProcessIsolationLog, LogLevel::Debug,
(
"Loading precursorless null principal shared worker within "
"current remotetype: (%s)",
preferredRemoteType.get()));
behavior = IsolationBehavior::Anywhere;
}
}
if (behavior == IsolationBehavior::Parent && isNullPrincipalPrecursor) {
MOZ_LOG(gProcessIsolationLog, LogLevel::Debug,
(
"Ensuring sandboxed null-principal shared worker doesn't load in "
"the parent process"));
behavior = IsolationBehavior::ForceWebRemoteType;
}
if (behavior != IsolationBehavior::WebContent) {
options.mRemoteType = MOZ_TRY(
SpecialBehaviorRemoteType(behavior, preferredRemoteType, nullptr,
resultOrPrecursor->OriginAttributesRef()));
MOZ_LOG(
gProcessIsolationLog, LogLevel::Debug,
(
"Selecting specific %s worker remote type (%s) due to a special case "
"isolation behavior %s",
WorkerKindName(aWorkerKind), options.mRemoteType.get(),
IsolationBehaviorName(behavior)));
return options;
}
nsAutoCString siteOriginNoSuffix;
MOZ_TRY(resultOrPrecursor->GetSiteOriginNoSuffix(siteOriginNoSuffix));
bool isJitAllowed = AllowJITForSiteOrigin(siteOriginNoSuffix, nullptr);
// If we should be isolating this site, we can determine the correct fission
// remote type from the principal's site-origin.
if (ShouldIsolateSite(resultOrPrecursor, aUseRemoteSubframes)) {
nsAutoCString originSuffix = OriginSuffixForRemoteType(
resultOrPrecursor->OriginAttributesRef(), !isJitAllowed);
nsCString prefix = aWorkerKind == WorkerKindService
? SERVICEWORKER_REMOTE_TYPE
: FISSION_WEB_REMOTE_TYPE;
options.mRemoteType = prefix +
"="_ns + siteOriginNoSuffix + originSuffix;
MOZ_LOG(gProcessIsolationLog, LogLevel::Debug,
(
"Isolating web content %s worker in remote type (%s)",
WorkerKindName(aWorkerKind), options.mRemoteType.get()));
}
else {
options.mRemoteType = SharedWebRemoteType(
resultOrPrecursor->OriginAttributesRef(), !isJitAllowed);
MOZ_LOG(gProcessIsolationLog, LogLevel::Debug,
(
"Loading web content %s worker in shared web remote type",
WorkerKindName(aWorkerKind)));
}
return options;
}
// External protocols never directly load web content, so they aren't relevant
// for actual navigation or worker isolation options, but are potentially
// relevant when predicting a remote type for a new tab.
//
// This function is called to check if we have a default web-app handler for a
// scheme in order to perform process prediction based on the handler's URI.
//
// This sort of information is unfortunately buried pretty deep within the
// external protocol handler, so is unfortunately verbose to extract and handle.
static already_AddRefed<nsIURI> MaybeResolveWebAppHandler(nsIURI* aURI) {
MOZ_ASSERT(XRE_IsParentProcess());
nsCOMPtr<nsIIOService> ioSvc = do_GetIOService();
NS_ENSURE_TRUE(ioSvc, nullptr);
nsCOMPtr<nsIExternalProtocolService> extProtService =
do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID);
NS_ENSURE_TRUE(extProtService, nullptr);
// NOTE: We intentionally do not unwrap nested URIs. As external protocols do
// not return content, they are not supported by protocols like view-source,
// and should be treated as normal content.
nsAutoCString scheme;
nsresult rv = aURI->GetScheme(scheme);
NS_ENSURE_SUCCESS(rv, nullptr);
// First, we check if the scheme is internally handled by Gecko. Only schemes
// which are handled externally could be handled by a web handler app.
nsCOMPtr<nsIProtocolHandler> handler;
rv = ioSvc->GetProtocolHandler(scheme.get(), getter_AddRefs(handler));
NS_ENSURE_SUCCESS(rv, nullptr);
nsCOMPtr<nsIExternalProtocolHandler> extHandler = do_QueryInterface(handler);
if (!extHandler) {
return nullptr;
}
// Now that we know the scheme is being handled by the external protocol
// handler, we can get any handler information for the scheme. This will
// unfortunately query the OS, but that appears to be unavoidable.
nsCOMPtr<nsIHandlerInfo> handlerInfo;
rv = extProtService->GetProtocolHandlerInfo(scheme,
getter_AddRefs(handlerInfo));
if (NS_FAILED(rv) || !handlerInfo) {
return nullptr;
}
// If the user is going to be prompted, we don't pre-emptively predict the web
// handler's process, as a different handler could be used. ProcessIsolation
// will select the correct process during navigation.
bool alwaysAskBeforeHandling =
false;
rv = handlerInfo->GetAlwaysAskBeforeHandling(&alwaysAskBeforeHandling);
if (NS_FAILED(rv) || alwaysAskBeforeHandling) {
return nullptr;
}
nsCOMPtr<nsIHandlerApp> handlerApp;
rv = handlerInfo->GetPreferredApplicationHandler(getter_AddRefs(handlerApp));
if (NS_FAILED(rv) || !handlerApp) {
return nullptr;
}
nsCOMPtr<nsIWebHandlerApp> webHandlerApp = do_QueryInterface(handlerApp);
if (!webHandlerApp) {
return nullptr;
}
// We've located the web handler app, and can now perform substitution on the
// template to build the final URI to use for process selection.
nsAutoCString uriTemplate;
rv = webHandlerApp->GetUriTemplate(uriTemplate);
NS_ENSURE_SUCCESS(rv, nullptr);
nsAutoCString spec;
rv = aURI->GetSpec(spec);
NS_ENSURE_SUCCESS(rv, nullptr);
nsAutoCString escapedSpec;
bool success = NS_Escape(spec, escapedSpec, url_XAlphas);
NS_ENSURE_TRUE(success, nullptr);
uriTemplate.ReplaceSubstring(
"%s"_ns, escapedSpec);
nsCOMPtr<nsIURI> newURI;
rv = NS_NewURI(getter_AddRefs(newURI), uriTemplate);
NS_ENSURE_SUCCESS(rv, nullptr);
return newURI.forget();
}
Result<nsCString, nsresult> PredictRemoteTypeForURI(
nsIURI* aURI,
const OriginAttributes& aOriginAttributes,
const nsACString& aPreferredRemoteType,
bool aUseRemoteSubframes) {
MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
(
"PredictRemoteTypeForURI uri:%s, preferred:%s, oa:%s, "
"useRemoteSubframes:%d",
aURI->GetSpecOrDefault().get(),
PromiseFlatCString(aPreferredRemoteType).get(),
OriginSuffixForRemoteType(aOriginAttributes,
false).get(),
aUseRemoteSubframes));
IsolationBehavior behavior = IsolationBehaviorForURI(
aURI,
/* aIsSubframe */ false, /* aForChannelCreationURI */ true);
MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
(
"Base Isolation Behavior: %s", IsolationBehaviorName(behavior)));
// External protocol handlers are generally not relevant for navigation or
// worker loads, but are relevant pre-load when predicting a remote type.
// Attempt to resolve the URI for web-app protocol handlers before proceeding.
nsCOMPtr<nsIURI> uri = aURI;
if (nsCOMPtr<nsIURI> webAppHandlerURI = MaybeResolveWebAppHandler(uri)) {
uri = webAppHandlerURI;
behavior = IsolationBehaviorForURI(uri,
/* aIsSubframe */ false,
/* aForChannelCreationURI */ true);
MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
(
"Resolved WebAppHandler uri:%s isolationBehavior:%s",
uri->GetSpecOrDefault().get(), IsolationBehaviorName(behavior)));
}
// Allow javascript: and about:{blank,srcdoc} to load anywhere, as we don't
// have precursor principal information when predicting remote types.
if (uri->SchemeIs(
"javascript") ||
(uri->SchemeIs(
"about") && NS_IsContentAccessibleAboutURI(uri))) {
behavior = IsolationBehavior::Anywhere;
}
// Note that this may resolve a non-content,non-null principal for a blob URI.
nsCOMPtr<nsIPrincipal> principal =
BasePrincipal::CreateContentPrincipal(uri, aOriginAttributes);
if (behavior == IsolationBehavior::AboutReader) {
if (nsCOMPtr<nsIPrincipal> readerURIPrincipal =
GetAboutReaderURLPrincipal(uri, aOriginAttributes)) {
MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
(
"using about:reader's url origin %s",
OriginString(readerURIPrincipal).get()));
principal = readerURIPrincipal;
}
behavior = IsolationBehavior::WebContent;
}
// Perform a second pass with GetIsolationBehavior now using the principal
// URI, and after having resolved things like about:reader URIs.
if (behavior == IsolationBehavior::WebContent) {
if (principal->IsSystemPrincipal()) {
// This should only be possible for a blob URI at this point. Force it to
// a web content process.
MOZ_ASSERT(uri->SchemeIs(
"blob"),
"unexpected non-blob URI with system principal");
behavior = IsolationBehavior::ForceWebRemoteType;
}
else if (nsCOMPtr<nsIURI> principalURI = principal->GetURI()) {
behavior = IsolationBehaviorForURI(principalURI,
/* aIsSubframe */ false,
/* aForChannelCreationURI */ false);
}
}
MOZ_LOG(gProcessIsolationLog, LogLevel::Debug,
(
"Predicting IsolationBehavior %s for %s (principal %s)",
IsolationBehaviorName(behavior), uri->GetSpecOrDefault().get(),
OriginString(principal).get()));
// If we have a special behaviour RemoteType, return it.
if (behavior != IsolationBehavior::WebContent) {
nsCString remoteType = MOZ_TRY(SpecialBehaviorRemoteType(
behavior, aPreferredRemoteType, nullptr, aOriginAttributes));
MOZ_LOG(gProcessIsolationLog, LogLevel::Debug,
(
"Predicting specific remote type (%s) due to a special case "
"isolation behavior %s",
remoteType.get(), IsolationBehaviorName(behavior)));
return remoteType;
}
// We're dealing with a web remote type, get the site origin and origin suffix
// to build a remoteType from.
nsAutoCString siteOriginNoSuffix;
MOZ_TRY(principal->GetSiteOriginNoSuffix(siteOriginNoSuffix));
bool isJitAllowed = AllowJITForSiteOrigin(siteOriginNoSuffix, nullptr);
nsAutoCString originSuffix = OriginSuffixForRemoteType(
principal->OriginAttributesRef(), !isJitAllowed);
// The only situation we'll return a coop+coep remote type is if the preferred
// remote type would perfectly match. Check if that is the case.
if (StringBeginsWith(aPreferredRemoteType,
WITH_COOP_COEP_REMOTE_TYPE_PREFIX)) {
nsCString coopCoepRemoteType =
WITH_COOP_COEP_REMOTE_TYPE
"="_ns + siteOriginNoSuffix + originSuffix;
if (coopCoepRemoteType == aPreferredRemoteType) {
MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
(
"Predicting preferred COOP+COEP remote type (%s) due to "
"compatible site-origin %s",
coopCoepRemoteType.get(), OriginString(principal).get()));
return coopCoepRemoteType;
}
}
nsCString remoteType;
if (ShouldIsolateSite(principal, aUseRemoteSubframes)) {
remoteType =
FISSION_WEB_REMOTE_TYPE
"="_ns + siteOriginNoSuffix + originSuffix;
}
else {
remoteType = SharedWebRemoteType(aOriginAttributes, !isJitAllowed);
}
MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
(
"Predicting web remote type (%s)", remoteType.get()));
return remoteType;
}
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'",
PromiseFlatCString(aPermissionType).get(), 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);
(
void)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;
}
bool ValidatePrincipalCouldPotentiallyBeLoadedBy(
nsIPrincipal* aPrincipal,
const nsACString& aRemoteType,
const EnumSet<ValidatePrincipalOptions>& aOptions) {
// Don't bother validating principals from the parent process.
if (aRemoteType == NOT_REMOTE_TYPE) {
return true;
}
// If there is no principal, only allow it if AllowNullPtr is specified.
if (!aPrincipal) {
return aOptions.contains(ValidatePrincipalOptions::AllowNullPtr);
}
// We currently do not track relationships between specific null principals
// and content processes, so we can not validate much here.
if (aPrincipal->GetIsNullPrincipal()) {
return true;
}
// If we have a system principal, only allow it if AllowSystem is passed.
if (aPrincipal->IsSystemPrincipal()) {
return aOptions.contains(ValidatePrincipalOptions::AllowSystem);
}
// Performing checks against the remote type requires the IOService and
// ThirdPartyService to be available, check we're not late in shutdown.
if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownFinal)) {
return true;
}
// Only allow expanded principals if AllowExpanded is passed. Each
// sub-principal will be validated independently.
if (aPrincipal->GetIsExpandedPrincipal()) {
if (!aOptions.contains(ValidatePrincipalOptions::AllowExpanded)) {
return false;
}
// FIXME: There are more constraints on expanded principals in-practice,
// such as the structure of extension expanded principals. This may need
// to be investigated more in the future.
nsCOMPtr<nsIExpandedPrincipal> expandedPrincipal =
do_QueryInterface(aPrincipal);
const auto& allowList = expandedPrincipal->AllowList();
for (
const auto& innerPrincipal : allowList) {
if (!ValidatePrincipalCouldPotentiallyBeLoadedBy(innerPrincipal,
aRemoteType, aOptions)) {
return false;
}
}
return true;
}
// At this point we know we're working with a content principal.
MOZ_ASSERT(aPrincipal->GetIsContentPrincipal());
nsAutoCString originNoSuffix;
MOZ_ALWAYS_SUCCEEDS(aPrincipal->GetOriginNoSuffix(originNoSuffix));
// NOTE: We intentionally do scheme checks against the origin here, rather
// than the principal's URI. This is because nested URIs like `view-source:`
// are preserved in the principal, but do not impact the origin.
nsAutoCString originScheme;
MOZ_ALWAYS_SUCCEEDS(net_ExtractURLScheme(originNoSuffix, originScheme));
// We can load a `resource://` URI in any process. This usually comes up due
// to pdf.js and the JSON viewer. See bug 1686200.
if (originScheme ==
"resource"_ns) {
return true;
}
// A URI with a file:// scheme can never load in a non-file content process
// due to sandboxing.
if (originScheme ==
"file"_ns) {
// If we don't support a separate 'file' process, then we can return here.
if (!StaticPrefs::browser_tabs_remote_separateFileUriProcess()) {
return true;
}
return aRemoteType == FILE_REMOTE_TYPE;
}
if (originScheme ==
"about"_ns) {
nsCOMPtr<nsIURI> aboutURI;
if (NS_FAILED(NS_NewURI(getter_AddRefs(aboutURI), originNoSuffix))) {
MOZ_DIAGNOSTIC_ASSERT(
false,
"The originNoSuffix isn't a valid URI?");
return false;
}
MOZ_ASSERT(aboutURI->SchemeIs(
"about"));
// We cannot validate about module flags off-main-thread, as about modules
// are not threadsafe, and can be implemented in JS.
if (!NS_IsMainThread()) {
return true;
}
// NOTE: The logic for about URIs is somewhat complex, so we lean on
// IsolationBehaviorForURI to ensure it matches.
switch (IsolationBehaviorForURI(aboutURI,
/* aIsSubframe */ false,
/* aForChannelCreationURI */ true)) {
case IsolationBehavior::Parent:
return false;
case IsolationBehavior::Anywhere:
return true;
case IsolationBehavior::AboutReader:
// Allow about:reader to load anywhere, as it is process-allocated based
// on the content it displays, and which content is being displayed is
// unfortunately not part of the principal.
return true;
case IsolationBehavior::Extension:
return aRemoteType == EXTENSION_REMOTE_TYPE;
case IsolationBehavior::PrivilegedAbout:
return aRemoteType == PRIVILEGEDABOUT_REMOTE_TYPE;
case IsolationBehavior::ForceWebRemoteType:
return RemoteTypePrefix(aRemoteType) == WEB_REMOTE_TYPE;
case IsolationBehavior::WebContent:
case IsolationBehavior::Error:
// NOTE: We can encounter races around about: pages being unregistered.
// To avoid false positives, we fail open if no about module is present.
return true;
default:
MOZ_CRASH(
"Unexpected IsolationBehaviorForURI for about: URI");
return false;
}
}
// Web content can contain extension content frames, so any content process
// may send us an extension's principal.
// NOTE: We don't check AddonPolicy here, as that can disappear if the add-on
// is disabled or uninstalled. As this is a lax check, looking at the scheme
// should be sufficient.
if (originScheme ==
"moz-extension"_ns) {
return true;
}
// If the remote type doesn't have an origin suffix, we can do no further
// principal validation with it.
int32_t equalIdx = aRemoteType.FindChar(
'=');
if (equalIdx == kNotFound) {
return true;
}
// Split out the remote type prefix and the origin suffix.
nsDependentCSubstring typePrefix(aRemoteType,
0, equalIdx);
nsDependentCSubstring typeOrigin(aRemoteType, equalIdx +
1);
// Only validate webIsolated and webServiceWorker remote types for now. This
// should be expanded in the future.
if (typePrefix != FISSION_WEB_REMOTE_TYPE &&
typePrefix != SERVICEWORKER_REMOTE_TYPE) {
return true;
}
// Trim any OriginAttributes from the origin, as those will not be validated.
int32_t suffixIdx = typeOrigin.RFindChar(
'^');
nsDependentCSubstring typeOriginNoSuffix(typeOrigin,
0, suffixIdx);
// If the origin perfectly matches, we can skip computing the site origin.
if (typeOriginNoSuffix == originNoSuffix) {
return true;
}
// NOTE: Currently every webIsolated remote type is site-origin keyed, meaning
// we can unconditionally compare site origins. If this changes in the future,
// this logic will need to be updated to reflect that.
nsAutoCString siteOriginNoSuffix;
if (NS_FAILED(aPrincipal->GetSiteOriginNoSuffix(siteOriginNoSuffix))) {
MOZ_ASSERT_UNREACHABLE(
"Failed when not late in shutdown?");
return false;
}
return siteOriginNoSuffix == typeOriginNoSuffix;
}
}
// namespace mozilla::dom