blob: 6f452e90dfd1a7f5890938e08c7408e3a7d17544 [file]
// Copyright 2024, The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Android boot support.
use crate::{
device_tree::{DeviceTreeComponentSource, DeviceTreeComponentsRegistry, FDT_ALIGNMENT},
gbl_print, gbl_println, GblOps, Result,
};
use bootimg::{BootImage, VendorImageHeader};
use bootparams::{bootconfig::BootConfigBuilder, commandline::CommandlineBuilder};
use core::ffi::CStr;
use dttable::DtTableImage;
use fdt::Fdt;
use liberror::Error;
use libutils::{aligned_offset, aligned_subslice};
use misc::{AndroidBootMode, BootloaderMessage};
use safemath::SafeNum;
use zerocopy::{ByteSlice, IntoBytes, Ref};
mod vboot;
use vboot::{avb_verify_slot, PartitionsToVerify};
mod load;
use load::split_chunks;
pub use load::{android_load_verify, LoadedImages};
#[cfg(target_arch = "aarch64")]
use crate::decompress::decompress_kernel;
/// Device tree bootargs property to store kernel command line.
pub const BOOTARGS_PROP: &CStr = c"bootargs";
/// Linux kernel requires 2MB alignment.
const KERNEL_ALIGNMENT: usize = 2 * 1024 * 1024;
/// A helper to convert a bytes slice containing a null-terminated string to `str`
fn cstr_bytes_to_str(data: &[u8]) -> core::result::Result<&str, Error> {
Ok(CStr::from_bytes_until_nul(data)?.to_str()?)
}
/// Helper function to parse common fields from boot image headers.
///
/// # Returns
///
/// Returns a tuple of 6 slices corresponding to:
/// (kernel_size, cmdline, page_size, ramdisk_size, second_size, dtb_size)
fn boot_header_elements<B: ByteSlice + PartialEq>(
hdr: &BootImage<B>,
) -> Result<(usize, &str, usize, usize, usize, usize)> {
const PAGE_SIZE: usize = 4096; // V3/V4 image has fixed page size 4096;
Ok(match hdr {
BootImage::V2(ref hdr) => (
hdr._base._base.kernel_size as usize,
cstr_bytes_to_str(&hdr._base._base.cmdline[..])?,
hdr._base._base.page_size as usize,
hdr._base._base.ramdisk_size as usize,
hdr._base._base.second_size as usize,
hdr.dtb_size as usize,
),
BootImage::V3(ref hdr) => (
hdr.kernel_size as usize,
cstr_bytes_to_str(&hdr.cmdline[..])?,
PAGE_SIZE,
hdr.ramdisk_size as usize,
0,
0,
),
BootImage::V4(ref hdr) => (
hdr._base.kernel_size as usize,
cstr_bytes_to_str(&hdr._base.cmdline[..])?,
PAGE_SIZE,
hdr._base.ramdisk_size as usize,
0,
0,
),
_ => {
return Err(Error::UnsupportedVersion.into());
}
})
}
/// Helper function to parse common fields from vendor image headers.
///
/// # Returns
///
/// Returns a tuple of 5 slices corresponding to:
/// (vendor_ramdisk_size, hdr_size, cmdline, page_size, dtb_size, vendor_bootconfig_size, vendor_ramdisk_table_size)
fn vendor_header_elements<B: ByteSlice + PartialEq>(
hdr: &VendorImageHeader<B>,
) -> Result<(usize, usize, &str, usize, usize, usize, usize)> {
Ok(match hdr {
VendorImageHeader::V3(ref hdr) => (
hdr.vendor_ramdisk_size as usize,
SafeNum::from(Ref::bytes(hdr).len())
.round_up(hdr.page_size)
.try_into()
.map_err(Error::from)?,
cstr_bytes_to_str(&hdr.cmdline.as_bytes())?,
hdr.page_size as usize,
hdr.dtb_size as usize,
0,
0,
),
VendorImageHeader::V4(ref hdr) => (
hdr._base.vendor_ramdisk_size as usize,
SafeNum::from(Ref::bytes(hdr).len())
.round_up(hdr._base.page_size)
.try_into()
.map_err(Error::from)?,
cstr_bytes_to_str(&hdr._base.cmdline.as_bytes())?,
hdr._base.page_size as usize,
hdr._base.dtb_size as usize,
hdr.bootconfig_size as usize,
hdr.vendor_ramdisk_table_size as usize,
),
})
}
/// Loads Android images from disk and fixes up bootconfig, commandline, and FDT.
///
/// A number of simplifications are made:
///
/// * No A/B slot switching is performed. It always boot from *_a slot.
/// * No dynamic partitions.
/// * Only support V3/V4 image and Android 13+ (generic ramdisk from the "init_boot" partition)
/// * Only support booting recovery from boot image
///
/// # Arguments
/// * `ops`: the [GblOps] object providing platform-specific backends.
/// * `load`: the combined buffer to load all images into.
///
/// # Returns
/// Returns a tuple of 4 slices corresponding to:
/// (ramdisk load buffer, FDT load buffer, kernel load buffer, unused buffer).
pub fn load_android_simple<'a, 'b, 'c>(
ops: &mut impl GblOps<'b, 'c>,
load: &'a mut [u8],
) -> Result<(&'a mut [u8], &'a mut [u8], &'a mut [u8], &'a mut [u8])> {
const PAGE_SIZE: usize = 4096; // V3/V4 image has fixed page size 4096;
let (bcb_buffer, load) = load.split_at_mut(BootloaderMessage::SIZE_BYTES);
ops.read_from_partition_sync("misc", 0, bcb_buffer)?;
let bcb = BootloaderMessage::from_bytes_ref(bcb_buffer)?;
let boot_mode = bcb.boot_mode()?;
gbl_println!(ops, "boot mode from BCB: {}", boot_mode);
// TODO(b/370317273): use high level abstraction over boot to avoid working
// with offsets on application level.
// Parse boot header.
let (boot_header_buffer, load) = load.split_at_mut(PAGE_SIZE);
ops.read_from_partition_sync("boot_a", 0, boot_header_buffer)?;
let boot_header = BootImage::parse(boot_header_buffer).map_err(Error::from)?;
let (
kernel_size,
boot_cmdline,
kernel_hdr_size,
boot_ramdisk_size,
boot_second_size,
boot_dtb_size,
) = boot_header_elements(&boot_header)?;
gbl_println!(ops, "boot image size: {}", kernel_size);
gbl_println!(ops, "boot image cmdline: \"{}\"", boot_cmdline);
gbl_println!(ops, "boot ramdisk size: {}", boot_ramdisk_size);
gbl_println!(ops, "boot dtb size: {}", boot_dtb_size);
// TODO(b/370317273): use high level abstraction over vendor_boot to avoid working
// with offsets on application level.
// Parse vendor boot header.
let (vendor_boot_header_buffer, load) = load.split_at_mut(PAGE_SIZE);
let vendor_boot_header;
let (
vendor_ramdisk_size,
vendor_hdr_size,
vendor_cmdline,
vendor_page_size,
vendor_dtb_size,
vendor_bootconfig_size,
vendor_ramdisk_table_size,
) = match ops.partition_size("vendor_boot_a") {
Ok(Some(_sz)) => {
ops.read_from_partition_sync("vendor_boot_a", 0, vendor_boot_header_buffer)?;
vendor_boot_header =
VendorImageHeader::parse(vendor_boot_header_buffer).map_err(Error::from)?;
vendor_header_elements(&vendor_boot_header)?
}
_ => (0 as usize, 0 as usize, "", 0 as usize, 0 as usize, 0 as usize, 0),
};
gbl_println!(ops, "vendor ramdisk size: {}", vendor_ramdisk_size);
gbl_println!(ops, "vendor cmdline: \"{}\"", vendor_cmdline);
gbl_println!(ops, "vendor dtb size: {}", vendor_dtb_size);
let (dtbo_buffer, load) = match ops.partition_size("dtbo_a") {
Ok(Some(sz)) => {
let (dtbo_buffer, load) = load.split_at_mut(sz.try_into().unwrap());
ops.read_from_partition_sync("dtbo_a", 0, dtbo_buffer)?;
(Some(dtbo_buffer), load)
}
_ => (None, load),
};
let mut components: DeviceTreeComponentsRegistry<'a> = DeviceTreeComponentsRegistry::new();
let load = match dtbo_buffer {
Some(ref dtbo_buffer) => {
let dtbo_table = DtTableImage::from_bytes(dtbo_buffer)?;
components.append_from_dtbo(&dtbo_table, load)?
}
_ => load,
};
// First: check for custom FDT (Cuttlefish).
let load = if ops.get_custom_device_tree().is_none() {
// Second: "vendor_boot" FDT.
let (source, part, offset, size) = if vendor_dtb_size > 0 {
// DTB is located after the header and ramdisk (aligned).
let offset = (SafeNum::from(vendor_hdr_size) + SafeNum::from(vendor_ramdisk_size))
.round_up(vendor_page_size)
.try_into()
.map_err(Error::from)?;
(DeviceTreeComponentSource::VendorBoot, "vendor_boot_a", offset, vendor_dtb_size)
// Third: "boot" FDT.
} else if boot_dtb_size > 0 {
// DTB is located after the header, kernel, ramdisk, and second images (aligned).
let mut offset = SafeNum::from(kernel_hdr_size);
for image_size in [kernel_size, boot_ramdisk_size, boot_second_size] {
offset += SafeNum::from(image_size).round_up(kernel_hdr_size);
}
(
DeviceTreeComponentSource::Boot,
"boot_a",
offset.try_into().map_err(Error::from)?,
boot_dtb_size,
)
} else {
return Err(Error::NoFdt.into());
};
let (fdt_buffer, load) = aligned_subslice(load, FDT_ALIGNMENT)?.split_at_mut(size);
ops.read_from_partition_sync(part, offset, fdt_buffer)?;
components.append(ops, source, fdt_buffer, load)?
} else {
load
};
// Parse init_boot header
let init_boot_header_buffer = &mut load[..PAGE_SIZE];
let (generic_ramdisk_size, init_boot_hdr_size) = match ops.partition_size("init_boot_a") {
Ok(Some(_sz)) => {
ops.read_from_partition_sync("init_boot_a", 0, init_boot_header_buffer)?;
let init_boot_header =
BootImage::parse(init_boot_header_buffer).map_err(Error::from)?;
match init_boot_header {
BootImage::V3(ref hdr) => (hdr.ramdisk_size as usize, PAGE_SIZE),
BootImage::V4(ref hdr) => (hdr._base.ramdisk_size as usize, PAGE_SIZE),
_ => {
gbl_println!(ops, "V0/V1/V2 images are not supported");
return Err(Error::UnsupportedVersion.into());
}
}
}
_ => (0, 0),
};
gbl_println!(ops, "init_boot image size: {}", generic_ramdisk_size);
// Load and prepare various images.
let images_buffer = aligned_subslice(load, KERNEL_ALIGNMENT)?;
let load = &mut images_buffer[..];
// Load kernel
// Kernel may need to reserve additional memory after itself. To avoid the risk of this
// memory overlapping with ramdisk. We place kernel after ramdisk. We first load it to the tail
// of the buffer and move it forward as much as possible after ramdisk and fdt are loaded,
// fixed-up and finalized.
let boot_img_load_offset: usize = {
let off = SafeNum::from(load.len())
- SafeNum::from(kernel_size).round_up(kernel_hdr_size)
- SafeNum::from(boot_ramdisk_size).round_up(kernel_hdr_size);
let off_idx: usize = off.try_into().map_err(Error::from)?;
let aligned_off = off - (&load[off_idx] as *const _ as usize % KERNEL_ALIGNMENT);
aligned_off.try_into().map_err(Error::from)?
};
let (load, boot_img_buffer) = load.split_at_mut(boot_img_load_offset);
let boot_partition_load_size: usize = (SafeNum::from(kernel_size).round_up(kernel_hdr_size)
+ SafeNum::from(boot_ramdisk_size).round_up(kernel_hdr_size))
.try_into()
.unwrap();
ops.read_from_partition_sync(
"boot_a",
kernel_hdr_size.try_into().unwrap(),
&mut boot_img_buffer[..boot_partition_load_size],
)?;
// Load vendor ramdisk
let mut ramdisk_load_curr = SafeNum::ZERO;
if vendor_ramdisk_size > 0 {
ops.read_from_partition_sync(
"vendor_boot_a",
u64::try_from(vendor_hdr_size).map_err(Error::from)?,
&mut load[ramdisk_load_curr.try_into().map_err(Error::from)?..][..vendor_ramdisk_size],
)?;
}
ramdisk_load_curr += vendor_ramdisk_size;
// Load generic ramdisk
if generic_ramdisk_size > 0 {
ops.read_from_partition_sync(
"init_boot_a",
init_boot_hdr_size.try_into().unwrap(),
&mut load[ramdisk_load_curr.try_into().map_err(Error::from)?..][..generic_ramdisk_size],
)?;
ramdisk_load_curr += generic_ramdisk_size;
}
// Load ramdisk from boot image
if boot_ramdisk_size > 0 {
let kernel_size_roundup: usize =
SafeNum::from(kernel_size).round_up(kernel_hdr_size).try_into().unwrap();
load[ramdisk_load_curr.try_into().map_err(Error::from)?..][..boot_ramdisk_size]
.copy_from_slice(&boot_img_buffer[kernel_size_roundup..][..boot_ramdisk_size]);
ramdisk_load_curr += boot_ramdisk_size;
}
// Prepare partition data for avb verification
let (_vendor_boot_load_buffer, remains) = load.split_at_mut(vendor_ramdisk_size);
let (_init_boot_load_buffer, remains) = remains.split_at_mut(generic_ramdisk_size);
let (_boot_ramdisk_load_buffer, remains) = remains.split_at_mut(boot_ramdisk_size);
// Prepare a BootConfigBuilder to add avb generated bootconfig.
let mut bootconfig_builder = BootConfigBuilder::new(remains)?;
// Preloaded partitions aren't used. Will be fixed by using load.rs implementation
avb_verify_slot(ops, 0, &PartitionsToVerify::default(), &mut bootconfig_builder)?;
// Move kernel to end of the boot image buffer
let (_boot_img_buffer, kernel_tail_buffer) = {
let off = SafeNum::from(boot_img_buffer.len()) - kernel_size;
let off_idx: usize = off.try_into().map_err(Error::from)?;
let aligned_off = off - (&boot_img_buffer[off_idx] as *const _ as usize % KERNEL_ALIGNMENT);
let aligned_off_idx = aligned_off.try_into().map_err(Error::from)?;
boot_img_buffer.copy_within(0..kernel_size, aligned_off_idx);
boot_img_buffer.split_at_mut(aligned_off_idx)
};
// Add slot index
bootconfig_builder.add("androidboot.slot_suffix=_a\n")?;
// Placeholder value for now. Userspace can use this value to tell if device is booted with GBL.
// TODO(yochiang): Generate useful value like version, build_incremental in the bootconfig.
bootconfig_builder.add("androidboot.gbl.version=0\n")?;
bootconfig_builder.add("androidboot.gbl.build_number=")?;
match option_env!("BUILD_NUMBER") {
None | Some("") => {
bootconfig_builder.add("eng.build\n")?;
}
Some(build_number) => {
bootconfig_builder.add(build_number)?;
bootconfig_builder.add("\n")?;
}
}
match boot_mode {
// TODO(b/329716686): Support bootloader mode
AndroidBootMode::Normal | AndroidBootMode::BootloaderBootOnce => {
bootconfig_builder.add("androidboot.force_normal_boot=1\n")?
}
_ => {
// Do nothing
}
}
// V4 image has vendor bootconfig.
if vendor_bootconfig_size > 0 {
let mut bootconfig_offset = SafeNum::from(vendor_hdr_size);
for image_size in [vendor_ramdisk_size, vendor_dtb_size, vendor_ramdisk_table_size] {
bootconfig_offset += SafeNum::from(image_size).round_up(vendor_page_size);
}
bootconfig_builder.add_with(|_, out| {
ops.read_from_partition_sync(
"vendor_boot_a",
bootconfig_offset.try_into()?,
&mut out[..vendor_bootconfig_size as usize],
)?;
Ok(vendor_bootconfig_size as usize)
})?;
}
// TODO(b/353272981): Handle buffer too small
bootconfig_builder.add_with(|bytes, out| {
// TODO(b/353272981): Verify provided bootconfig and fail here
Ok(ops.fixup_bootconfig(&bytes, out)?.map(|slice| slice.len()).unwrap_or(0))
})?;
gbl_println!(ops, "final bootconfig: \"{}\"", bootconfig_builder);
ramdisk_load_curr += bootconfig_builder.config_bytes().len();
// On ARM, we may need to decompress the kernel and re-split the buffer to the new kernel size.
#[cfg(target_arch = "aarch64")]
let (load, kernel_size, kernel_tail_buffer) = {
let kernel_size = kernel_tail_buffer.len();
let compressed_kernel_offset = images_buffer.len() - kernel_size;
let decompressed_kernel_offset =
decompress_kernel(ops, images_buffer, compressed_kernel_offset)?;
let (load, kernel_tail_buffer) = images_buffer.split_at_mut(decompressed_kernel_offset);
(load, kernel_tail_buffer.len(), kernel_tail_buffer)
};
// Use the remaining load buffer for the FDT.
let (ramdisk_load_buffer, load) =
load.split_at_mut(ramdisk_load_curr.try_into().map_err(Error::from)?);
let (base, overlays): (&[u8], &[&[u8]]) = if let Some(custom_fdt) = ops.get_custom_device_tree()
{
(custom_fdt, &[])
} else {
ops.select_device_trees(&mut components)?;
components.selected()?
};
let fdt_buffer = aligned_subslice(load, FDT_ALIGNMENT)?;
let mut fdt = Fdt::new_from_init(fdt_buffer, base)?;
gbl_println!(ops, "Applying {} overlays", overlays.len());
fdt.multioverlay_apply(overlays)?;
gbl_println!(ops, "Overlays applied");
// Add ramdisk range to FDT
let ramdisk_addr: u64 =
(ramdisk_load_buffer.as_ptr() as usize).try_into().map_err(Error::from)?;
let ramdisk_end: u64 =
ramdisk_addr + u64::try_from(ramdisk_load_buffer.len()).map_err(Error::from)?;
fdt.set_property("chosen", c"linux,initrd-start", &ramdisk_addr.to_be_bytes())?;
fdt.set_property("chosen", c"linux,initrd-end", &ramdisk_end.to_be_bytes())?;
gbl_println!(ops, "linux,initrd-start: {:#x}", ramdisk_addr);
gbl_println!(ops, "linux,initrd-end: {:#x}", ramdisk_end);
// Update the FDT commandline.
let device_tree_commandline_length = match fdt.get_property("chosen", BOOTARGS_PROP) {
Ok(val) => CStr::from_bytes_until_nul(val).map_err(Error::from)?.to_bytes().len(),
Err(_) => 0,
};
// Reserve 1024 bytes for separators and fixup.
let final_commandline_len =
device_tree_commandline_length + boot_cmdline.len() + vendor_cmdline.len() + 1024;
let final_commandline_buffer =
fdt.set_property_placeholder("chosen", BOOTARGS_PROP, final_commandline_len)?;
let mut commandline_builder =
CommandlineBuilder::new_from_prefix(&mut final_commandline_buffer[..])?;
commandline_builder.add(boot_cmdline)?;
commandline_builder.add(vendor_cmdline)?;
// TODO(b/353272981): Handle buffer too small
commandline_builder.add_with(|current, out| {
// TODO(b/353272981): Verify provided command line and fail here.
Ok(ops.fixup_os_commandline(current, out)?.map(|fixup| fixup.len()).unwrap_or(0))
})?;
gbl_println!(ops, "final cmdline: \"{}\"", commandline_builder.as_str());
// Make sure we provide an actual device tree size, so FW can calculate amount of space
// available for fixup.
fdt.shrink_to_fit()?;
// TODO(b/353272981): Make a copy of current device tree and verify provided fixup.
// TODO(b/353272981): Handle buffer too small
ops.fixup_device_tree(fdt.as_mut())?;
fdt.shrink_to_fit()?;
// Move the kernel backward as much as possible to preserve more space after it. This is
// necessary in case the input buffer is at the end of address space.
let kernel_tail_buffer_size = kernel_tail_buffer.len();
let ramdisk_load_buffer_size = ramdisk_load_buffer.len();
let fdt_len = fdt.header_ref()?.actual_size();
// Split out the ramdisk.
let (ramdisk, remains) = images_buffer.split_at_mut(ramdisk_load_buffer_size);
// Split out the fdt.
let (fdt, kernel) = aligned_subslice(remains, FDT_ALIGNMENT)?.split_at_mut(fdt_len);
// Move the kernel backward as much as possible.
let kernel = aligned_subslice(kernel, KERNEL_ALIGNMENT)?;
let kernel_start = kernel.len().checked_sub(kernel_tail_buffer_size).unwrap();
kernel.copy_within(kernel_start..kernel_start.checked_add(kernel_size).unwrap(), 0);
// Split out the remaining buffer.
let (kernel, remains) = kernel.split_at_mut(kernel_size);
Ok((ramdisk, fdt, kernel, remains))
}
/// Loads Android images from the given slot on disk and fixes up bootconfig, commandline, and FDT.
///
/// On success, returns a tuple of (ramdisk, fdt, kernel, unused buffer).
pub fn android_load_verify_fixup<'a, 'b, 'c>(
ops: &mut impl GblOps<'b, 'c>,
slot: u8,
is_recovery: bool,
load: &'a mut [u8],
) -> Result<(&'a mut [u8], &'a mut [u8], &'a mut [u8], &'a mut [u8])> {
let load_addr = load.as_ptr() as usize;
let images = android_load_verify(ops, slot, is_recovery, load)?;
let mut components = DeviceTreeComponentsRegistry::new();
let fdt_load = &mut images.unused[..];
// TODO(b/353272981): Remove get_custom_device_tree
let (fdt_load, base, overlays) = match ops.get_custom_device_tree() {
Some(v) => (fdt_load, v, &[][..]),
_ => {
let mut remains = match images.dtbo.len() > 0 {
// TODO(b/384964561, b/374336105): Investigate if we can avoid additional copy.
true => components
.append_from_dtbo(&DtTableImage::from_bytes(images.dtbo)?, fdt_load)?,
_ => fdt_load,
};
if images.dtb.len() > 0 {
remains =
components.append(ops, DeviceTreeComponentSource::Boot, images.dtb, remains)?;
}
if images.dtb_part.len() > 0 {
let dttable = DtTableImage::from_bytes(images.dtb_part)?;
remains = components.append_from_dttable(true, &dttable, remains)?;
}
ops.select_device_trees(&mut components)?;
let (base, overlays) = components.selected()?;
(remains, base, overlays)
}
};
let fdt_load = aligned_subslice(fdt_load, FDT_ALIGNMENT)?;
let mut fdt = Fdt::new_from_init(&mut fdt_load[..], base)?;
// Adds ramdisk range to FDT
let ramdisk_addr: u64 = (images.ramdisk.as_ptr() as usize).try_into().map_err(Error::from)?;
let ramdisk_end: u64 = ramdisk_addr + u64::try_from(images.ramdisk.len()).unwrap();
fdt.set_property("chosen", c"linux,initrd-start", &ramdisk_addr.to_be_bytes())?;
fdt.set_property("chosen", c"linux,initrd-end", &ramdisk_end.to_be_bytes())?;
gbl_println!(ops, "linux,initrd-start: {:#x}", ramdisk_addr);
gbl_println!(ops, "linux,initrd-end: {:#x}", ramdisk_end);
// Updates the FDT commandline.
let device_tree_commandline_length = match fdt.get_property("chosen", BOOTARGS_PROP) {
Ok(val) => CStr::from_bytes_until_nul(val).map_err(Error::from)?.to_bytes().len(),
Err(_) => 0,
};
// Reserves 1024 bytes for separators and fixup.
let final_commandline_len = device_tree_commandline_length
+ images.boot_cmdline.len()
+ images.vendor_cmdline.len()
+ 1024;
let final_commandline_buffer =
fdt.set_property_placeholder("chosen", BOOTARGS_PROP, final_commandline_len)?;
let mut commandline_builder =
CommandlineBuilder::new_from_prefix(&mut final_commandline_buffer[..])?;
commandline_builder.add(images.boot_cmdline)?;
commandline_builder.add(images.vendor_cmdline)?;
// TODO(b/353272981): Handle buffer too small
commandline_builder.add_with(|current, out| {
// TODO(b/353272981): Verify provided command line and fail here.
Ok(ops.fixup_os_commandline(current, out)?.map(|fixup| fixup.len()).unwrap_or(0))
})?;
gbl_println!(ops, "final cmdline: \"{}\"", commandline_builder.as_str());
gbl_println!(ops, "Applying {} overlays", overlays.len());
fdt.multioverlay_apply(overlays)?;
gbl_println!(ops, "Overlays applied");
// `DeviceTreeComponentsRegistry` internally uses ArrayVec which causes it to have a default
// life time equal to the scope it lives in. This is unnecessarily strict and prevents us from
// accessing `load` buffer.
drop(components);
// Make sure we provide an actual device tree size, so FW can calculate amount of space
// available for fixup.
fdt.shrink_to_fit()?;
// TODO(b/353272981): Make a copy of current device tree and verify provided fixup.
// TODO(b/353272981): Handle buffer too small
ops.fixup_device_tree(fdt.as_mut())?;
fdt.shrink_to_fit()?;
// Moves the kernel forward to reserve as much space as possible. This is in case there is not
// enough memory after `load`, i.e. the memory after it is not mapped or is reserved.
let ramdisk_off = usize::try_from(ramdisk_addr).unwrap() - load_addr;
let fdt_len = fdt.header_ref()?.actual_size();
let fdt_off = fdt_load.as_ptr() as usize - load_addr;
let kernel_off = images.kernel.as_ptr() as usize - load_addr;
let kernel_len = images.kernel.len();
let mut kernel_new = (SafeNum::from(fdt_off) + fdt_len).try_into().map_err(Error::from)?;
kernel_new += aligned_offset(&mut load[kernel_new..], KERNEL_ALIGNMENT)?;
load.copy_within(kernel_off..kernel_off + kernel_len, kernel_new);
let ([_, ramdisk, fdt, kernel], unused) =
split_chunks(load, &[ramdisk_off, fdt_off - ramdisk_off, kernel_new - fdt_off, kernel_len]);
let ramdisk = &mut ramdisk[..usize::try_from(ramdisk_end - ramdisk_addr).unwrap()];
Ok((ramdisk, fdt, kernel, unused))
}
/// Runs full Android bootloader bootflow before kernel handoff.
///
/// The API performs slot selection, handles boot mode, fastboot and loads and verifies Android from
/// disk.
///
/// On success, returns a tuple of slices corresponding to `(ramdisk, FDT, kernel, unused)`
pub fn android_main<'a, 'b, 'c>(
ops: &mut impl GblOps<'a, 'b>,
load: &'c mut [u8],
) -> Result<(&'c mut [u8], &'c mut [u8], &'c mut [u8], &'c mut [u8])> {
let (bcb_buffer, _) = load
.split_at_mut_checked(BootloaderMessage::SIZE_BYTES)
.ok_or(Error::BufferTooSmall(Some(BootloaderMessage::SIZE_BYTES)))
.inspect_err(|e| gbl_println!(ops, "Buffer too small for reading misc. {e}"))?;
ops.read_from_partition_sync("misc", 0, bcb_buffer)
.inspect_err(|e| gbl_println!(ops, "Failed to read misc partition {e}"))?;
let bcb = BootloaderMessage::from_bytes_ref(bcb_buffer)
.inspect_err(|e| gbl_println!(ops, "Failed to parse bootloader messgae {e}"))?;
let boot_mode = bcb
.boot_mode()
.inspect_err(|e| gbl_println!(ops, "Failed to parse BCB boot mode {e}. Ignored"))
.unwrap_or(AndroidBootMode::Normal);
gbl_println!(ops, "Boot mode from BCB: {}", boot_mode);
// TODO(b/383620444): Add slot and fastboot support.
let is_recovery = matches!(boot_mode, AndroidBootMode::Recovery);
android_load_verify_fixup(ops, 0, is_recovery, load)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
gbl_avb::state::{BootStateColor, KeyValidationStatus},
ops::test::{FakeGblOps, FakeGblOpsStorage},
tests::AlignedBuffer,
};
use load::tests::{
check_ramdisk, make_expected_bootconfig, read_test_data, read_test_data_as_str,
AvbResultBootconfigBuilder, TEST_PUBLIC_KEY_DIGEST, TEST_VENDOR_BOOTCONFIG,
};
use std::{collections::HashMap, ffi::CString};
const TEST_ROLLBACK_INDEX_LOCATION: usize = 1;
// TODO(b/384964561): This is a temporaray test for making sure the generated images work. It
// will be replaced with more thorough tests as we productionizes `load_android_simple`.
#[test]
fn test_load_android_simple() {
let mut storage = FakeGblOpsStorage::default();
storage.add_raw_device(c"boot_a", read_test_data("boot_no_ramdisk_v4_a.img"));
storage.add_raw_device(c"init_boot_a", read_test_data("init_boot_a.img"));
storage.add_raw_device(c"vendor_boot_a", read_test_data("vendor_boot_v4_a.img"));
storage.add_raw_device(c"vbmeta_a", read_test_data("vbmeta_v4_v4_init_boot_a.img"));
storage.add_raw_device(c"misc", vec![0u8; 4 * 1024 * 1024]);
let mut ops = FakeGblOps::new(&storage);
ops.avb_ops.unlock_state = Ok(false);
ops.avb_ops.rollbacks = HashMap::from([(TEST_ROLLBACK_INDEX_LOCATION, Ok(0))]);
let fdt = AlignedBuffer::new_with_data(
include_bytes!("../../../libfdt/test/data/base.dtb"),
FDT_ALIGNMENT,
);
ops.custom_device_tree = Some(&fdt);
let mut load_buffer = AlignedBuffer::new(8 * 1024 * 1024, KERNEL_ALIGNMENT);
let mut out_color = None;
let mut handler = |color,
_: Option<&CStr>,
_: Option<&[u8]>,
_: Option<&[u8]>,
_: Option<&[u8]>,
_: Option<&[u8]>,
_: Option<&[u8]>,
_: Option<&[u8]>| {
out_color = Some(color);
Ok(())
};
ops.avb_handle_verification_result = Some(&mut handler);
ops.avb_key_validation_status = Some(Ok(KeyValidationStatus::Valid));
load_android_simple(&mut ops, &mut load_buffer).unwrap();
assert_eq!(out_color, Some(BootStateColor::Green));
}
/// Helper for testing `android_load_verify_fixup` given a partition layout, target slot and
/// custom device tree.
fn test_android_load_verify_fixup(
slot: u8,
partitions: &[(CString, String)],
expected_kernel: &[u8],
expected_ramdisk: &[u8],
expected_bootconfig: &[u8],
expected_bootargs: &str,
expected_fdt_property: &[(&str, &CStr, Option<&[u8]>)],
) {
let mut storage = FakeGblOpsStorage::default();
for (part, file) in partitions {
storage.add_raw_device(part, read_test_data(file));
}
let mut ops = FakeGblOps::new(&storage);
ops.avb_ops.unlock_state = Ok(false);
ops.avb_ops.rollbacks = HashMap::from([(TEST_ROLLBACK_INDEX_LOCATION, Ok(0))]);
let mut out_color = None;
let mut handler = |color,
_: Option<&CStr>,
_: Option<&[u8]>,
_: Option<&[u8]>,
_: Option<&[u8]>,
_: Option<&[u8]>,
_: Option<&[u8]>,
_: Option<&[u8]>| {
out_color = Some(color);
Ok(())
};
ops.avb_handle_verification_result = Some(&mut handler);
ops.avb_key_validation_status = Some(Ok(KeyValidationStatus::Valid));
let mut load_buffer = AlignedBuffer::new(8 * 1024 * 1024, KERNEL_ALIGNMENT);
let (ramdisk, fdt, kernel, _) =
android_load_verify_fixup(&mut ops, slot, false, &mut load_buffer).unwrap();
assert_eq!(kernel, expected_kernel);
check_ramdisk(ramdisk, expected_ramdisk, expected_bootconfig);
let fdt = Fdt::new(fdt).unwrap();
// "linux,initrd-start/end" are updated.
assert_eq!(
fdt.get_property("/chosen", c"linux,initrd-start").unwrap(),
(ramdisk.as_ptr() as usize).to_be_bytes(),
);
assert_eq!(
fdt.get_property("/chosen", c"linux,initrd-end").unwrap(),
(ramdisk.as_ptr() as usize + ramdisk.len()).to_be_bytes(),
);
// Commandlines are updated.
assert_eq!(
CStr::from_bytes_until_nul(fdt.get_property("/chosen", c"bootargs").unwrap()).unwrap(),
CString::new(expected_bootargs).unwrap().as_c_str(),
);
// Fixup is applied.
assert_eq!(fdt.get_property("/chosen", c"fixup").unwrap(), &[1]);
// Other FDT properties are as expected.
for (path, property, res) in expected_fdt_property {
assert_eq!(
fdt.get_property(&path, &property).ok(),
res.clone(),
"{path}:{property:?} value doesn't match"
);
}
}
/// Helper for testing `android_load_verify_fixup` for v2 boot image or lower.
fn test_android_load_verify_fixup_v2_or_lower(
ver: u8,
slot: char,
additional_parts: &[(&CStr, &str)],
additional_expected_fdt_properties: &[(&str, &CStr, Option<&[u8]>)],
) {
let vbmeta = format!("vbmeta_v{ver}_{slot}.img");
let mut parts: Vec<(CString, String)> = vec![
(CString::new(format!("boot_{slot}")).unwrap(), format!("boot_v{ver}_{slot}.img")),
(CString::new(format!("vbmeta_{slot}")).unwrap(), vbmeta.clone()),
];
for (part, file) in additional_parts.iter().cloned() {
parts.push((part.into(), file.into()));
}
test_android_load_verify_fixup(
(u64::from(slot) - ('a' as u64)).try_into().unwrap(),
&parts,
&read_test_data(format!("kernel_{slot}.img")),
&read_test_data(format!("generic_ramdisk_{slot}.img")),
&make_expected_bootconfig(&vbmeta, slot, ""),
"existing_arg_1=existing_val_1 existing_arg_2=existing_val_2 cmd_key_1=cmd_val_1,cmd_key_2=cmd_val_2",
additional_expected_fdt_properties,
)
}
#[test]
fn test_android_load_verify_fixup_v0_slot_a() {
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[("/chosen", c"dtb_slot", Some(b"a\0"))];
// V0 image doesn't have built-in dtb. We need to provide from dtb partition.
let parts = &[(c"dtb_a", "dtb_a.img")];
test_android_load_verify_fixup_v2_or_lower(0, 'a', parts, fdt_prop);
}
#[test]
fn test_android_load_verify_fixup_v0_dtbo_slot_a() {
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[
("/chosen", c"dtb_slot", Some(b"a\0")),
("/chosen", c"overlay_a_property", Some(b"overlay_a_val\0")),
];
let parts = &[(c"dtbo_a", "dtbo_a.img"), (c"dtb_a", "dtb_a.img")];
test_android_load_verify_fixup_v2_or_lower(0, 'a', parts, fdt_prop);
}
#[test]
fn test_android_load_verify_fixup_v0_slot_b() {
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[("/chosen", c"dtb_slot", Some(b"b\0"))];
let parts = &[(c"dtb_b", "dtb_b.img")];
test_android_load_verify_fixup_v2_or_lower(0, 'b', parts, fdt_prop);
}
#[test]
fn test_android_load_verify_fixup_v0_dtbo_slot_b() {
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[
("/chosen", c"dtb_slot", Some(b"b\0")),
("/chosen", c"overlay_b_property", Some(b"overlay_b_val\0")),
];
let parts = &[(c"dtbo_b", "dtbo_b.img"), (c"dtb_b", "dtb_b.img")];
test_android_load_verify_fixup_v2_or_lower(0, 'b', parts, fdt_prop);
}
#[test]
fn test_android_load_verify_fixup_v1_slot_a() {
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[("/chosen", c"dtb_slot", Some(b"a\0"))];
// V1 image doesn't have built-in dtb. We need to provide from dtb partition.
let parts = &[(c"dtb_a", "dtb_a.img")];
test_android_load_verify_fixup_v2_or_lower(1, 'a', parts, fdt_prop);
}
#[test]
fn test_android_load_verify_fixup_v1_dtbo_slot_a() {
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[
("/chosen", c"dtb_slot", Some(b"a\0")),
("/chosen", c"overlay_a_property", Some(b"overlay_a_val\0")),
];
let parts = &[(c"dtbo_a", "dtbo_a.img"), (c"dtb_a", "dtb_a.img")];
test_android_load_verify_fixup_v2_or_lower(1, 'a', parts, fdt_prop);
}
#[test]
fn test_android_load_verify_fixup_v1_slot_b() {
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[("/chosen", c"dtb_slot", Some(b"b\0"))];
let parts = &[(c"dtb_b", "dtb_b.img")];
test_android_load_verify_fixup_v2_or_lower(1, 'b', parts, fdt_prop);
}
#[test]
fn test_android_load_verify_fixup_v1_dtbo_slot_b() {
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[
("/chosen", c"dtb_slot", Some(b"b\0")),
("/chosen", c"overlay_b_property", Some(b"overlay_b_val\0")),
];
let parts = &[(c"dtbo_b", "dtbo_b.img"), (c"dtb_b", "dtb_b.img")];
test_android_load_verify_fixup_v2_or_lower(1, 'b', parts, fdt_prop);
}
#[test]
fn test_android_load_verify_fixup_v2_slot_a() {
// V2 image has built-in dtb. We don't need to provide custom device tree.
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[("/chosen", c"builtin", Some(&[1]))];
test_android_load_verify_fixup_v2_or_lower(2, 'a', &[], fdt_prop);
}
#[test]
fn test_android_load_verify_fixup_v2_dtbo_slot_a() {
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[
("/chosen", c"builtin", Some(&[1])),
("/chosen", c"overlay_a_property", Some(b"overlay_a_val\0")),
];
let parts = &[(c"dtbo_a".into(), "dtbo_a.img".into())];
test_android_load_verify_fixup_v2_or_lower(2, 'a', parts, fdt_prop);
}
#[test]
fn test_android_load_verify_fixup_v2_slot_b() {
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[("/chosen", c"builtin", Some(&[1]))];
test_android_load_verify_fixup_v2_or_lower(2, 'b', &[], fdt_prop);
}
#[test]
fn test_android_load_verify_fixup_v2_dtbo_slot_b() {
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[
("/chosen", c"builtin", Some(&[1])),
("/chosen", c"overlay_b_property", Some(b"overlay_b_val\0")),
];
let parts = &[(c"dtbo_b".into(), "dtbo_b.img".into())];
test_android_load_verify_fixup_v2_or_lower(2, 'b', parts, fdt_prop);
}
/// Common helper for testing `android_load_verify_fixup` for v3/v4 boot image.
fn test_android_load_verify_fixup_v3_or_v4(
slot: char,
partitions: &[(CString, String)],
vbmeta_file: &str,
expected_vendor_bootconfig: &str,
additional_expected_fdt_properties: &[(&str, &CStr, Option<&[u8]>)],
) {
let expected_ramdisk = [
read_test_data(format!("vendor_ramdisk_{slot}.img")),
read_test_data(format!("generic_ramdisk_{slot}.img")),
]
.concat();
test_android_load_verify_fixup(
(u64::from(slot) - ('a' as u64)).try_into().unwrap(),
&partitions,
&read_test_data(format!("kernel_{slot}.img")),
&expected_ramdisk,
&make_expected_bootconfig(&vbmeta_file, slot, expected_vendor_bootconfig),
"existing_arg_1=existing_val_1 existing_arg_2=existing_val_2 cmd_key_1=cmd_val_1,cmd_key_2=cmd_val_2 cmd_vendor_key_1=cmd_vendor_val_1,cmd_vendor_key_2=cmd_vendor_val_2",
additional_expected_fdt_properties,
)
}
/// Helper for testing `android_load_verify_fixup` for v3/v4 boot image without init_boot.
fn test_android_load_verify_fixup_v3_or_v4_no_init_boot(
boot_ver: u32,
vendor_ver: u32,
slot: char,
expected_vendor_bootconfig: &str,
additional_parts: &[(CString, String)],
additional_expected_fdt_properties: &[(&str, &CStr, Option<&[u8]>)],
) {
let vbmeta = format!("vbmeta_v{boot_ver}_v{vendor_ver}_{slot}.img");
let mut parts: Vec<(CString, String)> = vec![
(CString::new(format!("boot_{slot}")).unwrap(), format!("boot_v{boot_ver}_{slot}.img")),
(
CString::new(format!("vendor_boot_{slot}")).unwrap(),
format!("vendor_boot_v{vendor_ver}_{slot}.img"),
),
(CString::new(format!("vbmeta_{slot}")).unwrap(), vbmeta.clone()),
];
parts.extend_from_slice(additional_parts);
test_android_load_verify_fixup_v3_or_v4(
slot,
&parts,
&vbmeta,
expected_vendor_bootconfig,
additional_expected_fdt_properties,
);
}
#[test]
fn test_android_load_verify_fixup_v3_v3_no_init_boot_slot_a() {
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[("/chosen", c"builtin", Some(&[1]))];
test_android_load_verify_fixup_v3_or_v4_no_init_boot(3, 3, 'a', "", &[], fdt_prop);
}
#[test]
fn test_android_load_verify_fixup_v3_v3_no_init_boot_dtbo_slot_a() {
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[
("/chosen", c"builtin", Some(&[1])),
("/chosen", c"overlay_a_property", Some(b"overlay_a_val\0")),
];
let parts = &[(c"dtbo_a".into(), "dtbo_a.img".into())];
test_android_load_verify_fixup_v3_or_v4_no_init_boot(3, 3, 'a', "", parts, fdt_prop);
}
#[test]
fn test_android_load_verify_fixup_v3_v3_no_init_boot_slot_b() {
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[("/chosen", c"builtin", Some(&[1]))];
test_android_load_verify_fixup_v3_or_v4_no_init_boot(3, 3, 'a', "", &[], fdt_prop);
}
#[test]
fn test_android_load_verify_fixup_v3_v3_no_init_boot_dtbo_slot_b() {
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[
("/chosen", c"builtin", Some(&[1])),
("/chosen", c"overlay_b_property", Some(b"overlay_b_val\0")),
];
let parts = &[(c"dtbo_b".into(), "dtbo_b.img".into())];
test_android_load_verify_fixup_v3_or_v4_no_init_boot(3, 3, 'b', "", parts, fdt_prop);
}
#[test]
fn test_android_load_verify_fixup_v4_v3_no_init_boot_slot_a() {
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[("/chosen", c"builtin", Some(&[1]))];
test_android_load_verify_fixup_v3_or_v4_no_init_boot(4, 3, 'a', "", &[], fdt_prop);
}
#[test]
fn test_android_load_verify_fixup_v4_v3_no_init_boot_dtbo_slot_a() {
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[
("/chosen", c"builtin", Some(&[1])),
("/chosen", c"overlay_a_property", Some(b"overlay_a_val\0")),
];
let parts = &[(c"dtbo_a".into(), "dtbo_a.img".into())];
test_android_load_verify_fixup_v3_or_v4_no_init_boot(4, 3, 'a', "", parts, fdt_prop);
}
#[test]
fn test_android_load_verify_fixup_v4_v3_no_init_boot_slot_b() {
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[("/chosen", c"builtin", Some(&[1]))];
test_android_load_verify_fixup_v3_or_v4_no_init_boot(4, 3, 'a', "", &[], fdt_prop);
}
#[test]
fn test_android_load_verify_fixup_v4_v3_no_init_boot_dtbo_slot_b() {
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[
("/chosen", c"builtin", Some(&[1])),
("/chosen", c"overlay_b_property", Some(b"overlay_b_val\0")),
];
let parts = &[(c"dtbo_b".into(), "dtbo_b.img".into())];
test_android_load_verify_fixup_v3_or_v4_no_init_boot(4, 3, 'b', "", parts, fdt_prop);
}
#[test]
fn test_android_load_verify_fixup_v3_v4_no_init_boot_slot_a() {
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[("/chosen", c"builtin", Some(&[1]))];
let config = TEST_VENDOR_BOOTCONFIG;
test_android_load_verify_fixup_v3_or_v4_no_init_boot(3, 4, 'a', config, &[], fdt_prop);
}
#[test]
fn test_android_load_verify_fixup_v3_v4_no_init_boot_dtbo_slot_a() {
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[
("/chosen", c"builtin", Some(&[1])),
("/chosen", c"overlay_a_property", Some(b"overlay_a_val\0")),
];
let parts = &[(c"dtbo_a".into(), "dtbo_a.img".into())];
let config = TEST_VENDOR_BOOTCONFIG;
test_android_load_verify_fixup_v3_or_v4_no_init_boot(3, 4, 'a', config, parts, fdt_prop);
}
#[test]
fn test_android_load_verify_fixup_v3_v4_no_init_boot_slot_b() {
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[("/chosen", c"builtin", Some(&[1]))];
let config = TEST_VENDOR_BOOTCONFIG;
test_android_load_verify_fixup_v3_or_v4_no_init_boot(3, 4, 'a', config, &[], fdt_prop);
}
#[test]
fn test_android_load_verify_fixup_v3_v4_no_init_boot_dtbo_slot_b() {
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[
("/chosen", c"builtin", Some(&[1])),
("/chosen", c"overlay_b_property", Some(b"overlay_b_val\0")),
];
let parts = &[(c"dtbo_b".into(), "dtbo_b.img".into())];
let config = TEST_VENDOR_BOOTCONFIG;
test_android_load_verify_fixup_v3_or_v4_no_init_boot(3, 4, 'b', config, parts, fdt_prop);
}
#[test]
fn test_android_load_verify_fixup_v4_v4_no_init_boot_slot_a() {
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[("/chosen", c"builtin", Some(&[1]))];
let config = TEST_VENDOR_BOOTCONFIG;
test_android_load_verify_fixup_v3_or_v4_no_init_boot(4, 4, 'a', config, &[], fdt_prop);
}
#[test]
fn test_android_load_verify_fixup_v4_v4_no_init_boot_dtbo_slot_a() {
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[
("/chosen", c"builtin", Some(&[1])),
("/chosen", c"overlay_a_property", Some(b"overlay_a_val\0")),
];
let parts = &[(c"dtbo_a".into(), "dtbo_a.img".into())];
let config = TEST_VENDOR_BOOTCONFIG;
test_android_load_verify_fixup_v3_or_v4_no_init_boot(4, 4, 'a', config, parts, fdt_prop);
}
#[test]
fn test_android_load_verify_fixup_v4_v4_no_init_boot_slot_b() {
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[("/chosen", c"builtin", Some(&[1]))];
let config = TEST_VENDOR_BOOTCONFIG;
test_android_load_verify_fixup_v3_or_v4_no_init_boot(4, 4, 'a', config, &[], fdt_prop);
}
#[test]
fn test_android_load_verify_fixup_v4_v4_no_init_boot_dtbo_slot_b() {
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[
("/chosen", c"builtin", Some(&[1])),
("/chosen", c"overlay_b_property", Some(b"overlay_b_val\0")),
];
let parts = &[(c"dtbo_b".into(), "dtbo_b.img".into())];
let config = TEST_VENDOR_BOOTCONFIG;
test_android_load_verify_fixup_v3_or_v4_no_init_boot(4, 4, 'b', config, parts, fdt_prop);
}
/// Helper for testing `android_load_verify_fixup` for v3/v4 boot image with init_boot.
fn test_android_load_verify_fixup_v3_or_v4_init_boot(
boot_ver: u32,
vendor_ver: u32,
slot: char,
expected_vendor_bootconfig: &str,
additional_parts: &[(CString, String)],
additional_expected_fdt_properties: &[(&str, &CStr, Option<&[u8]>)],
) {
let vbmeta = format!("vbmeta_v{boot_ver}_v{vendor_ver}_init_boot_{slot}.img");
let mut parts: Vec<(CString, String)> = vec![
(
CString::new(format!("boot_{slot}")).unwrap(),
format!("boot_no_ramdisk_v{boot_ver}_{slot}.img"),
),
(
CString::new(format!("vendor_boot_{slot}")).unwrap(),
format!("vendor_boot_v{vendor_ver}_{slot}.img"),
),
(CString::new(format!("init_boot_{slot}")).unwrap(), format!("init_boot_{slot}.img")),
(CString::new(format!("vbmeta_{slot}")).unwrap(), vbmeta.clone()),
];
parts.extend_from_slice(additional_parts);
test_android_load_verify_fixup_v3_or_v4(
slot,
&parts,
&vbmeta,
expected_vendor_bootconfig,
additional_expected_fdt_properties,
);
}
#[test]
fn test_android_load_verify_fixup_v3_v3_init_boot_slot_a() {
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[("/chosen", c"builtin", Some(&[1]))];
test_android_load_verify_fixup_v3_or_v4_init_boot(3, 3, 'a', "", &[], fdt_prop);
}
#[test]
fn test_android_load_verify_fixup_v3_v3_init_boot_dtbo_slot_a() {
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[
("/chosen", c"builtin", Some(&[1])),
("/chosen", c"overlay_a_property", Some(b"overlay_a_val\0")),
];
let parts = &[(c"dtbo_a".into(), "dtbo_a.img".into())];
test_android_load_verify_fixup_v3_or_v4_init_boot(3, 3, 'a', "", parts, fdt_prop);
}
#[test]
fn test_android_load_verify_fixup_v3_v3_init_boot_slot_b() {
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[("/chosen", c"builtin", Some(&[1]))];
test_android_load_verify_fixup_v3_or_v4_init_boot(3, 3, 'a', "", &[], fdt_prop);
}
#[test]
fn test_android_load_verify_fixup_v3_v3_init_boot_dtbo_slot_b() {
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[
("/chosen", c"builtin", Some(&[1])),
("/chosen", c"overlay_b_property", Some(b"overlay_b_val\0")),
];
let parts = &[(c"dtbo_b".into(), "dtbo_b.img".into())];
test_android_load_verify_fixup_v3_or_v4_init_boot(3, 3, 'b', "", parts, fdt_prop);
}
#[test]
fn test_android_load_verify_fixup_v4_v3_init_boot_slot_a() {
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[("/chosen", c"builtin", Some(&[1]))];
test_android_load_verify_fixup_v3_or_v4_init_boot(4, 3, 'a', "", &[], fdt_prop);
}
#[test]
fn test_android_load_verify_fixup_v4_v3_init_boot_dtbo_slot_a() {
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[
("/chosen", c"builtin", Some(&[1])),
("/chosen", c"overlay_a_property", Some(b"overlay_a_val\0")),
];
let parts = &[(c"dtbo_a".into(), "dtbo_a.img".into())];
test_android_load_verify_fixup_v3_or_v4_init_boot(4, 3, 'a', "", parts, fdt_prop);
}
#[test]
fn test_android_load_verify_fixup_v4_v3_init_boot_slot_b() {
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[("/chosen", c"builtin", Some(&[1]))];
test_android_load_verify_fixup_v3_or_v4_init_boot(4, 3, 'a', "", &[], fdt_prop);
}
#[test]
fn test_android_load_verify_fixup_v4_v3_init_boot_dtbo_slot_b() {
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[
("/chosen", c"builtin", Some(&[1])),
("/chosen", c"overlay_b_property", Some(b"overlay_b_val\0")),
];
let parts = &[(c"dtbo_b".into(), "dtbo_b.img".into())];
test_android_load_verify_fixup_v3_or_v4_init_boot(4, 3, 'b', "", parts, fdt_prop);
}
#[test]
fn test_android_load_verify_fixup_v3_v4_init_boot_slot_a() {
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[("/chosen", c"builtin", Some(&[1]))];
let config = TEST_VENDOR_BOOTCONFIG;
test_android_load_verify_fixup_v3_or_v4_init_boot(3, 4, 'a', config, &[], fdt_prop);
}
#[test]
fn test_android_load_verify_fixup_v3_v4_init_boot_dtbo_slot_a() {
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[
("/chosen", c"builtin", Some(&[1])),
("/chosen", c"overlay_a_property", Some(b"overlay_a_val\0")),
];
let parts = &[(c"dtbo_a".into(), "dtbo_a.img".into())];
let config = TEST_VENDOR_BOOTCONFIG;
test_android_load_verify_fixup_v3_or_v4_init_boot(3, 4, 'a', config, parts, fdt_prop);
}
#[test]
fn test_android_load_verify_fixup_v3_v4_init_boot_slot_b() {
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[("/chosen", c"builtin", Some(&[1]))];
let config = TEST_VENDOR_BOOTCONFIG;
test_android_load_verify_fixup_v3_or_v4_init_boot(3, 4, 'a', config, &[], fdt_prop);
}
#[test]
fn test_android_load_verify_fixup_v3_v4_init_boot_dtbo_slot_b() {
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[
("/chosen", c"builtin", Some(&[1])),
("/chosen", c"overlay_b_property", Some(b"overlay_b_val\0")),
];
let parts = &[(c"dtbo_b".into(), "dtbo_b.img".into())];
let config = TEST_VENDOR_BOOTCONFIG;
test_android_load_verify_fixup_v3_or_v4_init_boot(3, 4, 'b', config, parts, fdt_prop);
}
#[test]
fn test_android_load_verify_fixup_v4_v4_init_boot_slot_a() {
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[("/chosen", c"builtin", Some(&[1]))];
let config = TEST_VENDOR_BOOTCONFIG;
test_android_load_verify_fixup_v3_or_v4_init_boot(4, 4, 'a', config, &[], fdt_prop);
}
#[test]
fn test_android_load_verify_fixup_v4_v4_init_boot_dtbo_slot_a() {
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[
("/chosen", c"builtin", Some(&[1])),
("/chosen", c"overlay_a_property", Some(b"overlay_a_val\0")),
];
let parts = &[(c"dtbo_a".into(), "dtbo_a.img".into())];
let config = TEST_VENDOR_BOOTCONFIG;
test_android_load_verify_fixup_v3_or_v4_init_boot(4, 4, 'a', config, parts, fdt_prop);
}
#[test]
fn test_android_load_verify_fixup_v4_v4_init_boot_slot_b() {
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[("/chosen", c"builtin", Some(&[1]))];
let config = TEST_VENDOR_BOOTCONFIG;
test_android_load_verify_fixup_v3_or_v4_init_boot(4, 4, 'a', config, &[], fdt_prop);
}
#[test]
fn test_android_load_verify_fixup_v4_v4_init_boot_dtbo_slot_b() {
let fdt_prop: &[(&str, &CStr, Option<&[u8]>)] = &[
("/chosen", c"builtin", Some(&[1])),
("/chosen", c"overlay_b_property", Some(b"overlay_b_val\0")),
];
let parts = &[(c"dtbo_b".into(), "dtbo_b.img".into())];
let config = TEST_VENDOR_BOOTCONFIG;
test_android_load_verify_fixup_v3_or_v4_init_boot(4, 4, 'b', config, parts, fdt_prop);
}
/// Helper for checking V2 image loaded from slot A and in normal mode.
fn checks_loaded_v2_slot_a_normal_mode(ramdisk: &[u8], kernel: &[u8]) {
let expected_bootconfig = AvbResultBootconfigBuilder::new()
.vbmeta_size(read_test_data("vbmeta_v2_a.img").len())
.digest(read_test_data_as_str("vbmeta_v2_a.digest.txt").strip_suffix("\n").unwrap())
.public_key_digest(TEST_PUBLIC_KEY_DIGEST)
.extra(FakeGblOps::GBL_TEST_BOOTCONFIG)
.extra("androidboot.force_normal_boot=1\n")
.extra(format!("androidboot.slot_suffix=_a\n"))
.build();
check_ramdisk(ramdisk, &read_test_data("generic_ramdisk_a.img"), &expected_bootconfig);
assert_eq!(kernel, read_test_data("kernel_a.img"));
}
/// Helper for checking V2 image loaded from slot A and in recovery mode.
fn checks_loaded_v2_slot_a_recovery_mode(ramdisk: &[u8], kernel: &[u8]) {
let expected_bootconfig = AvbResultBootconfigBuilder::new()
.vbmeta_size(read_test_data("vbmeta_v2_a.img").len())
.digest(read_test_data_as_str("vbmeta_v2_a.digest.txt").strip_suffix("\n").unwrap())
.public_key_digest(TEST_PUBLIC_KEY_DIGEST)
.extra(FakeGblOps::GBL_TEST_BOOTCONFIG)
.extra(format!("androidboot.slot_suffix=_a\n"))
.build();
check_ramdisk(ramdisk, &read_test_data("generic_ramdisk_a.img"), &expected_bootconfig);
assert_eq!(kernel, read_test_data("kernel_a.img"));
}
/// Helper for getting default FakeGblOps for tests.
fn default_test_gbl_ops(storage: &FakeGblOpsStorage) -> FakeGblOps {
let mut ops = FakeGblOps::new(&storage);
ops.avb_ops.unlock_state = Ok(false);
ops.avb_ops.rollbacks = HashMap::from([(TEST_ROLLBACK_INDEX_LOCATION, Ok(0))]);
ops.avb_key_validation_status = Some(Ok(KeyValidationStatus::Valid));
ops
}
#[test]
fn test_android_load_verify_fixup_recovery_mode() {
// Recovery mode is specified by the absence of bootconfig arg
// "androidboot.force_normal_boot=1\n" and therefore independent of image versions. We can
// pick any image version for test. Use v2 for simplicity.
let mut storage = FakeGblOpsStorage::default();
storage.add_raw_device(c"boot_a", read_test_data("boot_v2_a.img"));
storage.add_raw_device(c"vbmeta_a", read_test_data("vbmeta_v2_a.img"));
let mut ops = default_test_gbl_ops(&storage);
let mut load_buffer = AlignedBuffer::new(8 * 1024 * 1024, KERNEL_ALIGNMENT);
let (ramdisk, _, kernel, _) =
android_load_verify_fixup(&mut ops, 0, true, &mut load_buffer).unwrap();
checks_loaded_v2_slot_a_recovery_mode(ramdisk, kernel)
}
#[test]
fn test_android_main_bcb_normal_mode() {
let mut storage = FakeGblOpsStorage::default();
storage.add_raw_device(c"boot_a", read_test_data("boot_v2_a.img"));
storage.add_raw_device(c"vbmeta_a", read_test_data("vbmeta_v2_a.img"));
storage.add_raw_device(c"misc", vec![0u8; 4 * 1024 * 1024]);
let mut ops = default_test_gbl_ops(&storage);
let mut load_buffer = AlignedBuffer::new(8 * 1024 * 1024, KERNEL_ALIGNMENT);
let (ramdisk, _, kernel, _) = android_main(&mut ops, &mut load_buffer).unwrap();
checks_loaded_v2_slot_a_normal_mode(ramdisk, kernel)
}
#[test]
fn test_android_main_bcb_recovery_mode() {
let mut storage = FakeGblOpsStorage::default();
storage.add_raw_device(c"boot_a", read_test_data("boot_v2_a.img"));
storage.add_raw_device(c"vbmeta_a", read_test_data("vbmeta_v2_a.img"));
storage.add_raw_device(c"misc", vec![0u8; 4 * 1024 * 1024]);
let mut ops = default_test_gbl_ops(&storage);
ops.write_to_partition_sync("misc", 0, &mut b"boot-recovery".to_vec()).unwrap();
let mut load_buffer = AlignedBuffer::new(8 * 1024 * 1024, KERNEL_ALIGNMENT);
let (ramdisk, _, kernel, _) = android_main(&mut ops, &mut load_buffer).unwrap();
checks_loaded_v2_slot_a_recovery_mode(ramdisk, kernel)
}
}