CVE-2025-48602 AI-generated analysis
platform/frameworks/base, SystemUI keyguard (KeyguardViewMediator)
KeyguardViewMediator now drops any queued START_KEYGUARD_EXIT_ANIM message on user switch and, when the exit animation has been flagged as canceled, the post-traversal completion callback finishes the remote animation with the keyguard showing and forces the showing state instead of only logging and returning.
Root cause
In packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java, a user switch (setCurrentUser on Android 14) removed pending DISMISS and HIDE messages and, for a secure target user, set mIsKeyguardExitAnimationCanceled, but it did not remove a queued START_KEYGUARD_EXIT_ANIM message, so an exit animation requested before the switch could still start afterwards. In addition, in exitKeyguardAndFinishSurfaceBehindRemoteAnimation the runnable posted via DejankUtils.postAfterTraversal only logged and returned when mIsKeyguardExitAnimationCanceled was true; it neither finished the remote animation with the keyguard showing (finishSurfaceBehindRemoteAnimation(true)) nor forced the mediator showing state back to true.
How the fix works
- KeyguardViewMediator.java:setCurrentUser (Android 14): adds mHandler.removeMessages(START_KEYGUARD_EXIT_ANIM) next to the existing removal of DISMISS and HIDE, so an exit animation queued before the user switch is not started afterwards. The pre-existing branch for a secure new user still sets mIsKeyguardExitAnimationCanceled = true, calls setPendingLock(true), ActivityTaskManager.setLockScreenShown(true, mAodShowing) and doKeyguardLocked(null).
- KeyguardViewMediator.java: adds a @VisibleForTesting postAfterTraversal(Runnable) wrapper around DejankUtils.postAfterTraversal so the deferred callback can be captured by tests; exitKeyguardAndFinishSurfaceBehindRemoteAnimation now calls this wrapper.
- KeyguardViewMediator.java:exitKeyguardAndFinishSurfaceBehindRemoteAnimation: inside the posted runnable, the mIsKeyguardExitAnimationCanceled check is moved after the (!mPM.isInteractive() && !mPendingLock) check and its action is changed from a bare return to finishSurfaceBehindRemoteAnimation(true /* showKeyguard */) followed by setShowingLocked(true /* showing */, true /* force */) and then return; onKeyguardExitFinished is not reached.
- KeyguardViewMediatorTest.java: adds testGoingAwayFollowedByBeforeUserSwitchWithDelayedExitAnimationDoesNotHideKeyguard, which starts an exit animation for an insecure user, requests keyguardDonePending/readyForKeyguardDone, switches to a secure user, then runs the captured post-traversal runnable and verifies notifyFinishedKeyguardExitAnimation(true) and isShowingAndNotOccluded(); the test constructor overrides postAfterTraversal to capture the runnable. An existing test gains keyguardDonePending/readyForKeyguardDone calls, and testRegisterDumpable is relaxed to startsWith on the dumpable name.
Completeness
- Covers two points: the START_KEYGUARD_EXIT_ANIM handler message queued before the switch (dropped), and the post-traversal completion callback of an exit animation already in flight (now finishes the remote animation with keyguard showing and forces the mediator showing state).
- On Android 14, mIsKeyguardExitAnimationCanceled is set only in the mLockPatternUtils.isSecure(newUserId) branch of setCurrentUser (pre-existing code); switching to a non-secure user takes resetStateLocked() and does not set the flag. On Android 15/16 the fix sets the flag unconditionally in onBeforeUserSwitching.
- The canceled check is performed only inside the deferred runnable; on Android 14 the code that runs before posting (e.g. InteractionJankMonitor end) is unchanged.
- Where and when mIsKeyguardExitAnimationCanceled is reset to false is not shown in the material, so whether a stale flag can affect a later legitimate unlock cannot be judged from the diff.
- Other handler messages (DISMISS/HIDE) were already removed on user switch before this commit and are not modified.
Behavior changes
- A user switch now also discards any pending START_KEYGUARD_EXIT_ANIM message, so an exit animation requested before the switch will not start.
- When the exit animation has been canceled, the deferred completion callback now calls finishSurfaceBehindRemoteAnimation(true) (in the test observable as notifyFinishedKeyguardExitAnimation(true)) and forces setShowingLocked(true, true), instead of only logging.
- The canceled check now runs after the non-interactive/no-pending-lock check inside the posted runnable, so that error path takes precedence.
- New package-private @VisibleForTesting method postAfterTraversal(Runnable) on KeyguardViewMediator; no change to DejankUtils itself.
Per major
- Android 14: Fixed by 1c3c01a414a0 in android-security-14.0.0_r24 (cherry-pick of f17b66e7). Adapted fix: removeMessages(START_KEYGUARD_EXIT_ANIM) added to setCurrentUser (the flag mIsKeyguardExitAnimationCanceled was already set there for secure users); canceled branch inside the posted runnable now finishes the remote animation with showKeyguard=true and forces setShowingLocked(true, true). At android-security-14.0.0_r29, 8/8 lines of the patched region are present.
- Android 15: Fixed by 67769a49e360 in android-security-15.0.0_r12. Adapted: removeMessages(START_KEYGUARD_EXIT_ANIM) and an unconditional mIsKeyguardExitAnimationCanceled = true are added to the onBeforeUserSwitching handler (synchronized block) rather than setCurrentUser; same postAfterTraversal wrapper and canceled-branch change (2-arg setShowingLocked). At android-security-15.0.0_r17, 8/8 lines present.
- Android 16: Fixed by c69e16c3efda in android-security-16.0.0_r3. Adapted: same onBeforeUserSwitching change as 15; additionally the pre-existing early canceled check located before the post is removed and replaced by the check inside the runnable; uses 3-arg setShowingLocked(showing, force, reason) and onKeyguardExitFinished(reason). At android-security-16.0.0_r8, 8/8 lines present.
- Android 17: Not affected per the bulletin (Android 17 not listed). At android-security-17.0.0_r1, 7/8 lines of the patched region are present.
Unknowns
- Where mIsKeyguardExitAnimationCanceled is reset to false (not in the diff or context).
- Body of finishSurfaceBehindRemoteAnimation and of the non-interactive error branch inside the posted runnable (not shown).
- How the user switch is initiated in the reported scenario and which caller invokes setCurrentUser on Android 14.
- What was visible to the user in the unfixed race (not shown in the material).
- Why Android 17 is listed as not affected while 7/8 patched lines are present at android-security-17.0.0_r1.