CVE-2025-22422 AI-generated analysis
platform/frameworks/base (android.hardware.biometrics BiometricPrompt/PromptInfo, SystemUI biometrics AuthController/AuthContainerView/AuthDialog) and platform/packages/apps/Settings (password/BiometricFragment)
SystemUI's AuthController now dismisses a BiometricPrompt owned by Settings' ConfirmDeviceCredentialActivity when the top activity has a different class name. It does this by carrying the owning activity's class name in PromptInfo and comparing it with the top task's component, instead of comparing package names only.
Root cause
In packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java, the task-stack-change handler evicted the current biometric dialog only when runningTasks.get(0).topActivity.getPackageName() differed from mCurrentDialog.getOpPackageName() and the client was not system. When an app uses createConfirmDeviceCredentialIntent(), Settings' ConfirmDeviceCredentialActivity (via BiometricFragment) requests the prompt, so the client package is Settings. If the app then switches to Settings, the top package still equals the client package and the prompt is not cancelled. Per the commit message, this leaves 'settings activity showing background bp'.
How the fix works
- frameworks/base core/java/android/hardware/biometrics/PromptInfo.java: adds a new parceled field, mClassNameIfItIsConfirmDeviceCredentialActivity (default null), written and read at the end of the parcel. It also adds a package-private setter setClassNameIfItIsConfirmDeviceCredentialActivity(String) and a public getter. containsTestConfigurations() now returns true when this field is non-null, in the same chain as mAllowBackgroundAuthentication.
- frameworks/base core/java/android/hardware/biometrics/BiometricPrompt.java: adds the hidden method Builder.setClassNameIfItIsConfirmDeviceCredentialActivity(), annotated @RequiresPermission(anyOf = {TEST_BIOMETRIC, USE_BIOMETRIC_INTERNAL}). It stores mContext.getClass().getName() into the PromptInfo.
- frameworks/base packages/SystemUI AuthDialog.java / AuthContainerView.java: adds getClassNameIfItIsConfirmDeviceCredentialActivity() to the AuthDialog interface. AuthContainerView returns mConfig.mPromptInfo.getClassNameIfItIsConfirmDeviceCredentialActivity().
- frameworks/base packages/SystemUI AuthController.java: the task-stack-change handler is refactored to call the new isOwnerInBackground(). That method keeps the package rule: the owner is in background if the top package differs from the client and the client is not system. A null or empty task list returns false, and a null top package counts as a match. The method also adds a CDCA rule: if the dialog's class name is non-null and topActivity.getClassName() is non-null and different, isInBackground is true regardless of the package or system exemption. When the result is true, the dialog is dismissed without callback, callbacks receive onBiometricPromptDismissed(), and mReceiver.onDialogDismissed(DISMISSED_REASON_USER_CANCEL, null) is sent.
- packages/apps/Settings src/com/android/settings/password/BiometricFragment.java onCreate(): the BiometricPrompt.Builder chain gains .setClassNameIfItIsConfirmDeviceCredentialActivity(). The prompt therefore records the class name of the fragment's getContext().
Completeness
- The class-name check applies only to prompts whose PromptInfo has the class name set. In the material, only Settings' BiometricFragment sets it. Clients without it are still checked by package name only.
- The system-client exemption (Utils.isSystem(mContext, clientPackage)) is preserved, except in the CDCA class-name case, which overrides it.
- isOwnerInBackground() is lenient when data is missing. A null or empty task list means no eviction. A null top package satisfies the package rule. A null top class name disables the CDCA class check.
- The recorded class name is mContext.getClass().getName() from the fragment's context. The comparison succeeds only if it equals topActivity.getClassName(). The material does not show which Context getContext() returns in all configurations.
- Setting the class name makes containsTestConfigurations() return true, and the Builder method is annotated with TEST_BIOMETRIC/USE_BIOMETRIC_INTERNAL. Whether and where this is enforced at runtime is not shown in the material.
Behavior changes
- A biometric prompt started by ConfirmDeviceCredentialActivity is now dismissed when the top activity's class name differs from the recorded one. The receiver gets DISMISSED_REASON_USER_CANCEL and callbacks get onBiometricPromptDismissed.
- The PromptInfo parcel layout gains a trailing String field.
- New hidden API: BiometricPrompt.Builder.setClassNameIfItIsConfirmDeviceCredentialActivity(). PromptInfo.containsTestConfigurations() returns true when the class name is set.
- Log output changes ('Evicting client due to top activity is not : <pkg>', 'isInBackground <bool>').
Per major
- Android 14: Same fix: platform/frameworks/base 6894ba3da6da plus platform/packages/apps/Settings 1c0ab9c696d7 in android-security-14.0.0_r19 (class-name based check). 3/3 lines of the patched region are present at android-security-14.0.0_r29.
- Android 15: Adapted fix: platform/packages/apps/Settings 32915818d780 in android-security-15.0.0_r7. It replaces setComponentNameForConfirmDeviceCredentialActivity(callingActivity) with setRealCallerForConfirmDeviceCredentialActivity(...), set from getCallingActivity() in ConfirmDeviceCredentialActivity. The matching frameworks/base change for 15 is not in the material. 8/8 lines are present at android-security-15.0.0_r17.
- Android 16: Not affected per the bulletin. 8/8 lines of the patched region are present at android-security-16.0.0_r8.
- Android 17: Not affected per the bulletin. 8/8 lines of the patched region are present at android-security-17.0.0_r1.
Unknowns
- The frameworks/base side of the Android 15 fix and the Android 13 fix are not in the material.
- Whether and where a TEST_BIOMETRIC/USE_BIOMETRIC_INTERNAL gate is enforced for PromptInfo with test configurations is not shown.
- What Utils.isSystem(mContext, clientPackage) treats as 'system', and whether Settings qualifies, is not shown.
- Whether any caller other than Settings' BiometricFragment sets the class name is not shown.
- The name of the AuthController method that holds the eviction logic is not shown in the diff.
- The concrete security impact, beyond the bulletin's High/EoP rating and the commit's description, is not shown.