Replace bare remote type strings in C++ process-selection plumbing with a RemoteType type which stores the parsed kind, isolation URI, and process selection attributes directly. This preserves the existing serialized string form for IPC and JS-facing APIs, while making native callers use explicit predicates and structured fields instead of manually parsing remote type prefixes and suffixes. No JS-exposed API for parsing or otherwise interpreting remote types are currently exposed in this patch. My current expectation is that this will likely look like a `nsIRemoteType` interface which wraps this `RemoteType` value type, exposing helpful getters for JS callers. Differential Revision: https://phabricator.services.mozilla.com/D310442
1649 lines
64 KiB
C++
1649 lines
64 KiB
C++
/* 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(nsIURI* aSiteOriginURI,
|
|
WindowGlobalParent* aParentWindow) {
|
|
nsresult rv;
|
|
|
|
nsCOMPtr<nsIEnterprisePolicies> policyService =
|
|
do_GetService("@mozilla.org/enterprisepolicies;1");
|
|
if (!policyService) {
|
|
return true;
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> topSiteOriginURI = aSiteOriginURI;
|
|
|
|
// If this is a subframe then use the principal of the top window.
|
|
if (aParentWindow) {
|
|
nsAutoCString topSiteOriginNoSuffix;
|
|
rv = aParentWindow->TopWindowContext()
|
|
->DocumentPrincipal()
|
|
->GetSiteOriginNoSuffix(topSiteOriginNoSuffix);
|
|
NS_ENSURE_SUCCESS(rv, true);
|
|
|
|
rv = NS_NewURI(getter_AddRefs(topSiteOriginURI), topSiteOriginNoSuffix);
|
|
NS_ENSURE_SUCCESS(rv, true);
|
|
}
|
|
|
|
bool isJitAllowed = true;
|
|
if (NS_FAILED(policyService->IsAllowedForURI("jit"_ns, topSiteOriginURI,
|
|
&isJitAllowed))) {
|
|
return true;
|
|
}
|
|
|
|
if (!isJitAllowed) {
|
|
MOZ_LOG(gProcessIsolationLog, LogLevel::Debug,
|
|
("JIT is disabled for site %s by enterprise policy",
|
|
topSiteOriginURI->GetSpecOrDefault().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.
|
|
*
|
|
* `aIsWorker` selects process isolation for a remote worker rather than for a
|
|
* document; see the file:// URI allowlist handling below for why the two
|
|
* differ.
|
|
*/
|
|
static IsolationBehavior IsolationBehaviorForURI(nsIURI* aURI, bool aIsSubframe,
|
|
bool aForChannelCreationURI,
|
|
bool aIsWorker) {
|
|
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,
|
|
aIsWorker);
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the domain is allowlisted to allow it to use file:// URIs, then we have
|
|
// to run it in a file content process, in case it uses file:// sub-resources.
|
|
//
|
|
// This only applies to documents. A worker has no sub-resources of its own to
|
|
// render, and forcing it into the file process would additionally mean
|
|
// rejecting it outright whenever the requesting process isn't the file
|
|
// process (see ValidateBehaviorForWorker). Workers were likewise excluded
|
|
// from this rule back when it lived in E10SUtils; see bug 2064648.
|
|
if (!aIsWorker) {
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Helper method for logging origin attributes as a string.
|
|
*/
|
|
static nsAutoCString OriginAttributesString(const OriginAttributes& aAttrs) {
|
|
nsAutoCString originSuffix;
|
|
aAttrs.CreateSuffix(originSuffix);
|
|
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<RemoteType, nsresult> SpecialBehaviorRemoteType(
|
|
IsolationBehavior aBehavior, const RemoteType& aCurrentRemoteType,
|
|
WindowGlobalParent* aParentWindow, const OriginAttributes& aAttrs) {
|
|
switch (aBehavior) {
|
|
case IsolationBehavior::ForceWebRemoteType:
|
|
return {RemoteType::SharedWeb(aAttrs)};
|
|
case IsolationBehavior::PrivilegedAbout:
|
|
// The privileged about: content process cannot be disabled, as it
|
|
// causes various actors to break.
|
|
return {RemoteType(RemoteType::Kind::PrivilegedAbout)};
|
|
case IsolationBehavior::Extension:
|
|
if (ExtensionPolicyService::GetSingleton().UseRemoteExtensions()) {
|
|
return {RemoteType(RemoteType::Kind::Extension)};
|
|
}
|
|
return {RemoteType(RemoteType::Kind::NotRemote)};
|
|
case IsolationBehavior::File:
|
|
if (StaticPrefs::browser_tabs_remote_separateFileUriProcess()) {
|
|
return {RemoteType(RemoteType::Kind::File)};
|
|
}
|
|
return {RemoteType::SharedWeb(aAttrs)};
|
|
case IsolationBehavior::PrivilegedMozilla:
|
|
return {RemoteType(RemoteType::Kind::PrivilegedMozilla)};
|
|
case IsolationBehavior::Parent:
|
|
return {RemoteType(RemoteType::Kind::NotRemote)};
|
|
case IsolationBehavior::Anywhere:
|
|
return {aCurrentRemoteType};
|
|
case IsolationBehavior::Inherit:
|
|
MOZ_DIAGNOSTIC_ASSERT(aParentWindow);
|
|
return {aParentWindow->GetRemoteType()};
|
|
|
|
case IsolationBehavior::Error:
|
|
return Err(NS_ERROR_UNEXPECTED);
|
|
|
|
default:
|
|
MOZ_ASSERT_UNREACHABLE();
|
|
return Err(NS_ERROR_UNEXPECTED);
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
Result<NavigationIsolationOptions, nsresult> IsolationOptionsForNavigation(
|
|
CanonicalBrowsingContext* aTopBC, WindowGlobalParent* aParentWindow,
|
|
nsIURI* aChannelCreationURI, nsIChannel* aChannel,
|
|
const RemoteType& aCurrentRemoteType, bool aHasCOOPMismatch,
|
|
bool aForNewTab, uint32_t aLoadStateLoadType,
|
|
const Maybe<uint64_t>& aChannelId,
|
|
const Maybe<RemoteType>& 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->Stringify().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,
|
|
/* aIsWorker */ false);
|
|
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.IsNotRemote() &&
|
|
(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,
|
|
/* aIsWorker */ 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.IsExtension() &&
|
|
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()));
|
|
|
|
// The channel carries the container the load has been switched into, which
|
|
// the toplevel BC doesn't have: the load is going to be retargeted into a new
|
|
// tab created for that container, so select the process for it.
|
|
OriginAttributes originAttributes = aTopBC->OriginAttributesRef();
|
|
if (aForNewTab && !aParentWindow &&
|
|
resultOrPrecursor->GetIsContentPrincipal()) {
|
|
originAttributes.mUserContextId =
|
|
resultOrPrecursor->OriginAttributesRef().mUserContextId;
|
|
}
|
|
|
|
// 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.IsNotRemote() &&
|
|
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, originAttributes));
|
|
|
|
if (options.mRemoteType != aCurrentRemoteType &&
|
|
(options.mRemoteType.IsNotRemote() ||
|
|
aCurrentRemoteType.IsNotRemote())) {
|
|
options.mReplaceBrowsingContext = true;
|
|
}
|
|
|
|
MOZ_LOG(
|
|
gProcessIsolationLog, LogLevel::Debug,
|
|
("Selecting specific remote type (%s) due to a special case isolation "
|
|
"behavior %s",
|
|
options.mRemoteType.Stringify().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.IsNotRemote()) {
|
|
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));
|
|
|
|
nsCOMPtr<nsIURI> siteOriginURI;
|
|
MOZ_TRY(NS_NewURI(getter_AddRefs(siteOriginURI), 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().IsNotRemote() &&
|
|
principalIsSameSite(wgp->DocumentPrincipal())) {
|
|
MOZ_LOG(gProcessIsolationLog, LogLevel::Debug,
|
|
("Found existing frame with matching principal "
|
|
"(remoteType:(%s), origin:%s)",
|
|
wgp->GetRemoteType().Stringify().get(),
|
|
OriginString(wgp->DocumentPrincipal()).get()));
|
|
options.mRemoteType = wgp->GetRemoteType();
|
|
return options;
|
|
}
|
|
|
|
// Also enumerate over this WindowContexts' subframes.
|
|
contexts.AppendElements(wc->Children());
|
|
}
|
|
}
|
|
}
|
|
|
|
options.mRemoteType = RemoteType::SharedWeb(originAttributes);
|
|
|
|
if (!AllowJITForSiteOrigin(siteOriginURI, aParentWindow)) {
|
|
options.mRemoteType = options.mRemoteType.WithDisableJit(true);
|
|
}
|
|
|
|
if (options.mShouldCrossOriginIsolate ||
|
|
ShouldIsolateSite(resultOrPrecursor, aTopBC->UseRemoteSubframes())) {
|
|
options.mRemoteType = options.mRemoteType.WithSiteOrigin(
|
|
siteOriginNoSuffix, options.mShouldCrossOriginIsolate
|
|
? RemoteType::Kind::WebCoopCoep
|
|
: RemoteType::Kind::WebContent);
|
|
}
|
|
|
|
return options;
|
|
}
|
|
|
|
static bool ValidateBehaviorForWorker(IsolationBehavior aBehavior,
|
|
const RemoteType& aCurrentRemoteType) {
|
|
if (aCurrentRemoteType.IsNotRemote()) {
|
|
return true;
|
|
}
|
|
|
|
switch (aBehavior) {
|
|
case IsolationBehavior::Parent:
|
|
// Can't load in a parent process from any other process.
|
|
return false;
|
|
|
|
case IsolationBehavior::AboutReader:
|
|
case IsolationBehavior::Inherit:
|
|
// Not relevant for Workers.
|
|
return false;
|
|
|
|
case IsolationBehavior::WebContent:
|
|
case IsolationBehavior::ForceWebRemoteType:
|
|
case IsolationBehavior::Anywhere:
|
|
return true;
|
|
|
|
case IsolationBehavior::Extension:
|
|
// Extension iframes could be loaded in any process.
|
|
return true;
|
|
|
|
case IsolationBehavior::PrivilegedAbout:
|
|
return aCurrentRemoteType.IsPrivilegedAbout();
|
|
|
|
case IsolationBehavior::File:
|
|
return !StaticPrefs::browser_tabs_remote_separateFileUriProcess() ||
|
|
aCurrentRemoteType.IsFile();
|
|
|
|
case IsolationBehavior::PrivilegedMozilla:
|
|
return aCurrentRemoteType.IsPrivilegedMozilla();
|
|
|
|
case IsolationBehavior::Error:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
Result<WorkerIsolationOptions, nsresult> IsolationOptionsForWorker(
|
|
nsIPrincipal* aPrincipal, WorkerKind aWorkerKind,
|
|
const RemoteType& aCurrentRemoteType, bool aUseRemoteSubframes) {
|
|
MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
|
|
("IsolationOptionsForWorker principal:%s, kind:%s, current:%s",
|
|
OriginString(aPrincipal).get(), WorkerKindName(aWorkerKind),
|
|
aCurrentRemoteType.Stringify().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.
|
|
RemoteType preferredRemoteType =
|
|
RemoteType::SharedWeb(aPrincipal->OriginAttributesRef());
|
|
if (aWorkerKind == WorkerKind::WorkerKindShared &&
|
|
!aCurrentRemoteType.IsWebCoopCoep()) {
|
|
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,
|
|
/* aIsWorker */ true);
|
|
} 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.IsNotRemote()) {
|
|
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.IsNotRemote()) {
|
|
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.Stringify().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 (!ValidateBehaviorForWorker(behavior, aCurrentRemoteType)) {
|
|
MOZ_LOG(
|
|
gProcessIsolationLog, LogLevel::Warning,
|
|
("Rejecting invalid worker isolation behavior %s for remote type %s",
|
|
IsolationBehaviorName(behavior),
|
|
aCurrentRemoteType.Stringify().get()));
|
|
return Err(NS_ERROR_FAILURE);
|
|
}
|
|
|
|
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.Stringify().get(),
|
|
IsolationBehaviorName(behavior)));
|
|
return options;
|
|
}
|
|
|
|
nsAutoCString siteOriginNoSuffix;
|
|
MOZ_TRY(resultOrPrecursor->GetSiteOriginNoSuffix(siteOriginNoSuffix));
|
|
|
|
nsCOMPtr<nsIURI> siteOriginURI;
|
|
MOZ_TRY(NS_NewURI(getter_AddRefs(siteOriginURI), siteOriginNoSuffix));
|
|
|
|
options.mRemoteType =
|
|
RemoteType::SharedWeb(resultOrPrecursor->OriginAttributesRef());
|
|
|
|
if (!AllowJITForSiteOrigin(siteOriginURI, nullptr)) {
|
|
options.mRemoteType = options.mRemoteType.WithDisableJit(true);
|
|
}
|
|
|
|
// 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)) {
|
|
options.mRemoteType = options.mRemoteType.WithSiteOrigin(
|
|
siteOriginNoSuffix, aWorkerKind == WorkerKindService
|
|
? RemoteType::Kind::WebServiceWorker
|
|
: RemoteType::Kind::WebContent);
|
|
}
|
|
|
|
MOZ_LOG(gProcessIsolationLog, LogLevel::Debug,
|
|
("Loading web content %s worker in remote type (%s)",
|
|
WorkerKindName(aWorkerKind), options.mRemoteType.Stringify().get()));
|
|
|
|
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<RemoteType, nsresult> PredictRemoteTypeForURI(
|
|
nsIURI* aURI, const OriginAttributes& aOriginAttributes,
|
|
const RemoteType& aPreferredRemoteType, bool aUseRemoteSubframes) {
|
|
MOZ_LOG(
|
|
gProcessIsolationLog, LogLevel::Verbose,
|
|
("PredictRemoteTypeForURI uri:%s, preferred:%s, oa:%s, "
|
|
"useRemoteSubframes:%d",
|
|
aURI->GetSpecOrDefault().get(), aPreferredRemoteType.Stringify().get(),
|
|
OriginAttributesString(aOriginAttributes).get(), aUseRemoteSubframes));
|
|
|
|
IsolationBehavior behavior = IsolationBehaviorForURI(
|
|
aURI, /* aIsSubframe */ false, /* aForChannelCreationURI */ true,
|
|
/* aIsWorker */ false);
|
|
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,
|
|
/* aIsWorker */ false);
|
|
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,
|
|
/* aIsWorker */ 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) {
|
|
RemoteType 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.Stringify().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));
|
|
|
|
nsCOMPtr<nsIURI> siteOriginURI;
|
|
MOZ_TRY(NS_NewURI(getter_AddRefs(siteOriginURI), siteOriginNoSuffix));
|
|
|
|
RemoteType remoteType = RemoteType::SharedWeb(aOriginAttributes);
|
|
|
|
if (!AllowJITForSiteOrigin(siteOriginURI, nullptr)) {
|
|
remoteType = remoteType.WithDisableJit(true);
|
|
}
|
|
|
|
// 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 (aPreferredRemoteType.IsWebCoopCoep()) {
|
|
RemoteType coopCoepRemoteType = remoteType.WithSiteOrigin(
|
|
siteOriginNoSuffix, RemoteType::Kind::WebCoopCoep);
|
|
if (coopCoepRemoteType == aPreferredRemoteType) {
|
|
MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
|
|
("Predicting preferred COOP+COEP remote type (%s) due to "
|
|
"compatible site-origin %s",
|
|
coopCoepRemoteType.Stringify().get(),
|
|
OriginString(principal).get()));
|
|
return coopCoepRemoteType;
|
|
}
|
|
}
|
|
|
|
if (ShouldIsolateSite(principal, aUseRemoteSubframes)) {
|
|
remoteType = remoteType.WithSiteOrigin(siteOriginNoSuffix);
|
|
}
|
|
|
|
MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
|
|
("Predicting web remote type (%s)", remoteType.Stringify().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 RemoteType& aRemoteType,
|
|
const EnumSet<ValidatePrincipalOptions>& aOptions,
|
|
FunctionRef<bool(nsIPrincipal*)> aIsPrincipalLoaded) {
|
|
#ifdef DEBUG
|
|
if (!aIsPrincipalLoaded) {
|
|
MOZ_ASSERT(
|
|
aOptions.contains(ValidatePrincipalOptions::AllowNotLoadedOrigin),
|
|
"`AllowNotLoadedOrigin` is required if calling "
|
|
"ValidatePrincipalCouldPotentiallyBeLoadedBy directly");
|
|
MOZ_ASSERT(
|
|
!aOptions.contains(ValidatePrincipalOptions::AllowSystemIfLoaded),
|
|
"`AllowSystemIfLoaded` is invalid if calling "
|
|
"ValidatePrincipalCouldPotentiallyBeLoadedBy directly");
|
|
}
|
|
#endif
|
|
|
|
// Don't bother validating principals from the parent process.
|
|
if (aRemoteType.IsNotRemote()) {
|
|
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 reliably 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 when explicitly requested.
|
|
if (aPrincipal->IsSystemPrincipal()) {
|
|
return aOptions.contains(ValidatePrincipalOptions::AlwaysAllowSystem) ||
|
|
(aOptions.contains(ValidatePrincipalOptions::AllowSystemIfLoaded) &&
|
|
aIsPrincipalLoaded(aPrincipal));
|
|
}
|
|
|
|
// 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, aIsPrincipalLoaded)) {
|
|
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 never load a chrome:// principal within a content process.
|
|
if (originScheme == "chrome"_ns) {
|
|
return false;
|
|
}
|
|
|
|
// We can load a `resource://` URI in any process without the parent process
|
|
// being involved. This usually comes up due to pdf.js and the JSON viewer.
|
|
// See bug 1686200.
|
|
if (originScheme == "resource"_ns) {
|
|
return true;
|
|
}
|
|
|
|
// Web content can contain extension content frames and contain extension
|
|
// content scripts, 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;
|
|
}
|
|
|
|
// All other content principal schemes are always loaded via. the parent
|
|
// process, so we can early-return if `aIsPrincipalLoaded` returns false.
|
|
if (!aOptions.contains(ValidatePrincipalOptions::AllowNotLoadedOrigin) &&
|
|
!aIsPrincipalLoaded(aPrincipal)) {
|
|
return false;
|
|
}
|
|
|
|
// 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.IsFile();
|
|
}
|
|
|
|
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,
|
|
/* aIsWorker */ false)) {
|
|
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.IsExtension();
|
|
case IsolationBehavior::PrivilegedAbout:
|
|
return aRemoteType.IsPrivilegedAbout();
|
|
case IsolationBehavior::ForceWebRemoteType:
|
|
return aRemoteType.IsSharedWeb();
|
|
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;
|
|
}
|
|
}
|
|
|
|
// Only validate webIsolated and webServiceWorker remote types for now. This
|
|
// should be expanded in the future.
|
|
if (!aRemoteType.IsIsolatedWeb()) {
|
|
return true;
|
|
}
|
|
|
|
// COOP+COEP processes might have cross-site iframes without remote subframes.
|
|
//
|
|
// HACK: Unfortunately, we can't easily check useRemoteSubframes here, but we
|
|
// shouldn't be loading any webCOOP+COEP windows without useRemoteSubframes if
|
|
// Fission is enabled.
|
|
if (aRemoteType.IsWebCoopCoep() && !mozilla::FissionAutostart()) {
|
|
return true;
|
|
}
|
|
|
|
// NOTE: Currently every webIsolated remote type is site-origin keyed, meaning
|
|
// we can unconditionally compare site origin to the origin from the
|
|
// remoteType. 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 aRemoteType.OriginNoSuffix() == siteOriginNoSuffix;
|
|
}
|
|
|
|
} // namespace mozilla::dom
|