Low chrome Logic Error 🔧 Commit mapped

Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in SurfaceCapture
DescriptionInappropriate implementation in SurfaceCapture
ComponentSurfaceCapture
Bug ClassLogic Error
Tracker497366217
Fix commitb81c7f19e540 (chromium/src) +84/-1
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-29

Changed Functions

FunctionChangeNotes
TEST_P
third_party/blink/renderer/modules/mediastream/media_devices_test.cc
modified

Files Changed

  • content/browser/media/capture/sub_capture_target_id_web_contents_helper.cc
  • content/browser/renderer_host/media/sub_capture_target_id_web_contents_helper_unittest.cc
  • third_party/blink/renderer/modules/mediastream/media_devices.cc
  • third_party/blink/renderer/modules/mediastream/media_devices_test.cc
  • tools/metrics/histograms/metadata/media/enums.xml
From b81c7f19e540bda946e8eee68907e79b4ed5d30a Mon Sep 17 00:00:00 2001
From: Johannes Kron <kron@chromium.org>
Date: Tue, 23 Jun 2026 15:49:09 -0700
Subject: [PATCH] Restrict SubCaptureTarget production in fenced frames

CropTarget and RestrictionTarget production should not be allowed inside
fenced frames. This CL rejects calls to produce sub-capture target IDs
at the renderer boundary and returns nullptr at the browser host level
when requested by a fenced frame, ensuring isolation.

Fixed: 497366217
Change-Id: I3b42dd3983f9a3e53f6941a7a56e878a8d752fce
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7956563
Reviewed-by: Tove Petersson <tovep@chromium.org>
Commit-Queue: Johannes Kron <kron@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1651340}
---

diff --git a/content/browser/media/capture/sub_capture_target_id_web_contents_helper.cc b/content/browser/media/capture/sub_capture_target_id_web_contents_helper.cc
index c79f169..477246476 100644
--- a/content/browser/media/capture/sub_capture_target_id_web_contents_helper.cc
+++ b/content/browser/media/capture/sub_capture_target_id_web_contents_helper.cc
@@ -65,6 +65,11 @@
   if (!rfhi || !rfhi->IsActive()) {
     return nullptr;
   }
+
+  if (rfhi->IsNestedWithinFencedFrame()) {
+    return nullptr;
+  }
+
   rfhi = rfhi->GetMainFrame();  // TODO(crbug.com/40287690): Remove this line.
 
   if (GetContentClient()
diff --git a/content/browser/renderer_host/media/sub_capture_target_id_web_contents_helper_unittest.cc b/content/browser/renderer_host/media/sub_capture_target_id_web_contents_helper_unittest.cc
index f7051fdf..52ddc23 100644
--- a/content/browser/renderer_host/media/sub_capture_target_id_web_contents_helper_unittest.cc
+++ b/content/browser/renderer_host/media/sub_capture_target_id_web_contents_helper_unittest.cc
@@ -12,6 +12,7 @@
 
 #include "base/uuid.h"
 #include "build/build_config.h"
+#include "content/public/test/test_renderer_host.h"
 #include "content/test/test_render_view_host.h"
 #include "content/test/test_web_contents.h"
 #include "testing/gmock/include/gmock/gmock-matchers.h"
@@ -306,4 +307,41 @@
   EXPECT_TRUE(helper->IsAssociatedWith(id, type_));
 }
 
+TEST_P(SubCaptureTargetIdWebContentsHelperTest,
+       GetRelevantWebContentsFencedFrameReturnsNull) {
+  std::unique_ptr<TestWebContents> web_contents = MakeTestWebContents();
+  web_contents->NavigateAndCommit(GURL("https://tests-r-us.com/first.html"));
+  TestRenderFrameHost* main_rfh = web_contents->GetPrimaryMainFrame();
+
+  // A normal main frame should return the WebContents.
+  EXPECT_EQ(SubCaptureTargetIdWebContentsHelper::GetRelevantWebContents(
+                main_rfh->GetGlobalId()),
+            web_contents.get());
+
+  // A normal subframe should also return the WebContents.
+  RenderFrameHost* normal_subframe =
+      RenderFrameHostTester::For(main_rfh)->AppendChild("normal_subframe");
+  ASSERT_NE(normal_subframe, nullptr);
+  EXPECT_EQ(SubCaptureTargetIdWebContentsHelper::GetRelevantWebContents(
+                normal_subframe->GetGlobalId()),
+            web_contents.get());
+
+  // A fenced frame root should return nullptr.
+  RenderFrameHost* fenced_frame_root =
+      RenderFrameHostTester::For(main_rfh)->AppendFencedFrame();
+  ASSERT_NE(fenced_frame_root, nullptr);
+  EXPECT_EQ(SubCaptureTargetIdWebContentsHelper::GetRelevantWebContents(
+                fenced_frame_root->GetGlobalId()),
+            nullptr);
+
+  // A subframe nested inside a fenced frame tree should also return nullptr.
+  RenderFrameHost* fenced_frame_subframe =
+      RenderFrameHostTester::For(fenced_frame_root)
+          ->AppendChild("fenced_frame_subframe");
+  ASSERT_NE(fenced_frame_subframe, nullptr);
+  EXPECT_EQ(SubCaptureTargetIdWebContentsHelper::GetRelevantWebContents(
+                fenced_frame_subframe->GetGlobalId()),
+            nullptr);
+}
+
 }  // namespace content
