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 <grt@chromium.org>
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 <creis@chromium.org>
Commit-Queue: Greg Thompson <grt@chromium.org>
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