CVE-2024-32896 AI-generated analysis
platform/frameworks/base, RecoverySystemService / android.security.AndroidKeyStoreMaintenance; platform/system/sepolicy, system_server keystore2 policy
RecoverySystemService.rebootRecoveryWithCommand() now destroys all KeyMint keys via the new AndroidKeyStoreMaintenance.deleteAllKeys() (IKeystoreMaintenance.deleteAllKeys) before rebooting to recovery when the command contains "--wipe_data", with a matching SELinux allow for system_server, so FBE-encrypted data becomes unrecoverable even if the recovery data wipe is interrupted or skipped.
Root cause
Before the fix, a factory reset request through RecoverySystemService.rebootRecoveryWithCommand() only wrote the command (including "--wipe_data") into the BCB via setupOrClearBcb(true, command) and then called PowerManager.reboot(REBOOT_RECOVERY), leaving data destruction to recovery after the reboot. The Keystore/KeyMint keys that protect the synthetic password blobs (used by LockSettingsService to derive FBE keys) and the keys protecting DE and metadata encryption keys (used by vold) stayed intact until recovery ran. Per the commit messages, if the recovery wipe was interrupted or skipped, the FBE-encrypted data could still be recovered.
How the fix works
- platform/system/sepolicy private/system_server.te and prebuilts/api/34.0/private/system_server.te: adds delete_all_keys to the existing 'allow system_server keystore:keystore2 { ... }' rule so system_server may invoke IKeystoreMaintenance.deleteAllKeys().
- keystore/java/android/security/AndroidKeyStoreMaintenance.java: new static deleteAllKeys() that calls StrictMode.noteDiskWrite(), then getService().deleteAllKeys() on the android.security.maintenance IKeystoreMaintenance binder. RemoteException and NullPointerException are converted to KeyStoreException(SYSTEM_ERROR); ServiceSpecificException is converted to KeyStoreException(e.errorCode).
- services/core/java/com/android/server/recoverysystem/RecoverySystemService.java rebootRecoveryWithCommand(): adds constant RECOVERY_WIPE_DATA_COMMAND = "--wipe_data" and computes isForcedWipe = command != null && command.contains(RECOVERY_WIPE_DATA_COMMAND). Inside the sRequestLock block, after setupOrClearBcb(true, command) succeeds and before pm.reboot(PowerManager.REBOOT_RECOVERY), it calls deleteSecrets() when isForcedWipe is true.
- RecoverySystemService.deleteSecrets(): logs Slogf.w(TAG, "deleteSecrets"), calls AndroidKeyStoreMaintenance.deleteAllKeys(), and on android.security.KeyStoreException logs Log.wtf and returns; the reboot still proceeds.
Completeness
- Key destruction runs only when the command string contains "--wipe_data"; recovery commands without that substring do not trigger deleteSecrets().
- Only the RecoverySystemService.rebootRecoveryWithCommand() path is covered; the material shows no other path that writes a wipe command to the BCB.
- Failure of deleteAllKeys() is only logged with Log.wtf; the device still reboots to recovery with the wipe deferred, so in that failure case the pre-fix behavior remains.
- deleteSecrets() runs only if setupOrClearBcb(true, command) succeeded; if BCB setup fails the method returns early without rebooting.
- The sepolicy change grants system_server the delete_all_keys permission the Java call relies on; any failure of the call is logged and not retried.
Behavior changes
- Factory reset via rebootRecoveryWithCommand with "--wipe_data" now deletes all keys in all KeyMint devices (including synthetic password protector keys and vold DE/metadata protector keys) before the reboot, intended to make FBE-encrypted data unrecoverable even if recovery does not complete the wipe.
- New warning log "deleteSecrets" from RecoverySystemService, and Log.wtf "Failed to delete all keys from keystore." on failure.
- system_server gains the keystore2 delete_all_keys permission in SELinux policy.
- Per the commit test notes, if the wipe is interrupted after the fix, the device reboots into recovery and prompts for a factory reset ('Cannot load Android system. Your data may be corrupt. ...').
Per major
- Android 14: same fix: frameworks/base 8b7b2c66ca96 and sepolicy 92cbf8d4f18c in android-security-14.0.0_r12 (Merged-In I5eb8e97f..., I5be2f9e8...); at android-security-14.0.0_r29 8/8 lines of the patched region present.
- Android 15: resolved at base (android-15.0.0_r1) per per-major facts; 6/6 lines of the patched region present at android-security-15.0.0_r17. The commit listed for 15, platform/build/soong 7ee98928ea1b ('Add secretkeeper-v1-java to platform APIs'), does not contain the deleteSecrets/deleteAllKeys logic.
- Android 16: not affected (not listed by the bulletin); 8/8 lines of the patched region present at android-security-16.0.0_r8.
- Android 17: not affected (not listed by the bulletin); 8/8 lines of the patched region present at android-security-17.0.0_r1.
Unknowns
- Whether other paths exist that write a wipe command to the BCB or enter recovery without going through rebootRecoveryWithCommand (none shown in the material).
- Where the RECOVERY permission check for rebootRecoveryWithCommand is enforced (the diff does not show it; tests imply enforceCallingOrSelfPermission(RECOVERY)).
- How a missing sepolicy permission surfaces to the Java caller (error code not shown).
- The exact frameworks/base and sepolicy commits that carry this change on Android 15, since the listed 15 commit is a soong change with different content.
- Whether IKeystoreMaintenance.deleteAllKeys() was already present in the keystore2 AIDL and daemon on the 14 branch or was added by a separate, unlisted change.