diff --git a/third_party/blink/renderer/modules/mediastream/media_devices.cc b/third_party/blink/renderer/modules/mediastream/media_devices.cc
index b74ee0e2..79675aa 100644
--- a/third_party/blink/renderer/modules/mediastream/media_devices.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_devices.cc
@@ -268,7 +268,8 @@
   kDuplicateCallBeforePromiseResolution = 3,
   kDuplicateCallAfterPromiseResolution = 4,
   kElementAndMediaDevicesNotInSameExecutionContext = 5,
-  kMaxValue = kElementAndMediaDevicesNotInSameExecutionContext
+  kFencedFrameNotAllowed = 6,
+  kMaxValue = kFencedFrameNotAllowed
 };
 
 void RecordUma(SubCaptureTarget::Type type,
@@ -1665,6 +1666,17 @@
     return false;
   }
 
+  if (window->GetFrame() && window->GetFrame()->IsInFencedFrameTree()) {
+    RecordUma(type, ProduceTargetFunctionResult::kFencedFrameNotAllowed);
+    exception_state.ThrowDOMException(
+        DOMExceptionCode::kNotAllowedError,
+        type == SubCaptureTarget::Type::kCropTarget
+            ? "CropTarget.fromElement is not allowed in a fenced frame tree."
+            : "RestrictionTarget.fromElement is not allowed in a fenced frame "
+              "tree.");
+    return false;
+  }
+
   if (GetExecutionContext() != element->GetExecutionContext()) {
     RecordUma(type, ProduceTargetFunctionResult::
                         kElementAndMediaDevicesNotInSameExecutionContext);
diff --git a/third_party/blink/renderer/modules/mediastream/media_devices_test.cc b/third_party/blink/renderer/modules/mediastream/media_devices_test.cc
index f42893d..268ed63 100644
--- a/third_party/blink/renderer/modules/mediastream/media_devices_test.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_devices_test.cc
@@ -1534,6 +1534,33 @@
       String("The Element and the MediaDevices object must be same-window."));
 }
 
+TEST_P(ProduceSubCaptureTargetTest, RejectsIfFencedFrame) {
+  V8TestingScope scope;
+  auto* media_devices = GetMediaDevices(*GetDocument().domWindow());
+  ASSERT_TRUE(media_devices);
+
+  // Set the page to behave as a fenced frame root.
+  GetDocument().GetPage()->SetIsMainFrameFencedFrameRoot();
+  ASSERT_TRUE(GetDocument().GetFrame()->IsInFencedFrameTree());
+
+  SetBodyContent("<div id='test-div'></div>");
+  Element* const div = GetDocument().getElementById(AtomicString("test-div"));
+
+  bool got_promise =
+      ProduceSubCaptureTargetAndGetPromise(scope, type_, media_devices, div);
+  EXPECT_FALSE(got_promise);
+  EXPECT_TRUE(scope.GetExceptionState().HadException());
+  EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
+            DOMExceptionCode::kNotAllowedError);
+  EXPECT_EQ(
+      scope.GetExceptionState().Message(),
+      type_ == SubCaptureTarget::Type::kCropTarget
+          ? String(
+                "CropTarget.fromElement is not allowed in a fenced frame tree.")
+          : String("RestrictionTarget.fromElement is not allowed in a fenced "
+                   "frame tree."));
+}
+
 TEST_P(ProduceSubCaptureTargetTest, DuplicateId) {
   V8TestingScope scope;
   auto* media_devices = GetMediaDevices(*GetDocument().domWindow());
diff --git a/tools/metrics/histograms/metadata/media/enums.xml b/tools/metrics/histograms/metadata/media/enums.xml
index 584e1da5..2a7bb66 100644
--- a/tools/metrics/histograms/metadata/media/enums.xml
+++ b/tools/metrics/histograms/metadata/media/enums.xml
@@ -2156,6 +2156,7 @@
   <int value="3" label="DuplicateCallBeforePromiseResolution"/>
   <int value="4" label="DuplicateCallAfterPromiseResolution"/>
   <int value="5" label="ElementAndMediaDevicesNotInSameExecutionContext"/>
+  <int value="6" label="FencedFrameNotAllowed"/>
 </enum>
 
 <enum name="ProduceTargetPromiseResult">
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/content/browser/renderer_host/media/sub_capture_target_id_web_contents_helper_unittest.cc b/content/browser/renderer_host/media/sub_capture_target_id_web_contents_helper_unittest.cc
index f7051fdf..52ddc23 100644
--- a/content/browser/renderer_host/media/sub_capture_target_id_web_contents_helper_unittest.cc
+++ b/content/browser/renderer_host/media/sub_capture_target_id_web_contents_helper_unittest.cc
@@ -12,6 +12,7 @@
 
 #include "base/uuid.h"
 #include "build/build_config.h"
+#include "content/public/test/test_renderer_host.h"
 #include "content/test/test_render_view_host.h"
 #include "content/test/test_web_contents.h"
 #include "testing/gmock/include/gmock/gmock-matchers.h"
@@ -306,4 +307,41 @@
   EXPECT_TRUE(helper->IsAssociatedWith(id, type_));
 }
 
