Chrome · SurfaceCapture
CVE-2026-17905
Logic Error in SurfaceCapture
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
TEST_Pthird_party/blink/renderer/modules/mediastream/media_devices_test.cc |
modified |
Files Changed
content/browser/media/capture/sub_capture_target_id_web_contents_helper.cccontent/browser/renderer_host/media/sub_capture_target_id_web_contents_helper_unittest.ccthird_party/blink/renderer/modules/mediastream/media_devices.ccthird_party/blink/renderer/modules/mediastream/media_devices_test.cctools/metrics/histograms/metadata/media/enums.xml
Patch
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.
References
On This Page