From: uazo Date: Tue, 20 Sep 2022 07:20:01 +0000 Subject: Partition blobs by top frame URL Verifies that the blob was created with the same top frame URL or, if not defined, by the same agent cluster. Original License: GPL-2.0-or-later - https://spdx.org/licenses/GPL-2.0-or-later.html License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html --- .../renderer_host/render_frame_host_impl.cc | 2 + .../Partition-blobs-by-top-frame-URL.inc | 1 + storage/browser/blob/blob_url_registry.cc | 31 +++++++++- storage/browser/blob/blob_url_registry.h | 11 +++- storage/browser/blob/blob_url_store_impl.cc | 62 +++++++++++++++++-- storage/browser/blob/blob_url_store_impl.h | 15 ++++- storage/browser/blob/features.cc | 1 + .../public/mojom/blob/blob_url_store.mojom | 13 +++- .../core/fileapi/public_url_manager.cc | 37 ++++++++++- 9 files changed, 158 insertions(+), 15 deletions(-) create mode 100644 cromite_flags/third_party/blink/common/features_cc/Partition-blobs-by-top-frame-URL.inc diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc --- a/content/browser/renderer_host/render_frame_host_impl.cc +++ b/content/browser/renderer_host/render_frame_host_impl.cc @@ -13524,6 +13524,8 @@ void RenderFrameHostImpl::ReportBlockingCrossPartitionBlobURL( } bool RenderFrameHostImpl::IsFullCookieAccessAllowed() { + // 6313600: [Blob URL] Allow contexts with a StorageAccessHandle + // to bypass Blob URL partitioning | https://chromium-review.googlesource.com/c/chromium/src/+/6313600 return GetContentClient()->browser()->IsFullCookieAccessAllowed( GetBrowserContext(), WebContents::FromRenderFrameHost(this), GetLastCommittedURL(), GetStorageKey(), GetCookieSettingOverrides()); diff --git a/cromite_flags/third_party/blink/common/features_cc/Partition-blobs-by-top-frame-URL.inc b/cromite_flags/third_party/blink/common/features_cc/Partition-blobs-by-top-frame-URL.inc new file mode 100644 --- /dev/null +++ b/cromite_flags/third_party/blink/common/features_cc/Partition-blobs-by-top-frame-URL.inc @@ -0,0 +1 @@ +SET_CROMITE_FEATURE_ENABLED(kEnforceNoopenerOnBlobURLNavigation); diff --git a/storage/browser/blob/blob_url_registry.cc b/storage/browser/blob/blob_url_registry.cc --- a/storage/browser/blob/blob_url_registry.cc +++ b/storage/browser/blob/blob_url_registry.cc @@ -93,13 +93,18 @@ bool BlobUrlRegistry::AddUrlMapping( mojo::PendingRemote blob, const blink::StorageKey& storage_key, const url::Origin& renderer_origin, - int render_process_host_id) { + int render_process_host_id, + const base::UnguessableToken& unsafe_agent_cluster_id, + const std::optional& unsafe_top_level_site) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(!BlobUrlUtils::UrlHasFragment(blob_url)); if (IsUrlMapped(blob_url, storage_key) == BlobUrlRegistry::MappingStatus::kIsMapped) { return false; } + url_to_unsafe_agent_cluster_id_[blob_url] = unsafe_agent_cluster_id; + if (unsafe_top_level_site) + url_to_unsafe_top_level_site_[blob_url] = *unsafe_top_level_site; BlobUrlData data; data.blob = std::move(blob); data.storage_key = storage_key; @@ -121,6 +126,8 @@ bool BlobUrlRegistry::RemoveUrlMapping(const GURL& blob_url, return false; } url_to_data_.erase(data_it); + url_to_unsafe_agent_cluster_id_.erase(blob_url); + url_to_unsafe_top_level_site_.erase(blob_url); return true; } @@ -184,6 +191,28 @@ BlobUrlRegistry::MappingStatus BlobUrlRegistry::IsUrlMapped( return BlobUrlRegistry::MappingStatus::kNotMappedOther; } +std::optional BlobUrlRegistry::GetUnsafeAgentClusterID( + const GURL& blob_url) const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + auto it = url_to_unsafe_agent_cluster_id_.find(blob_url); + if (it != url_to_unsafe_agent_cluster_id_.end()) + return it->second; + if (fallback_) + return fallback_->GetUnsafeAgentClusterID(blob_url); + return std::nullopt; +} + +std::optional BlobUrlRegistry::GetUnsafeTopLevelSite( + const GURL& blob_url) const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + auto it = url_to_unsafe_top_level_site_.find(blob_url); + if (it != url_to_unsafe_top_level_site_.end()) + return it->second; + if (fallback_) + return fallback_->GetUnsafeTopLevelSite(blob_url); + return std::nullopt; +} + mojo::PendingRemote BlobUrlRegistry::GetBlobFromUrl( const GURL& url) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); diff --git a/storage/browser/blob/blob_url_registry.h b/storage/browser/blob/blob_url_registry.h --- a/storage/browser/blob/blob_url_registry.h +++ b/storage/browser/blob/blob_url_registry.h @@ -96,7 +96,9 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) BlobUrlRegistry { mojo::PendingRemote blob, const blink::StorageKey& storage_key, const url::Origin& renderer_origin, - int render_process_host_id); + int render_process_host_id, + const base::UnguessableToken& unsafe_agent_cluster_id, + const std::optional& unsafe_top_level_site); // Removes the given URL mapping associated with `storage_key`. Returns false // if the URL wasn't mapped. @@ -107,6 +109,11 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) BlobUrlRegistry { MappingStatus IsUrlMapped(const GURL& blob_url, const blink::StorageKey& storage_key) const; + std::optional GetUnsafeAgentClusterID( + const GURL& blob_url) const; + std::optional GetUnsafeTopLevelSite( + const GURL& blob_url) const; + // Returns the blob from the given url. Returns a null remote if the mapping // doesn't exist. mojo::PendingRemote GetBlobFromUrl(const GURL& url); @@ -162,6 +169,8 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) BlobUrlRegistry { }; std::map url_to_data_; + std::map url_to_unsafe_agent_cluster_id_; + std::map url_to_unsafe_top_level_site_; std::map>> token_to_url_and_blob_; diff --git a/storage/browser/blob/blob_url_store_impl.cc b/storage/browser/blob/blob_url_store_impl.cc --- a/storage/browser/blob/blob_url_store_impl.cc +++ b/storage/browser/blob/blob_url_store_impl.cc @@ -115,9 +115,35 @@ BlobURLStoreImpl::~BlobURLStoreImpl() { } } +bool BlobURLStoreImpl::IsSamePartition( + const GURL& blob_url, + const base::UnguessableToken& unsafe_agent_cluster_id, + const std::optional& unsafe_top_level_site) { + bool is_same_partition = false; + const std::optional& top_level_site = + registry_->GetUnsafeTopLevelSite(blob_url); + const std::optional agent_cluster_id = + registry_->GetUnsafeAgentClusterID(blob_url); + if (top_level_site.has_value()) { + is_same_partition = (top_level_site == unsafe_top_level_site); + } else { + is_same_partition = (agent_cluster_id == unsafe_agent_cluster_id); + } + // LOG(INFO) << "---BlobURLStoreImpl " + // << " is_same_partition=" << is_same_partition + // << " blob_url=" << blob_url + // << " top_level_site=" << (top_level_site.has_value() ? top_level_site->GetDebugString() : "") + // << " unsafe_top_level_site=" << (unsafe_top_level_site.has_value() ? unsafe_top_level_site->GetDebugString() : "") + // << " agent_cluster_id=" << (agent_cluster_id.has_value() ? agent_cluster_id->ToString() : "") + // << " unsafe_agent_cluster_id=" << unsafe_agent_cluster_id.ToString(); + return is_same_partition; +} + void BlobURLStoreImpl::Register( mojo::PendingRemote blob, const GURL& url, + const base::UnguessableToken& unsafe_agent_cluster_id, + const std::optional& unsafe_top_level_site, RegisterCallback callback) { // TODO(crbug.com/40061399): Generate blob URLs here, rather than // validating the URLs the renderer process generated. @@ -125,10 +151,18 @@ void BlobURLStoreImpl::Register( std::move(callback).Run(); return; } + // LOG(INFO) << "---BlobURLStoreImpl Register" + // << " url=" << url + // << " storage_key_origin=" << storage_key_.GetDebugString() + // << " renderer_origin_=" << renderer_origin_ + // << " render_process_host_id_=" << render_process_host_id_ + // << " unsafe_top_level_site=" << (unsafe_top_level_site.has_value() ? unsafe_top_level_site->GetDebugString() : "") + // << " unsafe_agent_cluster_id=" << unsafe_agent_cluster_id.ToString(); if (registry_) registry_->AddUrlMapping(url, std::move(blob), storage_key_, - renderer_origin_, render_process_host_id_); + renderer_origin_, render_process_host_id_, + unsafe_agent_cluster_id, unsafe_top_level_site); urls_.insert(url); std::move(callback).Run(); } @@ -150,7 +184,7 @@ bool BlobURLStoreImpl::ShouldPartitionBlobUrlAccess( features::kBlockCrossPartitionBlobUrlFetching) && !partitioning_disabled_by_policy_; - const bool should_bypass_partitioning = + const bool should_bypass_partitioning = ((false)) && has_storage_access_handle && mapping_status == BlobUrlRegistry::MappingStatus:: @@ -160,7 +194,9 @@ bool BlobURLStoreImpl::ShouldPartitionBlobUrlAccess( void BlobURLStoreImpl::ResolveAsURLLoaderFactory( const GURL& url, - mojo::PendingReceiver receiver) { + mojo::PendingReceiver receiver, + const base::UnguessableToken& unsafe_agent_cluster_id, + const std::optional& unsafe_top_level_site) { if (!registry_) { BlobURLLoaderFactory::Create(mojo::NullRemote(), url, std::move(receiver)); return; @@ -183,6 +219,7 @@ void BlobURLStoreImpl::ResolveAsURLLoaderFactory( if (IsBlobUrlAccessCrossPartitionSameOrigin(mapping_status)) { if (ShouldPartitionBlobUrlAccess(has_storage_access_handle, mapping_status)) { + // LOG(INFO) << "---ResolveAsURLLoaderFactory blocked" << url; partitioning_blob_url_closure_.Run( url, blink::mojom::PartitioningBlobURLInfo:: kBlockedCrossPartitionFetching); @@ -194,6 +231,13 @@ void BlobURLStoreImpl::ResolveAsURLLoaderFactory( } } + if (!IsSamePartition(url, unsafe_agent_cluster_id, unsafe_top_level_site)) { + // LOG(INFO) << "---ResolveAsURLLoaderFactory blocked by IsSamePartition" << url; + BlobURLLoaderFactory::Create(mojo::NullRemote(), url, std::move(receiver)); + //std::move(callback).Run(std::nullopt, std::nullopt); + return; + } + // LOG(INFO) << "---ResolveAsURLLoaderFactory allowed " << url; BlobURLLoaderFactory::Create(registry_->GetBlobFromUrl(url), url, std::move(receiver)); } @@ -201,7 +245,9 @@ void BlobURLStoreImpl::ResolveAsURLLoaderFactory( void BlobURLStoreImpl::ResolveAsBlobURLToken( const GURL& url, mojo::PendingReceiver token, - bool is_top_level_navigation) { + bool is_top_level_navigation, + const base::UnguessableToken& unsafe_agent_cluster_id, + const std::optional& unsafe_top_level_site) { // This function is known to be heap allocation heavy and performance // critical. Extra memory safety checks can introduce regression // (https://crbug.com/414710225) and these are disabled here. @@ -218,6 +264,7 @@ void BlobURLStoreImpl::ResolveAsBlobURLToken( registry_->IsUrlMapped(BlobUrlUtils::ClearUrlFragment(url), storage_key_); if (IsBlobUrlAccessCrossPartitionSameOrigin(mapping_status)) { + // LOG(INFO) << "---ResolveAsBlobURLToken blocked" << url; if (ShouldPartitionBlobUrlAccess(has_storage_access_handle, mapping_status)) { partitioning_blob_url_closure_.Run( @@ -228,12 +275,17 @@ void BlobURLStoreImpl::ResolveAsBlobURLToken( partitioning_blob_url_closure_.Run(url, std::nullopt); } } + if (!IsSamePartition(url, unsafe_agent_cluster_id, unsafe_top_level_site)) { + // LOG(INFO) << "---ResolveAsBlobURLToken blocked by IsSamePartition" << url; + //std::move(callback).Run(std::nullopt); + return; + } mojo::PendingRemote blob = registry_->GetBlobFromUrl(url); if (!blob) { return; } - + // LOG(INFO) << "---ResolveAsBlobURLToken allowed " << url; new BlobURLTokenImpl(registry_, url, std::move(blob), std::move(token)); } diff --git a/storage/browser/blob/blob_url_store_impl.h b/storage/browser/blob/blob_url_store_impl.h --- a/storage/browser/blob/blob_url_store_impl.h +++ b/storage/browser/blob/blob_url_store_impl.h @@ -55,16 +55,22 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) BlobURLStoreImpl void Register( mojo::PendingRemote blob, const GURL& url, + const base::UnguessableToken& unsafe_agent_cluster_id, + const std::optional& unsafe_top_level_site, RegisterCallback callback) override; void Revoke(const GURL& url) override; void ResolveAsURLLoaderFactory( const GURL& url, - mojo::PendingReceiver receiver) + mojo::PendingReceiver receiver, + const base::UnguessableToken& unsafe_agent_cluster_id, + const std::optional& unsafe_top_level_site) override; void ResolveAsBlobURLToken( const GURL& url, mojo::PendingReceiver token, - bool is_top_level_navigation) override; + bool is_top_level_navigation, + const base::UnguessableToken& unsafe_agent_cluster_id, + const std::optional& unsafe_top_level_site) override; private: // Checks if the passed in url is a valid blob url for this blob url store. @@ -77,6 +83,11 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) BlobURLStoreImpl bool has_storage_access_handle, BlobUrlRegistry::MappingStatus mapping_status); + bool IsSamePartition( + const GURL& blob_url, + const base::UnguessableToken& unsafe_agent_cluster_id, + const std::optional& unsafe_top_level_site); + const blink::StorageKey storage_key_; // The origin used by the worker/document associated with this BlobURLStore on // the renderer side. This will almost always be the same as `storage_key_`'s diff --git a/storage/browser/blob/features.cc b/storage/browser/blob/features.cc --- a/storage/browser/blob/features.cc +++ b/storage/browser/blob/features.cc @@ -9,6 +9,7 @@ namespace features { // Please keep features in alphabetical order. BASE_FEATURE(kBlockCrossPartitionBlobUrlFetching, base::FEATURE_ENABLED_BY_DEFAULT); +SET_CROMITE_FEATURE_ENABLED(kBlockCrossPartitionBlobUrlFetching); // Please keep features in alphabetical order. diff --git a/third_party/blink/public/mojom/blob/blob_url_store.mojom b/third_party/blink/public/mojom/blob/blob_url_store.mojom --- a/third_party/blink/public/mojom/blob/blob_url_store.mojom +++ b/third_party/blink/public/mojom/blob/blob_url_store.mojom @@ -4,6 +4,7 @@ module blink.mojom; import "mojo/public/mojom/base/unguessable_token.mojom"; +import "services/network/public/mojom/schemeful_site.mojom"; import "services/network/public/mojom/url_loader_factory.mojom"; import "third_party/blink/public/mojom/blob/blob.mojom"; import "url/mojom/url.mojom"; @@ -19,7 +20,9 @@ interface BlobURLStore { // new blob: URL rather than letting the caller in the renderer provide one. [Sync] Register( pending_remote blob, - url.mojom.Url url) => (); + url.mojom.Url url, + mojo_base.mojom.UnguessableToken unsafe_agent_cluster_id, + network.mojom.SchemefulSite? unsafe_top_level_site) => (); // Revokes a public Blob URL. Revoke(url.mojom.Url url); @@ -33,7 +36,9 @@ interface BlobURLStore { // both the blob URL and all other references to the blob have been dropped. ResolveAsURLLoaderFactory( url.mojom.Url url, - pending_receiver factory); + pending_receiver factory, + mojo_base.mojom.UnguessableToken unsafe_agent_cluster_id, + network.mojom.SchemefulSite? unsafe_top_level_site); // Resolves a public Blob URL into a BlobURLToken. The BlobURLToken can be // used by the browser process to securely look up the blob a URL used to @@ -41,7 +46,9 @@ interface BlobURLStore { // As long as the token is alive, the resolved blob will also be kept alive. ResolveAsBlobURLToken(url.mojom.Url url, pending_receiver token, - bool is_top_level_navigation); + bool is_top_level_navigation, + mojo_base.mojom.UnguessableToken unsafe_agent_cluster_id, + network.mojom.SchemefulSite? unsafe_top_level_site); }; // A token representing a Blob URL. The browser process can use this to look up diff --git a/third_party/blink/renderer/core/fileapi/public_url_manager.cc b/third_party/blink/renderer/core/fileapi/public_url_manager.cc --- a/third_party/blink/renderer/core/fileapi/public_url_manager.cc +++ b/third_party/blink/renderer/core/fileapi/public_url_manager.cc @@ -61,6 +61,25 @@ static void RemoveFromNullOriginMapIfNecessary(const KURL& blob_url) { BlobURLNullOriginMap::GetInstance()->Remove(blob_url); } +static std::optional GetInsecureTopLevelSite( + ExecutionContext* execution_context) { + std::optional top_level_site; + if (execution_context->IsWindow()) { + auto* window = To(execution_context); + if (window->top() && window->top()->GetFrame()) { + top_level_site = BlinkSchemefulSite(window->top() + ->GetFrame() + ->GetSecurityContext() + ->GetSecurityOrigin()); + } + } else if (auto* worker_global_scope = + DynamicTo(execution_context)) { + top_level_site = BlinkSchemefulSite( + worker_global_scope->top_level_frame_security_origin()); + } + return top_level_site; +} + } // namespace PublicURLManager::PublicURLManager(ExecutionContext* execution_context) @@ -158,7 +177,17 @@ String PublicURLManager::RegisterURL(URLRegistrable* registrable) { mojo::PendingReceiver blob_receiver = blob_remote.InitWithNewPipeAndPassReceiver(); - GetBlobURLStore().Register(std::move(blob_remote), url); + std::optional top_level_site; + if (GetExecutionContext()->IsWindow()) { + auto* window = To(GetExecutionContext()); + if (window->top() && window->top()->GetFrame()) { + top_level_site = BlinkSchemefulSite(window->top() + ->GetFrame() + ->GetSecurityContext() + ->GetSecurityOrigin()); + } + } + GetBlobURLStore().Register(std::move(blob_remote), url, GetExecutionContext()->GetAgentClusterID(), top_level_site); mojo_urls_.insert(url_string); registrable->CloneMojoBlob(std::move(blob_receiver)); @@ -208,7 +237,8 @@ void PublicURLManager::Resolve( DCHECK(url.ProtocolIs("blob")); - GetBlobURLStore().ResolveAsURLLoaderFactory(url, std::move(factory_receiver)); + GetBlobURLStore().ResolveAsURLLoaderFactory(url, std::move(factory_receiver), + GetExecutionContext()->GetAgentClusterID(), GetInsecureTopLevelSite(GetExecutionContext())); } void PublicURLManager::ResolveAsBlobURLToken( @@ -221,7 +251,8 @@ void PublicURLManager::ResolveAsBlobURLToken( DCHECK(url.ProtocolIs("blob")); GetBlobURLStore().ResolveAsBlobURLToken(url, std::move(token_receiver), - is_top_level_navigation); + is_top_level_navigation, + GetExecutionContext()->GetAgentClusterID(), GetInsecureTopLevelSite(GetExecutionContext())); } void PublicURLManager::ContextDestroyed() { --