Medium chrome Memory Corruption 🔧 Commit mapped

Overview

Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactMemory corruption in PolicyContainer
DescriptionMemory corruption in PolicyContainer
ComponentPolicyContainer
Sub-componentPolicy-container
Bug ClassMemory Corruption
Tracker531182364
Fix commit30d509307f5d (chromium/src) +112/-8
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-20

Files Changed

  • third_party/blink/web_tests/external/wpt/content-security-policy/inheritance/iframe-srcdoc-history-inheritance-expected.txt
  • third_party/blink/web_tests/external/wpt/referrer-policy/generic/inheritance/iframe-inheritance-history-about-srcdoc-expected.txt
  • content/browser/renderer_host/navigation_policy_container_builder_browsertest.cc
  • content/browser/renderer_host/navigation_policy_container_builder.cc
From 30d509307f5d83d4f33d3f173e8a17cf7868d69d Mon Sep 17 00:00:00 2001
From: Antonio Sartori <[email protected]>
Date: Mon, 21 Sep 2026 02:52:12 -0700
Subject: [PATCH] [policy-container] Don't use history policies for about:srcdoc

History navigations to about:srcdoc always reload the srcdoc content
from the iframe srcdoc attribute, which is set from the parent. For
consistency, the policy container policies should also always be
recomputed from the parent, and not be loaded from history.

Bug: 531182364
Change-Id: Ic2568fbfe55c8aca88b9b265bd264a56b32a209f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8377282
Reviewed-by: Charlie Reis <[email protected]>
Commit-Queue: Antonio Sartori <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1701633}
---
diff --git a/content/browser/renderer_host/navigation_policy_container_builder.cc b/content/browser/renderer_host/navigation_policy_container_builder.cc
index 06658a1..fed3345 100644
--- a/content/browser/renderer_host/navigation_policy_container_builder.cc
+++ b/content/browser/renderer_host/navigation_policy_container_builder.cc
@@ -292,13 +292,20 @@
   const GURL& url = navigation_handle->GetURL();
   if (!url.SchemeIsLocal()) {
     policies = delivered_policies_.Clone();
-  } else if (history_policies_) {
+  } else if (history_policies_ && !url.IsAboutSrcdoc()) {
     // For a local scheme, history policies should not incorporate delivered
     // ones as this may lead to duplication of some policies already stored in
     // history. For example, consider the following HTML:
     //    <iframe src="about:blank" csp="something">
     // This will store CSP: something in history. The next time we have a
     // history navigation we will have CSP: something twice.
+    //
+    // `about:srcdoc` is an exception here: for `about:srcdoc` we ignore history
+    // policies because its document body is loaded from the iframe element's
+    // current `srcdoc` attribute rather than restored from history, so its
+    // policies must be computed from the current state of the parent document
+    // and the iframe element's `csp` attribute (contained in
+    // `delivered_policies_`) as well.
     policies = history_policies_->Clone();
   } else {
     policies = ComputeInheritedPolicies(url, initiator_policies);
diff --git a/content/browser/renderer_host/navigation_policy_container_builder_browsertest.cc b/content/browser/renderer_host/navigation_policy_container_builder_browsertest.cc
index 27814c7..bc8d387 100644
--- a/content/browser/renderer_host/navigation_policy_container_builder_browsertest.cc
+++ b/content/browser/renderer_host/navigation_policy_container_builder_browsertest.cc
@@ -21,6 +21,7 @@
 #include "services/network/public/cpp/ip_address_space_overrides_test_utils.h"
 #include "services/network/public/cpp/network_switches.h"
 #include "services/network/public/mojom/content_security_policy.mojom.h"
+#include "services/network/public/mojom/web_sandbox_flags.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/url_constants.h"
@@ -252,8 +253,12 @@
 }
 
 // Verifies that when the URL of the document to commit is `about:srcdoc`, and
