Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactMissing authorization in SiteIsolation
DescriptionMissing authorization in SiteIsolation
ComponentSiteIsolation
Bug ClassLogic Error
Tracker516665605
Fix commit03b18568967f (chromium/src) +53/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-08-25

Changed Functions

FunctionChangeNotes
IN_PROC_BROWSER_TEST_F
content/browser/renderer_host/cookie_browsertest.cc
modified

Files Changed

  • content/browser/renderer_host/cookie_browsertest.cc
  • content/browser/renderer_host/render_frame_host_impl.cc
From 03b18568967f29eb7d198a513c9be98291a5ce6c Mon Sep 17 00:00:00 2001
From: Alex Moshchuk <[email protected]>
Date: Wed, 22 Jul 2026 13:23:28 -0700
Subject: [PATCH] Block PDF processes from binding RestrictedCookieManager

RenderFrameHostImpl::SendCommitNavigation() already skips the eager
RestrictedCookieManager bind [1] for PDF processes, since PDF renderers
are isolated from cookies and storage for the origin that served the
PDF. The BrowserInterfaceBroker fallback path,
RenderFrameHostImpl::BindRestrictedCookieManager(), did not have the
same gate, so a frame committed in a PDF-isolated process could still
obtain a RestrictedCookieManager for its committed origin via the
broker.

Add a CanAccessDataForOrigin check to BindRestrictedCookieManager() so
that we do not grant the cookie manager interface to PDF renderer
processes, and also to sandboxed frame processes as a bonus. There's
intentionally no renderer kill added, because the renderer doesn't
prevent a sandboxed frame process from requesting this interface via
cookieStore.get(). (The renderer does have subsequent security checks
[2] to deny access to cookies from opaque origins before it ever tries
to use that interface.)

Add a (mostly Gemini-written) content_browsertest that commits a page
with LoadURLParams::is_pdf set and verifies that document.cookie can
neither read nor write cookies for the committed origin. The new test
fails without the fix applied.

[1]
https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/render_frame_host_impl.cc;l=17120;drc=b251030f22d6810f6755262d2182316f60552d60

[2]
https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/modules/cookie_store/cookie_store.cc;l=506;drc=bd8f7f70a1197b77f4bb0a886e2778f707e333e1

Bug: 516665605
Change-Id: Id5f13ce3f10cec726c5736daffcadcfbdf67001f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8127316
Reviewed-by: Charlie Reis <[email protected]>
Commit-Queue: Alex Moshchuk <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1666575}
---

diff --git a/content/browser/renderer_host/cookie_browsertest.cc b/content/browser/renderer_host/cookie_browsertest.cc
index 015c8a1d..708a2836 100644
--- a/content/browser/renderer_host/cookie_browsertest.cc
+++ b/content/browser/renderer_host/cookie_browsertest.cc
@@ -22,6 +22,7 @@
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
+#include "content/public/browser/navigation_controller.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/security_principal.h"
 #include "content/public/browser/site_isolation_policy.h"
@@ -832,6 +833,40 @@
       v.DepictFrameTree(tab->GetPrimaryFrameTree().root()));
 }
 
