123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404 |
- // Copyright 2019 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "components/site_isolation/site_isolation_policy.h"
- #include "base/containers/contains.h"
- #include "base/feature_list.h"
- #include "base/json/values_util.h"
- #include "base/metrics/field_trial_params.h"
- #include "base/metrics/histogram_functions.h"
- #include "base/system/sys_info.h"
- #include "build/build_config.h"
- #include "components/prefs/pref_service.h"
- #include "components/prefs/scoped_user_pref_update.h"
- #include "components/site_isolation/features.h"
- #include "components/site_isolation/pref_names.h"
- #include "components/user_prefs/user_prefs.h"
- #include "content/public/browser/browser_context.h"
- #include "content/public/browser/site_instance.h"
- #include "content/public/browser/site_isolation_mode.h"
- #include "content/public/browser/site_isolation_policy.h"
- #include "content/public/common/content_features.h"
- namespace site_isolation {
- namespace {
- using IsolatedOriginSource =
- content::ChildProcessSecurityPolicy::IsolatedOriginSource;
- bool g_disallow_memory_threshold_caching = false;
- bool ShouldCacheMemoryThresholdDecision() {
- return base::FeatureList::IsEnabled(
- features::kCacheSiteIsolationMemoryThreshold);
- }
- struct IsolationDisableDecisions {
- bool should_disable_strict;
- bool should_disable_partial;
- };
- bool ShouldDisableSiteIsolationDueToMemorySlow(
- content::SiteIsolationMode site_isolation_mode) {
- // The memory threshold behavior differs for desktop and Android:
- // - Android uses a 1900MB default threshold for partial site isolation modes
- // and a 3200MB default threshold for strict site isolation. See docs in
- // https://crbug.com/849815. The thresholds roughly correspond to 2GB+ and
- // 4GB+ devices and are lower to account for memory carveouts, which
- // reduce the amount of memory seen by AmountOfPhysicalMemoryMB(). Both
- // partial and strict site isolation thresholds can be overridden via
- // params defined in a kSiteIsolationMemoryThresholds field trial.
- // - Desktop does not enforce a default memory threshold, but for now we
- // still support a threshold defined via a kSiteIsolationMemoryThresholds
- // field trial. The trial typically carries the threshold in a param; if
- // it doesn't, use a default that's slightly higher than 1GB (see
- // https://crbug.com/844118).
- int default_memory_threshold_mb;
- #if BUILDFLAG(IS_ANDROID)
- if (site_isolation_mode == content::SiteIsolationMode::kStrictSiteIsolation) {
- default_memory_threshold_mb = 3200;
- } else {
- default_memory_threshold_mb = 1900;
- }
- #else
- default_memory_threshold_mb = 1077;
- #endif
- if (base::FeatureList::IsEnabled(features::kSiteIsolationMemoryThresholds)) {
- std::string param_name;
- switch (site_isolation_mode) {
- case content::SiteIsolationMode::kStrictSiteIsolation:
- param_name = features::kStrictSiteIsolationMemoryThresholdParamName;
- break;
- case content::SiteIsolationMode::kPartialSiteIsolation:
- param_name = features::kPartialSiteIsolationMemoryThresholdParamName;
- break;
- }
- int memory_threshold_mb = base::GetFieldTrialParamByFeatureAsInt(
- features::kSiteIsolationMemoryThresholds, param_name,
- default_memory_threshold_mb);
- return base::SysInfo::AmountOfPhysicalMemoryMB() <= memory_threshold_mb;
- }
- #if BUILDFLAG(IS_ANDROID)
- if (base::SysInfo::AmountOfPhysicalMemoryMB() <=
- default_memory_threshold_mb) {
- return true;
- }
- #endif
- return false;
- }
- IsolationDisableDecisions MakeBothDecisions() {
- IsolationDisableDecisions result{
- .should_disable_strict = ShouldDisableSiteIsolationDueToMemorySlow(
- content::SiteIsolationMode::kStrictSiteIsolation),
- .should_disable_partial = ShouldDisableSiteIsolationDueToMemorySlow(
- content::SiteIsolationMode::kPartialSiteIsolation)};
- return result;
- }
- bool CachedDisableSiteIsolation(
- content::SiteIsolationMode site_isolation_mode) {
- static const IsolationDisableDecisions decisions = MakeBothDecisions();
- if (site_isolation_mode == content::SiteIsolationMode::kStrictSiteIsolation) {
- return decisions.should_disable_strict;
- }
- return decisions.should_disable_partial;
- }
- } // namespace
- // static
- bool SiteIsolationPolicy::IsIsolationForPasswordSitesEnabled() {
- // If the user has explicitly enabled site isolation for password sites from
- // chrome://flags or from the command line, honor this regardless of policies
- // that may disable site isolation. In particular, this means that the
- // chrome://flags switch for this feature takes precedence over any memory
- // threshold restrictions and over a switch for disabling site isolation.
- if (base::FeatureList::GetInstance()->IsFeatureOverriddenFromCommandLine(
- features::kSiteIsolationForPasswordSites.name,
- base::FeatureList::OVERRIDE_ENABLE_FEATURE)) {
- return true;
- }
- // Don't isolate anything when site isolation is turned off by the user or
- // policy. This includes things like the switches::kDisableSiteIsolation
- // command-line switch, the corresponding "Disable site isolation" entry in
- // chrome://flags, enterprise policy controlled via
- // switches::kDisableSiteIsolationForPolicy, and memory threshold checks in
- // ShouldDisableSiteIsolationDueToMemoryThreshold().
- if (!content::SiteIsolationPolicy::AreDynamicIsolatedOriginsEnabled())
- return false;
- // The feature needs to be checked last, because checking the feature
- // activates the field trial and assigns the client either to a control or an
- // experiment group - such assignment should be final.
- return base::FeatureList::IsEnabled(features::kSiteIsolationForPasswordSites);
- }
- // static
- bool SiteIsolationPolicy::IsIsolationForOAuthSitesEnabled() {
- // If the user has explicitly enabled site isolation for OAuth sites from the
- // command line, honor this regardless of policies that may disable site
- // isolation.
- if (base::FeatureList::GetInstance()->IsFeatureOverriddenFromCommandLine(
- features::kSiteIsolationForOAuthSites.name,
- base::FeatureList::OVERRIDE_ENABLE_FEATURE)) {
- return true;
- }
- // Don't isolate anything when site isolation is turned off by the user or
- // policy. This includes things like the switches::kDisableSiteIsolation
- // command-line switch, the corresponding "Disable site isolation" entry in
- // chrome://flags, enterprise policy controlled via
- // switches::kDisableSiteIsolationForPolicy, and memory threshold checks in
- // ShouldDisableSiteIsolationDueToMemoryThreshold().
- if (!content::SiteIsolationPolicy::AreDynamicIsolatedOriginsEnabled())
- return false;
- // The feature needs to be checked last, because checking the feature
- // activates the field trial and assigns the client either to a control or an
- // experiment group - such assignment should be final.
- return base::FeatureList::IsEnabled(features::kSiteIsolationForOAuthSites);
- }
- // static
- bool SiteIsolationPolicy::IsEnterprisePolicyApplicable() {
- #if BUILDFLAG(IS_ANDROID)
- // https://crbug.com/844118: Limiting policy to devices with > 1GB RAM.
- // Using 1077 rather than 1024 because 1) it helps ensure that devices with
- // exactly 1GB of RAM won't get included because of inaccuracies or off-by-one
- // errors and 2) this is the bucket boundary in Memory.Stats.Win.TotalPhys2.
- bool have_enough_memory = base::SysInfo::AmountOfPhysicalMemoryMB() > 1077;
- return have_enough_memory;
- #else
- return true;
- #endif
- }
- // static
- bool SiteIsolationPolicy::ShouldDisableSiteIsolationDueToMemoryThreshold(
- content::SiteIsolationMode site_isolation_mode) {
- static const bool cache_memory_threshold_decision =
- ShouldCacheMemoryThresholdDecision();
- if (!g_disallow_memory_threshold_caching && cache_memory_threshold_decision) {
- return CachedDisableSiteIsolation(site_isolation_mode);
- }
- return ShouldDisableSiteIsolationDueToMemorySlow(site_isolation_mode);
- }
- // static
- void SiteIsolationPolicy::PersistIsolatedOrigin(
- content::BrowserContext* context,
- const url::Origin& origin,
- IsolatedOriginSource source) {
- DCHECK(context);
- DCHECK(!context->IsOffTheRecord());
- DCHECK(!origin.opaque());
- // This function currently supports two sources for persistence, for
- // user-triggered and web-triggered isolated origins.
- if (source == IsolatedOriginSource::USER_TRIGGERED) {
- PersistUserTriggeredIsolatedOrigin(context, origin);
- } else if (source == IsolatedOriginSource::WEB_TRIGGERED) {
- PersistWebTriggeredIsolatedOrigin(context, origin);
- } else {
- NOTREACHED();
- }
- }
- // static
- void SiteIsolationPolicy::PersistUserTriggeredIsolatedOrigin(
- content::BrowserContext* context,
- const url::Origin& origin) {
- // User-triggered isolated origins are currently stored in a simple list of
- // unlimited size.
- // TODO(alexmos): Cap the maximum number of entries and evict older entries.
- // See https://crbug.com/1172407.
- ListPrefUpdate update(user_prefs::UserPrefs::Get(context),
- site_isolation::prefs::kUserTriggeredIsolatedOrigins);
- base::Value* list = update.Get();
- base::Value value(origin.Serialize());
- if (!base::Contains(list->GetListDeprecated(), value))
- list->Append(std::move(value));
- }
- // static
- void SiteIsolationPolicy::PersistWebTriggeredIsolatedOrigin(
- content::BrowserContext* context,
- const url::Origin& origin) {
- // Web-triggered isolated origins are stored in a dictionary of (origin,
- // timestamp) pairs. The number of entries is capped by a field trial param,
- // and older entries are evicted.
- DictionaryPrefUpdate update(
- user_prefs::UserPrefs::Get(context),
- site_isolation::prefs::kWebTriggeredIsolatedOrigins);
- base::Value* dict = update.Get();
- // Add the origin. If it already exists, this will just update the
- // timestamp.
- dict->SetKey(origin.Serialize(), base::TimeToValue(base::Time::Now()));
- // Check whether the maximum number of stored sites was exceeded and remove
- // one or more entries, starting with the oldest timestamp. Note that more
- // than one entry may need to be removed, since the maximum number of entries
- // could change over time (via a change in the field trial param).
- size_t max_size =
- ::features::kSiteIsolationForCrossOriginOpenerPolicyMaxSitesParam.Get();
- while (dict->DictSize() > max_size) {
- auto items = dict->DictItems();
- auto oldest_site_time_pair = std::min_element(
- items.begin(), items.end(), [](auto pair_a, auto pair_b) {
- absl::optional<base::Time> time_a = base::ValueToTime(pair_a.second);
- absl::optional<base::Time> time_b = base::ValueToTime(pair_b.second);
- // has_value() should always be true unless the prefs were corrupted.
- // In that case, prioritize the corrupted entry for removal.
- return (time_a.has_value() ? time_a.value() : base::Time::Min()) <
- (time_b.has_value() ? time_b.value() : base::Time::Min());
- });
- dict->RemoveKey(oldest_site_time_pair->first);
- }
- }
- // static
- void SiteIsolationPolicy::ApplyPersistedIsolatedOrigins(
- content::BrowserContext* browser_context) {
- auto* policy = content::ChildProcessSecurityPolicy::GetInstance();
- // If the user turned off password-triggered isolation, don't apply any
- // stored isolated origins, but also don't clear them from prefs, so that
- // they can be used if password-triggered isolation is re-enabled later.
- if (IsIsolationForPasswordSitesEnabled()) {
- std::vector<url::Origin> origins;
- for (const auto& value :
- user_prefs::UserPrefs::Get(browser_context)
- ->GetValueList(prefs::kUserTriggeredIsolatedOrigins)) {
- origins.push_back(url::Origin::Create(GURL(value.GetString())));
- }
- if (!origins.empty()) {
- policy->AddFutureIsolatedOrigins(
- origins, IsolatedOriginSource::USER_TRIGGERED, browser_context);
- }
- base::UmaHistogramCounts1000(
- "SiteIsolation.SavedUserTriggeredIsolatedOrigins.Size", origins.size());
- }
- // Similarly, load saved web-triggered isolated origins only if isolation of
- // COOP sites (currently the only source of these origins) is enabled with
- // persistence, but don't remove them from prefs otherwise.
- if (content::SiteIsolationPolicy::ShouldPersistIsolatedCOOPSites()) {
- std::vector<url::Origin> origins;
- std::vector<std::string> expired_entries;
- auto* pref_service = user_prefs::UserPrefs::Get(browser_context);
- const auto& dict =
- pref_service->GetValueDict(prefs::kWebTriggeredIsolatedOrigins);
- for (auto site_time_pair : dict) {
- // Only isolate origins that haven't expired.
- absl::optional<base::Time> timestamp =
- base::ValueToTime(site_time_pair.second);
- base::TimeDelta expiration_timeout =
- ::features::
- kSiteIsolationForCrossOriginOpenerPolicyExpirationTimeoutParam
- .Get();
- if (timestamp.has_value() &&
- base::Time::Now() - timestamp.value() <= expiration_timeout) {
- origins.push_back(url::Origin::Create(GURL(site_time_pair.first)));
- } else {
- expired_entries.push_back(site_time_pair.first);
- }
- }
- // Remove expired entries (as well as those with an invalid timestamp).
- if (!expired_entries.empty()) {
- DictionaryPrefUpdate update(pref_service,
- prefs::kWebTriggeredIsolatedOrigins);
- base::Value* updated_dict = update.Get();
- for (const auto& entry : expired_entries)
- updated_dict->RemoveKey(entry);
- }
- if (!origins.empty()) {
- policy->AddFutureIsolatedOrigins(
- origins, IsolatedOriginSource::WEB_TRIGGERED, browser_context);
- }
- base::UmaHistogramCounts100(
- "SiteIsolation.SavedWebTriggeredIsolatedOrigins.Size", origins.size());
- }
- }
- // static
- void SiteIsolationPolicy::IsolateStoredOAuthSites(
- content::BrowserContext* browser_context,
- const std::vector<url::Origin>& logged_in_sites) {
- // Only isolate logged-in sites if the corresponding feature is enabled and
- // other isolation requirements (such as memory threshold) are satisfied.
- // Note that we don't clear logged-in sites from prefs if site isolation is
- // disabled so that they can be used if isolation is re-enabled later.
- if (!IsIsolationForOAuthSitesEnabled())
- return;
- auto* policy = content::ChildProcessSecurityPolicy::GetInstance();
- policy->AddFutureIsolatedOrigins(
- logged_in_sites,
- content::ChildProcessSecurityPolicy::IsolatedOriginSource::USER_TRIGGERED,
- browser_context);
- // Note that the max count matches
- // login_detection::GetOauthLoggedInSitesMaxSize().
- base::UmaHistogramCounts100("SiteIsolation.SavedOAuthSites.Size",
- logged_in_sites.size());
- }
- // static
- void SiteIsolationPolicy::IsolateNewOAuthURL(
- content::BrowserContext* browser_context,
- const GURL& signed_in_url) {
- if (!IsIsolationForOAuthSitesEnabled())
- return;
- // OAuth information is currently persisted and restored by other layers. See
- // login_detection::prefs::SaveSiteToOAuthSignedInList().
- constexpr bool kShouldPersist = false;
- content::SiteInstance::StartIsolatingSite(
- browser_context, signed_in_url,
- content::ChildProcessSecurityPolicy::IsolatedOriginSource::USER_TRIGGERED,
- kShouldPersist);
- }
- // static
- bool SiteIsolationPolicy::ShouldPdfCompositorBeEnabledForOopifs() {
- #if BUILDFLAG(IS_ANDROID)
- // TODO(crbug.com/1022917): Always enable on Android, at which point, this
- // method should go away.
- //
- // Only use the PDF compositor when one of the site isolation modes that
- // forces OOPIFs is on. This includes:
- // - Full site isolation, which may be forced on.
- // - Password-triggered site isolation for high-memory devices
- // - Isolated origins specified via command line, enterprise policy, or field
- // trials.
- return content::SiteIsolationPolicy::UseDedicatedProcessesForAllSites() ||
- IsIsolationForPasswordSitesEnabled() ||
- content::SiteIsolationPolicy::AreIsolatedOriginsEnabled();
- #else
- // Always use the PDF compositor on non-mobile platforms.
- return true;
- #endif
- }
- // static
- void SiteIsolationPolicy::SetDisallowMemoryThresholdCachingForTesting(
- bool disallow_caching) {
- g_disallow_memory_threshold_caching = disallow_caching;
- }
- } // namespace site_isolation
|