-// when a navigation entry with policies is given, then the parent's policies
-// are ignored in favor of the policies from the entry.
+// when a navigation entry with policies is given, then the entry's policies
+// are ignored in favor of the parent's policies with delivered policies merged
+// in. The document body for `about:srcdoc` is loaded from the iframe element's
+// current `srcdoc` attribute rather than restored from history, so the
+// policies must reflect the current state of the parent and the iframe's `csp`
+// attribute rather than the historical state.
 IN_PROC_BROWSER_TEST_F(NavigationPolicyContainerBuilderBrowserTest,
                        FinalPoliciesAboutSrcDocWithParentAndHistory) {
   // First navigate to a local scheme with non-default policies. To do that, we
@@ -282,12 +287,13 @@
 
   EXPECT_NE(*builder.HistoryPolicies(), *builder.ParentPolicies());
 
-  PolicyContainerPolicies history_policies = builder.HistoryPolicies()->Clone();
+  PolicyContainerPolicies expected_policies = builder.ParentPolicies()->Clone();
 
   // Deliver a Content Security Policy via `AddContentSecurityPolicy`. This
-  // policy should not be incorporated in the final policies, since the builder
-  // is using the history policies.
-  builder.AddContentSecurityPolicy(MakeTestCSP());
+  // policy should be incorporated in the final policies together with the
+  // parent's policies, ignoring the history policies.
+  network::mojom::ContentSecurityPolicyPtr test_csp = MakeTestCSP();
+  builder.AddContentSecurityPolicy(test_csp.Clone());
 
   MockNavigationHandle navigation_handle(AboutSrcdocUrl(), nullptr);
   builder.ComputePolicies(&navigation_handle, /*initiator_policies=*/nullptr,
@@ -295,7 +301,8 @@
                           /*is_credentialless=*/false,
                           /*is_secure_context_root=*/false);
 
-  EXPECT_EQ(builder.FinalPolicies(), history_policies);
+  expected_policies.content_security_policies.push_back(std::move(test_csp));
+  EXPECT_EQ(builder.FinalPolicies(), expected_policies);
 }
 
 // Verifies that history policies are ignored in the case of error pages.
@@ -519,4 +526,84 @@
   EXPECT_EQ(builder.FinalPolicies(), initiator_policies);
 }
 
