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 <[email protected]> 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 <[email protected]> Commit-Queue: Johannes Kron <[email protected]> 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