Medium watch CVSS 4.3 chrome Authorization Error 🔧 Commit mapped

Overview

Medium
Severity
4.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect authorization in Core
DescriptionIncorrect authorization in Core
ComponentCore
Sub-componentContent
Bug ClassAuthorization Error
Action tierwatch — logic/policy bug — security-relevant, no memory-unsafety
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker497269030
Fix commitbb53bffbfeb5 (chromium/src) +151/-19
CWECWE-863
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-03-28 (per vendor advisory)
Fix landed2026-07-14 (public git)
Disclosed2026-08-25
Patch gap42 days — fix public in source before the advisory

Background

User Activation v2
browser-tracked “sticky” and “transient” activation state per frame, set by genuine user input and required by gesture-gated web APIs; it propagates to ancestors, and otherwise only to same-origin frames (“Same-origin Visibility”).
Document Picture-in-Picture (PiP)
an always-on-top window whose WebContents is opened by a page and linked to its opener frame tree, exposed via GetDocumentPictureInPictureOpenerFrameTree().
`kDocumentPictureInPictureUserActivation`
a feature flag under which activation in a PiP window is also propagated to the opener frame tree (and vice versa); the regression test also enables media::kDocumentPictureInPictureNavigation to navigate the PiP window cross-origin.
`VerifyCreateNewWindowParams`
browser-side validation of the renderer-supplied mojom::CreateNewWindowParams, which kills the renderer via bad_message::ReceivedBadMessage on invalid input.

Root Cause Analysis

In FrameTreeNode::NotifyUserActivation (named in the commit message), after activating the frame and its ancestors, the removed code unconditionally activated the root of the PiP opener frame tree (pip_opener->root()->current_frame_host()) whenever kDocumentPictureInPictureUserActivation was enabled and the tree was a PiP tree, without comparing origins. Every other cross-tree/sibling propagation in this function is restricted to frames whose GetLastCommittedOrigin() IsSameOriginWith(current_origin), so this path violated the “Same-origin Visibility” invariant: activation in a cross-origin PiP document gave the opener’s main frame sticky and transient activation.

The fix moves the PiP-opener block after current_origin is computed and only activates opener_frame_host when its origin is same-origin with the activated frame; the regression test shows a b.com PiP document no longer activates an a.com opener, while an a.com PiP document still does. Separately, the fix tightens VerifyCreateNewWindowParams to reject params.pip_options unless params.disposition == WindowOpenDisposition::NEW_PICTURE_IN_PICTURE, terminating the renderer with RFH_CREATE_NEW_WINDOW_INVALID_PIP_OPTIONS, so a compromised renderer cannot forge a PiP relationship on an ordinary window.

Key insight
The PiP-to-opener activation propagation in NotifyUserActivation lacked the same-origin check used by every other non-ancestor propagation path, letting a cross-origin PiP frame grant activation to the opener’s main frame; the fix gates it on IsSameOriginWith(current_origin) and adds IPC validation so pip_options cannot accompany a non-PiP disposition.

Attack Path

  1. PiP window with cross-origin content A page’s PiP window ends up showing a document cross-origin to the opener’s main frame; the test builds this with a b.com subframe as PiP opener and a navigation of the PiP window to b.com (requires kDocumentPictureInPictureUserActivation, and the test also enables kDocumentPictureInPictureNavigation).
  2. User interacts with the PiP document A genuine user activation in the cross-origin PiP frame reaches FrameTreeNode::NotifyUserActivation in the browser.
  3. Activation leaks to opener Pre-fix, the PiP-opener branch called DidReceiveUserActivation and ActivateUserActivation on the opener tree’s root regardless of origin, giving the cross-origin opener main frame sticky and transient activation.
  4. Forged PiP relationship (compromised renderer) Pre-fix, a compromised renderer could also send CreateNewWindowParams carrying pip_options with a non-PiP disposition to set up a PiP-style opener relationship for an ordinary window, now rejected as a bad message (consequences beyond that are (inferred)).
  5. Use of borrowed activation The activated cross-origin frame could invoke activation-gated APIs (e.g. popups or fullscreen) without the user having interacted with it (inferred).

Impact Assessment

