CVE-2026-13810
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
TEST_Fthird_party/blink/renderer/core/exported/web_view_test.cc |
modified |
Files Changed
third_party/blink/renderer/core/exported/web_input_method_controller_impl.ccthird_party/blink/renderer/core/exported/web_view_test.cc
Patch
From 3be8f92c7e8368b5454a9392da0fdbbc9b39bc74 Mon Sep 17 00:00:00 2001
From: Tom Anderson <thomasanderson@chromium.org>
Date: Mon, 01 Jun 2026 17:42:19 -0700
Subject: [PATCH] [Blink] Guard user activation against empty IME events
On Linux platforms, window focus events trigger the display server or
compositor (via IME frameworks like IBus/Fcitx or zwp_text_input_v3) to
send state-clearing preedit/reset events with an empty string.
Historically, WebInputMethodControllerImpl::SetComposition and
CommitText unconditionally triggered local frame UserActivation (with
kInteraction), allowing window focus/creation to erroneously grant
transient and sticky user activation. This bypassed user gesture
security checks and enabled silent password exfiltration under XSS.
This CL prevents user activation from triggering when the incoming IME
text is empty. We also add a unit test to verify that empty IME
composition and commit events do not elevate user activation, while
non-empty events do.
Bug: 504600482
Change-Id: I82444a21888d4e61fe1a8cf75871ed47b18d1aef
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7868631
Commit-Queue: Thomas Anderson <thomasanderson@chromium.org>
Auto-Submit: Thomas Anderson <thomasanderson@chromium.org>
Reviewed-by: Ian Kilpatrick <ikilpatrick@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1639820}
---
diff --git a/third_party/blink/renderer/core/exported/web_input_method_controller_impl.cc b/third_party/blink/renderer/core/exported/web_input_method_controller_impl.cc
index e7b51b9..03e4553 100644
--- a/third_party/blink/renderer/core/exported/web_input_method_controller_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_input_method_controller_impl.cc
@@ -97,8 +97,10 @@
return false;
}
- LocalFrame::NotifyUserActivation(
- GetFrame(), mojom::blink::UserActivationNotificationType::kInteraction);
+ if (!text.IsEmpty()) {
+ LocalFrame::NotifyUserActivation(
+ GetFrame(), mojom::blink::UserActivationNotificationType::kInteraction);
+ }
GetInputMethodController().SetComposition(
String(text), ImeTextSpanVectorBuilder::Build(ime_text_spans),
@@ -152,8 +154,10 @@
const std::vector<ui::ImeTextSpan>& ime_text_spans,
const WebRange& replacement_range,
int relative_caret_position) {
- LocalFrame::NotifyUserActivation(
- GetFrame(), mojom::blink::UserActivationNotificationType::kInteraction);
+ if (!text.IsEmpty()) {
+ LocalFrame::NotifyUserActivation(
+ GetFrame(), mojom::blink::UserActivationNotificationType::kInteraction);
+ }
if (IsEditContextActive()) {
return GetInputMethodController().GetActiveEditContext()->CommitText(
diff --git a/third_party/blink/renderer/core/exported/web_view_test.cc b/third_party/blink/renderer/core/exported/web_view_test.cc
index 388f01c0..a59f6825 100644
--- a/third_party/blink/renderer/core/exported/web_view_test.cc
+++ b/third_party/blink/renderer/core/exported/web_view_test.cc
@@ -1229,6 +1229,54 @@
WebInputMethodController::kKeepSelection);
}
+TEST_F(WebViewTest, IMECompositionAndCommitUserActivation) {
+ RegisterMockedHttpURLLoad("input_field_default.html");
+ WebViewImpl* web_view = web_view_helper_.InitializeAndLoad(
+ base_url_ + "input_field_default.html");
+ web_view->MainFrameImpl()->GetFrame()->SetInitialFocus(false);
+
+ LocalFrame* frame = web_view->MainFrameImpl()->GetFrame();
+ EXPECT_FALSE(LocalFrame::HasTransientUserActivation(frame));
+ EXPECT_FALSE(frame->HasStickyUserActivation());
+
+ WebInputMethodController* active_input_method_controller =
+ web_view->MainFrameImpl()
+ ->FrameWidget()
+ ->GetActiveWebInputMethodController();
+
+ std::vector<ui::ImeTextSpan> empty_ime_text_spans;
+
+ // 1. Calling SetComposition with an empty string should NOT trigger user
+ // activation.
+ active_input_method_controller->SetComposition(
+ WebString::FromUtf8(""), empty_ime_text_spans, WebRange(), 0, 0);
+ EXPECT_FALSE(LocalFrame::HasTransientUserActivation(frame));
+ EXPECT_FALSE(frame->HasStickyUserActivation());
+
+ // 2. Calling SetComposition with a non-empty string SHOULD trigger user
+ // activation.
+ active_input_method_controller->SetComposition(
+ WebString::FromUtf8("hello"), empty_ime_text_spans, WebRange(), 5, 5);
+ EXPECT_TRUE(LocalFrame::HasTransientUserActivation(frame));
+ EXPECT_TRUE(frame->HasStickyUserActivation());
+
+ // Consume transient activation for the next steps.
+ LocalFrame::ConsumeTransientUserActivation(frame);
+ EXPECT_FALSE(LocalFrame::HasTransientUserActivation(frame));
+
+ // 3. Calling CommitText with an empty string should NOT trigger user
+ // activation.
+ active_input_method_controller->CommitText(
+ WebString::FromUtf8(""), empty_ime_text_spans, WebRange(), 0);
+ EXPECT_FALSE(LocalFrame::HasTransientUserActivation(frame));
+
+ // 4. Calling CommitText with a non-empty string SHOULD trigger user
+ // activation.
+ active_input_method_controller->CommitText(
+ WebString::FromUtf8("world"), empty_ime_text_spans, WebRange(), 0);
+ EXPECT_TRUE(LocalFrame::HasTransientUserActivation(frame));
+}
+
// Regression test for https://crbug.com/873999
TEST_F(WebViewTest, LongPressOutsideInputShouldNotSelectPlaceholderText) {
RegisterMockedHttpURLLoad("input_placeholder.html");
Regression Test / PoC
diff --git a/third_party/blink/renderer/core/exported/web_view_test.cc b/third_party/blink/renderer/core/exported/web_view_test.cc
index 388f01c0..a59f6825 100644
--- a/third_party/blink/renderer/core/exported/web_view_test.cc
+++ b/third_party/blink/renderer/core/exported/web_view_test.cc
@@ -1229,6 +1229,54 @@
WebInputMethodController::kKeepSelection);
}
+TEST_F(WebViewTest, IMECompositionAndCommitUserActivation) {
+ RegisterMockedHttpURLLoad("input_field_default.html");
+ WebViewImpl* web_view = web_view_helper_.InitializeAndLoad(
+ base_url_ + "input_field_default.html");
+ web_view->MainFrameImpl()->GetFrame()->SetInitialFocus(false);
+
+ LocalFrame* frame = web_view->MainFrameImpl()->GetFrame();
+ EXPECT_FALSE(LocalFrame::HasTransientUserActivation(frame));
+ EXPECT_FALSE(frame->HasStickyUserActivation());
+
+ WebInputMethodController* active_input_method_controller =
+ web_view->MainFrameImpl()
+ ->FrameWidget()
+ ->GetActiveWebInputMethodController();
+
+ std::vector<ui::ImeTextSpan> empty_ime_text_spans;
+
+ // 1. Calling SetComposition with an empty string should NOT trigger user
+ // activation.
+ active_input_method_controller->SetComposition(
+ WebString::FromUtf8(""), empty_ime_text_spans, WebRange(), 0, 0);
+ EXPECT_FALSE(LocalFrame::HasTransientUserActivation(frame));
+ EXPECT_FALSE(frame->HasStickyUserActivation());
+
+ // 2. Calling SetComposition with a non-empty string SHOULD trigger user
+ // activation.
+ active_input_method_controller->SetComposition(
+ WebString::FromUtf8("hello"), empty_ime_text_spans, WebRange(), 5, 5);
+ EXPECT_TRUE(LocalFrame::HasTransientUserActivation(frame));
+ EXPECT_TRUE(frame->HasStickyUserActivation());
+
+ // Consume transient activation for the next steps.
+ LocalFrame::ConsumeTransientUserActivation(frame);
+ EXPECT_FALSE(LocalFrame::HasTransientUserActivation(frame));
+
+ // 3. Calling CommitText with an empty string should NOT trigger user
+ // activation.
+ active_input_method_controller->CommitText(
+ WebString::FromUtf8(""), empty_ime_text_spans, WebRange(), 0);
+ EXPECT_FALSE(LocalFrame::HasTransientUserActivation(frame));
+
+ // 4. Calling CommitText with a non-empty string SHOULD trigger user
+ // activation.
+ active_input_method_controller->CommitText(
+ WebString::FromUtf8("world"), empty_ime_text_spans, WebRange(), 0);
+ EXPECT_TRUE(LocalFrame::HasTransientUserActivation(frame));
+}
+
// Regression test for https://crbug.com/873999
TEST_F(WebViewTest, LongPressOutsideInputShouldNotSelectPlaceholderText) {
RegisterMockedHttpURLLoad("input_placeholder.html");
Original Bug Report
Linux Chromium incorrectly triggers userActivation on focus. This allows XSS to silently harvest saved passwords on page load without clicks, bypassing mandatory user gesture security checks.
Report description
Linux Chromium incorrectly triggers userActivation on focus. This allows XSS to silently harvest saved passwords on page load without clicks, bypassing mandatory user gesture security checks.
Bug location
Where do you want to report your vulnerability?
Chrome VRP – Report security issues affecting the Chrome browser. See program rules
The problem
Please describe the technical details of the vulnerability
A platform-specific security regression in Chromium’s Linux implementation allows for Zero-Click Credential Harvesting. On Linux (Wayland/X11), the browser erroneously promotes the frame’s UserActivation state to true upon window focus/creation. This bypasses the mandatory “User Gesture” requirement, allowing a malicious script—delivered via XSS—to programmatically read and exfiltrate saved passwords from the autofill manager without any human interaction.
echnical Details
- User Activation v2 (UAv2) Security Model
Modern browsers rely on the User Activation v2 model to gate sensitive APIs. This model tracks two specific states for every frame:
hasBeenActive (Sticky): Indicates if the user has ever interacted with the page.
isActive (Transient): Indicates if there was a very recent interaction (within ~300ms).
Chromium’s password manager is designed to protect autofilled data by preventing JavaScript from accessing the .value property of a password field unless a Transient Activation is present. This is intended to ensure that a user must at least click or type before a script can “scrape” the field. 2. The Failure Point: Ozone Display Layer
On Linux, Chromium uses the Ozone abstraction layer to communicate with display servers (Wayland or X11). The vulnerability lies in how Ozone handles Window Focus Events.
In tiling compositors like Hyprland or desktop environments on Kali Linux, the browser receives a signal (e.g., xdg_toplevel.configure or FocusIn) the moment the window is spawned and focused. The Chromium Linux backend incorrectly maps these compositor-level focus events to a User Activation signal. Because the browser process “trusts” these signals as a proxy for user intent, it prematurely sets the frame’s activation state to Active. 3. Exploitation via Race Condition
When a URL-based XSS payload (like your <img src=x onerror="…">) is executed, it runs almost simultaneously with the window’s creation.
Launch: The browser process starts and opens the URL.
Focus: The window manager (Hyprland/X11) focuses the new window.
Bypass: Chromium receives the focus event and incorrectly "flips" the hasBeenActive and isActive bits to true.
Exfiltration: The script inside the onerror attribute checks the value of the autofilled password field. Because the activation state is now true, the browser permits the script to read the password string instead of returning an empty value.
- Comparison to Hardened Platforms
On Windows, Chromium uses low-level Win32 Hooks that listen specifically for hardware interrupts (physical mouse/keyboard events). It distinguishes between a window gaining focus and a user clicking on a window. Since the Linux implementation lacks this hardware-level verification for the activation state, it remains susceptible to “Zero-Click” exfiltration that is effectively impossible on Windows and macOS.
Impact analysis
- Attacker Profile
Any malicious actor capable of finding and exploiting a Reflected or Stored XSS on a target domain, or an actor deploying Malvertising through a compromised ad network, can exploit this vulnerability. The attack requires no specialized access, only the ability to execute JavaScript in the context of the vulnerable origin. 2. Assets Compromised
The primary impact is the unauthorized exfiltration of clear-text credentials (usernames and passwords) saved in the Chromium Password Manager. This can lead to immediate account takeover (ATO) on the affected domain. 3. Exploitation Severity: Zero-Click
The severity of this finding is high because it removes the “User Gesture” requirement—the critical defense-in-depth layer meant to prevent automated harvesting. By transforming a standard XSS into a Zero-Click credential thief, the vulnerability allows for:
Silent Exploitation: Victims are not required to click, scroll, or interact with the page to be compromised. A simple page load or the rendering of a malicious advertisement is sufficient.
Mass Harvesting: Attackers can automate the silent collection of credentials from every Linux-based Chromium user who visits a compromised page.
- Scope and Platform Regression
This issue represents a significant platform-specific security regression. While Chromium on Windows and macOS successfully gates password .value access behind physical user interactions, this logic failure leaves Linux Chromium users (specifically those in Wayland/X11 environments) uniquely vulnerable to silent identity theft that is effectively blocked on other operating systems.
The cause
What version of Chrome have you found the security issue in?
Google Chrome 147.0.7727.101
Is the security issue related to a crash?
No, it is not related to a crash.
Choose the type of vulnerability
Sensitive data exposure