Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f270be49 authored by flawedworld's avatar flawedworld Committed by Daniel Micay
Browse files

safely flash bootloader firmware to both slots

Information from Daniel Micay <danielmicay@gmail.com>:

SoC firmware anti-rollback protection is being actively used for 6th and
7th generation Pixels. The code implementing this was deployed as part
of Android 13 resulting in an initial rollback version increase.

Consider the situation where a device shipped with v5 firmware and was
upgraded to v7 by the user. It has v7 on the current slot and v5 on the
alternate slot. It has been successfully booted with v7 firmware so the
OS has incremented the rollback version to v7. If the user unlocks and
unsuccessfully flashes new firmware or a new OS, the device rolls back
to the other slot, bricking itself.

The safest solution to this problem is flashing the unused slot with the
new firmware, making it active, booting it and then repeating it. This
makes sure that the new firmware is flashed to both slots and verifies
that it boots up successfully before proceeding with flashing the OS.

It makes sense to do this for ALL devices because all devices can deploy
similar anti-rollback protection and this also improves overall safety
of installation. Anti-rollback protection isn't the only reason that the
inactive slot could have non-booting firmware. Something could have gone
wrong previously so the user only has 1 booting firmware slot remaining.
This new approach makes fastboot.js much safer than Google's approach to
flashing.

GrapheneOS has adopted this for both fastboot.js and also the CLI
flashing script in the factory images zip since shortly after the
release of Android 13. It's very well tested now and we've avoided the
issues with bricked devices people are experiencing with flashing the
stock OS.
parent 8d2f548b
Loading
Loading
Loading
Loading
+28 −2
Original line number Diff line number Diff line
@@ -123,10 +123,27 @@ async function tryFlashImages(
        let pattern = new RegExp(`${imageName}(?:-.+)?\\.img$`);
        let entry = entries.find((entry) => entry.filename.match(pattern));
        if (entry !== undefined) {
            if (imageName == "bootloader") {
                let current_slot = await device.getVariable("current-slot");
                if (current_slot == "a") {
                    await flashEntryBlob(device, entry, onProgress, (imageName + "_b"));
                    await device.runCommand("set_active:b");
                } else if (current_slot == "b") {
                    await flashEntryBlob(device, entry, onProgress, (imageName + "_a"));
                    await device.runCommand("set_active:a");
                } else {
                    throw new FastbootError(
                        "FAIL",
                        `Invalid slot given by bootloader.`
                    );
                }
            }
            else {
                await flashEntryBlob(device, entry, onProgress, imageName);
            }
        }
    }
}

async function checkRequirements(device: FastbootDevice, androidInfo: string) {
    // Deal with CRLF just in case
@@ -218,7 +235,16 @@ export async function flashZip(
        await device.reboot("bootloader", true, onReconnect);
    }

    // 1. Bootloader pack
    // 1. Bootloader pack (repeated for slot A and B)
    await tryFlashImages(device, entries, onProgress, ["bootloader"]);
    await common.runWithTimedProgress(
        onProgress,
        "reboot",
        "device",
        BOOTLOADER_REBOOT_TIME,
        tryReboot(device, "bootloader", onReconnect)
    );

    await tryFlashImages(device, entries, onProgress, ["bootloader"]);
    await common.runWithTimedProgress(
        onProgress,