A cross-origin document could cause another origin’s main frame to receive user activation it was not entitled to, an authorization bypass of the browser-process user-activation model that undermines gesture-gated API protections. Preconditions are the Document PiP user-activation feature being enabled, a cross-origin document in the PiP window, and a real user interaction with it; the pip_options forging variant additionally requires a compromised renderer. Concrete abuse of the borrowed activation is (inferred); the advisory states only “Incorrect authorization in Core” and reports no in-the-wild exploitation.

Changed Functions

FunctionChangeNotes
if
content/browser/renderer_host/frame_tree_node.cc
modified
PictureInPictureFrameTreeBrowserTest
content/browser/renderer_host/frame_tree_browsertest.cc
modified
PictureInPictureFrameTreeBrowserTest
content/browser/renderer_host/frame_tree_browsertest.cc
modified
BrowserContextGroupSwapFrameTreeBrowserTest
content/browser/renderer_host/frame_tree_browsertest.cc
modified

Files Changed

  • tools/metrics/histograms/metadata/stability/enums.xml
  • content/browser/bad_message.h
  • content/browser/renderer_host/frame_tree_browsertest.cc
  • content/browser/renderer_host/ipc_utils.cc
  • content/browser/renderer_host/frame_tree_node.cc
  • content/browser/renderer_host/ipc_utils.h

Audit Directions

  • Cross-tree activation propagation
    review every place user activation crosses WebContents/frame-tree boundaries (PiP in both directions, fenced frames, portals/prerender, guest views, popups/openers) for missing IsSameOriginWith gating matching “Same-origin Visibility”.
  • Renderer-supplied relationship flags
    audit CreateNewWindowParams and similar IPCs for optional fields (options structs, feature bits) that are honoured without cross-checking against disposition or other fields that should constrain them.
  • Feature-flagged security paths
    check code under flags like kDocumentPictureInPictureUserActivation and kDocumentPictureInPictureNavigation for checks present on the main path but missing on the flagged branch, especially after the flag lets content navigate to a new origin.
From bb53bffbfeb5f5f6fc898bad7bc68f52a4392de8 Mon Sep 17 00:00:00 2001
From: Tzarial <[email protected]>
Date: Tue, 14 Jul 2026 08:25:38 -0700
Subject: [PATCH] [agy][content] Gate PiP user activation on origin

FrameTreeNode::NotifyUserActivation() propagates user activation from a
document picture-in-picture window to the root of its opener frame tree.
Unlike other paths, this path did not require the activated frame to be
same-origin with the opener's main frame.

Gate this propagation on IsSameOriginWith(), matching other paths.

Additionally, validate in VerifyCreateNewWindowParams that pip_options
are only allowed when disposition is NEW_PICTURE_IN_PICTURE. This
prevents compromised renderers from forging PiP relationships.

Fixed: 497269030
Test: PictureInPictureFrameTreeBrowserTest.*
Change-Id: I7cb7dc07e8b57335f11597ed7c70878a2fcf573f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8086453
Reviewed-by: Caitlin Fischer <[email protected]>
Commit-Queue: Tzarial <[email protected]>
Reviewed-by: Rakina Zata Amni <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1661875}
---
diff --git a/content/browser/bad_message.h b/content/browser/bad_message.h
index 0102faa3..c943914 100644
--- a/content/browser/bad_message.h
+++ b/content/browser/bad_message.h
@@ -383,6 +383,7 @@
   RFH_AUTHENTICATOR_PDF_PROCESS_BLOCKED = 355,
   BIBI_BIND_GAMEPAD_MONITOR_BLOCKED_BY_PERMISSIONS_POLICY = 356,
   BIBI_BIND_GAMEPAD_HAPTICS_MANAGER_BLOCKED_BY_PERMISSIONS_POLICY = 357,
+  RFH_CREATE_NEW_WINDOW_INVALID_PIP_OPTIONS = 358,
 
   // 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/frame_tree_node.cc b/content/browser/renderer_host/frame_tree_node.cc
index 5dba052..c07dca3 100644
--- a/content/browser/renderer_host/frame_tree_node.cc
+++ b/content/browser/renderer_host/frame_tree_node.cc
@@ -796,33 +796,23 @@
   // User Activation V2 requires activating all ancestor frames in addition to
   // the current frame. See
   // https://html.spec.whatwg.org/multipage/interaction.html#tracking-user-activation.
