Chrome · PermissionElement › CE
CVE-2026-106259
Authorization Error in PermissionElement (CE)
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifthird_party/blink/renderer/core/html/html_capability_element_base.cc |
modified | |
TEST_Fthird_party/blink/renderer/core/html/html_capability_element_base_test.cc |
modified | |
HTMLCapabilityElementBaseClickingEnabledTestthird_party/blink/renderer/core/html/html_capability_element_base_test.cc |
modified |
Files Changed
third_party/blink/web_tests/external/wpt/html/semantics/permission-element/usermedia/no-focus.htmlthird_party/blink/renderer/core/html/html_capability_element_base.ccthird_party/blink/renderer/core/html/html_capability_element_base_test.cc
Patch
From e46ec753c3fd3f3982d091817762f9f7ecaba0f2 Mon Sep 17 00:00:00 2001 From: Thomas Nguyen <[email protected]> Date: Wed, 02 Sep 2026 13:59:19 -0700 Subject: [PATCH] [CE] Restrict script-initiated focus on capability elements Internal programmatic focus paths, such as modal dialog transitions or popover autofocus, reach this method with `FocusType::kNone`. This allowed capability elements to acquire focus without transient user activation, bypassing safeguards. By tracking `FocusTrigger` instead, internal script-driven invocations are properly captured and blocked unless user activation is present. Fixed: 540076586, 344739595 Change-Id: I5caec2bab0d2817f17b3e9f458e6da773b6761de Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8328459 Reviewed-by: Dominic Farolino <[email protected]> Commit-Queue: Thomas Nguyen <[email protected]> Cr-Commit-Position: refs/heads/main@{#1691080} --- diff --git a/third_party/blink/renderer/core/html/html_capability_element_base.cc b/third_party/blink/renderer/core/html/html_capability_element_base.cc index 67dc6b54..459b65d 100644 --- a/third_party/blink/renderer/core/html/html_capability_element_base.cc +++ b/third_party/blink/renderer/core/html/html_capability_element_base.cc @@ -389,10 +389,12 @@ if (fallback_mode_) { return HTMLElement::Focus(params); } - // This will only apply to `focus` and `blur` JS API. Other focus types (like + // This will only apply to script-initiated focusing. Other focus types (like // accessibility focusing and manual user focus), will still be permitted as - // usual. - if (params.type == mojom::blink::FocusType::kScript && + // usual. `FocusTrigger` (rather than `FocusType`) is used because internal + // script-driven paths such as dialog.showModal() / popover autofocus reach + // this with FocusType::kNone. + if (params.focus_trigger == FocusTrigger::kScript && !LocalFrame::HasTransientUserActivation(GetDocument().GetFrame())) { return; } diff --git a/third_party/blink/renderer/core/html/html_capability_element_base_test.cc b/third_party/blink/renderer/core/html/html_capability_element_base_test.cc index bcaca0a6..13d58e0 100644 --- a/third_party/blink/renderer/core/html/html_capability_element_base_test.cc +++ b/third_party/blink/renderer/core/html/html_capability_element_base_test.cc @@ -24,9 +24,11 @@ #include "third_party/blink/renderer/core/css/properties/longhands.h" #include "third_party/blink/renderer/core/dom/document.h" #include "third_party/blink/renderer/core/dom/document_init.h" +#include "third_party/blink/renderer/core/dom/focus_params.h" #include "third_party/blink/renderer/core/frame/local_dom_window.h" #include "third_party/blink/renderer/core/frame/local_frame.h" #include "third_party/blink/renderer/core/geometry/dom_rect.h" +#include "third_party/blink/renderer/core/html/html_dialog_element.h" #include "third_party/blink/renderer/core/html/html_geolocation_element.h" #include "third_party/blink/renderer/core/html/html_iframe_element.h" #include "third_party/blink/renderer/core/html/html_install_element.h" @@ -665,6 +667,53 @@ permission_element->permissionStatus()); } +TEST_F(HTMLCapabilityElementBaseTest, FocusRequiresUserActivation) { + auto* permission_element = CreatePermissionElement("camera"); + EXPECT_EQ(nullptr, GetDocument().FocusedElement()); + + // 1. Script-initiated focus without transient user activation is blocked. + permission_element->Focus(FocusParams()); + EXPECT_EQ(nullptr, GetDocument().FocusedElement()); + + permission_element->Focus(FocusParams(FocusTrigger::kScript)); + EXPECT_EQ(nullptr, GetDocument().FocusedElement()); + + // 2. User-gesture focus succeeds even without transient user activation. + permission_element->Focus(FocusParams(FocusTrigger::kUserGesture)); + EXPECT_EQ(permission_element, GetDocument().FocusedElement()); + + GetDocument().ClearFocusedElement(); + EXPECT_EQ(nullptr, GetDocument().FocusedElement()); + + // 3. Script-initiated focus with transient user activation succeeds. + LocalFrame::NotifyUserActivation( + GetDocument().GetFrame(), + mojom::blink::UserActivationNotificationType::kTest); + permission_element->Focus(FocusParams()); + EXPECT_EQ(permission_element, GetDocument().FocusedElement()); +} + +TEST_F(HTMLCapabilityElementBaseTest, + DialogAutofocusDoesNotFocusWithoutUserActivation) { + SetBodyContent(R"HTML( + <dialog id='d'> + <usermedia id='camera' type='camera' autofocus></usermedia> + </dialog> + )HTML"); + GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); + GetDocument().View()->UpdateAllLifecyclePhasesForTest(); + + auto* dialog = To<HTMLDialogElement>( + GetDocument().QuerySelector(AtomicString("dialog"))); + auto* permission_element = To<HTMLCapabilityElementBase>( + GetDocument().QuerySelector(AtomicString("usermedia"))); + + // Open the dialog programmatically without user activation. + dialog->showModal(ASSERT_NO_EXCEPTION); + EXPECT_TRUE(dialog->IsOpenAndActive()); + EXPECT_NE(permission_element, GetDocument().FocusedElement()); +} + class HTMLCapabilityElementBaseClickingEnabledTest : public HTMLCapabilityElementBaseTest { public: diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/usermedia/no-focus.html b/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/usermedia/no-focus.html index f8acda9..c7bd813 100644 --- a/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/usermedia/no-focus.html +++ b/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/usermedia/no-focus.html @@ -14,14 +14,34 @@ <span tabindex="0" id="focusable_span">This is some text</span> +<dialog id="dialog_without_activation"> + <usermedia id="camera_dialog_no_activation" type="camera" autofocus></usermedia> +</dialog> + +<dialog id="dialog_with_activation"> + <usermedia id="camera_dialog_activation" type="camera" autofocus></usermedia> +</dialog> + <script> promise_test(async(t) => { await new Promise((r) => t.step_timeout(r, PEPC_CLICK_DELAY)); + assert_false(navigator.userActivation.isActive, "Should start without user activation"); focusable_span.focus(); valid_usermedia_element.focus(); assert_equals(document.activeElement, focusable_span, "UserMedia element should not be focused."); + const dialog = document.getElementById('dialog_without_activation'); + const camera = document.getElementById('camera_dialog_no_activation'); + dialog.showModal(); + assert_true(dialog.open); + assert_not_equals(document.activeElement, camera, + "UserMedia element inside dialog should not receive focus without user activation"); + dialog.close(); + }, "UserMedia element is not focusable by script and does not autofocus in dialog without user activation"); + + promise_test(async(t) => { + await new Promise((r) => t.step_timeout(r, PEPC_CLICK_DELAY)); focusable_span.focus(); await test_driver.bless('Focus with user activation', () => { valid_usermedia_element.focus(); @@ -30,6 +50,17 @@ "Focus is allowed with user activation"); focusable_span.focus(); + const dialog = document.getElementById('dialog_with_activation'); + const camera = document.getElementById('camera_dialog_activation'); + await test_driver.bless('Open dialog with user activation', () => { + dialog.showModal(); + }); + assert_true(dialog.open); + assert_equals(document.activeElement, camera, + "UserMedia element inside dialog should receive focus when opened with user activation"); + dialog.close(); + + focusable_span.focus(); actions = new test_driver.Actions() .pointerMove(1, 1, {origin: valid_usermedia_element}) .pointerDown() @@ -41,6 +72,6 @@ focusable_span.focus(); assert_equals(document.activeElement, focusable_span, "Other element should be focused"); -}, "UserMedia element is not focusable by script without user activation"); + }, "UserMedia element and dialog autofocus are allowed with user activation"); </script> </body>
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/third_party/blink/renderer/core/html/html_capability_element_base_test.cc b/third_party/blink/renderer/core/html/html_capability_element_base_test.cc
index bcaca0a6..13d58e0 100644
--- a/third_party/blink/renderer/core/html/html_capability_element_base_test.cc
+++ b/third_party/blink/renderer/core/html/html_capability_element_base_test.cc
@@ -24,9 +24,11 @@
#include "third_party/blink/renderer/core/css/properties/longhands.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/document_init.h"
+#include "third_party/blink/renderer/core/dom/focus_params.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/geometry/dom_rect.h"
+#include "third_party/blink/renderer/core/html/html_dialog_element.h"
#include "third_party/blink/renderer/core/html/html_geolocation_element.h"
#include "third_party/blink/renderer/core/html/html_iframe_element.h"
#include "third_party/blink/renderer/core/html/html_install_element.h"
@@ -665,6 +667,53 @@
permission_element->permissionStatus());
}
+TEST_F(HTMLCapabilityElementBaseTest, FocusRequiresUserActivation) {
+ auto* permission_element = CreatePermissionElement("camera");
+ EXPECT_EQ(nullptr, GetDocument().FocusedElement());
+
+ // 1. Script-initiated focus without transient user activation is blocked.
+ permission_element->Focus(FocusParams());
+ EXPECT_EQ(nullptr, GetDocument().FocusedElement());
+
+ permission_element->Focus(FocusParams(FocusTrigger::kScript));
+ EXPECT_EQ(nullptr, GetDocument().FocusedElement());
+
+ // 2. User-gesture focus succeeds even without transient user activation.
+ permission_element->Focus(FocusParams(FocusTrigger::kUserGesture));
+ EXPECT_EQ(permission_element, GetDocument().FocusedElement());
+
+ GetDocument().ClearFocusedElement();
+ EXPECT_EQ(nullptr, GetDocument().FocusedElement());
+
+ // 3. Script-initiated focus with transient user activation succeeds.
+ LocalFrame::NotifyUserActivation(
+ GetDocument().GetFrame(),
+ mojom::blink::UserActivationNotificationType::kTest);
+ permission_element->Focus(FocusParams());
+ EXPECT_EQ(permission_element, GetDocument().FocusedElement());
+}
+
+TEST_F(HTMLCapabilityElementBaseTest,
+ DialogAutofocusDoesNotFocusWithoutUserActivation) {
+ SetBodyContent(R"HTML(
+ <dialog id='d'>
+ <usermedia id='camera' type='camera' autofocus></usermedia>
+ </dialog>
+ )HTML");
+ GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
+ GetDocument().View()->UpdateAllLifecyclePhasesForTest();
+
+ auto* dialog = To<HTMLDialogElement>(
+ GetDocument().QuerySelector(AtomicString("dialog")));
+ auto* permission_element = To<HTMLCapabilityElementBase>(
+ GetDocument().QuerySelector(AtomicString("usermedia")));
+
+ // Open the dialog programmatically without user activation.
+ dialog->showModal(ASSERT_NO_EXCEPTION);
+ EXPECT_TRUE(dialog->IsOpenAndActive());
+ EXPECT_NE(permission_element, GetDocument().FocusedElement());
+}
+
class HTMLCapabilityElementBaseClickingEnabledTest
: public HTMLCapabilityElementBaseTest {
public:
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/usermedia/no-focus.html b/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/usermedia/no-focus.html
index f8acda9..c7bd813 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/usermedia/no-focus.html
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/usermedia/no-focus.html
@@ -14,14 +14,34 @@
<span tabindex="0" id="focusable_span">This is some text</span>
+<dialog id="dialog_without_activation">
+ <usermedia id="camera_dialog_no_activation" type="camera" autofocus></usermedia>
+</dialog>
+
+<dialog id="dialog_with_activation">
+ <usermedia id="camera_dialog_activation" type="camera" autofocus></usermedia>
+</dialog>
+
<script>
promise_test(async(t) => {
await new Promise((r) => t.step_timeout(r, PEPC_CLICK_DELAY));
+ assert_false(navigator.userActivation.isActive, "Should start without user activation");
focusable_span.focus();
valid_usermedia_element.focus();
assert_equals(document.activeElement, focusable_span,
"UserMedia element should not be focused.");
+ const dialog = document.getElementById('dialog_without_activation');
+ const camera = document.getElementById('camera_dialog_no_activation');
+ dialog.showModal();
+ assert_true(dialog.open);
+ assert_not_equals(document.activeElement, camera,
+ "UserMedia element inside dialog should not receive focus without user activation");
+ dialog.close();
+ }, "UserMedia element is not focusable by script and does not autofocus in dialog without user activation");
+
+ promise_test(async(t) => {
+ await new Promise((r) => t.step_timeout(r, PEPC_CLICK_DELAY));
focusable_span.focus();
await test_driver.bless('Focus with user activation', () => {
valid_usermedia_element.focus();
@@ -30,6 +50,17 @@
"Focus is allowed with user activation");
focusable_span.focus();
+ const dialog = document.getElementById('dialog_with_activation');
+ const camera = document.getElementById('camera_dialog_activation');
+ await test_driver.bless('Open dialog with user activation', () => {
+ dialog.showModal();
+ });
+ assert_true(dialog.open);
+ assert_equals(document.activeElement, camera,
+ "UserMedia element inside dialog should receive focus when opened with user activation");
+ dialog.close();
+
+ focusable_span.focus();
actions = new test_driver.Actions()
.pointerMove(1, 1, {origin: valid_usermedia_element})
.pointerDown()
@@ -41,6 +72,6 @@
focusable_span.focus();
assert_equals(document.activeElement, focusable_span,
"Other element should be focused");
-}, "UserMedia element is not focusable by script without user activation");
+ }, "UserMedia element and dialog autofocus are allowed with user activation");
</script>
</body>
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