Chrome · Navigation
CVE-2026-17988
Logic Error in Navigation
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcontent/browser/renderer_host/render_frame_host_impl.cc |
modified |
Files Changed
content/browser/back_forward_cache_browsertest.cccontent/browser/renderer_host/render_frame_host_impl.cccontent/browser/renderer_host/render_frame_host_impl.h
Patch
From 02caf58c9b9d3e71e45df3c8a3db9642ea7ced62 Mon Sep 17 00:00:00 2001
From: Tzarial <zork@google.com>
Date: Fri, 26 Jun 2026 10:16:34 -0700
Subject: [PATCH] [agy][content] Ignore history user activation from inactive RFHs.
DidConsumeHistoryUserActivation IPC can be sent by a compromised
renderer on behalf of a subframe RenderFrameHost while its page is in
the BackForwardCache. Since its FrameTreeNode's frame_tree_ still
references the primary FrameTree, walking that tree would consume
the history user activation state on an unrelated active page.
This CL adds a check to ensure we only process this IPC when the
RenderFrameHost is in kActive state, matching the security pattern of
UpdateUserActivationState.
Fixed: 520005624
Test: RenderFrameHostImplTest.DidConsumeHistoryUserActivationNonActive
Change-Id: Ide3168f3e6bb0c285e6f1a0fc90b8076f605a2af
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7989439
Commit-Queue: Tzarial <zork@chromium.org>
Reviewed-by: Arthur Sonzogni <arthursonzogni@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1653252}
---
diff --git a/content/browser/back_forward_cache_browsertest.cc b/content/browser/back_forward_cache_browsertest.cc
index 21dd284..8b84b1f2 100644
--- a/content/browser/back_forward_cache_browsertest.cc
+++ b/content/browser/back_forward_cache_browsertest.cc
@@ -3691,6 +3691,46 @@
ExpectRestored(FROM_HERE);
}
+// Regression test for https://crbug.com/520005624.
+// Ensures that receiving a DidConsumeHistoryUserActivation IPC from a subframe
+// that is in the BackForwardCache is ignored and does not clear history user
+// activation of the active page.
+IN_PROC_BROWSER_TEST_F(
+ BackForwardCacheBrowserTest,
+ DidConsumeHistoryUserActivationFromCachedSubframeIsIgnored) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ GURL url_ab(embedded_test_server()->GetURL(
+ "a.com", "/cross_site_iframe_factory.html?a(b)"));
+ GURL url_c(embedded_test_server()->GetURL("c.com", "/title1.html"));
+
+ // 1) Navigate to A(B).
+ ASSERT_TRUE(NavigateToURL(shell(), url_ab));
+ RenderFrameHostImplWrapper rfh_a(current_frame_host());
+ RenderFrameHostImplWrapper rfh_b(rfh_a->child_at(0)->current_frame_host());
+
+ // 2) Navigate to C.
+ ASSERT_TRUE(NavigateToURL(shell(), url_c));
+ RenderFrameHostImplWrapper rfh_c(current_frame_host());
+
+ // Verify A(B) is in the BackForwardCache.
+ ASSERT_TRUE(rfh_a->IsInBackForwardCache());
+ ASSERT_TRUE(rfh_b->IsInBackForwardCache());
+
+ // 3) Set history user activation active on the active page C.
+ rfh_c->UpdateUserActivationState(
+ blink::mojom::UserActivationUpdateType::kNotifyActivation,
+ blink::mojom::UserActivationNotificationType::kInteraction);
+ EXPECT_TRUE(rfh_c->IsHistoryUserActivationActive());
+
+ // 4) Simulate receiving DidConsumeHistoryUserActivation from B (which is in
+ // BFCache).
+ rfh_b->DidConsumeHistoryUserActivation();
+
+ // 5) The history user activation on active page C must NOT be consumed.
+ EXPECT_TRUE(rfh_c->IsHistoryUserActivationActive());
+}
+
// BEFORE ADDING A NEW TEST HERE
// Read the note at the top about the other files you could add it to.
} // namespace content
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index 860abec..6e0b31c 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -9632,12 +9632,15 @@
}
void RenderFrameHostImpl::DidConsumeHistoryUserActivation() {
- // owner_ may be null for IsPendingDeletion() or IsInBackForwardCache(), in
- // which case the history user activation is managed by a different active
- // RenderFrameHost.
- if (owner_) {
- owner_->DidConsumeHistoryUserActivation();
+ // This IPC is only sent from an active document, but might be received after
+ // the transition to the backforward cache or after entering pending
+ // deletion. It must be ignored as it would affect the state of the primary
+ // page instead of the old one.
+ if (lifecycle_state() != LifecycleStateImpl::kActive) {
+ return;
}
+ CHECK(owner_); // See `owner_` invariants about `lifecycle_state_`.
+ owner_->DidConsumeHistoryUserActivation();
}
void RenderFrameHostImpl::HadStickyUserActivationBeforeNavigationChanged(
diff --git a/content/browser/renderer_host/render_frame_host_impl.h b/content/browser/renderer_host/render_frame_host_impl.h
index bd8c4967..2d526d8 100644
--- a/content/browser/renderer_host/render_frame_host_impl.h
+++ b/content/browser/renderer_host/render_frame_host_impl.h
@@ -3544,6 +3544,8 @@
FRIEND_TEST_ALL_PREFIXES(
RenderFrameHostImplTest,
BrowserInitiatedCloseIsNotCancelledIfPageIsntPrimary);
+ FRIEND_TEST_ALL_PREFIXES(RenderFrameHostImplTest,
+ DidConsumeHistoryUserActivationNonActive);
FRIEND_TEST_ALL_PREFIXES(DocumentUserDataTest, CheckInPendingDeletionState);
FRIEND_TEST_ALL_PREFIXES(WebContentsImplBrowserTest, FrozenAndUnfrozenIPC);
FRIEND_TEST_ALL_PREFIXES(RenderFrameHostImplBrowserTest,
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/content/browser/back_forward_cache_browsertest.cc b/content/browser/back_forward_cache_browsertest.cc
index 21dd284..8b84b1f2 100644
--- a/content/browser/back_forward_cache_browsertest.cc
+++ b/content/browser/back_forward_cache_browsertest.cc
@@ -3691,6 +3691,46 @@
ExpectRestored(FROM_HERE);
}
+// Regression test for https://crbug.com/520005624.
+// Ensures that receiving a DidConsumeHistoryUserActivation IPC from a subframe
+// that is in the BackForwardCache is ignored and does not clear history user
+// activation of the active page.
+IN_PROC_BROWSER_TEST_F(
+ BackForwardCacheBrowserTest,
+ DidConsumeHistoryUserActivationFromCachedSubframeIsIgnored) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ GURL url_ab(embedded_test_server()->GetURL(
+ "a.com", "/cross_site_iframe_factory.html?a(b)"));
+ GURL url_c(embedded_test_server()->GetURL("c.com", "/title1.html"));
+
+ // 1) Navigate to A(B).
+ ASSERT_TRUE(NavigateToURL(shell(), url_ab));
+ RenderFrameHostImplWrapper rfh_a(current_frame_host());
+ RenderFrameHostImplWrapper rfh_b(rfh_a->child_at(0)->current_frame_host());
+
+ // 2) Navigate to C.
+ ASSERT_TRUE(NavigateToURL(shell(), url_c));
+ RenderFrameHostImplWrapper rfh_c(current_frame_host());
+
+ // Verify A(B) is in the BackForwardCache.
+ ASSERT_TRUE(rfh_a->IsInBackForwardCache());
+ ASSERT_TRUE(rfh_b->IsInBackForwardCache());
+
+ // 3) Set history user activation active on the active page C.
+ rfh_c->UpdateUserActivationState(
+ blink::mojom::UserActivationUpdateType::kNotifyActivation,
+ blink::mojom::UserActivationNotificationType::kInteraction);
+ EXPECT_TRUE(rfh_c->IsHistoryUserActivationActive());
+
+ // 4) Simulate receiving DidConsumeHistoryUserActivation from B (which is in
+ // BFCache).
+ rfh_b->DidConsumeHistoryUserActivation();
+
+ // 5) The history user activation on active page C must NOT be consumed.
+ EXPECT_TRUE(rfh_c->IsHistoryUserActivationActive());
+}
+
// BEFORE ADDING A NEW TEST HERE
// Read the note at the top about the other files you could add it to.
} // namespace content
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