Chrome · Mobile › iOS
CVE-2026-106192
Info Disclosure in Mobile (iOS)
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifios/chrome/browser/safari_data_import/coordinator/safari_data_import_import_coordinator.mm |
modified |
Files Changed
ios/chrome/browser/safari_data_import/coordinator/DEPSios/chrome/browser/safari_data_import/test/safari_data_import_egtest.mmios/chrome/browser/safari_data_import/coordinator/BUILD.gnios/chrome/browser/safari_data_import/test/BUILD.gnios/chrome/browser/safari_data_import/coordinator/safari_data_import_import_coordinator.mm
Patch
From 6e2bef06197563409e189cc8405434b65e4b7a55 Mon Sep 17 00:00:00 2001
From: Leo Zhao <leozhao@google.com>
Date: Mon, 14 Sep 2026 11:24:04 -0700
Subject: [PATCH] [iOS] Add reauth shielding to Safari import conflict resolution
Integrate LocalReauthenticationCoordinator to display the
reauthentication shield when the scene transitions to inactive or
background states, and dismiss the conflict resolution view controller
if reauthentication fails.
Bug: 524681280
Change-Id: If47738cd7b495e9713a66206b91b711aa8c0457d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8376898
Reviewed-by: Tommy Martino <tmartino@chromium.org>
Reviewed-by: Angela Novakovic <novakovica@google.com>
Commit-Queue: Leo Zhao <leozhao@google.com>
Cr-Commit-Position: refs/heads/main@{#1697371}
---
diff --git a/ios/chrome/browser/safari_data_import/coordinator/safari_data_import_import_coordinator.mm b/ios/chrome/browser/safari_data_import/coordinator/safari_data_import_import_coordinator.mm
index d2542543..7f5bfd8 100644
--- a/ios/chrome/browser/safari_data_import/coordinator/safari_data_import_import_coordinator.mm
+++ b/ios/chrome/browser/safari_data_import/coordinator/safari_data_import_import_coordinator.mm
@@ -40,6 +40,7 @@
#import "ios/chrome/browser/safari_data_import/public/metrics.h"
#import "ios/chrome/browser/safari_data_import/public/safari_data_import_stage.h"
#import "ios/chrome/browser/safari_data_import/ui/safari_data_import_import_view_controller.h"
+#import "ios/chrome/browser/settings/ui_bundled/password/reauthentication/local_reauthentication_coordinator.h"
#import "ios/chrome/browser/shared/model/application_context/application_context.h"
#import "ios/chrome/browser/shared/model/browser/browser.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
@@ -59,6 +60,7 @@
@interface SafariDataImportImportCoordinator () <
DataImportCredentialConflictResolutionViewControllerDelegate,
DataImportImportStageTransitionHandler,
+ LocalReauthenticationCoordinatorDelegate,
PromoStyleViewControllerDelegate,
UITableViewDelegate>
@@ -80,6 +82,9 @@
UIDocumentPickerViewController* _documentProvider;
/// Table view that displays the import status of Safari data.
ImportDataItemTableView* _tableView;
+ /// Coordinator for shielding credential conflict resolution when the app is
+ /// backgrounded or when multitasking App Switcher is opened.
+ LocalReauthenticationCoordinator* _reauthCoordinator;
}
@synthesize mediator = _mediator;
@@ -112,6 +117,7 @@
}
- (void)stop {
+ [self stopReauthCoordinator];
[self.mediator disconnect];
self.delegate = nil;
_containerViewController = nil;
@@ -302,13 +308,33 @@
#pragma mark - DataImportCredentialConflictResolutionViewControllerDelegate
- (void)cancelledConflictResolution {
+ [self stopReauthCoordinator];
[_containerViewController dismissViewControllerAnimated:YES completion:nil];
}
- (void)resolvedCredentialConflicts {
+ [self stopReauthCoordinator];
[_containerViewController dismissViewControllerAnimated:YES completion:nil];
}
+#pragma mark - LocalReauthenticationCoordinatorDelegate
+
+- (void)successfulReauthenticationWithCoordinator:
+ (LocalReauthenticationCoordinator*)coordinator {
+}
+
+- (void)dismissUIAfterFailedReauthenticationWithCoordinator:
+ (LocalReauthenticationCoordinator*)coordinator {
+ CHECK_EQ(_reauthCoordinator, coordinator);
+ [self stopReauthCoordinator];
+ // Dismiss only the modal conflict resolution screen, returning the user to
+ // the ready-for-import stage without losing progress in the import flow.
+ [_containerViewController dismissViewControllerAnimated:YES completion:nil];
+}
+
+- (void)willPushReauthenticationViewController {
+}
+
#pragma mark - Private
/// Displays the document picker so the user could select the Safari data file.
@@ -357,7 +383,9 @@
initWithRootViewController:conflictResolutionViewController];
wrapper.toolbarHidden = NO;
wrapper.modalInPresentation = YES;
- [self presentViewController:wrapper];
+ if ([self presentViewController:wrapper]) {
+ [self startReauthCoordinatorWithNavigationController:wrapper];
+ }
}
/// Handler for actions in `self.fileDeletionAlert`.
@@ -423,4 +451,25 @@
return alert;
}
+/// Starts `_reauthCoordinator` with `navigationController`. Once started, it
+/// observes scene state changes and pushes a shield view controller when the
+/// scene is backgrounded or foreground inactive (e.g. App Switcher).
+- (void)startReauthCoordinatorWithNavigationController:
+ (UINavigationController*)navigationController {
+ [self stopReauthCoordinator];
+ _reauthCoordinator = [[LocalReauthenticationCoordinator alloc]
+ initWithBaseNavigationController:navigationController
+ browser:self.browser
+ authOnStart:NO];
+ _reauthCoordinator.delegate = self;
+ [_reauthCoordinator start];
+}
+
+/// Stops `_reauthCoordinator` and cleans up its delegate.
+- (void)stopReauthCoordinator {
+ [_reauthCoordinator stop];
+ _reauthCoordinator.delegate = nil;
+ _reauthCoordinator = nil;
+}
+
@end
diff --git a/ios/chrome/browser/safari_data_import/test/safari_data_import_egtest.mm b/ios/chrome/browser/safari_data_import/test/safari_data_import_egtest.mm
index b7a4efac..12b6deb9 100644
--- a/ios/chrome/browser/safari_data_import/test/safari_data_import_egtest.mm
+++ b/ios/chrome/browser/safari_data_import/test/safari_data_import_egtest.mm
@@ -12,6 +12,7 @@
#import "ios/chrome/browser/passwords/model/password_manager_app_interface.h"
#import "ios/chrome/browser/safari_data_import/test/safari_data_import_app_interface.h"
#import "ios/chrome/browser/safari_data_import/test/safari_data_import_earl_grey_ui.h"
+#import "ios/chrome/browser/settings/ui_bundled/password/reauthentication/local_reauthentication_constants.h"
#import "ios/chrome/browser/settings/ui_bundled/settings_table_view_controller_constants.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/common/ui/confirmation_alert/constants.h"
@@ -45,6 +46,12 @@
/// User name for the invalid password.
NSString* const kInvalidPasswordUsername = @"Superman";
+/// Matcher for the "Import" button on the import screen.
+id<GREYMatcher> ImportButton() {
+ return ImportScreenButtonWithTextId(
+ IDS_IOS_SAFARI_IMPORT_IMPORT_ACTION_BUTTON_IMPORT);
+}
+
} // namespace
/// Tests for Safari data import.
@@ -255,9 +262,7 @@
/// Check that the items table has displayed.
ExpectImportTableHasRowCount(4);
/// Import the data and wait until completion.
- [[EarlGrey selectElementWithMatcher:
- ImportScreenButtonWithTextId(
- IDS_IOS_SAFARI_IMPORT_IMPORT_ACTION_BUTTON_IMPORT)]
+ [[EarlGrey selectElementWithMatcher:ImportButton()]
performAction:grey_tap()];
[ChromeEarlGrey waitForUIElementToAppearWithMatcher:
ImportScreenButtonWithTextId(
@@ -314,9 +319,7 @@
LoadFile(SafariDataImportTestFile::kValid);
ExpectImportTableHasRowCount(4);
/// Import the data and check conflict resolution page.
- [[EarlGrey selectElementWithMatcher:
- ImportScreenButtonWithTextId(
- IDS_IOS_SAFARI_IMPORT_IMPORT_ACTION_BUTTON_IMPORT)]
+ [[EarlGrey selectElementWithMatcher:ImportButton()]
performAction:grey_tap()];
id<GREYMatcher> conflictResolutionTable = grey_accessibilityID(
GetCredentialConflictResolutionTableViewAccessibilityIdentifier());
@@ -397,15 +400,72 @@
}
}
+/// Tests that the password conflict resolution surface is shielded by the local
+/// reauthentication view controller when the app is backgrounded, preventing
+/// plaintext passwords from leaking into multitasking snapshots.
+- (void)testPasswordConflictResolutionShieldedOnBackground {
+ if (@available(iOS 18.2, *)) {
+ [ReauthenticationAppInterface mockReauthenticationModuleExpectedResult:
+ ReauthenticationResult::kFailure];
+ [ReauthenticationAppInterface
+ mockReauthenticationModuleShouldSkipReAuth:NO];
+
+ /// Store some password that will result in a conflict.
+ NSString* existingPassword = @"Google!Password)";
+ NSURL* url = [NSURL URLWithString:kURL];
+ [PasswordManagerAppInterface storeCredentialWithUsername:kUsername1
+ password:existingPassword
+ URL:url];
+ [PasswordManagerAppInterface storeCredentialWithUsername:kUsername2
+ password:existingPassword
+ URL:url];
+
+ /// Start the flow.
+ GoToImportScreen();
+ LoadFile(SafariDataImportTestFile::kValid);
+ ExpectImportTableHasRowCount(4);
+
+ /// Import the data and check conflict resolution page.
+ [[EarlGrey selectElementWithMatcher:ImportButton()]
+ performAction:grey_tap()];
+ id<GREYMatcher> conflictResolutionTable = grey_accessibilityID(
+ GetCredentialConflictResolutionTableViewAccessibilityIdentifier());
+ [ChromeEarlGrey
+ waitForUIElementToAppearWithMatcher:conflictResolutionTable];
+
+ /// Background and return to foreground. Reauth UI should shield the screen.
+ [[AppLaunchManager sharedManager] backgroundAndForegroundApp];
+
+ id<GREYMatcher> reauthController = grey_accessibilityID(
+ password_manager::
+ kReauthenticationViewControllerAccessibilityIdentifier);
+ [ChromeEarlGrey waitForUIElementToAppearWithMatcher:reauthController];
+ [[EarlGrey selectElementWithMatcher:conflictResolutionTable]
+ assertWithMatcher:grey_notVisible()];
+
+ /// Return mock failure result. The coordinator should dismiss the conflict
+ /// resolution view controller upon failed authentication.
+ [ReauthenticationAppInterface mockReauthenticationModuleReturnMockedResult];
+
+ [ChromeEarlGrey waitForUIElementToDisappearWithMatcher:reauthController];
+ [ChromeEarlGrey
+ waitForUIElementToDisappearWithMatcher:conflictResolutionTable];
+
+ /// Verify that the user is returned to the ready-to-import stage without
+ /// losing progress.
+ ExpectImportTableHasRowCount(4);
+ [[EarlGrey selectElementWithMatcher:ImportButton()]
+ assertWithMatcher:grey_sufficientlyVisible()];
+ }
+}
+
/// Tests uploading a file that only contains potential "history" items.
- (void)testUploadPartiallyValidFile {
if (@available(iOS 18.2, *)) {
GoToImportScreen();
LoadFile(SafariDataImportTestFile::kPartiallyValid);
ExpectImportTableHasRowCount(4);
- [[EarlGrey selectElementWithMatcher:
- ImportScreenButtonWithTextId(
- IDS_IOS_SAFARI_IMPORT_IMPORT_ACTION_BUTTON_IMPORT)]
+ [[EarlGrey selectElementWithMatcher:ImportButton()]
performAction:grey_tap()];
[ChromeEarlGrey waitForUIElementToAppearWithMatcher:
ImportScreenButtonWithTextId(
diff --git a/ios/chrome/browser/safari_data_import/coordinator/BUILD.gn b/ios/chrome/browser/safari_data_import/coordinator/BUILD.gn
index a8207cc..4d77ef4 100644
--- a/ios/chrome/browser/safari_data_import/coordinator/BUILD.gn
+++ b/ios/chrome/browser/safari_data_import/coordinator/BUILD.gn
@@ -94,6 +94,7 @@
"//ios/chrome/browser/safari_data_import/public",
"//ios/chrome/browser/safari_data_import/public:metrics",
"//ios/chrome/browser/safari_data_import/ui",
+ "//ios/chrome/browser/settings/ui_bundled/password/reauthentication",
"//ios/chrome/browser/shared/model/application_context",
"//ios/chrome/browser/shared/model/browser",
"//ios/chrome/browser/shared/model/profile",
diff --git a/ios/chrome/browser/safari_data_import/coordinator/DEPS b/ios/chrome/browser/safari_data_import/coordinator/DEPS
index 0302f73..c3f2e9f 100644
--- a/ios/chrome/browser/safari_data_import/coordinator/DEPS
+++ b/ios/chrome/browser/safari_data_import/coordinator/DEPS
@@ -13,6 +13,7 @@
"+ios/chrome/browser/promos_manager",
"+ios/chrome/browser/reading_list/model",
"+ios/chrome/browser/scoped_ui_blocker/ui_bundled",
+ "+ios/chrome/browser/settings/ui_bundled/password/reauthentication",
"+ios/chrome/browser/sync/model",
"+ios/chrome/browser/webauthn/model",
# keep-sorted end
diff --git a/ios/chrome/browser/safari_data_import/test/BUILD.gn b/ios/chrome/browser/safari_data_import/test/BUILD.gn
index 18ab52c..7dadb5f7 100644
--- a/ios/chrome/browser/safari_data_import/test/BUILD.gn
+++ b/ios/chrome/browser/safari_data_import/test/BUILD.gn
@@ -17,6 +17,7 @@
"//ios/chrome/browser/device_reauth/test:eg_test_support+eg2",
"//ios/chrome/browser/passwords/model:eg_test_support+eg2",
"//ios/chrome/browser/settings/ui_bundled:constants",
+ "//ios/chrome/browser/settings/ui_bundled/password/reauthentication:reauthentication_constants",
"//ios/chrome/browser/shared/public/features",
"//ios/chrome/browser/shared/ui/symbols",
"//ios/chrome/common/ui/confirmation_alert:constants",
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/ios/chrome/browser/safari_data_import/test/BUILD.gn b/ios/chrome/browser/safari_data_import/test/BUILD.gn
index 18ab52c..7dadb5f7 100644
--- a/ios/chrome/browser/safari_data_import/test/BUILD.gn
+++ b/ios/chrome/browser/safari_data_import/test/BUILD.gn
@@ -17,6 +17,7 @@
"//ios/chrome/browser/device_reauth/test:eg_test_support+eg2",
"//ios/chrome/browser/passwords/model:eg_test_support+eg2",
"//ios/chrome/browser/settings/ui_bundled:constants",
+ "//ios/chrome/browser/settings/ui_bundled/password/reauthentication:reauthentication_constants",
"//ios/chrome/browser/shared/public/features",
"//ios/chrome/browser/shared/ui/symbols",
"//ios/chrome/common/ui/confirmation_alert:constants",
diff --git a/ios/chrome/browser/safari_data_import/test/safari_data_import_egtest.mm b/ios/chrome/browser/safari_data_import/test/safari_data_import_egtest.mm
index b7a4efac..12b6deb9 100644
--- a/ios/chrome/browser/safari_data_import/test/safari_data_import_egtest.mm
+++ b/ios/chrome/browser/safari_data_import/test/safari_data_import_egtest.mm
@@ -12,6 +12,7 @@
#import "ios/chrome/browser/passwords/model/password_manager_app_interface.h"
#import "ios/chrome/browser/safari_data_import/test/safari_data_import_app_interface.h"
#import "ios/chrome/browser/safari_data_import/test/safari_data_import_earl_grey_ui.h"
+#import "ios/chrome/browser/settings/ui_bundled/password/reauthentication/local_reauthentication_constants.h"
#import "ios/chrome/browser/settings/ui_bundled/settings_table_view_controller_constants.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/common/ui/confirmation_alert/constants.h"
@@ -45,6 +46,12 @@
/// User name for the invalid password.
NSString* const kInvalidPasswordUsername = @"Superman";
+/// Matcher for the "Import" button on the import screen.
+id<GREYMatcher> ImportButton() {
+ return ImportScreenButtonWithTextId(
+ IDS_IOS_SAFARI_IMPORT_IMPORT_ACTION_BUTTON_IMPORT);
+}
+
} // namespace
/// Tests for Safari data import.
@@ -255,9 +262,7 @@
/// Check that the items table has displayed.
ExpectImportTableHasRowCount(4);
/// Import the data and wait until completion.
- [[EarlGrey selectElementWithMatcher:
- ImportScreenButtonWithTextId(
- IDS_IOS_SAFARI_IMPORT_IMPORT_ACTION_BUTTON_IMPORT)]
+ [[EarlGrey selectElementWithMatcher:ImportButton()]
performAction:grey_tap()];
[ChromeEarlGrey waitForUIElementToAppearWithMatcher:
ImportScreenButtonWithTextId(
@@ -314,9 +319,7 @@
LoadFile(SafariDataImportTestFile::kValid);
ExpectImportTableHasRowCount(4);
/// Import the data and check conflict resolution page.
- [[EarlGrey selectElementWithMatcher:
- ImportScreenButtonWithTextId(
- IDS_IOS_SAFARI_IMPORT_IMPORT_ACTION_BUTTON_IMPORT)]
+ [[EarlGrey selectElementWithMatcher:ImportButton()]
performAction:grey_tap()];
id<GREYMatcher> conflictResolutionTable = grey_accessibilityID(
GetCredentialConflictResolutionTableViewAccessibilityIdentifier());
@@ -397,15 +400,72 @@
}
}
+/// Tests that the password conflict resolution surface is shielded by the local
+/// reauthentication view controller when the app is backgrounded, preventing
+/// plaintext passwords from leaking into multitasking snapshots.
+- (void)testPasswordConflictResolutionShieldedOnBackground {
+ if (@available(iOS 18.2, *)) {
+ [ReauthenticationAppInterface mockReauthenticationModuleExpectedResult:
+ ReauthenticationResult::kFailure];
+ [ReauthenticationAppInterface
+ mockReauthenticationModuleShouldSkipReAuth:NO];
+
+ /// Store some password that will result in a conflict.
+ NSString* existingPassword = @"Google!Password)";
+ NSURL* url = [NSURL URLWithString:kURL];
+ [PasswordManagerAppInterface storeCredentialWithUsername:kUsername1
+ password:existingPassword
+ URL:url];
+ [PasswordManagerAppInterface storeCredentialWithUsername:kUsername2
+ password:existingPassword
+ URL:url];
+
+ /// Start the flow.
+ GoToImportScreen();
+ LoadFile(SafariDataImportTestFile::kValid);
+ ExpectImportTableHasRowCount(4);
+
+ /// Import the data and check conflict resolution page.
+ [[EarlGrey selectElementWithMatcher:ImportButton()]
+ performAction:grey_tap()];
+ id<GREYMatcher> conflictResolutionTable = grey_accessibilityID(
+ GetCredentialConflictResolutionTableViewAccessibilityIdentifier());
+ [ChromeEarlGrey
+ waitForUIElementToAppearWithMatcher:conflictResolutionTable];
+
+ /// Background and return to foreground. Reauth UI should shield the screen.
+ [[AppLaunchManager sharedManager] backgroundAndForegroundApp];
+
+ id<GREYMatcher> reauthController = grey_accessibilityID(
+ password_manager::
+ kReauthenticationViewControllerAccessibilityIdentifier);
+ [ChromeEarlGrey waitForUIElementToAppearWithMatcher:reauthController];
+ [[EarlGrey selectElementWithMatcher:conflictResolutionTable]
+ assertWithMatcher:grey_notVisible()];
+
+ /// Return mock failure result. The coordinator should dismiss the conflict
+ /// resolution view controller upon failed authentication.
+ [ReauthenticationAppInterface mockReauthenticationModuleReturnMockedResult];
+
+ [ChromeEarlGrey waitForUIElementToDisappearWithMatcher:reauthController];
+ [ChromeEarlGrey
+ waitForUIElementToDisappearWithMatcher:conflictResolutionTable];
+
+ /// Verify that the user is returned to the ready-to-import stage without
+ /// losing progress.
+ ExpectImportTableHasRowCount(4);
+ [[EarlGrey selectElementWithMatcher:ImportButton()]
+ assertWithMatcher:grey_sufficientlyVisible()];
+ }
+}
+
/// Tests uploading a file that only contains potential "history" items.
- (void)testUploadPartiallyValidFile {
if (@available(iOS 18.2, *)) {
GoToImportScreen();
LoadFile(SafariDataImportTestFile::kPartiallyValid);
ExpectImportTableHasRowCount(4);
- [[EarlGrey selectElementWithMatcher:
- ImportScreenButtonWithTextId(
- IDS_IOS_SAFARI_IMPORT_IMPORT_ACTION_BUTTON_IMPORT)]
+ [[EarlGrey selectElementWithMatcher:ImportButton()]
performAction:grey_tap()];
[ChromeEarlGrey waitForUIElementToAppearWithMatcher:
ImportScreenButtonWithTextId(
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