CVE-2025-0077 AI-generated analysis
platform/frameworks/base, ActivityManager multi-user switching (services/core/java/com/android/server/am/UserController.java, core/java/android/app/IUserSwitchObserver.aidl)
Restores locking the device (keyguard shown and waited for) at the beginning of a foreground user switch in UserController.startUser, moves the IUserSwitchObserver.onBeforeUserSwitching dispatch to that same early point, and makes onBeforeUserSwitching a synchronous binder call so observers (SystemUI user tracking) have updated the current user before the keyguard is locked.
Root cause
Android 15 carried commit ad64f73046717374d4f9bc613307d01e93e19ebe ("Move showing keyguard after the UserSwitchObservers"), which removed the lock at the start of UserController.startUser (foreground path) and instead called showKeyguard()/dismissKeyguard() from completeUserSwitch via runWithTimeout, i.e. only at the end of the switch after the observers. The revert's message states this caused b/360838273 and can cause security issues, and that the keyguard must be shown at the beginning of the user switch as before. In addition, IUserSwitchObserver was a fully oneway interface, so onBeforeUserSwitching returned before SystemUI's UserTrackerImpl updated its notion of the current user. When switching from a user with lock "None" to a user with credentials, the keyguard could believe the current user was the old unlocked user and skip showing (per commit 3b04c948727c message). The onBeforeUserSwitching loop also lived in dispatchUserSwitch, after setCurrentUser and the lock point.
How the fix works
- Commit a09b6451c99f (revert), UserController.java:startUser (foreground branch): after mInjector.getWindowManager().setCurrentUser(userId) and reportCurWakefulnessUsageEvent(), when userSwitchUiEnabled it calls setSwitchingUser(true) and, if mInjector.getKeyguardManager().isDeviceSecure(userId), mInjector.lockDeviceNowAndWaitForKeyguardShown() before the switch proceeds.
- Commit a09b6451c99f, UserController.Injector: adds lockDeviceNowAndWaitForKeyguardShown(). It returns immediately if getWindowManager().isKeyguardLocked(). Otherwise it registers an ActivityTaskManagerInternal.ScreenObserver, calls getWindowManager().lockDeviceNow(), and blocks on a CountDownLatch until onKeyguardStateChanged(true). It throws RuntimeException if the keyguard is not shown within 20 seconds or if interrupted. The observer is unregistered in finally.
- Commit a09b6451c99f, UserController.java:completeUserSwitch: removes showKeyguard()/runWithTimeout()/SHOW_KEYGUARD_TIMEOUT_MS and restores the await() chain: dismissKeyguard only when UI is enabled and the target user is not secure, then dismissUserSwitchDialog, then REPORT_USER_SWITCH_COMPLETE_MSG. Injector.dismissKeyguard regains a 2 s DISMISS_KEYGUARD_TIMEOUT_MS runOnce fallback posted via mHandler.
- Commit 37a4df78c7e1, UserController.java:startUser (foreground branch): adds dispatchOnBeforeUserSwitching(userId) right after updateProfileRelatedCaches() and before setCurrentUser/lockDeviceNowAndWaitForKeyguardShown. The new private dispatchOnBeforeUserSwitching() iterates mUserSwitchObservers and calls onBeforeUserSwitching(newUserId) with tracing. The equivalent loop is removed from dispatchUserSwitch.
- Commit 3b04c948727c, IUserSwitchObserver.aidl: drops the interface-level oneway so onBeforeUserSwitching(int) becomes a synchronous binder call. onUserSwitching, onUserSwitchComplete, onForegroundProfileSwitch and onLockedBootComplete are individually marked oneway. Per the commit message, this makes the system wait for onBeforeUserSwitching calls to complete before lockDeviceNowAndWaitForKeyguardShown runs.
- UserControllerTest.java: the keyguard-shown test now runs startUser on a thread and asserts REPORT_USER_SWITCH_MSG is not sent until onKeyguardStateChanged(true). onBeforeUserSwitching is now verified after startUser rather than after dispatchUserSwitch. testRuntimeExceptionIsThrownIfTheKeyguardIsNotShown is removed.
Completeness
- Covers the foreground startUser path only: locking happens only when userSwitchUiEnabled is true and isDeviceSecure(userId) is true for the target user. If the user switch UI is disabled no lock is performed at the start of the switch.
- The revert's commit message says it must be submitted together with a proper fix in WindowManagerService.lockDeviceNow(). That companion change is not in the provided material, so whether the WindowManager side is complete cannot be judged from the code shown.
- The ordering guarantee depends on observers doing their current-user update inside the now synchronous onBeforeUserSwitching. onUserSwitching and later callbacks remain oneway.
- At android-security-15.0.0_r17 the code has moved on from the r6 fix. IUserSwitchObserver.aidl is again oneway with onBeforeUserSwitching(int newUserId, IRemoteCallback reply). In the shown startUser region, updateProfileRelatedCaches() is directly followed by setCurrentUser(userId) without a visible dispatchOnBeforeUserSwitching(userId) call (7/8 patched lines present). The lockDeviceNowAndWaitForKeyguardShown call is present at r17.
Behavior changes
- startUser now blocks the calling thread for up to 20 seconds waiting for the keyguard to be shown when switching to a secure user with switch UI enabled. If the keyguard is not shown within 20 s (or the wait is interrupted), Injector.lockDeviceNowAndWaitForKeyguardShown throws a RuntimeException.
- In the r6 fix onBeforeUserSwitching is a synchronous binder call, so slow observers directly delay the user switch in system_server. RemoteException is ignored.
- IUserSwitchObserver.aidl transaction semantics changed (the interface is no longer oneway at top level and the other methods are explicitly oneway), affecting all observer implementations built against this hidden AIDL.
- Observers receive onBeforeUserSwitching earlier (before WindowManager setCurrentUser and before the lock) and no longer from dispatchUserSwitch.
- Injector.dismissKeyguard callbacks are posted to mHandler with a 2 s DISMISS_KEYGUARD_TIMEOUT_MS fallback that runs the continuation at most once. SHOW_KEYGUARD_TIMEOUT_MS and showKeyguard()/runWithTimeout() are removed.
Per major
- Android 14: Not affected per the bulletin. At android-security-14.0.0_r29, 8/8 lines of the patched region are present.
- Android 15: Fixed in android-security-15.0.0_r6 by three frameworks/base commits: a09b6451c99f (revert, restores early lockDeviceNowAndWaitForKeyguardShown), 37a4df78c7e1 (early dispatchOnBeforeUserSwitching) and 3b04c948727c (synchronous onBeforeUserSwitching AIDL). At android-security-15.0.0_r17 the lock call is present (7/8 lines), but the AIDL has since changed to an IRemoteCallback reply form and the direct dispatchOnBeforeUserSwitching call is not visible in the shown region.
- Android 16: Not affected per the bulletin. At android-security-16.0.0_r8, 8/8 lines of the patched region are present.
- Android 17: Not affected per the bulletin. At android-security-17.0.0_r1, 7/8 lines of the patched region are present.
Unknowns
- The exact security consequence is only referenced via b/360838273#comment22 and is not described in the material.
- The companion WindowManagerService.lockDeviceNow() fix mentioned by the revert is not included.
- Whether the dispatchOnBeforeUserSwitching(userId) call still exists elsewhere in startUser at android-security-15.0.0_r17, and which later change introduced the IRemoteCallback-based onBeforeUserSwitching.
- Which Android majors ever carried commit ad64f73046717374d4f9bc613307d01e93e19ebe, and which patched-region line differs in 15 r17 and 17 r1.