Medium chrome Input Validation 🔧 Commit mapped

Overview

Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactImproper input validation in Viz
DescriptionImproper input validation in Viz
ComponentViz
Bug ClassInput Validation
Tracker502462485
Fix commit23246e7a66e2 (chromium/src) +252/-7
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-06

Changed Functions

FunctionChangeNotes
if
content/browser/renderer_host/render_frame_host_impl.cc
modified
DidCommitTransitionReplacer
content/browser/security_exploit_browsertest.cc
modified
transition_to_inject_
content/browser/security_exploit_browsertest.cc
modified
if
content/browser/security_exploit_browsertest.cc
modified
SecurityExploitPageTransitionTest
content/browser/security_exploit_browsertest.cc
modified
SecurityExploitDisallowedQualifierTest
content/browser/security_exploit_browsertest.cc
modified
SecurityExploitViaDisabledWebSecurityTest
content/browser/security_exploit_browsertest.cc
modified

Files Changed

  • content/browser/bad_message.h
  • tools/metrics/histograms/metadata/stability/enums.xml
  • content/browser/renderer_host/render_frame_host_impl.cc
  • ui/base/page_transition_types.h
  • components/sync/base/page_transition_conversion.cc
  • content/browser/security_exploit_browsertest.cc
From 23246e7a66e250e1221b9488c0ff3a44d83af097 Mon Sep 17 00:00:00 2001
From: Arthur Sonzogni <arthursonzogni@chromium.org>
Date: Wed, 02 Sep 2026 06:53:35 -0700
Subject: [PATCH] Enforce PageTransition validation on commit

When receiving DidCommitProvisionalLoad or
DidCommitSameDocumentNavigation from a renderer, the browser
previously accepted params->transition without validating that its core
transition type and qualifiers matched the expected frame type,
initiation source, or browser-provided parameters.

This CL adds PageTransition validation in
RenderFrameHostImpl::ValidateDidCommitParams to:
1. Verify that the PageTransition is a valid PageTransition type.
2. Verify that main frame transitions are only committed by outermost
   main frames, and subframes/fenced frames commit subframe transitions.
3. Verify that renderer-initiated navigations commit web-triggerable
   transitions and do not forge browser-only qualifiers (e.g.
   PAGE_TRANSITION_FROM_ADDRESS_BAR).
4. Verify that browser-initiated main-frame navigations commit matching
   core transition types.

Fixed: 502462485
Change-Id: Ie99423245a87e21f51ec6934b9add346db6f6cd4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8305792
Reviewed-by: Mikel Astiz <mastiz@chromium.org>
Commit-Queue: Arthur Sonzogni <arthursonzogni@chromium.org>
Reviewed-by: Roger McFarlane <rogerm@chromium.org>
Reviewed-by: Mitsuru Oshima <oshima@chromium.org>
Reviewed-by: Dave Tapuska <dtapuska@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1690676}
---
diff --git a/components/sync/base/page_transition_conversion.cc b/components/sync/base/page_transition_conversion.cc
index e2a34ce..0118d7d 100644
--- a/components/sync/base/page_transition_conversion.cc
+++ b/components/sync/base/page_transition_conversion.cc
@@ -64,6 +64,7 @@
     case ui::PAGE_TRANSITION_SERVER_REDIRECT:
     case ui::PAGE_TRANSITION_IS_REDIRECT_MASK:
     case ui::PAGE_TRANSITION_QUALIFIER_MASK:
+    case ui::PAGE_TRANSITION_RENDERER_DISALLOWED_QUALIFIERS_MASK:
       break;
   }
   NOTREACHED();
diff --git a/content/browser/bad_message.h b/content/browser/bad_message.h
index 83a157ae..3db4f1ec 100644
--- a/content/browser/bad_message.h
+++ b/content/browser/bad_message.h
@@ -405,6 +405,11 @@
   PSI_STORAGE_ACCESS_FROM_SANDBOXED_FRAME = 377,
   HSI_PLAY_HAPTICS_BLOCKED_BY_PERMISSIONS_POLICY = 378,
   RFH_CREATE_CHILD_FRAME_INVALID_INITIATOR_TOKEN = 379,
