CVE-2026-28624 AI-generated analysis
platform/packages/apps/Settings, password/ConfirmDeviceCredentialActivity and biometrics/fingerprint/FingerprintSettings (mandatory biometrics / identity check flow)
Removes the EXTRA_DATA pass-through from ConfirmDeviceCredentialActivity, so the activity no longer echoes a caller-supplied Intent back as its RESULT_OK result, and makes FingerprintSettings keep the gatekeeper password handle in its own state instead of reading it from the returned Intent.
Root cause
ConfirmDeviceCredentialActivity read a Parcelable Intent from its launching intent (intent.getParcelableExtra(EXTRA_DATA, Intent.class)) into mIntentData. On successful authentication its callback called setResult(Activity.RESULT_OK, mIntentData), returning whatever Intent the launcher supplied. FingerprintSettings used this channel to pass the credential result Intent (which contains the gatekeeper password handle) through the biometric prompt via Utils.launchBiometricPromptForMandatoryBiometrics(..., data). On BIOMETRIC_AUTH_REQUEST RESULT_OK it called handleAuthenticationSuccessful(data), which extracted the gatekeeper password handle from the returned Intent and used it for requestGatekeeperHat / addFirstFingerprint. The activity therefore echoed a caller-controlled Intent as its result, and the fingerprint settings flow trusted the content of that returned Intent as authentication state.
How the fix works
- src/com/android/settings/password/ConfirmDeviceCredentialActivity.java: deletes the EXTRA_DATA constant and the mIntentData field. onCreate no longer reads the EXTRA_DATA extra, and the authentication success callback now calls setResult(Activity.RESULT_OK) with no result Intent.
- src/com/android/settings/Utils.java: removes the launchBiometricPromptForMandatoryBiometrics(Fragment, int, int, boolean, Intent data) overload and the EXTRA_DATA static import. getIntentForBiometricAuthentication(Resources, int, boolean) loses its data parameter and no longer calls intent.putExtra(EXTRA_DATA, data). The remaining Fragment and Activity launch helpers are updated to the new signature.
- src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java: adds a long mGkPwHandle field. It is saved under KEY_GK_PW_HANDLE ("gk_pw_handle") in onSaveInstanceState and restored from savedInstanceState. In onActivityResult for the credential result, when BiometricUtils.containsGatekeeperPasswordHandle(data) is true, it stores mGkPwHandle = BiometricUtils.getGatekeeperPasswordHandle(data) and then launches the biometric prompt without data. The NOT_ACTIVE path calls handleAuthenticationSuccessful(mGkPwHandle).
- FingerprintSettings.java onActivityResult, BIOMETRIC_AUTH_REQUEST branch: on RESULT_OK it no longer reads the returned Intent. If mGkPwHandle == 0L it logs "Gatekeeper password not set."; otherwise it calls handleAuthenticationSuccessful(mGkPwHandle).
- FingerprintSettings.java handleAuthenticationSuccessful: the signature changes from (Intent data) to (long gkPwHandle). addFirstFingerprint(gkPwHandle), provider.requestGatekeeperHat(gkPwHandle, challenge, mUserId) and provider.removeGatekeeperPasswordHandle(gkPwHandle) now use the locally held handle.
Completeness
- The fix removes the echo channel at the activity instead of validating it: the success setResult changed in the diff no longer attaches any Intent. This covers launchers of ConfirmDeviceCredentialActivity and InternalActivity on that path, not only the Settings-internal mandatory biometrics flow.
- The producer of EXTRA_DATA (Utils.getIntentForBiometricAuthentication, which had no other callers per index search) and the consumer of the returned data (FingerprintSettings) are updated in the same commit. Removing the public EXTRA_DATA constant and the Utils overload would cause any remaining in-tree user to fail to compile.
- The mGkPwHandle == 0L guard is only in the BIOMETRIC_AUTH_REQUEST branch. The NOT_ACTIVE path passes mGkPwHandle right after setting it inside the containsGatekeeperPasswordHandle(data) check.
- The diff does not clear mGkPwHandle after use in handleAuthenticationSuccessful, and the value stays in saved instance state. This is a state hygiene point; it does not reintroduce the external channel.
Behavior changes
- The ConfirmDeviceCredentialActivity success result now carries no data Intent (setResult(RESULT_OK) only). A launcher that relied on getting EXTRA_DATA back no longer receives it.
- Utils.launchBiometricPromptForMandatoryBiometrics(Fragment, int, int, boolean, Intent) is removed (API change inside Settings).
- FingerprintSettings saves a new long "gk_pw_handle" in its instance state. If the biometric prompt returns RESULT_OK with no stored handle, it logs "Gatekeeper password not set." and skips enrollment and HAT generation.
Per major
- Android 14: Not affected per the bulletin. At android-security-14.0.0_r29 only 3/8 lines of the patched region are present, which suggests the EXTRA_DATA / mandatory biometrics code is largely absent.
- Android 15: Not affected per the bulletin. At android-security-15.0.0_r17 only 3/8 lines of the patched region are present, which suggests the EXTRA_DATA pass-through is largely absent.
- Android 16: Same fix: commit 192ccfdcab3a in android-security-16.0.0_r8 (resolved by bug_id), covering the three files described above.
- Android 17: Same fix: commit d800aed5a84f in android-security-17.0.0_r1 (resolved by bug_id). The changes are the same; only the surrounding code differs: on 17, getIntentForBiometricAuthentication has no mandatoryBiometrics flag check, and the FingerprintSettings branch has no IdentityCheckBiometricErrorDialog else-if.
Unknowns
- The material does not show whether ConfirmDeviceCredentialActivity (as opposed to InternalActivity) is exported and reachable from third-party apps.
- Neither the bulletin nor the diff describes the exact privilege gained. Only the removal of the pass-through is evident.
- Whether the FingerprintSettings path that trusted the result was itself the exploited path, or was only hardened alongside the activity change.
- Whether ConfirmDeviceCredentialActivity returns any other launcher-supplied extras on other result paths. The diff context only shows the success setResult call that was changed.