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 <elkurin@google.com>
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 <rakina@chromium.org>
Reviewed-by: Evan Stade <evanstade@microsoft.com>
Reviewed-by: Kenichi Ishibashi <bashi@chromium.org>
Commit-Queue: Christian Dullweber <dullweber@chromium.org>
Reviewed-by: Kentaro Hara <haraken@chromium.org>
Auto-Submit: Eriko Kurimoto <elkurin@chromium.org>
Reviewed-by: Christian Dullweber <dullweber@chromium.org>
Reviewed-by: Sam McNally <sammc@chromium.org>
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