[go: nahoru, domu]

blob: 701cf1575094821ddaae1372664b7731ef207831 [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/child_process_security_policy_impl.h"
#include <tuple>
#include <utility>
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/containers/cxx20_erase.h"
#include "base/debug/crash_logging.h"
#include "base/debug/dump_without_crashing.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_macros.h"
#include "base/ranges/algorithm.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "build/build_config.h"
#include "components/permissions/features.h"
#include "content/browser/bad_message.h"
#include "content/browser/isolated_origin_util.h"
#include "content/browser/process_lock.h"
#include "content/browser/renderer_host/render_process_host_impl.h"
#include "content/browser/site_info.h"
#include "content/browser/site_instance_impl.h"
#include "content/browser/url_info.h"
#include "content/browser/webui/url_data_manager_backend.h"
#include "content/common/content_navigation_policy.h"
#include "content/common/features.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_or_resource_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/child_process_data.h"
#include "content/public/browser/child_process_host.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/resource_context.h"
#include "content/public/browser/site_instance.h"
#include "content/public/browser/site_isolation_policy.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/bindings_policy.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_features.h"
#include "content/public/common/url_constants.h"
#include "net/base/filename_util.h"
#include "net/base/url_util.h"
#include "net/net_buildflags.h"
#include "services/network/public/cpp/resource_request_body.h"
#include "storage/browser/file_system/file_permission_policy.h"
#include "storage/browser/file_system/file_system_context.h"
#include "storage/browser/file_system/file_system_url.h"
#include "storage/browser/file_system/isolated_context.h"
#include "storage/common/file_system/file_system_util.h"
#include "third_party/blink/public/common/features.h"
#include "url/gurl.h"
#include "url/url_canon.h"
#include "url/url_constants.h"
namespace content {
namespace {
// Used internally only. These bit positions have no relationship to any
// underlying OS and can be changed to accommodate finer-grained permissions.
enum ChildProcessSecurityPermissions {
READ_FILE_PERMISSION = 1 << 0,
WRITE_FILE_PERMISSION = 1 << 1,
CREATE_NEW_FILE_PERMISSION = 1 << 2,
CREATE_OVERWRITE_FILE_PERMISSION = 1 << 3,
DELETE_FILE_PERMISSION = 1 << 4,
// Used by Media Galleries API
COPY_INTO_FILE_PERMISSION = 1 << 5,
};
// Used internally only. Bitmasks that are actually used by the Grant* and Can*
// methods. These contain one or more ChildProcessSecurityPermissions.
enum ChildProcessSecurityGrants {
READ_FILE_GRANT = READ_FILE_PERMISSION,
WRITE_FILE_GRANT = WRITE_FILE_PERMISSION,
CREATE_NEW_FILE_GRANT = CREATE_NEW_FILE_PERMISSION |
COPY_INTO_FILE_PERMISSION,
CREATE_READ_WRITE_FILE_GRANT = CREATE_NEW_FILE_PERMISSION |
CREATE_OVERWRITE_FILE_PERMISSION |
READ_FILE_PERMISSION |
WRITE_FILE_PERMISSION |
COPY_INTO_FILE_PERMISSION |
DELETE_FILE_PERMISSION,
COPY_INTO_FILE_GRANT = COPY_INTO_FILE_PERMISSION,
DELETE_FILE_GRANT = DELETE_FILE_PERMISSION,
};
// https://crbug.com/646278 Valid blob URLs should contain canonically
// serialized origins.
bool IsMalformedBlobUrl(const GURL& url) {
if (!url.SchemeIsBlob())
return false;
// If the part after blob: survives a roundtrip through url::Origin, then
// it's a normal blob URL.
std::string canonical_origin = url::Origin::Create(url).Serialize();
canonical_origin.append(1, '/');
if (base::StartsWith(url.GetContent(), canonical_origin,
base::CompareCase::INSENSITIVE_ASCII))
return false;
// This is a malformed blob URL.
return true;
}
// Helper function that checks to make sure calls on
// CanAccessDataForOrigin() are only made on valid threads.
// TODO(acolwell): Expand the usage of this check to other
// ChildProcessSecurityPolicyImpl methods.
bool IsRunningOnExpectedThread() {
if (BrowserThread::CurrentlyOn(BrowserThread::IO) ||
BrowserThread::CurrentlyOn(BrowserThread::UI)) {
return true;
}
std::string thread_name(base::PlatformThread::GetName());
// TODO(acolwell): Remove once all tests are updated to properly
// identify that they are running on the UI or IO threads.
if (thread_name.empty())
return true;
LOG(ERROR) << "Running on unexpected thread '" << thread_name << "'";
return false;
}
base::debug::CrashKeyString* GetRequestedOriginCrashKey() {
static auto* requested_origin_key = base::debug::AllocateCrashKeyString(
"requested_origin", base::debug::CrashKeySize::Size256);
return requested_origin_key;
}
base::debug::CrashKeyString* GetExpectedProcessLockKey() {
static auto* expected_process_lock_key = base::debug::AllocateCrashKeyString(
"expected_process_lock", base::debug::CrashKeySize::Size64);
return expected_process_lock_key;
}
base::debug::CrashKeyString* GetKilledProcessOriginLockKey() {
static auto* crash_key = base::debug::AllocateCrashKeyString(
"killed_process_origin_lock", base::debug::CrashKeySize::Size64);
return crash_key;
}
base::debug::CrashKeyString* GetCanAccessDataFailureReasonKey() {
static auto* crash_key = base::debug::AllocateCrashKeyString(
"can_access_data_failure_reason", base::debug::CrashKeySize::Size256);
return crash_key;
}
base::debug::CrashKeyString* GetCanAccessDataKeepAliveDurationKey() {
static auto* keep_alive_duration_key = base::debug::AllocateCrashKeyString(
"keep_alive_duration", base::debug::CrashKeySize::Size256);
return keep_alive_duration_key;
}
base::debug::CrashKeyString* GetCanAccessDataShutdownDelayRefCountKey() {
static auto* shutdown_delay_key = base::debug::AllocateCrashKeyString(
"shutdown_delay_ref_count", base::debug::CrashKeySize::Size32);
return shutdown_delay_key;
}
base::debug::CrashKeyString* GetCanAccessDataProcessRFHCount() {
static auto* process_rfh_count_key = base::debug::AllocateCrashKeyString(
"process_rfh_count", base::debug::CrashKeySize::Size32);
return process_rfh_count_key;
}
void LogCanAccessDataForOriginCrashKeys(
const std::string& expected_process_lock,
const std::string& killed_process_origin_lock,
const std::string& requested_origin,
const std::string& failure_reason,
const std::string& keep_alive_durations,
const std::string& shutdown_delay_ref_count,
const std::string& process_rfh_count) {
base::debug::SetCrashKeyString(GetExpectedProcessLockKey(),
expected_process_lock);
base::debug::SetCrashKeyString(GetKilledProcessOriginLockKey(),
killed_process_origin_lock);
base::debug::SetCrashKeyString(GetRequestedOriginCrashKey(),
requested_origin);
base::debug::SetCrashKeyString(GetCanAccessDataFailureReasonKey(),
failure_reason);
base::debug::SetCrashKeyString(GetCanAccessDataKeepAliveDurationKey(),
keep_alive_durations);
base::debug::SetCrashKeyString(GetCanAccessDataShutdownDelayRefCountKey(),
shutdown_delay_ref_count);
base::debug::SetCrashKeyString(GetCanAccessDataProcessRFHCount(),
process_rfh_count);
}
// Checks whether a lock mismatch should be ignored to allow most visited tiles
// to commit in third-party NTP processes.
//
// TODO(crbug.com/566091): This exception should be removed once these tiles
// can be loaded in OOPIFs on the NTP.
bool AllowProcessLockMismatchForNTP(const ProcessLock& expected_lock,
const ProcessLock& actual_lock) {
// First, ensure that the expected lock corresponds to a WebUI site that
// does not require its process to be locked. This should only be the case
// for sites used to load most visited tiles.
const auto& webui_schemes = URLDataManagerBackend::GetWebUISchemes();
if (!base::Contains(webui_schemes, expected_lock.lock_url().scheme())) {
return false;
}
if (GetContentClient()->browser()->DoesWebUIUrlRequireProcessLock(
expected_lock.lock_url())) {
return false;
}
// Now, check that the actual lock corresponds to an NTP process (using its
// site_url() since this check relies on checking effective URLs for NTPs),
// and that the expected lock (based on the URL for which we're doing the
// access check) is allowed to stay in that process. This restricts the lock
// mismatch to just NTP processes, disallowing most visited tiles from being
// embedded on sites in other processes.
return GetContentClient()->browser()->ShouldStayInParentProcessForNTP(
expected_lock.lock_url(), actual_lock.site_url());
}
} // namespace
ChildProcessSecurityPolicyImpl::Handle::Handle()
: child_id_(ChildProcessHost::kInvalidUniqueID) {}
ChildProcessSecurityPolicyImpl::Handle::Handle(int child_id,
bool duplicating_handle)
: child_id_(child_id) {
auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
if (!policy->AddProcessReference(child_id_, duplicating_handle))
child_id_ = ChildProcessHost::kInvalidUniqueID;
}
ChildProcessSecurityPolicyImpl::Handle::Handle(Handle&& rhs)
: child_id_(rhs.child_id_) {
rhs.child_id_ = ChildProcessHost::kInvalidUniqueID;
}
ChildProcessSecurityPolicyImpl::Handle
ChildProcessSecurityPolicyImpl::Handle::Duplicate() {
return Handle(child_id_, /* duplicating_handle */ true);
}
ChildProcessSecurityPolicyImpl::Handle::~Handle() {
if (child_id_ != ChildProcessHost::kInvalidUniqueID) {
auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
policy->RemoveProcessReference(child_id_);
}
}
ChildProcessSecurityPolicyImpl::Handle& ChildProcessSecurityPolicyImpl::Handle::
operator=(Handle&& rhs) {
if (child_id_ != ChildProcessHost::kInvalidUniqueID &&
child_id_ != rhs.child_id_) {
auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
policy->RemoveProcessReference(child_id_);
}
child_id_ = rhs.child_id_;
rhs.child_id_ = ChildProcessHost::kInvalidUniqueID;
return *this;
}
bool ChildProcessSecurityPolicyImpl::Handle::is_valid() const {
return child_id_ != ChildProcessHost::kInvalidUniqueID;
}
bool ChildProcessSecurityPolicyImpl::Handle::CanCommitURL(const GURL& url) {
if (child_id_ == ChildProcessHost::kInvalidUniqueID)
return false;
auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
return policy->CanCommitURL(child_id_, url);
}
bool ChildProcessSecurityPolicyImpl::Handle::CanReadFile(
const base::FilePath& file) {
if (child_id_ == ChildProcessHost::kInvalidUniqueID)
return false;
auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
return policy->CanReadFile(child_id_, file);
}
bool ChildProcessSecurityPolicyImpl::Handle::CanReadFileSystemFile(
const storage::FileSystemURL& url) {
if (child_id_ == ChildProcessHost::kInvalidUniqueID)
return false;
auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
return policy->CanReadFileSystemFile(child_id_, url);
}
bool ChildProcessSecurityPolicyImpl::Handle::CanAccessDataForOrigin(
const url::Origin& origin) {
if (child_id_ == ChildProcessHost::kInvalidUniqueID) {
LogCanAccessDataForOriginCrashKeys(
"(unknown)", "(unknown)", origin.GetDebugString(), "handle_not_valid",
"no_keep_alive_durations", "no shutdown delay ref count",
"no process rfh count");
return false;
}
auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
return policy->CanAccessDataForOrigin(child_id_, origin);
}
ChildProcessSecurityPolicyImpl::OriginAgentClusterOptInEntry::
OriginAgentClusterOptInEntry(
const OriginAgentClusterIsolationState& oac_isolation_state_in,
const url::Origin& origin_in)
: oac_isolation_state(oac_isolation_state_in), origin(origin_in) {}
ChildProcessSecurityPolicyImpl::OriginAgentClusterOptInEntry::
OriginAgentClusterOptInEntry(const OriginAgentClusterOptInEntry&) = default;
ChildProcessSecurityPolicyImpl::OriginAgentClusterOptInEntry::
~OriginAgentClusterOptInEntry() = default;
// The SecurityState class is used to maintain per-child process security state
// information.
class ChildProcessSecurityPolicyImpl::SecurityState {
public:
typedef std::map<BrowsingInstanceId, OriginAgentClusterIsolationState>
BrowsingInstanceInfoMap;
explicit SecurityState(BrowserContext* browser_context)
: enabled_bindings_(0),
can_read_raw_cookies_(false),
can_send_midi_(false),
can_send_midi_sysex_(false),
browser_context_(browser_context),
resource_context_(browser_context->GetResourceContext()) {
if (!base::FeatureList::IsEnabled(
permissions::features::kBlockMidiByDefault)) {
can_send_midi_ = true;
}
}
SecurityState(const SecurityState&) = delete;
SecurityState& operator=(const SecurityState&) = delete;
~SecurityState() {
storage::IsolatedContext* isolated_context =
storage::IsolatedContext::GetInstance();
for (auto iter = filesystem_permissions_.begin();
iter != filesystem_permissions_.end(); ++iter) {
isolated_context->RemoveReference(iter->first);
}
UMA_HISTOGRAM_COUNTS_10000(
"SiteIsolation.BrowsingInstance.MaxCountPerProcess",
max_browsing_instance_count_);
}
// Grant permission to request and commit URLs with the specified origin.
void GrantCommitOrigin(const url::Origin& origin) {
if (origin.opaque())
return;
origin_map_[origin] = CommitRequestPolicy::kCommitAndRequest;
}
void GrantRequestOrigin(const url::Origin& origin) {
if (origin.opaque())
return;
// Anything already in |origin_map_| must have at least request permission
// already. In that case, the emplace() below will be a no-op.
origin_map_.emplace(origin, CommitRequestPolicy::kRequestOnly);
}
void GrantCommitScheme(const std::string& scheme) {
scheme_map_[scheme] = CommitRequestPolicy::kCommitAndRequest;
}
void GrantRequestScheme(const std::string& scheme) {
// Anything already in |scheme_map_| must have at least request permission
// already. In that case, the emplace() below will be a no-op.
scheme_map_.emplace(scheme, CommitRequestPolicy::kRequestOnly);
}
// Grant certain permissions to a file.
void GrantPermissionsForFile(const base::FilePath& file, int permissions) {
base::FilePath stripped = file.StripTrailingSeparators();
file_permissions_[stripped] |= permissions;
}
// Grant navigation to a file but not the file:// scheme in general.
void GrantRequestOfSpecificFile(const base::FilePath &file) {
request_file_set_.insert(file.StripTrailingSeparators());
}
// Revokes all permissions granted to a file.
void RevokeAllPermissionsForFile(const base::FilePath& file) {
base::FilePath stripped = file.StripTrailingSeparators();
file_permissions_.erase(stripped);
request_file_set_.erase(stripped);
}
// Grant certain permissions to a file.
void GrantPermissionsForFileSystem(const std::string& filesystem_id,
int permissions) {
if (!base::Contains(filesystem_permissions_, filesystem_id))
storage::IsolatedContext::GetInstance()->AddReference(filesystem_id);
filesystem_permissions_[filesystem_id] |= permissions;
}
bool HasPermissionsForFileSystem(const std::string& filesystem_id,
int permissions) {
FileSystemMap::const_iterator it =
filesystem_permissions_.find(filesystem_id);
if (it == filesystem_permissions_.end())
return false;
return (it->second & permissions) == permissions;
}
#if BUILDFLAG(IS_ANDROID)
// Determine if the certain permissions have been granted to a content URI.
bool HasPermissionsForContentUri(const base::FilePath& file,
int permissions) {
DCHECK(!file.empty());
DCHECK(file.IsContentUri());
if (!permissions)
return false;
base::FilePath file_path = file.StripTrailingSeparators();
FileMap::const_iterator it = file_permissions_.find(file_path);
if (it != file_permissions_.end())
return (it->second & permissions) == permissions;
return false;
}
#endif
void GrantBindings(int bindings) {
enabled_bindings_ |= bindings;
}
void GrantReadRawCookies() {
can_read_raw_cookies_ = true;
}
void RevokeReadRawCookies() {
can_read_raw_cookies_ = false;
}
void GrantPermissionForMidi() { can_send_midi_ = true; }
void GrantPermissionForMidiSysEx() {
can_send_midi_ = true;
can_send_midi_sysex_ = true;
}
// Determine whether permission has been granted to commit |url|.
bool CanCommitURL(const GURL& url) {
DCHECK(!url.SchemeIsBlob() && !url.SchemeIsFileSystem())
<< "inner_url extraction should be done already.";
// Having permission to a scheme implies permission to all of its URLs.
auto scheme_judgment = scheme_map_.find(url.scheme());
if (scheme_judgment != scheme_map_.end() &&
scheme_judgment->second == CommitRequestPolicy::kCommitAndRequest) {
return true;
}
// Check for permission for specific origin.
if (CanCommitOrigin(url::Origin::Create(url)))
return true;
// file:// URLs may sometimes be more granular, e.g. dragging and dropping a
// file from the local filesystem. The child itself may not have been
// granted access to the entire file:// scheme, but it should still be
// allowed to request the dragged and dropped file.
if (url.SchemeIs(url::kFileScheme)) {
base::FilePath path;
if (net::FileURLToFilePath(url, &path))
return base::Contains(request_file_set_, path);
}
return false; // Unmentioned schemes are disallowed.
}
bool CanRequestURL(const GURL& url) {
DCHECK(!url.SchemeIsBlob() && !url.SchemeIsFileSystem())
<< "inner_url extraction should be done already.";
// Having permission to a scheme implies permission to all of its URLs.
auto scheme_judgment = scheme_map_.find(url.scheme());
if (scheme_judgment != scheme_map_.end())
return true;
if (CanRequestOrigin(url::Origin::Create(url)))
return true;
// Otherwise, delegate to CanCommitURL. Unmentioned schemes are disallowed.
// TODO(dcheng): It would be nice to avoid constructing the origin twice.
return CanCommitURL(url);
}
// Determine if the certain permissions have been granted to a file.
bool HasPermissionsForFile(const base::FilePath& file, int permissions) {
#if BUILDFLAG(IS_ANDROID)
if (file.IsContentUri())
return HasPermissionsForContentUri(file, permissions);
#endif
if (!permissions || file.empty() || !file.IsAbsolute())
return false;
base::FilePath current_path = file.StripTrailingSeparators();
base::FilePath last_path;
int skip = 0;
while (current_path != last_path) {
base::FilePath base_name = current_path.BaseName();
if (base_name.value() == base::FilePath::kParentDirectory) {
++skip;
} else if (skip > 0) {
if (base_name.value() != base::FilePath::kCurrentDirectory)
--skip;
} else {
FileMap::const_iterator it = file_permissions_.find(current_path);
if (it != file_permissions_.end())
return (it->second & permissions) == permissions;
}
last_path = current_path;
current_path = current_path.DirName();
}
return false;
}
void SetProcessLock(const ProcessLock& lock_to_set,
const IsolationContext& context,
bool is_process_used) {
CHECK(!lock_to_set.is_invalid());
CHECK(!process_lock_.is_locked_to_site());
CHECK_NE(SiteInstanceImpl::GetDefaultSiteURL(), lock_to_set.lock_url());
if (process_lock_.is_invalid()) {
DCHECK(browsing_instance_info_map_.empty());
CHECK(lock_to_set.allows_any_site() || lock_to_set.is_locked_to_site());
} else {
// Verify that we are not trying to update the lock with different
// COOP/COEP information.
CHECK_EQ(process_lock_.GetWebExposedIsolationInfo(),
lock_to_set.GetWebExposedIsolationInfo());
if (process_lock_.allows_any_site()) {
// TODO(acolwell): Remove ability to lock to an allows_any_site
// lock multiple times. Legacy behavior allows the old "lock to site"
// path to generate an "allow_any_site" lock if an empty URL is passed
// to SiteInstanceImpl::SetSite().
CHECK(lock_to_set.allows_any_site() || lock_to_set.is_locked_to_site());
// Do not allow a lock to become more strict if the process has already
// been used to render any pages.
if (lock_to_set.is_locked_to_site()) {
CHECK(!is_process_used)
<< "Cannot lock an already used process to " << lock_to_set;
}
} else {
NOTREACHED() << "Unexpected lock type.";
}
}
process_lock_ = lock_to_set;
AddBrowsingInstanceInfo(context);
}
void AddBrowsingInstanceInfo(const IsolationContext& context) {
DCHECK(!context.browsing_instance_id().is_null());
browsing_instance_info_map_.insert(
{context.browsing_instance_id(), context.default_isolation_state()});
// Track the maximum number of BrowsingInstances in the process in case
// we need to remove delayed cleanup and let the set grow unbounded.
// Also track the default isolation state for this BrowsingInstance for
// future access checks, since the global default can change over time.
if (browsing_instance_info_map_.size() > max_browsing_instance_count_) {
max_browsing_instance_count_ = browsing_instance_info_map_.size();
}
}
const ProcessLock& process_lock() const { return process_lock_; }
const BrowsingInstanceInfoMap& browsing_instance_info() {
return browsing_instance_info_map_;
}
void ClearBrowsingInstanceId(const BrowsingInstanceId& id) {
browsing_instance_info_map_.erase(id);
}
bool has_web_ui_bindings() const {
return enabled_bindings_ & kWebUIBindingsPolicyMask;
}
bool can_read_raw_cookies() const {
return can_read_raw_cookies_;
}
bool CanSendMidi() const {
if (base::FeatureList::IsEnabled(
permissions::features::kBlockMidiByDefault)) {
// Ensure the flags are in a consistent state: we can only send SysEx
// messages if we can also send non-SysEx messages
CHECK(can_send_midi_ || !can_send_midi_sysex_);
return can_send_midi_;
} else {
return true;
}
}
bool CanSendMidiSysEx() const {
if (base::FeatureList::IsEnabled(
permissions::features::kBlockMidiByDefault)) {
// Ensure the flags are in a consistent state: we can only send SysEx
// messages if we can also send non-SysEx messages
CHECK(can_send_midi_ || !can_send_midi_sysex_);
}
return can_send_midi_sysex_;
}
BrowserOrResourceContext GetBrowserOrResourceContext() const {
if (BrowserThread::CurrentlyOn(BrowserThread::UI) && browser_context_)
return BrowserOrResourceContext(browser_context_);
if (BrowserThread::CurrentlyOn(BrowserThread::IO) && resource_context_)
return BrowserOrResourceContext(resource_context_);
return BrowserOrResourceContext();
}
void ClearBrowserContextIfMatches(const BrowserContext* browser_context) {
if (browser_context == browser_context_)
browser_context_ = nullptr;
}
private:
enum class CommitRequestPolicy {
kRequestOnly,
kCommitAndRequest,
};
bool CanCommitOrigin(const url::Origin& origin) {
auto it = origin_map_.find(origin);
if (it == origin_map_.end())
return false;
return it->second == CommitRequestPolicy::kCommitAndRequest;
}
bool CanRequestOrigin(const url::Origin& origin) {
// Anything already in |origin_map_| must have at least request permissions
// already.
return origin_map_.find(origin) != origin_map_.end();
}
typedef std::map<std::string, CommitRequestPolicy> SchemeMap;
typedef std::map<url::Origin, CommitRequestPolicy> OriginMap;
typedef int FilePermissionFlags; // bit-set of base::File::Flags
typedef std::map<base::FilePath, FilePermissionFlags> FileMap;
typedef std::map<std::string, FilePermissionFlags> FileSystemMap;
typedef std::set<base::FilePath> FileSet;
// Maps URL schemes to commit/request policies the child process has been
// granted. There is no provision for revoking.
SchemeMap scheme_map_;
// The map of URL origins to commit/request policies the child process has
// been granted. There is no provision for revoking.
OriginMap origin_map_;
// The set of files the child process is permited to upload to the web.
FileMap file_permissions_;
// The set of files the child process is permitted to load.
FileSet request_file_set_;
int enabled_bindings_;
bool can_read_raw_cookies_;
bool can_send_midi_;
bool can_send_midi_sysex_;
ProcessLock process_lock_;
// A map containing the IDs of all BrowsingInstances with documents in this
// process, along with their default isolation states. Empty when
// |process_lock_| is invalid, or if all BrowsingInstances in the
// SecurityState have been destroyed.
//
// After a process is locked, it might be reused by navigations from frames
// in other BrowsingInstances, e.g., when we're over process limit and when
// those navigations utilize the same process lock. This set tracks all the
// BrowsingInstances that share this process.
//
// This is needed for security checks on the IO thread, where we only know
// the process ID and need to compute the expected origin lock, which
// requires knowing the set of applicable isolated origins in each respective
// BrowsingInstance.
BrowsingInstanceInfoMap browsing_instance_info_map_;
// The maximum number of BrowsingInstances that have been in this
// SecurityState's RenderProcessHost, for metrics.
unsigned max_browsing_instance_count_ = 0;
// The set of isolated filesystems the child process is permitted to access.
FileSystemMap filesystem_permissions_;
raw_ptr<BrowserContext> browser_context_;
raw_ptr<ResourceContext, DanglingAcrossTasks> resource_context_;
};
// IsolatedOriginEntry implementation.
ChildProcessSecurityPolicyImpl::IsolatedOriginEntry::IsolatedOriginEntry(
const url::Origin& origin,
bool applies_to_future_browsing_instances,
BrowsingInstanceId browsing_instance_id,
BrowserContext* browser_context,
ResourceContext* resource_context,
bool isolate_all_subdomains,
IsolatedOriginSource source)
: origin_(origin),
applies_to_future_browsing_instances_(
applies_to_future_browsing_instances),
browsing_instance_id_(browsing_instance_id),
browser_context_(browser_context),
resource_context_(resource_context),
isolate_all_subdomains_(isolate_all_subdomains),
source_(source) {
// If there is a BrowserContext, there must also be a ResourceContext
// associated with this entry.
DCHECK_EQ(!browser_context, !resource_context);
}
ChildProcessSecurityPolicyImpl::IsolatedOriginEntry::IsolatedOriginEntry(
const IsolatedOriginEntry& other) = default;
ChildProcessSecurityPolicyImpl::IsolatedOriginEntry&
ChildProcessSecurityPolicyImpl::IsolatedOriginEntry::operator=(
const IsolatedOriginEntry& other) = default;
ChildProcessSecurityPolicyImpl::IsolatedOriginEntry::IsolatedOriginEntry(
IsolatedOriginEntry&& other) = default;
ChildProcessSecurityPolicyImpl::IsolatedOriginEntry&
ChildProcessSecurityPolicyImpl::IsolatedOriginEntry::operator=(
IsolatedOriginEntry&& other) = default;
ChildProcessSecurityPolicyImpl::IsolatedOriginEntry::~IsolatedOriginEntry() =
default;
bool ChildProcessSecurityPolicyImpl::IsolatedOriginEntry::
AppliesToAllBrowserContexts() const {
return !browser_context_;
}
bool ChildProcessSecurityPolicyImpl::IsolatedOriginEntry::MatchesProfile(
const BrowserOrResourceContext& browser_or_resource_context) const {
DCHECK(IsRunningOnExpectedThread());
// Globally isolated origins aren't associated with any particular profile
// and should apply to all profiles.
if (AppliesToAllBrowserContexts())
return true;
if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
return browser_context_ == browser_or_resource_context.ToBrowserContext();
} else if (BrowserThread::CurrentlyOn(BrowserThread::IO)) {
return resource_context_ == browser_or_resource_context.ToResourceContext();
}
NOTREACHED();
return false;
}
bool ChildProcessSecurityPolicyImpl::IsolatedOriginEntry::
MatchesBrowsingInstance(BrowsingInstanceId browsing_instance_id) const {
if (applies_to_future_browsing_instances_)
return browsing_instance_id_ <= browsing_instance_id;
return browsing_instance_id_ == browsing_instance_id;
}
// Make sure BrowsingInstance state is cleaned up after the max amount of time
// RenderProcessHost might stick around for various IncrementKeepAliveRefCount
// calls. For now, track that as the KeepAliveHandleFactory timeout (the current
// longest value) plus the unload timeout, with a bit of an extra margin.
// // TODO(wjmaclean): Refactor IncrementKeepAliveRefCount to track how much
// time is needed rather than leaving the interval open ended, so that we can
// enforce a max delay here and in RenderProcessHost. https://crbug.com/1181838
ChildProcessSecurityPolicyImpl::ChildProcessSecurityPolicyImpl()
: browsing_instance_cleanup_delay_(
RenderProcessHostImpl::kKeepAliveHandleFactoryTimeout +
base::Seconds(2)) {
// We know about these schemes and believe them to be safe.
RegisterWebSafeScheme(url::kHttpScheme);
RegisterWebSafeScheme(url::kHttpsScheme);
#if BUILDFLAG(ENABLE_WEBSOCKETS)
RegisterWebSafeScheme(url::kWsScheme);
RegisterWebSafeScheme(url::kWssScheme);
#endif // BUILDFLAG(ENABLE_WEBSOCKETS)
RegisterWebSafeScheme(url::kDataScheme);
// TODO(nick): https://crbug.com/651534 blob: and filesystem: schemes embed
// other origins, so we should not treat them as web safe. Remove callers of
// IsWebSafeScheme(), and then eliminate the next two lines.
RegisterWebSafeScheme(url::kBlobScheme);
RegisterWebSafeScheme(url::kFileSystemScheme);
// We know about the following pseudo schemes and treat them specially.
RegisterPseudoScheme(url::kAboutScheme);
RegisterPseudoScheme(url::kJavaScriptScheme);
RegisterPseudoScheme(kViewSourceScheme);
RegisterPseudoScheme(kGoogleChromeScheme);
}
ChildProcessSecurityPolicyImpl::~ChildProcessSecurityPolicyImpl() {
}
// static
ChildProcessSecurityPolicy* ChildProcessSecurityPolicy::GetInstance() {
return ChildProcessSecurityPolicyImpl::GetInstance();
}
ChildProcessSecurityPolicyImpl* ChildProcessSecurityPolicyImpl::GetInstance() {
return base::Singleton<ChildProcessSecurityPolicyImpl>::get();
}
void ChildProcessSecurityPolicyImpl::Add(int child_id,
BrowserContext* browser_context) {
DCHECK(browser_context);
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK_NE(child_id, ChildProcessHost::kInvalidUniqueID);
base::AutoLock lock(lock_);
if (security_state_.find(child_id) != security_state_.end()) {
NOTREACHED() << "Add child process at most once.";
return;
}
security_state_[child_id] = std::make_unique<SecurityState>(browser_context);
CHECK(AddProcessReferenceLocked(child_id, /* duplicating_handle */ false));
}
void ChildProcessSecurityPolicyImpl::AddForTesting(
int child_id,
BrowserContext* browser_context) {
Add(child_id, browser_context);
LockProcess(
IsolationContext(BrowsingInstanceId(1), browser_context,
/*is_guest=*/false, /*is_fenced=*/false,
OriginAgentClusterIsolationState::CreateNonIsolated()),
child_id, /*is_process_used=*/false,
ProcessLock::CreateAllowAnySite(
StoragePartitionConfig::CreateDefault(browser_context),
WebExposedIsolationInfo::CreateNonIsolated()));
}
void ChildProcessSecurityPolicyImpl::Remove(int child_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK_NE(child_id, ChildProcessHost::kInvalidUniqueID);
base::AutoLock lock(lock_);
auto state = security_state_.find(child_id);
if (state == security_state_.end())
return;
// Moving the existing SecurityState object into a pending map so
// that we can preserve permission state and avoid mutations to this
// state after Remove() has been called.
pending_remove_state_[child_id] = std::move(state->second);
security_state_.erase(child_id);
RemoveProcessReferenceLocked(child_id);
}
void ChildProcessSecurityPolicyImpl::RegisterWebSafeScheme(
const std::string& scheme) {
base::AutoLock lock(lock_);
DCHECK_EQ(0U, schemes_okay_to_request_in_any_process_.count(scheme))
<< "Add schemes at most once.";
DCHECK_EQ(0U, pseudo_schemes_.count(scheme))
<< "Web-safe implies not pseudo.";
schemes_okay_to_request_in_any_process_.insert(scheme);
schemes_okay_to_commit_in_any_process_.insert(scheme);
}
void ChildProcessSecurityPolicyImpl::RegisterWebSafeIsolatedScheme(
const std::string& scheme,
bool always_allow_in_origin_headers) {
base::AutoLock lock(lock_);
DCHECK_EQ(0U, schemes_okay_to_request_in_any_process_.count(scheme))
<< "Add schemes at most once.";
DCHECK_EQ(0U, pseudo_schemes_.count(scheme))
<< "Web-safe implies not pseudo.";
schemes_okay_to_request_in_any_process_.insert(scheme);
if (always_allow_in_origin_headers)
schemes_okay_to_appear_as_origin_headers_.insert(scheme);
}
bool ChildProcessSecurityPolicyImpl::IsWebSafeScheme(
const std::string& scheme) {
base::AutoLock lock(lock_);
return base::Contains(schemes_okay_to_request_in_any_process_, scheme);
}
void ChildProcessSecurityPolicyImpl::RegisterPseudoScheme(
const std::string& scheme) {
base::AutoLock lock(lock_);
DCHECK_EQ(0U, pseudo_schemes_.count(scheme)) << "Add schemes at most once.";
DCHECK_EQ(0U, schemes_okay_to_request_in_any_process_.count(scheme))
<< "Pseudo implies not web-safe.";
DCHECK_EQ(0U, schemes_okay_to_commit_in_any_process_.count(scheme))
<< "Pseudo implies not web-safe.";
pseudo_schemes_.insert(scheme);
}
bool ChildProcessSecurityPolicyImpl::IsPseudoScheme(
const std::string& scheme) {
base::AutoLock lock(lock_);
return base::Contains(pseudo_schemes_, scheme);
}
void ChildProcessSecurityPolicyImpl::ClearRegisteredSchemeForTesting(
const std::string& scheme) {
base::AutoLock lock(lock_);
schemes_okay_to_request_in_any_process_.erase(scheme);
schemes_okay_to_commit_in_any_process_.erase(scheme);
pseudo_schemes_.erase(scheme);
}
void ChildProcessSecurityPolicyImpl::GrantCommitURL(int child_id,
const GURL& url) {
// Can't grant the capability to commit invalid URLs.
if (!url.is_valid())
return;
// Can't grant the capability to commit pseudo schemes.
if (IsPseudoScheme(url.scheme()))
return;
url::Origin origin = url::Origin::Create(url);
// Blob and filesystem URLs require special treatment; grant access to the
// inner origin they embed instead.
// TODO(dcheng): Can this logic be simplified to just derive an origin up
// front and use that? That probably requires fixing GURL canonicalization of
// blob URLs though. For now, be consistent with how CanRequestURL and
// CanCommitURL normalize.
if (url.SchemeIsBlob() || url.SchemeIsFileSystem()) {
if (IsMalformedBlobUrl(url))
return;
GrantCommitURL(child_id, GURL(origin.Serialize()));
}
// TODO(dcheng): In the future, URLs with opaque origins would ideally carry
// around an origin with them, so we wouldn't need to grant commit access to
// the entire scheme.
if (!origin.opaque())
GrantCommitOrigin(child_id, origin);
// The scheme has already been whitelisted for every child process, so no need
// to do anything else.
if (IsWebSafeScheme(url.scheme()))
return;
base::AutoLock lock(lock_);
auto state = security_state_.find(child_id);
if (state == security_state_.end())
return;
if (origin.opaque()) {
// If it's impossible to grant commit rights to just the origin (among other
// things, URLs with non-standard schemes will be treated as opaque
// origins), then grant access to commit all URLs of that scheme.
state->second->GrantCommitScheme(url.scheme());
} else {
// When the child process has been commanded to request this scheme, grant
// it the capability to request all URLs of that scheme.
state->second->GrantRequestScheme(url.scheme());
}
}
void ChildProcessSecurityPolicyImpl::GrantRequestSpecificFileURL(
int child_id,
const GURL& url) {
if (!url.SchemeIs(url::kFileScheme))
return;
{
base::AutoLock lock(lock_);
auto state = security_state_.find(child_id);
if (state == security_state_.end())
return;
// When the child process has been commanded to request a file:// URL,
// then we grant it the capability for that URL only.
base::FilePath path;
if (net::FileURLToFilePath(url, &path))
state->second->GrantRequestOfSpecificFile(path);
}
}
void ChildProcessSecurityPolicyImpl::GrantReadFile(int child_id,
const base::FilePath& file) {
GrantPermissionsForFile(child_id, file, READ_FILE_GRANT);
}
void ChildProcessSecurityPolicyImpl::GrantCreateReadWriteFile(
int child_id, const base::FilePath& file) {
GrantPermissionsForFile(child_id, file, CREATE_READ_WRITE_FILE_GRANT);
}
void ChildProcessSecurityPolicyImpl::GrantCopyInto(int child_id,
const base::FilePath& dir) {
GrantPermissionsForFile(child_id, dir, COPY_INTO_FILE_GRANT);
}
void ChildProcessSecurityPolicyImpl::GrantDeleteFrom(
int child_id, const base::FilePath& dir) {
GrantPermissionsForFile(child_id, dir, DELETE_FILE_GRANT);
}
void ChildProcessSecurityPolicyImpl::GrantPermissionsForFile(
int child_id, const base::FilePath& file, int permissions) {
base::AutoLock lock(lock_);
auto state = security_state_.find(child_id);
if (state == security_state_.end())
return;
state->second->GrantPermissionsForFile(file, permissions);
}
void ChildProcessSecurityPolicyImpl::RevokeAllPermissionsForFile(
int child_id, const base::FilePath& file) {
base::AutoLock lock(lock_);
auto state = security_state_.find(child_id);
if (state == security_state_.end())
return;
state->second->RevokeAllPermissionsForFile(file);
}
void ChildProcessSecurityPolicyImpl::GrantReadFileSystem(
int child_id, const std::string& filesystem_id) {
GrantPermissionsForFileSystem(child_id, filesystem_id, READ_FILE_GRANT);
}
void ChildProcessSecurityPolicyImpl::GrantWriteFileSystem(
int child_id, const std::string& filesystem_id) {
GrantPermissionsForFileSystem(child_id, filesystem_id, WRITE_FILE_GRANT);
}
void ChildProcessSecurityPolicyImpl::GrantCreateFileForFileSystem(
int child_id, const std::string& filesystem_id) {
GrantPermissionsForFileSystem(child_id, filesystem_id, CREATE_NEW_FILE_GRANT);
}
void ChildProcessSecurityPolicyImpl::GrantCreateReadWriteFileSystem(
int child_id, const std::string& filesystem_id) {
GrantPermissionsForFileSystem(
child_id, filesystem_id, CREATE_READ_WRITE_FILE_GRANT);
}
void ChildProcessSecurityPolicyImpl::GrantCopyIntoFileSystem(
int child_id, const std::string& filesystem_id) {
GrantPermissionsForFileSystem(child_id, filesystem_id, COPY_INTO_FILE_GRANT);
}
void ChildProcessSecurityPolicyImpl::GrantDeleteFromFileSystem(
int child_id, const std::string& filesystem_id) {
GrantPermissionsForFileSystem(child_id, filesystem_id, DELETE_FILE_GRANT);
}
void ChildProcessSecurityPolicyImpl::GrantSendMidiMessage(int child_id) {
if (base::FeatureList::IsEnabled(
permissions::features::kBlockMidiByDefault)) {
base::AutoLock lock(lock_);
auto state = security_state_.find(child_id);
if (state == security_state_.end()) {
return;
}
state->second->GrantPermissionForMidi();
}
return;
}
void ChildProcessSecurityPolicyImpl::GrantSendMidiSysExMessage(int child_id) {
base::AutoLock lock(lock_);
auto state = security_state_.find(child_id);
if (state == security_state_.end())
return;
state->second->GrantPermissionForMidiSysEx();
}
void ChildProcessSecurityPolicyImpl::GrantCommitOrigin(
int child_id,
const url::Origin& origin) {
base::AutoLock lock(lock_);
auto state = security_state_.find(child_id);
if (state == security_state_.end())
return;
state->second->GrantCommitOrigin(origin);
}
void ChildProcessSecurityPolicyImpl::GrantRequestOrigin(
int child_id,
const url::Origin& origin) {
base::AutoLock lock(lock_);
auto state = security_state_.find(child_id);
if (state == security_state_.end())
return;
state->second->GrantRequestOrigin(origin);
}
void ChildProcessSecurityPolicyImpl::GrantRequestScheme(
int child_id,
const std::string& scheme) {
base::AutoLock lock(lock_);
auto state = security_state_.find(child_id);
if (state == security_state_.end())
return;
state->second->GrantRequestScheme(scheme);
}
void ChildProcessSecurityPolicyImpl::GrantWebUIBindings(int child_id,
int bindings) {
// Only WebUI bindings should come through here.
CHECK(bindings & kWebUIBindingsPolicyMask);
CHECK_EQ(0, bindings & ~kWebUIBindingsPolicyMask);
base::AutoLock lock(lock_);
auto state = security_state_.find(child_id);
if (state == security_state_.end())
return;
state->second->GrantBindings(bindings);
}
void ChildProcessSecurityPolicyImpl::GrantReadRawCookies(int child_id) {
base::AutoLock lock(lock_);
auto state = security_state_.find(child_id);
if (state == security_state_.end())
return;
state->second->GrantReadRawCookies();
}
void ChildProcessSecurityPolicyImpl::RevokeReadRawCookies(int child_id) {
base::AutoLock lock(lock_);
auto state = security_state_.find(child_id);
if (state == security_state_.end())
return;
state->second->RevokeReadRawCookies();
}
bool ChildProcessSecurityPolicyImpl::CanRequestURL(
int child_id, const GURL& url) {
if (!url.is_valid())
return false; // Can't request invalid URLs.
const std::string& scheme = url.scheme();
// Every child process can request <about:blank>, <about:blank?foo>,
// <about:blank/#foo> and <about:srcdoc>.
//
// URLs like <about:version>, <about:crash>, <view-source:...> shouldn't be
// requestable by any child process. Also, this case covers
// <javascript:...>, which should be handled internally by the process and
// not kicked up to the browser.
// TODO(dcheng): Figure out why this check is different from CanCommitURL,
// which checks for direct equality with kAboutBlankURL.
if (IsPseudoScheme(scheme))
return url.IsAboutBlank() || url.IsAboutSrcdoc();
// Blob and filesystem URLs require special treatment; validate the inner
// origin they embed.
if (url.SchemeIsBlob() || url.SchemeIsFileSystem()) {
if (IsMalformedBlobUrl(url))
return false;
url::Origin origin = url::Origin::Create(url);
return origin.opaque() || CanRequestURL(child_id, GURL(origin.Serialize()));
}
if (IsWebSafeScheme(scheme))
return true;
{
base::AutoLock lock(lock_);
auto state = security_state_.find(child_id);
if (state == security_state_.end())
return false;
// Otherwise, we consult the child process's security state to see if it is
// allowed to request the URL.
if (state->second->CanRequestURL(url))
return true;
}
// If |url| has WebUI scheme, the process must usually be locked, unless
// running in single-process mode. Since this is a check whether the process
// can request |url|, the check must operate based on scheme because one WebUI
// should be able to request subresources from another WebUI of the same
// scheme.
const auto& webui_schemes = URLDataManagerBackend::GetWebUISchemes();
if (!RenderProcessHost::run_renderer_in_process() &&
base::Contains(webui_schemes, url.scheme())) {
bool should_be_locked =
GetContentClient()->browser()->DoesWebUIUrlRequireProcessLock(url);
if (should_be_locked) {
const ProcessLock lock = GetProcessLock(child_id);
if (!lock.is_locked_to_site() || !lock.matches_scheme(url.scheme()))
return false;
}
}
// Also allow URLs destined for ShellExecute and not the browser itself.
return !GetContentClient()->browser()->IsHandledURL(url);
}
bool ChildProcessSecurityPolicyImpl::CanRedirectToURL(const GURL& url) {
if (!url.is_valid())
return false; // Can't redirect to invalid URLs.
const std::string& scheme = url.scheme();
// Can't redirect to error pages.
if (scheme == kChromeErrorScheme)
return false;
if (IsPseudoScheme(scheme)) {
// Redirects to a pseudo scheme (about, javascript, view-source, ...) are
// not allowed. An exception is made for <about:blank> and its variations.
return url.IsAboutBlank();
}
// Note about redirects and special URLs:
// * data-url: Blocked by net::DataProtocolHandler::IsSafeRedirectTarget().
// * filesystem-url: Blocked by
// storage::FilesystemProtocolHandler::IsSafeRedirectTarget().
// Depending on their inner origins and if the request is browser-initiated or
// renderer-initiated, blob-urls might get blocked by CanCommitURL or in
// DocumentLoader::RedirectReceived. If not blocked, a 'file not found'
// response will be generated in net::BlobURLRequestJob::DidStart().
return true;
}
bool ChildProcessSecurityPolicyImpl::CanCommitURL(int child_id,
const GURL& url) {
if (!url.is_valid())
return false; // Can't commit invalid URLs.
const std::string& scheme = url.scheme();
// Of all the pseudo schemes, only about:blank and about:srcdoc are allowed to
// commit.
if (IsPseudoScheme(scheme))
return url.IsAboutBlank() || url.IsAboutSrcdoc();
// Blob and filesystem URLs require special treatment; validate the inner
// origin they embed.
if (url.SchemeIsBlob() || url.SchemeIsFileSystem()) {
if (IsMalformedBlobUrl(url))
return false;
url::Origin origin = url::Origin::Create(url);
return origin.opaque() || CanCommitURL(child_id, GURL(origin.Serialize()));
}
// With site isolation, a URL from a site may only be committed in a process
// dedicated to that site. This check will ensure that |url| can't commit if
// the process is locked to a different site.
if (!CanAccessDataForMaybeOpaqueOrigin(
child_id, url, false /* url_is_precursor_of_opaque_origin */))
return false;
{
base::AutoLock lock(lock_);
// Most schemes can commit in any process. Note that we check
// schemes_okay_to_commit_in_any_process_ here, which is stricter than
// IsWebSafeScheme().
//
// TODO(creis, nick): https://crbug.com/515309: The line below does not
// enforce that http pages cannot commit in an extension process.
if (base::Contains(schemes_okay_to_commit_in_any_process_, scheme))
return true;
auto* state = GetSecurityState(child_id);
if (!state)
return false;
// Otherwise, we consult the child process's security state to see if it is
// allowed to commit the URL.
return state->CanCommitURL(url);
}
}
bool ChildProcessSecurityPolicyImpl::CanReadFile(int child_id,
const base::FilePath& file) {
return HasPermissionsForFile(child_id, file, READ_FILE_GRANT);
}
bool ChildProcessSecurityPolicyImpl::CanReadAllFiles(
int child_id,
const std::vector<base::FilePath>& files) {
return base::ranges::all_of(files,
[this, child_id](const base::FilePath& file) {
return CanReadFile(child_id, file);
});
}
bool ChildProcessSecurityPolicyImpl::CanReadRequestBody(
int child_id,
const storage::FileSystemContext* file_system_context,
const scoped_refptr<network::ResourceRequestBody>& body) {
if (!body)
return true;
for (const network::DataElement& element : *body->elements()) {
switch (element.type()) {
case network::DataElement::Tag::kFile:
if (!CanReadFile(child_id,
element.As<network::DataElementFile>().path()))
return false;
break;
case network::DataElement::Tag::kBytes:
// Data is self-contained within |body| - no need to check access.
break;
case network::DataElement::Tag::kDataPipe:
// Data is self-contained within |body| - no need to check access.
break;
default:
// Fail safe - deny access.
NOTREACHED();
return false;
}
}
return true;
}
bool ChildProcessSecurityPolicyImpl::CanReadRequestBody(
RenderProcessHost* process,
const scoped_refptr<network::ResourceRequestBody>& body) {
CHECK(process);
DCHECK_CURRENTLY_ON(BrowserThread::UI);
return CanReadRequestBody(
process->GetID(), process->GetStoragePartition()->GetFileSystemContext(),
body);
}
bool ChildProcessSecurityPolicyImpl::CanCreateReadWriteFile(
int child_id,
const base::FilePath& file) {
return HasPermissionsForFile(child_id, file, CREATE_READ_WRITE_FILE_GRANT);
}
bool ChildProcessSecurityPolicyImpl::CanReadFileSystem(
int child_id, const std::string& filesystem_id) {
return HasPermissionsForFileSystem(child_id, filesystem_id, READ_FILE_GRANT);
}
bool ChildProcessSecurityPolicyImpl::CanReadWriteFileSystem(
int child_id, const std::string& filesystem_id) {
return HasPermissionsForFileSystem(child_id, filesystem_id,
READ_FILE_GRANT | WRITE_FILE_GRANT);
}
bool ChildProcessSecurityPolicyImpl::CanCopyIntoFileSystem(
int child_id, const std::string& filesystem_id) {
return HasPermissionsForFileSystem(child_id, filesystem_id,
COPY_INTO_FILE_GRANT);
}
bool ChildProcessSecurityPolicyImpl::CanDeleteFromFileSystem(
int child_id, const std::string& filesystem_id) {
return HasPermissionsForFileSystem(child_id, filesystem_id,
DELETE_FILE_GRANT);
}
bool ChildProcessSecurityPolicyImpl::HasPermissionsForFile(
int child_id, const base::FilePath& file, int permissions) {
base::AutoLock lock(lock_);
return ChildProcessHasPermissionsForFile(child_id, file, permissions);
}
bool ChildProcessSecurityPolicyImpl::HasPermissionsForFileSystemFile(
int child_id,
const storage::FileSystemURL& filesystem_url,
int permissions) {
if (!filesystem_url.is_valid())
return false;
if (filesystem_url.path().ReferencesParent())
return false;
// Any write access is disallowed on the root path.
if (storage::VirtualPath::IsRootPath(filesystem_url.path()) &&
(permissions & ~READ_FILE_GRANT)) {
return false;
}
if (filesystem_url.mount_type() == storage::kFileSystemTypeIsolated) {
// When Isolated filesystems is overlayed on top of another filesystem,
// its per-filesystem permission overrides the underlying filesystem
// permissions).
return HasPermissionsForFileSystem(
child_id, filesystem_url.mount_filesystem_id(), permissions);
}
// If |filesystem_url.origin()| is not committable in this process, then this
// page should not be able to place content in that origin via the filesystem
// API either.
// TODO(lukasza): Audit whether CanAccessDataForOrigin can be used directly
// here.
if (!CanCommitURL(child_id, filesystem_url.origin().GetURL()))
return false;
int found_permissions = 0;
{
base::AutoLock lock(lock_);
auto found = file_system_policy_map_.find(filesystem_url.type());
if (found == file_system_policy_map_.end())
return false;
found_permissions = found->second;
}
if ((found_permissions & storage::FILE_PERMISSION_READ_ONLY) &&
permissions & ~READ_FILE_GRANT) {
return false;
}
// Note that HasPermissionsForFile (called below) will internally acquire the
// |lock_|, therefore the |lock_| has to be released before the call (since
// base::Lock is not reentrant).
if (found_permissions & storage::FILE_PERMISSION_USE_FILE_PERMISSION)
return HasPermissionsForFile(child_id, filesystem_url.path(), permissions);
if (found_permissions & storage::FILE_PERMISSION_SANDBOX)
return true;
return false;
}
bool ChildProcessSecurityPolicyImpl::CanReadFileSystemFile(
int child_id,
const storage::FileSystemURL& filesystem_url) {
return HasPermissionsForFileSystemFile(child_id, filesystem_url,
READ_FILE_GRANT);
}
bool ChildProcessSecurityPolicyImpl::CanWriteFileSystemFile(
int child_id,
const storage::FileSystemURL& filesystem_url) {
return HasPermissionsForFileSystemFile(child_id, filesystem_url,
WRITE_FILE_GRANT);
}
bool ChildProcessSecurityPolicyImpl::CanCreateFileSystemFile(
int child_id,
const storage::FileSystemURL& filesystem_url) {
return HasPermissionsForFileSystemFile(child_id, filesystem_url,
CREATE_NEW_FILE_GRANT);
}
bool ChildProcessSecurityPolicyImpl::CanCreateReadWriteFileSystemFile(
int child_id,
const storage::FileSystemURL& filesystem_url) {
return HasPermissionsForFileSystemFile(child_id, filesystem_url,
CREATE_READ_WRITE_FILE_GRANT);
}
bool ChildProcessSecurityPolicyImpl::CanCopyIntoFileSystemFile(
int child_id,
const storage::FileSystemURL& filesystem_url) {
return HasPermissionsForFileSystemFile(child_id, filesystem_url,
COPY_INTO_FILE_GRANT);
}
bool ChildProcessSecurityPolicyImpl::CanDeleteFileSystemFile(
int child_id,
const storage::FileSystemURL& filesystem_url) {
return HasPermissionsForFileSystemFile(child_id, filesystem_url,
DELETE_FILE_GRANT);
}
bool ChildProcessSecurityPolicyImpl::CanMoveFileSystemFile(
int child_id,
const storage::FileSystemURL& src_url,
const storage::FileSystemURL& dest_url) {
return HasPermissionsForFileSystemFile(child_id, dest_url,
CREATE_NEW_FILE_GRANT) &&
HasPermissionsForFileSystemFile(child_id, src_url, READ_FILE_GRANT) &&
HasPermissionsForFileSystemFile(child_id, src_url, DELETE_FILE_GRANT);
}
bool ChildProcessSecurityPolicyImpl::CanCopyFileSystemFile(
int child_id,
const storage::FileSystemURL& src_url,
const storage::FileSystemURL& dest_url) {
return HasPermissionsForFileSystemFile(child_id, src_url, READ_FILE_GRANT) &&
HasPermissionsForFileSystemFile(child_id, dest_url,
COPY_INTO_FILE_GRANT);
}
bool ChildProcessSecurityPolicyImpl::HasWebUIBindings(int child_id) {
base::AutoLock lock(lock_);
auto state = security_state_.find(child_id);
if (state == security_state_.end())
return false;
return state->second->has_web_ui_bindings();
}
bool ChildProcessSecurityPolicyImpl::CanReadRawCookies(int child_id) {
base::AutoLock lock(lock_);
auto state = security_state_.find(child_id);
if (state == security_state_.end())
return false;
return state->second->can_read_raw_cookies();
}
bool ChildProcessSecurityPolicyImpl::ChildProcessHasPermissionsForFile(
int child_id, const base::FilePath& file, int permissions) {
auto* state = GetSecurityState(child_id);
if (!state)
return false;
return state->HasPermissionsForFile(file, permissions);
}
size_t ChildProcessSecurityPolicyImpl::BrowsingInstanceIdCountForTesting(
int child_id) {
base::AutoLock lock(lock_);
SecurityState* security_state = GetSecurityState(child_id);
if (security_state)
return security_state->browsing_instance_info().size();
return 0;
}
CanCommitStatus ChildProcessSecurityPolicyImpl::CanCommitOriginAndUrl(
int child_id,
const IsolationContext& isolation_context,
const UrlInfo& url_info) {
DCHECK(url_info.origin.has_value());
const url::Origin url_origin =
url::Origin::Resolve(url_info.url, *url_info.origin);
if (!CanAccessDataForOrigin(child_id, url_origin)) {
// Check for special cases, like blob:null/ and data: URLs, where the
// origin does not contain information to match against the process lock,
// but using the whole URL can result in a process lock match. Note that
// the origin being committed in `url_info.origin` will not actually be
// used when computing `expected_process_lock` below in many cases; see
// https://crbug.com/1320402.
const auto expected_process_lock =
ProcessLock::Create(isolation_context, url_info);
const ProcessLock& actual_process_lock = GetProcessLock(child_id);
if (actual_process_lock == expected_process_lock)
return CanCommitStatus::CAN_COMMIT_ORIGIN_AND_URL;
return CanCommitStatus::CANNOT_COMMIT_URL;
}
if (!CanAccessDataForOrigin(child_id, *url_info.origin))
return CanCommitStatus::CANNOT_COMMIT_ORIGIN;
// Ensure that the origin derived from |url| is consistent with |origin|.
// Note: We can't use origin.IsSameOriginWith() here because opaque origins
// with precursors may have different nonce values.
const auto url_tuple_or_precursor_tuple =
url_origin.GetTupleOrPrecursorTupleIfOpaque();
const auto origin_tuple_or_precursor_tuple =
url_info.origin->GetTupleOrPrecursorTupleIfOpaque();
if (url_tuple_or_precursor_tuple.IsValid() &&
origin_tuple_or_precursor_tuple.IsValid() &&
origin_tuple_or_precursor_tuple != url_tuple_or_precursor_tuple) {
// Allow a WebView specific exception for origins that have a data scheme.
// WebView converts data: URLs into non-opaque data:// origins which is
// different than what all other builds do. This causes the consistency
// check to fail because we try to compare a data:// origin with an opaque
// origin that contains precursor info.
if (url_tuple_or_precursor_tuple.scheme() == url::kDataScheme &&
url::AllowNonStandardSchemesForAndroidWebView()) {
return CanCommitStatus::CAN_COMMIT_ORIGIN_AND_URL;
}
return CanCommitStatus::CANNOT_COMMIT_ORIGIN;
}
return CanCommitStatus::CAN_COMMIT_ORIGIN_AND_URL;
}
bool ChildProcessSecurityPolicyImpl::CanAccessDataForOrigin(
int child_id,
const url::Origin& origin) {
if (ShouldRestrictCanAccessDataForOriginToUIThread()) {
// Ensure this is only called on the UI thread, which is the only thread
// with sufficient information to do the full set of checks.
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
} else {
// For legacy cases, this may be called on multiple threads.
DCHECK(IsRunningOnExpectedThread());
}
GURL url_to_check;
if (origin.opaque()) {
auto precursor_tuple = origin.GetTupleOrPrecursorTupleIfOpaque();
if (!precursor_tuple.IsValid()) {
// Allow opaque origins w/o precursors (if the security state exists).
// TODO(acolwell): Investigate all cases that trigger this path (e.g.,
// browser-initiated navigations to data: URLs) and fix them so we have
// precursor information (or the process lock is compatible with a missing
// precursor). Remove this logic once that has been completed.
base::AutoLock lock(lock_);
SecurityState* security_state = GetSecurityState(child_id);
return !!security_state;
} else {
url_to_check = precursor_tuple.GetURL();
}
} else {
url_to_check = origin.GetURL();
}
bool success = CanAccessDataForMaybeOpaqueOrigin(child_id, url_to_check,
origin.opaque());
if (success)
return true;
// Note: LogCanAccessDataForOriginCrashKeys() is called in the
// CanAccessDataForOrigin() call above. The code below overrides the origin
// crash key set in that call with data from |origin| because it provides
// more accurate information than the origin derived from |url_to_check|.
auto* requested_origin_key = GetRequestedOriginCrashKey();
base::debug::SetCrashKeyString(requested_origin_key, origin.GetDebugString());
return false;
}
bool ChildProcessSecurityPolicyImpl::CanAccessDataForMaybeOpaqueOrigin(
int child_id,
const GURL& url,
bool url_is_precursor_of_opaque_origin) {
if (ShouldRestrictCanAccessDataForOriginToUIThread()) {
// Ensure this is only called on the UI thread, which is the only thread
// with sufficient information to do the full set of checks.
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
} else {
// For legacy cases, this may be called on multiple threads.
DCHECK(IsRunningOnExpectedThread());
}
base::AutoLock lock(lock_);
SecurityState* security_state = GetSecurityState(child_id);
BrowserOrResourceContext browser_or_resource_context;
if (security_state)
browser_or_resource_context = security_state->GetBrowserOrResourceContext();
ProcessLock expected_process_lock;
std::string failure_reason;
if (!security_state) {
failure_reason = "no_security_state";
} else if (!browser_or_resource_context) {
failure_reason = "no_browser_or_resource_context";
} else {
ProcessLock actual_process_lock = security_state->process_lock();
// Deny access if the process is unlocked. An unlocked process means that
// the process has not been associated with a SiteInstance yet and therefore
// this request is likely invalid.
if (actual_process_lock.is_invalid()) {
failure_reason = "process_lock_is_invalid";
} else {
// Loop over all BrowsingInstanceIDs in the SecurityState, and return true
// if any of them would return true, otherwise return false. This allows
// the checks to be slightly stricter in cases where all BrowsingInstances
// agree (e.g., whether an origin is considered isolated and thus
// inaccessible from a site-locked process). When the BrowsingInstances
// do not agree, the check might be slightly weaker (as the least common
// denominator), but the differences must never violate the ProcessLock.
if (security_state->browsing_instance_info().empty()) {
// If no BrowsingInstances are found, then the some of the state we need
// to perform an accurate check is unexpectedly missing, because there
// should always be a BrowsingInstance for such requests, even from
// workers. Thus, we should usually kill the process in this case, so
// that a compromised renderer can't bypass checks by sending IPCs when
// no BrowsingInstances are left.
//
// However, if the requested `url` is compatible with the current
// ProcessLock, then there is no need to kill the process because the
// checks would have passed anyway. To reduce the number of crashes
// while we debug why no BrowsingInstances were found (in
// https://crbug.com/1148542), we'll allow requests with an acceptable
// process lock to proceed.
// TODO(1148542): Remove this when known cases of having no
// BrowsingInstance IDs are solved.
url::Origin origin(url::Origin::Create(url));
bool matches_origin_keyed_process =
actual_process_lock.is_origin_keyed_process() &&
actual_process_lock.lock_url() == origin.GetURL();
bool matches_site_keyed_process =
!actual_process_lock.is_origin_keyed_process() &&
actual_process_lock.lock_url() ==
SiteInfo::GetSiteForOrigin(origin);
// ProcessLocks with is_pdf() = true actually means that the process is
// not supposed to access certain resources from the lock's site/origin,
// so it's safest here to fall through in that case. See discussion of
// https://crbug.com/1271197 below.
if (!actual_process_lock.is_pdf()) {
// If the ProcessLock isn't locked to a site, we should fall through
// since we have no way of knowing if the requested url was expecting
// to be in a locked process.
if (actual_process_lock.is_locked_to_site()) {
if (matches_origin_keyed_process || matches_site_keyed_process) {
return true;
} else {
failure_reason = base::StringPrintf(
"No BrowsingInstanceIDs: Lock Mismatch. lock = %s vs. "
"requested_url = %s ",
actual_process_lock.ToString().c_str(), url.spec().c_str());
}
} else {
failure_reason =
"No BrowsingInstanceIDs: process not locked to site";
}
} else {
failure_reason = "No BrowsingInstanceIDs: process lock is_pdf";
}
// This will fall through to the call to
// LogCanAccessDataForOriginCrashKeys below, then return false.
}
for (auto browsing_instance_info_entry :
security_state->browsing_instance_info()) {
auto& browsing_instance_id = browsing_instance_info_entry.first;
auto& default_isolation_state = browsing_instance_info_entry.second;
// In the case of multiple BrowsingInstances in the SecurityState, note
// that failure reasons will only be reported if none of the
// BrowsingInstances allow access. In that event, |failure_reason|
// contains the concatenated reasons for each BrowsingInstance, each
// prefaced by its id.
failure_reason += base::StringPrintf(
"[BI=%d]", browsing_instance_id.GetUnsafeValue());
// Use the actual process lock's state to compute `is_guest` and
// `is_fenced` for the expected process lock's `isolation_context`.
// Guest status and fenced status doesn't currently influence the
// outcome of this access check, and even if it did, `url` wouldn't be
// sufficient to tell whether the request belongs solely to a guest (or
// non-guest) or fenced process. Note that a guest isn't allowed to
// access data outside of its own StoragePartition, but this is enforced
// by other means (e.g., resource access APIs can't name an alternate
// StoragePartition).
IsolationContext isolation_context(
browsing_instance_id, browser_or_resource_context,
actual_process_lock.is_guest(), actual_process_lock.is_fenced(),
default_isolation_state);
// NOTE: If we're on the IO thread, the call to
// ProcessLock::Create() below will return a ProcessLock with
// an (internally) identical site_url, one that does not use effective
// URLs. That's ok in this instance since we only ever look at the lock
// url.
//
// Since we are dealing with a valid ProcessLock at this point, we know
// the lock contains a valid StoragePartitionConfig and COOP/COEP
// information because that information must be provided when creating
// the locks.
//
// At this point, any origin opt-in isolation requests should be
// complete, so to avoid the possibility of opting something set
// |origin_isolation_request| to kNone below (this happens by default in
// UrlInfoInit's ctor). Note: We might need to revisit this if
// CanAccessDataForOrigin() needs to be called while a SiteInstance is
// being determined for a navigation, i.e. during
// GetSiteInstanceForNavigationRequest(). If this happens, we'd need
// to plumb UrlInfo::origin_isolation_request value from the ongoing
// NavigationRequest into here. Also, we would likely need to attach
// the BrowsingInstanceID to UrlInfo once the SiteInstance has been
// determined in case the RenderProcess has multiple BrowsingInstances
// in it.
// TODO(acolwell): Provide a way for callers, that know their request's
// require COOP/COEP handling, to pass in their COOP/COEP information
// so it can be used here instead of the values in
// |actual_process_lock|.
// TODO(crbug.com/1271197): The code below is subtly incorrect in cases
// where actual_process_lock.is_pdf() is true, since in the case of PDFs
// the lock is intended to prevent access to the lock's site/origin,
// while still allowing the navigation to commit.
expected_process_lock = ProcessLock::Create(
isolation_context,
UrlInfo(UrlInfoInit(url)
.WithStoragePartitionConfig(
actual_process_lock.GetStoragePartitionConfig())
.WithWebExposedIsolationInfo(
actual_process_lock.GetWebExposedIsolationInfo())
.WithIsPdf(actual_process_lock.is_pdf())
.WithSandbox(actual_process_lock.is_sandboxed())
.WithUniqueSandboxId(
actual_process_lock.unique_sandbox_id())));
if (actual_process_lock.is_locked_to_site()) {
// Jail-style enforcement - a process with a lock can only access
// data from origins that require exactly the same lock.
if (actual_process_lock == expected_process_lock)
return true;
// TODO(acolwell, nasko): https://crbug.com/1029092: Ensure the
// precursor of opaque origins matches the renderer's origin lock.
if (url_is_precursor_of_opaque_origin) {
const GURL& lock_url = actual_process_lock.lock_url();
// SitePerProcessBrowserTest
// .TwoBlobURLsWithNullOriginDontShareProcess.
if (lock_url.SchemeIsBlob() &&
base::StartsWith(lock_url.path_piece(), "null/")) {
return true;
}
// DeclarativeApiTest.PersistRules.
if (actual_process_lock.matches_scheme(url::kDataScheme))
return true;
}
// Make an exception to allow most visited tiles to commit in
// third-party NTP processes.
// TODO(crbug.com/566091): This exception should be removed once
// these tiles can be loaded in OOPIFs on the NTP.
if (AllowProcessLockMismatchForNTP(expected_process_lock,
actual_process_lock)) {
return true;
}
// TODO(wjmaclean): We should update the ProcessLock comparison API
// to return a reason why two locks differ.
if (actual_process_lock.lock_url() !=
expected_process_lock.lock_url()) {
failure_reason += "lock_mismatch:url ";
// If the actual lock is same-site to the expected lock, then this
// is an isolated origins mismatch; in that case we add text to
// |failure_reason| to make this case easy to search for.
// Note: We don't compare ports, since the mismatch might be between
// isolated and non-isolated.
url::Origin actual_origin =
url::Origin::Create(actual_process_lock.lock_url());
url::Origin expected_origin =
url::Origin::Create(expected_process_lock.lock_url());
if (actual_process_lock.lock_url() ==
SiteInfo::GetSiteForOrigin(expected_origin) ||
expected_process_lock.lock_url() ==
SiteInfo::GetSiteForOrigin(actual_origin)) {
failure_reason += "[origin vs site mismatch] ";
}
} else {
// TODO(wjmaclean,alexmos): Apparently this might not be true
// anymore, since is_pdf() and web_exposed_isolation_info() have
// been added to the ProcessLock. We need to update the code here to
// differentiate these cases, as well as adding documentation (or
// some other mechanism) to prevent these getting out of sync in
// future.
failure_reason += "lock_mismatch:requires_origin_keyed_process ";
}
} else {
// Citadel-style enforcement - an unlocked process should not be
// able to access data from origins that require a lock.
// Allow the corresponding base::Feature to turn off enforcement.
if (!base::FeatureList::IsEnabled(kSiteIsolationCitadelEnforcement)) {
return true;
}
// Skip these checks on the IO thread, since we can't use
// RenderProcessHost or ShouldLockProcessToSite() there.
//
// TODO(crbug.com/764958): Remove this once this is reachable only on
// the UI thread.
if (!ShouldRestrictCanAccessDataForOriginToUIThread() &&
BrowserThread::CurrentlyOn(BrowserThread::IO)) {
return true;
}
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// TODO(lukasza): Consider making the checks below IO-thread-friendly,
// by storing |is_unused| inside SecurityState.
RenderProcessHost* process = RenderProcessHostImpl::FromID(child_id);
if (process) { // |process| can be null in unittests
// Unlocked process can be legitimately used when navigating from an
// unused process (about:blank, NTP on Android) to an isolated
// origin. See also https://crbug.com/945399. Returning |true|
// below will allow such navigations to succeed (i.e. pass
// CanCommitOriginAndUrl checks). We don't expect unused processes
// to be used outside of navigations (e.g. when checking
// CanAccessDataForOrigin for localStorage, etc.).
if (process->IsUnused())
return true;
}
// See the ProcessLock::Create() call above regarding why we pass
// kNone for |origin_isolation_request| below.
SiteInfo site_info = SiteInfo::Create(
isolation_context,
UrlInfo(UrlInfoInit(url).WithWebExposedIsolationInfo(
actual_process_lock.GetWebExposedIsolationInfo())));
// A process that's not locked to any site can only access data from
// origins that do not require a locked process.
if (!site_info.ShouldLockProcessToSite(isolation_context))
return true;
failure_reason += " citadel_enforcement ";
if (url_is_precursor_of_opaque_origin) {
failure_reason += "for_precursor ";
}
}
}
}
}
// Record the duration of KeepAlive requests to include in the crash keys.
std::string keep_alive_durations;
std::string shutdown_delay_ref_count;
std::string process_rfh_count;
if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
if (auto* process = RenderProcessHostImpl::FromID(child_id)) {
keep_alive_durations = process->GetKeepAliveDurations();
shutdown_delay_ref_count =
base::NumberToString(process->GetShutdownDelayRefCount());
process_rfh_count =
base::NumberToString(process->GetRenderFrameHostCount());
}
} else {
keep_alive_durations = "no durations available: on IO thread.";
}
// Returning false here will result in a renderer kill. Set some crash
// keys that will help understand the circumstances of that kill.
LogCanAccessDataForOriginCrashKeys(
expected_process_lock.ToString(),
GetKilledProcessOriginLock(security_state),
url.DeprecatedGetOriginAsURL().spec(), failure_reason,
keep_alive_durations, shutdown_delay_ref_count, process_rfh_count);
return false;
}
void ChildProcessSecurityPolicyImpl::IncludeIsolationContext(
int child_id,
const IsolationContext& isolation_context) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
base::AutoLock lock(lock_);
auto* state = GetSecurityState(child_id);
DCHECK(state);
state->AddBrowsingInstanceInfo(isolation_context);
}
void ChildProcessSecurityPolicyImpl::LockProcess(
const IsolationContext& context,
int child_id,
bool is_process_used,
const ProcessLock& process_lock) {
// LockProcess should only be called on the UI thread (OTOH, it is okay to
// call GetProcessLock from any thread).
DCHECK_CURRENTLY_ON(BrowserThread::UI);
base::AutoLock lock(lock_);
auto state = security_state_.find(child_id);
DCHECK(state != security_state_.end());
state->second->SetProcessLock(process_lock, context, is_process_used);
}
void ChildProcessSecurityPolicyImpl::LockProcessForTesting(
const IsolationContext& isolation_context,
int child_id,
const GURL& url) {
SiteInfo site_info = SiteInfo::CreateForTesting(isolation_context, url);
LockProcess(isolation_context, child_id, /* is_process_used=*/false,
ProcessLock::FromSiteInfo(site_info));
}
ProcessLock ChildProcessSecurityPolicyImpl::GetProcessLock(int child_id) {
base::AutoLock lock(lock_);
auto state = security_state_.find(child_id);
if (state == security_state_.end())
return ProcessLock();
return state->second->process_lock();
}
void ChildProcessSecurityPolicyImpl::GrantPermissionsForFileSystem(
int child_id,
const std::string& filesystem_id,
int permission) {
base::AutoLock lock(lock_);
auto state = security_state_.find(child_id);
if (state == security_state_.end())
return;
state->second->GrantPermissionsForFileSystem(filesystem_id, permission);
}
bool ChildProcessSecurityPolicyImpl::HasPermissionsForFileSystem(
int child_id,
const std::string& filesystem_id,
int permission) {
base::AutoLock lock(lock_);
auto* state = GetSecurityState(child_id);
if (!state)
return false;
return state->HasPermissionsForFileSystem(filesystem_id, permission);
}
void ChildProcessSecurityPolicyImpl::RegisterFileSystemPermissionPolicy(
storage::FileSystemType type,
int policy) {
base::AutoLock lock(lock_);
file_system_policy_map_[type] = policy;
}
bool ChildProcessSecurityPolicyImpl::CanSendMidiMessage(int child_id) {
base::AutoLock lock(lock_);
auto state = security_state_.find(child_id);
if (state == security_state_.end()) {
return false;
}
return state->second->CanSendMidi();
}
bool ChildProcessSecurityPolicyImpl::CanSendMidiSysExMessage(int child_id) {
base::AutoLock lock(lock_);
auto state = security_state_.find(child_id);
if (state == security_state_.end())
return false;
return state->second->CanSendMidiSysEx();
}
void ChildProcessSecurityPolicyImpl::AddFutureIsolatedOrigins(
const std::vector<url::Origin>& origins_to_add,
IsolatedOriginSource source,
BrowserContext* browser_context) {
std::vector<IsolatedOriginPattern> patterns;
patterns.reserve(origins_to_add.size());
base::ranges::transform(
origins_to_add, std::back_inserter(patterns),
[](const url::Origin& o) { return IsolatedOriginPattern(o); });
AddFutureIsolatedOrigins(patterns, source, browser_context);
}
void ChildProcessSecurityPolicyImpl::AddFutureIsolatedOrigins(
base::StringPiece origins_to_add,
IsolatedOriginSource source,
BrowserContext* browser_context) {
std::vector<IsolatedOriginPattern> patterns =
ParseIsolatedOrigins(origins_to_add);
AddFutureIsolatedOrigins(patterns, source, browser_context);
}
void ChildProcessSecurityPolicyImpl::AddFutureIsolatedOrigins(
const std::vector<IsolatedOriginPattern>& patterns,
IsolatedOriginSource source,
BrowserContext* browser_context) {
// This can only be called from the UI thread, as it reads state that's only
// available (and is only safe to be retrieved) on the UI thread, such as
// BrowsingInstance IDs.
DCHECK_CURRENTLY_ON(BrowserThread::UI);
base::AutoLock isolated_origins_lock(isolated_origins_lock_);
for (const IsolatedOriginPattern& pattern : patterns) {
if (!pattern.is_valid()) {
LOG(ERROR) << "Invalid isolated origin: " << pattern.pattern();
continue;
}
url::Origin origin_to_add = pattern.origin();
// Isolated origins added here should apply only to future
// BrowsingInstances and processes. Determine the first BrowsingInstance
// ID to which they should apply.
BrowsingInstanceId browsing_instance_id =
SiteInstanceImpl::NextBrowsingInstanceId();
AddIsolatedOriginInternal(browser_context, origin_to_add,
true /* applies_to_future_browsing_instances */,
browsing_instance_id,
pattern.isolate_all_subdomains(), source);
}
}
void ChildProcessSecurityPolicyImpl::AddIsolatedOriginInternal(
BrowserContext* browser_context,
const url::Origin& origin_to_add,
bool applies_to_future_browsing_instances,
BrowsingInstanceId browsing_instance_id,
bool isolate_all_subdomains,
IsolatedOriginSource source) {
// GetSiteForOrigin() is used to look up the site URL of |origin| to speed
// up the isolated origin lookup. This only performs a straightforward
// translation of an origin to eTLD+1; it does *not* take into account
// effective URLs, isolated origins, and other logic that's not needed
// here, but *is* typically needed for making process model decisions. Be
// very careful about using GetSiteForOrigin() elsewhere, and consider
// whether you should be using SiteInfo::Create() instead.
GURL key(SiteInfo::GetSiteForOrigin(origin_to_add));
// Check if the origin to be added already exists, in which case it may not
// need to be added again.
bool should_add = true;
for (const auto& entry : isolated_origins_[key]) {
// TODO(alexmos): The exact origin comparison here allows redundant entries
// with certain uses of `isolate_all_subdomains`. See
// https://crbug.com/1184580.
if (entry.origin() != origin_to_add)
continue;
// If the added origin already exists for the same BrowserContext and
// covers the same BrowsingInstances, don't re-add it.
if (entry.browser_context() == browser_context) {
if (entry.applies_to_future_browsing_instances() &&
entry.browsing_instance_id() <= browsing_instance_id) {
// If the existing entry applies to future BrowsingInstances, and it
// has a lower/same BrowsingInstance ID, don't re-add the origin. Note
// that if the new isolated origin is also requested to apply to future
// BrowsingInstances, the threshold ID must necessarily be greater than
// the old ID, since NextBrowsingInstanceId() returns monotonically
// increasing IDs.
if (applies_to_future_browsing_instances)
DCHECK_LE(entry.browsing_instance_id(), browsing_instance_id);
should_add = false;
break;
} else if (!entry.applies_to_future_browsing_instances() &&
entry.browsing_instance_id() == browsing_instance_id) {
// Otherwise, don't re-add the origin if the existing entry is for the
// same BrowsingInstance ID. Note that if an origin had been added for
// a specific BrowsingInstance, we can't later receive a request to
// isolate that origin within future BrowsingInstances that start at
// the same (or lower) BrowsingInstance. Requests to isolate future
// BrowsingInstances should always reference
// SiteInstanceImpl::NextBrowsingInstanceId(), which always refers to
// an ID that's greater than any existing BrowsingInstance ID.
DCHECK(!applies_to_future_browsing_instances);
should_add = false;
break;
}
}
// Otherwise, allow the origin to be added again for a different profile
// (or globally for all profiles), possibly with a different
// BrowsingInstance ID cutoff. Note that a particular origin might have
// multiple entries, each one for a different profile, so we must loop
// over all such existing entries before concluding that |origin| really
// needs to be added.
}
if (should_add) {
ResourceContext* resource_context =
browser_context ? browser_context->GetResourceContext() : nullptr;
IsolatedOriginEntry entry(std::move(origin_to_add),
applies_to_future_browsing_instances,
browsing_instance_id, browser_context,
resource_context, isolate_all_subdomains, source);
isolated_origins_[key].emplace_back(std::move(entry));
}
}
void ChildProcessSecurityPolicyImpl::RemoveStateForBrowserContext(
const BrowserContext& browser_context) {
{
base::AutoLock isolated_origins_lock(isolated_origins_lock_);
for (auto& iter : isolated_origins_) {
base::EraseIf(iter.second,
[&browser_context](const IsolatedOriginEntry& entry) {
// Remove if BrowserContext matches.
return (entry.browser_context() == &browser_context);
});
}
// Also remove map entries for site URLs which no longer have any
// IsolatedOriginEntries remaining.
base::EraseIf(isolated_origins_,
[](const auto& pair) { return pair.second.empty(); });
}
{
base::AutoLock lock(lock_);
for (auto& pair : security_state_)
pair.second->ClearBrowserContextIfMatches(&browser_context);
for (auto& pair : pending_remove_state_)
pair.second->ClearBrowserContextIfMatches(&browser_context);
}
}
bool ChildProcessSecurityPolicyImpl::IsIsolatedOrigin(
const IsolationContext& isolation_context,
const url::Origin& origin,
bool origin_requests_isolation) {
url::Origin unused_result;
return GetMatchingProcessIsolatedOrigin(
isolation_context, origin, origin_requests_isolation, &unused_result);
}
bool ChildProcessSecurityPolicyImpl::IsGloballyIsolatedOriginForTesting(
const url::Origin& origin) {
BrowserOrResourceContext no_browser_context;
BrowsingInstanceId null_browsing_instance_id;
IsolationContext isolation_context(
null_browsing_instance_id, no_browser_context, /*is_guest=*/false,
/*is_fenced=*/false,
OriginAgentClusterIsolationState::CreateNonIsolated());
return IsIsolatedOrigin(isolation_context, origin, false);
}
std::vector<url::Origin> ChildProcessSecurityPolicyImpl::GetIsolatedOrigins(
absl::optional<IsolatedOriginSource> source,
BrowserContext* browser_context) {
std::vector<url::Origin> origins;
base::AutoLock isolated_origins_lock(isolated_origins_lock_);
for (const auto& iter : isolated_origins_) {
for (const auto& isolated_origin_entry : iter.second) {
if (source && source.value() != isolated_origin_entry.source())
continue;
// If browser_context is specified, ensure that the entry matches it. If
// the browser_context is not specified, only consider entries that are
// not associated with a profile (i.e., which apply globally to the
// entire browser).
bool matches_profile =
browser_context ? isolated_origin_entry.MatchesProfile(
BrowserOrResourceContext(browser_context))
: isolated_origin_entry.AppliesToAllBrowserContexts();
if (!matches_profile)
continue;
// Do not include origins that only apply to specific BrowsingInstances.
if (!isolated_origin_entry.applies_to_future_browsing_instances())
continue;
origins.push_back(isolated_origin_entry.origin());
}
}
return origins;
}
bool ChildProcessSecurityPolicyImpl::IsIsolatedSiteFromSource(
const url::Origin& origin,
IsolatedOriginSource source) {
base::AutoLock isolated_origins_lock(isolated_origins_lock_);
GURL site_url = SiteInfo::GetSiteForOrigin(origin);
auto it = isolated_origins_.find(site_url);
if (it == isolated_origins_.end())
return false;
url::Origin site_origin = url::Origin::Create(site_url);
for (const auto& entry : it->second) {
if (entry.source() == source && entry.origin() == site_origin)
return true;
}
return false;
}
bool ChildProcessSecurityPolicyImpl::GetMatchingProcessIsolatedOrigin(
const IsolationContext& isolation_context,
const url::Origin& origin,
bool requests_origin_keyed_process,
url::Origin* result) {
// GetSiteForOrigin() is used to look up the site URL of |origin| to speed
// up the isolated origin lookup. This only performs a straightforward
// translation of an origin to eTLD+1; it does *not* take into account
// effective URLs, isolated origins, and other logic that's not needed
// here, but *is* typically needed for making process model decisions. Be
// very careful about using GetSiteForOrigin() elsewhere, and consider
// whether you should be using GetSiteForURL() instead.
return GetMatchingProcessIsolatedOrigin(
isolation_context, origin, requests_origin_keyed_process,
SiteInfo::GetSiteForOrigin(origin), result);
}
bool ChildProcessSecurityPolicyImpl::GetMatchingProcessIsolatedOrigin(
const IsolationContext& isolation_context,
const url::Origin& origin,
bool requests_origin_keyed_process,
const GURL& site_url,
url::Origin* result) {
DCHECK(IsRunningOnExpectedThread());
*result = url::Origin();
base::AutoLock isolated_origins_lock(isolated_origins_lock_);
// If |isolation_context| does not specify a BrowsingInstance ID, then assume
// that we want to retrieve the latest applicable information; i.e., return
// the latest matching isolated origins that would apply to future
// BrowsingInstances. Using NextBrowsingInstanceId() will match all
// available IsolatedOriginEntries.
BrowsingInstanceId browsing_instance_id(
isolation_context.browsing_instance_id());
if (browsing_instance_id.is_null()) {
browsing_instance_id = SiteInstanceImpl::NextBrowsingInstanceId();
} else {
// Check the opt-in isolation status of |origin| in |isolation_context|.
// Note that while IsolatedOrigins considers any sub-origin of an isolated
// origin as also being isolated, with opt-in we will always either return
// false, or true with result set to |origin|. We give priority to origins
// requesting opt-in isolation over command-line isolation, but don't check
// for opt-in if we didn't get a valid BrowsingInstance id.
// Note: This should only return a full origin if we are doing
// process-isolated Origin-keyed Agent Clusters, which will only be the case
// when site-isolation is enabled. Otherwise we put the origin into its
// corresponding site, even if Origin-keyed Agent Clusters will be enabled
// on the renderer side.
// TODO(wjmaclean,alexmos,acolwell): We should revisit this when we have
// SiteInstanceGroups, since at that point we can again return an origin
// here (and thus create a new SiteInstance) even when
// IsProcessIsolationForOriginAgentClusterEnabled() returns false; in that
// case a SiteInstanceGroup will allow a logical group of SiteInstances that
// live same-process.
if (SiteIsolationPolicy::IsProcessIsolationForOriginAgentClusterEnabled()) {
OriginAgentClusterIsolationState oac_isolation_state_request =
requests_origin_keyed_process
? OriginAgentClusterIsolationState::CreateForOriginAgentCluster(
true /* requires_origin_keyed_process */)
: OriginAgentClusterIsolationState::CreateNonIsolated();
OriginAgentClusterIsolationState oac_isolation_state_result =
DetermineOriginAgentClusterIsolation(isolation_context, origin,
oac_isolation_state_request);
if (oac_isolation_state_result.requires_origin_keyed_process()) {
*result = origin;
return true;
}
}
}
// Look up the list of origins corresponding to |origin|'s site.
auto it = isolated_origins_.find(site_url);
// Subtle corner case: if the site's host ends with a dot, do the lookup
// without it. A trailing dot shouldn't be able to bypass isolated origins:
// if "https://foo.com" is an isolated origin, "https://foo.com." should
// match it.
if (it == isolated_origins_.end() && site_url.has_host() &&
site_url.host_piece().back() == '.') {
GURL::Replacements replacements;
base::StringPiece host(site_url.host_piece());
host.remove_suffix(1);
replacements.SetHostStr(host);
it = isolated_origins_.find(site_url.ReplaceComponents(replacements));
}
// Looks for all isolated origins that were already isolated at the time
// |isolation_context| was created. If multiple isolated origins are
// registered with a common domain suffix, return the most specific one. For
// example, if foo.isolated.com and isolated.com are both isolated origins,
// bar.foo.isolated.com should return foo.isolated.com.
bool found = false;
if (it != isolated_origins_.end()) {
for (const auto& isolated_origin_entry : it->second) {
// If this isolated origin applies only to a specific profile, don't
// use it for a different profile.
if (!isolated_origin_entry.MatchesProfile(
isolation_context.browser_or_resource_context()))
continue;
if (isolated_origin_entry.MatchesBrowsingInstance(browsing_instance_id) &&
IsolatedOriginUtil::DoesOriginMatchIsolatedOrigin(
origin, isolated_origin_entry.origin())) {
// If a match has been found that requires all subdomains to be isolated
// then return immediately. |origin| is returned to ensure proper
// process isolation, e.g. https://a.b.c.isolated.com matches an
// IsolatedOriginEntry constructed from http://[*.]isolated.com, so
// https://a.b.c.isolated.com must be returned.
if (isolated_origin_entry.isolate_all_subdomains()) {
*result = origin;
uint16_t default_port = url::DefaultPortForScheme(
origin.scheme().data(), origin.scheme().length());
if (origin.port() != default_port) {
*result = url::Origin::Create(GURL(origin.scheme() +
url::kStandardSchemeSeparator +
origin.host()));
}
return true;
}
if (!found || result->host().length() <
isolated_origin_entry.origin().host().length()) {
*result = isolated_origin_entry.origin();
found = true;
}
}
}
}
return found;
}
OriginAgentClusterIsolationState
ChildProcessSecurityPolicyImpl::DetermineOriginAgentClusterIsolation(
const IsolationContext& isolation_context,
const url::Origin& origin,
const OriginAgentClusterIsolationState& requested_isolation_state) {
if (!IsolatedOriginUtil::IsValidOriginForOptInIsolation(origin))
return OriginAgentClusterIsolationState::CreateNonIsolated();
// See if the same origin exists in the BrowsingInstance already, and if so
// return its isolation status.
// There are two cases we're worried about here: (i) we've previously seen the
// origin and isolated it, in which case we should continue to isolate it, and
// (ii) we've previously seen the origin and *not* isolated it, in which case
// we should continue to not isolate it.
BrowsingInstanceId browsing_instance_id(
isolation_context.browsing_instance_id());
if (!browsing_instance_id.is_null()) {
base::AutoLock origins_isolation_opt_in_lock(
origins_isolation_opt_in_lock_);
// Look for |origin| in the isolation status list.
OriginAgentClusterIsolationState* oac_isolation_state =
LookupOriginIsolationState(browsing_instance_id, origin);
if (oac_isolation_state)
return *oac_isolation_state;
}
// If we get to this point, then |origin| is neither opted-in nor opted-out.
// At this point we allow opting in if it's requested. This is true for
// either logical OriginAgentCluster, or OriginAgentCluster with an
// origin-keyed process.
return requested_isolation_state;
}
bool ChildProcessSecurityPolicyImpl::
HasOriginEverRequestedOriginAgentClusterValue(
BrowserContext* browser_context,
const url::Origin& origin) {
base::AutoLock origins_isolation_opt_in_lock(origins_isolation_opt_in_lock_);
return base::Contains(origin_isolation_opt_ins_and_outs_, browser_context) &&
base::Contains(origin_isolation_opt_ins_and_outs_[browser_context],
origin);
}
OriginAgentClusterIsolationState*
ChildProcessSecurityPolicyImpl::LookupOriginIsolationState(
const BrowsingInstanceId& browsing_instance_id,
const url::Origin& origin) {
auto it_isolation_by_browsing_instance =
origin_isolation_by_browsing_instance_.find(browsing_instance_id);
if (it_isolation_by_browsing_instance ==
origin_isolation_by_browsing_instance_.end()) {
return nullptr;
}
auto& origin_list = it_isolation_by_browsing_instance->second;
auto it_origin_list = base::ranges::find(
origin_list, origin, &OriginAgentClusterOptInEntry::origin);
if (it_origin_list != origin_list.end())
return &(it_origin_list->oac_isolation_state);
return nullptr;
}
OriginAgentClusterIsolationState*
ChildProcessSecurityPolicyImpl::LookupOriginIsolationStateForTesting(
const BrowsingInstanceId& browsing_instance_id,
const url::Origin& origin) {
base::AutoLock lock(origins_isolation_opt_in_lock_);
return LookupOriginIsolationState(browsing_instance_id, origin);
}
void ChildProcessSecurityPolicyImpl::AddDefaultIsolatedOriginIfNeeded(
const IsolationContext& isolation_context,
const url::Origin& origin,
bool is_global_walk_or_frame_removal) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!IsolatedOriginUtil::IsValidOriginForOptInIsolation(origin))
return;
BrowsingInstanceId browsing_instance_id(
isolation_context.browsing_instance_id());
// All callers to this function live on the UI thread, so the IsolationContext
// should contain a BrowserContext*.
BrowserContext* browser_context =
isolation_context.browser_or_resource_context().ToBrowserContext();
DCHECK(browser_context);
CHECK(!browsing_instance_id.is_null());
base::AutoLock origins_isolation_opt_in_lock(origins_isolation_opt_in_lock_);
// Commits of origins that have ever sent the OriginAgentCluster header in
// this BrowserContext are tracked in every BrowsingInstance in this
// BrowserContext, to avoid having to do multiple global walks. If the origin
// isn't in the list of such origins (i.e., the common case), return early to
// avoid unnecessary work, since this is called on every commit. Skip this
// during global walks and frame removals, since we do want to track the
// origin's non-isolated status in those cases.
if (!is_global_walk_or_frame_removal &&
!(base::Contains(origin_isolation_opt_ins_and_outs_, browser_context) &&
base::Contains(origin_isolation_opt_ins_and_outs_[browser_context],
origin))) {
return;
}
// If |origin| is already in the opt-in-out list, then we don't want to add it
// to the list. Technically this check is unnecessary during global
// walks (when the origin won't be in this list yet), but it matters during
// frame removal (when we don't want to add an opted-in origin to the
// list as non-isolated when its frame is removed).
if (LookupOriginIsolationState(browsing_instance_id, origin)) {
return;
}
// Since there was no prior record for this BrowsingInstance, track that this
// origin should use the default isolation model in use by the
// BrowsingInstance.
origin_isolation_by_browsing_instance_[browsing_instance_id].emplace_back(
isolation_context.default_isolation_state(), origin);
}
void ChildProcessSecurityPolicyImpl::
RemoveOptInIsolatedOriginsForBrowsingInstance(
const BrowsingInstanceId& browsing_instance_id) {
// After a suitable delay, remove this BrowsingInstance's info from any
// SecurityStates that are using it.
// TODO(wjmaclean): Monitor the CanAccessDataForOrigin crash key in renderer
// kills to see if we get post-BrowsingInstance-destruction ProcessLock
// mismatches, indicating this cleanup should be further delayed.
auto task_closure = [](const BrowsingInstanceId id) {
ChildProcessSecurityPolicyImpl* policy =
ChildProcessSecurityPolicyImpl::GetInstance();
policy->RemoveOptInIsolatedOriginsForBrowsingInstanceInternal(id);
};
if (browsing_instance_cleanup_delay_.is_positive()) {
// Do the actual state cleanup after posting a task to the IO thread, to
// give a chance for any last unprocessed tasks to be handled. The cleanup
// itself locks the data structures and can safely happen from either
// thread.
GetIOThreadTaskRunner({})->PostDelayedTask(
FROM_HERE, base::BindOnce(task_closure, browsing_instance_id),
browsing_instance_cleanup_delay_);
} else {
// Since this is just used in tests, it's ok to do it on either thread.
task_closure(browsing_instance_id);
}
}
void ChildProcessSecurityPolicyImpl::
RemoveOptInIsolatedOriginsForBrowsingInstanceInternal(
const BrowsingInstanceId browsing_instance_id) {
// If a BrowsingInstance is destructing, we should always have an id for it.
CHECK(!browsing_instance_id.is_null());
{
// content_unittests don't always report being on the IO thread.
DCHECK(IsRunningOnExpectedThread());
base::AutoLock lock(lock_);
for (auto& it : security_state_)
it.second->ClearBrowsingInstanceId(browsing_instance_id);
// Note: if the BrowsingInstanceId set is empty at the end of this function,
// we must never remove the ProcessLock in case the associated RenderProcess
// is compromised, in which case we wouldn't want to reuse it for another
// origin.
}
{
base::AutoLock origins_isolation_opt_in_lock(
origins_isolation_opt_in_lock_);
origin_isolation_by_browsing_instance_.erase(browsing_instance_id);
}
{
base::AutoLock isolated_origins_lock(isolated_origins_lock_);
for (auto& iter : isolated_origins_) {
base::EraseIf(iter.second, [&browsing_instance_id](
const IsolatedOriginEntry& entry) {
// Remove entries that are specific to `browsing_instance_id` and
// do not apply to future BrowsingInstances.
return (entry.browsing_instance_id() == browsing_instance_id &&
!entry.applies_to_future_browsing_instances());
});
}
}
}
void ChildProcessSecurityPolicyImpl::AddCoopIsolatedOriginForBrowsingInstance(
const IsolationContext& isolation_context,
const url::Origin& origin,
IsolatedOriginSource source) {
// We ought to have validated the origin prior to getting here. If the
// origin isn't valid at this point, something has gone wrong.
CHECK(IsolatedOriginUtil::IsValidIsolatedOrigin(origin))
<< "Trying to isolate invalid origin: " << origin;
// This can only be called from the UI thread, as it reads state that's only
// available (and is only safe to be retrieved) on the UI thread, such as
// BrowsingInstance IDs.
DCHECK_CURRENTLY_ON(BrowserThread::UI);
BrowsingInstanceId browsing_instance_id(
isolation_context.browsing_instance_id());
// This function should only be called when a BrowsingInstance is registering
// a new SiteInstance, so |browsing_instance_id| should always be defined.
CHECK(!browsing_instance_id.is_null());
// For site-keyed isolation, add `origin` to the isolated_origins_ map (which
// supports subdomain matching).
// Ensure that `origin` is a site (scheme + eTLD+1) rather than any origin.
auto site_origin = url::Origin::Create(SiteInfo::GetSiteForOrigin(origin));
CHECK_EQ(origin, site_origin);
base::AutoLock isolated_origins_lock(isolated_origins_lock_);
// Explicitly set `applies_to_future_browsing_instances` to false to only
// isolate `origin` within the provided BrowsingInstance, but not future
// ones. Note that it's possible for `origin` to also become isolated for
// future BrowsingInstances if AddFutureIsolatedOrigins() is called for it
// later.
AddIsolatedOriginInternal(
isolation_context.browser_or_resource_context().ToBrowserContext(),
origin, false /* applies_to_future_browsing_instances */,
isolation_context.browsing_instance_id(),
false /* isolate_all_subdomains */, source);
}
void ChildProcessSecurityPolicyImpl::AddOriginIsolationStateForBrowsingInstance(
const IsolationContext& isolation_context,
const url::Origin& origin,
bool is_origin_agent_cluster,
bool requires_origin_keyed_process) {
// This can only be called from the UI thread, as it reads state that's only
// available (and is only safe to be retrieved) on the UI thread, such as
// BrowsingInstance IDs.
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(
is_origin_agent_cluster ||
SiteIsolationPolicy::AreOriginAgentClustersEnabledByDefault(
isolation_context.browser_or_resource_context().ToBrowserContext()));
// We ought to have validated the origin prior to getting here. If the
// origin isn't valid at this point, something has gone wrong.
CHECK((is_origin_agent_cluster &&
IsolatedOriginUtil::IsValidOriginForOptInIsolation(origin)) ||
// The second part of this check is specific to OAC-by-default, and is
// required to allow explicit opt-outs for HTTP schemed origins. See
// OriginAgentClusterInsecureEnabledBrowserTest.DocumentDomain_Disabled.
IsolatedOriginUtil::IsValidOriginForOptOutIsolation(origin))
<< "Trying to isolate invalid origin: " << origin;
BrowsingInstanceId browsing_instance_id(
isolation_context.browsing_instance_id());
// This function should only be called when a BrowsingInstance is registering
// a new SiteInstance, so |browsing_instance_id| should always be defined.
CHECK(!browsing_instance_id.is_null());
// For origin-keyed isolation, use the origin_isolation_by_browsing_instance_
// map.
base::AutoLock origins_isolation_opt_in_lock(origins_isolation_opt_in_lock_);
auto it = origin_isolation_by_browsing_instance_.find(browsing_instance_id);
if (it == origin_isolation_by_browsing_instance_.end()) {
std::tie(it, std::ignore) = origin_isolation_by_browsing_instance_.emplace(
browsing_instance_id, std::vector<OriginAgentClusterOptInEntry>());
}
// We only support adding new entries, not modifying existing ones. If at
// some point in the future we allow isolation status to change during the
// lifetime of a BrowsingInstance, then this will need to be updated.
if (!base::Contains(it->second, origin,
&OriginAgentClusterOptInEntry::origin)) {
it->second.emplace_back(
is_origin_agent_cluster
? OriginAgentClusterIsolationState::CreateForOriginAgentCluster(
requires_origin_keyed_process)
: OriginAgentClusterIsolationState::CreateNonIsolated(),
origin);
}
}
bool ChildProcessSecurityPolicyImpl::UpdateOriginIsolationOptInListIfNecessary(
BrowserContext* browser_context,
const url::Origin& origin) {
if (!IsolatedOriginUtil::IsValidOriginForOptInIsolation(origin))
return false;
base::AutoLock origins_isolation_opt_in_lock(origins_isolation_opt_in_lock_);
if (base::Contains(origin_isolation_opt_ins_and_outs_, browser_context) &&
base::Contains(origin_isolation_opt_ins_and_outs_[browser_context],
origin)) {
return false;
}
origin_isolation_opt_ins_and_outs_[browser_context].insert(origin);
return true;
}
void ChildProcessSecurityPolicyImpl::RemoveIsolatedOriginForTesting(
const url::Origin& origin) {
GURL key(SiteInfo::GetSiteForOrigin(origin));
base::AutoLock isolated_origins_lock(isolated_origins_lock_);
base::EraseIf(isolated_origins_[key],
[&origin](const IsolatedOriginEntry& entry) {
// Remove if origin matches.
return (entry.origin() == origin);
});
if (isolated_origins_[key].empty())
isolated_origins_.erase(key);
}
void ChildProcessSecurityPolicyImpl::ClearIsolatedOriginsForTesting() {
base::AutoLock isolated_origins_lock(isolated_origins_lock_);
isolated_origins_.clear();
}
ChildProcessSecurityPolicyImpl::SecurityState*
ChildProcessSecurityPolicyImpl::GetSecurityState(int child_id) {
auto itr = security_state_.find(child_id);
if (itr != security_state_.end())
return itr->second.get();
auto pending_itr = pending_remove_state_.find(child_id);
if (pending_itr == pending_remove_state_.end())
return nullptr;
// At this point the SecurityState in the map is being kept alive
// by a Handle object or we are waiting for the deletion task to be run on
// the IO thread.
SecurityState* pending_security_state = pending_itr->second.get();
auto count_itr = process_reference_counts_.find(child_id);
if (count_itr != process_reference_counts_.end()) {
// There must be a Handle that still holds a reference to this
// pending state so it is safe to return. The assumption is that the
// owner of this Handle is making a security check.
return pending_security_state;
}
// Since we don't have an entry in |process_reference_counts_| it means
// that we are waiting for the deletion task posted to the IO thread to run.
// Only allow the state to be accessed by the IO thread in this situation.
if (BrowserThread::CurrentlyOn(BrowserThread::IO))
return pending_security_state;
return nullptr;
}
std::vector<IsolatedOriginPattern>
ChildProcessSecurityPolicyImpl::ParseIsolatedOrigins(
base::StringPiece pattern_list) {
std::vector<base::StringPiece> origin_strings = base::SplitStringPiece(
pattern_list, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
std::vector<IsolatedOriginPattern> patterns;
patterns.reserve(origin_strings.size());
for (const base::StringPiece& origin_string : origin_strings)
patterns.emplace_back(origin_string);
return patterns;
}
// static
std::string ChildProcessSecurityPolicyImpl::GetKilledProcessOriginLock(
const SecurityState* security_state) {
if (!security_state)
return "(child id not found)";
if (!security_state->GetBrowserOrResourceContext())
return "(empty and null context)";
return security_state->process_lock().ToString();
}
void ChildProcessSecurityPolicyImpl::LogKilledProcessOriginLock(int child_id) {
base::AutoLock lock(lock_);
const auto itr = security_state_.find(child_id);
const SecurityState* security_state =
itr != security_state_.end() ? itr->second.get() : nullptr;
base::debug::SetCrashKeyString(GetKilledProcessOriginLockKey(),
GetKilledProcessOriginLock(security_state));
}
ChildProcessSecurityPolicyImpl::Handle
ChildProcessSecurityPolicyImpl::CreateHandle(int child_id) {
return Handle(child_id, /* duplicating_handle */ false);
}
bool ChildProcessSecurityPolicyImpl::AddProcessReference(
int child_id,
bool duplicating_handle) {
base::AutoLock lock(lock_);
return AddProcessReferenceLocked(child_id, duplicating_handle);
}
bool ChildProcessSecurityPolicyImpl::AddProcessReferenceLocked(
int child_id,
bool duplicating_handle) {
if (child_id == ChildProcessHost::kInvalidUniqueID)
return false;
// Check to see if the SecurityState has been removed from |security_state_|
// via a Remove() call. This corresponds to the process being destroyed.
if (security_state_.find(child_id) == security_state_.end()) {
if (!duplicating_handle) {
// Do not allow Handles to be created after the process has been
// destroyed, unless they are being duplicated.
return false;
}
// The process has been destroyed but we are allowing an existing Handle
// to be duplicated. Verify that the process reference count is available
// and indicates another Handle has a reference.
auto itr = process_reference_counts_.find(child_id);
CHECK(itr != process_reference_counts_.end());
CHECK_GT(itr->second, 0);
}
++process_reference_counts_[child_id];
return true;
}
void ChildProcessSecurityPolicyImpl::RemoveProcessReference(int child_id) {
base::AutoLock lock(lock_);
RemoveProcessReferenceLocked(child_id);
}
void ChildProcessSecurityPolicyImpl::RemoveProcessReferenceLocked(
int child_id) {
auto itr = process_reference_counts_.find(child_id);
CHECK(itr != process_reference_counts_.end());
if (itr->second > 1) {
itr->second--;
return;
}
DCHECK_EQ(itr->second, 1);
process_reference_counts_.erase(itr);
// |child_id| could be inside tasks that are on the IO thread task queues. We
// need to keep the |pending_remove_state_| entry around until we have
// successfully executed a task on the IO thread. This should ensure that any
// pending tasks on the IO thread will have completed before we remove the
// entry.
// TODO(acolwell): Remove this call once all objects on the IO thread have
// been converted to use Handles.
GetIOThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(
[](ChildProcessSecurityPolicyImpl* policy, int child_id) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
base::AutoLock lock(policy->lock_);
policy->pending_remove_state_.erase(child_id);
},
base::Unretained(this), child_id));
}
} // namespace content