flawopen.com/Teardowns/cve-2024-32896-android-factory-reset-wipe-bypass

● CVE-2024-32896 · CVSS 7.8 · High
FlawOpen Security Research

CVE-2024-32896: Android Factory Reset Could Be Interrupted Before Keys Were Destroyed

CVE-2024-32896 (CVSS 7.8, exploited in limited, targeted attacks): a factory reset only rebooted into recovery and left the encryption keys in place until the wipe ran, so someone holding the phone could interrupt the reboot and keep the data recoverable. The fix deletes all Keystore keys before rebooting.

💡 Plain English Explainer (ELI5)

A company's rule for a lost laptop is to send it a "shred" order. The laptop then walks to the shredding room, where every file is destroyed. But someone holding the laptop can block the corridor on the way, and the files arrive nowhere and stay intact. The fix is that, before setting off, the laptop burns the only keys to its locked filing cabinet. Even if the walk is interrupted, nobody can ever open the cabinet again.

Core Concepts & Subsystem Terms

Factory reset (--wipe_data)
A wipe requested by the user, a device-admin or MDM app, or a remote-wipe service. RecoverySystemService.rebootRecoveryWithCommand() writes the --wipe_data command for recovery and reboots.
Bootloader control block (BCB)
A small partition (misc) where Android leaves instructions for the bootloader and recovery, written by setupOrClearBcb().
FBE and the synthetic password
File-based encryption. User data keys are derived from a synthetic password whose protector blobs are themselves bound to KeyMint keys, so destroying those keys makes the data undecryptable.
KeyMint / Keystore
The hardware-backed key service. AndroidKeyStoreMaintenance.deleteAllKeys() asks every KeyMint device to delete all of its keys.

Root Cause Analysis

rebootRecoveryWithCommand() in RecoverySystemService.java handled --wipe_data by writing the command to the bootloader control block and rebooting, trusting recovery to erase /data afterwards. Until that erase finished, the KeyMint keys protecting the synthetic password and the DE and metadata encryption keys were intact. Interrupting the reboot, or preventing the wipe from running, left the encrypted data and its keys in place, a logic error in the order of operations. The fix (AOSP 8b7b2c66, bug 324321147) calls deleteSecrets(), which runs AndroidKeyStoreMaintenance.deleteAllKeys(), before the reboot. It is the Android framework half of the Pixel firmware fix tracked as CVE-2024-29748.

Step-by-Step Attack Flow

Step 1

A wipe is requested

The owner, a device-admin or MDM app, or a remote-wipe service asks for a factory reset, which ends in rebootRecoveryWithCommand("--wipe_data ...").

Step 2

Reboot without destroying anything

setupOrClearBcb() stores the command and pm.reboot(REBOOT_RECOVERY) restarts the phone. The encryption keys are untouched at this point.

Step 3

The wipe is interrupted

Someone with physical possession stops the reboot short of recovery, for example by holding Volume Down to land in the bootloader, so the erase never runs.

Step 4

Data survives the reset

The encrypted user data and the keys that protect it remain on the device, so it can still be attacked later with forensic tools instead of being gone.

Source Code: Flaw vs. Secure Implementation

UNPATCHED FLAW
// services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
@Override // Binder call
public void rebootRecoveryWithCommand(String command) {
    if (DEBUG) Slog.d(TAG, "rebootRecoveryWithCommand: [" + command + "]");
    synchronized (sRequestLock) {
        if (!setupOrClearBcb(true, command)) {
            return;
        }

        // BUG: for "--wipe_data" nothing is destroyed yet. The keys that
        // protect the user's encrypted data survive until recovery runs the
        // wipe. If the reboot is interrupted or the wipe is skipped, the data
        // is still recoverable.
        PowerManager pm = mInjector.getPowerManager();
        pm.reboot(PowerManager.REBOOT_RECOVERY);
    }
}
HARDENED SECURE PATCH
// services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
static final String RECOVERY_WIPE_DATA_COMMAND = "--wipe_data";

@Override // Binder call
public void rebootRecoveryWithCommand(String command) {
    if (DEBUG) Slog.d(TAG, "rebootRecoveryWithCommand: [" + command + "]");

    boolean isForcedWipe = command != null && command.contains(RECOVERY_WIPE_DATA_COMMAND);
    synchronized (sRequestLock) {
        if (!setupOrClearBcb(true, command)) {
            return;
        }

        // FIX: destroy the keys first. Deleting every KeyMint key (including
        // the synthetic-password protector keys and the keys protecting DE and
        // metadata encryption keys) makes FBE data unrecoverable even if the
        // wipe in recovery is interrupted or skipped.
        if (isForcedWipe) {
            deleteSecrets();
        }

        PowerManager pm = mInjector.getPowerManager();
        pm.reboot(PowerManager.REBOOT_RECOVERY);
    }
}

private static void deleteSecrets() {
    Slogf.w(TAG, "deleteSecrets");
    try {
        AndroidKeyStoreMaintenance.deleteAllKeys();
    } catch (android.security.KeyStoreException e) {
        Log.wtf(TAG, "Failed to delete all keys from keystore.", e);
    }
}

Engineering & System Hardening Checklist

Sources