+  RFH_COMMIT_NAVIGATION_INVALID_TRANSITION_TYPE = 380,
+  RFH_COMMIT_NAVIGATION_NON_WEBBY_TRANSITION = 381,
+  RFH_COMMIT_NAVIGATION_TRANSITION_FRAME_TYPE_MISMATCH = 382,
+  RFH_COMMIT_NAVIGATION_DISALLOWED_QUALIFIER = 383,
+  RFH_COMMIT_NAVIGATION_BROWSER_INITIATED_TRANSITION_MISMATCH = 384,
 
   // Please add new elements here. The naming convention is abbreviated class
   // name (e.g. RenderFrameHost becomes RFH) plus a unique description of the
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index 079a839..aa60250 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -16122,6 +16122,83 @@
     return false;
   }
 
+  // Validate PageTransition.
+  if (!ui::IsValidPageTransitionType(params->transition)) {
+    bad_message::ReceivedBadMessage(
+        process, bad_message::RFH_COMMIT_NAVIGATION_INVALID_TRANSITION_TYPE);
+    return false;
+  }
+
+  ui::PageTransition transition = ui::PageTransitionFromInt(params->transition);
+  bool is_main_frame = !GetParent() && !IsFencedFrameRoot();
+
+  // Main frame transitions must only be used by main frames (excluding fenced
+  // frame roots). Subframes and fenced frame roots must use subframe
+  // transitions.
+  if (ui::PageTransitionIsMainFrame(transition) != is_main_frame) {
+    bad_message::ReceivedBadMessage(
+        process,
+        bad_message::RFH_COMMIT_NAVIGATION_TRANSITION_FRAME_TYPE_MISMATCH);
+    return false;
+  }
+
+  // Fenced frame roots must only use PAGE_TRANSITION_AUTO_SUBFRAME (plus
+  // optional client redirect qualifier). Fenced frames are being removed and
+  // shouldn't be enabled by default (see crbug.com/540020472), hence a CHECK is
+  // used here instead of a bad message.
+  if (IsFencedFrameRoot()) {
+    CHECK(ui::PageTransitionCoreTypeIs(transition,
+                                       ui::PAGE_TRANSITION_AUTO_SUBFRAME));
+  }
+
+  // Ensure the renderer does not add privileged / browser-only qualifiers.
+  uint32_t request_qualifiers =
+      navigation_request
+          ? static_cast<uint32_t>(
+                navigation_request->common_params().transition &
+                ui::PAGE_TRANSITION_RENDERER_DISALLOWED_QUALIFIERS_MASK)
+          : 0;
+  uint32_t commit_qualifiers = static_cast<uint32_t>(
+      params->transition &
+      ui::PAGE_TRANSITION_RENDERER_DISALLOWED_QUALIFIERS_MASK);
+  if ((commit_qualifiers & ~request_qualifiers) != 0) {
+    bad_message::ReceivedBadMessage(
+        process, bad_message::RFH_COMMIT_NAVIGATION_DISALLOWED_QUALIFIER);
+    return false;
+  }
+
+  if (is_main_frame) {
+    if (!navigation_request) {
+      // For synchronous same-document navigations initiated by the renderer
+      // without a browser NavigationRequest, the transition must be
+      // web-triggerable.
+      if (!ui::PageTransitionIsWebTriggerable(transition)) {
+        bad_message::ReceivedBadMessage(
+            process, bad_message::RFH_COMMIT_NAVIGATION_NON_WEBBY_TRANSITION);
+        return false;
+      }
+    } else {
+      // For main-frame navigations where the browser provided an expected
+      // transition in NavigationRequest, the renderer must not change the core
+      // transition type (except possibly FORM_SUBMIT for POST requests).
+      ui::PageTransition expected_core_transition =
+          ui::PageTransitionStripQualifier(ui::PageTransitionFromInt(
+              navigation_request->common_params().transition));
+      bool core_type_matches =
+          ui::PageTransitionCoreTypeIs(transition, expected_core_transition) ||
+          (navigation_request->common_params().post_data &&
+           ui::PageTransitionCoreTypeIs(transition,
+                                        ui::PAGE_TRANSITION_FORM_SUBMIT));
+      if (!core_type_matches) {
+        bad_message::ReceivedBadMessage(
+            process,
+            bad_message::
+                RFH_COMMIT_NAVIGATION_BROWSER_INITIATED_TRANSITION_MISMATCH);
+        return false;
+      }
+    }
+  }
+
   return true;
 }
 
