tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

ProcessIsolation.cpp (51257B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set sw=2 ts=8 et tw=80 : */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "mozilla/dom/ProcessIsolation.h"
      8 
      9 #include "mozilla/AppShutdown.h"
     10 #include "mozilla/Assertions.h"
     11 #include "mozilla/BasePrincipal.h"
     12 #include "mozilla/ClearOnShutdown.h"
     13 #include "mozilla/ContentPrincipal.h"
     14 #include "mozilla/ExtensionPolicyService.h"
     15 #include "mozilla/Logging.h"
     16 #include "mozilla/NullPrincipal.h"
     17 #include "mozilla/PermissionManager.h"
     18 #include "mozilla/Preferences.h"
     19 #include "mozilla/RefPtr.h"
     20 #include "mozilla/StaticPrefs_browser.h"
     21 #include "mozilla/StaticPrefs_fission.h"
     22 #include "mozilla/StaticPtr.h"
     23 #include "mozilla/dom/BrowsingContextGroup.h"
     24 #include "mozilla/dom/CanonicalBrowsingContext.h"
     25 #include "mozilla/dom/ContentChild.h"
     26 #include "mozilla/dom/ContentParent.h"
     27 #include "mozilla/dom/Element.h"
     28 #include "mozilla/dom/RemoteType.h"
     29 #include "mozilla/dom/WindowGlobalParent.h"
     30 #include "mozilla/extensions/WebExtensionPolicy.h"
     31 #include "nsAboutProtocolUtils.h"
     32 #include "nsDocShell.h"
     33 #include "nsError.h"
     34 #include "nsIChromeRegistry.h"
     35 #include "nsIHttpChannel.h"
     36 #include "nsIHttpChannelInternal.h"
     37 #include "nsIProtocolHandler.h"
     38 #include "nsIXULRuntime.h"
     39 #include "nsNetUtil.h"
     40 #include "nsSHistory.h"
     41 #include "nsServiceManagerUtils.h"
     42 #include "nsURLHelper.h"
     43 
     44 namespace mozilla::dom {
     45 
     46 mozilla::LazyLogModule gProcessIsolationLog{"ProcessIsolation"};
     47 
     48 namespace {
     49 
     50 // Strategy used to determine whether or not a particular site should load into
     51 // a webIsolated content process. The particular strategy chosen is controlled
     52 // by the `fission.webContentIsolationStrategy` pref, which must hold one of the
     53 // following values.
     54 enum class WebContentIsolationStrategy : uint32_t {
     55  // All web content is loaded into a shared `web` content process. This is
     56  // similar to the non-Fission behaviour, however remote subframes may still
     57  // be used for sites with special isolation behaviour, such as extension or
     58  // mozillaweb content processes.
     59  IsolateNothing = 0,
     60  // Web content is always isolated into its own `webIsolated` content process
     61  // based on site-origin, and will only load in a shared `web` content process
     62  // if site-origin could not be determined.
     63  IsolateEverything = 1,
     64  // Only isolates web content loaded by sites which are considered "high
     65  // value". A site is considered "high value" if it has been granted a
     66  // `highValue*` permission by the permission manager, which is done in
     67  // response to certain actions.
     68  IsolateHighValue = 2,
     69 };
     70 
     71 /**
     72 * Helper class for caching the result of splitting prefs which are represented
     73 * as a comma-separated list of strings.
     74 */
     75 struct CommaSeparatedPref {
     76 public:
     77  explicit constexpr CommaSeparatedPref(nsLiteralCString aPrefName)
     78      : mPrefName(aPrefName) {}
     79 
     80  void OnChange() {
     81    if (mValues) {
     82      mValues->Clear();
     83      nsAutoCString prefValue;
     84      if (NS_SUCCEEDED(Preferences::GetCString(mPrefName.get(), prefValue))) {
     85        for (const auto& value :
     86             nsCCharSeparatedTokenizer(prefValue, ',').ToRange()) {
     87          mValues->EmplaceBack(value);
     88        }
     89      }
     90    }
     91  }
     92 
     93  const nsTArray<nsCString>& Get() {
     94    if (!mValues) {
     95      mValues = new nsTArray<nsCString>;
     96      Preferences::RegisterCallbackAndCall(
     97          [](const char*, void* aData) {
     98            static_cast<CommaSeparatedPref*>(aData)->OnChange();
     99          },
    100          mPrefName, this);
    101      RunOnShutdown([this] {
    102        delete this->mValues;
    103        this->mValues = nullptr;
    104      });
    105    }
    106    return *mValues;
    107  }
    108 
    109  auto begin() { return Get().cbegin(); }
    110  auto end() { return Get().cend(); }
    111 
    112 private:
    113  nsLiteralCString mPrefName;
    114  nsTArray<nsCString>* MOZ_OWNING_REF mValues = nullptr;
    115 };
    116 
    117 CommaSeparatedPref sSeparatedMozillaDomains{
    118    "browser.tabs.remote.separatedMozillaDomains"_ns};
    119 
    120 /**
    121 * Certain URIs have special isolation behaviour, and need to be loaded within
    122 * specific process types.
    123 */
    124 enum class IsolationBehavior {
    125  // This URI loads web content and should be treated as a content load, being
    126  // isolated based on the response principal if enabled.
    127  WebContent,
    128  // Forcibly load in a process with the "web" remote type. This will ignore the
    129  // response principal completely.
    130  // This is generally reserved for internal documents which are loaded in
    131  // content, but not in the privilegedabout content process.
    132  ForceWebRemoteType,
    133  // Load this URI in the privileged about content process.
    134  PrivilegedAbout,
    135  // Load this URI in the extension process.
    136  Extension,
    137  // Load this URI in the file content process.
    138  File,
    139  // Load this URI in the priviliged mozilla content process.
    140  PrivilegedMozilla,
    141  // Load this URI explicitly in the parent process.
    142  Parent,
    143  // Load this URI wherever the browsing context is currently loaded. This is
    144  // generally used for error pages.
    145  Anywhere,
    146  // May only be returned for subframes. Inherits the remote type of the parent
    147  // document which is embedding this document.
    148  Inherit,
    149  // Special case for the `about:reader` URI which should be loaded in the same
    150  // process which would be used for the "url" query parameter.
    151  AboutReader,
    152  // There was a fatal error, and the load should be aborted.
    153  Error,
    154 };
    155 
    156 /**
    157 * Returns a static string with the name of the given isolation behaviour. For
    158 * use in logging code.
    159 */
    160 static const char* IsolationBehaviorName(IsolationBehavior aBehavior) {
    161  switch (aBehavior) {
    162    case IsolationBehavior::WebContent:
    163      return "WebContent";
    164    case IsolationBehavior::ForceWebRemoteType:
    165      return "ForceWebRemoteType";
    166    case IsolationBehavior::PrivilegedAbout:
    167      return "PrivilegedAbout";
    168    case IsolationBehavior::Extension:
    169      return "Extension";
    170    case IsolationBehavior::File:
    171      return "File";
    172    case IsolationBehavior::PrivilegedMozilla:
    173      return "PrivilegedMozilla";
    174    case IsolationBehavior::Parent:
    175      return "Parent";
    176    case IsolationBehavior::Anywhere:
    177      return "Anywhere";
    178    case IsolationBehavior::Inherit:
    179      return "Inherit";
    180    case IsolationBehavior::AboutReader:
    181      return "AboutReader";
    182    case IsolationBehavior::Error:
    183      return "Error";
    184    default:
    185      return "Unknown";
    186  }
    187 }
    188 
    189 /**
    190 * Returns a static string with the name of the given worker kind. For use in
    191 * logging code.
    192 */
    193 static const char* WorkerKindName(WorkerKind aWorkerKind) {
    194  switch (aWorkerKind) {
    195    case WorkerKindDedicated:
    196      return "Dedicated";
    197    case WorkerKindShared:
    198      return "Shared";
    199    case WorkerKindService:
    200      return "Service";
    201    default:
    202      return "Unknown";
    203  }
    204 }
    205 
    206 /**
    207 * Check if a given URI has specialized process isolation behaviour, such as
    208 * needing to be loaded within a specific type of content process.
    209 *
    210 * When handling a navigation, this method will be called twice: first with the
    211 * channel's creation URI, and then it will be called with a result principal's
    212 * URI.
    213 */
    214 static IsolationBehavior IsolationBehaviorForURI(nsIURI* aURI, bool aIsSubframe,
    215                                                 bool aForChannelCreationURI) {
    216  nsAutoCString scheme;
    217  MOZ_ALWAYS_SUCCEEDS(aURI->GetScheme(scheme));
    218 
    219  if (scheme == "chrome"_ns) {
    220    // `chrome://` URIs are always loaded in the parent process, unless they
    221    // have opted in to loading in a content process. This is currently only
    222    // done in tests.
    223    //
    224    // FIXME: These flags should be removed from `chrome` URIs at some point.
    225    nsCOMPtr<nsIXULChromeRegistry> chromeReg =
    226        do_GetService("@mozilla.org/chrome/chrome-registry;1");
    227    bool mustLoadRemotely = false;
    228    if (NS_SUCCEEDED(chromeReg->MustLoadURLRemotely(aURI, &mustLoadRemotely)) &&
    229        mustLoadRemotely) {
    230      return IsolationBehavior::ForceWebRemoteType;
    231    }
    232    bool canLoadRemotely = false;
    233    if (NS_SUCCEEDED(chromeReg->CanLoadURLRemotely(aURI, &canLoadRemotely)) &&
    234        canLoadRemotely) {
    235      return IsolationBehavior::Anywhere;
    236    }
    237    return IsolationBehavior::Parent;
    238  }
    239 
    240  if (scheme == "about"_ns) {
    241    nsAutoCString path;
    242    MOZ_ALWAYS_SUCCEEDS(NS_GetAboutModuleName(aURI, path));
    243 
    244    // The `about:blank` and `about:srcdoc` pages are loaded by normal web
    245    // content, and should be allocated processes based on their simple content
    246    // principals.
    247    if (path == "blank"_ns || path == "srcdoc"_ns) {
    248      MOZ_ASSERT(NS_IsContentAccessibleAboutURI(aURI));
    249      return IsolationBehavior::WebContent;
    250    }
    251 
    252    MOZ_ASSERT(!NS_IsContentAccessibleAboutURI(aURI));
    253    // If we're loading an `about:reader` URI, perform isolation based on the
    254    // principal of the URI being loaded.
    255    if (path == "reader"_ns && aForChannelCreationURI) {
    256      return IsolationBehavior::AboutReader;
    257    }
    258 
    259    // Otherwise, we're going to be loading an about: page. Consult the module.
    260    nsCOMPtr<nsIAboutModule> aboutModule;
    261    if (NS_FAILED(NS_GetAboutModule(aURI, getter_AddRefs(aboutModule))) ||
    262        !aboutModule) {
    263      // If we don't know of an about: module for this load, it's going to end
    264      // up being a network error. Allow the load to finish as normal.
    265      return IsolationBehavior::WebContent;
    266    }
    267 
    268    // NOTE: about modules can be implemented in JS, so this may run script, and
    269    // therefore can spuriously fail.
    270    uint32_t flags = 0;
    271    if (NS_FAILED(aboutModule->GetURIFlags(aURI, &flags))) {
    272      NS_WARNING(
    273          "nsIAboutModule::GetURIFlags unexpectedly failed. Abort the load");
    274      return IsolationBehavior::Error;
    275    }
    276 
    277    if (flags & nsIAboutModule::URI_MUST_LOAD_IN_EXTENSION_PROCESS) {
    278      return IsolationBehavior::Extension;
    279    }
    280 
    281    if (flags & nsIAboutModule::URI_MUST_LOAD_IN_CHILD) {
    282      if (flags & nsIAboutModule::URI_CAN_LOAD_IN_PRIVILEGEDABOUT_PROCESS) {
    283        return IsolationBehavior::PrivilegedAbout;
    284      }
    285      return IsolationBehavior::ForceWebRemoteType;
    286    }
    287 
    288    if (flags & nsIAboutModule::URI_CAN_LOAD_IN_CHILD) {
    289      return IsolationBehavior::Anywhere;
    290    }
    291 
    292    return IsolationBehavior::Parent;
    293  }
    294 
    295  // If the test-only `dataUriInDefaultWebProcess` pref is enabled, dump all
    296  // `data:` URIs in a "web" content process, rather than loading them in
    297  // content processes based on their precursor origins.
    298  if (StaticPrefs::browser_tabs_remote_dataUriInDefaultWebProcess() &&
    299      scheme == "data"_ns) {
    300    return IsolationBehavior::ForceWebRemoteType;
    301  }
    302 
    303  // Make sure to unwrap nested URIs before we early return for channel creation
    304  // URI. The checks past this point are intended to operate on the principal,
    305  // which has it's origin constructed from the innermost URI.
    306  nsCOMPtr<nsIURI> inner;
    307  if (nsCOMPtr<nsINestedURI> nested = do_QueryInterface(aURI);
    308      nested && NS_SUCCEEDED(nested->GetInnerURI(getter_AddRefs(inner)))) {
    309    return IsolationBehaviorForURI(inner, aIsSubframe, aForChannelCreationURI);
    310  }
    311 
    312  // If we're doing the initial check based on the channel creation URI, stop
    313  // here as we want to only perform the following checks on the true channel
    314  // result principal.
    315  if (aForChannelCreationURI) {
    316    return IsolationBehavior::WebContent;
    317  }
    318 
    319  // Protocols used by Thunderbird to display email messages.
    320  if (scheme == "imap"_ns || scheme == "mailbox"_ns || scheme == "news"_ns ||
    321      scheme == "nntp"_ns || scheme == "snews"_ns || scheme == "x-moz-ews"_ns) {
    322    return IsolationBehavior::Parent;
    323  }
    324 
    325  // There is more handling for extension content processes in the caller, but
    326  // they should load in an extension content process unless we're loading a
    327  // subframe.
    328  if (scheme == "moz-extension"_ns) {
    329    if (aIsSubframe) {
    330      // As a temporary measure, extension iframes must be loaded within the
    331      // same process as their parent document.
    332      return IsolationBehavior::Inherit;
    333    }
    334    return IsolationBehavior::Extension;
    335  }
    336 
    337  if (scheme == "file"_ns) {
    338    return IsolationBehavior::File;
    339  }
    340 
    341  // Check if the URI is listed as a privileged mozilla content process.
    342  if (scheme == "https"_ns &&
    343      StaticPrefs::
    344          browser_tabs_remote_separatePrivilegedMozillaWebContentProcess()) {
    345    nsAutoCString host;
    346    if (NS_SUCCEEDED(aURI->GetAsciiHost(host))) {
    347      // This code is duplicated in E10SUtils.sys.mjs, please update both
    348      for (const auto& separatedDomain : sSeparatedMozillaDomains) {
    349        // If the domain exactly matches our host, or our host ends with "." +
    350        // separatedDomain, we consider it matching.
    351        if (separatedDomain == host ||
    352            (separatedDomain.Length() < host.Length() &&
    353             host.CharAt(host.Length() - separatedDomain.Length() - 1) == '.' &&
    354             StringEndsWith(host, separatedDomain))) {
    355          return IsolationBehavior::PrivilegedMozilla;
    356        }
    357      }
    358    }
    359  }
    360 
    361  nsCOMPtr<nsIScriptSecurityManager> secMan =
    362      nsContentUtils::GetSecurityManager();
    363  bool inFileURIAllowList = false;
    364  if (NS_SUCCEEDED(secMan->InFileURIAllowlist(aURI, &inFileURIAllowList)) &&
    365      inFileURIAllowList) {
    366    return IsolationBehavior::File;
    367  }
    368 
    369  return IsolationBehavior::WebContent;
    370 }
    371 
    372 /**
    373 * Helper method for logging the origin of a principal as a string.
    374 */
    375 static nsAutoCString OriginString(nsIPrincipal* aPrincipal) {
    376  nsAutoCString origin;
    377  aPrincipal->GetOrigin(origin);
    378  return origin;
    379 }
    380 
    381 /**
    382 * Trim the OriginAttributes from aPrincipal, and use it to create a
    383 * OriginSuffix string appropriate to use within a remoteType string.
    384 */
    385 static nsAutoCString OriginSuffixForRemoteType(nsIPrincipal* aPrincipal) {
    386  nsAutoCString originSuffix;
    387  OriginAttributes attrs = aPrincipal->OriginAttributesRef();
    388  attrs.StripAttributes(OriginAttributes::STRIP_FIRST_PARTY_DOMAIN |
    389                        OriginAttributes::STRIP_PARITION_KEY);
    390  attrs.CreateSuffix(originSuffix);
    391  return originSuffix;
    392 }
    393 
    394 /**
    395 * Given an about:reader URI, extract the "url" query parameter, and use it to
    396 * construct a principal which should be used for process selection.
    397 */
    398 static already_AddRefed<BasePrincipal> GetAboutReaderURLPrincipal(
    399    nsIURI* aURI, const OriginAttributes& aAttrs) {
    400 #ifdef DEBUG
    401  MOZ_ASSERT(aURI->SchemeIs("about"));
    402  nsAutoCString path;
    403  MOZ_ALWAYS_SUCCEEDS(NS_GetAboutModuleName(aURI, path));
    404  MOZ_ASSERT(path == "reader"_ns);
    405 #endif
    406 
    407  nsAutoCString query;
    408  MOZ_ALWAYS_SUCCEEDS(aURI->GetQuery(query));
    409 
    410  // Extract the "url" parameter from the `about:reader`'s query parameters,
    411  // and recover a content principal from it.
    412  nsAutoCString readerSpec;
    413  if (URLParams::Extract(query, "url"_ns, readerSpec)) {
    414    nsCOMPtr<nsIURI> readerUri;
    415    if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(readerUri), readerSpec))) {
    416      return BasePrincipal::CreateContentPrincipal(readerUri, aAttrs);
    417    }
    418  }
    419  return nullptr;
    420 }
    421 
    422 /**
    423 * Check the Cross-Origin-Opener-Policy of the given channel or ancestor
    424 * BrowsingContext, checking if the response should be cross-origin isolated.
    425 */
    426 static bool ShouldCrossOriginIsolate(nsIChannel* aChannel,
    427                                     WindowGlobalParent* aParentWindow) {
    428  nsILoadInfo::CrossOriginOpenerPolicy coop =
    429      nsILoadInfo::OPENER_POLICY_UNSAFE_NONE;
    430  if (aParentWindow) {
    431    coop = aParentWindow->BrowsingContext()->Top()->GetOpenerPolicy();
    432  } else if (nsCOMPtr<nsIHttpChannelInternal> httpChannel =
    433                 do_QueryInterface(aChannel)) {
    434    MOZ_ALWAYS_SUCCEEDS(httpChannel->GetCrossOriginOpenerPolicy(&coop));
    435  }
    436  return coop ==
    437         nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP;
    438 }
    439 
    440 /**
    441 * Returns `true` if loads for this site should be isolated on a per-site basis.
    442 * If `aTopBC` is nullptr, this is being called to check if a shared or service
    443 * worker should be isolated.
    444 */
    445 static bool ShouldIsolateSite(nsIPrincipal* aPrincipal,
    446                              bool aUseRemoteSubframes) {
    447  // If Fission is disabled, we never want to isolate. We check the toplevel BC
    448  // if it's available, or the global pref if checking for shared or service
    449  // workers.
    450  if (!aUseRemoteSubframes) {
    451    return false;
    452  }
    453 
    454  // non-content principals currently can't have webIsolated remote types
    455  // assigned to them, so should not be isolated.
    456  if (!aPrincipal->GetIsContentPrincipal()) {
    457    return false;
    458  }
    459 
    460  switch (WebContentIsolationStrategy(
    461      StaticPrefs::fission_webContentIsolationStrategy())) {
    462    case WebContentIsolationStrategy::IsolateNothing:
    463      MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
    464              ("Not isolating '%s' as isolation is disabled",
    465               OriginString(aPrincipal).get()));
    466      return false;
    467    case WebContentIsolationStrategy::IsolateEverything:
    468      MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
    469              ("Isolating '%s' as isolation is enabled for all sites",
    470               OriginString(aPrincipal).get()));
    471      return true;
    472    case WebContentIsolationStrategy::IsolateHighValue: {
    473      RefPtr<PermissionManager> perms = PermissionManager::GetInstance();
    474      if (NS_WARN_IF(!perms)) {
    475        // If we somehow have no permission manager, fall back to the safest
    476        // option, and try to isolate.
    477        MOZ_ASSERT_UNREACHABLE("Permission manager is missing");
    478        return true;
    479      }
    480 
    481      static constexpr nsLiteralCString kHighValuePermissions[] = {
    482          mozilla::dom::kHighValueCOOPPermission,
    483          mozilla::dom::kHighValueHasSavedLoginPermission,
    484          mozilla::dom::kHighValueIsLoggedInPermission,
    485      };
    486 
    487      for (const auto& type : kHighValuePermissions) {
    488        uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION;
    489        if (NS_SUCCEEDED(perms->TestPermissionFromPrincipal(aPrincipal, type,
    490                                                            &permission)) &&
    491            permission == nsIPermissionManager::ALLOW_ACTION) {
    492          MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
    493                  ("Isolating '%s' due to high-value permission '%s'",
    494                   OriginString(aPrincipal).get(), type.get()));
    495          return true;
    496        }
    497      }
    498      MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
    499              ("Not isolating '%s' as it is not high-value",
    500               OriginString(aPrincipal).get()));
    501      return false;
    502    }
    503    default:
    504      // An invalid pref value was used. Fall back to the safest option and
    505      // isolate everything.
    506      NS_WARNING("Invalid pref value for fission.webContentIsolationStrategy");
    507      MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
    508              ("Isolating '%s' due to unknown strategy pref value",
    509               OriginString(aPrincipal).get()));
    510      return true;
    511  }
    512 }
    513 
    514 static Result<nsCString, nsresult> SpecialBehaviorRemoteType(
    515    IsolationBehavior aBehavior, const nsACString& aCurrentRemoteType,
    516    WindowGlobalParent* aParentWindow) {
    517  switch (aBehavior) {
    518    case IsolationBehavior::ForceWebRemoteType:
    519      return {WEB_REMOTE_TYPE};
    520    case IsolationBehavior::PrivilegedAbout:
    521      // The privileged about: content process cannot be disabled, as it
    522      // causes various actors to break.
    523      return {PRIVILEGEDABOUT_REMOTE_TYPE};
    524    case IsolationBehavior::Extension:
    525      if (ExtensionPolicyService::GetSingleton().UseRemoteExtensions()) {
    526        return {EXTENSION_REMOTE_TYPE};
    527      }
    528      return {NOT_REMOTE_TYPE};
    529    case IsolationBehavior::File:
    530      if (StaticPrefs::browser_tabs_remote_separateFileUriProcess()) {
    531        return {FILE_REMOTE_TYPE};
    532      }
    533      return {WEB_REMOTE_TYPE};
    534    case IsolationBehavior::PrivilegedMozilla:
    535      return {PRIVILEGEDMOZILLA_REMOTE_TYPE};
    536    case IsolationBehavior::Parent:
    537      return {NOT_REMOTE_TYPE};
    538    case IsolationBehavior::Anywhere:
    539      return {nsCString(aCurrentRemoteType)};
    540    case IsolationBehavior::Inherit:
    541      MOZ_DIAGNOSTIC_ASSERT(aParentWindow);
    542      return {nsCString(aParentWindow->GetRemoteType())};
    543 
    544    case IsolationBehavior::Error:
    545      return Err(NS_ERROR_UNEXPECTED);
    546 
    547    default:
    548      MOZ_ASSERT_UNREACHABLE();
    549      return Err(NS_ERROR_UNEXPECTED);
    550  }
    551 }
    552 
    553 enum class WebProcessType {
    554  Web,
    555  WebIsolated,
    556  WebCoopCoep,
    557 };
    558 
    559 }  // namespace
    560 
    561 Result<NavigationIsolationOptions, nsresult> IsolationOptionsForNavigation(
    562    CanonicalBrowsingContext* aTopBC, WindowGlobalParent* aParentWindow,
    563    nsIURI* aChannelCreationURI, nsIChannel* aChannel,
    564    const nsACString& aCurrentRemoteType, bool aHasCOOPMismatch,
    565    bool aForNewTab, uint32_t aLoadStateLoadType,
    566    const Maybe<uint64_t>& aChannelId,
    567    const Maybe<nsCString>& aRemoteTypeOverride) {
    568  // Get the final principal, used to select which process to load into.
    569  nsCOMPtr<nsIPrincipal> resultPrincipal;
    570  nsresult rv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
    571      aChannel, getter_AddRefs(resultPrincipal));
    572  if (NS_FAILED(rv)) {
    573    MOZ_LOG(gProcessIsolationLog, LogLevel::Error,
    574            ("failed to get channel result principal"));
    575    return Err(rv);
    576  }
    577 
    578  MOZ_LOG(
    579      gProcessIsolationLog, LogLevel::Verbose,
    580      ("IsolationOptionsForNavigation principal:%s, uri:%s, parentUri:%s",
    581       OriginString(resultPrincipal).get(),
    582       aChannelCreationURI->GetSpecOrDefault().get(),
    583       aParentWindow ? aParentWindow->GetDocumentURI()->GetSpecOrDefault().get()
    584                     : ""));
    585 
    586  // If we're loading a null principal, we can't easily make a process
    587  // selection decision off ot it. Instead, we'll use our null principal's
    588  // precursor principal to make process selection decisions.
    589  bool isNullPrincipalPrecursor = false;
    590  nsCOMPtr<nsIPrincipal> resultOrPrecursor(resultPrincipal);
    591  if (nsCOMPtr<nsIPrincipal> precursor =
    592          resultOrPrecursor->GetPrecursorPrincipal()) {
    593    MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
    594            ("using null principal precursor origin %s",
    595             OriginString(precursor).get()));
    596    resultOrPrecursor = precursor;
    597    isNullPrincipalPrecursor = true;
    598  }
    599 
    600  NavigationIsolationOptions options;
    601  options.mReplaceBrowsingContext = aHasCOOPMismatch;
    602  options.mShouldCrossOriginIsolate =
    603      ShouldCrossOriginIsolate(aChannel, aParentWindow);
    604 
    605  // Check if this load has an explicit remote type override. This is used to
    606  // perform an about:blank load within a specific content process.
    607  if (aRemoteTypeOverride) {
    608    MOZ_DIAGNOSTIC_ASSERT(
    609        NS_IsAboutBlank(aChannelCreationURI),
    610        "Should only have aRemoteTypeOverride for about:blank URIs");
    611    if (NS_WARN_IF(!resultPrincipal->GetIsNullPrincipal())) {
    612      MOZ_LOG(gProcessIsolationLog, LogLevel::Error,
    613              ("invalid remote type override on non-null principal"));
    614      return Err(NS_ERROR_DOM_SECURITY_ERR);
    615    }
    616 
    617    MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
    618            ("using remote type override (%s) for load",
    619             aRemoteTypeOverride->get()));
    620    options.mRemoteType = *aRemoteTypeOverride;
    621    return options;
    622  }
    623 
    624  // First, check for any special cases which should be handled using the
    625  // channel creation URI, and handle them.
    626  auto behavior = IsolationBehaviorForURI(aChannelCreationURI, aParentWindow,
    627                                          /* aForChannelCreationURI */ true);
    628  MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
    629          ("Channel Creation Isolation Behavior: %s",
    630           IsolationBehaviorName(behavior)));
    631 
    632  // In the about:reader special case, we want to fetch the relevant information
    633  // from the URI, an then treat it as a normal web content load.
    634  if (behavior == IsolationBehavior::AboutReader) {
    635    if (RefPtr<BasePrincipal> readerURIPrincipal = GetAboutReaderURLPrincipal(
    636            aChannelCreationURI, resultOrPrecursor->OriginAttributesRef())) {
    637      MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
    638              ("using about:reader's url origin %s",
    639               OriginString(readerURIPrincipal).get()));
    640      resultOrPrecursor = readerURIPrincipal;
    641    }
    642    behavior = IsolationBehavior::WebContent;
    643    // If loading an about:reader page in a BrowsingContext which shares a
    644    // BrowsingContextGroup with other toplevel documents, replace the
    645    // BrowsingContext to destroy any references.
    646    //
    647    // With SHIP we can apply this to all about:reader loads, but otherwise
    648    // do it at least where there are opener/group relationships.
    649    if (mozilla::SessionHistoryInParent() ||
    650        aTopBC->Group()->Toplevels().Length() > 1) {
    651      options.mReplaceBrowsingContext = true;
    652    }
    653  }
    654 
    655  // If we're running in a test which is requesting that system-triggered
    656  // about:blank documents load within the current process, override the
    657  // behaviour for loads which meet the requirements.
    658  if (StaticPrefs::browser_tabs_remote_systemTriggeredAboutBlankAnywhere() &&
    659      NS_IsAboutBlank(aChannelCreationURI)) {
    660    nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
    661    if (loadInfo->TriggeringPrincipal()->IsSystemPrincipal() &&
    662        resultOrPrecursor->GetIsNullPrincipal()) {
    663      MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
    664              ("Forcing system-principal triggered about:blank load to "
    665               "complete in the current process"));
    666      behavior = IsolationBehavior::Anywhere;
    667    }
    668  }
    669 
    670 #ifdef MOZ_WIDGET_ANDROID
    671  // If we're loading an error page on android, it must complete within the same
    672  // process as the errored page load would complete in due to code expecting
    673  // that behavior. See bug 1673763.
    674  if (aLoadStateLoadType == LOAD_ERROR_PAGE) {
    675    MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
    676            ("Forcing error page load to complete in the current process"));
    677    behavior = IsolationBehavior::Anywhere;
    678  }
    679 #endif
    680 
    681  // If we're loading for a specific extension, we'll need to perform a
    682  // BCG-switching load to get our toplevel extension window in the correct
    683  // BrowsingContextGroup.
    684  if (auto* addonPolicy =
    685          BasePrincipal::Cast(resultOrPrecursor)->AddonPolicy()) {
    686    if (aParentWindow) {
    687      // As a temporary measure, extension iframes must be loaded within the
    688      // same process as their parent document.
    689      MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
    690              ("Loading extension subframe in same process as parent"));
    691      behavior = IsolationBehavior::Inherit;
    692    } else {
    693      MOZ_LOG(
    694          gProcessIsolationLog, LogLevel::Verbose,
    695          ("Found extension frame with addon policy. Will use group id %" PRIx64
    696           " (currentId: %" PRIx64 ")",
    697           addonPolicy->GetBrowsingContextGroupId(), aTopBC->Group()->Id()));
    698      behavior = IsolationBehavior::Extension;
    699      if (aTopBC->Group()->Id() != addonPolicy->GetBrowsingContextGroupId()) {
    700        options.mReplaceBrowsingContext = true;
    701        options.mSpecificGroupId = addonPolicy->GetBrowsingContextGroupId();
    702      }
    703    }
    704  }
    705 
    706  // Do a second run of `GetIsolationBehavior`, this time using the
    707  // principal's URI to handle additional special cases such as the file and
    708  // privilegedmozilla content process.
    709  if (behavior == IsolationBehavior::WebContent) {
    710    if (resultOrPrecursor->IsSystemPrincipal()) {
    711      // We're loading something with a system principal which isn't caught in
    712      // one of our other edge-cases. If the load started in the parent process,
    713      // and it's safe for it to end in the parent process, we should finish the
    714      // load there.
    715      bool isUIResource = false;
    716      if (aCurrentRemoteType.IsEmpty() &&
    717          (aChannelCreationURI->SchemeIs("about") ||
    718           (NS_SUCCEEDED(NS_URIChainHasFlags(
    719                aChannelCreationURI, nsIProtocolHandler::URI_IS_UI_RESOURCE,
    720                &isUIResource)) &&
    721            isUIResource))) {
    722        behavior = IsolationBehavior::Parent;
    723      } else {
    724        // In general, we don't want to load documents with a system principal
    725        // in a content process, however we need to in some cases, such as when
    726        // loading blob: URLs created by system code. We can force the load to
    727        // finish in a content process instead.
    728        behavior = IsolationBehavior::ForceWebRemoteType;
    729      }
    730    } else if (nsCOMPtr<nsIURI> principalURI = resultOrPrecursor->GetURI()) {
    731      behavior = IsolationBehaviorForURI(principalURI, aParentWindow,
    732                                         /* aForChannelCreationURI */ false);
    733    }
    734  }
    735 
    736  // If we're currently loaded in the extension process, and are going to switch
    737  // to some other remote type, make sure we leave the extension's BCG which we
    738  // may have entered earlier to separate extension and non-extension BCGs from
    739  // each-other.
    740  if (!aParentWindow && aCurrentRemoteType == EXTENSION_REMOTE_TYPE &&
    741      behavior != IsolationBehavior::Extension &&
    742      behavior != IsolationBehavior::Anywhere) {
    743    MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
    744            ("Forcing BC replacement to leave extension BrowsingContextGroup "
    745             "%" PRIx64 " on navigation",
    746             aTopBC->Group()->Id()));
    747    options.mReplaceBrowsingContext = true;
    748  }
    749 
    750  // We don't want to load documents with sandboxed null principals, like
    751  // `data:` URIs, in the parent process, even if they were created by a
    752  // document which would otherwise be loaded in the parent process.
    753  if (behavior == IsolationBehavior::Parent && isNullPrincipalPrecursor) {
    754    MOZ_LOG(gProcessIsolationLog, LogLevel::Debug,
    755            ("Ensuring sandboxed null-principal load doesn't occur in the "
    756             "parent process"));
    757    behavior = IsolationBehavior::ForceWebRemoteType;
    758  }
    759 
    760  MOZ_LOG(
    761      gProcessIsolationLog, LogLevel::Debug,
    762      ("Using IsolationBehavior %s for %s (original uri %s)",
    763       IsolationBehaviorName(behavior), OriginString(resultOrPrecursor).get(),
    764       aChannelCreationURI->GetSpecOrDefault().get()));
    765 
    766  // Check if we can put the previous document into the BFCache.
    767  if (mozilla::BFCacheInParent() && nsSHistory::GetMaxTotalViewers() > 0 &&
    768      !aForNewTab && !aParentWindow && !aTopBC->HadOriginalOpener() &&
    769      behavior != IsolationBehavior::Parent &&
    770      (ExtensionPolicyService::GetSingleton().UseRemoteExtensions() ||
    771       behavior != IsolationBehavior::Extension) &&
    772      !aCurrentRemoteType.IsEmpty() &&
    773      aTopBC->GetHasLoadedNonInitialDocument() &&
    774      (aLoadStateLoadType == LOAD_NORMAL ||
    775       aLoadStateLoadType == LOAD_HISTORY || aLoadStateLoadType == LOAD_LINK ||
    776       aLoadStateLoadType == LOAD_STOP_CONTENT ||
    777       aLoadStateLoadType == LOAD_STOP_CONTENT_AND_REPLACE) &&
    778      (!aTopBC->GetActiveSessionHistoryEntry() ||
    779       aTopBC->GetActiveSessionHistoryEntry()->GetSaveLayoutStateFlag())) {
    780    if (nsCOMPtr<nsIURI> uri = aTopBC->GetCurrentURI()) {
    781      MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
    782              ("current uri: %s", uri->GetSpecOrDefault().get()));
    783    }
    784    options.mTryUseBFCache =
    785        aTopBC->AllowedInBFCache(aChannelId, aChannelCreationURI);
    786    if (options.mTryUseBFCache) {
    787      options.mReplaceBrowsingContext = true;
    788      options.mActiveSessionHistoryEntry =
    789          aTopBC->GetActiveSessionHistoryEntry();
    790    }
    791  }
    792 
    793  // If the load has any special remote type handling, do so at this point.
    794  if (behavior != IsolationBehavior::WebContent) {
    795    options.mRemoteType = MOZ_TRY(
    796        SpecialBehaviorRemoteType(behavior, aCurrentRemoteType, aParentWindow));
    797 
    798    if (options.mRemoteType != aCurrentRemoteType &&
    799        (options.mRemoteType.IsEmpty() || aCurrentRemoteType.IsEmpty())) {
    800      options.mReplaceBrowsingContext = true;
    801    }
    802 
    803    MOZ_LOG(
    804        gProcessIsolationLog, LogLevel::Debug,
    805        ("Selecting specific remote type (%s) due to a special case isolation "
    806         "behavior %s",
    807         options.mRemoteType.get(), IsolationBehaviorName(behavior)));
    808    return options;
    809  }
    810 
    811  // At this point we're definitely not going to be loading in the parent
    812  // process anymore, so we're definitely going to be replacing BrowsingContext
    813  // if we're in the parent process.
    814  if (aCurrentRemoteType.IsEmpty()) {
    815    MOZ_ASSERT(!aParentWindow);
    816    options.mReplaceBrowsingContext = true;
    817  }
    818 
    819  // NOTE: Currently we always perform process isolation based on the
    820  // siteOrigin, not based on the full origin, even if the
    821  // `Origin-Agent-Cluster` header is provided and we are keying DocGroups
    822  // by-origin.
    823  //
    824  // If in the future we want to start keying based on full origin in some
    825  // cases, the logic below will need to be updated to handle this. Note that
    826  // the UseOriginAgentCluster bit may not have been set on the
    827  // BrowsingContextGroup when this check is being evaluated (as it is set after
    828  // process selection, which may cause a BrowsingContextGroup switch).
    829 
    830  nsAutoCString siteOriginNoSuffix;
    831  MOZ_TRY(resultOrPrecursor->GetSiteOriginNoSuffix(siteOriginNoSuffix));
    832 
    833  // Check if we've already loaded a document with the given principal in some
    834  // content process. We want to finish the load in the same process in that
    835  // case.
    836  //
    837  // The exception to that is with extension loads and the system principal,
    838  // where we may have multiple documents with the same principal in different
    839  // processes. Those have been handled above, and will not be reaching here.
    840  //
    841  // If we're doing a replace load or opening a new tab, we won't be staying in
    842  // the same BrowsingContextGroup, so ignore this step.
    843  if (!options.mReplaceBrowsingContext && !aForNewTab) {
    844    // Helper for efficiently determining if a given origin is same-site. This
    845    // will attempt to do a fast equality check, and will only fall back to
    846    // computing the site-origin for content principals.
    847    auto principalIsSameSite = [&](nsIPrincipal* aDocumentPrincipal) -> bool {
    848      // If we're working with a null principal with a precursor, compare
    849      // precursors, as `resultOrPrecursor` has already been stripped to its
    850      // precursor.
    851      nsCOMPtr<nsIPrincipal> documentPrincipal(aDocumentPrincipal);
    852      if (nsCOMPtr<nsIPrincipal> precursor =
    853              documentPrincipal->GetPrecursorPrincipal()) {
    854        documentPrincipal = precursor;
    855      }
    856 
    857      // First, attempt to use `Equals` to compare principals, and if that
    858      // fails compare siteOrigins. Only compare siteOrigin for content
    859      // principals, as non-content principals will never have siteOrigin !=
    860      // origin.
    861      nsAutoCString documentSiteOrigin;
    862      return resultOrPrecursor->Equals(documentPrincipal) ||
    863             (documentPrincipal->GetIsContentPrincipal() &&
    864              resultOrPrecursor->GetIsContentPrincipal() &&
    865              NS_SUCCEEDED(documentPrincipal->GetSiteOriginNoSuffix(
    866                  documentSiteOrigin)) &&
    867              documentSiteOrigin == siteOriginNoSuffix);
    868    };
    869 
    870    // XXX: Consider also checking in-flight process switches to see if any have
    871    // matching principals?
    872    AutoTArray<RefPtr<BrowsingContext>, 8> contexts;
    873    aTopBC->Group()->GetToplevels(contexts);
    874    while (!contexts.IsEmpty()) {
    875      auto bc = contexts.PopLastElement();
    876      for (const auto& wc : bc->GetWindowContexts()) {
    877        WindowGlobalParent* wgp = wc->Canonical();
    878 
    879        // Check if this WindowGlobalParent has the given resultPrincipal, and
    880        // if it does, we need to load in that process.
    881        if (!wgp->GetRemoteType().IsEmpty() &&
    882            principalIsSameSite(wgp->DocumentPrincipal())) {
    883          MOZ_LOG(gProcessIsolationLog, LogLevel::Debug,
    884                  ("Found existing frame with matching principal "
    885                   "(remoteType:(%s), origin:%s)",
    886                   PromiseFlatCString(wgp->GetRemoteType()).get(),
    887                   OriginString(wgp->DocumentPrincipal()).get()));
    888          options.mRemoteType = wgp->GetRemoteType();
    889          return options;
    890        }
    891 
    892        // Also enumerate over this WindowContexts' subframes.
    893        contexts.AppendElements(wc->Children());
    894      }
    895    }
    896  }
    897 
    898  nsAutoCString originSuffix = OriginSuffixForRemoteType(resultOrPrecursor);
    899 
    900  WebProcessType webProcessType = WebProcessType::Web;
    901  if (ShouldIsolateSite(resultOrPrecursor, aTopBC->UseRemoteSubframes())) {
    902    webProcessType = WebProcessType::WebIsolated;
    903  }
    904 
    905  // Check if we should be cross-origin isolated.
    906  if (options.mShouldCrossOriginIsolate) {
    907    webProcessType = WebProcessType::WebCoopCoep;
    908  }
    909 
    910  switch (webProcessType) {
    911    case WebProcessType::Web:
    912      options.mRemoteType = WEB_REMOTE_TYPE;
    913      break;
    914    case WebProcessType::WebIsolated:
    915      options.mRemoteType =
    916          FISSION_WEB_REMOTE_TYPE "="_ns + siteOriginNoSuffix + originSuffix;
    917      break;
    918    case WebProcessType::WebCoopCoep:
    919      options.mRemoteType =
    920          WITH_COOP_COEP_REMOTE_TYPE "="_ns + siteOriginNoSuffix + originSuffix;
    921      break;
    922  }
    923  return options;
    924 }
    925 
    926 Result<WorkerIsolationOptions, nsresult> IsolationOptionsForWorker(
    927    nsIPrincipal* aPrincipal, WorkerKind aWorkerKind,
    928    const nsACString& aCurrentRemoteType, bool aUseRemoteSubframes) {
    929  MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
    930          ("IsolationOptionsForWorker principal:%s, kind:%s, current:%s",
    931           OriginString(aPrincipal).get(), WorkerKindName(aWorkerKind),
    932           PromiseFlatCString(aCurrentRemoteType).get()));
    933 
    934  MOZ_ASSERT(NS_IsMainThread());
    935  MOZ_RELEASE_ASSERT(
    936      aWorkerKind == WorkerKindService || aWorkerKind == WorkerKindShared,
    937      "Unexpected remote worker kind");
    938 
    939  if (aWorkerKind == WorkerKindService &&
    940      !aPrincipal->GetIsContentPrincipal()) {
    941    MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
    942            ("Rejecting service worker with non-content principal"));
    943    return Err(NS_ERROR_UNEXPECTED);
    944  }
    945 
    946  if (aPrincipal->GetIsExpandedPrincipal()) {
    947    MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
    948            ("Rejecting remote worker with expanded principal"));
    949    return Err(NS_ERROR_UNEXPECTED);
    950  }
    951 
    952  // In some cases, such as for null principals without precursors, we will want
    953  // to load a shared worker in a process based on the current process. This is
    954  // not done for service workers - process selection for those should function
    955  // the same in all processes.
    956  //
    957  // We only allow the current remote type to be used if it is not a COOP+COEP
    958  // remote type, in order to avoid loading a shared worker in one of these
    959  // processes. Currently process selection for workers occurs before response
    960  // headers are available, so we will never select to load a shared worker in a
    961  // COOP+COEP content process.
    962  nsCString preferredRemoteType = DEFAULT_REMOTE_TYPE;
    963  if (aWorkerKind == WorkerKind::WorkerKindShared &&
    964      !StringBeginsWith(aCurrentRemoteType,
    965                        WITH_COOP_COEP_REMOTE_TYPE_PREFIX)) {
    966    preferredRemoteType = aCurrentRemoteType;
    967  }
    968 
    969  WorkerIsolationOptions options;
    970 
    971  // If we're loading a null principal, we can't easily make a process
    972  // selection decision off ot it. Instead, we'll use our null principal's
    973  // precursor principal to make process selection decisions.
    974  bool isNullPrincipalPrecursor = false;
    975  nsCOMPtr<nsIPrincipal> resultOrPrecursor(aPrincipal);
    976  if (nsCOMPtr<nsIPrincipal> precursor =
    977          resultOrPrecursor->GetPrecursorPrincipal()) {
    978    MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
    979            ("using null principal precursor origin %s",
    980             OriginString(precursor).get()));
    981    resultOrPrecursor = precursor;
    982    isNullPrincipalPrecursor = true;
    983  }
    984 
    985  IsolationBehavior behavior = IsolationBehavior::WebContent;
    986  if (resultOrPrecursor->GetIsContentPrincipal()) {
    987    nsCOMPtr<nsIURI> uri = resultOrPrecursor->GetURI();
    988    behavior = IsolationBehaviorForURI(uri, /* aIsSubframe */ false,
    989                                       /* aForChannelCreationURI */ false);
    990  } else if (resultOrPrecursor->IsSystemPrincipal()) {
    991    MOZ_ASSERT(aWorkerKind == WorkerKindShared);
    992 
    993    // Allow system principal shared workers to load within either the
    994    // parent process or privilegedabout process, depending on the
    995    // responsible process.
    996    if (preferredRemoteType == NOT_REMOTE_TYPE) {
    997      MOZ_LOG(gProcessIsolationLog, LogLevel::Debug,
    998              ("Loading system principal shared worker in parent process"));
    999      behavior = IsolationBehavior::Parent;
   1000    } else if (preferredRemoteType == PRIVILEGEDABOUT_REMOTE_TYPE) {
   1001      MOZ_LOG(gProcessIsolationLog, LogLevel::Debug,
   1002              ("Loading system principal shared worker in privilegedabout "
   1003               "process"));
   1004      behavior = IsolationBehavior::PrivilegedAbout;
   1005    } else {
   1006      MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
   1007              ("Cannot load system-principal shared worker in "
   1008               "non-privilegedabout content process"));
   1009      return Err(NS_ERROR_UNEXPECTED);
   1010    }
   1011  } else {
   1012    MOZ_ASSERT(resultOrPrecursor->GetIsNullPrincipal());
   1013    MOZ_ASSERT(aWorkerKind == WorkerKindShared);
   1014 
   1015    if (preferredRemoteType == NOT_REMOTE_TYPE) {
   1016      MOZ_LOG(gProcessIsolationLog, LogLevel::Debug,
   1017              ("Ensuring precursorless null principal shared worker loads in a "
   1018               "content process"));
   1019      behavior = IsolationBehavior::ForceWebRemoteType;
   1020    } else {
   1021      MOZ_LOG(gProcessIsolationLog, LogLevel::Debug,
   1022              ("Loading precursorless null principal shared worker within "
   1023               "current remotetype: (%s)",
   1024               preferredRemoteType.get()));
   1025      behavior = IsolationBehavior::Anywhere;
   1026    }
   1027  }
   1028 
   1029  if (behavior == IsolationBehavior::Parent && isNullPrincipalPrecursor) {
   1030    MOZ_LOG(gProcessIsolationLog, LogLevel::Debug,
   1031            ("Ensuring sandboxed null-principal shared worker doesn't load in "
   1032             "the parent process"));
   1033    behavior = IsolationBehavior::ForceWebRemoteType;
   1034  }
   1035 
   1036  if (behavior != IsolationBehavior::WebContent) {
   1037    options.mRemoteType = MOZ_TRY(
   1038        SpecialBehaviorRemoteType(behavior, preferredRemoteType, nullptr));
   1039 
   1040    MOZ_LOG(
   1041        gProcessIsolationLog, LogLevel::Debug,
   1042        ("Selecting specific %s worker remote type (%s) due to a special case "
   1043         "isolation behavior %s",
   1044         WorkerKindName(aWorkerKind), options.mRemoteType.get(),
   1045         IsolationBehaviorName(behavior)));
   1046    return options;
   1047  }
   1048 
   1049  // If we should be isolating this site, we can determine the correct fission
   1050  // remote type from the principal's site-origin.
   1051  if (ShouldIsolateSite(resultOrPrecursor, aUseRemoteSubframes)) {
   1052    nsAutoCString siteOriginNoSuffix;
   1053    MOZ_TRY(resultOrPrecursor->GetSiteOriginNoSuffix(siteOriginNoSuffix));
   1054    nsAutoCString originSuffix = OriginSuffixForRemoteType(resultOrPrecursor);
   1055 
   1056    nsCString prefix = aWorkerKind == WorkerKindService
   1057                           ? SERVICEWORKER_REMOTE_TYPE
   1058                           : FISSION_WEB_REMOTE_TYPE;
   1059    options.mRemoteType = prefix + "="_ns + siteOriginNoSuffix + originSuffix;
   1060 
   1061    MOZ_LOG(gProcessIsolationLog, LogLevel::Debug,
   1062            ("Isolating web content %s worker in remote type (%s)",
   1063             WorkerKindName(aWorkerKind), options.mRemoteType.get()));
   1064  } else {
   1065    options.mRemoteType = WEB_REMOTE_TYPE;
   1066 
   1067    MOZ_LOG(gProcessIsolationLog, LogLevel::Debug,
   1068            ("Loading web content %s worker in shared web remote type",
   1069             WorkerKindName(aWorkerKind)));
   1070  }
   1071  return options;
   1072 }
   1073 
   1074 void AddHighValuePermission(nsIPrincipal* aResultPrincipal,
   1075                            const nsACString& aPermissionType) {
   1076  RefPtr<PermissionManager> perms = PermissionManager::GetInstance();
   1077  if (NS_WARN_IF(!perms)) {
   1078    return;
   1079  }
   1080 
   1081  // We can't act on non-content principals, so if the load was sandboxed, try
   1082  // to use the unsandboxed precursor principal to add the highValue permission.
   1083  nsCOMPtr<nsIPrincipal> resultOrPrecursor(aResultPrincipal);
   1084  if (!aResultPrincipal->GetIsContentPrincipal()) {
   1085    resultOrPrecursor = aResultPrincipal->GetPrecursorPrincipal();
   1086    if (!resultOrPrecursor) {
   1087      return;
   1088    }
   1089  }
   1090 
   1091  // Use the site-origin principal as we want to add the permission for the
   1092  // entire site, rather than a specific subdomain, as process isolation acts on
   1093  // a site granularity.
   1094  nsAutoCString siteOrigin;
   1095  if (NS_FAILED(resultOrPrecursor->GetSiteOrigin(siteOrigin))) {
   1096    return;
   1097  }
   1098 
   1099  nsCOMPtr<nsIPrincipal> sitePrincipal =
   1100      BasePrincipal::CreateContentPrincipal(siteOrigin);
   1101  if (!sitePrincipal || !sitePrincipal->GetIsContentPrincipal()) {
   1102    return;
   1103  }
   1104 
   1105  MOZ_LOG(dom::gProcessIsolationLog, LogLevel::Verbose,
   1106          ("Adding %s Permission for site '%s'", aPermissionType.BeginReading(),
   1107           siteOrigin.get()));
   1108 
   1109  uint32_t expiration = 0;
   1110  if (aPermissionType.Equals(mozilla::dom::kHighValueCOOPPermission)) {
   1111    expiration = StaticPrefs::fission_highValue_coop_expiration();
   1112  } else if (aPermissionType.Equals(
   1113                 mozilla::dom::kHighValueHasSavedLoginPermission) ||
   1114             aPermissionType.Equals(
   1115                 mozilla::dom::kHighValueIsLoggedInPermission)) {
   1116    expiration = StaticPrefs::fission_highValue_login_expiration();
   1117  } else {
   1118    MOZ_ASSERT_UNREACHABLE("Unknown permission type");
   1119  }
   1120 
   1121  // XXX: Would be nice if we could use `TimeStamp` here, but there's
   1122  // unfortunately no convenient way to recover a time in milliseconds since the
   1123  // unix epoch from `TimeStamp`.
   1124  int64_t expirationTime =
   1125      (PR_Now() / PR_USEC_PER_MSEC) + (int64_t(expiration) * PR_MSEC_PER_SEC);
   1126  (void)perms->AddFromPrincipal(
   1127      sitePrincipal, aPermissionType, nsIPermissionManager::ALLOW_ACTION,
   1128      nsIPermissionManager::EXPIRE_TIME, expirationTime);
   1129 }
   1130 
   1131 void AddHighValuePermission(const nsACString& aOrigin,
   1132                            const nsACString& aPermissionType) {
   1133  nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
   1134  nsCOMPtr<nsIPrincipal> principal;
   1135  nsresult rv =
   1136      ssm->CreateContentPrincipalFromOrigin(aOrigin, getter_AddRefs(principal));
   1137  if (NS_WARN_IF(NS_FAILED(rv))) {
   1138    return;
   1139  }
   1140 
   1141  AddHighValuePermission(principal, aPermissionType);
   1142 }
   1143 
   1144 bool IsIsolateHighValueSiteEnabled() {
   1145  return mozilla::FissionAutostart() &&
   1146         WebContentIsolationStrategy(
   1147             StaticPrefs::fission_webContentIsolationStrategy()) ==
   1148             WebContentIsolationStrategy::IsolateHighValue;
   1149 }
   1150 
   1151 bool ValidatePrincipalCouldPotentiallyBeLoadedBy(
   1152    nsIPrincipal* aPrincipal, const nsACString& aRemoteType,
   1153    const EnumSet<ValidatePrincipalOptions>& aOptions) {
   1154  // Don't bother validating principals from the parent process.
   1155  if (aRemoteType == NOT_REMOTE_TYPE) {
   1156    return true;
   1157  }
   1158 
   1159  // If there is no principal, only allow it if AllowNullPtr is specified.
   1160  if (!aPrincipal) {
   1161    return aOptions.contains(ValidatePrincipalOptions::AllowNullPtr);
   1162  }
   1163 
   1164  // We currently do not track relationships between specific null principals
   1165  // and content processes, so we can not validate much here.
   1166  if (aPrincipal->GetIsNullPrincipal()) {
   1167    return true;
   1168  }
   1169 
   1170  // If we have a system principal, only allow it if AllowSystem is passed.
   1171  if (aPrincipal->IsSystemPrincipal()) {
   1172    return aOptions.contains(ValidatePrincipalOptions::AllowSystem);
   1173  }
   1174 
   1175  // Performing checks against the remote type requires the IOService and
   1176  // ThirdPartyService to be available, check we're not late in shutdown.
   1177  if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownFinal)) {
   1178    return true;
   1179  }
   1180 
   1181  // We can load a `resource://` URI in any process. This usually comes up due
   1182  // to pdf.js and the JSON viewer. See bug 1686200.
   1183  if (aPrincipal->SchemeIs("resource")) {
   1184    return true;
   1185  }
   1186 
   1187  // Only allow expanded principals if AllowExpanded is passed. Each
   1188  // sub-principal will be validated independently.
   1189  if (aPrincipal->GetIsExpandedPrincipal()) {
   1190    if (!aOptions.contains(ValidatePrincipalOptions::AllowExpanded)) {
   1191      return false;
   1192    }
   1193    // FIXME: There are more constraints on expanded principals in-practice,
   1194    // such as the structure of extension expanded principals. This may need
   1195    // to be investigated more in the future.
   1196    nsCOMPtr<nsIExpandedPrincipal> expandedPrincipal =
   1197        do_QueryInterface(aPrincipal);
   1198    const auto& allowList = expandedPrincipal->AllowList();
   1199    for (const auto& innerPrincipal : allowList) {
   1200      if (!ValidatePrincipalCouldPotentiallyBeLoadedBy(innerPrincipal,
   1201                                                       aRemoteType, aOptions)) {
   1202        return false;
   1203      }
   1204    }
   1205    return true;
   1206  }
   1207 
   1208  // A URI with a file:// scheme can never load in a non-file content process
   1209  // due to sandboxing.
   1210  if (aPrincipal->SchemeIs("file")) {
   1211    // If we don't support a separate 'file' process, then we can return here.
   1212    if (!StaticPrefs::browser_tabs_remote_separateFileUriProcess()) {
   1213      return true;
   1214    }
   1215    return aRemoteType == FILE_REMOTE_TYPE;
   1216  }
   1217 
   1218  if (aPrincipal->SchemeIs("about")) {
   1219    uint32_t flags = 0;
   1220    nsresult rv = aPrincipal->GetAboutModuleFlags(&flags);
   1221    // In tests, we can race between about: pages being unregistered, and a
   1222    // content process unregistering a Blob URL. To be safe here, we fail open
   1223    // if no about module is present.
   1224    if (NS_FAILED(rv)) {
   1225      return false;
   1226    }
   1227 
   1228    // Block principals for about: URIs which can't load in this process.
   1229    if (!(flags & (nsIAboutModule::URI_CAN_LOAD_IN_CHILD |
   1230                   nsIAboutModule::URI_MUST_LOAD_IN_CHILD))) {
   1231      return false;
   1232    }
   1233    if (flags & nsIAboutModule::URI_MUST_LOAD_IN_EXTENSION_PROCESS) {
   1234      return aRemoteType == EXTENSION_REMOTE_TYPE;
   1235    }
   1236    return true;
   1237  }
   1238 
   1239  // Web content can contain extension content frames, so any content process
   1240  // may send us an extension's principal.
   1241  // NOTE: We don't check AddonPolicy here, as that can disappear if the add-on
   1242  // is disabled or uninstalled. As this is a lax check, looking at the scheme
   1243  // should be sufficient.
   1244  if (aPrincipal->SchemeIs("moz-extension")) {
   1245    return true;
   1246  }
   1247 
   1248  // If the remote type doesn't have an origin suffix, we can do no further
   1249  // principal validation with it.
   1250  int32_t equalIdx = aRemoteType.FindChar('=');
   1251  if (equalIdx == kNotFound) {
   1252    return true;
   1253  }
   1254 
   1255  // Split out the remote type prefix and the origin suffix.
   1256  nsDependentCSubstring typePrefix(aRemoteType, 0, equalIdx);
   1257  nsDependentCSubstring typeOrigin(aRemoteType, equalIdx + 1);
   1258 
   1259  // Only validate webIsolated and webServiceWorker remote types for now. This
   1260  // should be expanded in the future.
   1261  if (typePrefix != FISSION_WEB_REMOTE_TYPE &&
   1262      typePrefix != SERVICEWORKER_REMOTE_TYPE) {
   1263    return true;
   1264  }
   1265 
   1266  // Trim any OriginAttributes from the origin, as those will not be validated.
   1267  int32_t suffixIdx = typeOrigin.RFindChar('^');
   1268  nsDependentCSubstring typeOriginNoSuffix(typeOrigin, 0, suffixIdx);
   1269 
   1270  // NOTE: Currently every webIsolated remote type is site-origin keyed, meaning
   1271  // we can unconditionally compare site origins. If this changes in the future,
   1272  // this logic will need to be updated to reflect that.
   1273  nsAutoCString siteOriginNoSuffix;
   1274  if (NS_FAILED(aPrincipal->GetSiteOriginNoSuffix(siteOriginNoSuffix))) {
   1275    return false;
   1276  }
   1277  return siteOriginNoSuffix == typeOriginNoSuffix;
   1278 }
   1279 
   1280 }  // namespace mozilla::dom