+TEST_P(SubCaptureTargetIdWebContentsHelperTest,
+       GetRelevantWebContentsFencedFrameReturnsNull) {
+  std::unique_ptr<TestWebContents> web_contents = MakeTestWebContents();
+  web_contents->NavigateAndCommit(GURL("https://tests-r-us.com/first.html"));
+  TestRenderFrameHost* main_rfh = web_contents->GetPrimaryMainFrame();
+
+  // A normal main frame should return the WebContents.
+  EXPECT_EQ(SubCaptureTargetIdWebContentsHelper::GetRelevantWebContents(
+                main_rfh->GetGlobalId()),
+            web_contents.get());
+
+  // A normal subframe should also return the WebContents.
+  RenderFrameHost* normal_subframe =
+      RenderFrameHostTester::For(main_rfh)->AppendChild("normal_subframe");
+  ASSERT_NE(normal_subframe, nullptr);
+  EXPECT_EQ(SubCaptureTargetIdWebContentsHelper::GetRelevantWebContents(
+                normal_subframe->GetGlobalId()),
+            web_contents.get());
+
+  // A fenced frame root should return nullptr.
+  RenderFrameHost* fenced_frame_root =
+      RenderFrameHostTester::For(main_rfh)->AppendFencedFrame();
+  ASSERT_NE(fenced_frame_root, nullptr);
+  EXPECT_EQ(SubCaptureTargetIdWebContentsHelper::GetRelevantWebContents(
+                fenced_frame_root->GetGlobalId()),
+            nullptr);
+
+  // A subframe nested inside a fenced frame tree should also return nullptr.
+  RenderFrameHost* fenced_frame_subframe =
+      RenderFrameHostTester::For(fenced_frame_root)
+          ->AppendChild("fenced_frame_subframe");
+  ASSERT_NE(fenced_frame_subframe, nullptr);
+  EXPECT_EQ(SubCaptureTargetIdWebContentsHelper::GetRelevantWebContents(
+                fenced_frame_subframe->GetGlobalId()),
+            nullptr);
+}
+
 }  // namespace content
diff --git a/third_party/blink/renderer/modules/mediastream/media_devices_test.cc b/third_party/blink/renderer/modules/mediastream/media_devices_test.cc
index f42893d..268ed63 100644
--- a/third_party/blink/renderer/modules/mediastream/media_devices_test.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_devices_test.cc
@@ -1534,6 +1534,33 @@
       String("The Element and the MediaDevices object must be same-window."));
 }
 
+TEST_P(ProduceSubCaptureTargetTest, RejectsIfFencedFrame) {
+  V8TestingScope scope;
+  auto* media_devices = GetMediaDevices(*GetDocument().domWindow());
+  ASSERT_TRUE(media_devices);
+
+  // Set the page to behave as a fenced frame root.
+  GetDocument().GetPage()->SetIsMainFrameFencedFrameRoot();
+  ASSERT_TRUE(GetDocument().GetFrame()->IsInFencedFrameTree());
+
+  SetBodyContent("<div id='test-div'></div>");
+  Element* const div = GetDocument().getElementById(AtomicString("test-div"));
+
+  bool got_promise =
+      ProduceSubCaptureTargetAndGetPromise(scope, type_, media_devices, div);
+  EXPECT_FALSE(got_promise);
+  EXPECT_TRUE(scope.GetExceptionState().HadException());
+  EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
+            DOMExceptionCode::kNotAllowedError);
+  EXPECT_EQ(
+      scope.GetExceptionState().Message(),
+      type_ == SubCaptureTarget::Type::kCropTarget
+          ? String(
+                "CropTarget.fromElement is not allowed in a fenced frame tree.")
+          : String("RestrictionTarget.fromElement is not allowed in a fenced "
+                   "frame tree."));
+}
+
 TEST_P(ProduceSubCaptureTargetTest, DuplicateId) {
   V8TestingScope scope;
   auto* media_devices = GetMediaDevices(*GetDocument().domWindow());
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.