Medium chrome Logic Error 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient validation of untrusted input in Persistent Cache
DescriptionInsufficient validation of untrusted input in Persistent Cache
ComponentPersistent Cache
Bug ClassLogic Error
Tracker495783187
Fix commit6fa333753643 (chromium/src) +143/-8
CISA KEVNot listed
CreditedGoogle
Disclosed2026-05-05

Changed Functions

FunctionChangeNotes
TEST_P
content/browser/renderer_host/code_cache_host_impl_unittest.cc
modified
BindLambdaForTesting
content/browser/renderer_host/code_cache_host_impl_unittest.cc
modified

Files Changed

  • content/browser/renderer_host/code_cache_host_impl.cc
  • content/browser/renderer_host/code_cache_host_impl_unittest.cc
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.