+  // This propagation is spec-mandated to cross origin boundaries (e.g., from
+  // a cross-origin subframe to its parent), so no same-origin check is
+  // performed here.
   for (RenderFrameHostImpl* rfh = current_frame_host(); rfh;
        rfh = rfh->GetParent()) {
     rfh->DidReceiveUserActivation();
     rfh->ActivateUserActivation(notification_type, sticky_only);
   }
 
-  // If we're in a picture-in-picture frame tree, then also activate the opener
-  // frame of the picture-in-picture root.
-  FrameTree* pip_opener =
-      frame_tree().delegate()->GetDocumentPictureInPictureOpenerFrameTree();
-  if (base::FeatureList::IsEnabled(
-          blink::features::kDocumentPictureInPictureUserActivation) &&
-      pip_opener) {
-    RenderFrameHostImpl* opener_frame_host =
-        pip_opener->root()->current_frame_host();
-
-    opener_frame_host->DidReceiveUserActivation();
-    opener_frame_host->ActivateUserActivation(notification_type, sticky_only);
-  }
-
-  current_frame_host()->browsing_context_state()->set_has_active_user_gesture(
-      true);
-
   // See the "Same-origin Visibility" section in |UserActivationState| class
   // doc.
   const url::Origin& current_origin =
       this->current_frame_host()->GetLastCommittedOrigin();
