Chrome · Input
CVE-2026-18007
Logic Error in Input
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcomponents/input/web_input_event_builders_android.cc |
modified | |
TESTcomponents/input/web_input_event_builders_android_unittest.cc |
modified |
Files Changed
components/input/web_input_event_builders_android.cccomponents/input/web_input_event_builders_android_unittest.cc
Patch
From 19dbe763d92833bd30bfd96e3cfcf8c4058fe16b Mon Sep 17 00:00:00 2001 From: Tzarial <[email protected]> Date: Thu, 25 Jun 2026 08:38:02 -0700 Subject: [PATCH] [agy][input] Fix key encoding On Android, physical hardware keyboards delivering supplementary-plane characters (e.g. emojis >= U+10000) are truncated in the Build() path of WebKeyboardEventBuilder because of an implicit narrowing cast from int to char16_t when assigning to result.unmodified_text[0]. This causes a divergence where JS event.key is correct (since dom_key retains the full 21-bit scalar), but the text[] field receives only the low 16 bits. Since the password-reuse detector (PhishGuard) uses text[] for comparison, it fails to match saved credentials, enabling a phishing bypass. This CL fixes the issue by encoding supplementary-plane characters as UTF-16 surrogate pairs into unmodified_text and copying the whole array to text. Test: components_unittests Change-Id: I2317dba06c61614423b26bf806881d4fff5f9509 Fixed: 522404101 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7994938 Commit-Queue: Tzarial <[email protected]> Reviewed-by: Aman Verma <[email protected]> Cr-Commit-Position: refs/heads/main@{#1652453} --- diff --git a/components/input/web_input_event_builders_android.cc b/components/input/web_input_event_builders_android.cc index 4c4264c6..7cbd1ac 100644 --- a/components/input/web_input_event_builders_android.cc +++ b/components/input/web_input_event_builders_android.cc @@ -7,6 +7,8 @@ #include <android/input.h> #include "base/check.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversion_utils.h" #include "base/time/time.h" #include "base/trace_event/trace_event.h" #include "ui/events/android/key_event_utils.h" @@ -144,14 +146,19 @@ result.dom_code = static_cast<int>(dom_code); result.dom_key = GetDomKeyFromEvent(env, android_key_event, keycode, modifiers, unicode_character); - result.unmodified_text[0] = unicode_character; + std::u16string unmodified_text_str; + if (unicode_character) { + base::WriteUnicodeCharacter(unicode_character, &unmodified_text_str); + } if (result.windows_key_code == ui::VKEY_RETURN) { // This is the same behavior as GTK: // We need to treat the enter key as a key press of character \r. This // is apparently just how webkit handles it and what it expects. - result.unmodified_text[0] = '\r'; + unmodified_text_str = u"\r"; } - result.text[0] = result.unmodified_text[0]; + base::u16cstrlcpy(result.unmodified_text.data(), unmodified_text_str.c_str(), + result.unmodified_text.size()); + result.text = result.unmodified_text; result.is_system_key = is_system_key; result.is_confirmed_physical_keyboard_input = IsConfirmedPhysicalKeyboardEvent(env, android_key_event); diff --git a/components/input/web_input_event_builders_android_unittest.cc b/components/input/web_input_event_builders_android_unittest.cc index cd9fdce..ca6abc2 100644 --- a/components/input/web_input_event_builders_android_unittest.cc +++ b/components/input/web_input_event_builders_android_unittest.cc @@ -177,6 +177,38 @@ EXPECT_EQ(ui::DomKey::UNIDENTIFIED, ui::DomKey(web_event.dom_key)); } +// Verifies that supplementary-plane code points (e.g. emojis or extra-plane +// chars) are encoded as surrogate pairs in unmodified_text and text, matching +// dom_key and avoiding truncation / narrowing. +TEST(WebInputEventBuilderAndroidTest, SupplementaryPlaneTextVsDomKeyMatch) { + constexpr int kCodePoint = 0x1F600; // U+1F600 GRINNING FACE + + // Use the synthetic-event form (env=nullptr) so the test doesn't depend on a + // device KCM; |unicode_character| is supplied directly, mirroring what + // ImeAdapterImpl.sendKeyEvent forwards from KeyEvent.getUnicodeChar(). + WebKeyboardEvent web_event = input::WebKeyboardEventBuilder::Build( + /*env=*/nullptr, /*android_key_event=*/nullptr, + WebKeyboardEvent::Type::kKeyDown, /*modifiers=*/0, + blink::WebInputEvent::GetStaticTimeStampForTests(), + /*keycode=*/AKEYCODE_A, /*scancode=*/0, + /*unicode_character=*/kCodePoint, + /*is_system_key=*/false); + + // dom_key preserves the full 21-bit scalar -> JS event.key will be "😀". + EXPECT_EQ(ui::DomKey::FromCharacter(kCodePoint), + ui::DomKey(web_event.dom_key)) + << "dom_key must hold the full code point"; + std::string js_event_key = + ui::KeycodeConverter::DomKeyToKeyString(ui::DomKey(web_event.dom_key)); + EXPECT_EQ("\xF0\x9F\x98\x80", js_event_key); // UTF-8 of U+1F600 + + // text[] now holds the properly encoded UTF-16 surrogate pair. + std::u16string text(web_event.text.data()); + EXPECT_EQ(2u, text.size()) << "text[] must hold the 2 surrogate code units"; + EXPECT_EQ(std::u16string(u"\U0001F600"), text) + << "text[] must match the saved-password encoding (surrogate pair)"; +} + // Testing new Android keycode introduced in API 24. TEST(WebInputEventBuilderAndroidTest, CutCopyPasteKey) { JNIEnv* env = AttachCurrentThread();
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/components/input/web_input_event_builders_android_unittest.cc b/components/input/web_input_event_builders_android_unittest.cc
index cd9fdce..ca6abc2 100644
--- a/components/input/web_input_event_builders_android_unittest.cc
+++ b/components/input/web_input_event_builders_android_unittest.cc
@@ -177,6 +177,38 @@
EXPECT_EQ(ui::DomKey::UNIDENTIFIED, ui::DomKey(web_event.dom_key));
}
+// Verifies that supplementary-plane code points (e.g. emojis or extra-plane
+// chars) are encoded as surrogate pairs in unmodified_text and text, matching
+// dom_key and avoiding truncation / narrowing.
+TEST(WebInputEventBuilderAndroidTest, SupplementaryPlaneTextVsDomKeyMatch) {
+ constexpr int kCodePoint = 0x1F600; // U+1F600 GRINNING FACE
+
+ // Use the synthetic-event form (env=nullptr) so the test doesn't depend on a
+ // device KCM; |unicode_character| is supplied directly, mirroring what
+ // ImeAdapterImpl.sendKeyEvent forwards from KeyEvent.getUnicodeChar().
+ WebKeyboardEvent web_event = input::WebKeyboardEventBuilder::Build(
+ /*env=*/nullptr, /*android_key_event=*/nullptr,
+ WebKeyboardEvent::Type::kKeyDown, /*modifiers=*/0,
+ blink::WebInputEvent::GetStaticTimeStampForTests(),
+ /*keycode=*/AKEYCODE_A, /*scancode=*/0,
+ /*unicode_character=*/kCodePoint,
+ /*is_system_key=*/false);
+
+ // dom_key preserves the full 21-bit scalar -> JS event.key will be "😀".
+ EXPECT_EQ(ui::DomKey::FromCharacter(kCodePoint),
+ ui::DomKey(web_event.dom_key))
+ << "dom_key must hold the full code point";
+ std::string js_event_key =
+ ui::KeycodeConverter::DomKeyToKeyString(ui::DomKey(web_event.dom_key));
+ EXPECT_EQ("\xF0\x9F\x98\x80", js_event_key); // UTF-8 of U+1F600
+
+ // text[] now holds the properly encoded UTF-16 surrogate pair.
+ std::u16string text(web_event.text.data());
+ EXPECT_EQ(2u, text.size()) << "text[] must hold the 2 surrogate code units";
+ EXPECT_EQ(std::u16string(u"\U0001F600"), text)
+ << "text[] must match the saved-password encoding (surrogate pair)";
+}
+
// Testing new Android keycode introduced in API 24.
TEST(WebInputEventBuilderAndroidTest, CutCopyPasteKey) {
JNIEnv* env = AttachCurrentThread();
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