CVE-2026-17966
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifui/base/ime/input_method_base.cc |
modified | |
TEST_Fui/views/cocoa/bridged_native_widget_unittest.mm |
modified |
Files Changed
ui/base/ime/input_method_base.ccui/views/cocoa/bridged_native_widget_unittest.mmui/views/cocoa/text_input_host.mm
Patch
From cb92015f0518b7c598904aca5ce37c3093c155e7 Mon Sep 17 00:00:00 2001 From: Avi Drissman <[email protected]> Date: Mon, 01 Jun 2026 19:37:44 -0700 Subject: [PATCH] Honor TEXT_INPUT_FLAG_HAS_BEEN_PASSWORD on macOS TEXT_INPUT_FLAG_HAS_BEEN_PASSWORD is set if a text field was a password field at any point in the past. Other IME code handles it; update TextInputHost to handle it as well. Fixed: 518058990 Change-Id: Ib8e65cd97f556a3f469e12e086ee38b46a6a6964 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7888102 Reviewed-by: Keren Zhu <[email protected]> Commit-Queue: Avi Drissman <[email protected]> Cr-Commit-Position: refs/heads/main@{#1639888} --- diff --git a/ui/base/ime/input_method_base.cc b/ui/base/ime/input_method_base.cc index e2a557f..99f49321 100644 --- a/ui/base/ime/input_method_base.cc +++ b/ui/base/ime/input_method_base.cc @@ -16,6 +16,7 @@ #include "ui/base/ime/input_method_observer.h" #include "ui/base/ime/text_input_client.h" #include "ui/base/ime/text_input_flags.h" +#include "ui/base/ime/text_input_type.h" #include "ui/base/ime/virtual_keyboard_controller_stub.h" #include "ui/events/event.h" @@ -74,11 +75,15 @@ TextInputType InputMethodBase::GetTextInputType() const { TextInputClient* client = GetTextInputClient(); - return client - ? (client->GetTextInputFlags() & TEXT_INPUT_FLAG_HAS_BEEN_PASSWORD - ? TEXT_INPUT_TYPE_PASSWORD - : client->GetTextInputType()) - : TEXT_INPUT_TYPE_NONE; + if (!client) { + return TEXT_INPUT_TYPE_NONE; + } + + if (client->GetTextInputFlags() & TEXT_INPUT_FLAG_HAS_BEEN_PASSWORD) { + return TEXT_INPUT_TYPE_PASSWORD; + } + + return client->GetTextInputType(); } void InputMethodBase::SetVirtualKeyboardVisibilityIfEnabled(bool should_show) { diff --git a/ui/views/cocoa/bridged_native_widget_unittest.mm b/ui/views/cocoa/bridged_native_widget_unittest.mm index fcd6d8d..105a9c38 100644 --- a/ui/views/cocoa/bridged_native_widget_unittest.mm +++ b/ui/views/cocoa/bridged_native_widget_unittest.mm @@ -8,6 +8,7 @@ #include <cstddef> #include <memory> #include <string> +#include <string_view> #import "base/apple/foundation_util.h" #import "base/apple/scoped_objc_class_swizzler.h" @@ -27,6 +28,7 @@ #include "ui/base/cocoa/find_pasteboard.h" #import "ui/base/cocoa/window_size_constants.h" #include "ui/base/ime/input_method.h" +#include "ui/base/ime/text_input_flags.h" #include "ui/base/metadata/metadata_header_macros.h" #include "ui/base/metadata/metadata_impl_macros.h" #import "ui/base/test/cocoa_helper.h" @@ -567,9 +569,9 @@ // Install a textfield with input type |text_input_type| in the view hierarchy // and make it the text input client. Also initializes |dummy_text_view_|. Textfield* InstallTextField( - const std::u16string& text, + std::u16string_view text, ui::TextInputType text_input_type = ui::TEXT_INPUT_TYPE_TEXT); - Textfield* InstallTextField(const std::string& text); + Textfield* InstallTextField(std::string_view text); // Returns the actual current text for |ns_view_|, or the selected substring. NSString* GetActualText(); @@ -667,7 +669,7 @@ BridgedNativeWidgetTest::~BridgedNativeWidgetTest() = default; Textfield* BridgedNativeWidgetTest::InstallTextField( - const std::u16string& text, + std::u16string_view text, ui::TextInputType text_input_type) { Textfield* textfield = new Textfield(); textfield->SetText(text); @@ -692,7 +694,7 @@ return textfield; } -Textfield* BridgedNativeWidgetTest::InstallTextField(const std::string& text) { +Textfield* BridgedNativeWidgetTest::InstallTextField(std::string_view text) { return InstallTextField(base::ASCIIToUTF16(text)); } @@ -1087,15 +1089,23 @@ // Ensure a nil NSTextInputContext is returned when the ui::TextInputClient is // not editable, a password field, or unset. TEST_F(BridgedNativeWidgetTest, InputContext) { - const std::u16string test_string = u"test_str"; + constexpr std::u16string_view test_string = u"test_str"; + InstallTextField(test_string, ui::TEXT_INPUT_TYPE_PASSWORD); - EXPECT_FALSE([ns_view_ inputContext]); - InstallTextField(test_string, ui::TEXT_INPUT_TYPE_TEXT); - EXPECT_TRUE([ns_view_ inputContext]); + EXPECT_FALSE(ns_view_.inputContext); + + Textfield* text_field = + InstallTextField(test_string, ui::TEXT_INPUT_TYPE_TEXT); + EXPECT_TRUE(ns_view_.inputContext); + + text_field->SetTextInputFlags(ui::TEXT_INPUT_FLAG_HAS_BEEN_PASSWORD); + EXPECT_FALSE(ns_view_.inputContext); + GetNSWindowHost()->text_input_host()->SetTextInputClient(nullptr); - EXPECT_FALSE([ns_view_ inputContext]); + EXPECT_FALSE(ns_view_.inputContext); + InstallTextField(test_string, ui::TEXT_INPUT_TYPE_NONE); - EXPECT_FALSE([ns_view_ inputContext]); + EXPECT_FALSE(ns_view_.inputContext); } // Test getting complete string using text input protocol. diff --git a/ui/views/cocoa/text_input_host.mm b/ui/views/cocoa/text_input_host.mm index a252190..2e5aac6 100644 --- a/ui/views/cocoa/text_input_host.mm +++ b/ui/views/cocoa/text_input_host.mm @@ -10,6 +10,8 @@ #include "ui/accelerated_widget_mac/window_resize_helper_mac.h" #include "ui/base/cocoa/menu_utils.h" #include "ui/base/ime/text_input_client.h" +#include "ui/base/ime/text_input_flags.h" +#include "ui/base/ime/text_input_type.h" #include "ui/events/keycodes/dom/dom_code.h" #include "ui/views/cocoa/native_widget_mac_ns_window_host.h" @@ -238,16 +240,25 @@ } // When not in an editable mode, or while entering passwords - // (http://crbug.com/23219), we don't want to show IME candidate windows. + // (https://crbug.com/41007509), we don't want to show IME candidate windows. // Returning nil prevents this view from getting messages defined as part of // the NSTextInputClient protocol. - switch (pending_text_input_client_->GetTextInputType()) { - case ui::TEXT_INPUT_TYPE_NONE: - case ui::TEXT_INPUT_TYPE_PASSWORD: - return true; - default: - *out_has_input_context = true; + // + // Honor TEXT_INPUT_FLAG_HAS_BEEN_PASSWORD: a revealed password/PIN field + // reports TEXT_INPUT_TYPE_TEXT but must still be hidden from IMEs. See + // InputMethodBase::GetTextInputType(). + if (pending_text_input_client_->GetTextInputFlags() & + ui::TEXT_INPUT_FLAG_HAS_BEEN_PASSWORD) { + return true; } + ui::TextInputType type = pending_text_input_client_->GetTextInputType(); + if (type == ui::TEXT_INPUT_TYPE_NONE || + type == ui::TEXT_INPUT_TYPE_PASSWORD) { + return true; + } + + // Otherwise, allow a context. + *out_has_input_context = true; return true; }
Regression Test / PoC
diff --git a/ui/views/cocoa/bridged_native_widget_unittest.mm b/ui/views/cocoa/bridged_native_widget_unittest.mm
index fcd6d8d..105a9c38 100644
--- a/ui/views/cocoa/bridged_native_widget_unittest.mm
+++ b/ui/views/cocoa/bridged_native_widget_unittest.mm
@@ -8,6 +8,7 @@
#include <cstddef>
#include <memory>
#include <string>
+#include <string_view>
#import "base/apple/foundation_util.h"
#import "base/apple/scoped_objc_class_swizzler.h"
@@ -27,6 +28,7 @@
#include "ui/base/cocoa/find_pasteboard.h"
#import "ui/base/cocoa/window_size_constants.h"
#include "ui/base/ime/input_method.h"
+#include "ui/base/ime/text_input_flags.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#import "ui/base/test/cocoa_helper.h"
@@ -567,9 +569,9 @@
// Install a textfield with input type |text_input_type| in the view hierarchy
// and make it the text input client. Also initializes |dummy_text_view_|.
Textfield* InstallTextField(
- const std::u16string& text,
+ std::u16string_view text,
ui::TextInputType text_input_type = ui::TEXT_INPUT_TYPE_TEXT);
- Textfield* InstallTextField(const std::string& text);
+ Textfield* InstallTextField(std::string_view text);
// Returns the actual current text for |ns_view_|, or the selected substring.
NSString* GetActualText();
@@ -667,7 +669,7 @@
BridgedNativeWidgetTest::~BridgedNativeWidgetTest() = default;
Textfield* BridgedNativeWidgetTest::InstallTextField(
- const std::u16string& text,
+ std::u16string_view text,
ui::TextInputType text_input_type) {
Textfield* textfield = new Textfield();
textfield->SetText(text);
@@ -692,7 +694,7 @@
return textfield;
}
-Textfield* BridgedNativeWidgetTest::InstallTextField(const std::string& text) {
+Textfield* BridgedNativeWidgetTest::InstallTextField(std::string_view text) {
return InstallTextField(base::ASCIIToUTF16(text));
}
@@ -1087,15 +1089,23 @@
// Ensure a nil NSTextInputContext is returned when the ui::TextInputClient is
// not editable, a password field, or unset.
TEST_F(BridgedNativeWidgetTest, InputContext) {
- const std::u16string test_string = u"test_str";
+ constexpr std::u16string_view test_string = u"test_str";
+
InstallTextField(test_string, ui::TEXT_INPUT_TYPE_PASSWORD);
- EXPECT_FALSE([ns_view_ inputContext]);
- InstallTextField(test_string, ui::TEXT_INPUT_TYPE_TEXT);
- EXPECT_TRUE([ns_view_ inputContext]);
+ EXPECT_FALSE(ns_view_.inputContext);
+
+ Textfield* text_field =
+ InstallTextField(test_string, ui::TEXT_INPUT_TYPE_TEXT);
+ EXPECT_TRUE(ns_view_.inputContext);
+
+ text_field->SetTextInputFlags(ui::TEXT_INPUT_FLAG_HAS_BEEN_PASSWORD);
+ EXPECT_FALSE(ns_view_.inputContext);
+
GetNSWindowHost()->text_input_host()->SetTextInputClient(nullptr);
- EXPECT_FALSE([ns_view_ inputContext]);
+ EXPECT_FALSE(ns_view_.inputContext);
+
InstallTextField(test_string, ui::TEXT_INPUT_TYPE_NONE);
- EXPECT_FALSE([ns_view_ inputContext]);
+ EXPECT_FALSE(ns_view_.inputContext);
}
// Test getting complete string using text input protocol.
Original Bug Report
macOS TextInputHost::HasInputContext ignores TEXT_INPUT_FLAG_HAS_BEEN_PASSWORD
Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: On macOS, TextInputHost::HasInputContext queries the raw text input type and ignores the TEXT_INPUT_FLAG_HAS_BEEN_PASSWORD security flag. Consequently, when a password or GPM PIN field is toggled to a visible state, active third-party Input Method Editors (IMEs) can access and potentially log the plaintext credential. This bypasses the platform defense-in-depth protection implemented on Windows, Linux, and ChromeOS.
Affected files:
ui/views/cocoa/text_input_host.mm
Estimated timestamp from git blame: 2023-04-20
Root Cause Analysis
The TEXT_INPUT_FLAG_HAS_BEEN_PASSWORD flag was introduced as a defense-in-depth measure to prevent revealed passwords and PINs from being exposed to Input Method Editors (IMEs). On platforms utilizing InputMethodBase, InputMethodBase::GetTextInputType() correctly coerces the returned text type back to TEXT_INPUT_TYPE_PASSWORD when this flag is present to suppress active IMEs.
However, the macOS Views NSTextInputClient bridge does not route through InputMethodBase. Instead, TextInputHost::HasInputContext (in ui/views/cocoa/text_input_host.mm) reads the raw client type directly without querying text input flags:
switch (pending_text_input_client_->GetTextInputType()) {
case ui::TEXT_INPUT_TYPE_NONE:
case ui::TEXT_INPUT_TYPE_PASSWORD:
return true;
default:
*out_has_input_context = true;
}
Because the raw client type is returned as TEXT_INPUT_TYPE_TEXT once the credential is eye-toggled/revealed, the IME remains fully active and can query the contents of the textfield. Furthermore, Textfield::ImeEditingAllowed() in ui/views/controls/textfield/textfield.cc only checks the raw type, allowing the IME to query and learn the plaintext credential.
Affected Components
This potential security gap renders the toggle-reveal protection ineffective on macOS in the following views:
- Save/Update Password Bubble:
EditablePasswordCombobox(viaPasswordSaveUpdateViewinchrome/browser/ui/views/passwords/password_save_update_view.cc). - WebAuthn GPM PIN Sheet:
AuthenticatorGPMArbitraryPinView(inchrome/browser/ui/views/webauthn/authenticator_gpm_arbitrary_pin_view.cc).
Potential Trigger Path
Note: These are potential steps based on static analysis; our tooling does not currently support code execution or verification on live systems.
- A macOS user with an active third-party IME opens the Save Password bubble or the WebAuthn GPM PIN view.
- The user clicks the eye icon to reveal the password or PIN. This toggles the textfield type to
TEXT_INPUT_TYPE_TEXTand appendsTEXT_INPUT_FLAG_HAS_BEEN_PASSWORDto the input flags. - The user focuses the revealed textfield. AppKit queries
-[BridgedContentView inputContext], which callsTextInputHost::HasInputContext. HasInputContextretrievesTEXT_INPUT_TYPE_TEXTas the raw type, and setsout_has_input_context = true.- Because a non-nil
NSTextInputContextis returned, the active third-party IME queries text via standardNSTextInputClientprotocols (e.g.,-attributedSubstringForProposedRange:actualRange:). - Since
Textfield::ImeEditingAllowed()only checks the raw type, it allows the IME to query and read the plaintext credential.
Suggested Fix
- Update
TextInputHost::HasInputContextinui/views/cocoa/text_input_host.mmto check the client’s text input flags:
ui::TextInputType type = pending_text_input_client_->GetTextInputType();
if (pending_text_input_client_->GetTextInputFlags() &
ui::TEXT_INPUT_FLAG_HAS_BEEN_PASSWORD) {
type = ui::TEXT_INPUT_TYPE_PASSWORD;
}
switch (type) {
case ui::TEXT_INPUT_TYPE_NONE:
case ui::TEXT_INPUT_TYPE_PASSWORD:
return true;
default:
*out_has_input_context = true;
}
- Update
Textfield::ImeEditingAllowedinui/views/controls/textfield/textfield.ccto check for this flag as well:
bool Textfield::ImeEditingAllowed() const {
if (GetTextInputFlags() & ui::TEXT_INPUT_FLAG_HAS_BEEN_PASSWORD) {
return false;
}
ui::TextInputType t = GetTextInputType();
return (t != ui::TEXT_INPUT_TYPE_NONE && t != ui::TEXT_INPUT_TYPE_PASSWORD);
}
Evaluated with Chrome root at commit: fb72408a8493c46bc75fae1c70d03daec96b3040
Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:
- If you are familiar with the severity guidelines, you may adjust the severity.
- If this is a false positive, and there’s no work to be done, please close as WAI.
- If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.
Data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.