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 <gastonr@microsoft.com>
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 <daniec@microsoft.com>
> Reviewed-by: James Cook <jamescook@chromium.org>
> Commit-Queue: Gaston Rodriguez <gastonr@microsoft.com>
> 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 <gastonr@microsoft.com>
Reviewed-by: James Cook <jamescook@chromium.org>
Reviewed-by: Dan Clark <daniec@microsoft.com>
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