CVE-2025-48568 AI-generated analysis
platform/frameworks/base, ActivityManager UserController and SystemUI keyguard (KeyguardViewMediator, KeyguardUnlockAnimationController)
Makes user switching to a secure user wait for a SystemUI lock-now callback, and makes SystemUI reject keyguard exit animations whose going-away request was made for a user other than the current one. This keeps the keyguard from being dismissed across a user switch.
Root cause
UserController's lockDeviceNowAndWaitForKeyguardShown returned early if WindowManager already reported the keyguard as locked. Otherwise it waited for a generic ScreenObserver keyguard-shown event. When the user switch UI was enabled, completeUserSwitch also called dismissKeyguard for non-secure target users. In SystemUI, KeyguardViewMediator did not record which user a keyguardGoingAway request belonged to. As the added test shows, a going-away request made for one user could lead to an exit animation after a switch to another, secure user, and that animation could hide the keyguard. doKeyguardLocked also short-circuited (reset only) when the keyguard was showing but already going away. setCurrentUser did not cancel pending DISMISS/HIDE work or force the keyguard to show for secure users.
How the fix works
- KeyguardManager: adds the hidden constant LOCK_ON_USER_SWITCH_CALLBACK ("onSwitchCallback").
- UserController.Injector.lockDeviceNowAndWaitForKeyguardShown: removes the isKeyguardLocked() early return and the ScreenObserver. It now calls getWindowManager().lockNow(bundle) with an IRemoteCallback stored under LOCK_ON_USER_SWITCH_CALLBACK and waits on a latch. If no callback arrives within 20 seconds it throws a RuntimeException (the commit message describes this as crashing the system).
- UserController.completeUserSwitch: removes the await chain, the Injector.dismissKeyguard step and DISMISS_KEYGUARD_TIMEOUT_MS. It now only dismisses the user switch dialog (when the switch UI is enabled) and then sends REPORT_USER_SWITCH_COMPLETE_MSG.
- KeyguardViewMediator.doKeyguardLocked: when options carry the callback binder, it registers a LockNowCallback for the current user. The early-return paths (externally disabled, already showing, not provisioned, lockscreen disabled) call notifyLockNowCallback. If the keyguard is showing but going away, it no longer short-circuits and re-shows the keyguard instead.
- KeyguardViewMediator.updateActivityLockScreenState (when showing) and handleShow (not-system-ready path) call notifyLockNowCallback. notifyLockNowCallback sends the result only for callbacks whose user id matches the current user.
- KeyguardViewMediator.setCurrentUser: on a real user change, it removes pending mDismissToken, DISMISS and HIDE messages, resets keyguard-done state, adjusts the status bar and clears the going-away state. For secure users it sets mIsKeyguardExitAnimationCanceled and pending lock, calls ActivityTaskManager setLockScreenShown(true, ...) and then doKeyguardLocked(null). For other users it resets state.
- KeyguardViewMediator: records mGoingAwayRequestedForUserId in mKeyguardGoingAwayRunnable and showSurfaceBehindKeyguard. On a user mismatch, handleStartKeyguardExitAnimation marks the exit animation canceled, finishes the remote animation, clears mHiding, and then either re-locks (secure user) or resets and dismisses. On a mismatch, handleCancelKeyguardExitAnimation sets pending lock.
- KeyguardViewMediator.exitKeyguardAndFinishSurfaceBehindRemoteAnimation: returns early if mIsKeyguardExitAnimationCanceled is set, both before and inside the post-traversal step. In the post-traversal step it re-locks if the device is not interactive and no lock is pending. The parameter is renamed from cancelled to showKeyguard. finishSurfaceBehindRemoteAnimation now notifies KeyguardUnlockAnimationController.
- KeyguardViewMediator.dismiss ignores requests while the keyguard is going away. The old onUserSwitching/onUserSwitchComplete KeyguardUpdateMonitor callbacks are replaced by a UserTracker onUserChanged callback. If the user is still current and non-secure, it dismisses after a 500 ms delay tied to mDismissToken.
- KeyguardUnlockAnimationController.notifyFinishedKeyguardExitAnimation(showKeyguard): forces the surface behind to full alpha and sets the launcher unlock amount only when showKeyguard is false.
Completeness
- Covers both sides: the system_server user-switch handshake (UserController) and the SystemUI keyguard state machine (user validation for going-away and exit animations).
- The user-id validation applies to the legacy keyguardGoingAway path in KeyguardViewMediator. On Android 15 the checks are skipped when KeyguardWmStateRefactor is enabled. The shown part of the diff does not change that path (WmLockscreenVisibilityManager), but the diff is truncated.
- Per index search, CarKeyguardViewMediator (packages/apps/Car/SystemUI) extends KeyguardViewMediator. This commit does not touch it, and the material does not show whether it overrides the changed methods.
Behavior changes
- system_server throws a RuntimeException if SystemUI does not deliver the LOCK_ON_USER_SWITCH_CALLBACK result within 20 seconds of lockNow during a user switch.
- lockDeviceNowAndWaitForKeyguardShown no longer skips lockNow when the keyguard already reports as locked.
- UserController no longer dismisses the keyguard for non-secure users. SystemUI dismisses it 500 ms after the user change, if the user is still current and non-secure.
- Exit animations whose going-away request was made for a different user are aborted, and the keyguard is re-shown or re-evaluated.
- dismiss() calls are ignored while the keyguard is going away.
Per major
- Android 14: Fixed by frameworks/base ec3f9fa9145c in android-security-14.0.0_r24. The lock-on-switch logic is in setCurrentUser, with a UserTracker onUserChanged callback. The callback handshake is in doKeyguardLocked and notifyLockNowCallback. At r29, 6 of the 8 patched-region lines are present.
- Android 15: Adapted fix bec2efb2d714 in android-security-15.0.0_r12. The shown diff adds UserTracker.isUserSwitching and the BEFORE_USER_SWITCHING/USER_SWITCHING handlers (handleBeforeUserSwitching, handleUserSwitching). It uses mSelectedUserInteractor and gates the user-id checks on !KeyguardWmStateRefactor.isEnabled(). Unlike 14, its handleUserSwitchComplete dismisses (shows the bouncer) for secure users. All 8 patched-region lines are present at r17.
- Android 16: Not affected (not listed by the bulletin). 6/8 patched-region lines present at android-security-16.0.0_r8.
- Android 17: Not affected (not listed by the bulletin). 6/8 patched-region lines present at android-security-17.0.0_r1.
Unknowns
- The material does not describe the exact trigger or attack scenario.
- The material does not show why Android 14 r29 has only 6 of the 8 patched-region lines (later rework or partial revert).
- The material does not show whether the KeyguardWmStateRefactor path on 15 needs an equivalent check.
- The last 175 lines of the Android 15 diff are truncated, including possible setCurrentUser or other changes.
- The material does not show whether CarKeyguardViewMediator or OEM subclasses override the changed methods.