Chrome · Omnibox
CVE-2026-14068
Logic Error in Omnibox
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
TEST_Fios/chrome/browser/omnibox/model/omnibox_text_controller_unittest.mm |
modified | |
TEST_Fios/chrome/browser/omnibox/ui/omnibox_text_field_ios_unittest.mm |
modified |
Files Changed
ios/chrome/browser/omnibox/model/BUILD.gnios/chrome/browser/omnibox/model/omnibox_text_controller.mmios/chrome/browser/omnibox/model/omnibox_text_controller_unittest.mmios/chrome/browser/omnibox/ui/omnibox_text_field_ios_unittest.mmios/chrome/browser/omnibox/ui/omnibox_text_field_paste_delegate.mm
Patch
From 930491ddf198e351de441a69f5555b9db5141863 Mon Sep 17 00:00:00 2001 From: Justin Cohen <[email protected]> Date: Mon, 18 May 2026 09:42:14 -0700 Subject: [PATCH] ios: Sanitize drag-and-drop and refinement inputs in the Omnibox. Update the UITextPasteDelegate and OmniboxTextController refinement implementations in the omnibox to run incoming text and URL inputs through the standard SanitizeTextForPaste utility. This aligns behavior between manual clipboard pastes, drag-and-drop delegates, and custom drop/refinement flows (such as Composebox). Fixed: 504210171 Change-Id: I04e84f74c0be6880e8bbe2edff51726c18afa38a Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7854723 Auto-Submit: Justin Cohen <[email protected]> Reviewed-by: Radu Nitescu <[email protected]> Commit-Queue: Justin Cohen <[email protected]> Cr-Commit-Position: refs/heads/main@{#1632222} --- diff --git a/ios/chrome/browser/omnibox/model/BUILD.gn b/ios/chrome/browser/omnibox/model/BUILD.gn index df3803d..39846d22 100644 --- a/ios/chrome/browser/omnibox/model/BUILD.gn +++ b/ios/chrome/browser/omnibox/model/BUILD.gn @@ -162,6 +162,7 @@ "//extensions/buildflags", "//ios/chrome/browser/autocomplete/model", "//ios/chrome/browser/location_bar/model:test_support", + "//ios/chrome/browser/omnibox/ui:model_interface", "//ios/chrome/browser/search_engines/model:template_url_service_factory", "//ios/chrome/browser/shared/model/browser/test:test_support", "//ios/chrome/browser/shared/model/prefs:browser_prefs", diff --git a/ios/chrome/browser/omnibox/model/omnibox_text_controller.mm b/ios/chrome/browser/omnibox/model/omnibox_text_controller.mm index cb3d02e6..4480b5a5 100644 --- a/ios/chrome/browser/omnibox/model/omnibox_text_controller.mm +++ b/ios/chrome/browser/omnibox/model/omnibox_text_controller.mm @@ -837,13 +837,14 @@ } - (void)refineWithText:(const std::u16string&)text { + std::u16string sanitizedText = omnibox::SanitizeTextForPaste(text); id<OmniboxTextInput> textInput = self.textInput; // Exit preedit state and append the match. Refocus if necessary. [textInput exitPreEditState]; - [self setUserText:text]; + [self setUserText:sanitizedText]; - [self setWindowText:text - caretPos:text.length() + [self setWindowText:sanitizedText + caretPos:sanitizedText.length() startAutocomplete:true notifyTextChanged:true]; @@ -853,7 +854,7 @@ [textInput.omniboxTextInputDelegate textInputDidChange:textInput]; [textInput.view becomeFirstResponder]; // Set the caret pos to the end of the text (crbug.com/331622199). - [self setCaretPos:text.length()]; + [self setCaretPos:sanitizedText.length()]; } #pragma mark - Private diff --git a/ios/chrome/browser/omnibox/model/omnibox_text_controller_unittest.mm b/ios/chrome/browser/omnibox/model/omnibox_text_controller_unittest.mm index 6be39cf..f8e97ed 100644 --- a/ios/chrome/browser/omnibox/model/omnibox_text_controller_unittest.mm +++ b/ios/chrome/browser/omnibox/model/omnibox_text_controller_unittest.mm @@ -16,6 +16,7 @@ #import "ios/chrome/browser/omnibox/model/fake_omnibox_client.h" #import "ios/chrome/browser/omnibox/model/omnibox_autocomplete_controller.h" #import "ios/chrome/browser/omnibox/model/omnibox_text_model.h" +#import "ios/chrome/browser/omnibox/ui/omnibox_text_input.h" #import "ios/chrome/browser/search_engines/model/template_url_service_factory.h" #import "ios/chrome/browser/shared/model/prefs/browser_prefs.h" #import "ios/chrome/browser/shared/model/prefs/pref_names.h" @@ -26,6 +27,8 @@ #import "testing/gmock/include/gmock/gmock.h" #import "testing/gtest/include/gtest/gtest.h" #import "testing/platform_test.h" +#import "third_party/ocmock/OCMock/OCMock.h" +#import "third_party/ocmock/gtest_support.h" // Mocking the text controller to not rely on the textfield view. @interface TestOmniboxTextController : OmniboxTextController @@ -123,9 +126,9 @@ omnibox_text_controller_ = nil; [omnibox_autocomplete_controller_ disconnect]; omnibox_autocomplete_controller_ = nil; + omnibox_text_model_.reset(); autocomplete_classifier_override_->Shutdown(); autocomplete_controller_.reset(); - omnibox_text_model_.reset(); omnibox_client_.reset(); profile_.reset(); TestingApplicationContext::GetGlobal()->SetLocalState(nullptr); @@ -296,3 +299,32 @@ [omnibox_text_controller_ onCopy]; // The test passes if it doesn't crash. } + +TEST_F(OmniboxTextControllerTest, RefineWithTextSanitizesJavaScript) { + id textInputMock = OCMProtocolMock(@protocol(OmniboxTextInput)); + OCMStub([textInputMock exitPreEditState]); + OCMStub([textInputMock view]).andReturn([[UIView alloc] init]); + OCMStub([textInputMock omniboxTextInputDelegate]).andReturn(nil); + + __block NSString* textValue = @""; + OCMStub([textInputMock setText:[OCMArg any]]) + .andDo(^(NSInvocation* invocation) { + void* arg; + [invocation getArgument:&arg atIndex:2]; + textValue = (__bridge NSString*)arg; + }); + OCMStub([textInputMock text]).andDo(^(NSInvocation* invocation) { + [invocation setReturnValue:&textValue]; + }); + + omnibox_text_controller_.textInput = textInputMock; + + [omnibox_text_controller_ refineWithText:u"https://example.com"]; + EXPECT_EQ(u"https://example.com", [omnibox_text_controller_ displayedText]); + + [omnibox_text_controller_ refineWithText:u"javascript:alert(1)"]; + EXPECT_EQ(u"alert(1)", [omnibox_text_controller_ displayedText]); + + [omnibox_text_controller_ refineWithText:u"java\x0d\x0ascript:alert(2)"]; + EXPECT_EQ(u"alert(2)", [omnibox_text_controller_ displayedText]); +} diff --git a/ios/chrome/browser/omnibox/ui/omnibox_text_field_ios_unittest.mm b/ios/chrome/browser/omnibox/ui/omnibox_text_field_ios_unittest.mm index 971c21a3..7fd7ec8 100644 --- a/ios/chrome/browser/omnibox/ui/omnibox_text_field_ios_unittest.mm +++ b/ios/chrome/browser/omnibox/ui/omnibox_text_field_ios_unittest.mm @@ -12,6 +12,7 @@ #import "base/test/allow_check_is_test_for_testing.h" #import "base/test/task_environment.h" #import "ios/chrome/browser/omnibox/public/omnibox_presentation_context.h" +#import "ios/chrome/browser/omnibox/ui/omnibox_text_field_paste_delegate.h" #import "ios/chrome/browser/omnibox/ui/omnibox_text_input_delegate.h" #import "ios/chrome/browser/shared/model/paths/paths.h" #import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h" @@ -239,3 +240,51 @@ } } // namespace + +@interface OmniboxTextFieldPasteDelegate (Testing) +@property(nonatomic, strong) NSURL* URL; +@end + +TEST_F(OmniboxTextFieldIOSTest, PasteDelegateSanitizesDragAndDrop) { + OmniboxTextFieldPasteDelegate* delegate = + [[OmniboxTextFieldPasteDelegate alloc] init]; + delegate.textInput = textfield_; + + UITextRange* range = OCMClassMock([UITextRange class]); + + // 1. Test standard string drop (without javascript scheme) + NSAttributedString* item1 = + [[NSAttributedString alloc] initWithString:@"https://example.com"]; + NSAttributedString* result1 = + [delegate textPasteConfigurationSupporting:textfield_ + combineItemAttributedStrings:@[ item1 ] + forRange:range]; + EXPECT_NSEQ(@"https://example.com", result1.string); + + // 2. Test malicious javascript scheme drop + NSAttributedString* item2 = + [[NSAttributedString alloc] initWithString:@"javascript:alert(1)"]; + NSAttributedString* result2 = + [delegate textPasteConfigurationSupporting:textfield_ + combineItemAttributedStrings:@[ item2 ] + forRange:range]; + EXPECT_NSEQ(@"alert(1)", result2.string); + + // 3. Test nested/broken javascript scheme drops + NSAttributedString* item3 = [[NSAttributedString alloc] + initWithString:@"java\x0d\x0ascript:alert(0)"]; + NSAttributedString* result3 = + [delegate textPasteConfigurationSupporting:textfield_ + combineItemAttributedStrings:@[ item3 ] + forRange:range]; + EXPECT_NSEQ(@"alert(0)", result3.string); + + // 4. Test cached URL sanitization + delegate.URL = [NSURL URLWithString:@"javascript:alert(2)"]; + NSAttributedString* result4 = + [delegate textPasteConfigurationSupporting:textfield_ + combineItemAttributedStrings:@[] + forRange:range]; + EXPECT_NSEQ(@"alert(2)", result4.string); + EXPECT_EQ(nil, delegate.URL); +} diff --git a/ios/chrome/browser/omnibox/ui/omnibox_text_field_paste_delegate.mm b/ios/chrome/browser/omnibox/ui/omnibox_text_field_paste_delegate.mm index c784942..ea55b2d 100644 --- a/ios/chrome/browser/omnibox/ui/omnibox_text_field_paste_delegate.mm +++ b/ios/chrome/browser/omnibox/ui/omnibox_text_field_paste_delegate.mm @@ -5,6 +5,8 @@ #import "ios/chrome/browser/omnibox/ui/omnibox_text_field_paste_delegate.h"
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/ios/chrome/browser/omnibox/model/omnibox_text_controller_unittest.mm b/ios/chrome/browser/omnibox/model/omnibox_text_controller_unittest.mm
index 6be39cf..f8e97ed 100644
--- a/ios/chrome/browser/omnibox/model/omnibox_text_controller_unittest.mm
+++ b/ios/chrome/browser/omnibox/model/omnibox_text_controller_unittest.mm
@@ -16,6 +16,7 @@
#import "ios/chrome/browser/omnibox/model/fake_omnibox_client.h"
#import "ios/chrome/browser/omnibox/model/omnibox_autocomplete_controller.h"
#import "ios/chrome/browser/omnibox/model/omnibox_text_model.h"
+#import "ios/chrome/browser/omnibox/ui/omnibox_text_input.h"
#import "ios/chrome/browser/search_engines/model/template_url_service_factory.h"
#import "ios/chrome/browser/shared/model/prefs/browser_prefs.h"
#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
@@ -26,6 +27,8 @@
#import "testing/gmock/include/gmock/gmock.h"
#import "testing/gtest/include/gtest/gtest.h"
#import "testing/platform_test.h"
+#import "third_party/ocmock/OCMock/OCMock.h"
+#import "third_party/ocmock/gtest_support.h"
// Mocking the text controller to not rely on the textfield view.
@interface TestOmniboxTextController : OmniboxTextController
@@ -123,9 +126,9 @@
omnibox_text_controller_ = nil;
[omnibox_autocomplete_controller_ disconnect];
omnibox_autocomplete_controller_ = nil;
+ omnibox_text_model_.reset();
autocomplete_classifier_override_->Shutdown();
autocomplete_controller_.reset();
- omnibox_text_model_.reset();
omnibox_client_.reset();
profile_.reset();
TestingApplicationContext::GetGlobal()->SetLocalState(nullptr);
@@ -296,3 +299,32 @@
[omnibox_text_controller_ onCopy];
// The test passes if it doesn't crash.
}
+
+TEST_F(OmniboxTextControllerTest, RefineWithTextSanitizesJavaScript) {
+ id textInputMock = OCMProtocolMock(@protocol(OmniboxTextInput));
+ OCMStub([textInputMock exitPreEditState]);
+ OCMStub([textInputMock view]).andReturn([[UIView alloc] init]);
+ OCMStub([textInputMock omniboxTextInputDelegate]).andReturn(nil);
+
+ __block NSString* textValue = @"";
+ OCMStub([textInputMock setText:[OCMArg any]])
+ .andDo(^(NSInvocation* invocation) {
+ void* arg;
+ [invocation getArgument:&arg atIndex:2];
+ textValue = (__bridge NSString*)arg;
+ });
+ OCMStub([textInputMock text]).andDo(^(NSInvocation* invocation) {
+ [invocation setReturnValue:&textValue];
+ });
+
+ omnibox_text_controller_.textInput = textInputMock;
+
+ [omnibox_text_controller_ refineWithText:u"https://example.com"];
+ EXPECT_EQ(u"https://example.com", [omnibox_text_controller_ displayedText]);
+
+ [omnibox_text_controller_ refineWithText:u"javascript:alert(1)"];
+ EXPECT_EQ(u"alert(1)", [omnibox_text_controller_ displayedText]);
+
+ [omnibox_text_controller_ refineWithText:u"java\x0d\x0ascript:alert(2)"];
+ EXPECT_EQ(u"alert(2)", [omnibox_text_controller_ displayedText]);
+}
diff --git a/ios/chrome/browser/omnibox/ui/omnibox_text_field_ios_unittest.mm b/ios/chrome/browser/omnibox/ui/omnibox_text_field_ios_unittest.mm
index 971c21a3..7fd7ec8 100644
--- a/ios/chrome/browser/omnibox/ui/omnibox_text_field_ios_unittest.mm
+++ b/ios/chrome/browser/omnibox/ui/omnibox_text_field_ios_unittest.mm
@@ -12,6 +12,7 @@
#import "base/test/allow_check_is_test_for_testing.h"
#import "base/test/task_environment.h"
#import "ios/chrome/browser/omnibox/public/omnibox_presentation_context.h"
+#import "ios/chrome/browser/omnibox/ui/omnibox_text_field_paste_delegate.h"
#import "ios/chrome/browser/omnibox/ui/omnibox_text_input_delegate.h"
#import "ios/chrome/browser/shared/model/paths/paths.h"
#import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h"
@@ -239,3 +240,51 @@
}
} // namespace
+
+@interface OmniboxTextFieldPasteDelegate (Testing)
+@property(nonatomic, strong) NSURL* URL;
+@end
+
+TEST_F(OmniboxTextFieldIOSTest, PasteDelegateSanitizesDragAndDrop) {
+ OmniboxTextFieldPasteDelegate* delegate =
+ [[OmniboxTextFieldPasteDelegate alloc] init];
+ delegate.textInput = textfield_;
+
+ UITextRange* range = OCMClassMock([UITextRange class]);
+
+ // 1. Test standard string drop (without javascript scheme)
+ NSAttributedString* item1 =
+ [[NSAttributedString alloc] initWithString:@"https://example.com"];
+ NSAttributedString* result1 =
+ [delegate textPasteConfigurationSupporting:textfield_
+ combineItemAttributedStrings:@[ item1 ]
+ forRange:range];
+ EXPECT_NSEQ(@"https://example.com", result1.string);
+
+ // 2. Test malicious javascript scheme drop
+ NSAttributedString* item2 =
+ [[NSAttributedString alloc] initWithString:@"javascript:alert(1)"];
+ NSAttributedString* result2 =
+ [delegate textPasteConfigurationSupporting:textfield_
+ combineItemAttributedStrings:@[ item2 ]
+ forRange:range];
+ EXPECT_NSEQ(@"alert(1)", result2.string);
+
+ // 3. Test nested/broken javascript scheme drops
+ NSAttributedString* item3 = [[NSAttributedString alloc]
+ initWithString:@"java\x0d\x0ascript:alert(0)"];
+ NSAttributedString* result3 =
+ [delegate textPasteConfigurationSupporting:textfield_
+ combineItemAttributedStrings:@[ item3 ]
+ forRange:range];
+ EXPECT_NSEQ(@"alert(0)", result3.string);
+
+ // 4. Test cached URL sanitization
+ delegate.URL = [NSURL URLWithString:@"javascript:alert(2)"];
+ NSAttributedString* result4 =
+ [delegate textPasteConfigurationSupporting:textfield_
+ combineItemAttributedStrings:@[]
+ forRange:range];
+ EXPECT_NSEQ(@"alert(2)", result4.string);
+ EXPECT_EQ(nil, delegate.URL);
+}
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