Chrome · CacheStorage
CVE-2026-113098
Memory Corruption in CacheStorage
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
CacheStorageCachecontent/browser/cache_storage/cache_storage_cache.cc |
modified | |
ifcontent/browser/cache_storage/cache_storage_cache.cc |
modified |
Files Changed
third_party/blink/renderer/platform/loader/fetch/url_loader/cached_metadata_handler_test.ccservices/network/public/cpp/BUILD.gncontent/browser/cache_storage/cache_storage_dispatcher_host.ccthird_party/blink/common/loader/mime_sniffing_throttle.ccthird_party/blink/public/mojom/fetch/fetch_api_response.mojomthird_party/blink/public/mojom/loader/code_cache.mojomservices/network/public/cpp/cache_storage_side_data_writer_mojom_traits.hthird_party/blink/renderer/core/fetch/fetch_response_data.ccthird_party/blink/renderer/platform/loader/fetch/persistent_code_cache_host.cccontent/browser/cache_storage/cache_storage_cache_unittest.cccontent/browser/cache_storage/cache_storage_cache.ccthird_party/blink/public/platform/web_url_response.hthird_party/blink/renderer/platform/loader/fetch/url_loader/resource_request_sender.ccthird_party/blink/public/mojom/BUILD.gnthird_party/blink/renderer/platform/loader/fetch/url_loader/worker_main_script_loader.ccthird_party/blink/renderer/bindings/core/v8/v8_wasm_response_extensions.ccservices/network/public/mojom/cache_storage_side_data_writer.mojomthird_party/blink/renderer/platform/loader/fetch/url_loader/cached_metadata_handler.cccontent/browser/cache_storage/cache_storage_manager_unittest.ccthird_party/blink/renderer/platform/loader/fetch/url_loader/isolated_code_cache_fetcher.ccthird_party/blink/renderer/platform/loader/fetch/url_loader/resource_request_sender_unittest.ccthird_party/blink/renderer/platform/exported/web_url_response.cccontent/renderer/service_worker/service_worker_subresource_loader.hthird_party/blink/renderer/platform/loader/fetch/url_loader/cached_metadata_handler.hthird_party/blink/renderer/core/fetch/response.ccthird_party/blink/public/mojom/cache_storage/cache_storage.mojomcontent/browser/cache_storage/cache_storage.ccservices/network/public/mojom/BUILD.gncontent/browser/renderer_host/code_cache_host_impl.cccontent/browser/service_worker/service_worker_main_resource_loader.cccontent/renderer/service_worker/service_worker_subresource_loader.ccthird_party/blink/renderer/platform/loader/fetch/resource_response.ccthird_party/blink/renderer/platform/loader/fetch/url_loader/url_loader.cccomponents/content_settings/renderer/content_settings_agent_impl_browsertest.ccservices/network/public/mojom/url_response_head.mojomthird_party/blink/renderer/platform/loader/fetch/url_loader/background_url_loader.cccontent/browser/renderer_host/code_cache_host_impl.hcontent/browser/service_worker/service_worker_main_resource_loader.hcontent/browser/cache_storage/cache_storage_cache.hthird_party/blink/renderer/platform/loader/fetch/persistent_code_cache_host.hthird_party/blink/renderer/core/fetch/fetch_response_data.hthird_party/blink/renderer/modules/cache_storage/cache_test.cccontent/common/background_fetch/background_fetch_types.ccthird_party/blink/renderer/platform/loader/fetch/resource_response.h
Patch
From 8841f4cd23838793f034ebabf6e3417df86d80e3 Mon Sep 17 00:00:00 2001 From: Eriko Kurimoto <[email protected]> Date: Mon, 05 Oct 2026 02:51:36 -0700 Subject: [PATCH] Plumb CacheStorageSideDataWriter capability remote for code cache Currently, URLResponseHead and FetchAPIResponse pass cache names as strings for CacheStorage code-cache metadata feedback. This introduces a TOCTOU risk and loses storage bucket identity when writing side data. This change replaces string cache/bucket names with a pending_remote<network.mojom.CacheStorageSideDataWriter> bound directly to the CacheStorageCache entry that served the response, invoked directly by the renderer. TAG=agy CONV=0ba9eb11-a35f-45aa-941c-58216e07432c Bug: 548989584 Change-Id: I80860ed642f6635d10fc034703a2865d63b9a251 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8303572 Reviewed-by: Rakina Zata Amni <[email protected]> Reviewed-by: Evan Stade <[email protected]> Reviewed-by: Kenichi Ishibashi <[email protected]> Commit-Queue: Christian Dullweber <[email protected]> Reviewed-by: Kentaro Hara <[email protected]> Auto-Submit: Eriko Kurimoto <[email protected]> Reviewed-by: Christian Dullweber <[email protected]> Reviewed-by: Sam McNally <[email protected]> Cr-Commit-Position: refs/heads/main@{#1711121} --- diff --git a/content/browser/cache_storage/cache_storage.cc b/content/browser/cache_storage/cache_storage.cc index 632ebca..bbf5826 100644 --- a/content/browser/cache_storage/cache_storage.cc +++ b/content/browser/cache_storage/cache_storage.cc @@ -1241,6 +1241,7 @@ } cache_index_->FinalizeDoomedCache(); + impl->InvalidateSideDataWriters(); auto map_iter = cache_map_.find(impl->cache_name()); CHECK(map_iter != cache_map_.end()); diff --git a/content/browser/cache_storage/cache_storage_cache.cc b/content/browser/cache_storage/cache_storage_cache.cc index a45e5623..cf8f895 100644 --- a/content/browser/cache_storage/cache_storage_cache.cc +++ b/content/browser/cache_storage/cache_storage_cache.cc @@ -44,6 +44,8 @@ #include "content/browser/cache_storage/cache_storage_trace_utils.h" #include "content/common/background_fetch/background_fetch_types.h" #include "crypto/hmac.h" +#include "mojo/public/cpp/bindings/pending_receiver.h" +#include "mojo/public/cpp/bindings/receiver.h" #include "mojo/public/cpp/bindings/remote.h" #include "net/base/completion_repeating_callback.h" #include "net/base/io_buffer.h" @@ -508,6 +510,7 @@ mime_type, request_method, /*blob=*/nullptr, blink::mojom::ServiceWorkerResponseError::kUnknown, response_time, base::UTF16ToUTF8(cache_name), + /*cache_storage_side_data_writer=*/mojo::NullRemote(), std::vector<std::string>( metadata.response().cors_exposed_header_names().begin(), metadata.response().cors_exposed_header_names().end()), @@ -606,6 +609,52 @@ const int64_t trace_id = 0; }; +class CacheStorageCache::SideDataWriter + : public network::mojom::CacheStorageSideDataWriter { + public: + SideDataWriter( + CacheStorageCache* cache, + const GURL& url, + base::Time expected_response_time, + mojo::PendingReceiver<network::mojom::CacheStorageSideDataWriter> + receiver) + : handle_(cache->CreateHandle()), + url_(url), + expected_response_time_(expected_response_time), + receiver_(this, std::move(receiver)) { + receiver_.set_disconnect_handler( + base::BindOnce(&SideDataWriter::OnDisconnect, base::Unretained(this))); + } + + SideDataWriter(const SideDataWriter&) = delete; + SideDataWriter& operator=(const SideDataWriter&) = delete; + + ~SideDataWriter() override = default; + + const GURL& url() const { return url_; } + + // network::mojom::CacheStorageSideDataWriter: + void WriteSideData(mojo_base::BigBuffer data) override { + int64_t trace_id = blink::cache_storage::CreateTraceId(); + TRACE_EVENT("CacheStorage", + "CacheStorageCache::SideDataWriter::WriteSideData", + perfetto::Flow::Global(trace_id), "url", url_.spec()); + CacheStorageCache* cache = CacheStorageCache::From(handle_); + cache->WriteSideData(std::move(handle_), url_, expected_response_time_, + trace_id, std::move(data)); + } + + private: + void OnDisconnect() { + CacheStorageCache::From(handle_)->SideDataWriterDisconnected(this); + } + + CacheStorageCacheHandle handle_; + const GURL url_; + const base::Time expected_response_time_; + mojo::Receiver<network::mojom::CacheStorageSideDataWriter> receiver_; +}; + // static std::unique_ptr<CacheStorageCache> CacheStorageCache::CreateMemoryCache( const storage::BucketLocator& bucket_locator, @@ -646,6 +695,50 @@ return base::WrapUnique(cache); } +mojo::PendingRemote<network::mojom::CacheStorageSideDataWriter> +CacheStorageCache::CreateSideDataWriter(const GURL& url, + base::Time expected_response_time) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (backend_state_ == BACKEND_CLOSED) { + return mojo::NullRemote(); + } + mojo::PendingRemote<network::mojom::CacheStorageSideDataWriter> remote; + side_data_writers_.insert(std::make_unique<SideDataWriter>( + this, NormalizeCacheUrl(url), expected_response_time, + remote.InitWithNewPipeAndPassReceiver())); + return remote; +} + +void CacheStorageCache::SideDataWriterDisconnected(SideDataWriter* writer) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + auto it = side_data_writers_.find(writer); + if (it != side_data_writers_.end()) { + // Extract the node before destroying `SideDataWriter`, as dropping its + // `CacheStorageCacheHandle` may synchronously delete `this`. + std::ignore = side_data_writers_.extract(it); + } +} + +void CacheStorageCache::InvalidateSideDataWriters(std::optional<GURL> url) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + // Extract nodes before destroying `SideDataWriter` instances, as dropping + // their `CacheStorageCacheHandle`s may synchronously delete `this`. + if (!url.has_value()) { + auto writers = std::move(side_data_writers_); + return; + } + GURL normalized_url = NormalizeCacheUrl(*url); + std::vector<std::unique_ptr<SideDataWriter>> writers_to_delete; + for (auto it = side_data_writers_.begin(); it != side_data_writers_.end();) { + if ((*it)->url() == normalized_url) { + writers_to_delete.push_back( + std::move(side_data_writers_.extract(it++).value())); + } else { + ++it; + } + } +} + base::WeakPtr<CacheStorageCache> CacheStorageCache::AsWeakPtr() { return weak_ptr_factory_.GetWeakPtr(); } @@ -727,29 +820,28 @@ scheduler_->WrapCallbackToRunNext(id, std::move(callback)))); } -void CacheStorageCache::WriteSideData(ErrorCallback callback, - const GURL& url, +void CacheStorageCache::WriteSideData(CacheStorageCacheHandle cache_handle, + GURL url, base::Time expected_response_time, int64_t trace_id, - scoped_refptr<net::IOBuffer> buffer, - int buf_len) { + mojo_base::BigBuffer data) { + InvalidateSideDataWriters(url); + if (IsClosingOrClosed()) { - scheduler_task_runner_->PostTask( - FROM_HERE, - base::BindOnce( - std::move(callback), - MakeErrorStorage(ErrorStorageType::kWriteSideDataBackendClosed))); return; } + // Copy shared memory to a private heap buffer to avoid TOCTOU issues. + data.MakePrivateBytes(); + // GetBucketSpaceRemaining is called before entering a scheduled operation // since it can call Size, another scheduled operation. quota_manager_proxy_->GetBucketSpaceRemaining( bucket_locator_, scheduler_task_runner_, base::BindOnce( &CacheStorageCache::WriteSideDataDidGetBucketSpaceRemaining, - weak_ptr_factory_.GetWeakPtr(), std::move(callback), url, - expected_response_time, trace_id, buffer, buf_len)); + weak_ptr_factory_.GetWeakPtr(), std::move(cache_handle), + std::move(url), expected_response_time, trace_id, std::move(data))); } void CacheStorageCache::BatchOperation( @@ -1363,6 +1455,9 @@ .Run(CacheStorageError::kErrorQueryTooLarge, nullptr); return; } + match->response->cache_storage_side_data_writer = + CreateSideDataWriter(GURL(blob_entry->disk_cache_entry()->GetKey()), + match->response->response_time); if (blob_entry->disk_cache_entry()->GetDataSize(INDEX_RESPONSE_BODY) == 0) { QueryCacheOpenNextEntry(std::move(query_cache_context)); return; @@ -1570,30 +1665,23 @@ } void CacheStorageCache::WriteSideDataDidGetBucketSpaceRemaining( - ErrorCallback callback, + CacheStorageCacheHandle cache_handle, const GURL& url, base::Time expected_response_time, int64_t trace_id, - scoped_refptr<net::IOBuffer> buffer, - int buf_len, + mojo_base::BigBuffer data, storage::QuotaErrorOr<int64_t> space_remaining) { TRACE_EVENT("CacheStorage", "CacheStorageCache::WriteSideDataDidGetBucketSpaceRemaining", perfetto::Flow::Global(trace_id)); if (IsClosingOrClosed()) { - scheduler_task_runner_->PostTask( - FROM_HERE, - base::BindOnce( - std::move(callback), - MakeErrorStorage(ErrorStorageType::kWriteSideDataBackendClosed))); return; } - if (!space_remaining.has_value() || space_remaining.value() < buf_len) { - scheduler_task_runner_->PostTask( - FROM_HERE, base::BindOnce(std::move(callback), - CacheStorageError::kErrorQuotaExceeded)); + if (!space_remaining.has_value() || + !base::IsValueInRangeForNumericType<int64_t>(data.size()) || + space_remaining.value() < static_cast<int64_t>(data.size())) { return; } @@ -1602,35 +1690,32 @@ id, CacheStorageSchedulerMode::kExclusive, CacheStorageSchedulerOp::kWriteSideData, CacheStorageSchedulerPriority::kNormal, - base::BindOnce(&CacheStorageCache::WriteSideDataImpl, - weak_ptr_factory_.GetWeakPtr(), - scheduler_->WrapCallbackToRunNext(id, std::move(callback)), - url, expected_response_time, trace_id, buffer, buf_len)); + base::BindOnce( + &CacheStorageCache::WriteSideDataImpl, weak_ptr_factory_.GetWeakPtr(), + scheduler_->WrapCallbackToRunNext( + id, base::OnceClosure( + base::DoNothingWithBoundArgs(std::move(cache_handle)))), + url, expected_response_time, trace_id, std::move(data))); } -void CacheStorageCache::WriteSideDataImpl(ErrorCallback callback, +void CacheStorageCache::WriteSideDataImpl(base::OnceClosure callback, const GURL& url, base::Time expected_response_time, int64_t trace_id, - scoped_refptr<net::IOBuffer> buffer, - int buf_len) { + mojo_base::BigBuffer data) { CHECK_NE(BACKEND_UNINITIALIZED, backend_state_, base::NotFatalUntil::M158); TRACE_EVENT("CacheStorage", "CacheStorageCache::WriteSideDataImpl", perfetto::Flow::Global(trace_id), "url", url.spec()); if (backend_state_ != BACKEND_OPEN) { - std::move(callback).Run( - MakeErrorStorage(ErrorStorageType::kWriteSideDataImplBackendClosed)); + MakeErrorStorage(ErrorStorageType::kWriteSideDataImplBackendClosed); + std::move(callback).Run(); return; } - // Hold the cache alive while performing any operation touching the - // disk_cache backend. - callback = WrapCallbackWithHandle(std::move(callback)); - auto split_callback = base::SplitOnceCallback( base::BindOnce(&CacheStorageCache::WriteSideDataDidOpenEntry, weak_ptr_factory_.GetWeakPtr(), std::move(callback), - expected_response_time, trace_id, buffer, buf_len)); + expected_response_time, trace_id, std::move(data))); // Note, the simple disk_cache priority is not important here because we // only allow one write operation at a time. Therefore there will be no @@ -1643,17 +1728,16 @@ } void CacheStorageCache::WriteSideDataDidOpenEntry( - ErrorCallback callback, + base::OnceClosure callback, base::Time expected_response_time, int64_t trace_id, - scoped_refptr<net::IOBuffer> buffer, - int buf_len, + mojo_base::BigBuffer data, disk_cache::EntryResult result) { TRACE_EVENT("CacheStorage", "CacheStorageCache::WriteSideDataDidOpenEntry", perfetto::Flow::Global(trace_id)); if (result.net_error() != net::OK) { - std::move(callback).Run(CacheStorageError::kErrorNotFound); + std::move(callback).Run(); return; } @@ -1667,15 +1751,14 @@ base::BindOnce(&CacheStorageCache::WriteSideDataDidReadMetaData, weak_ptr_factory_.GetWeakPtr(), std::move(callback), expected_response_time, - trace_id, buffer, buf_len, std::move(entry))); + trace_id, std::move(data), std::move(entry))); } void CacheStorageCache::WriteSideDataDidReadMetaData( - ErrorCallback callback, + base::OnceClosure callback, base::Time expected_response_time, int64_t trace_id, - scoped_refptr<net::IOBuffer> buffer, - int buf_len, + mojo_base::BigBuffer data, ScopedWritableEntry entry, std::unique_ptr<proto::CacheMetadata> headers) { TRACE_EVENT("CacheStorage", "CacheStorageCache::WriteSideDataDidReadMetaData", @@ -1690,6 +1773,9 @@ // Get a temporary copy of the entry pointer before passing it in base::Bind. disk_cache::Entry* temp_entry_ptr = entry.get(); + auto buffer = base::MakeRefCounted<storage::BigIOBuffer>(std::move(data)); + int buf_len = buffer->size(); + // Create a callback that is copyable, even though it can only be called once. // BindRepeating() cannot be used directly because |callback|, |entry| and // |response| are not copyable. @@ -1708,7 +1794,7 @@ } void CacheStorageCache::WriteSideDataDidWrite( - ErrorCallback callback, + base::OnceClosure callback, ScopedWritableEntry entry, int expected_bytes, std::unique_ptr<::content::proto::CacheMetadata> metadata, @@ -1750,12 +1836,13 @@ CacheStorageError::kSuccess); } -void CacheStorageCache::WriteSideDataDidWriteMetadata(ErrorCallback callback, - ScopedWritableEntry entry, - int64_t padding, - int64_t side_data_padding, - int expected_bytes, - int rv) { +void CacheStorageCache::WriteSideDataDidWriteMetadata( + base::OnceClosure callback, + ScopedWritableEntry entry, + int64_t padding, + int64_t side_data_padding, + int expected_bytes, + int rv) { auto result = blink::mojom::CacheStorageError::kSuccess; if (rv != expected_bytes) { result = MakeErrorStorage( @@ -1766,7 +1853,7 @@ } void CacheStorageCache::WriteSideDataComplete( - ErrorCallback callback, + base::OnceClosure callback, ScopedWritableEntry entry, int64_t padding, int64_t side_data_padding, @@ -1778,18 +1865,18 @@ if (error != CacheStorageError::kErrorNotFound) { entry.reset(); cache_padding_ -= (padding + side_data_padding); - UpdateCacheSize(base::BindOnce(std::move(callback), error)); + UpdateCacheSize(std::move(callback)); return; } entry.get_deleter() .WritingCompleted(); // Since we didn't change the entry. - std::move(callback).Run(error); ... (file diff truncated)
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/components/content_settings/renderer/content_settings_agent_impl_browsertest.cc b/components/content_settings/renderer/content_settings_agent_impl_browsertest.cc
index c185dd1d..2fae63c 100644
--- a/components/content_settings/renderer/content_settings_agent_impl_browsertest.cc
+++ b/components/content_settings/renderer/content_settings_agent_impl_browsertest.cc
@@ -240,11 +240,6 @@
}
void ClearCodeCacheEntry(blink::mojom::CodeCacheType cache_type,
const GURL& url) override {}
- void DidGenerateCacheableMetadataInCacheStorage(
- const GURL& url,
- base::Time expected_response_time,
- mojo_base::BigBuffer data,
- const std::string& cache_storage_cache_name) override {}
private:
mojo::Receiver<blink::mojom::CodeCacheHost> receiver_{this};
diff --git a/content/browser/cache_storage/cache_storage_cache_unittest.cc b/content/browser/cache_storage/cache_storage_cache_unittest.cc
index 0725af7..a9b058c 100644
--- a/content/browser/cache_storage/cache_storage_cache_unittest.cc
+++ b/content/browser/cache_storage/cache_storage_cache_unittest.cc
@@ -687,6 +687,8 @@
void Init() { InitBackend(); }
+ using CacheStorageCache::WriteSideData;
+
base::CheckedNumeric<uint64_t> GetRequiredSafeSpaceForRequest(
const blink::mojom::FetchAPIRequestPtr& request) {
return CalculateRequiredSafeSpaceForRequest(request);
@@ -894,6 +896,7 @@
/*mime_type=*/std::nullopt, net::HttpRequestHeaders::kGetMethod,
/*blob=*/nullptr, blink::mojom::ServiceWorkerResponseError::kUnknown,
response_time_, /*cache_storage_cache_name=*/std::string(),
+ /*cache_storage_side_data_writer=*/mojo::NullRemote(),
/*cors_exposed_header_names=*/std::vector<std::string>(),
/*side_data_blob=*/nullptr,
/*side_data_for_cache_put=*/std::nullopt,
@@ -1060,16 +1063,22 @@
base::Time expected_response_time,
scoped_refptr<net::IOBuffer> buffer,
int buf_len) {
+ int old_modified_count =
+ quota_manager_proxy_->notify_bucket_modified_count();
base::HistogramTester histogram_tester;
- base::RunLoop run_loop;
cache_->WriteSideData(
- base::BindOnce(&CacheStorageCacheTest::ErrorTypeCallback,
- base::Unretained(this), base::Unretained(&run_loop)),
- url, expected_response_time, /* trace_id = */ 0, buffer, buf_len);
- run_loop.Run();
- if (callback_error_ == CacheStorageError::kSuccess)
+ cache_->CreateHandle(), url, expected_response_time, /*trace_id=*/0,
+ mojo_base::BigBuffer(base::as_bytes(
+ buffer->first(base::checked_cast<size_t>(buf_len)))));
+ size_t expected_ref_count = cache_->handle_ref_count_ - 1;
+ EXPECT_TRUE(base::test::RunUntil(
+ [&] { return cache_->handle_ref_count_ == expected_ref_count; }));
+ bool succeeded = quota_manager_proxy_->notify_bucket_modified_count() >
+ old_modified_count;
+ if (succeeded) {
CheckOpHistograms(histogram_tester, "WriteSideData");
- return callback_error_ == CacheStorageError::kSuccess;
+ }
+ return succeeded;
}
int64_t Size() {
@@ -1261,10 +1270,10 @@
operations.push_back(std::move(operation));
cache_->BatchOperation(std::move(operations), 0, batch.GetCallback(),
base::DoNothing());
- base::test::TestFuture<CacheStorageError> side_data;
- cache_->WriteSideData(side_data.GetCallback(), NoBodyUrl(), response_time_,
- 0, base::MakeRefCounted<net::StringIOBuffer>("data"),
- 4);
+ cache_->WriteSideData(cache_->CreateHandle(), NoBodyUrl(), response_time_,
+ /*trace_id=*/0,
+ mojo_base::BigBuffer(base::as_byte_span("data")));
+ EXPECT_TRUE(cache_->IsUnreferenced());
base::test::TestFuture<int64_t> size;
cache_->Size(size.GetCallback());
base::test::TestFuture<int64_t> size_then_close;
@@ -1273,7 +1282,7 @@
EXPECT_TRUE(base::test::RunUntil([&] {
return match.IsReady() && all.IsReady() && keys.IsReady() &&
entries.IsReady() && put.IsReady() && batch.IsReady() &&
- side_data.IsReady() && size.IsReady() && size_then_close.IsReady();
+ size.IsReady() && size_then_close.IsReady();
}));
EXPECT_FALSE(closed.IsReady());
EXPECT_FALSE(before.IsReady());
@@ -1285,7 +1294,6 @@
EXPECT_TRUE(entries.IsReady());
EXPECT_TRUE(put.IsReady());
EXPECT_TRUE(batch.IsReady());
- EXPECT_TRUE(side_data.IsReady());
EXPECT_TRUE(size.IsReady());
EXPECT_TRUE(size_then_close.IsReady());
if (match.IsReady()) {
@@ -1306,9 +1314,6 @@
if (batch.IsReady()) {
EXPECT_EQ(CacheStorageError::kErrorStorage, batch.Get()->value);
}
- if (side_data.IsReady()) {
- EXPECT_EQ(CacheStorageError::kErrorStorage, side_data.Get());
- }
if (size.IsReady()) {
EXPECT_EQ(0, size.Get());
}
@@ -1656,6 +1661,93 @@
EXPECT_FALSE(Match(body_head_request_));
}
+class TestCacheSizeObserver : public CacheStorageCacheObserver {
+ public:
+ explicit TestCacheSizeObserver(base::OnceClosure closure)
+ : closure_(std::move(closure)) {}
+ void CacheSizeUpdated(const CacheStorageCache* cache) override {
+ if (closure_) {
+ std::move(closure_).Run();
+ }
+ }
+
+ private:
+ base::OnceClosure closure_;
+};
+
+TEST_P(CacheStorageCacheTestP, MatchResponseIncludesSideDataWriterRemote) {
+ base::Time response_time(base::Time::Now());
+ blink::mojom::FetchAPIResponsePtr response = CreateBlobBodyResponse();
+ response->response_time = response_time;
+ EXPECT_TRUE(Put(body_request_, std::move(response)));
+
+ EXPECT_TRUE(Match(body_request_));
+ ASSERT_TRUE(callback_response_);
+ ASSERT_TRUE(callback_response_->cache_storage_side_data_writer.is_valid());
+
+ mojo::Remote<network::mojom::CacheStorageSideDataWriter> writer(
+ std::move(callback_response_->cache_storage_side_data_writer));
+ EXPECT_TRUE(writer.is_connected());
+
+ base::RunLoop size_updated_loop;
+ TestCacheSizeObserver size_observer(size_updated_loop.QuitClosure());
+ cache_->SetObserver(&size_observer);
+
+ const std::string expected_side_data = "SideDataViaMojoRemote";
+ writer->WriteSideData(
+ mojo_base::BigBuffer(base::as_byte_span(expected_side_data)));
+ writer.FlushForTesting();
+
+ // The writer should self-destruct after a single WriteSideData call,
+ // disconnecting its receiver.
+ EXPECT_FALSE(writer.is_connected());
+
+ size_updated_loop.Run();
+ cache_->SetObserver(nullptr);
+
+ EXPECT_TRUE(Match(body_request_));
+ ASSERT_TRUE(callback_response_->blob);
+ mojo::Remote<blink::mojom::Blob> blob(
+ std::move(callback_response_->blob->blob));
+ EXPECT_EQ(expected_side_data, CopySideData(blob.get()));
+}
+
+TEST_P(CacheStorageCacheTestP,
+ MatchResponseSideDataWriterDisconnectedWhenEntryDeleted) {
+ EXPECT_TRUE(Put(no_body_request_, CreateNoBodyResponse()));
+ EXPECT_TRUE(Match(no_body_request_));
+ ASSERT_TRUE(callback_response_);
+ ASSERT_TRUE(callback_response_->cache_storage_side_data_writer.is_valid());
+
+ mojo::Remote<network::mojom::CacheStorageSideDataWriter> writer(
+ std::move(callback_response_->cache_storage_side_data_writer));
+ EXPECT_TRUE(writer.is_connected());
+
+ EXPECT_TRUE(Delete(no_body_request_));
+ writer.FlushForTesting();
+
+ EXPECT_FALSE(writer.is_connected());
+}
+
+TEST_P(CacheStorageCacheTestP,
+ MatchResponseSideDataWriterDisconnectedWhenCacheClosed) {
+ EXPECT_TRUE(Put(no_body_request_, CreateNoBodyResponse()));
+ EXPECT_TRUE(Match(no_body_request_));
+ ASSERT_TRUE(callback_response_);
+ ASSERT_TRUE(callback_response_->cache_storage_side_data_writer.is_valid());
+
+ mojo::Remote<network::mojom::CacheStorageSideDataWriter> writer(
+ std::move(callback_response_->cache_storage_side_data_writer));
+ EXPECT_TRUE(writer.is_connected());
+
+ base::RunLoop loop;
+ cache_->Close(loop.QuitClosure());
+ loop.Run();
+ writer.FlushForTesting();
+
+ EXPECT_FALSE(writer.is_connected());
+}
+
TEST_P(CacheStorageCacheTestP, MatchAll_Empty) {
std::vector<blink::mojom::FetchAPIResponsePtr> responses;
EXPECT_TRUE(MatchAll(&responses));
@@ -2268,7 +2360,6 @@
std::ranges::fill(buffer->span(), 0);
EXPECT_FALSE(
WriteSideData(no_body_request_->url, response_time, buffer, kSize));
- EXPECT_EQ(CacheStorageError::kErrorQuotaExceeded, callback_error_);
ASSERT_TRUE(Delete(no_body_request_));
}
@@ -2304,7 +2395,6 @@
std::ranges::fill(buffer->span(), 0);
EXPECT_FALSE(WriteSideData(no_body_request_->url,
response_time + base::Seconds(1), buffer, kSize));
- EXPECT_EQ(CacheStorageError::kErrorNotFound, callback_error_);
ASSERT_TRUE(Delete(no_body_request_));
}
@@ -2314,7 +2404,6 @@
std::ranges::fill(buffer->span(), 0);
EXPECT_FALSE(WriteSideData(GURL("http://www.example.com/not_exist"),
base::Time::Now(), buffer, kSize));
- EXPECT_EQ(CacheStorageError::kErrorNotFound, callback_error_);
}
TEST_P(CacheStorageCacheTestP, ReadSideDataShortRead) {
@@ -2788,9 +2877,10 @@
operations.push_back(std::move(operation));
cache_->BatchOperation(std::move(operations), 0, batch.GetCallback(),
base::DoNothing());
- base::test::TestFuture<CacheStorageError> side_data;
- cache_->WriteSideData(side_data.GetCallback(), BodyUrl(), response_time_, 0,
- base::MakeRefCounted<net::StringIOBuffer>("data"), 4);
+ cache_->WriteSideData(cache_->CreateHandle(), BodyUrl(), response_time_,
+ /*trace_id=*/0,
+ mojo_base::BigBuffer(base::as_byte_span("data")));
+ EXPECT_FALSE(cache_->IsUnreferenced());
base::test::TestFuture<void> closed;
cache_->Close(base::BindLambdaForTesting([&] {
@@ -2798,16 +2888,13 @@
closed.SetValue();
}));
EXPECT_TRUE(base::test::RunUntil(
- [&] { return batch.IsReady() && side_data.IsReady(); }));
+ [&] { return batch.IsReady() && cache_->IsUnreferenced(); }));
EXPECT_FALSE(closed.IsReady());
EXPECT_TRUE(batch.IsReady());
- EXPECT_TRUE(side_data.IsReady());
+ EXPECT_TRUE(cache_->IsUnreferenced());
if (batch.IsReady()) {
EXPECT_EQ(CacheStorageError::kErrorStorage, batch.Get()->value);
}
- if (side_data.IsReady()) {
- EXPECT_EQ(CacheStorageError::kErrorStorage, side_data.Get());
- }
std::move(unblock).Run();
ASSERT_TRUE(closed.Wait());
@@ -3057,15 +3144,10 @@
delayable_backend->set_delay_open_entry(true);
const std::string expected_side_data = "SideDataSample";
- scoped_refptr<net::IOBuffer> buffer =
- base::MakeRefCounted<net::StringIOBuffer>(expected_side_data);
- std::unique_ptr<base::RunLoop> loop(new base::RunLoop());
cache_->WriteSideData(
- base::BindOnce(&CacheStorageCacheTest::ErrorTypeCallback,
- base::Unretained(this), base::Unretained(loop.get())),
- BodyUrl(), response_time, /* trace_id = */ 0, buffer,
- expected_side_data.length());
+ cache_->CreateHandle(), BodyUrl(), response_time, /*trace_id=*/0,
+ mojo_base::BigBuffer(base::as_byte_span(expected_side_data)));
// Blocks on opening the cache entry.
base::RunLoop().RunUntilIdle();
@@ -3076,10 +3158,7 @@
// Allow the operation to continue.
EXPECT_TRUE(delayable_backend->OpenEntryContinue());
- loop->Run();
-
- // The operation should succeed.
- EXPECT_EQ(CacheStorageError::kSuccess, callback_error_);
+ EXPECT_TRUE(base::test::RunUntil([&] { return cache_->IsUnreferenced(); }));
}
TEST_P(CacheStorageCacheTestP, SelfRefsDuringBatchOperation) {
diff --git a/content/browser/cache_storage/cache_storage_manager_unittest.cc b/content/browser/cache_storage/cache_storage_manager_unittest.cc
index df8f5d93..4c0263f 100644
--- a/content/browser/cache_storage/cache_storage_manager_unittest.cc
+++ b/content/browser/cache_storage/cache_storage_manager_unittest.cc
@@ -688,6 +688,7 @@
net::HttpRequestHeaders::kGetMethod, std::move(blob),
blink::mojom::ServiceWorkerResponseError::kUnknown, base::Time(),
/*cache_storage_cache_name=*/std::string(),
+ /*cache_storage_side_data_writer=*/mojo::NullRemote(),
/*cors_exposed_header_names=*/std::vector<std::string>(),
... (truncated)
Loading diff…
Original Bug Report
The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.
References
On This Page