@@ -16387,12 +16464,6 @@
     mojom::DidCommitSameDocumentNavigationParamsPtr same_document_params,
     const base::TimeTicks& did_commit_ipc_received_time) {
   const bool is_same_document_navigation = !!same_document_params;
-  // Sanity-check the page transition for frame type. Fenced Frames
-  // will set page transition to AUTO_SUBFRAME.
-  // TODO(523085714): CHECK-exclusion: Convert to a CHECK once we are confident
-  // it won't be triggered.
-  DCHECK_EQ(ui::PageTransitionIsMainFrame(params->transition),
-            !GetParent() && !IsFencedFrameRoot());
   // TODO(https://crbug.com/445585641): Make this enforceable on Android.
   if (navigation_request &&
       navigation_request->commit_params().navigation_token !=
diff --git a/tools/metrics/histograms/metadata/stability/enums.xml b/tools/metrics/histograms/metadata/stability/enums.xml
index 509a10e..da18d59 100644
--- a/tools/metrics/histograms/metadata/stability/enums.xml
+++ b/tools/metrics/histograms/metadata/stability/enums.xml
@@ -559,6 +559,13 @@
   <int value="377" label="PSI_STORAGE_ACCESS_FROM_SANDBOXED_FRAME"/>
   <int value="378" label="HSI_PLAY_HAPTICS_BLOCKED_BY_PERMISSIONS_POLICY"/>
   <int value="379" label="RFH_CREATE_CHILD_FRAME_INVALID_INITIATOR_TOKEN"/>
+  <int value="380" label="RFH_COMMIT_NAVIGATION_INVALID_TRANSITION_TYPE"/>
+  <int value="381" label="RFH_COMMIT_NAVIGATION_NON_WEBBY_TRANSITION"/>
+  <int value="382"
+      label="RFH_COMMIT_NAVIGATION_TRANSITION_FRAME_TYPE_MISMATCH"/>
+  <int value="383" label="RFH_COMMIT_NAVIGATION_DISALLOWED_QUALIFIER"/>
+  <int value="384"
+      label="RFH_COMMIT_NAVIGATION_BROWSER_INITIATED_TRANSITION_MISMATCH"/>
 </enum>
 
 <enum name="BadMessageReasonExtensions">
diff --git a/ui/base/page_transition_types.h b/ui/base/page_transition_types.h
index ecb5790..55d39d47 100644
--- a/ui/base/page_transition_types.h
+++ b/ui/base/page_transition_types.h
@@ -155,10 +155,18 @@
   PAGE_TRANSITION_SERVER_REDIRECT = -2147483648,  // 0x80000000
 
   // Used to test whether a transition involves a redirect.
-  PAGE_TRANSITION_IS_REDIRECT_MASK = -1073741824,  // 0xC0000000
+  PAGE_TRANSITION_IS_REDIRECT_MASK =
+      PAGE_TRANSITION_CLIENT_REDIRECT | PAGE_TRANSITION_SERVER_REDIRECT,
 
   // General mask defining the bits used for the qualifiers.
   PAGE_TRANSITION_QUALIFIER_MASK = -256,  // 0xFFFFFF00
+
+  // Mask of qualifiers that the renderer is not allowed to add unless already
+  // set by the browser.
+  PAGE_TRANSITION_RENDERER_DISALLOWED_QUALIFIERS_MASK =
+      PAGE_TRANSITION_FORWARD_BACK | PAGE_TRANSITION_FROM_ADDRESS_BAR |
+      PAGE_TRANSITION_HOME_PAGE | PAGE_TRANSITION_FROM_API |
+      PAGE_TRANSITION_SERVER_REDIRECT,
 };
 
 // Compares two PageTransition types ignoring qualifiers. |rhs| is taken to
diff --git a/content/browser/security_exploit_browsertest.cc b/content/browser/security_exploit_browsertest.cc
index a14aede2..c3d76433 100644
--- a/content/browser/security_exploit_browsertest.cc
+++ b/content/browser/security_exploit_browsertest.cc
@@ -3645,6 +3645,159 @@
   }
 }
 
