Chrome · Editing
CVE-2026-113227
UAF in Editing
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
TESTthird_party/blink/renderer/core/editing/commands/clipboard_commands_test.cc |
modified |
Files Changed
third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/script/call_function/user_activation.pychrome/browser/extensions/content_capabilities_browsertest.ccthird_party/blink/web_tests/external/wpt/webdriver/tests/bidi/script/evaluate/user_activation.pythird_party/blink/renderer/core/editing/commands/clipboard_commands.ccthird_party/blink/renderer/core/editing/commands/clipboard_commands_test.cc
Patch
From 77750ea9a9fd3a226c8e9581c2e18fcc147d9f06 Mon Sep 17 00:00:00 2001 From: Gaston Rodriguez <[email protected]> Date: Thu, 01 Oct 2026 13:18:35 -0700 Subject: [PATCH] Reland "Add focus requirement to insert content into clipboard" This is a reland of commit c50edc4214d81e4f944c3415d55104a49e46d214 The original CL was reverted due to tests that failed with the new focus requirement which did not run on my CQ Dry Run. I made the tests require focus now, and they pass. Added (win/linux/mac)-webdriver-bidi-rel bot runs to CQ to ensure there are no other missing tests that would fail with these changes. Original change's description: > Add focus requirement to insert content into clipboard > > Currently, focus is not required to insert content into the clipboard > under certain circumstances (running on x11). The new async clipboard > API requires focus for a script to inject content into the clipboard. > > This seems reasonable since a webpage may require you to open a second > window to process additional input (like a SSO page), and injecting > content from an unfocused page could be a vector of attack to an > unassuming user. > > This CL adds the requirement for focus on the non-async path and a unit > test that verifies that this is enforced. > > Additionally, I was able to repro the issue on the provided html in the > linked bug and verified that this CL patches the repro. > > Bug: 563021080 > Change-Id: I928f5769e75f4c3d1c4ba9e69976f14dd85c50e6 > Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8422732 > Reviewed-by: Dan Clark <[email protected]> > Reviewed-by: James Cook <[email protected]> > Commit-Queue: Gaston Rodriguez <[email protected]> > Cr-Commit-Position: refs/heads/main@{#1706539} Bug: 563021080 Change-Id: I6314dcff954ec3a8a004742d5873d346f39ee253 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8488666 Commit-Queue: Gaston Rodriguez <[email protected]> Reviewed-by: James Cook <[email protected]> Reviewed-by: Dan Clark <[email protected]> Cr-Commit-Position: refs/heads/main@{#1709342} --- diff --git a/third_party/blink/renderer/core/editing/commands/clipboard_commands.cc b/third_party/blink/renderer/core/editing/commands/clipboard_commands.cc index 6706ec48..2168cf76 100644 --- a/third_party/blink/renderer/core/editing/commands/clipboard_commands.cc +++ b/third_party/blink/renderer/core/editing/commands/clipboard_commands.cc @@ -162,8 +162,12 @@ if (source == EditorCommandSource::kMenuOrKeyBinding) return true; Settings* const settings = frame.GetSettings(); - if ((settings && settings->GetJavaScriptCanAccessClipboard()) || - LocalFrame::HasTransientUserActivation(&frame)) { + if (settings && settings->GetJavaScriptCanAccessClipboard()) { + return true; + } + if (LocalFrame::HasTransientUserActivation(&frame) && + ((settings && settings->GetClipboardFocusExempt()) || + frame.GetDocument()->hasFocus())) { return true; } return frame.GetContentSettingsClient() && diff --git a/chrome/browser/extensions/content_capabilities_browsertest.cc b/chrome/browser/extensions/content_capabilities_browsertest.cc index 4c35b612..d8223c6 100644 --- a/chrome/browser/extensions/content_capabilities_browsertest.cc +++ b/chrome/browser/extensions/content_capabilities_browsertest.cc @@ -247,11 +247,13 @@ EXPECT_TRUE( CanWriteClipboardInAboutBlankFrame(extension.get(), GetTestURLFor("foo.example.com"))); - // TODO(dcheng): This should be false, but we cannot currently execute testing - // script without a user gesture. + // EvalJs supplies a user gesture, so the focused top-level document can + // write despite not having the clipboardWrite capability. EXPECT_TRUE( CanWriteClipboard(extension.get(), GetTestURLFor("bar.example.com"))); - EXPECT_TRUE(CanWriteClipboardInAboutBlankFrame( + // The about:blank child is not focused, so transient user activation alone + // does not allow it to write without the clipboardWrite capability. + EXPECT_FALSE(CanWriteClipboardInAboutBlankFrame( extension.get(), GetTestURLFor("bar.example.com"))); EXPECT_FALSE( diff --git a/third_party/blink/renderer/core/editing/commands/clipboard_commands_test.cc b/third_party/blink/renderer/core/editing/commands/clipboard_commands_test.cc index 6548b6b..3fce2b9b 100644 --- a/third_party/blink/renderer/core/editing/commands/clipboard_commands_test.cc +++ b/third_party/blink/renderer/core/editing/commands/clipboard_commands_test.cc @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "third_party/blink/renderer/core/editing/commands/clipboard_commands.h" + #include <memory> #include "base/memory/raw_ptr.h" @@ -16,9 +18,11 @@ #include "third_party/blink/renderer/core/editing/visible_selection.h" #include "third_party/blink/renderer/core/event_type_names.h" #include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/frame/settings.h" #include "third_party/blink/renderer/core/html/html_element.h" #include "third_party/blink/renderer/core/html_names.h" #include "third_party/blink/renderer/core/keywords.h" +#include "third_party/blink/renderer/core/page/focus_controller.h" #include "third_party/blink/renderer/core/testing/dummy_page_holder.h" #include "third_party/blink/renderer/core/testing/mock_clipboard_host.h" #include "third_party/blink/renderer/core/testing/page_test_base.h" @@ -154,4 +158,36 @@ ExpectDefaultPasteResult(&event_type_names::kBeforeinput, ""); } +TEST(ClipboardCommandsCopyTest, DomWriteRequiresFocusedDocumentUnlessExempt) { + test::TaskEnvironment task_environment; + auto page_holder = std::make_unique<DummyPageHolder>(gfx::Size(1, 1)); + LocalFrame& frame = page_holder->GetFrame(); + FocusController& focus_controller = + page_holder->GetPage().GetFocusController(); + focus_controller.SetActive(true); + focus_controller.SetFocused(true); + LocalFrame::NotifyUserActivation( + &frame, mojom::blink::UserActivationNotificationType::kTest); + + ASSERT_TRUE(LocalFrame::HasTransientUserActivation(&frame)); + EXPECT_TRUE( + ClipboardCommands::CanWriteClipboard(frame, EditorCommandSource::kDom)); + + focus_controller.SetFocused(false); + + // Verify that a webpage can't write to the clipboard when the page is + // unfocused. + EXPECT_TRUE(LocalFrame::HasTransientUserActivation(&frame)); + EXPECT_FALSE( + ClipboardCommands::CanWriteClipboard(frame, EditorCommandSource::kDom)); + EXPECT_TRUE(ClipboardCommands::CanWriteClipboard( + frame, EditorCommandSource::kMenuOrKeyBinding)); + + // Verify that a webpage can write to the clipboard if the page is unfocused + // but it is focus exempt. + frame.GetSettings()->SetClipboardFocusExempt(true); + EXPECT_TRUE( + ClipboardCommands::CanWriteClipboard(frame, EditorCommandSource::kDom)); +} + } // namespace blink diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/script/call_function/user_activation.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/script/call_function/user_activation.py index b7e90afa..cbf9c8a 100644 --- a/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/script/call_function/user_activation.py +++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/script/call_function/user_activation.py @@ -31,6 +31,10 @@ top_context["context"]), await_promise=False) + # Clipboard writes require document focus independently of user activation. + await bidi_session.browsing_context.activate( + context=top_context["context"]) + result = await bidi_session.script.call_function( function_declaration= "() => document.body.appendChild(document.createTextNode('test')) && " + diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/script/evaluate/user_activation.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/script/evaluate/user_activation.py index 273832f..38d68b8 100644 --- a/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/script/evaluate/user_activation.py +++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/script/evaluate/user_activation.py @@ -31,6 +31,10 @@ top_context["context"]), await_promise=False) + # Clipboard writes require document focus independently of user activation. + await bidi_session.browsing_context.activate( + context=top_context["context"]) + result = await bidi_session.script.evaluate( expression= "document.body.appendChild(document.createTextNode('test')) && document.execCommand('selectAll') && document.execCommand('copy')",
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/chrome/browser/extensions/content_capabilities_browsertest.cc b/chrome/browser/extensions/content_capabilities_browsertest.cc
index 4c35b612..d8223c6 100644
--- a/chrome/browser/extensions/content_capabilities_browsertest.cc
+++ b/chrome/browser/extensions/content_capabilities_browsertest.cc
@@ -247,11 +247,13 @@
EXPECT_TRUE(
CanWriteClipboardInAboutBlankFrame(extension.get(),
GetTestURLFor("foo.example.com")));
- // TODO(dcheng): This should be false, but we cannot currently execute testing
- // script without a user gesture.
+ // EvalJs supplies a user gesture, so the focused top-level document can
+ // write despite not having the clipboardWrite capability.
EXPECT_TRUE(
CanWriteClipboard(extension.get(), GetTestURLFor("bar.example.com")));
- EXPECT_TRUE(CanWriteClipboardInAboutBlankFrame(
+ // The about:blank child is not focused, so transient user activation alone
+ // does not allow it to write without the clipboardWrite capability.
+ EXPECT_FALSE(CanWriteClipboardInAboutBlankFrame(
extension.get(), GetTestURLFor("bar.example.com")));
EXPECT_FALSE(
diff --git a/third_party/blink/renderer/core/editing/commands/clipboard_commands_test.cc b/third_party/blink/renderer/core/editing/commands/clipboard_commands_test.cc
index 6548b6b..3fce2b9b 100644
--- a/third_party/blink/renderer/core/editing/commands/clipboard_commands_test.cc
+++ b/third_party/blink/renderer/core/editing/commands/clipboard_commands_test.cc
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include "third_party/blink/renderer/core/editing/commands/clipboard_commands.h"
+
#include <memory>
#include "base/memory/raw_ptr.h"
@@ -16,9 +18,11 @@
#include "third_party/blink/renderer/core/editing/visible_selection.h"
#include "third_party/blink/renderer/core/event_type_names.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
+#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/html/html_element.h"
#include "third_party/blink/renderer/core/html_names.h"
#include "third_party/blink/renderer/core/keywords.h"
+#include "third_party/blink/renderer/core/page/focus_controller.h"
#include "third_party/blink/renderer/core/testing/dummy_page_holder.h"
#include "third_party/blink/renderer/core/testing/mock_clipboard_host.h"
#include "third_party/blink/renderer/core/testing/page_test_base.h"
@@ -154,4 +158,36 @@
ExpectDefaultPasteResult(&event_type_names::kBeforeinput, "");
}
+TEST(ClipboardCommandsCopyTest, DomWriteRequiresFocusedDocumentUnlessExempt) {
+ test::TaskEnvironment task_environment;
+ auto page_holder = std::make_unique<DummyPageHolder>(gfx::Size(1, 1));
+ LocalFrame& frame = page_holder->GetFrame();
+ FocusController& focus_controller =
+ page_holder->GetPage().GetFocusController();
+ focus_controller.SetActive(true);
+ focus_controller.SetFocused(true);
+ LocalFrame::NotifyUserActivation(
+ &frame, mojom::blink::UserActivationNotificationType::kTest);
+
+ ASSERT_TRUE(LocalFrame::HasTransientUserActivation(&frame));
+ EXPECT_TRUE(
+ ClipboardCommands::CanWriteClipboard(frame, EditorCommandSource::kDom));
+
+ focus_controller.SetFocused(false);
+
+ // Verify that a webpage can't write to the clipboard when the page is
+ // unfocused.
+ EXPECT_TRUE(LocalFrame::HasTransientUserActivation(&frame));
+ EXPECT_FALSE(
+ ClipboardCommands::CanWriteClipboard(frame, EditorCommandSource::kDom));
+ EXPECT_TRUE(ClipboardCommands::CanWriteClipboard(
+ frame, EditorCommandSource::kMenuOrKeyBinding));
+
+ // Verify that a webpage can write to the clipboard if the page is unfocused
+ // but it is focus exempt.
+ frame.GetSettings()->SetClipboardFocusExempt(true);
+ EXPECT_TRUE(
+ ClipboardCommands::CanWriteClipboard(frame, EditorCommandSource::kDom));
+}
+
} // namespace blink
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/script/call_function/user_activation.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/script/call_function/user_activation.py
index b7e90afa..cbf9c8a 100644
--- a/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/script/call_function/user_activation.py
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/script/call_function/user_activation.py
@@ -31,6 +31,10 @@
top_context["context"]),
await_promise=False)
+ # Clipboard writes require document focus independently of user activation.
+ await bidi_session.browsing_context.activate(
+ context=top_context["context"])
+
result = await bidi_session.script.call_function(
function_declaration=
"() => document.body.appendChild(document.createTextNode('test')) && " +
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/script/evaluate/user_activation.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/script/evaluate/user_activation.py
index 273832f..38d68b8 100644
--- a/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/script/evaluate/user_activation.py
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/script/evaluate/user_activation.py
@@ -31,6 +31,10 @@
top_context["context"]),
await_promise=False)
+ # Clipboard writes require document focus independently of user activation.
+ await bidi_session.browsing_context.activate(
+ context=top_context["context"])
+
result = await bidi_session.script.evaluate(
expression=
"document.body.appendChild(document.createTextNode('test')) && document.execCommand('selectAll') && document.execCommand('copy')",
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