+// Verifies that when a subframe is navigated back to an `about:srcdoc` history
+// entry, the policies applied to the resulting document reflect the current
+// `csp` attribute of the iframe element. The document body loaded on such a
+// navigation is taken from the current `srcdoc` attribute rather than restored
+// from history, so the policies must correspond to the current attributes as
+// well.
+IN_PROC_BROWSER_TEST_F(NavigationPolicyContainerBuilderBrowserTest,
+                       SrcdocHistoryNavigationAppliesCurrentCspAttribute) {
+  EXPECT_TRUE(NavigateToURL(shell()->web_contents(), LoopbackUrl()));
+  NavigationController& controller = shell()->web_contents()->GetController();
+
+  // Create a srcdoc iframe with no `csp` attribute.
+  {
+    TestNavigationObserver observer(shell()->web_contents());
+    EXPECT_TRUE(ExecJs(root_frame_host(), R"(
+      const iframe = document.createElement('iframe');
+      iframe.id = 'child';
+      iframe.srcdoc = '<p>initial</p>';
+      document.body.appendChild(iframe);
+    )"));
+    observer.Wait();
+  }
+
+  ASSERT_EQ(1u, root_frame_host()->child_count());
+  FrameTreeNode* child = root_frame_host()->child_at(0);
+  EXPECT_TRUE(child->current_url().IsAboutSrcdoc());
+  EXPECT_FALSE(child->current_frame_host()->GetLastCommittedOrigin().opaque());
+  EXPECT_EQ(network::mojom::WebSandboxFlags::kNone,
+            GetPolicies(child->current_frame_host()).sandbox_flags);
+  ASSERT_EQ(1, controller.GetEntryCount());
+
+  // Navigate the child away so that a distinct `about:srcdoc` history entry
+  // remains in session history for it to return to later.
+  {
+    TestNavigationObserver observer(shell()->web_contents());
+    EXPECT_TRUE(ExecJs(child->current_frame_host(),
+                       JsReplace("location.href = $1;", LoopbackUrl())));
+    observer.Wait();
+  }
+  ASSERT_EQ(2, controller.GetEntryCount());
+
+  // Add a `csp` attribute containing a `sandbox` directive to the iframe
+  // element and update its `srcdoc`. The resulting navigation applies the
+  // required CSP and commits with an opaque origin.
+  {
+    TestNavigationObserver observer(shell()->web_contents());
+    EXPECT_TRUE(ExecJs(root_frame_host(), R"(
+      const iframe = document.getElementById('child');
+      iframe.setAttribute('csp', 'sandbox allow-scripts');
+      iframe.srcdoc = '<p>updated</p>';
+    )"));
+    observer.Wait();
+  }
+
+  ASSERT_TRUE(child->csp_attribute());
+  EXPECT_TRUE(child->current_url().IsAboutSrcdoc());
+  EXPECT_TRUE(child->current_frame_host()->GetLastCommittedOrigin().opaque());
+  EXPECT_NE(network::mojom::WebSandboxFlags::kNone,
+            GetPolicies(child->current_frame_host()).sandbox_flags &
+                network::mojom::WebSandboxFlags::kOrigin);
+  ASSERT_EQ(3, controller.GetEntryCount());
+
+  // Navigate the child back to its earlier `about:srcdoc` history entry.
+  ASSERT_TRUE(HistoryGoToIndex(shell()->web_contents(), 0));
+
+  // The child loads the current `srcdoc` value, not the value that was set
+  // when the target history entry was originally committed.
+  child = root_frame_host()->child_at(0);
+  EXPECT_TRUE(child->current_url().IsAboutSrcdoc());
+  EXPECT_EQ("updated",
+            EvalJs(child->current_frame_host(), "document.body.textContent"));
+
+  // The child must therefore commit with the required CSP taken from the
+  // current `csp` attribute, resulting in an opaque origin.
+  EXPECT_TRUE(child->current_frame_host()->GetLastCommittedOrigin().opaque());
+  EXPECT_NE(network::mojom::WebSandboxFlags::kNone,
+            GetPolicies(child->current_frame_host()).sandbox_flags &
+                network::mojom::WebSandboxFlags::kOrigin);
+}
+
 }  // namespace content
diff --git a/third_party/blink/web_tests/external/wpt/content-security-policy/inheritance/iframe-srcdoc-history-inheritance-expected.txt b/third_party/blink/web_tests/external/wpt/content-security-policy/inheritance/iframe-srcdoc-history-inheritance-expected.txt
new file mode 100644
index 0000000..11edccaa
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/content-security-policy/inheritance/iframe-srcdoc-history-inheritance-expected.txt
@@ -0,0 +1,5 @@
+This is a testharness.js-based test.
+[FAIL] iframe-srcdoc-history-inheritance
+  promise_test: Unhandled rejection with value: object "Error: The srcdoc iframe's img has been blocked by the new CSP. It means it was different and wasn't restored from history"
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/referrer-policy/generic/inheritance/iframe-inheritance-history-about-srcdoc-expected.txt b/third_party/blink/web_tests/external/wpt/referrer-policy/generic/inheritance/iframe-inheritance-history-about-srcdoc-expected.txt
new file mode 100644
index 0000000..d3be41c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/referrer-policy/generic/inheritance/iframe-inheritance-history-about-srcdoc-expected.txt
@@ -0,0 +1,5 @@
+This is a testharness.js-based test.
+[FAIL] History navigation reuses original policy.
+  assert_equals: History navigation reuses original policy. expected (undefined) undefined but got (string) "http://web-platform.test:8001/custom"
+Harness: the test ran to completion.
+
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/content/browser/renderer_host/navigation_policy_container_builder_browsertest.cc b/content/browser/renderer_host/navigation_policy_container_builder_browsertest.cc
index 27814c7..bc8d387 100644
--- a/content/browser/renderer_host/navigation_policy_container_builder_browsertest.cc
+++ b/content/browser/renderer_host/navigation_policy_container_builder_browsertest.cc
@@ -21,6 +21,7 @@
 #include "services/network/public/cpp/ip_address_space_overrides_test_utils.h"
 #include "services/network/public/cpp/network_switches.h"
 #include "services/network/public/mojom/content_security_policy.mojom.h"
+#include "services/network/public/mojom/web_sandbox_flags.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/url_constants.h"
@@ -252,8 +253,12 @@
 }
 
 // Verifies that when the URL of the document to commit is `about:srcdoc`, and
