Chrome · Persistent Cache
CVE-2026-7944
Logic Error in Persistent Cache
Overview
Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
TEST_Pcontent/browser/renderer_host/code_cache_host_impl_unittest.cc |
modified | |
BindLambdaForTestingcontent/browser/renderer_host/code_cache_host_impl_unittest.cc |
modified |
Files Changed
content/browser/renderer_host/code_cache_host_impl.cccontent/browser/renderer_host/code_cache_host_impl_unittest.cc
Patch
From 6fa33375364335468c0954cb3301e25f892f32cb Mon Sep 17 00:00:00 2001 From: Greg Thompson <[email protected]> Date: Fri, 27 Mar 2026 06:30:24 -0700 Subject: [PATCH] [CodeCache] Disable the code cache for PDF and sandboxed iframes This prevents compromised PDF renderers or those for origin-restricted sandboxed iframes from tampering with compiled code belonging to their hosting origin. Fixed: 495783187 Change-Id: I0015e8f27f7fb6f6315bf8345b1bbb5a358ed527 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7702794 Reviewed-by: Charlie Reis <[email protected]> Commit-Queue: Greg Thompson <[email protected]> Cr-Commit-Position: refs/heads/main@{#1606170} --- diff --git a/content/browser/renderer_host/code_cache_host_impl.cc b/content/browser/renderer_host/code_cache_host_impl.cc index f8116be..2b75860 100644 --- a/content/browser/renderer_host/code_cache_host_impl.cc +++ b/content/browser/renderer_host/code_cache_host_impl.cc @@ -129,9 +129,11 @@ // initiated navigation to a data: URL). In these cases, the code should not be // cached since the serialized value of opaque origins should not be used as a // key. -// Case 4. origin_lock if the scheme of origin_lock is +// Case 4. a std::nullopt for PDF processes and origin-restricted sandboxed +// iframes, to prevent them from accessing the cache of their hosting origins. +// Case 5. origin_lock if the scheme of origin_lock is // Http/Https/chrome/chrome-untrusted. -// Case 5. std::nullopt otherwise. +// Case 6. std::nullopt otherwise. std::optional<GURL> GetOriginLock(ChildProcessId render_process_id) { ProcessLock process_lock = ChildProcessSecurityPolicyImpl::GetInstance()->GetProcessLock( @@ -167,7 +169,15 @@ return std::nullopt; } - // Case 4: process_lock_url is used to enforce site-isolation in code caches. + // Case 4: PDF processes and origin-restricted sandboxed iframes should not + // have access to the code cache of their hosting origins. PDF processes are + // less trusted, and sandboxed iframes should be treated as having opaque + // origins. + if (process_lock.is_pdf() || process_lock.is_sandboxed()) { + return std::nullopt; + } + + // Case 5: process_lock_url is used to enforce site-isolation in code caches. // Http/https/chrome schemes are safe to be used as a secondary key. Other // schemes could be enabled if they are known to be safe and if it is // required to cache code from those origins. @@ -428,9 +438,11 @@ // initiated navigation to a data: URL). In these cases, the code should not // be cached since the serialized value of opaque origins should not be used // as a key. - // Case 3: origin_lock if the scheme of origin_lock is + // Case 3. a std::nullopt for PDF processes and origin-restricted sandboxed + // iframes, to prevent them from accessing the cache of their hosting origins. + // Case 4: origin_lock if the scheme of origin_lock is // Http/Https/chrome/chrome-untrusted. - // Case 4. std::nullopt otherwise. + // Case 5. std::nullopt otherwise. static std::optional<GURL> GetSecondaryKeyForCodeCache( const GURL& resource_url, ChildProcessId render_process_id, diff --git a/content/browser/renderer_host/code_cache_host_impl_unittest.cc b/content/browser/renderer_host/code_cache_host_impl_unittest.cc index 857cb82..8761e95 100644 --- a/content/browser/renderer_host/code_cache_host_impl_unittest.cc +++ b/content/browser/renderer_host/code_cache_host_impl_unittest.cc @@ -21,6 +21,7 @@ #include "content/browser/code_cache/generated_code_cache_context.h" #include "content/browser/process_lock.h" #include "content/browser/site_instance_impl.h" +#include "content/browser/url_info.h" #include "content/public/common/content_features.h" #include "content/public/common/content_switches.h" #include "content/public/common/url_constants.h" @@ -77,13 +78,18 @@ bool IsSitePerProcessOrStricter() { return GetParam(); } - void SetupRendererWithLock(ChildProcessId process_id, const GURL& url) { + void SetupRendererWithLock(ChildProcessId process_id, + const UrlInfo& url_info) { ChildProcessSecurityPolicyImpl* p = ChildProcessSecurityPolicyImpl::GetInstance(); p->AddForTesting(process_id, &browser_context_); scoped_refptr<SiteInstanceImpl> site_instance = - SiteInstanceImpl::CreateForTesting(&browser_context_, url); + SiteInstanceImpl::CreateForUrlInfo( + &browser_context_, url_info, + /*is_guest=*/false, + /*is_fenced=*/false, + /*is_fixed_storage_partition=*/false); ChildProcessSecurityPolicyImpl::GetInstance()->LockProcess( site_instance->GetIsolationContext(), process_id, false, ProcessLock::FromSiteInfo(site_instance->GetSiteInfo())); @@ -91,6 +97,10 @@ added_renderers_.push_back(process_id); } + void SetupRendererWithLock(ChildProcessId process_id, const GURL& url) { + SetupRendererWithLock(process_id, UrlInfo::CreateForTesting(url)); + } + protected: BrowserTaskEnvironment task_environment_; base::HistogramTester histogram_tester; @@ -505,6 +515,120 @@ run_loop.Run(); } +// Tests that a PDF page does not see a resource cached by an open web site +// on the same origin. Validates that process separation is properly maintained +// in the V8 cache. +TEST_P(CodeCacheHostImplTest, PdfObliviousToOpenWeb) { + base::RunLoop run_loop; + + // The URL of a resource loaded by both a site on the open web and a PDF + // page. + const GURL resource_url("https://victim.example.com/script.js"); + + // State for a site on the open web that loads the above resource. + const ChildProcessId kOpenWebProcessId(12); + const GURL open_web_site("https://victim.example.com/"); + SetupRendererWithLock(kOpenWebProcessId, open_web_site); + + // State for a PDF page that also loads the above resource on the same site. + // Note that this requires setting is_pdf to true in the starting UrlInfo. + const ChildProcessId kPdfProcessId(13); + UrlInfo url_info(UrlInfoInit(open_web_site).WithIsPdf(true)); + SetupRendererWithLock(kPdfProcessId, url_info); + + GeneratedCodeCacheContext::RunOrPostTask( + generated_code_cache_context_.get(), FROM_HERE, + base::BindLambdaForTesting([&]() { + // Create the open web's cache and put the resource into it. + auto open_web_host = CodeCacheHostImpl::Create( + kOpenWebProcessId, generated_code_cache_context_, + net::NetworkIsolationKey(net::SchemefulSite{open_web_site}, + net::SchemefulSite{open_web_site}), + blink::StorageKey::CreateFirstParty( + url::Origin::Create(open_web_site))); + open_web_host->DidGenerateCacheableMetadata( + blink::mojom::CodeCacheType::kJavascript, resource_url, + base::Time::Now(), + mojo_base::BigBuffer(base::byte_span_from_cstring("hi"))); + + // Create a PDF page's cache and make sure the resource is absent. + // It should NOT receive the contents of the HTML's V8 cache! + auto pdf_host = CodeCacheHostImpl::Create( + kPdfProcessId, generated_code_cache_context_, + net::NetworkIsolationKey(net::SchemefulSite{open_web_site}, + net::SchemefulSite{open_web_site}), + blink::StorageKey::CreateFirstParty( + url::Origin::Create(open_web_site))); + pdf_host->FetchCachedCode( + blink::mojom::CodeCacheType::kJavascript, resource_url, + base::BindLambdaForTesting([&](base::Time found_response_time, + mojo_base::BigBuffer found_data) { + EXPECT_EQ(found_response_time, base::Time()); + EXPECT_EQ(found_data.size(), 0U); + run_loop.Quit(); + })); + })); + + run_loop.Run(); +} + +// Tests that an origin-restricted sandboxed iframe does not see a resource +// cached by an open web site on the same origin. +TEST_P(CodeCacheHostImplTest, SandboxedIframeObliviousToOpenWeb) { + base::RunLoop run_loop; + + // The URL of a resource loaded by both a site on the open web and a + // sandboxed iframe. + const GURL resource_url("https://victim.example.com/script.js"); + + // State for a site on the open web that loads the above resource. + const ChildProcessId kOpenWebProcessId(12); + const GURL open_web_site("https://victim.example.com/"); + SetupRendererWithLock(kOpenWebProcessId, open_web_site); + + // State for a sandboxed iframe that also loads the above resource on the + // same site. Note that this requires setting is_sandboxed to true in the + // starting UrlInfo. + const ChildProcessId kSandboxedProcessId(13); + UrlInfo url_info(UrlInfoInit(open_web_site).WithSandbox(true)); + SetupRendererWithLock(kSandboxedProcessId, url_info); + + GeneratedCodeCacheContext::RunOrPostTask( + generated_code_cache_context_.get(), FROM_HERE, + base::BindLambdaForTesting([&]() { + // Create the open web's cache and put the resource into it. + auto open_web_host = CodeCacheHostImpl::Create( + kOpenWebProcessId, generated_code_cache_context_, + net::NetworkIsolationKey(net::SchemefulSite{open_web_site}, + net::SchemefulSite{open_web_site}),
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/content/browser/renderer_host/code_cache_host_impl_unittest.cc b/content/browser/renderer_host/code_cache_host_impl_unittest.cc
index 857cb82..8761e95 100644
--- a/content/browser/renderer_host/code_cache_host_impl_unittest.cc
+++ b/content/browser/renderer_host/code_cache_host_impl_unittest.cc
@@ -21,6 +21,7 @@
#include "content/browser/code_cache/generated_code_cache_context.h"
#include "content/browser/process_lock.h"
#include "content/browser/site_instance_impl.h"
+#include "content/browser/url_info.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/url_constants.h"
@@ -77,13 +78,18 @@
bool IsSitePerProcessOrStricter() { return GetParam(); }
- void SetupRendererWithLock(ChildProcessId process_id, const GURL& url) {
+ void SetupRendererWithLock(ChildProcessId process_id,
+ const UrlInfo& url_info) {
ChildProcessSecurityPolicyImpl* p =
ChildProcessSecurityPolicyImpl::GetInstance();
p->AddForTesting(process_id, &browser_context_);
scoped_refptr<SiteInstanceImpl> site_instance =
- SiteInstanceImpl::CreateForTesting(&browser_context_, url);
+ SiteInstanceImpl::CreateForUrlInfo(
+ &browser_context_, url_info,
+ /*is_guest=*/false,
+ /*is_fenced=*/false,
+ /*is_fixed_storage_partition=*/false);
ChildProcessSecurityPolicyImpl::GetInstance()->LockProcess(
site_instance->GetIsolationContext(), process_id, false,
ProcessLock::FromSiteInfo(site_instance->GetSiteInfo()));
@@ -91,6 +97,10 @@
added_renderers_.push_back(process_id);
}
+ void SetupRendererWithLock(ChildProcessId process_id, const GURL& url) {
+ SetupRendererWithLock(process_id, UrlInfo::CreateForTesting(url));
+ }
+
protected:
BrowserTaskEnvironment task_environment_;
base::HistogramTester histogram_tester;
@@ -505,6 +515,120 @@
run_loop.Run();
}
+// Tests that a PDF page does not see a resource cached by an open web site
+// on the same origin. Validates that process separation is properly maintained
+// in the V8 cache.
+TEST_P(CodeCacheHostImplTest, PdfObliviousToOpenWeb) {
+ base::RunLoop run_loop;
+
+ // The URL of a resource loaded by both a site on the open web and a PDF
+ // page.
+ const GURL resource_url("https://victim.example.com/script.js");
+
+ // State for a site on the open web that loads the above resource.
+ const ChildProcessId kOpenWebProcessId(12);
+ const GURL open_web_site("https://victim.example.com/");
+ SetupRendererWithLock(kOpenWebProcessId, open_web_site);
+
+ // State for a PDF page that also loads the above resource on the same site.
+ // Note that this requires setting is_pdf to true in the starting UrlInfo.
+ const ChildProcessId kPdfProcessId(13);
+ UrlInfo url_info(UrlInfoInit(open_web_site).WithIsPdf(true));
+ SetupRendererWithLock(kPdfProcessId, url_info);
+
+ GeneratedCodeCacheContext::RunOrPostTask(
+ generated_code_cache_context_.get(), FROM_HERE,
+ base::BindLambdaForTesting([&]() {
+ // Create the open web's cache and put the resource into it.
+ auto open_web_host = CodeCacheHostImpl::Create(
+ kOpenWebProcessId, generated_code_cache_context_,
+ net::NetworkIsolationKey(net::SchemefulSite{open_web_site},
+ net::SchemefulSite{open_web_site}),
+ blink::StorageKey::CreateFirstParty(
+ url::Origin::Create(open_web_site)));
+ open_web_host->DidGenerateCacheableMetadata(
+ blink::mojom::CodeCacheType::kJavascript, resource_url,
+ base::Time::Now(),
+ mojo_base::BigBuffer(base::byte_span_from_cstring("hi")));
+
+ // Create a PDF page's cache and make sure the resource is absent.
+ // It should NOT receive the contents of the HTML's V8 cache!
+ auto pdf_host = CodeCacheHostImpl::Create(
+ kPdfProcessId, generated_code_cache_context_,
+ net::NetworkIsolationKey(net::SchemefulSite{open_web_site},
+ net::SchemefulSite{open_web_site}),
+ blink::StorageKey::CreateFirstParty(
+ url::Origin::Create(open_web_site)));
+ pdf_host->FetchCachedCode(
+ blink::mojom::CodeCacheType::kJavascript, resource_url,
+ base::BindLambdaForTesting([&](base::Time found_response_time,
+ mojo_base::BigBuffer found_data) {
+ EXPECT_EQ(found_response_time, base::Time());
+ EXPECT_EQ(found_data.size(), 0U);
+ run_loop.Quit();
+ }));
+ }));
+
+ run_loop.Run();
+}
+
+// Tests that an origin-restricted sandboxed iframe does not see a resource
+// cached by an open web site on the same origin.
+TEST_P(CodeCacheHostImplTest, SandboxedIframeObliviousToOpenWeb) {
+ base::RunLoop run_loop;
+
+ // The URL of a resource loaded by both a site on the open web and a
+ // sandboxed iframe.
+ const GURL resource_url("https://victim.example.com/script.js");
+
+ // State for a site on the open web that loads the above resource.
+ const ChildProcessId kOpenWebProcessId(12);
+ const GURL open_web_site("https://victim.example.com/");
+ SetupRendererWithLock(kOpenWebProcessId, open_web_site);
+
+ // State for a sandboxed iframe that also loads the above resource on the
+ // same site. Note that this requires setting is_sandboxed to true in the
+ // starting UrlInfo.
+ const ChildProcessId kSandboxedProcessId(13);
+ UrlInfo url_info(UrlInfoInit(open_web_site).WithSandbox(true));
+ SetupRendererWithLock(kSandboxedProcessId, url_info);
+
+ GeneratedCodeCacheContext::RunOrPostTask(
+ generated_code_cache_context_.get(), FROM_HERE,
+ base::BindLambdaForTesting([&]() {
+ // Create the open web's cache and put the resource into it.
+ auto open_web_host = CodeCacheHostImpl::Create(
+ kOpenWebProcessId, generated_code_cache_context_,
+ net::NetworkIsolationKey(net::SchemefulSite{open_web_site},
+ net::SchemefulSite{open_web_site}),
+ blink::StorageKey::CreateFirstParty(
+ url::Origin::Create(open_web_site)));
+ open_web_host->DidGenerateCacheableMetadata(
+ blink::mojom::CodeCacheType::kJavascript, resource_url,
+ base::Time::Now(),
+ mojo_base::BigBuffer(base::byte_span_from_cstring("hi")));
+
+ // Create a sandboxed iframe's cache and make sure the resource is
+ // absent. It should NOT receive the contents of the HTML's V8 cache!
+ auto sandboxed_host = CodeCacheHostImpl::Create(
+ kSandboxedProcessId, generated_code_cache_context_,
+ net::NetworkIsolationKey(net::SchemefulSite{open_web_site},
+ net::SchemefulSite{open_web_site}),
+ blink::StorageKey::CreateFirstParty(
+ url::Origin::Create(open_web_site)));
+ sandboxed_host->FetchCachedCode(
+ blink::mojom::CodeCacheType::kJavascript, resource_url,
+ base::BindLambdaForTesting([&](base::Time found_response_time,
+ mojo_base::BigBuffer found_data) {
+ EXPECT_EQ(found_response_time, base::Time());
+ EXPECT_EQ(found_data.size(), 0U);
+ run_loop.Quit();
+ }));
+ }));
+
+ run_loop.Run();
+}
+
INSTANTIATE_TEST_SUITE_P(All,
CodeCacheHostImplTest,
testing::Values(true, false));
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