Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use fastboot flashall to flash partitions initially #253

Merged
merged 1 commit into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,30 +132,32 @@ If you lose your AVB or OTA signing key, you will no longer be able to sign new

1. Reboot into fastboot mode and unlock the bootloader if it isn't already unlocked. This will trigger a data wipe.

```bash
fastboot flashing unlock
```

2. When setting things up for the first time, the device must already be running the correct OS. Flash the original unpatched OTA if needed.

3. Extract the partition images from the patched OTA that are different from the original.

```bash
avbroot ota extract \
--input /path/to/ota.zip.patched \
--directory extracted
--directory extracted \
--fastboot
```

4. Flash the partition images that were extracted.
If you prefer to extract and flash all OS partitions just to be safe, pass in `--all`.

For each partition inside `extracted/`, except for `system`, run:
4. Flash the partition images that were extracted.

```bash
fastboot flash <partition> extracted/<partition>.img
ANDROID_PRODUCT_OUT=extracted fastboot flashall --skip-reboot
```

Then, reboot into recovery's fastbootd mode and flash `system`:
Note that this only flashes the OS partitions. The bootloader and modem/radio partitions are left untouched due to fastboot limitations. If they are not already up to date or if unsure, after fastboot completes, follow the steps in the [updates section](#updates) to sideload the patched OTA once. Sideloading OTAs always ensures that all partitions are up to date.

```bash
fastboot reboot fastboot
fastboot flash system extracted/system.img
```
Alternatively, for Pixel devices, running `flash-base.sh` from the factory image will also update the bootloader and modem.

5. Set up the custom AVB public key in the bootloader after rebooting from fastbootd to bootloader.

Expand Down
156 changes: 150 additions & 6 deletions avbroot/src/cli/ota.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1346,11 +1346,12 @@ pub fn extract_subcommand(cli: &ExtractCli, cancel_signal: &AtomicBool) -> Resul
.with_context(|| format!("Failed to open for reading: {:?}", cli.input))?;
let mut zip = ZipArchive::new(BufReader::new(raw_reader.reopen()?))
.with_context(|| format!("Failed to read zip: {:?}", cli.input))?;
let payload_entry = zip
.by_name(ota::PATH_PAYLOAD)
.with_context(|| format!("Failed to open zip entry: {:?}", ota::PATH_PAYLOAD))?;
let payload_offset = payload_entry.data_start();
let payload_size = payload_entry.size();
let (payload_offset, payload_size) = {
let entry = zip
.by_name(ota::PATH_PAYLOAD)
.with_context(|| format!("Failed to open zip entry: {}", ota::PATH_PAYLOAD))?;
(entry.data_start(), entry.size())
};

// Open the payload data directly.
let mut payload_reader = SectionReader::new(
Expand Down Expand Up @@ -1403,6 +1404,144 @@ pub fn extract_subcommand(cli: &ExtractCli, cancel_signal: &AtomicBool) -> Resul
cancel_signal,
)?;

if cli.fastboot {
const ANDROID_INFO: &str = "android-info.txt";
const FASTBOOT_INFO: &str = "fastboot-info.txt";

// Generate android-info.txt, which is always required for fastboot's
// flashall subcommand. We only add a basic device check to avoid
// accidental flashes on the wrong device.

let mut metadata_entry = zip
.by_name(ota::PATH_METADATA_PB)
.with_context(|| format!("Failed to open zip entry: {:?}", ota::PATH_METADATA_PB))?;
let mut metadata_raw = vec![];
metadata_entry
.read_to_end(&mut metadata_raw)
.with_context(|| format!("Failed to read OTA metadata: {}", ota::PATH_METADATA_PB))?;
let metadata = ota::parse_protobuf_metadata(&metadata_raw)
.with_context(|| format!("Failed to parse OTA metadata: {}", ota::PATH_METADATA_PB))?;

let device = metadata
.precondition
.as_ref()
.and_then(|p| p.device.first())
.ok_or_else(|| anyhow!("Device codename not found in OTA metadata"))?;

directory
.write(ANDROID_INFO, format!("require board={device}\n"))
.with_context(|| format!("Failed to write file: {ANDROID_INFO}"))?;

// Find out which images can be flashed with fastboot. The bootloader
// (and potentially modem) partitions need to be flashed as a whole and
// an OTA doesn't contain sufficient information to generate the
// required combined file.
let mut flashable_images = BTreeSet::new();

for name in &unique_images {
let file = directory
.open(format!("{name}.img"))
.with_context(|| format!("Failed to open image for reading: {name}"))?;

match avb::load_image(file) {
Ok(_) => {
flashable_images.insert(name);
}
// Treat images without AVB metadata as bootloader partitions.
Err(avb::Error::InvalidHeaderMagic(_)) => continue,
Err(e) => return Err(e).with_context(|| format!("Failed to load image: {name}")),
}
}

// Generate fastboot-info.txt to be able to control how exactly the
// images are flashed. This solves two problems:
//
// 1. fastboot flashall, by default, expects super_empty.img to exist
// for A/B devices. If it doesn't exist, it assumes that all images
// are meant for non-dynamic partitions and skips the fastbootd
// reboot. We cannot generate a proper super_empty.img because it
// requires knowing the number of super partitions along with their
// names and sizes. This information is not available in an OTA. The
// fastboot info file allows us to control when to reboot to
// fastbootd and flash each partition.
//
// This approach is a bit slower because we have to reboot into
// fastbootd. When the device only has a single super partition,
// fastboot will normally locally combine super_empty.img with the
// individual partitions to form a full super image and then flash it
// via the bootloader's fastboot mode.
//
// Also, because we can't generate super_empty.img, if the super
// partition metadata on the device somehow becomes corrupted,
// running fastboot flash all using avbroot's extracted images isn't
// sufficient for recovering the device. The user will need to copy
// that file from the factory image into the output directory and add
// an `update-super` line after `reboot fastboot` the fastboot info
// file.
//
// 2. fastboot flashall does not look at all .img files in a directory.
// Instead, it has a hardcoded list of partitions known to AOSP. This
// means obscure OEM-specific partitions are just silently ignored.
// Using a fastboot info file with explicit flash instructions avoids
// this problem entirely.

let mut dynamic = BTreeSet::new();

if let Some(dpm) = &header.manifest.dynamic_partition_metadata {
for group in &dpm.groups {
for name in &group.partition_names {
if unique_images.contains(name) {
dynamic.insert(name);
}
}
}
}

let flash_command = |name| {
let (prefix, suffix) = if flashable_images.contains(name) {
("", "")
} else {
("#", " # Bootloader/modem images cannot be flashed")
};

let extra_arg = if *name == "vbmeta" {
"--apply-vbmeta "
} else {
""
};

format!("{prefix}flash {extra_arg}{name}{suffix}\n")
};

let mut fastboot_info = String::new();

fastboot_info.push_str("# Generated by avbroot\n");
fastboot_info.push_str("version 1\n");

fastboot_info.push_str("# Flash non-dynamic partitions\n");
for name in &unique_images {
if !dynamic.contains(name) {
fastboot_info.push_str(&flash_command(name));
}
}

fastboot_info.push_str("# Reboot to fastbootd\n");
fastboot_info.push_str("reboot fastboot\n");

fastboot_info.push_str("# Flash dynamic partitions\n");
for name in &dynamic {
fastboot_info.push_str(&flash_command(name));
}

fastboot_info.push_str("# Wipe data when -w flag is used\n");
fastboot_info.push_str("if-wipe erase userdata\n");
fastboot_info.push_str("if-wipe erase metadata\n");

directory
.write(FASTBOOT_INFO, fastboot_info)
.with_context(|| format!("Failed to write file: {FASTBOOT_INFO}"))?;
}

Ok(())
}

Expand Down Expand Up @@ -1712,6 +1851,7 @@ pub struct PatchCli {
#[arg(
long,
value_name = "PARTITION",
hide = true,
help_heading = HEADING_OTHER
)]
pub boot_partition: Option<String>,
Expand All @@ -1737,8 +1877,12 @@ pub struct ExtractCli {
pub boot_only: bool,

/// (Deprecated: no longer needed)
#[arg(long, value_name = "PARTITION")]
#[arg(long, value_name = "PARTITION", hide = true)]
pub boot_partition: Option<String>,

/// Generate fastboot info files.
#[arg(long)]
pub fastboot: bool,
}

/// Verify signatures of an OTA.
Expand Down