-// when a navigation entry with policies is given, then the parent's policies
-// are ignored in favor of the policies from the entry.
+// when a navigation entry with policies is given, then the entry's policies
+// are ignored in favor of the parent's policies with delivered policies merged
+// in. The document body for `about:srcdoc` is loaded from the iframe element's
+// current `srcdoc` attribute rather than restored from history, so the
+// policies must reflect the current state of the parent and the iframe's `csp`
+// attribute rather than the historical state.
 IN_PROC_BROWSER_TEST_F(NavigationPolicyContainerBuilderBrowserTest,
                        FinalPoliciesAboutSrcDocWithParentAndHistory) {
   // First navigate to a local scheme with non-default policies. To do that, we
@@ -282,12 +287,13 @@
 
   EXPECT_NE(*builder.HistoryPolicies(), *builder.ParentPolicies());
 
-  PolicyContainerPolicies history_policies = builder.HistoryPolicies()->Clone();
+  PolicyContainerPolicies expected_policies = builder.ParentPolicies()->Clone();
 
   // Deliver a Content Security Policy via `AddContentSecurityPolicy`. This
-  // policy should not be incorporated in the final policies, since the builder
-  // is using the history policies.
-  builder.AddContentSecurityPolicy(MakeTestCSP());
+  // policy should be incorporated in the final policies together with the
+  // parent's policies, ignoring the history policies.
+  network::mojom::ContentSecurityPolicyPtr test_csp = MakeTestCSP();
+  builder.AddContentSecurityPolicy(test_csp.Clone());
 
   MockNavigationHandle navigation_handle(AboutSrcdocUrl(), nullptr);
   builder.ComputePolicies(&navigation_handle, /*initiator_policies=*/nullptr,
@@ -295,7 +301,8 @@
                           /*is_credentialless=*/false,
                           /*is_secure_context_root=*/false);
 
-  EXPECT_EQ(builder.FinalPolicies(), history_policies);
+  expected_policies.content_security_policies.push_back(std::move(test_csp));
+  EXPECT_EQ(builder.FinalPolicies(), expected_policies);
 }
 
 // Verifies that history policies are ignored in the case of error pages.
@@ -519,4 +526,84 @@
   EXPECT_EQ(builder.FinalPolicies(), initiator_policies);
 }
 