+// Verifies that a frame committed in a PDF-isolated process cannot bind a
+// RestrictedCookieManager for the committed origin. The SendCommitNavigation
+// path already skips the bind for PDF processes, so this exercises the
+// BrowserInterfaceBroker fallback used when no cookie manager was supplied at
+// commit time.
+IN_PROC_BROWSER_TEST_F(CookieBrowserTest, CookiesBlockedForPdfProcess) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+
+  WebContentsImpl* tab = static_cast<WebContentsImpl*>(shell()->web_contents());
+  GURL url = embedded_test_server()->GetURL("a.test", "/empty.html");
+
+  SetCookieDirect(tab, url, "A=1");
+  ASSERT_EQ("A=1", GetCookiesDirect(tab, url));
+
+  // Commit `url` as PDF content so that the resulting frame runs in a process
+  // whose SiteInfo has `is_pdf` set.
+  NavigationController::LoadURLParams params(url);
+  params.transition_type = ui::PageTransitionFromInt(
+      ui::PAGE_TRANSITION_TYPED | ui::PAGE_TRANSITION_FROM_ADDRESS_BAR);
+  params.is_pdf = true;
+  NavigateToURLBlockUntilNavigationsComplete(
+      tab, params, 1, /*ignore_uncommitted_navigations=*/false);
+  ASSERT_TRUE(IsLastCommittedEntryOfPageType(tab, PAGE_TYPE_NORMAL));
+  ASSERT_EQ(url, tab->GetLastCommittedURL());
+
+  RenderFrameHost* frame = tab->GetPrimaryMainFrame();
+
+  // The PDF process must not be able to read or write cookies for the committed
+  // origin.
+  EXPECT_EQ("", GetCookieFromJS(frame));
+  std::ignore = EvalJs(frame, "document.cookie = 'B=2'");
+  EXPECT_EQ("A=1", GetCookiesDirect(tab, url));
+}
+
 IN_PROC_BROWSER_TEST_F(CookieBrowserTest, CookieNotReadableAfterExpiry) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index f3e0c1f..ca28b2c3 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -15058,6 +15058,24 @@
 
 void RenderFrameHostImpl::BindRestrictedCookieManager(
     mojo::PendingReceiver<network::mojom::RestrictedCookieManager> receiver) {
+  // Check whether the current frame is permitted to access cookies. For
+  // example, this will avoid binding the interface for PDF or sandboxed frame
+  // processes that should never need to access cookies.
+  auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
+  if (!policy->CanAccessDataForOrigin(GetProcess()->GetID().GetUnsafeValue(),
+                                      GetLastCommittedOrigin())) {
+    // Note that there's intentionally no renderer kill here because the
+    // renderer doesn't prevent code in a sandboxed frame from requesting this
+    // interface via cookieStore.get() (despite such a frame having an opaque
+    // origin and no access to any cookies). The renderer does have subsequent
+    // security checks to deny access to cookies from opaque origins before it
+    // ever tries to use that interface, though. If a compromised renderer skips
+    // those checks and attempts to send IPCs to read/write cookies, those IPCs
+    // would fail if this interface isn't bound; writes would therefore be
+    // no-ops and reads would behave as if there are no cookies.
+    return;
+  }
+
   BindRestrictedCookieManagerWithOrigin(
       std::move(receiver), GetIsolationInfoForSubresources(),
       GetLastCommittedOrigin(), GetCookieSettingOverrides());
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/content/browser/renderer_host/cookie_browsertest.cc b/content/browser/renderer_host/cookie_browsertest.cc
index 015c8a1d..708a2836 100644
--- a/content/browser/renderer_host/cookie_browsertest.cc
+++ b/content/browser/renderer_host/cookie_browsertest.cc
@@ -22,6 +22,7 @@
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
+#include "content/public/browser/navigation_controller.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/security_principal.h"
 #include "content/public/browser/site_isolation_policy.h"
@@ -832,6 +833,40 @@
       v.DepictFrameTree(tab->GetPrimaryFrameTree().root()));
 }
 
