CVE-2026-13905
Overview
Files Changed
ios/chrome/browser/settings/ui_bundled/password/reauthentication/local_reauthentication_coordinator_unittest.mmios/chrome/browser/settings/ui_bundled/password/reauthentication/local_reauthentication_view_controller.mm
Patch
From 18161da564cb6a2d6967d815ed66213dc55dfea7 Mon Sep 17 00:00:00 2001 From: Rafał Godlewski <[email protected]> Date: Fri, 15 May 2026 06:54:28 -0700 Subject: [PATCH] [iOS] Make local reauth VC modalInPresentation Fixed: 504192688 Change-Id: Ib55e00787a775c69d8c66fc1a0428698b2c17383 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7844876 Reviewed-by: Alexis Hétu <[email protected]> Commit-Queue: Rafał Godlewski <[email protected]> Cr-Commit-Position: refs/heads/main@{#1631242} --- diff --git a/ios/chrome/browser/settings/ui_bundled/password/reauthentication/local_reauthentication_coordinator_unittest.mm b/ios/chrome/browser/settings/ui_bundled/password/reauthentication/local_reauthentication_coordinator_unittest.mm index c82ae41..5a6e51b6b 100644 --- a/ios/chrome/browser/settings/ui_bundled/password/reauthentication/local_reauthentication_coordinator_unittest.mm +++ b/ios/chrome/browser/settings/ui_bundled/password/reauthentication/local_reauthentication_coordinator_unittest.mm @@ -336,3 +336,18 @@ ASSERT_TRUE(delegate_.successfulReauth); CheckReauthenticationViewControllerNotPresented(); } + +// Tests that the reauth view controller has modalInPresentation set to YES. +TEST_F(ReauthenticationCoordinatorTest, + ReauthViewControllerIsModalInPresentation) { + CheckReauthenticationViewControllerNotPresented(); + + // Simulate start of transition to background state. + scene_state_.activationLevel = SceneActivationLevelForegroundInactive; + + CheckReauthenticationViewControllerIsPresented(); + + UIViewController* topViewController = + base_navigation_controller_.topViewController; + EXPECT_TRUE(topViewController.modalInPresentation); +} diff --git a/ios/chrome/browser/settings/ui_bundled/password/reauthentication/local_reauthentication_view_controller.mm b/ios/chrome/browser/settings/ui_bundled/password/reauthentication/local_reauthentication_view_controller.mm index 44e4862a..5d3950f 100644 --- a/ios/chrome/browser/settings/ui_bundled/password/reauthentication/local_reauthentication_view_controller.mm +++ b/ios/chrome/browser/settings/ui_bundled/password/reauthentication/local_reauthentication_view_controller.mm @@ -34,6 +34,9 @@ // This view does not support large titles as it uses a custom title view. self.navigationItem.largeTitleDisplayMode = UINavigationItemLargeTitleDisplayModeNever; + + // Prevent swiping down the VC on cancelled reauth. + self.modalInPresentation = YES; } return self;
Regression Test / PoC
diff --git a/ios/chrome/browser/settings/ui_bundled/password/reauthentication/local_reauthentication_coordinator_unittest.mm b/ios/chrome/browser/settings/ui_bundled/password/reauthentication/local_reauthentication_coordinator_unittest.mm
index c82ae41..5a6e51b6b 100644
--- a/ios/chrome/browser/settings/ui_bundled/password/reauthentication/local_reauthentication_coordinator_unittest.mm
+++ b/ios/chrome/browser/settings/ui_bundled/password/reauthentication/local_reauthentication_coordinator_unittest.mm
@@ -336,3 +336,18 @@
ASSERT_TRUE(delegate_.successfulReauth);
CheckReauthenticationViewControllerNotPresented();
}
+
+// Tests that the reauth view controller has modalInPresentation set to YES.
+TEST_F(ReauthenticationCoordinatorTest,
+ ReauthViewControllerIsModalInPresentation) {
+ CheckReauthenticationViewControllerNotPresented();
+
+ // Simulate start of transition to background state.
+ scene_state_.activationLevel = SceneActivationLevelForegroundInactive;
+
+ CheckReauthenticationViewControllerIsPresented();
+
+ UIViewController* topViewController =
+ base_navigation_controller_.topViewController;
+ EXPECT_TRUE(topViewController.modalInPresentation);
+}
Original Bug Report
Potential Auth-on-Entry bypass in iOS Password Manager via swipe-to-dismiss race condition
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 without the Chrome Security team. Please see go/chrome-ai-generated-security-bugs-faq for more information.
Overview: A potential race condition in the iOS Password Manager allows bypassing biometric re-authentication. By cancelling a FaceID/TouchID prompt and immediately swipe-dismissing a child modal sheet (like the Widget Promo), an attacker can cause the authentication failure to be swallowed, granting unauthorized access to the underlying password list.
Affected files:
ios/chrome/browser/settings/ui_bundled/password/passwords_coordinator.mmios/chrome/browser/settings/ui_bundled/password/widget_promo_instructions/widget_promo_instructions_coordinator.mmios/chrome/browser/settings/ui_bundled/password/password_details/add_password_coordinator.mmios/chrome/browser/settings/ui_bundled/password/reauthentication/local_reauthentication_coordinator.mmios/chrome/browser/settings/ui_bundled/password/reauthentication/local_reauthentication_view_controller.mm
Estimated timestamp from git blame: 2023-11-23
Description
A potential vulnerability exists in the iOS Password Manager where the ‘Auth-on-Entry’ protection can be bypassed. This is caused by a race condition between the interactive dismissal of a modal sheet and the asynchronous processing of a biometric re-authentication failure.
When opening certain child views in the Password Manager, such as the WidgetPromoInstructionsCoordinator or AddPasswordCoordinator, the application presents a SettingsNavigationController modally. These child coordinators override the default presentation controller delegate (self.settingsNavigationController.presentationController.delegate = self;) but fail to implement the presentationControllerShouldDismiss: method. As a result, iOS defaults to allowing the user to interactively swipe-down to dismiss the modal sheet.
If the application is backgrounded and then foregrounded while one of these child modals is open, the child’s LocalReauthenticationCoordinator pushes a blocking LocalReauthenticationViewController and prompts for FaceID/TouchID.
When an attacker taps ‘Cancel’ on the system biometric prompt, the iOS LAContext executes the reply block, and the ReauthenticationModule dispatches the authentication failure to the main queue asynchronously using dispatch_async (reauthentication_module.mm:88).
Because the biometric prompt takes a moment to animate away, a race condition window is created. If the attacker quickly performs a swipe-down gesture to dismiss the child modal sheet before the main queue processes the async failure block, the child coordinator is stopped. During its teardown, the child coordinator explicitly sets its _reauthCoordinator.delegate = nil (widget_promo_instructions_coordinator.mm:154).
When the asynchronous authentication failure callback finally executes on the main run loop, it attempts to notify its delegate ([_delegate dismissUIAfterFailedReauthenticationWithCoordinator:self]). Because the delegate was set to nil during the swipe-dismiss teardown, the Objective-C message is silently swallowed.
Meanwhile, the parent PasswordsCoordinator has resumed control and called restartReauthCoordinator, which starts a new reauthentication coordinator with authOnStart:NO. This unblocks the UI, leaving the attacker with full, unauthenticated access to the main Password Manager view containing the user’s saved passwords.
Impact
An attacker with local physical access to the unlocked device can potentially bypass the Password Manager’s re-authentication gate (Auth-on-Entry) if the app is backgrounded while one of the affected modal sheets is open. This leads to unauthorized access to the user’s saved passwords.
Potential Reproduction Steps
Note: Our tooling agent does not have the ability to run code or perform manual UI interaction; these are the suggested steps an attacker would follow to trigger the vulnerability based on static analysis.
- Open Settings -> Password Manager and authenticate successfully.
- Open either the Password Manager Widget promo (‘Show me how’) or the ‘Add Password’ (+) screen. This presents a modal sheet.
- Background the app (e.g., return to the home screen).
- Foreground the app. A FaceID/TouchID prompt will appear.
- Tap Cancel on the biometric prompt.
- Immediately perform a fast swipe-down gesture to dismiss the modal sheet before the failure handler processes the cancellation.
- If timed correctly, the modal sheet dismisses, the child coordinator is stopped, and the main saved-password list is revealed without requiring a new authentication challenge.
Suggested Fix
To prevent the interactive dismissal of the modal sheet during re-authentication, the affected child coordinators (e.g., WidgetPromoInstructionsCoordinator, AddPasswordCoordinator) should implement the UIAdaptivePresentationControllerDelegate method presentationControllerShouldDismiss:.
Alternatively, LocalReauthenticationViewController could set self.isModalInPresentation = YES; to natively prevent iOS from allowing the swipe-to-dismiss gesture while the blocking authentication view is on the screen.
Evaluated with Chrome root at commit: 7353d249d9cacf9c7218e1d7b8a39cf39c72d646
Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; 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.