+// Verifies that when a subframe is navigated back to an `about:srcdoc` history
+// entry, the policies applied to the resulting document reflect the current
+// `csp` attribute of the iframe element. The document body loaded on such a
+// navigation is taken from the current `srcdoc` attribute rather than restored
+// from history, so the policies must correspond to the current attributes as
+// well.
+IN_PROC_BROWSER_TEST_F(NavigationPolicyContainerBuilderBrowserTest,
+                       SrcdocHistoryNavigationAppliesCurrentCspAttribute) {
+  EXPECT_TRUE(NavigateToURL(shell()->web_contents(), LoopbackUrl()));
+  NavigationController& controller = shell()->web_contents()->GetController();
+
+  // Create a srcdoc iframe with no `csp` attribute.
+  {
+    TestNavigationObserver observer(shell()->web_contents());
+    EXPECT_TRUE(ExecJs(root_frame_host(), R"(
+      const iframe = document.createElement('iframe');
+      iframe.id = 'child';
+      iframe.srcdoc = '<p>initial</p>';
+      document.body.appendChild(iframe);
+    )"));
+    observer.Wait();
+  }
+
+  ASSERT_EQ(1u, root_frame_host()->child_count());
+  FrameTreeNode* child = root_frame_host()->child_at(0);
+  EXPECT_TRUE(child->current_url().IsAboutSrcdoc());
+  EXPECT_FALSE(child->current_frame_host()->GetLastCommittedOrigin().opaque());
+  EXPECT_EQ(network::mojom::WebSandboxFlags::kNone,
+            GetPolicies(child->current_frame_host()).sandbox_flags);
+  ASSERT_EQ(1, controller.GetEntryCount());
+
+  // Navigate the child away so that a distinct `about:srcdoc` history entry
+  // remains in session history for it to return to later.
+  {
+    TestNavigationObserver observer(shell()->web_contents());
+    EXPECT_TRUE(ExecJs(child->current_frame_host(),
+                       JsReplace("location.href = $1;", LoopbackUrl())));
+    observer.Wait();
+  }
+  ASSERT_EQ(2, controller.GetEntryCount());
+
+  // Add a `csp` attribute containing a `sandbox` directive to the iframe
+  // element and update its `srcdoc`. The resulting navigation applies the
+  // required CSP and commits with an opaque origin.
+  {
+    TestNavigationObserver observer(shell()->web_contents());
+    EXPECT_TRUE(ExecJs(root_frame_host(), R"(
+      const iframe = document.getElementById('child');
+      iframe.setAttribute('csp', 'sandbox allow-scripts');
+      iframe.srcdoc = '<p>updated</p>';
+    )"));
+    observer.Wait();
+  }
+
+  ASSERT_TRUE(child->csp_attribute());
+  EXPECT_TRUE(child->current_url().IsAboutSrcdoc());
+  EXPECT_TRUE(child->current_frame_host()->GetLastCommittedOrigin().opaque());
+  EXPECT_NE(network::mojom::WebSandboxFlags::kNone,
+            GetPolicies(child->current_frame_host()).sandbox_flags &
+                network::mojom::WebSandboxFlags::kOrigin);
+  ASSERT_EQ(3, controller.GetEntryCount());
+
+  // Navigate the child back to its earlier `about:srcdoc` history entry.
+  ASSERT_TRUE(HistoryGoToIndex(shell()->web_contents(), 0));
+
+  // The child loads the current `srcdoc` value, not the value that was set
+  // when the target history entry was originally committed.
+  child = root_frame_host()->child_at(0);
+  EXPECT_TRUE(child->current_url().IsAboutSrcdoc());
+  EXPECT_EQ("updated",
+            EvalJs(child->current_frame_host(), "document.body.textContent"));
+
+  // The child must therefore commit with the required CSP taken from the
+  // current `csp` attribute, resulting in an opaque origin.
+  EXPECT_TRUE(child->current_frame_host()->GetLastCommittedOrigin().opaque());
+  EXPECT_NE(network::mojom::WebSandboxFlags::kNone,
+            GetPolicies(child->current_frame_host()).sandbox_flags &
+                network::mojom::WebSandboxFlags::kOrigin);
+}
+
 }  // namespace content
diff --git a/third_party/blink/web_tests/external/wpt/content-security-policy/inheritance/iframe-srcdoc-history-inheritance-expected.txt b/third_party/blink/web_tests/external/wpt/content-security-policy/inheritance/iframe-srcdoc-history-inheritance-expected.txt
new file mode 100644
index 0000000..11edccaa
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/content-security-policy/inheritance/iframe-srcdoc-history-inheritance-expected.txt
@@ -0,0 +1,5 @@
+This is a testharness.js-based test.
+[FAIL] iframe-srcdoc-history-inheritance
+  promise_test: Unhandled rejection with value: object "Error: The srcdoc iframe's img has been blocked by the new CSP. It means it was different and wasn't restored from history"
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/referrer-policy/generic/inheritance/iframe-inheritance-history-about-srcdoc-expected.txt b/third_party/blink/web_tests/external/wpt/referrer-policy/generic/inheritance/iframe-inheritance-history-about-srcdoc-expected.txt
new file mode 100644
index 0000000..d3be41c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/referrer-policy/generic/inheritance/iframe-inheritance-history-about-srcdoc-expected.txt
@@ -0,0 +1,5 @@
+This is a testharness.js-based test.
+[FAIL] History navigation reuses original policy.
+  assert_equals: History navigation reuses original policy. expected (undefined) undefined but got (string) "http://web-platform.test:8001/custom"
+Harness: the test ran to completion.
+
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.