+class DidCommitTransitionReplacer : public DidCommitNavigationInterceptor {
+ public:
+  DidCommitTransitionReplacer(WebContents* web_contents,
+                              ui::PageTransition transition_to_inject)
+      : DidCommitNavigationInterceptor(web_contents),
+        transition_to_inject_(transition_to_inject) {}
+
+  DidCommitTransitionReplacer(const DidCommitTransitionReplacer&) = delete;
+  DidCommitTransitionReplacer& operator=(const DidCommitTransitionReplacer&) =
+      delete;
+
+  ~DidCommitTransitionReplacer() override = default;
+
+  void Activate() { is_activated_ = true; }
+
+ protected:
+  bool WillProcessDidCommitNavigation(
+      RenderFrameHost* render_frame_host,
+      NavigationRequest* navigation_request,
+      mojom::DidCommitProvisionalLoadParamsPtr* params,
+      mojom::DidCommitProvisionalLoadInterfaceParamsPtr* interface_params)
+      override {
+    if (is_activated_) {
+      (**params).transition = transition_to_inject_;
+      is_activated_ = false;
+    }
+    return true;
+  }
+
+ private:
+  ui::PageTransition transition_to_inject_;
+  bool is_activated_ = false;
+};
+
+class SecurityExploitPageTransitionTest
+    : public SecurityExploitBrowserTest,
+      public ::testing::WithParamInterface<ui::PageTransition> {};
+
+IN_PROC_BROWSER_TEST_P(SecurityExploitPageTransitionTest,
+                       DidCommitNonWebbyTransition) {
+  ui::PageTransition transition = GetParam();
+  WebContents* web_contents = shell()->web_contents();
+  DidCommitTransitionReplacer injector(web_contents, transition);
+
+  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
+  EXPECT_TRUE(NavigateToURL(web_contents, main_url));
+
+  RenderProcessHost* main_process =
+      web_contents->GetPrimaryMainFrame()->GetProcess();
+  RenderProcessHostBadIpcMessageWaiter kill_waiter(main_process);
+
+  injector.Activate();
+  ExecuteScriptAsync(web_contents,
+                     "history.pushState({}, '', '/title2.html');");
+
+  EXPECT_EQ(bad_message::RFH_COMMIT_NAVIGATION_NON_WEBBY_TRANSITION,
+            kill_waiter.Wait());
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    SecurityExploitPageTransitionTest,
+    ::testing::Values(ui::PAGE_TRANSITION_TYPED,
+                      ui::PAGE_TRANSITION_AUTO_BOOKMARK,
+                      ui::PAGE_TRANSITION_GENERATED,
+                      ui::PAGE_TRANSITION_AUTO_TOPLEVEL,
+                      ui::PAGE_TRANSITION_RELOAD,
+                      ui::PAGE_TRANSITION_KEYWORD,
+                      ui::PAGE_TRANSITION_KEYWORD_GENERATED));
+
+IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
+                       DidCommitBrowserInitiatedTransitionMismatch) {
+  WebContents* web_contents = shell()->web_contents();
+  DidCommitTransitionReplacer injector(web_contents,
+                                       ui::PAGE_TRANSITION_AUTO_BOOKMARK);
+
+  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
+  EXPECT_TRUE(NavigateToURL(web_contents, main_url));
+
+  RenderProcessHost* main_process =
+      web_contents->GetPrimaryMainFrame()->GetProcess();
+  RenderProcessHostBadIpcMessageWaiter kill_waiter(main_process);
+
+  injector.Activate();
+  ExecuteScriptAsync(web_contents, "window.location = '/title2.html';");
+
+  EXPECT_EQ(
+      bad_message::RFH_COMMIT_NAVIGATION_BROWSER_INITIATED_TRANSITION_MISMATCH,
+      kill_waiter.Wait());
+}
+
+class SecurityExploitDisallowedQualifierTest
+    : public SecurityExploitBrowserTest,
+      public ::testing::WithParamInterface<ui::PageTransition> {};
+
+IN_PROC_BROWSER_TEST_P(SecurityExploitDisallowedQualifierTest,
+                       DidCommitDisallowedQualifier) {
+  ui::PageTransition transition = GetParam();
+  WebContents* web_contents = shell()->web_contents();
+  DidCommitTransitionReplacer injector(web_contents, transition);
+
+  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
+  EXPECT_TRUE(NavigateToURL(web_contents, main_url));
+
+  RenderProcessHost* main_process =
+      web_contents->GetPrimaryMainFrame()->GetProcess();
+  RenderProcessHostBadIpcMessageWaiter kill_waiter(main_process);
+
+  injector.Activate();
+  ExecuteScriptAsync(web_contents, "window.location = '/title2.html';");
+
+  EXPECT_EQ(bad_message::RFH_COMMIT_NAVIGATION_DISALLOWED_QUALIFIER,
+            kill_waiter.Wait());
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    SecurityExploitDisallowedQualifierTest,
+    ::testing::Values(
+        static_cast<ui::PageTransition>(ui::PAGE_TRANSITION_LINK |
+                                        ui::PAGE_TRANSITION_FROM_ADDRESS_BAR),
+        static_cast<ui::PageTransition>(ui::PAGE_TRANSITION_LINK |
+                                        ui::PAGE_TRANSITION_HOME_PAGE),
+        static_cast<ui::PageTransition>(ui::PAGE_TRANSITION_LINK |
+                                        ui::PAGE_TRANSITION_FROM_API),
+        static_cast<ui::PageTransition>(ui::PAGE_TRANSITION_LINK |
+                                        ui::PAGE_TRANSITION_FORWARD_BACK),
+        static_cast<ui::PageTransition>(ui::PAGE_TRANSITION_LINK |
+                                        ui::PAGE_TRANSITION_SERVER_REDIRECT)));
+
+IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
+                       DidCommitInvalidTransitionForSubframe) {
+  WebContents* web_contents = shell()->web_contents();
+  DidCommitTransitionReplacer injector(web_contents, ui::PAGE_TRANSITION_LINK);
+
+  GURL main_url(embedded_test_server()->GetURL(
+      "a.com", "/cross_site_iframe_factory.html?a(a)"));
+  EXPECT_TRUE(NavigateToURL(web_contents, main_url));
+
+  FrameTreeNode* root =
+      static_cast<WebContentsImpl*>(web_contents)->GetPrimaryFrameTree().root();
+  FrameTreeNode* child = root->child_at(0);
+  RenderProcessHost* child_process = child->current_frame_host()->GetProcess();
+  RenderProcessHostBadIpcMessageWaiter kill_waiter(child_process);
+
+  injector.Activate();
+  ExecuteScriptAsync(child->current_frame_host(),
+                     "window.location = '/title2.html';");
+
+  EXPECT_EQ(bad_message::RFH_COMMIT_NAVIGATION_TRANSITION_FRAME_TYPE_MISMATCH,
+            kill_waiter.Wait());
+}
+
 class SecurityExploitViaDisabledWebSecurityTest
     : public SecurityExploitBrowserTest {
  public:
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/content/browser/security_exploit_browsertest.cc b/content/browser/security_exploit_browsertest.cc
index a14aede2..c3d76433 100644
--- a/content/browser/security_exploit_browsertest.cc
+++ b/content/browser/security_exploit_browsertest.cc
@@ -3645,6 +3645,159 @@
   }
 }
 
