blob: 3d0197865649291828dd0492d9be6752245675e5 [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 crate::{
gbl_avb::{
ops::{GblAvbOps, AVB_DIGEST_KEY},
state::{BootStateColor, KeyValidationStatus},
},
gbl_print, gbl_println, GblOps, Result,
};
use abr::SlotIndex;
use arrayvec::ArrayVec;
use avb::{slot_verify, HashtreeErrorMode, Ops as _, SlotVerifyFlags};
use bootparams::{bootconfig::BootConfigBuilder, entry::CommandlineParser};
use core::{ffi::CStr, fmt::Write};
use liberror::Error;
// Maximum number of partition allowed for verification.
//
// The value is randomly chosen for now. We can update it as we see more usecases.
const MAX_NUM_PARTITION: usize = 16;
// Type alias for ArrayVec of size `MAX_NUM_PARTITION`:
type ArrayMaxParts<T> = ArrayVec<T, MAX_NUM_PARTITION>;
/// A container holding partitions for libavb verification
pub(crate) struct PartitionsToVerify<'a> {
partitions: ArrayMaxParts<&'a CStr>,
preloaded: ArrayMaxParts<(&'a str, &'a [u8])>,
}
impl<'a> PartitionsToVerify<'a> {
/// Appends a partition to verify
#[cfg(test)]
pub fn try_push(&mut self, name: &'a CStr) -> Result<()> {
self.partitions.try_push(name).or(Err(Error::TooManyPartitions(MAX_NUM_PARTITION)))?;
Ok(())
}
/// Appends a partition, along with its preloaded data
pub fn try_push_preloaded(&mut self, name: &'a CStr, data: &'a [u8]) -> Result<()> {
let err = Err(Error::TooManyPartitions(MAX_NUM_PARTITION));
self.partitions.try_push(name).or(err)?;
self.preloaded.try_push((name.to_str().unwrap(), data)).or(err)?;
Ok(())
}
/// Appends partitions, along with preloaded data
pub fn try_extend_preloaded(&mut self, partitions: &PartitionsToVerify<'a>) -> Result<()> {
let err = Err(Error::TooManyPartitions(MAX_NUM_PARTITION));
self.partitions.try_extend_from_slice(partitions.partitions()).or(err)?;
self.preloaded.try_extend_from_slice(partitions.preloaded()).or(err)?;
Ok(())
}
fn partitions(&self) -> &[&'a CStr] {
&self.partitions
}
fn preloaded(&self) -> &[(&'a str, &'a [u8])] {
&self.preloaded
}
}
impl<'a> Default for PartitionsToVerify<'a> {
fn default() -> Self {
Self { partitions: ArrayMaxParts::new(), preloaded: ArrayMaxParts::new() }
}
}
/// Android verified boot flow.
///
/// All relevant images from disk must be preloaded and provided as `partitions`; in its final
/// state `ops` will provide the necessary callbacks for where the images should go in RAM and
/// which ones are preloaded.
///
/// # Arguments
/// * `ops`: [GblOps] providing device-specific backend.
/// * `slot`: The slot index.
/// * `partitions`: [PartitionsToVerify] providing pre-loaded partitions.
/// * `bootconfig_builder`: object to write the bootconfig data into.
///
/// # Returns
/// `()` on success. Returns an error if verification process failed and boot cannot
/// continue, or if parsing the command line or updating the boot configuration fail.
pub(crate) fn avb_verify_slot<'a, 'b, 'c>(
ops: &mut impl GblOps<'a, 'b>,
slot: u8,
partitions: &PartitionsToVerify<'c>,
bootconfig_builder: &mut BootConfigBuilder,
) -> Result<()> {
let slot = match slot {
0 => SlotIndex::A,
1 => SlotIndex::B,
_ => {
gbl_println!(ops, "AVB: Invalid slot index: {slot}");
return Err(Error::InvalidInput.into());
}
};
let mut avb_ops = GblAvbOps::new(ops, Some(slot), partitions.preloaded(), false);
let unlocked = avb_ops.read_is_device_unlocked()?;
let verify_result = slot_verify(
&mut avb_ops,
partitions.partitions(),
Some(slot.into()),
// TODO(b/337846185): Pass AVB_SLOT_VERIFY_FLAGS_RESTART_CAUSED_BY_HASHTREE_CORRUPTION in
// case verity corruption is detected by HLOS.
match unlocked {
true => SlotVerifyFlags::AVB_SLOT_VERIFY_FLAGS_ALLOW_VERIFICATION_ERROR,
_ => SlotVerifyFlags::AVB_SLOT_VERIFY_FLAGS_NONE,
},
// TODO(b/337846185): For demo, we use the same setting as Cuttlefish u-boot.
// Pass AVB_HASHTREE_ERROR_MODE_MANAGED_RESTART_AND_EIO and handle EIO.
HashtreeErrorMode::AVB_HASHTREE_ERROR_MODE_RESTART_AND_INVALIDATE,
);
let (color, verify_data) = match verify_result {
Ok(ref verify_data) => {
let color = match unlocked {
false
if avb_ops.key_validation_status()? == KeyValidationStatus::ValidCustomKey =>
{
BootStateColor::Yellow
}
false => BootStateColor::Green,
true => BootStateColor::Orange,
};
gbl_println!(
avb_ops.gbl_ops,
"AVB verification passed. Device is unlocked: {unlocked}. Color: {color}"
);
(color, Some(verify_data))
}
// Non-fatal error, can continue booting since verify_data is available.
Err(ref e) if e.verification_data().is_some() && unlocked => {
let color = BootStateColor::Orange;
gbl_println!(
avb_ops.gbl_ops,
"AVB verification failed with {e}. Device is unlocked: {unlocked}. Color: {color}. \
Continue current boot attempt."
);
(color, Some(e.verification_data().unwrap()))
}
// Fatal error. Cannot boot.
Err(ref e) => {
let color = BootStateColor::Red;
gbl_println!(
avb_ops.gbl_ops,
"AVB verification failed with {e}. Device is unlocked: {unlocked}. Color: {color}. \
Cannot continue boot."
);
(color, None)
}
};
// Gets digest from the result command line.
let mut digest = None;
if let Some(ref verify_data) = verify_data {
for entry in CommandlineParser::new(verify_data.cmdline().to_str().unwrap()) {
let entry = entry?;
if entry.key == AVB_DIGEST_KEY {
digest = entry.value;
}
write!(bootconfig_builder, "{}\n", entry).or(Err(Error::BufferTooSmall(None)))?;
}
}
// Allowes FW to handle verification result.
avb_ops.handle_verification_result(verify_data, color, digest)?;
match color {
BootStateColor::Red => Err(verify_result.unwrap_err().without_verify_data().into()),
_ => {
write!(bootconfig_builder, "androidboot.verifiedbootstate={}\n", color)
.or(Err(Error::BufferTooSmall(None)))?;
Ok(())
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{
android_boot::load::tests::{
dump_bootconfig, make_bootconfig, read_test_data, AvbResultBootconfigBuilder,
TEST_PUBLIC_KEY_DIGEST, TEST_VBMETA_V4_INIT_BOOT_A_DIGEST,
},
ops::test::{FakeGblOps, FakeGblOpsStorage},
IntegrationError::AvbIoError,
};
use avb::{IoError, SlotVerifyError};
use std::{collections::HashMap, ffi::CStr};
/// Helper for testing avb_verify_slot
fn test_avb_verify_slot<'a>(
partitions: &[(&CStr, &str)],
partitions_to_verify: &PartitionsToVerify<'a>,
device_unlocked: std::result::Result<bool, avb::IoError>,
rollback_result: std::result::Result<u64, avb::IoError>,
slot: u8,
expected_reported_color: Option<BootStateColor>,
expected_bootconfig: &[u8],
) -> Result<()> {
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 = device_unlocked;
ops.avb_ops.rollbacks = HashMap::from([(1, rollback_result)]);
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 bootconfig_buffer = vec![0u8; 512 * 1024];
let mut bootconfig_builder = BootConfigBuilder::new(&mut bootconfig_buffer).unwrap();
let verify_result =
avb_verify_slot(&mut ops, slot, partitions_to_verify, &mut bootconfig_builder);
let bootconfig_bytes = bootconfig_builder.config_bytes();
assert_eq!(out_color, expected_reported_color);
assert_eq!(
bootconfig_bytes,
expected_bootconfig,
"\nexpect: \n{}\nactual: \n{}\n",
dump_bootconfig(expected_bootconfig),
dump_bootconfig(bootconfig_bytes),
);
verify_result
}
#[test]
fn test_avb_verify_slot_success() {
let mut partitions_to_verify = PartitionsToVerify::default();
partitions_to_verify.try_push(c"boot").unwrap();
partitions_to_verify.try_push(c"init_boot").unwrap();
partitions_to_verify.try_push(c"vendor_boot").unwrap();
let partitions_data = [
(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_v4_v4_init_boot_a.img"),
];
let expected_bootconfig = AvbResultBootconfigBuilder::new()
.vbmeta_size(read_test_data("vbmeta_v4_v4_init_boot_a.img").len())
.digest(TEST_VBMETA_V4_INIT_BOOT_A_DIGEST)
.public_key_digest(TEST_PUBLIC_KEY_DIGEST)
.build();
assert_eq!(
test_avb_verify_slot(
&partitions_data,
&partitions_to_verify,
// Unlocked result
Ok(false),
// Rollback index result
Ok(0),
// Slot
0,
// Expected color
Some(BootStateColor::Green),
// Expected bootcofnig
&expected_bootconfig,
),
Ok(()),
);
}
#[test]
fn test_avb_verify_slot_from_preloaded_success() {
let boot = read_test_data("boot_no_ramdisk_v4_a.img");
let init_boot = read_test_data("init_boot_a.img");
let vendor_boot = read_test_data("vendor_boot_v4_a.img");
let mut partitions_to_verify = PartitionsToVerify::default();
partitions_to_verify.try_push_preloaded(c"boot", &boot).unwrap();
partitions_to_verify.try_push_preloaded(c"init_boot", &init_boot).unwrap();
partitions_to_verify.try_push_preloaded(c"vendor_boot", &vendor_boot).unwrap();
let partitions_data = [
// Required images aren't presented. Have to rely on preloaded.
(c"vbmeta_a", "vbmeta_v4_v4_init_boot_a.img"),
];
let expected_bootconfig = AvbResultBootconfigBuilder::new()
.vbmeta_size(read_test_data("vbmeta_v4_v4_init_boot_a.img").len())
.digest(TEST_VBMETA_V4_INIT_BOOT_A_DIGEST)
.public_key_digest(TEST_PUBLIC_KEY_DIGEST)
.build();
assert_eq!(
test_avb_verify_slot(
&partitions_data,
&partitions_to_verify,
// Unlocked result
Ok(false),
// Rollback index result
Ok(0),
// Slot
0,
// Expected color
Some(BootStateColor::Green),
// Expected bootcofnig
&expected_bootconfig,
),
Ok(()),
);
}
#[test]
fn test_avb_verify_slot_success_unlocked() {
let mut partitions_to_verify = PartitionsToVerify::default();
partitions_to_verify.try_push(c"boot").unwrap();
partitions_to_verify.try_push(c"init_boot").unwrap();
partitions_to_verify.try_push(c"vendor_boot").unwrap();
let partitions_data = [
(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_v4_v4_init_boot_a.img"),
];
let expected_bootconfig = AvbResultBootconfigBuilder::new()
.vbmeta_size(read_test_data("vbmeta_v4_v4_init_boot_a.img").len())
.digest(TEST_VBMETA_V4_INIT_BOOT_A_DIGEST)
.public_key_digest(TEST_PUBLIC_KEY_DIGEST)
.color(BootStateColor::Orange)
.unlocked(true)
.build();
assert_eq!(
test_avb_verify_slot(
&partitions_data,
&partitions_to_verify,
// Unlocked result
Ok(true),
// Rollback index result
Ok(0),
// Slot
0,
// Expected color
Some(BootStateColor::Orange),
// Expected bootconfig
&expected_bootconfig,
),
Ok(()),
);
}
#[test]
fn test_avb_verify_slot_verification_failed_unlocked() {
let mut partitions_to_verify = PartitionsToVerify::default();
partitions_to_verify.try_push(c"boot").unwrap();
partitions_to_verify.try_push(c"init_boot").unwrap();
partitions_to_verify.try_push(c"vendor_boot").unwrap();
let partitions_data = [
(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_v4_v4_init_boot_a.img"),
];
let expected_bootconfig = AvbResultBootconfigBuilder::new()
.vbmeta_size(read_test_data("vbmeta_v4_v4_init_boot_a.img").len())
.digest(TEST_VBMETA_V4_INIT_BOOT_A_DIGEST)
.public_key_digest(TEST_PUBLIC_KEY_DIGEST)
.color(BootStateColor::Orange)
.unlocked(true)
.build();
assert_eq!(
test_avb_verify_slot(
&partitions_data,
&partitions_to_verify,
// Unlocked result
Ok(true),
// Rollback index result
Ok(0),
// Slot
0,
// Expected color
Some(BootStateColor::Orange),
// Expected bootconfig
&expected_bootconfig,
),
// Device is unlocked, so can continue boot
Ok(()),
);
}
#[test]
fn test_avb_verify_slot_verification_fatal_failed_unlocked() {
let mut partitions_to_verify = PartitionsToVerify::default();
partitions_to_verify.try_push(c"boot").unwrap();
partitions_to_verify.try_push(c"init_boot").unwrap();
partitions_to_verify.try_push(c"vendor_boot").unwrap();
let partitions_data = [
(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_v4_v4_init_boot_a.img"),
];
let expected_bootconfig = make_bootconfig("");
assert_eq!(
test_avb_verify_slot(
&partitions_data,
&partitions_to_verify,
// Unlocked result
Ok(true),
// Get rollback index is failed
Err(IoError::NoSuchValue),
// Slot
0,
// Expected color
Some(BootStateColor::Red),
// Expected bootconfig
&expected_bootconfig,
),
// Fatal error, so cannot continue boot
Err(SlotVerifyError::Io.into()),
);
}
#[test]
fn test_avb_verify_slot_verification_failed_locked() {
let mut partitions_to_verify = PartitionsToVerify::default();
partitions_to_verify.try_push(c"boot").unwrap();
partitions_to_verify.try_push(c"init_boot").unwrap();
partitions_to_verify.try_push(c"vendor_boot").unwrap();
let partitions_data = [
// Wrong boot image, expect verification to fail.
(c"boot_a", "boot_v0_a.img"),
(c"init_boot_a", "init_boot_a.img"),
(c"vendor_boot_a", "vendor_boot_v4_a.img"),
(c"vbmeta_a", "vbmeta_v4_v4_init_boot_a.img"),
];
let expected_bootconfig = make_bootconfig("");
assert_eq!(
test_avb_verify_slot(
&partitions_data,
&partitions_to_verify,
// Unlocked result
Ok(false),
// Rollback index result
Ok(0),
// Slot
0,
// Expected color
Some(BootStateColor::Red),
// Expected bootconfig
&expected_bootconfig,
),
// Cannot continue boot
Err(SlotVerifyError::Verification(None).into()),
);
}
#[test]
fn test_avb_verify_slot_verification_failed_obtain_lock_status() {
let partitions_to_verify = PartitionsToVerify::default();
let expected_bootconfig = make_bootconfig("");
assert_eq!(
test_avb_verify_slot(
&[],
&partitions_to_verify,
// Unlocked result
Err(avb::IoError::NoSuchValue),
// Rollback index result
Ok(0),
// Slot
0,
// Expected color
None,
// Expected bootconfig
&expected_bootconfig,
),
// Cannot continue boot
Err(AvbIoError(IoError::NoSuchValue)),
);
}
}