+
+  current_frame_host()->browsing_context_state()->set_has_active_user_gesture(
+      true);
+
   for (FrameTreeNode* node : frame_tree().Nodes()) {
     if (node->current_frame_host()->GetLastCommittedOrigin().IsSameOriginWith(
             current_origin)) {
@@ -833,6 +823,22 @@
 
   if (base::FeatureList::IsEnabled(
           blink::features::kDocumentPictureInPictureUserActivation)) {
+    // If we're in a picture-in-picture frame tree, then also activate the
+    // opener frame of the picture-in-picture root if it is same-origin with the
+    // activated frame.
+    FrameTree* pip_opener =
+        frame_tree().delegate()->GetDocumentPictureInPictureOpenerFrameTree();
+    if (pip_opener) {
+      RenderFrameHostImpl* opener_frame_host =
+          pip_opener->root()->current_frame_host();
+      if (opener_frame_host->GetLastCommittedOrigin().IsSameOriginWith(
+              current_origin)) {
+        opener_frame_host->DidReceiveUserActivation();
+        opener_frame_host->ActivateUserActivation(notification_type,
+                                                  sticky_only);
+      }
+    }
+
     // If we own a picture-in-picture window, then also activate same-origin
     // frames within the picture-in-picture window.
     FrameTree* picture_in_picture_frame_tree =
diff --git a/content/browser/renderer_host/ipc_utils.cc b/content/browser/renderer_host/ipc_utils.cc
index fd9deb4f..94ee783 100644
--- a/content/browser/renderer_host/ipc_utils.cc
+++ b/content/browser/renderer_host/ipc_utils.cc
@@ -394,6 +394,13 @@
     return false;
   }
 
+  if (params.pip_options &&
+      params.disposition != WindowOpenDisposition::NEW_PICTURE_IN_PICTURE) {
+    bad_message::ReceivedBadMessage(
+        process, bad_message::RFH_CREATE_NEW_WINDOW_INVALID_PIP_OPTIONS);
+    return false;
+  }
+
   // Verify `form_submission_post_data`.
   auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
   if (!policy->CanReadRequestBody(process, params.form_submission_post_data)) {
diff --git a/content/browser/renderer_host/ipc_utils.h b/content/browser/renderer_host/ipc_utils.h
index 73d24681..b56bf12 100644
--- a/content/browser/renderer_host/ipc_utils.h
+++ b/content/browser/renderer_host/ipc_utils.h
@@ -6,6 +6,7 @@
 #define CONTENT_BROWSER_RENDERER_HOST_IPC_UTILS_H_
 
 #include "base/memory/scoped_refptr.h"
+#include "content/common/content_export.h"
 #include "content/common/frame.mojom.h"
 #include "content/public/browser/render_process_host.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
@@ -68,8 +69,9 @@
 // CreateNewWindowParams are invalid.
 //
 // This function has to be called on the UI thread.
-bool VerifyCreateNewWindowParams(const RenderFrameHostImpl& current_rfh,
-                                 const mojom::CreateNewWindowParams& params);
+CONTENT_EXPORT bool VerifyCreateNewWindowParams(
+    const RenderFrameHostImpl& current_rfh,
+    const mojom::CreateNewWindowParams& params);
 
 // Verify that the initiator frame identified by `initiator_frame_token` and
 // `initiator_process_id` can navigate `current_rfh`.
diff --git a/tools/metrics/histograms/metadata/stability/enums.xml b/tools/metrics/histograms/metadata/stability/enums.xml
index 803150e..d8bf5dc 100644
--- a/tools/metrics/histograms/metadata/stability/enums.xml
+++ b/tools/metrics/histograms/metadata/stability/enums.xml
@@ -531,6 +531,7 @@
       label="BIBI_BIND_GAMEPAD_MONITOR_BLOCKED_BY_PERMISSIONS_POLICY"/>
   <int value="357"
       label="BIBI_BIND_GAMEPAD_HAPTICS_MANAGER_BLOCKED_BY_PERMISSIONS_POLICY"/>
+  <int value="358" label="RFH_CREATE_NEW_WINDOW_INVALID_PIP_OPTIONS"/>
 </enum>
 
 <enum name="BadMessageReasonExtensions">
diff --git a/content/browser/renderer_host/frame_tree_browsertest.cc b/content/browser/renderer_host/frame_tree_browsertest.cc
index 77b119d..146ac7b 100644
--- a/content/browser/renderer_host/frame_tree_browsertest.cc
+++ b/content/browser/renderer_host/frame_tree_browsertest.cc
@@ -13,12 +13,14 @@
 #include "base/test/scoped_feature_list.h"
 #include "build/build_config.h"
 #include "content/browser/renderer_host/frame_tree_node.h"
+#include "content/browser/renderer_host/ipc_utils.h"
 #include "content/browser/renderer_host/navigation_request.h"
 #include "content/browser/renderer_host/render_frame_host_impl.h"
 #include "content/browser/renderer_host/render_process_host_impl.h"
 #include "content/browser/renderer_host/render_view_host_impl.h"
 #include "content/browser/web_contents/web_contents_impl.h"
 #include "content/common/content_navigation_policy.h"
+#include "content/common/frame.mojom.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/dedicated_worker_service.h"
 #include "content/public/browser/navigation_handle.h"
@@ -39,6 +41,7 @@
 #include "content/shell/common/shell_switches.h"
 #include "content/test/content_browser_test_utils_internal.h"
 #include "content/test/render_document_feature.h"
+#include "media/base/media_switches.h"
 #include "net/base/features.h"
 #include "net/dns/mock_host_resolver.h"
 #include "net/test/embedded_test_server/controllable_http_response.h"
@@ -1965,6 +1968,118 @@
   EXPECT_FALSE(root->HasTransientUserActivation());
 }
 
+class PictureInPictureFrameTreeBrowserTest
+    : public CrossProcessFrameTreeBrowserTest {
+ public:
+  PictureInPictureFrameTreeBrowserTest() {
+    scoped_feature_list_.InitWithFeatures(
+        {blink::features::kDocumentPictureInPictureUserActivation,
+         media::kDocumentPictureInPictureNavigation},
+        {});
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+// Tests that user activation in a document picture-in-picture window only
+// propagates to the opener window when the activated frame is same-origin with
+// the opener's main frame.
+IN_PROC_BROWSER_TEST_F(PictureInPictureFrameTreeBrowserTest,
+                       UserActivationOpenerPropagationSameOriginOnly) {
+  // Load an a.com page that embeds a cross-origin b.com subframe.
+  GURL main_url(embedded_test_server()->GetURL(
+      "a.com", "/cross_site_iframe_factory.html?a(b)"));
+  EXPECT_TRUE(NavigateToURL(shell(), main_url));
+
+  WebContentsImpl* opener_contents =
+      static_cast<WebContentsImpl*>(shell()->web_contents());
+  FrameTreeNode* opener_root = opener_contents->GetPrimaryFrameTree().root();
+  ASSERT_EQ(1U, opener_root->child_count());
+  FrameTreeNode* opener_child = opener_root->child_at(0);
+  ASSERT_FALSE(
+      opener_root->current_frame_host()
+          ->GetLastCommittedOrigin()
+          .IsSameOriginWith(
+              opener_child->current_frame_host()->GetLastCommittedOrigin()));
+
+  // Create a picture-in-picture window whose opener is the b.com subframe so
+  // that the picture-in-picture opener resolves to the embedding a.com page.
+  WebContents::CreateParams create_params(
+      opener_contents->GetBrowserContext(),
+      opener_child->current_frame_host()->GetSiteInstance());
+  create_params.picture_in_picture_options =
+      blink::mojom::PictureInPictureWindowOptions();
+  std::unique_ptr<WebContentsImpl> pip_contents =
+      WebContentsImpl::CreateWithOpener(create_params,
+                                        opener_child->current_frame_host());
+  ASSERT_EQ(&opener_contents->GetPrimaryFrameTree(),
+            pip_contents->GetDocumentPictureInPictureOpenerFrameTree());
+
+  // Navigate the picture-in-picture window to a b.com document so that its
+  // main frame is cross-origin with the opener's main frame.
+  GURL cross_origin_url(
+      embedded_test_server()->GetURL("b.com", "/title1.html"));
+  EXPECT_TRUE(NavigateToURL(pip_contents.get(), cross_origin_url));
+  FrameTreeNode* pip_root = pip_contents->GetPrimaryFrameTree().root();
+  ASSERT_FALSE(
+      pip_root->current_frame_host()->GetLastCommittedOrigin().IsSameOriginWith(
+          opener_root->current_frame_host()->GetLastCommittedOrigin()));
+
+  EXPECT_FALSE(opener_root->HasStickyUserActivation());
+  EXPECT_FALSE(opener_root->HasTransientUserActivation());
+
+  // Activating the cross-origin picture-in-picture main frame must not
+  // propagate to the opener.
+  EXPECT_TRUE(pip_root->UpdateUserActivationState(
+      blink::mojom::UserActivationUpdateType::kNotifyActivation,
+      blink::mojom::UserActivationNotificationType::kTest));
+  EXPECT_TRUE(pip_root->HasStickyUserActivation());
+  EXPECT_TRUE(pip_root->HasTransientUserActivation());
+  EXPECT_FALSE(opener_root->HasStickyUserActivation());
+  EXPECT_FALSE(opener_root->HasTransientUserActivation());
+
+  // Navigate the picture-in-picture window to a same-origin a.com document and
+  // verify that activation does propagate to the opener.
+  GURL same_origin_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
+  EXPECT_TRUE(NavigateToURL(pip_contents.get(), same_origin_url));
+  pip_root = pip_contents->GetPrimaryFrameTree().root();
+  ASSERT_TRUE(
+      pip_root->current_frame_host()->GetLastCommittedOrigin().IsSameOriginWith(
+          opener_root->current_frame_host()->GetLastCommittedOrigin()));
+
+  EXPECT_TRUE(pip_root->UpdateUserActivationState(
+      blink::mojom::UserActivationUpdateType::kNotifyActivation,
+      blink::mojom::UserActivationNotificationType::kTest));
+  EXPECT_TRUE(opener_root->HasStickyUserActivation());
+  EXPECT_TRUE(opener_root->HasTransientUserActivation());
+}
+
+IN_PROC_BROWSER_TEST_F(PictureInPictureFrameTreeBrowserTest,
+                       VerifyCreateNewWindowParamsInvalidPipOptions) {
+  EXPECT_TRUE(NavigateToURL(
+      shell(), embedded_test_server()->GetURL("a.com", "/title1.html")));
+
+  RenderFrameHostImpl* rfh = static_cast<RenderFrameHostImpl*>(
+      shell()->web_contents()->GetPrimaryMainFrame());
+
+  // Create invalid params: NEW_POPUP with pip_options.
+  mojom::CreateNewWindowParamsPtr params = mojom::CreateNewWindowParams::New();
+  params->disposition = WindowOpenDisposition::NEW_POPUP;
+  params->pip_options = blink::mojom::PictureInPictureWindowOptions::New();
+
+  RenderProcessHostWatcher crash_observer(
+      rfh->GetProcess(), RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
+
+  // Call the validation function. It should return false and trigger bad
+  // message.
+  EXPECT_FALSE(VerifyCreateNewWindowParams(*rfh, *params));
+
+  // The process should be terminated.
+  crash_observer.Wait();
+  EXPECT_FALSE(crash_observer.did_exit_normally());
+}
+
 class BrowserContextGroupSwapFrameTreeBrowserTest : public ContentBrowserTest {
  public:
   BrowserContextGroupSwapFrameTreeBrowserTest()
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/content/browser/renderer_host/frame_tree_browsertest.cc b/content/browser/renderer_host/frame_tree_browsertest.cc
index 77b119d..146ac7b 100644
--- a/content/browser/renderer_host/frame_tree_browsertest.cc
+++ b/content/browser/renderer_host/frame_tree_browsertest.cc
@@ -13,12 +13,14 @@
 #include "base/test/scoped_feature_list.h"
 #include "build/build_config.h"
 #include "content/browser/renderer_host/frame_tree_node.h"
+#include "content/browser/renderer_host/ipc_utils.h"
 #include "content/browser/renderer_host/navigation_request.h"
 #include "content/browser/renderer_host/render_frame_host_impl.h"
 #include "content/browser/renderer_host/render_process_host_impl.h"
 #include "content/browser/renderer_host/render_view_host_impl.h"
 #include "content/browser/web_contents/web_contents_impl.h"
 #include "content/common/content_navigation_policy.h"
+#include "content/common/frame.mojom.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/dedicated_worker_service.h"
 #include "content/public/browser/navigation_handle.h"
@@ -39,6 +41,7 @@
 #include "content/shell/common/shell_switches.h"
 #include "content/test/content_browser_test_utils_internal.h"
 #include "content/test/render_document_feature.h"
+#include "media/base/media_switches.h"
 #include "net/base/features.h"
 #include "net/dns/mock_host_resolver.h"
 #include "net/test/embedded_test_server/controllable_http_response.h"
@@ -1965,6 +1968,118 @@
   EXPECT_FALSE(root->HasTransientUserActivation());
 }
 
+class PictureInPictureFrameTreeBrowserTest
+    : public CrossProcessFrameTreeBrowserTest {
+ public:
+  PictureInPictureFrameTreeBrowserTest() {
+    scoped_feature_list_.InitWithFeatures(
+        {blink::features::kDocumentPictureInPictureUserActivation,
+         media::kDocumentPictureInPictureNavigation},
+        {});
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+// Tests that user activation in a document picture-in-picture window only
+// propagates to the opener window when the activated frame is same-origin with
+// the opener's main frame.
+IN_PROC_BROWSER_TEST_F(PictureInPictureFrameTreeBrowserTest,
+                       UserActivationOpenerPropagationSameOriginOnly) {
+  // Load an a.com page that embeds a cross-origin b.com subframe.
+  GURL main_url(embedded_test_server()->GetURL(
+      "a.com", "/cross_site_iframe_factory.html?a(b)"));
+  EXPECT_TRUE(NavigateToURL(shell(), main_url));
+
+  WebContentsImpl* opener_contents =
+      static_cast<WebContentsImpl*>(shell()->web_contents());
+  FrameTreeNode* opener_root = opener_contents->GetPrimaryFrameTree().root();
+  ASSERT_EQ(1U, opener_root->child_count());
+  FrameTreeNode* opener_child = opener_root->child_at(0);
+  ASSERT_FALSE(
+      opener_root->current_frame_host()
+          ->GetLastCommittedOrigin()
+          .IsSameOriginWith(
+              opener_child->current_frame_host()->GetLastCommittedOrigin()));
+
+  // Create a picture-in-picture window whose opener is the b.com subframe so
+  // that the picture-in-picture opener resolves to the embedding a.com page.
+  WebContents::CreateParams create_params(
+      opener_contents->GetBrowserContext(),
+      opener_child->current_frame_host()->GetSiteInstance());
+  create_params.picture_in_picture_options =
+      blink::mojom::PictureInPictureWindowOptions();
+  std::unique_ptr<WebContentsImpl> pip_contents =
+      WebContentsImpl::CreateWithOpener(create_params,
+                                        opener_child->current_frame_host());
+  ASSERT_EQ(&opener_contents->GetPrimaryFrameTree(),
+            pip_contents->GetDocumentPictureInPictureOpenerFrameTree());
+
+  // Navigate the picture-in-picture window to a b.com document so that its
+  // main frame is cross-origin with the opener's main frame.
+  GURL cross_origin_url(
+      embedded_test_server()->GetURL("b.com", "/title1.html"));
+  EXPECT_TRUE(NavigateToURL(pip_contents.get(), cross_origin_url));
+  FrameTreeNode* pip_root = pip_contents->GetPrimaryFrameTree().root();
+  ASSERT_FALSE(
+      pip_root->current_frame_host()->GetLastCommittedOrigin().IsSameOriginWith(
+          opener_root->current_frame_host()->GetLastCommittedOrigin()));
+
+  EXPECT_FALSE(opener_root->HasStickyUserActivation());
+  EXPECT_FALSE(opener_root->HasTransientUserActivation());
+
+  // Activating the cross-origin picture-in-picture main frame must not
+  // propagate to the opener.
+  EXPECT_TRUE(pip_root->UpdateUserActivationState(
+      blink::mojom::UserActivationUpdateType::kNotifyActivation,
+      blink::mojom::UserActivationNotificationType::kTest));
+  EXPECT_TRUE(pip_root->HasStickyUserActivation());
+  EXPECT_TRUE(pip_root->HasTransientUserActivation());
+  EXPECT_FALSE(opener_root->HasStickyUserActivation());
+  EXPECT_FALSE(opener_root->HasTransientUserActivation());
+
+  // Navigate the picture-in-picture window to a same-origin a.com document and
+  // verify that activation does propagate to the opener.
+  GURL same_origin_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
+  EXPECT_TRUE(NavigateToURL(pip_contents.get(), same_origin_url));
+  pip_root = pip_contents->GetPrimaryFrameTree().root();
+  ASSERT_TRUE(
+      pip_root->current_frame_host()->GetLastCommittedOrigin().IsSameOriginWith(
+          opener_root->current_frame_host()->GetLastCommittedOrigin()));
+
+  EXPECT_TRUE(pip_root->UpdateUserActivationState(
+      blink::mojom::UserActivationUpdateType::kNotifyActivation,
+      blink::mojom::UserActivationNotificationType::kTest));
+  EXPECT_TRUE(opener_root->HasStickyUserActivation());
+  EXPECT_TRUE(opener_root->HasTransientUserActivation());
+}
+
+IN_PROC_BROWSER_TEST_F(PictureInPictureFrameTreeBrowserTest,
+                       VerifyCreateNewWindowParamsInvalidPipOptions) {
+  EXPECT_TRUE(NavigateToURL(
+      shell(), embedded_test_server()->GetURL("a.com", "/title1.html")));
+
+  RenderFrameHostImpl* rfh = static_cast<RenderFrameHostImpl*>(
+      shell()->web_contents()->GetPrimaryMainFrame());
+
+  // Create invalid params: NEW_POPUP with pip_options.
+  mojom::CreateNewWindowParamsPtr params = mojom::CreateNewWindowParams::New();
+  params->disposition = WindowOpenDisposition::NEW_POPUP;
+  params->pip_options = blink::mojom::PictureInPictureWindowOptions::New();
+
+  RenderProcessHostWatcher crash_observer(
+      rfh->GetProcess(), RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
+
+  // Call the validation function. It should return false and trigger bad
+  // message.
+  EXPECT_FALSE(VerifyCreateNewWindowParams(*rfh, *params));
+
+  // The process should be terminated.
+  crash_observer.Wait();
+  EXPECT_FALSE(crash_observer.did_exit_normally());
+}
+
 class BrowserContextGroupSwapFrameTreeBrowserTest : public ContentBrowserTest {
  public:
   BrowserContextGroupSwapFrameTreeBrowserTest()
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.