+class DidCommitTransitionReplacer : public DidCommitNavigationInterceptor {
+ public:
+  DidCommitTransitionReplacer(WebContents* web_contents,
+                              ui::PageTransition transition_to_inject)
+      : DidCommitNavigationInterceptor(web_contents),
+        transition_to_inject_(transition_to_inject) {}
+
+  DidCommitTransitionReplacer(const DidCommitTransitionReplacer&) = delete;
+  DidCommitTransitionReplacer& operator=(const DidCommitTransitionReplacer&) =
+      delete;
+
+  ~DidCommitTransitionReplacer() override = default;
+
+  void Activate() { is_activated_ = true; }
+
+ protected:
+  bool WillProcessDidCommitNavigation(
+      RenderFrameHost* render_frame_host,
+      NavigationRequest* navigation_request,
+      mojom::DidCommitProvisionalLoadParamsPtr* params,
+      mojom::DidCommitProvisionalLoadInterfaceParamsPtr* interface_params)
+      override {
+    if (is_activated_) {
+      (**params).transition = transition_to_inject_;
+      is_activated_ = false;
+    }
+    return true;
+  }
+
+ private:
+  ui::PageTransition transition_to_inject_;
+  bool is_activated_ = false;
+};
+
+class SecurityExploitPageTransitionTest
+    : public SecurityExploitBrowserTest,
+      public ::testing::WithParamInterface<ui::PageTransition> {};
+
+IN_PROC_BROWSER_TEST_P(SecurityExploitPageTransitionTest,
+                       DidCommitNonWebbyTransition) {
+  ui::PageTransition transition = GetParam();
+  WebContents* web_contents = shell()->web_contents();
+  DidCommitTransitionReplacer injector(web_contents, transition);
+
+  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
+  EXPECT_TRUE(NavigateToURL(web_contents, main_url));
+
+  RenderProcessHost* main_process =
+      web_contents->GetPrimaryMainFrame()->GetProcess();
+  RenderProcessHostBadIpcMessageWaiter kill_waiter(main_process);
+
+  injector.Activate();
+  ExecuteScriptAsync(web_contents,
+                     "history.pushState({}, '', '/title2.html');");
+
+  EXPECT_EQ(bad_message::RFH_COMMIT_NAVIGATION_NON_WEBBY_TRANSITION,
+            kill_waiter.Wait());
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    SecurityExploitPageTransitionTest,
+    ::testing::Values(ui::PAGE_TRANSITION_TYPED,
+                      ui::PAGE_TRANSITION_AUTO_BOOKMARK,
+                      ui::PAGE_TRANSITION_GENERATED,
+                      ui::PAGE_TRANSITION_AUTO_TOPLEVEL,
+                      ui::PAGE_TRANSITION_RELOAD,
+                      ui::PAGE_TRANSITION_KEYWORD,
+                      ui::PAGE_TRANSITION_KEYWORD_GENERATED));
+
+IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
+                       DidCommitBrowserInitiatedTransitionMismatch) {
+  WebContents* web_contents = shell()->web_contents();
+  DidCommitTransitionReplacer injector(web_contents,
+                                       ui::PAGE_TRANSITION_AUTO_BOOKMARK);
+
+  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
+  EXPECT_TRUE(NavigateToURL(web_contents, main_url));
+
+  RenderProcessHost* main_process =
+      web_contents->GetPrimaryMainFrame()->GetProcess();
+  RenderProcessHostBadIpcMessageWaiter kill_waiter(main_process);
+
+  injector.Activate();
+  ExecuteScriptAsync(web_contents, "window.location = '/title2.html';");
+
+  EXPECT_EQ(
+      bad_message::RFH_COMMIT_NAVIGATION_BROWSER_INITIATED_TRANSITION_MISMATCH,
+      kill_waiter.Wait());
+}
+
+class SecurityExploitDisallowedQualifierTest
+    : public SecurityExploitBrowserTest,
+      public ::testing::WithParamInterface<ui::PageTransition> {};
+
+IN_PROC_BROWSER_TEST_P(SecurityExploitDisallowedQualifierTest,
+                       DidCommitDisallowedQualifier) {
+  ui::PageTransition transition = GetParam();
+  WebContents* web_contents = shell()->web_contents();
+  DidCommitTransitionReplacer injector(web_contents, transition);
+
+  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
+  EXPECT_TRUE(NavigateToURL(web_contents, main_url));
+
+  RenderProcessHost* main_process =
+      web_contents->GetPrimaryMainFrame()->GetProcess();
+  RenderProcessHostBadIpcMessageWaiter kill_waiter(main_process);
+
+  injector.Activate();
+  ExecuteScriptAsync(web_contents, "window.location = '/title2.html';");
+
+  EXPECT_EQ(bad_message::RFH_COMMIT_NAVIGATION_DISALLOWED_QUALIFIER,
+            kill_waiter.Wait());
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    SecurityExploitDisallowedQualifierTest,
+    ::testing::Values(
+        static_cast<ui::PageTransition>(ui::PAGE_TRANSITION_LINK |
+                                        ui::PAGE_TRANSITION_FROM_ADDRESS_BAR),
+        static_cast<ui::PageTransition>(ui::PAGE_TRANSITION_LINK |
+                                        ui::PAGE_TRANSITION_HOME_PAGE),
+        static_cast<ui::PageTransition>(ui::PAGE_TRANSITION_LINK |
+                                        ui::PAGE_TRANSITION_FROM_API),
+        static_cast<ui::PageTransition>(ui::PAGE_TRANSITION_LINK |
+                                        ui::PAGE_TRANSITION_FORWARD_BACK),
+        static_cast<ui::PageTransition>(ui::PAGE_TRANSITION_LINK |
+                                        ui::PAGE_TRANSITION_SERVER_REDIRECT)));
+
+IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
+                       DidCommitInvalidTransitionForSubframe) {
+  WebContents* web_contents = shell()->web_contents();
+  DidCommitTransitionReplacer injector(web_contents, ui::PAGE_TRANSITION_LINK);
+
+  GURL main_url(embedded_test_server()->GetURL(
+      "a.com", "/cross_site_iframe_factory.html?a(a)"));
+  EXPECT_TRUE(NavigateToURL(web_contents, main_url));
+
+  FrameTreeNode* root =
+      static_cast<WebContentsImpl*>(web_contents)->GetPrimaryFrameTree().root();
+  FrameTreeNode* child = root->child_at(0);
+  RenderProcessHost* child_process = child->current_frame_host()->GetProcess();
+  RenderProcessHostBadIpcMessageWaiter kill_waiter(child_process);
+
+  injector.Activate();
+  ExecuteScriptAsync(child->current_frame_host(),
+                     "window.location = '/title2.html';");
+
+  EXPECT_EQ(bad_message::RFH_COMMIT_NAVIGATION_TRANSITION_FRAME_TYPE_MISMATCH,
+            kill_waiter.Wait());
+}
+
 class SecurityExploitViaDisabledWebSecurityTest
     : public SecurityExploitBrowserTest {
  public:
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.