blob: c95eacf94a1a35c80bc77f30825f470920686972 [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.
use super::{avb_verify_slot, cstr_bytes_to_str};
use crate::{
android_boot::PartitionsToVerify,
constants::{FDT_ALIGNMENT, KERNEL_ALIGNMENT},
decompress::{decompress_kernel, is_compressed},
gbl_print, gbl_println,
ops::GblOps,
partition::RAW_PARTITION_NAME_LEN,
IntegrationError,
};
use arrayvec::ArrayString;
use bootimg::{defs::*, BootImage, VendorImageHeader};
use bootparams::bootconfig::BootConfigBuilder;
use core::{
array,
ffi::CStr,
fmt::Write,
ops::{Deref, Range},
};
use liberror::Error;
use safemath::SafeNum;
use zerocopy::{IntoBytes, Ref};
// Value of page size for v3/v4 header.
const PAGE_SIZE: usize = 4096;
// Represents a slot suffix.
struct SlotSuffix([u8; 3]);
impl SlotSuffix {
// Creates a new instance.
fn new(slot: u8) -> Result<Self, Error> {
let suffix = u32::from(slot) + u32::from(b'a');
match char::from_u32(suffix).map(|v| v.is_ascii_lowercase()) {
Some(true) => Ok(Self([b'_', suffix.try_into().unwrap(), 0])),
_ => Err(Error::Other(Some("Invalid slot index"))),
}
}
// Casts as CStr.
fn as_cstr(&self) -> &CStr {
CStr::from_bytes_with_nul(&self.0[..]).unwrap()
}
}
impl Deref for SlotSuffix {
type Target = str;
fn deref(&self) -> &Self::Target {
self.as_cstr().to_str().unwrap()
}
}
/// Returns a slotted partition name.
fn slotted_part(part: &str, slot: u8) -> Result<ArrayString<RAW_PARTITION_NAME_LEN>, Error> {
let mut res = ArrayString::new_const();
write!(res, "{}{}", part, &SlotSuffix::new(slot)? as &str).unwrap();
Ok(res)
}
// Helper for constructing a range that ends at a page aligned boundary. Specifically, it returns
// `start..round_up(start + sz, page_size)`
fn page_aligned_range(
start: impl Into<SafeNum>,
sz: impl Into<SafeNum>,
page_size: impl Into<SafeNum>,
) -> Result<Range<usize>, Error> {
let start = start.into();
Ok(start.try_into()?..(start + sz.into()).round_up(page_size.into()).try_into()?)
}
/// Represents a loaded boot image of version 2 and lower.
///
/// TODO(b/384964561): Investigate if the APIs are better suited for bootimg.rs. The issue
/// is that it uses `Error` and `SafeNum` from GBL.
struct BootImageV2Info<'a> {
cmdline: &'a str,
page_size: usize,
kernel_range: Range<usize>,
ramdisk_range: Range<usize>,
dtb_range: Range<usize>,
image_size: usize,
}
impl<'a> BootImageV2Info<'a> {
/// Creates a new instance.
fn new(buffer: &'a [u8]) -> Result<Self, Error> {
let header = BootImage::parse(buffer)?;
if matches!(header, BootImage::V3(_) | BootImage::V4(_)) {
return Err(Error::InvalidInput);
}
// This is valid since v1/v2 are superset of v0.
let v0 = Ref::into_ref(Ref::<_, boot_img_hdr_v0>::from_prefix(&buffer[..]).unwrap().0);
let page_size: usize = v0.page_size.try_into()?;
let cmdline = cstr_bytes_to_str(&v0.cmdline[..])?;
let kernel_range = page_aligned_range(page_size, v0.kernel_size, page_size)?;
let ramdisk_range = page_aligned_range(kernel_range.end, v0.ramdisk_size, page_size)?;
let second_range = page_aligned_range(ramdisk_range.end, v0.second_size, page_size)?;
let start = u64::try_from(second_range.end)?;
let (off, sz) = match header {
BootImage::V1(v) => (v.recovery_dtbo_offset, v.recovery_dtbo_size),
BootImage::V2(v) => (v._base.recovery_dtbo_offset, v._base.recovery_dtbo_size),
_ => (start, 0),
};
let recovery_dtb_range = match off >= start {
true => page_aligned_range(off, sz, page_size)?,
_ if off == 0 => page_aligned_range(start, 0, page_size)?,
_ => return Err(Error::Other(Some("Unexpected recovery_dtbo_offset"))),
};
let dtb_sz = match header {
BootImage::V2(v) => v.dtb_size,
_ => 0,
};
let dtb_range = page_aligned_range(recovery_dtb_range.end, dtb_sz, page_size)?;
let image_size = dtb_range.end;
Ok(Self { cmdline, page_size, kernel_range, ramdisk_range, dtb_range, image_size })
}
}
// Contains information of a V3/V4 boot image.
struct BootImageV3Info {
kernel_range: Range<usize>,
ramdisk_range: Range<usize>,
image_size: usize,
}
impl BootImageV3Info {
/// Creates a new instance.
fn new(buffer: &[u8]) -> Result<Self, Error> {
let header = BootImage::parse(buffer)?;
if !matches!(header, BootImage::V3(_) | BootImage::V4(_)) {
return Err(Error::InvalidInput);
}
let v3 = Self::v3(buffer);
let kernel_range = page_aligned_range(PAGE_SIZE, v3.kernel_size, PAGE_SIZE)?;
let ramdisk_range = page_aligned_range(kernel_range.end, v3.ramdisk_size, PAGE_SIZE)?;
let sz = match header {
BootImage::V4(v) => v.signature_size,
_ => 0,
};
let signature_range = page_aligned_range(ramdisk_range.end, sz, PAGE_SIZE)?;
let image_size = signature_range.end;
Ok(Self { kernel_range, ramdisk_range, image_size })
}
/// Gets the v3 base header.
fn v3(buffer: &[u8]) -> &boot_img_hdr_v3 {
// This is valid since v4 is superset of v3.
Ref::into_ref(Ref::from_prefix(&buffer[..]).unwrap().0)
}
// Decodes the kernel cmdline
fn cmdline(buffer: &[u8]) -> Result<&str, Error> {
cstr_bytes_to_str(&Self::v3(buffer).cmdline[..])
}
}
/// Contains vendor boot image information.
struct VendorBootImageInfo {
header_size: usize,
ramdisk_range: Range<usize>,
dtb_range: Range<usize>,
bootconfig_range: Range<usize>,
image_size: usize,
}
impl VendorBootImageInfo {
/// Creates a new instance.
fn new(buffer: &[u8]) -> Result<Self, Error> {
let header = VendorImageHeader::parse(buffer)?;
let v3 = Self::v3(buffer);
let page_size = v3.page_size;
let header_size = match header {
VendorImageHeader::V3(hdr) => SafeNum::from(hdr.as_bytes().len()),
VendorImageHeader::V4(hdr) => SafeNum::from(hdr.as_bytes().len()),
}
.round_up(page_size)
.try_into()?;
let ramdisk_range = page_aligned_range(header_size, v3.vendor_ramdisk_size, page_size)?;
let dtb_range = page_aligned_range(ramdisk_range.end, v3.dtb_size, page_size)?;
let (table_sz, bootconfig_sz) = match header {
VendorImageHeader::V4(hdr) => (hdr.vendor_ramdisk_table_size, hdr.bootconfig_size),
_ => (0, 0),
};
let table = page_aligned_range(dtb_range.end, table_sz, page_size)?;
let bootconfig_range = table.end..(table.end + usize::try_from(bootconfig_sz)?);
let image_size = SafeNum::from(bootconfig_range.end).round_up(page_size).try_into()?;
Ok(Self { header_size, ramdisk_range, dtb_range, bootconfig_range, image_size })
}
/// Gets the v3 base header.
fn v3(buffer: &[u8]) -> &vendor_boot_img_hdr_v3 {
Ref::into_ref(Ref::<_, _>::from_prefix(&buffer[..]).unwrap().0)
}
// Decodes the vendor cmdline
fn cmdline(buffer: &[u8]) -> Result<&str, Error> {
cstr_bytes_to_str(&Self::v3(buffer).cmdline[..])
}
}
/// Contains various loaded image components by `android_load_verify`
pub struct LoadedImages<'a> {
/// dtbo image.
pub dtbo: &'a mut [u8],
/// Kernel commandline.
pub boot_cmdline: &'a str,
/// Vendor commandline,
pub vendor_cmdline: &'a str,
/// DTB.
pub dtb: &'a mut [u8],
/// Kernel image.
pub kernel: &'a mut [u8],
/// Ramdisk image.
pub ramdisk: &'a mut [u8],
/// Unused portion. Can be used by the caller to construct FDT.
pub unused: &'a mut [u8],
}
impl<'a> Default for LoadedImages<'a> {
fn default() -> LoadedImages<'a> {
LoadedImages {
dtbo: &mut [][..],
boot_cmdline: "",
vendor_cmdline: "",
dtb: &mut [][..],
kernel: &mut [][..],
ramdisk: &mut [][..],
unused: &mut [][..],
}
}
}
/// Loads and verifies Android images of the given slot.
pub fn android_load_verify<'a, 'b, 'c>(
ops: &mut impl GblOps<'a, 'b>,
slot: u8,
load: &'c mut [u8],
) -> Result<LoadedImages<'c>, IntegrationError> {
let mut res = LoadedImages::default();
// Loads dtbo.
let dtbo_part = slotted_part("dtbo", slot)?;
let (dtbo, remains) = load_entire_part(ops, &dtbo_part, &mut load[..])?;
// Additional partitions loaded before loading standard boot images.
let mut partitions = PartitionsToVerify::default();
if dtbo.len() > 0 {
partitions.try_push_preloaded(c"dtbo", &dtbo[..])?;
}
// Loads boot image header and inspect version
ops.read_from_partition_sync(&slotted_part("boot", slot)?, 0, &mut remains[..PAGE_SIZE])?;
match BootImage::parse(&remains[..]).map_err(Error::from)? {
BootImage::V3(_) | BootImage::V4(_) => {
load_verify_v3_and_v4(ops, slot, &partitions, &mut res, remains)?
}
_ => load_verify_v2_and_lower(ops, slot, &partitions, &mut res, remains)?,
};
drop(partitions);
res.dtbo = dtbo;
Ok(res)
}
/// Loads and verifies android boot images of version 0, 1 and 2.
///
/// * Both kernel and ramdisk come from the boot image.
/// * vendor_boot, init_boot are irrelevant.
///
/// # Args
///
/// * `ops`: An implementation of [GblOps].
/// * `slot`: slot index.
/// * `additional_partitions`: Additional partitions for verification.
/// * `out`: A `&mut LoadedImages` for output.
/// * `load`: The load buffer. The boot header must be preloaded into this buffer.
fn load_verify_v2_and_lower<'a, 'b, 'c>(
ops: &mut impl GblOps<'a, 'b>,
slot: u8,
additional_partitions: &PartitionsToVerify,
out: &mut LoadedImages<'c>,
load: &'c mut [u8],
) -> Result<(), IntegrationError> {
// Loads boot image.
let boot_size = BootImageV2Info::new(load).unwrap().image_size;
let boot_part = slotted_part("boot", slot)?;
let (boot, remains) = split(load, boot_size)?;
ops.read_from_partition_sync(&boot_part, 0, boot)?;
// Performs libavb verification.
// Prepares a BootConfigBuilder to add avb generated bootconfig.
let mut bootconfig_builder = BootConfigBuilder::new(remains)?;
// Puts in a subscope for auto dropping `to_verify`, so that the slices it
// borrows can be released.
{
let mut to_verify = PartitionsToVerify::default();
to_verify.try_push_preloaded(c"boot", &boot[..])?;
to_verify.try_extend_preloaded(additional_partitions)?;
avb_verify_slot(ops, slot, &to_verify, &mut bootconfig_builder)?;
}
// Adds platform-specific bootconfig.
bootconfig_builder.add_with(|bytes, out| {
Ok(ops.fixup_bootconfig(&bytes, out)?.map(|slice| slice.len()).unwrap_or(0))
})?;
let bootconfig_size = bootconfig_builder.config_bytes().len();
// We now have the following layout:
//
// | boot_hdr | kernel | ramdisk | second | recovery_dtb | dtb | bootconfig | remains |
// |------------------------------`boot_ex`---------------------------------|
//
// We need to:
// 1. move bootconfig to after ramdisk.
// 2. relocate the kernel to the tail so that all memory after it can be used as scratch memory.
// It is observed that riscv kernel reaches into those memory and overwrites data.
//
// TODO(b/384964561): Investigate if `second`, `recovery_dtb` needs to be kept.
let (boot_ex, remains) = load.split_at_mut(boot_size + bootconfig_size);
let boot_img = BootImageV2Info::new(boot_ex).unwrap();
let page_size = boot_img.page_size;
// Relocates kernel to tail.
let kernel_range = boot_img.kernel_range;
let kernel = boot_ex.get(kernel_range.clone()).unwrap();
let (remains, _) = relocate_kernel(ops, kernel, remains)?;
// Relocates dtb to tail.
let dtb_range = boot_img.dtb_range;
let (_, dtb) = split_aligned_tail(remains, dtb_range.len(), FDT_ALIGNMENT)?;
dtb[..dtb_range.len()].clone_from_slice(boot_ex.get(dtb_range).unwrap());
// Move ramdisk forward and bootconfig following it.
let ramdisk_range = boot_img.ramdisk_range;
boot_ex.copy_within(ramdisk_range.start..ramdisk_range.end, kernel_range.start);
boot_ex.copy_within(boot_size.., kernel_range.start + ramdisk_range.len());
// We now have the following layout:
// | boot_hdr | ramdisk + bootconfig | unused | dtb | kernel |
let ramdisk_sz = ramdisk_range.len() + bootconfig_size;
let unused_sz = slice_offset(dtb, boot_ex) - page_size - ramdisk_sz;
let dtb_sz = dtb.len();
let hdr;
([hdr, out.ramdisk, out.unused, out.dtb], out.kernel) =
split_chunks(load, &[page_size, ramdisk_sz, unused_sz, dtb_sz]);
out.boot_cmdline = BootImageV2Info::new(hdr).unwrap().cmdline;
Ok(())
}
/// Loads and verifies android boot images of version 3 and 4.
///
/// V3, V4 images have the following characteristics:
///
/// * Kernel comes from "boot_a/b" partition.
/// * Generic ramdisk may come from either "boot_a/b" or "init_boot_a/b" partitions.
/// * Vendor ramdisk comes from "vendor_boot_a/b" partition.
/// * V4 vendor_boot contains additional bootconfig.
///
/// From the perspective of Android versions:
///
/// Android 11:
///
/// * Can use v3 header.
/// * Generic ramdisk is in the "boot_a/b" partitions.
///
/// Android 12:
///
/// * Can use v3 or v4 header.
/// * Generic ramdisk is in the "boot_a/b" partitions.
///
/// Android 13:
///
/// * Can use v3 or v4 header.
/// * Generic ramdisk is in the "init_boot_a/b" partitions.
///
/// # References
///
/// https://source.android.com/docs/core/architecture/bootloader/boot-image-header
/// https://source.android.com/docs/core/architecture/partitions/vendor-boot-partitions
/// https://source.android.com/docs/core/architecture/partitions/generic-boot
///
/// # Args
///
/// * `ops`: An implementation of [GblOps].
/// * `slot`: slot index.
/// * `additional_partitions`: Additional partitions for verification.
/// * `out`: A `&mut LoadedImages` for output.
/// * `load`: The load buffer. The boot header must be preloaded into this buffer.
fn load_verify_v3_and_v4<'a, 'b, 'c>(
ops: &mut impl GblOps<'a, 'b>,
slot: u8,
additional_partitions: &PartitionsToVerify,
out: &mut LoadedImages<'c>,
load: &'c mut [u8],
) -> Result<(), IntegrationError> {
// Creates a `start` marker for `slice_offset()` to compute absolute slice offset later.
let (start, load) = load.split_at_mut(0);
let boot_part = slotted_part("boot", slot)?;
let vendor_boot_part = slotted_part("vendor_boot", slot)?;
let init_boot_part = slotted_part("init_boot", slot)?;
let boot_img_info = BootImageV3Info::new(load).unwrap();
// Loads vendor boot image.
ops.read_from_partition_sync(&vendor_boot_part, 0, &mut load[..PAGE_SIZE])?;
let vendor_boot_info = VendorBootImageInfo::new(&load[..PAGE_SIZE])?;
let (vendor_boot, remains) = split(&mut load[..], vendor_boot_info.image_size)?;
ops.read_from_partition_sync(&vendor_boot_part, 0, vendor_boot)?;
// Loads boot image.
let (boot, remains) = split(remains, boot_img_info.image_size)?;
ops.read_from_partition_sync(&boot_part, 0, boot)?;
// Loads init_boot image if boot doesn't contain a ramdisk.
let (init_boot, remains, init_boot_info) = match boot_img_info.ramdisk_range.len() > 0 {
false => {
ops.read_from_partition_sync(&init_boot_part, 0, &mut remains[..PAGE_SIZE])?;
let init_boot_info = BootImageV3Info::new(&remains[..])?;
let (out, remains) = split(remains, init_boot_info.image_size)?;
ops.read_from_partition_sync(&init_boot_part, 0, out)?;
(out, remains, Some(init_boot_info))
}
_ => (&mut [][..], remains, None),
};
// Performs libavb verification.
// Prepares a BootConfigBuilder to add avb generated bootconfig.
let mut bootconfig_builder = BootConfigBuilder::new(remains)?;
// Puts in a subscope for auto dropping `to_verify`, so that the slices it
// borrows can be released.
{
let mut to_verify = PartitionsToVerify::default();
to_verify.try_push_preloaded(c"boot", &boot)?;
to_verify.try_push_preloaded(c"vendor_boot", &vendor_boot)?;
if init_boot.len() > 0 {
to_verify.try_push_preloaded(c"init_boot", &init_boot)?;
}
to_verify.try_extend_preloaded(additional_partitions)?;
avb_verify_slot(ops, slot, &to_verify, &mut bootconfig_builder)?;
}
// Adds platform-specific bootconfig.
bootconfig_builder.add_with(|bytes, out| {
Ok(ops.fixup_bootconfig(&bytes, out)?.map(|slice| slice.len()).unwrap_or(0))
})?;
// We now have the following layout:
//
// +------------------------+
// | vendor boot header |
// +------------------------+
// | vendor ramdisk |
// +------------------------+
// | dtb |
// +------------------------+
// | vendor ramdisk table |
// +------------------------+
// | vendor bootconfig |
// +------------------------+ +------------------------+
// | boot hdr | | boot hdr |
// +------------------------+ +------------------------+
// | kernel | | kernel |
// +------------------------+ +------------------------+
// | | | boot signature |
// | | or +------------------------+
// | generic ramdisk | | init_boot hdr |
// | | +------------------------+
// | | | generic ramdisk |
// +------------------------+ +------------------------+
// | boot signature | | boot signature |
// +------------------------+ +------------------------+
// | avb + board bootconfig |
// +------------------------+
// | unused |
// +------------------------+
//
// We need to:
// * Relocate kernel to the tail of the load buffer to reserve all memory after it for scratch.
// * Relocates dtb, boot hdr to elsewhere.
// * Move generic ramdisk to follow vendor ramdisk.
// * Move vendor bootconfig, avb + board bootconfig to follow generic ramdisk.
// Appends vendor bootconfig so that the section can be discarded.
let vendor_bootconfig = vendor_boot.get(vendor_boot_info.bootconfig_range).unwrap();
bootconfig_builder.add_with(|_, out| {
out.get_mut(..vendor_bootconfig.len())
.ok_or(Error::BufferTooSmall(Some(vendor_bootconfig.len())))?
.clone_from_slice(vendor_bootconfig);
Ok(vendor_bootconfig.len())
})?;
let bootconfig_size = bootconfig_builder.config_bytes().len();
let (bootconfig, remains) = remains.split_at_mut(bootconfig_size);
// Relocates kernel to tail.
let kernel = boot.get(boot_img_info.kernel_range.clone()).unwrap();
let (remains, kernel) = relocate_kernel(ops, kernel, remains)?;
let kernel_sz = kernel.len();
// Relocates boot header to tail.
let (remains, boot_hdr) = split_aligned_tail(remains, PAGE_SIZE, 1)?;
boot_hdr.clone_from_slice(&boot[..PAGE_SIZE]);
let boot_hdr_sz = boot_hdr.len();
// Relocates dtb to tail.
let dtb = vendor_boot.get(vendor_boot_info.dtb_range).unwrap();
let (_, dtb_reloc) = split_aligned_tail(remains, dtb.len(), FDT_ALIGNMENT)?;
dtb_reloc[..dtb.len()].clone_from_slice(dtb);
let dtb_sz = dtb_reloc.len();
// Moves generic ramdisk and bootconfig forward
let generic_ramdisk_range = match init_boot_info {
Some(v) => offset_range(v.ramdisk_range, slice_offset(init_boot, start)),
_ => offset_range(boot_img_info.ramdisk_range, slice_offset(boot, start)),
};
let vendor_ramdisk_range = vendor_boot_info.ramdisk_range;
let bootconfig_range = offset_range(0..bootconfig_size, slice_offset(bootconfig, start));
load.copy_within(generic_ramdisk_range.clone(), vendor_ramdisk_range.end);
load.copy_within(bootconfig_range, vendor_ramdisk_range.end + generic_ramdisk_range.len());
let ramdisk_sz = vendor_ramdisk_range.len() + generic_ramdisk_range.len() + bootconfig_size;
// We now have the following layout:
//
// +------------------------+
// | vendor boot header |
// +------------------------+
// | vendor ramdisk |
// +------------------------+
// | generic ramdisk |
// +------------------------+
// | vendor bootconfig |
// +------------------------+
// | avb + board bootconfig |
// +------------------------+
// | unused |
// +------------------------+
// | dtb |
// +------------------------+
// | boot hdr |
// +------------------------+
// | kernel |
// +------------------------+
//
// Splits out the images and returns.
let vendor_hdr_sz = vendor_boot_info.header_size;
let unused_sz = load.len() - vendor_hdr_sz - ramdisk_sz - boot_hdr_sz - dtb_sz - kernel_sz;
let (vendor_hdr, boot_hdr);
([vendor_hdr, out.ramdisk, out.unused, out.dtb, boot_hdr], out.kernel) =
split_chunks(load, &[vendor_hdr_sz, ramdisk_sz, unused_sz, dtb_sz, boot_hdr_sz]);
out.boot_cmdline = BootImageV3Info::cmdline(boot_hdr)?;
out.vendor_cmdline = VendorBootImageInfo::cmdline(vendor_hdr)?;
Ok(())
}
// A helper for calculating the relative offset of `buf` to `src`.
fn slice_offset(buf: &[u8], src: &[u8]) -> usize {
(buf.as_ptr() as usize).checked_sub(src.as_ptr() as usize).unwrap()
}
/// Wrapper of `split_at_mut_checked` with error conversion.
fn split(buffer: &mut [u8], size: usize) -> Result<(&mut [u8], &mut [u8]), Error> {
buffer.split_at_mut_checked(size).ok_or(Error::BufferTooSmall(Some(size)))
}
/// Split buffer from the tail with the given alignment such that the buffer is at least `size`
/// bytes.
fn split_aligned_tail(
buffer: &mut [u8],
size: usize,
align: usize,
) -> Result<(&mut [u8], &mut [u8]), Error> {
let off = SafeNum::from(buffer.len()) - size;
let rem = buffer[off.try_into()?..].as_ptr() as usize % align;
split(buffer, usize::try_from(off - rem)?)
}
/// Splits a buffer into multiple chunks of the given sizes.
///
/// Returns an array of slices corresponding to the given sizes and the remaining slice.
fn split_chunks<'a, const N: usize>(
buf: &'a mut [u8],
sizes: &[usize; N],
) -> ([&'a mut [u8]; N], &'a mut [u8]) {
let mut chunks: [_; N] = array::from_fn(|_| &mut [][..]);
let mut remains = buf;
for (i, ele) in sizes.iter().enumerate() {
(chunks[i], remains) = remains.split_at_mut(*ele);
}
(chunks, remains)
}
/// Helper for loading entire partition.
///
/// * Returns the loaded slice and the remaining slice.
/// * If the partition doesn't exist, an empty loaded slice is returned.
fn load_entire_part<'a, 'b, 'c>(
ops: &mut impl GblOps<'a, 'b>,
part: &str,
load: &'c mut [u8],
) -> Result<(&'c mut [u8], &'c mut [u8]), Error> {
match ops.partition_size(&part)? {
Some(sz) => {
let sz = sz.try_into()?;
gbl_println!(ops, "Found {} partition.", &part);
let (out, remains) = split(load, sz)?;
ops.read_from_partition_sync(&part, 0, out)?;
Ok((out, remains))
}
_ => {
gbl_println!(ops, "Partition {} doesn't exist.", &part);
Ok((&mut [][..], &mut load[..]))
}
}
}
/// A helper function for relocating and decompressing kernel to a different buffer.
///
/// The relocated kernel will be place at the tail.
///
/// Returns the leading unused slice and the relocated slice.
fn relocate_kernel<'a, 'b, 'c>(
ops: &mut impl GblOps<'a, 'b>,
kernel: &[u8],
dst: &'c mut [u8],
) -> Result<(&'c mut [u8], &'c mut [u8]), Error> {
if is_compressed(kernel) {
split(dst, kernel.len())?.0.clone_from_slice(kernel);
let off = decompress_kernel(ops, dst, 0)?;
Ok(dst.split_at_mut(off))
} else {
let (prefix, tail) = split_aligned_tail(dst, kernel.len(), KERNEL_ALIGNMENT)?;
tail[..kernel.len()].clone_from_slice(kernel);
Ok((prefix, tail))
}
}
// Adds offset to a given range i.e. [start+off, end+off)
fn offset_range(lhs: Range<usize>, off: usize) -> Range<usize> {
lhs.start.checked_add(off).unwrap()..lhs.end.checked_add(off).unwrap()
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::{
gbl_avb::state::{BootStateColor, KeyValidationStatus},
ops::test::{FakeGblOps, FakeGblOpsStorage},
tests::AlignedBuffer,
};
use bootparams::bootconfig::BOOTCONFIG_TRAILER_SIZE;
use std::{ascii::escape_default, collections::HashMap, fs, path::Path, string::String};
// See libgbl/testdata/gen_test_data.py for test data generation.
const TEST_ROLLBACK_INDEX_LOCATION: usize = 1;
// The DTB in the test mkbootimg images.
// See libgbl/testdata/gen_test_data.py for test data generation.
const BASE_DTB: &[u8] = include_bytes!("../../../libfdt/test/data/base.dtb");
// The commandline in the generated vendor boot image.
// See libgbl/testdata/gen_test_data.py for test data generation.
const TEST_VENDOR_CMDLINE: &str =
"cmd_vendor_key_1=cmd_vendor_val_1,cmd_vendor_key_1=cmd_vendor_val_1";
// The vendor bootconfig in the generated vendor boot image.
// See libgbl/testdata/gen_test_data.py for test data generation.
const TEST_VENDOR_BOOTCONFIG: &str =
"androidboot.config_1=val_1\x0aandroidboot.config_2=val_2\x0a";
/// Digest of public key used to execute AVB.
pub(crate) const TEST_PUBLIC_KEY_DIGEST: &str =
"7ec02ee1be696366f3fa91240a8ec68125c4145d698f597aa2b3464b59ca7fc3";
/// Vbmeta digest for vbmeta_v4_v4_init_boot_a test artifact.
pub(crate) const TEST_VBMETA_V4_INIT_BOOT_A_DIGEST: &str =
"cf41369887ecd31c543cac5fc48a868e2359e935735f528cc7bd149a7e0a4544fd36abbd5871eb03514d00fa05548bed5c804ace07e111eb17a3026adcfd1c14";
/// Reads a data file under libgbl/testdata/
pub(crate) fn read_test_data(file: impl AsRef<str>) -> Vec<u8> {
println!("reading file: {}", file.as_ref());
fs::read(Path::new(
format!("external/gbl/libgbl/testdata/android/{}", file.as_ref()).as_str(),
))
.unwrap()
}
/// Generates a readable string for a bootconfig bytes.
pub(crate) fn dump_bootconfig(data: &[u8]) -> String {
let s = data.iter().map(|v| escape_default(*v).to_string()).collect::<Vec<_>>().concat();
let s = s.split("\\\\").collect::<Vec<_>>().join("\\");
s.split("\\n").collect::<Vec<_>>().join("\n")
}
/// Helper for testing load/verify and assert verfiication success.
fn test_android_load_verify_success(
partitions: &[(&CStr, &str)],
expected_kernel: &[u8],
expected_ramdisk: &[u8],
expected_bootconfig: &[u8],
expected_dtb: &[u8],
expected_vendor_cmdline: &str,
) {
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 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));
let loaded = android_load_verify(&mut ops, 0, &mut load_buffer).unwrap();
assert!(loaded.dtb.starts_with(expected_dtb));
assert_eq!(out_color, Some(BootStateColor::Green));
assert_eq!(loaded.boot_cmdline, "cmd_key_1=cmd_val_1,cmd_key_2=cmd_val_2");
assert_eq!(loaded.vendor_cmdline, expected_vendor_cmdline);
assert!(loaded.kernel.starts_with(expected_kernel));
let (ramdisk, bootconfig) = loaded.ramdisk.split_at_mut(expected_ramdisk.len());
assert_eq!(ramdisk, expected_ramdisk);
assert_eq!(
bootconfig,
expected_bootconfig,
"\nexpect: \n{}\nactual: \n{}\n",
dump_bootconfig(expected_bootconfig),
dump_bootconfig(bootconfig),
);
}
/// A helper for generating avb bootconfig with the given parameters.
pub(crate) struct AvbResultBootconfigBuilder {
vbmeta_size: usize,
digest: String,
public_key_digest: String,
color: BootStateColor,
unlocked: bool,
extra: String,
}
impl AvbResultBootconfigBuilder {
pub(crate) fn new() -> Self {
Self {
vbmeta_size: 0,
digest: String::new(),
public_key_digest: String::new(),
color: BootStateColor::Green,
unlocked: false,
extra: String::new(),
}
}
pub(crate) fn vbmeta_size(mut self, size: usize) -> Self {
self.vbmeta_size = size;
self
}
pub(crate) fn digest(mut self, digest: impl Into<String>) -> Self {
self.digest = digest.into();
self
}
pub(crate) fn public_key_digest(mut self, pk_digest: impl Into<String>) -> Self {
self.public_key_digest = pk_digest.into();
self
}
pub(crate) fn color(mut self, color: BootStateColor) -> Self {
self.color = color;
self
}
pub(crate) fn unlocked(mut self, unlocked: bool) -> Self {
self.unlocked = unlocked;
self
}
pub(crate) fn extra(mut self, extra: impl Into<String>) -> Self {
self.extra = extra.into();
self
}
pub(crate) fn build_string(self) -> String {
let device_state = match self.unlocked {
true => "unlocked",
false => "locked",
};
format!(
"androidboot.vbmeta.device=PARTUUID=00000000-0000-0000-0000-000000000000
androidboot.vbmeta.public_key_digest={}
androidboot.vbmeta.avb_version=1.3
androidboot.vbmeta.device_state={}
androidboot.vbmeta.hash_alg=sha512
androidboot.vbmeta.size={}
androidboot.vbmeta.digest={}
androidboot.vbmeta.invalidate_on_error=yes
androidboot.veritymode=enforcing
androidboot.verifiedbootstate={}
{}",
self.public_key_digest,
device_state,
self.vbmeta_size,
self.digest,
self.color,
self.extra
)
}
pub(crate) fn build(self) -> Vec<u8> {
make_bootconfig(self.build_string())
}
}
// A helper for generating expected bootconfig.
pub(crate) fn make_bootconfig(bootconfig: impl AsRef<str>) -> Vec<u8> {
let bootconfig = bootconfig.as_ref();
let mut buffer = vec![0u8; bootconfig.len() + BOOTCONFIG_TRAILER_SIZE];
let mut res = BootConfigBuilder::new(&mut buffer).unwrap();
res.add_with(|_, out| {
out[..bootconfig.len()].clone_from_slice(bootconfig.as_bytes());
Ok(bootconfig.as_bytes().len())
})
.unwrap();
res.config_bytes().to_vec()
}
/// Helper for testing load/verify for v0//v1/v2 boot images.
///
/// # Args
///
/// * `partitions`: A list of pair `(partition name, file name)` for creating boot storage.
/// * `vbmeta_file`: The vbmeta file for the storage. Used for constructing expected bootconfig.
/// * `expected_dtb`: The expected DTB.
/// * `expected_digest`: The expected digest outputed by vbmeta.
fn test_android_load_verify_v2_and_lower(
partitions: &[(&CStr, &str)],
vbmeta_file: &str,
expected_dtb: &[u8],
expected_digest: &str,
) {
let expected_bootconfig = AvbResultBootconfigBuilder::new()
.vbmeta_size(read_test_data(vbmeta_file).len())
.digest(expected_digest)
.public_key_digest(TEST_PUBLIC_KEY_DIGEST)
.extra(FakeGblOps::GBL_TEST_BOOTCONFIG)
.build();
test_android_load_verify_success(
partitions,
&read_test_data("kernel_a.img"),
&read_test_data("generic_ramdisk_a.img"),
&expected_bootconfig,
expected_dtb,
"",
);
}
#[test]
fn test_android_load_verify_v0() {
let vbmeta = "vbmeta_v0_a.img";
let parts = [(c"boot_a", "boot_v0_a.img"), (c"vbmeta_a", vbmeta)];
test_android_load_verify_v2_and_lower(&parts[..], vbmeta, &[], "0976e60490f1213035010310ec3ba277e9cf7ad6ca68433a9eb43871bdf1ae317df70f412714b5d2f54ee9ce4723f3e855be25e0c87b31da6aedddb61fbeb0c6");
}
#[test]
fn test_android_load_verify_v1() {
let vbmeta = "vbmeta_v1_a.img";
let parts = [(c"boot_a", "boot_v1_a.img"), (c"vbmeta_a", vbmeta)];
test_android_load_verify_v2_and_lower(&parts[..], vbmeta, &[], "f483f94d975bc741562e64ac6814d6970b6c589dee84b7160c63e726d8848fe65c3e165393df22dd69338a65082f4f6d289549de05018e03b1184116fda111ce");
}
#[test]
fn test_android_load_verify_v2() {
let vbmeta = "vbmeta_v2_a.img";
let parts = [(c"boot_a", "boot_v2_a.img"), (c"vbmeta_a", vbmeta)];
test_android_load_verify_v2_and_lower(&parts[..], vbmeta, BASE_DTB, "554568eef20d8550c37c06f7988cc76d9ed113b3403a04f345ed4fb0d5acccff531a9f4f862a19f0a3977af8f574c11018f0c8eac142897f0d17527da1911ffe");
}
/// Helper for testing load/verify for v3/v4 boot/vendor_boot images.
///
/// # Args
///
/// * `partitions`: A list of pair `(partition name, file name)` for creating boot storage.
/// * `vbmeta_file`: The vbmeta file for the storage. Used for constructing expected bootconfig.
/// * `expected_digest`: The expected digest outputed by vbmeta.
/// * `expected_vendor_bootconfig`: The expected vendor_boot_config.
fn test_android_load_verify_v3_and_v4(
partitions: &[(&CStr, &str)],
vbmeta_file: &str,
expected_digest: &str,
expected_vendor_bootconfig: &str,
) {
let expected_bootconfig = AvbResultBootconfigBuilder::new()
.vbmeta_size(read_test_data(vbmeta_file).len())
.digest(expected_digest)
.public_key_digest(TEST_PUBLIC_KEY_DIGEST)
.extra(FakeGblOps::GBL_TEST_BOOTCONFIG.to_owned() + expected_vendor_bootconfig)
.build();
test_android_load_verify_success(
partitions,
&read_test_data("kernel_a.img"),
&[read_test_data("vendor_ramdisk_a.img"), read_test_data("generic_ramdisk_a.img")]
.concat(),
&expected_bootconfig,
BASE_DTB,
TEST_VENDOR_CMDLINE,
);
}
#[test]
fn test_android_load_verify_boot_v3_vendor_v3_no_init_boot() {
let vbmeta_file = "vbmeta_v3_v3_a.img";
let parts = [
(c"boot_a", "boot_v3_a.img"),
(c"vendor_boot_a", "vendor_boot_v3_a.img"),
(c"vbmeta_a", vbmeta_file),
];
test_android_load_verify_v3_and_v4(&parts[..], vbmeta_file, "c9ff8edd4be86f3a7b0850529bbe6a8095a00af8162151387f98393a5f551c28bcec5ef6c15c91ee994d075afcde9becfe236c62968750e32bd5f74ac2625c32", "");
}
#[test]
fn test_android_load_verify_boot_v3_vendor_v3_init_boot() {
let vbmeta_file = "vbmeta_v3_v3_init_boot_a.img";
let parts = [
(c"boot_a", "boot_no_ramdisk_v3_a.img"),
(c"init_boot_a", "init_boot_a.img"),
(c"vendor_boot_a", "vendor_boot_v3_a.img"),
(c"vbmeta_a", vbmeta_file),
];
test_android_load_verify_v3_and_v4(&parts[..], vbmeta_file, "c46faba2db23fb6758021747f2276165cff909d17b8d05938c267ec5bd370d09c94014dfd3adc5d36dd3a05eae94dce638197f06ee67fb726c55ef752383b5f7", "");
}
#[test]
fn test_android_load_verify_boot_v3_vendor_v4_no_init_boot() {
let vbmeta_file = "vbmeta_v3_v4_a.img";
let parts = [
(c"boot_a", "boot_v3_a.img"),
(c"vendor_boot_a", "vendor_boot_v4_a.img"),
(c"vbmeta_a", vbmeta_file),
];
test_android_load_verify_v3_and_v4(&parts[..], vbmeta_file, "1034811a69575d8f276cdbf4ddfb2eaf028d79cc78215fdfbadd50452b4155b64e484ed632ab85159cb91f40d084bf5aa48bfb3080a2229b4ecad2900114e765", TEST_VENDOR_BOOTCONFIG);
}
#[test]
fn test_android_load_verify_boot_v3_vendor_v4_init_boot() {
let vbmeta_file = "vbmeta_v3_v4_init_boot_a.img";
let parts = [
(c"boot_a", "boot_no_ramdisk_v3_a.img"),
(c"init_boot_a", "init_boot_a.img"),
(c"vendor_boot_a", "vendor_boot_v4_a.img"),
(c"vbmeta_a", vbmeta_file),
];
test_android_load_verify_v3_and_v4(&parts[..], vbmeta_file, "66bc22d274eddcb405001e7548404f143adef5d3e628e5b083ed7b88b11442e3dc09429b2ddd693a8b0dcd7d133b9b5f60f597845ab98d32e158b3996a450d65", TEST_VENDOR_BOOTCONFIG);
}
#[test]
fn test_android_load_verify_boot_v4_vendor_v3_no_init_boot() {
let vbmeta_file = "vbmeta_v4_v3_a.img";
let parts = [
(c"boot_a", "boot_v4_a.img"),
(c"vendor_boot_a", "vendor_boot_v3_a.img"),
(c"vbmeta_a", vbmeta_file),
];
test_android_load_verify_v3_and_v4(&parts[..], vbmeta_file, "6a87e40686eb4a56d4cff406475f153d846ed8dba8ce68666e1433b33f75adcf524fb510fcb34753e5c1778a2bec5de7b2fbeab31995518a339cec99a2d20e8b", "");
}
#[test]
fn test_android_load_verify_boot_v4_vendor_v3_init_boot() {
let vbmeta_file = "vbmeta_v4_v3_init_boot_a.img";
let parts = [
(c"boot_a", "boot_no_ramdisk_v4_a.img"),
(c"init_boot_a", "init_boot_a.img"),
(c"vendor_boot_a", "vendor_boot_v3_a.img"),
(c"vbmeta_a", vbmeta_file),
];
test_android_load_verify_v3_and_v4(&parts[..], vbmeta_file, "59b109531b311e0d66ee58ca2d347db9e379a2877d048625fb5bde6bcb80df03120b9be482c282bd8c753524c7114a9cf6cfbfa336e137bcb1af55c6bc4d169a", "");
}
#[test]
fn test_android_load_verify_boot_v4_vendor_v4_no_init_boot() {
let vbmeta_file = "vbmeta_v4_v4_a.img";
let parts = [
(c"boot_a", "boot_v4_a.img"),
(c"vendor_boot_a", "vendor_boot_v4_a.img"),
(c"vbmeta_a", vbmeta_file),
];
test_android_load_verify_v3_and_v4(&parts[..], vbmeta_file, "f074f0c7330873e71dadc4b00f010fa9aa0fcf81db1b1c3bc3cc9e98dd5ee572699d34b067f6b9cb44e8948f4e3c7182bec6761cf796f9f00c142a348b78264d", TEST_VENDOR_BOOTCONFIG);
}
#[test]
fn test_android_load_verify_boot_v4_vendor_v4_init_boot() {
let vbmeta_file = "vbmeta_v4_v4_init_boot_a.img";
let parts = [
(c"boot_a", "boot_no_ramdisk_v4_a.img"),
(c"init_boot_a", "init_boot_a.img"),
(c"vendor_boot_a", "vendor_boot_v4_a.img"),
(c"vbmeta_a", vbmeta_file),
];
test_android_load_verify_v3_and_v4(
&parts[..],
vbmeta_file,
TEST_VBMETA_V4_INIT_BOOT_A_DIGEST,
TEST_VENDOR_BOOTCONFIG,
);
}
}