+// Verifies that a frame committed in a PDF-isolated process cannot bind a
+// RestrictedCookieManager for the committed origin. The SendCommitNavigation
+// path already skips the bind for PDF processes, so this exercises the
+// BrowserInterfaceBroker fallback used when no cookie manager was supplied at
+// commit time.
+IN_PROC_BROWSER_TEST_F(CookieBrowserTest, CookiesBlockedForPdfProcess) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+
+  WebContentsImpl* tab = static_cast<WebContentsImpl*>(shell()->web_contents());
+  GURL url = embedded_test_server()->GetURL("a.test", "/empty.html");
+
+  SetCookieDirect(tab, url, "A=1");
+  ASSERT_EQ("A=1", GetCookiesDirect(tab, url));
+
+  // Commit `url` as PDF content so that the resulting frame runs in a process
+  // whose SiteInfo has `is_pdf` set.
+  NavigationController::LoadURLParams params(url);
+  params.transition_type = ui::PageTransitionFromInt(
+      ui::PAGE_TRANSITION_TYPED | ui::PAGE_TRANSITION_FROM_ADDRESS_BAR);
+  params.is_pdf = true;
+  NavigateToURLBlockUntilNavigationsComplete(
+      tab, params, 1, /*ignore_uncommitted_navigations=*/false);
+  ASSERT_TRUE(IsLastCommittedEntryOfPageType(tab, PAGE_TYPE_NORMAL));
+  ASSERT_EQ(url, tab->GetLastCommittedURL());
+
+  RenderFrameHost* frame = tab->GetPrimaryMainFrame();
+
+  // The PDF process must not be able to read or write cookies for the committed
+  // origin.
+  EXPECT_EQ("", GetCookieFromJS(frame));
+  std::ignore = EvalJs(frame, "document.cookie = 'B=2'");
+  EXPECT_EQ("A=1", GetCookiesDirect(tab, url));
+}
+
 IN_PROC_BROWSER_TEST_F(CookieBrowserTest, CookieNotReadableAfterExpiry) {
   ASSERT_TRUE(embedded_test_server()->Start());
Loading diff…

Original Bug Report

reported by [email protected]

PDF process isolation bypass via lazy RestrictedCookieManager binding

Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: A compromised PDF renderer process can bypass storage access controls by lazy-binding a RestrictedCookieManager interface. Although cookie/storage access is blocked for PDF processes on the eager navigation commit path, the lazy path via BrowserInterfaceBroker lacks any PDF-specific verification. This allows a compromised PDF renderer to retrieve and modify non-HttpOnly cookies for the hosting origin.

Affected files:

  • content/browser/renderer_host/render_frame_host_impl.cc
  • content/browser/browser_interface_binders.cc

Estimated timestamp from git blame: 2024-06-26

Root Cause Analysis

In Chromium, PDF documents are isolated into specialized, sandboxed renderer processes. These processes are intended to operate under a strict storage access block. ChildProcessSecurityPolicyImpl::IsAccessAllowedForPdfProcess explicitly returns false for AccessType::kCanAccessDataForCommittedOrigin to prevent PDF renderers from accessing passwords, storage, or cookies for the hosting origin.

To enforce this, the eager navigation commit-time path in RenderFrameHostImpl::SendCommitNavigation checks should_block_storage_access_for_pdf (based on GetSiteInstance()->GetSiteInfo().is_pdf()) and skips binding a RestrictedCookieManager:

// content/browser/renderer_host/render_frame_host_impl.cc
bool should_block_storage_access_for_pdf =
    GetSiteInstance()->GetSiteInfo().is_pdf();
...
if (common_params->url.SchemeIsHTTPOrHTTPS() && !origin_to_commit.opaque() &&
    !should_block_storage_access_for_pdf && ...) {
  cookie_manager_info = mojom::CookieManagerInfo::New();
  ...
  BindRestrictedCookieManagerWithOrigin(...);   // <-- Skipped for PDF
}

However, the lazy binding path—used when a renderer explicitly requests the interface via the BrowserInterfaceBroker—completely lacks any equivalent check.

In content/browser/browser_interface_binders.cc, the broker is registered without any PDF-specific constraints:

map->Add<network::mojom::RestrictedCookieManager>(
    &BindRenderFrameHostImpl<
        &RenderFrameHostImpl::BindRestrictedCookieManager>);

When invoked, this routes to RenderFrameHostImpl::BindRestrictedCookieManager, which binds the manager using the frame’s last committed origin without verifying whether the process is a PDF process, and without querying ChildProcessSecurityPolicy:

void RenderFrameHostImpl::BindRestrictedCookieManager(
    mojo::PendingReceiver<network::mojom::RestrictedCookieManager> receiver) {
  BindRestrictedCookieManagerWithOrigin(
      std::move(receiver), GetIsolationInfoForSubresources(),
      GetLastCommittedOrigin(), GetCookieSettingOverrides());
}

Because the subsequent layers in StoragePartitionImpl and NetworkContext assume that the browser process has already enforced access control before requesting the RestrictedCookieManager from the out-of-process Network Service, the interface is successfully bound and returned to the renderer.

Potential Attack Scenario

(Note: These are suggested/potential steps; our tooling does not have the ability to run code or verify a live exploit)

  1. An attacker uploads a malicious PDF file onto a target origin (e.g., https://victim.example/malicious.pdf).
  2. A user navigates to the PDF. The browser spawns an isolated PDF renderer process for it.
  3. The attacker exploits a memory corruption vulnerability (such as a PDFium bug) in the sandboxed PDF renderer process to achieve arbitrary code execution within the renderer.
  4. The compromised PDF renderer requests the ’network.mojom.RestrictedCookieManager’ interface via its BrowserInterfaceBroker pipe.
  5. The browser process maps the request to RenderFrameHostImpl::BindRestrictedCookieManager and binds it using the frame’s last committed origin (https://victim.example) without any PDF validation.
  6. The compromised PDF renderer uses the resulting RestrictedCookieManager interface to read and write non-HttpOnly cookies for https://victim.example, bypassing the PDF isolation storage restrictions.

Suggested Fix

Add a check in RenderFrameHostImpl::BindRestrictedCookieManager to prevent PDF frames from lazy-binding the interface. If GetSiteInstance()->GetSiteInfo().is_pdf() is true, the request should be blocked (and ideally, a bad message should be reported to terminate the compromised renderer):

void RenderFrameHostImpl::BindRestrictedCookieManager(
    mojo::PendingReceiver<network::mojom::RestrictedCookieManager> receiver) {
  if (GetSiteInstance()->GetSiteInfo().is_pdf()) {
    mojo::ReportBadMessage('PDF processes are not allowed to access cookies.');
    return;
  }
  BindRestrictedCookieManagerWithOrigin(
      std::move(receiver), GetIsolationInfoForSubresources(),
      GetLastCommittedOrigin(), GetCookieSettingOverrides());
}

Evaluated with Chrome root at commit: a2bea94528f4bd6cc57739c43fa3bb890b8367d3


Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:

  • If you are familiar with the severity guidelines, you may adjust the severity.
  • If this is a false positive, and there’s no work to be done, please close as WAI.
  • If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.

Data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.

View on issue tracker