Chrome · Network
CVE-2026-76022
OOB in Network
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifnet/http/http_cache_unittest.cc |
modified | |
TEST_Fnet/http/http_cache_unittest.cc |
modified |
Files Changed
net/http/http_cache_transaction.ccnet/http/http_cache_transaction.hnet/http/http_cache_unittest.ccnet/http/partial_data.cc
Patch
From f0171e077fe31182c571370f49f8959369e7cb8d Mon Sep 17 00:00:00 2001 From: Maks Orlovich <[email protected]> Date: Fri, 14 Aug 2026 10:01:58 -0700 Subject: [PATCH] HttpCacheTransaction: fix confusion around range request w/Vary:Range Bug: 543798025 Change-Id: Idaceab5116fe9fedccc97fdd13333b0cd9fc9f0b Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8243730 Reviewed-by: Adam Rice <[email protected]> Commit-Queue: Maks Orlovich <[email protected]> Cr-Commit-Position: refs/heads/main@{#1679626} --- diff --git a/net/http/http_cache_transaction.cc b/net/http/http_cache_transaction.cc index 543a583..4fe7e55 100644 --- a/net/http/http_cache_transaction.cc +++ b/net/http/http_cache_transaction.cc @@ -1635,18 +1635,18 @@ TransitionToState(STATE_CACHE_READ_RESPONSE_COMPLETE); io_buf_len_ = entry_->GetEntry()->GetDataSize(kResponseInfoIndex); - read_buf_ = base::MakeRefCounted<IOBufferWithSize>(io_buf_len_); + cache_buf_ = base::MakeRefCounted<IOBufferWithSize>(io_buf_len_); net_log_.BeginEvent(NetLogEventType::HTTP_CACHE_READ_INFO); BeginDiskCacheAccessTimeCount(); - return entry_->GetEntry()->ReadData(kResponseInfoIndex, 0, read_buf_.get(), + return entry_->GetEntry()->ReadData(kResponseInfoIndex, 0, cache_buf_.get(), io_buf_len_, io_callback_); } int HttpCache::Transaction::DoCacheReadResponseComplete(int result) { TRACE_EVENT_INSTANT(TRACE_DISABLED_BY_DEFAULT("net"), "DoCacheReadResponseComplete", track_for_state_change_, - "result", result, "io_buf_len", read_buf_->size()); + "result", result, "io_buf_len", cache_buf_->size()); net_log_.EndEventWithNetErrorCode(NetLogEventType::HTTP_CACHE_READ_INFO, result); EndDiskCacheAccessTimeCount(DiskCacheAccessType::kRead); @@ -1654,8 +1654,8 @@ // Record the time immediately before the cached response is parsed. read_headers_since_ = TimeTicks::Now(); - if (result != read_buf_->size() || - !HttpCache::ParseResponseInfo(read_buf_->span(), &response_, + if (result != cache_buf_->size() || + !HttpCache::ParseResponseInfo(cache_buf_->span(), &response_, &truncated_)) { return OnCacheReadError(result, true); } diff --git a/net/http/http_cache_transaction.h b/net/http/http_cache_transaction.h index 29e9263..66bcb78 100644 --- a/net/http/http_cache_transaction.h +++ b/net/http/http_cache_transaction.h @@ -765,6 +765,7 @@ // Length of the buffer passed in Read(). int read_buf_len_ = 0; + scoped_refptr<IOBuffer> cache_buf_; int io_buf_len_ = 0; int read_offset_ = 0; // Disk offset for reads on the compressed cache path. Advances by the diff --git a/net/http/http_cache_unittest.cc b/net/http/http_cache_unittest.cc index ceeb606..3760bba 100644 --- a/net/http/http_cache_unittest.cc +++ b/net/http/http_cache_unittest.cc @@ -562,7 +562,9 @@ start, end, length_); response_headers->append(content_range); - if (!request->extra_headers.HasHeader("If-None-Match") || modified_) { + if ((!request->extra_headers.HasHeader("If-None-Match") && + !request->extra_headers.HasHeader("If-Modified-Since")) || + modified_) { std::string data; if (end == start) { EXPECT_EQ(0, end % 10); @@ -581,8 +583,14 @@ int64_t len = end - start + 1; std::string content_length = base::StringPrintf("Content-Length: %" PRId64 "\n", len); - response_headers->replace(response_headers->find("Content-Length:"), - content_length.size(), content_length); + size_t length_start = response_headers->find("Content-Length:"); + CHECK_NE(length_start, std::string::npos); + size_t length_end = response_headers->find('\n', length_start); + if (length_end == std::string::npos) { + length_end = response_headers->length(); + } + response_headers->replace(length_start, length_end - length_start, + content_length); } } else { response_status->assign("HTTP/1.1 304 Not Modified"); @@ -3831,6 +3839,74 @@ EXPECT_EQ(1, cache.disk_cache()->create_count()); } +TEST_F(HttpCacheRangeGetTest, VaryRange) { + MockHttpCache cache; + cache.disk_cache()->set_double_create_check(false); + + ScopedMockTransaction transaction(kRangeGET_TransactionOK); + transaction.response_headers = + "Last-Modified: Sat, 18 Apr 2007 01:10:43 GMT\n" + "Date: Sat, 18 Apr 2007 02:10:43 GMT\n" + "Accept-Ranges: bytes\n" + "Vary: range\n" + "Content-Length: 10\n"; + + RangeTransactionServer range_support; + range_support.set_length(60000); + + // Request the transaction's 40-49 range, and get it cached. + { + std::string headers; + RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers); + Verify206Response(headers, 40, 49); + } + + // Now request an extension. + transaction.request_headers = "Range: bytes = 40-59009\r\n" EXTRA_HEADER; + MockHttpRequest request(transaction); + Context c; + + c.trans = cache.CreateTransaction(); + ASSERT_TRUE(c.trans); + + int rv = c.callback.GetResult( + c.trans->Start(&request, c.callback.callback(), NetLogWithSource())); + ASSERT_THAT(rv, IsOk()); + + // First read the 40-49 portion. + scoped_refptr<IOBufferWithSize> buf = + base::MakeRefCounted<IOBufferWithSize>(10); + rv = c.callback.GetResult( + c.trans->Read(buf.get(), buf->size(), c.callback.callback())); + EXPECT_EQ(10, rv); + EXPECT_EQ(buf->first(10), base::byte_span_from_cstring("rg: 40-49 ")); + + // Replace with an in-progress different entry, with a different range, that's + // in-flight. + Context c2; + ScopedMockTransaction transaction2(transaction); + transaction2.request_headers = "Range: bytes = 30-39\r\n" EXTRA_HEADER; + transaction2.load_flags = LOAD_BYPASS_CACHE; + transaction2.data = "rg: 30-39 "; + MockHttpRequest request2(transaction2); + + c2.trans = cache.CreateTransaction(); + ASSERT_TRUE(c2.trans); + + rv = c2.callback.GetResult( + c2.trans->Start(&request2, c2.callback.callback(), NetLogWithSource())); + ASSERT_THAT(rv, IsOk()); + + // Try to read the 50-5059 portion. The right bits should come in. + scoped_refptr<IOBufferWithSize> buf2 = + base::MakeRefCounted<IOBufferWithSize>(6000); + std::ranges::fill(buf2->span(), 'A'); + rv = c.callback.GetResult( + c.trans->Read(buf2.get(), buf2->size(), c.callback.callback())); + EXPECT_LE(10, rv); + EXPECT_EQ(buf2->first(10), base::byte_span_from_cstring("rg: 50-59 ")); +} + // A test of doing a range request to a cached 301 response TEST_F(HttpCacheRangeGetTest, CachedRedirect) { RangeTransactionServer handler; @@ -9603,7 +9679,6 @@ std::string headers; MockTransaction transaction2(kRangeGET_TransactionOK); RangeTransactionServer handler; - handler.set_not_modified(true); RunTransactionTestWithResponse(cache.http_cache(), transaction2, &headers); // We are expecting a 206. @@ -9637,7 +9712,7 @@ base::RunLoop().RunUntilIdle(); // Now we should receive a range from the server and drop the stored entry. - handler.set_not_modified(false); + handler.set_modified(true); transaction2.request_headers = kRangeGET_TransactionOK.request_headers; RunTransactionTestWithResponse(cache.http_cache(), transaction2, &headers); Verify206Response(headers, 40, 49); diff --git a/net/http/partial_data.cc b/net/http/partial_data.cc index 3b2373dd..9c34bb17 100644 --- a/net/http/partial_data.cc +++ b/net/http/partial_data.cc @@ -185,6 +185,17 @@ disk_cache::Entry* entry, bool truncated, bool writing_in_progress) { + // Vary: range is a very confusing for us when we're gluing things + // together, so just give up on it. + size_t iter = 0; + constexpr std::string_view name = "vary"; + std::optional<std::string_view> vary_entry; + while ((vary_entry = headers->EnumerateHeader(&iter, name))) {
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/net/http/http_cache_unittest.cc b/net/http/http_cache_unittest.cc
index ceeb606..3760bba 100644
--- a/net/http/http_cache_unittest.cc
+++ b/net/http/http_cache_unittest.cc
@@ -562,7 +562,9 @@
start, end, length_);
response_headers->append(content_range);
- if (!request->extra_headers.HasHeader("If-None-Match") || modified_) {
+ if ((!request->extra_headers.HasHeader("If-None-Match") &&
+ !request->extra_headers.HasHeader("If-Modified-Since")) ||
+ modified_) {
std::string data;
if (end == start) {
EXPECT_EQ(0, end % 10);
@@ -581,8 +583,14 @@
int64_t len = end - start + 1;
std::string content_length =
base::StringPrintf("Content-Length: %" PRId64 "\n", len);
- response_headers->replace(response_headers->find("Content-Length:"),
- content_length.size(), content_length);
+ size_t length_start = response_headers->find("Content-Length:");
+ CHECK_NE(length_start, std::string::npos);
+ size_t length_end = response_headers->find('\n', length_start);
+ if (length_end == std::string::npos) {
+ length_end = response_headers->length();
+ }
+ response_headers->replace(length_start, length_end - length_start,
+ content_length);
}
} else {
response_status->assign("HTTP/1.1 304 Not Modified");
@@ -3831,6 +3839,74 @@
EXPECT_EQ(1, cache.disk_cache()->create_count());
}
+TEST_F(HttpCacheRangeGetTest, VaryRange) {
+ MockHttpCache cache;
+ cache.disk_cache()->set_double_create_check(false);
+
+ ScopedMockTransaction transaction(kRangeGET_TransactionOK);
+ transaction.response_headers =
+ "Last-Modified: Sat, 18 Apr 2007 01:10:43 GMT\n"
+ "Date: Sat, 18 Apr 2007 02:10:43 GMT\n"
+ "Accept-Ranges: bytes\n"
+ "Vary: range\n"
+ "Content-Length: 10\n";
+
+ RangeTransactionServer range_support;
+ range_support.set_length(60000);
+
+ // Request the transaction's 40-49 range, and get it cached.
+ {
+ std::string headers;
+ RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
+ Verify206Response(headers, 40, 49);
+ }
+
+ // Now request an extension.
+ transaction.request_headers = "Range: bytes = 40-59009\r\n" EXTRA_HEADER;
+ MockHttpRequest request(transaction);
+ Context c;
+
+ c.trans = cache.CreateTransaction();
+ ASSERT_TRUE(c.trans);
+
+ int rv = c.callback.GetResult(
+ c.trans->Start(&request, c.callback.callback(), NetLogWithSource()));
+ ASSERT_THAT(rv, IsOk());
+
+ // First read the 40-49 portion.
+ scoped_refptr<IOBufferWithSize> buf =
+ base::MakeRefCounted<IOBufferWithSize>(10);
+ rv = c.callback.GetResult(
+ c.trans->Read(buf.get(), buf->size(), c.callback.callback()));
+ EXPECT_EQ(10, rv);
+ EXPECT_EQ(buf->first(10), base::byte_span_from_cstring("rg: 40-49 "));
+
+ // Replace with an in-progress different entry, with a different range, that's
+ // in-flight.
+ Context c2;
+ ScopedMockTransaction transaction2(transaction);
+ transaction2.request_headers = "Range: bytes = 30-39\r\n" EXTRA_HEADER;
+ transaction2.load_flags = LOAD_BYPASS_CACHE;
+ transaction2.data = "rg: 30-39 ";
+ MockHttpRequest request2(transaction2);
+
+ c2.trans = cache.CreateTransaction();
+ ASSERT_TRUE(c2.trans);
+
+ rv = c2.callback.GetResult(
+ c2.trans->Start(&request2, c2.callback.callback(), NetLogWithSource()));
+ ASSERT_THAT(rv, IsOk());
+
+ // Try to read the 50-5059 portion. The right bits should come in.
+ scoped_refptr<IOBufferWithSize> buf2 =
+ base::MakeRefCounted<IOBufferWithSize>(6000);
+ std::ranges::fill(buf2->span(), 'A');
+ rv = c.callback.GetResult(
+ c.trans->Read(buf2.get(), buf2->size(), c.callback.callback()));
+ EXPECT_LE(10, rv);
+ EXPECT_EQ(buf2->first(10), base::byte_span_from_cstring("rg: 50-59 "));
+}
+
// A test of doing a range request to a cached 301 response
TEST_F(HttpCacheRangeGetTest, CachedRedirect) {
RangeTransactionServer handler;
@@ -9603,7 +9679,6 @@
std::string headers;
MockTransaction transaction2(kRangeGET_TransactionOK);
RangeTransactionServer handler;
- handler.set_not_modified(true);
RunTransactionTestWithResponse(cache.http_cache(), transaction2, &headers);
// We are expecting a 206.
@@ -9637,7 +9712,7 @@
base::RunLoop().RunUntilIdle();
// Now we should receive a range from the server and drop the stored entry.
- handler.set_not_modified(false);
+ handler.set_modified(true);
transaction2.request_headers = kRangeGET_TransactionOK.request_headers;
RunTransactionTestWithResponse(cache.http_cache(), transaction2, &headers);
Verify206Response(headers, 40, 49);
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