| // Copyright 2023, 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. |
| |
| //! # Generic Boot Loader (gbl) Library |
| //! |
| //! TODO: b/312610098 - add documentation. |
| //! |
| //! The intended users of this library are firmware, bootloader, and bring-up teams at OEMs and SOC |
| //! Vendors |
| //! |
| //! This library is `no_std` as it is intended for use in bootloaders that typically will not |
| //! support the Rust standard library. However, it does require `alloc` with a global allocator, |
| //! currently used for: |
| //! * libavb |
| //! * kernel decompression |
| |
| #![cfg_attr(not(any(test, android_dylib)), no_std)] |
| // TODO: b/312610985 - return warning for unused partitions |
| #![allow(async_fn_in_trait)] |
| extern crate avb; |
| extern crate core; |
| extern crate gbl_storage; |
| extern crate spin; |
| extern crate zbi; |
| |
| use avb::{HashtreeErrorMode, SlotVerifyData, SlotVerifyError, SlotVerifyFlags}; |
| use core::ffi::CStr; |
| |
| pub mod android_boot; |
| pub mod boot_mode; |
| pub mod boot_reason; |
| pub mod decompress; |
| pub mod device_tree; |
| pub mod error; |
| pub mod fastboot; |
| pub mod fuchsia_boot; |
| pub mod gbl_avb; |
| pub mod ops; |
| pub mod partition; |
| |
| /// The 'slots' module, containing types and traits for |
| /// querying and modifying slotted boot behavior. |
| pub mod slots; |
| |
| mod image_buffer; |
| mod overlap; |
| |
| use slots::{BootTarget, BootToken, Cursor, OneShot, SuffixBytes, UnbootableReason}; |
| |
| pub use avb::Descriptor; |
| pub use boot_mode::BootMode; |
| pub use boot_reason::KnownBootReason; |
| pub use error::{IntegrationError, Result}; |
| use liberror::Error; |
| pub use ops::{GblOps, Os}; |
| |
| use overlap::is_overlap; |
| |
| // TODO: b/312607649 - Replace placeholders with actual structures: https://r.android.com/2721974, etc |
| /// TODO: b/312607649 - placeholder type |
| pub struct Partition {} |
| /// TODO: b/312607649 - placeholder type |
| pub struct InfoStruct {} |
| |
| /// Structure representing partition and optional address it is required to be loaded. |
| /// If no address is provided GBL will use default one. |
| pub struct PartitionRamMap<'b, 'c> { |
| /// Partition details |
| pub partition: &'b Partition, |
| |
| /// Optional memory region to load partitions. |
| /// If it's not provided default values will be used. |
| pub address: Option<&'c mut [u8]>, |
| } |
| |
| /// Boot Image in memory |
| #[allow(dead_code)] |
| pub struct BootImage<'a>(&'a mut [u8]); |
| |
| /// Vendor Boot Image in memory |
| pub struct VendorBootImage<'a>(&'a mut [u8]); |
| |
| /// Init Boot Image in memory |
| pub struct InitBootImage<'a>(&'a mut [u8]); |
| |
| /// Kernel Image in memory |
| #[allow(dead_code)] |
| pub struct KernelImage<'a>(&'a mut [u8]); |
| |
| /// Ramdisk in memory |
| pub struct Ramdisk<'a>(&'a mut [u8]); |
| /// Bootconfig in memory |
| #[allow(dead_code)] |
| pub struct Bootconfig<'a>(&'a mut [u8]); |
| /// DTB in memory |
| #[allow(dead_code)] |
| pub struct Dtb<'a>(&'a mut [u8]); |
| |
| /// Create Boot Image from corresponding partition for `partitions_ram_map` and `avb_descriptors` |
| /// lists |
| pub fn get_boot_image<'a: 'b, 'b: 'c, 'c, 'd>( |
| partitions_ram_map: &'a mut [PartitionRamMap<'b, 'c>], |
| ) -> (Option<BootImage<'c>>, &'a mut [PartitionRamMap<'b, 'c>]) { |
| match partitions_ram_map.len() { |
| 0 => (None, partitions_ram_map), |
| _ => { |
| let (partition_map, tail) = partitions_ram_map.split_first_mut().unwrap(); |
| (partition_map.address.take().map(BootImage), tail) |
| } |
| } |
| } |
| |
| /// Create Vendor Boot Image from corresponding partition for `partitions_ram_map` and |
| /// `avb_descriptors` lists |
| pub fn get_vendor_boot_image<'a: 'b, 'b: 'c, 'c, 'd>( |
| partitions_ram_map: &'a mut [PartitionRamMap<'b, 'c>], |
| ) -> (Option<VendorBootImage<'c>>, &'a mut [PartitionRamMap<'b, 'c>]) { |
| match partitions_ram_map.len() { |
| 0 => (None, partitions_ram_map), |
| _ => { |
| let (partition_map, tail) = partitions_ram_map.split_first_mut().unwrap(); |
| (partition_map.address.take().map(VendorBootImage), tail) |
| } |
| } |
| } |
| |
| /// Create Init Boot Image from corresponding partition for `partitions` and `avb_descriptors` lists |
| pub fn get_init_boot_image<'a: 'b, 'b: 'c, 'c, 'd>( |
| partitions_ram_map: &'a mut [PartitionRamMap<'b, 'c>], |
| ) -> (Option<InitBootImage<'c>>, &'a mut [PartitionRamMap<'b, 'c>]) { |
| match partitions_ram_map.len() { |
| 0 => (None, partitions_ram_map), |
| _ => { |
| let (partition_map, tail) = partitions_ram_map.split_first_mut().unwrap(); |
| (partition_map.address.take().map(InitBootImage), tail) |
| } |
| } |
| } |
| |
| /// Create separate image types from [avb::Descriptor] |
| pub fn get_images<'a: 'b, 'b: 'c, 'c, 'd>( |
| partitions_ram_map: &'a mut [PartitionRamMap<'b, 'c>], |
| ) -> ( |
| Option<BootImage<'c>>, |
| Option<InitBootImage<'c>>, |
| Option<VendorBootImage<'c>>, |
| &'a mut [PartitionRamMap<'b, 'c>], |
| ) { |
| let (boot_image, partitions_ram_map) = get_boot_image(partitions_ram_map); |
| let (init_boot_image, partitions_ram_map) = get_init_boot_image(partitions_ram_map); |
| let (vendor_boot_image, partitions_ram_map) = get_vendor_boot_image(partitions_ram_map); |
| (boot_image, init_boot_image, vendor_boot_image, partitions_ram_map) |
| } |
| |
| /// GBL object that provides implementation of helpers for boot process. |
| pub struct Gbl<'a, G> |
| where |
| G: GblOps<'a>, |
| { |
| ops: &'a mut G, |
| boot_token: Option<BootToken>, |
| } |
| |
| // TODO(b/312610985): Investigate whether to deprecate this and remove this allow. |
| #[allow(unused_variables)] |
| impl<'a, G> Gbl<'a, G> |
| where |
| G: GblOps<'a>, |
| { |
| /// Returns a new [Gbl] object. |
| /// |
| /// # Arguments |
| /// * `ops` - the [GblOps] callbacks to use |
| pub fn new(ops: &'a mut G) -> Self { |
| Self { ops, boot_token: Some(BootToken(())) } |
| } |
| |
| /// Verify + Load Image Into memory |
| /// |
| /// Load from disk, validate with AVB |
| /// |
| /// # Arguments |
| /// * `avb_ops` - implementation for `avb::Ops` |
| /// * `partitions_to_verify` - names of all the partitions to verify with libavb. |
| /// * `slot_verify_flags` - AVB slot verification flags |
| /// * `boot_target` - [Optional] Boot Target |
| /// |
| /// # Returns |
| /// * `Ok(SlotVerifyData)` - avb verification data |
| /// * `Err(Error)` - on failure |
| pub fn load_and_verify_image<'b>( |
| &mut self, |
| avb_ops: &mut impl avb::Ops<'b>, |
| partitions_to_verify: &[&CStr], |
| slot_verify_flags: SlotVerifyFlags, |
| boot_target: Option<BootTarget>, |
| ) -> Result<SlotVerifyData<'b>> { |
| let bytes: SuffixBytes = |
| if let Some(tgt) = boot_target { tgt.suffix().into() } else { Default::default() }; |
| |
| let avb_suffix = CStr::from_bytes_until_nul(&bytes).map_err(Error::from)?; |
| |
| Ok(avb::slot_verify( |
| avb_ops, |
| partitions_to_verify, |
| Some(avb_suffix), |
| slot_verify_flags, |
| HashtreeErrorMode::AVB_HASHTREE_ERROR_MODE_EIO, |
| ) |
| .map_err(|v| v.without_verify_data())?) |
| } |
| |
| /// Load Slot Manager Interface |
| /// |
| /// The default implementation loads from the `durable_boot` partition |
| /// and writes changes back on the destruction of the cursor. |
| /// |
| /// # Returns |
| /// |
| /// * `Ok(Cursor)` - Cursor object that manages a Manager |
| /// * `Err(Error)` - on failure |
| pub fn load_slot_interface( |
| &'a mut self, |
| persist: &'a mut dyn FnMut(&mut [u8]) -> core::result::Result<(), Error>, |
| ) -> Result<Cursor<'a>> { |
| let boot_token = self.boot_token.take().ok_or(Error::OperationProhibited)?; |
| self.ops.load_slot_interface(persist, boot_token) |
| } |
| |
| /// Info Load |
| /// |
| /// Unpack boot image in RAM |
| /// |
| /// # Arguments |
| /// * `boot_image_buffer` - Buffer that contains (Optionally Verified) Boot Image |
| /// * `boot_mode` - Boot Mode |
| /// * `boot_target` - [Optional] Boot Target |
| /// |
| /// # Returns |
| /// |
| /// * `Ok(InfoStruct)` - Info Struct (Concatenated kernel commandline - includes slot, |
| /// bootconfig selection, normal_mode, Concatenated bootconfig) on success |
| /// * `Err(Error)` - on failure |
| pub fn unpack_boot_image( |
| &self, |
| boot_image_buffer: &BootImage, |
| boot_target: Option<BootTarget>, |
| ) -> Result<InfoStruct> { |
| unimplemented!(); |
| } |
| |
| /// Kernel Load |
| /// |
| /// Prepare kernel in RAM for booting |
| /// |
| /// # Arguments |
| /// * `info` - Info Struct from Info Load |
| /// * `image_buffer` - Buffer that contains (Verified) Boot Image |
| /// * `load_buffer` - Kernel Load buffer |
| /// |
| /// # Returns |
| /// |
| /// * `Ok(())` - on success |
| /// * `Err(Error)` - on failure |
| pub fn kernel_load<'b>( |
| &self, |
| info: &InfoStruct, |
| image_buffer: BootImage, |
| load_buffer: &'b mut [u8], |
| ) -> Result<KernelImage<'b>> { |
| unimplemented!(); |
| } |
| |
| /// Ramdisk + Bootconfig Load |
| /// |
| /// Kernel Load |
| /// (Could break this into a RD and Bootconfig specific function each, TBD) |
| /// Prepare ramdisk/bootconfig in RAM for booting |
| /// |
| /// # Arguments |
| /// * `info` - Info Struct from Info Load |
| /// * `vendor_boot_image` - Buffer that contains (Verified) Vendor Boot Image |
| /// * `init_boot_image` - Buffer that contains (Verified) Init Boot Image |
| /// * `ramdisk_load_buffer` - Ramdisk Load buffer (not compressed). It will be filled with |
| /// a concatenation of `vendor_boot_image`, `init_boot_image` and bootconfig at the end. |
| /// |
| /// # Returns |
| /// |
| /// * `Ok(&str)` - on success returns Kernel command line |
| /// * `Err(Error)` - on failure |
| pub fn ramdisk_bootconfig_load( |
| &self, |
| info: &InfoStruct, |
| vendor_boot_image: &VendorBootImage, |
| init_boot_image: &InitBootImage, |
| ramdisk: &mut Ramdisk, |
| ) -> Result<&'static str> { |
| unimplemented!(); |
| } |
| |
| /// DTB Update And Load |
| /// |
| /// Prepare DTB in RAM for booting |
| /// |
| /// # Arguments |
| /// * `info` - Info Struct from Info Load |
| /// * `vendor_boot_image_buffer` - Buffer that contains (Verified) Vendor Boot Image |
| /// |
| /// # Returns |
| /// |
| /// * `Ok()` - on success |
| /// * `Err(Error)` - on failure |
| pub fn dtb_update_and_load( |
| &self, |
| info: &InfoStruct, |
| vendor_boot_image_buffer: VendorBootImage, |
| ) -> Result<Dtb> { |
| unimplemented!(); |
| } |
| |
| /// Kernel Jump |
| /// |
| /// |
| /// # Arguments |
| /// * `kernel_load_buffer` - Kernel Load buffer |
| /// * `ramdisk_bootconfi_load_buffer` - Concatenated Ramdisk, (Bootconfig if present) Load |
| /// buffer |
| /// * `dtb_load_buffer` - DTB Load buffer |
| /// * `boot_token` - Consumable boot token |
| /// |
| /// # Returns |
| /// |
| /// * doesn't return on success |
| /// * `Err(Error)` - on failure |
| // Nevertype could be used here when it is stable https://github.com/serde-rs/serde/issues/812 |
| pub fn kernel_jump( |
| &self, |
| kernel_load_buffer: KernelImage, |
| ramdisk_load_buffer: Ramdisk, |
| dtb_load_buffer: Dtb, |
| boot_token: BootToken, |
| ) -> Result<()> { |
| unimplemented!(); |
| } |
| |
| /// Load, verify, and boot |
| /// |
| /// Wrapper around the above functions for devices that don't need custom behavior between each |
| /// step |
| /// |
| /// Warning: If the call to load_verify_boot fails, the device MUST |
| /// be restarted in order to make forward boot progress. |
| /// Callers MAY log the error, enter an interactive mode, |
| /// or take other actions before rebooting. |
| /// |
| /// # Arguments |
| /// * `avb_ops` - implementation for `avb::Ops` that would be borrowed in result to prevent |
| /// changes to partitions until it is out of scope. |
| /// * `partitions_to_verify` - names of all the partitions to verify with libavb. |
| /// * `partitions_ram_map` - Partitions to verify and optional address for them to be loaded. |
| /// * `slot_verify_flags` - AVB slot verification flags |
| /// * `slot_cursor` - Cursor object that manages interactions with boot slot management |
| /// * `kernel_load_buffer` - Buffer for loading the kernel. |
| /// * `ramdisk_load_buffer` - Buffer for loading the ramdisk. |
| /// * `fdt` - Buffer containing a flattened device tree blob. |
| /// |
| /// # Returns |
| /// * doesn't return on success |
| /// * `Err(Error)` - on failure |
| // Nevertype could be used here when it is stable https://github.com/serde-rs/serde/issues/812 |
| #[allow(clippy::too_many_arguments)] |
| pub fn load_verify_boot<'b: 'c, 'c, 'd: 'b>( |
| &mut self, |
| avb_ops: &mut impl avb::Ops<'b>, |
| partitions_to_verify: &[&CStr], |
| partitions_ram_map: &'d mut [PartitionRamMap<'b, 'c>], |
| slot_verify_flags: SlotVerifyFlags, |
| slot_cursor: Cursor, |
| kernel_load_buffer: &mut [u8], |
| ramdisk_load_buffer: &mut [u8], |
| fdt: &mut [u8], |
| ) -> Result<()> { |
| let dtb = Dtb(&mut fdt[..]); |
| let mut ramdisk = Ramdisk(ramdisk_load_buffer); |
| |
| // Call the inner method which consumes the cursor |
| // in order to properly manager cursor lifetime |
| // and cleanup. |
| let (kernel_image, token) = self.lvb_inner( |
| avb_ops, |
| &mut ramdisk, |
| kernel_load_buffer, |
| partitions_to_verify, |
| partitions_ram_map, |
| slot_verify_flags, |
| slot_cursor, |
| )?; |
| |
| self.kernel_jump(kernel_image, ramdisk, dtb, token) |
| } |
| |
| fn is_unrecoverable_error(error: &IntegrationError) -> bool { |
| // Note: these ifs are nested instead of chained because multiple |
| // expressions in an if-let is an unstable features |
| if let IntegrationError::AvbSlotVerifyError(ref avb_error) = error { |
| // These are the AVB errors that are not recoverable on a subsequent attempt. |
| // If necessary in the future, this helper function can be moved to the GblOps trait |
| // and customized for platform specific behavior. |
| if matches!( |
| avb_error, |
| SlotVerifyError::Verification(_) |
| | SlotVerifyError::PublicKeyRejected |
| | SlotVerifyError::RollbackIndex |
| ) { |
| return true; |
| } |
| } |
| false |
| } |
| |
| fn lvb_inner<'b: 'c, 'c, 'd: 'b, 'e>( |
| &mut self, |
| avb_ops: &mut impl avb::Ops<'b>, |
| ramdisk: &mut Ramdisk, |
| kernel_load_buffer: &'e mut [u8], |
| partitions_to_verify: &[&CStr], |
| partitions_ram_map: &'d mut [PartitionRamMap<'b, 'c>], |
| slot_verify_flags: SlotVerifyFlags, |
| slot_cursor: Cursor, |
| ) -> Result<(KernelImage<'e>, BootToken)> { |
| let oneshot_status = slot_cursor.ctx.get_oneshot_status(); |
| slot_cursor.ctx.clear_oneshot_status(); |
| |
| let boot_target = match oneshot_status { |
| None | Some(OneShot::Bootloader) => slot_cursor.ctx.get_boot_target()?, |
| Some(OneShot::Continue(recovery)) => BootTarget::Recovery(recovery), |
| }; |
| |
| let verify_data = self |
| .load_and_verify_image( |
| avb_ops, |
| partitions_to_verify, |
| slot_verify_flags, |
| Some(boot_target), |
| ) |
| .map_err(|e: IntegrationError| { |
| if let BootTarget::NormalBoot(slot) = boot_target { |
| if Self::is_unrecoverable_error(&e) { |
| let _ = slot_cursor.ctx.set_slot_unbootable( |
| slot.suffix, |
| UnbootableReason::VerificationFailure, |
| ); |
| } else { |
| // Note: the call to mark_boot_attempt will fail if any of the following occur: |
| // * the target was already Unbootable before the call to load_and_verify_image |
| // * policy, I/O, or other errors in mark_boot_attempt |
| // |
| // We don't really care about those circumstances. |
| // The call here is a best effort attempt to decrement tries remaining. |
| let _ = slot_cursor.ctx.mark_boot_attempt(); |
| } |
| } |
| e |
| })?; |
| |
| let (boot_image, init_boot_image, vendor_boot_image, _) = get_images(partitions_ram_map); |
| let boot_image = boot_image.ok_or(Error::MissingImage)?; |
| let vendor_boot_image = vendor_boot_image.ok_or(Error::MissingImage)?; |
| let init_boot_image = init_boot_image.ok_or(Error::MissingImage)?; |
| |
| if is_overlap(&[ |
| boot_image.0, |
| vendor_boot_image.0, |
| init_boot_image.0, |
| &ramdisk.0, |
| kernel_load_buffer, |
| ]) { |
| return Err(IntegrationError::UnificationError(Error::BufferOverlap)); |
| } |
| |
| let info_struct = self.unpack_boot_image(&boot_image, Some(boot_target))?; |
| |
| let kernel_image = self.kernel_load(&info_struct, boot_image, kernel_load_buffer)?; |
| |
| let cmd_line = self.ramdisk_bootconfig_load( |
| &info_struct, |
| &vendor_boot_image, |
| &init_boot_image, |
| ramdisk, |
| )?; |
| |
| self.dtb_update_and_load(&info_struct, vendor_boot_image)?; |
| |
| let token = slot_cursor.ctx.mark_boot_attempt().map_err(|_| Error::OperationProhibited)?; |
| |
| Ok((kernel_image, token)) |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| extern crate avb_sysdeps; |
| extern crate avb_test; |
| use super::*; |
| use crate::ops::test::FakeGblOps; |
| use avb::{CertPermanentAttributes, SlotVerifyError}; |
| use avb_test::{FakeVbmetaKey, TestOps}; |
| use std::{fs, path::Path}; |
| use zerocopy::FromBytes; |
| |
| const TEST_ZIRCON_PARTITION_NAME: &str = "zircon_a"; |
| const TEST_ZIRCON_PARTITION_NAME_CSTR: &CStr = c"zircon_a"; |
| const TEST_ZIRCON_IMAGE_PATH: &str = "zircon_a.zbi"; |
| const TEST_ZIRCON_VBMETA_PATH: &str = "zircon_a.vbmeta"; |
| const TEST_ZIRCON_VBMETA_CERT_PATH: &str = "zircon_a.vbmeta.cert"; |
| const TEST_PUBLIC_KEY_PATH: &str = "testkey_rsa4096_pub.bin"; |
| const TEST_PERMANENT_ATTRIBUTES_PATH: &str = "cert_permanent_attributes.bin"; |
| const TEST_PERMANENT_ATTRIBUTES_HASH_PATH: &str = "cert_permanent_attributes.hash"; |
| const TEST_BAD_PERMANENT_ATTRIBUTES_PATH: &str = "cert_permanent_attributes.bad.bin"; |
| const TEST_BAD_PERMANENT_ATTRIBUTES_HASH_PATH: &str = "cert_permanent_attributes.bad.hash"; |
| const TEST_VBMETA_ROLLBACK_LOCATION: usize = 0; // Default value, we don't explicitly set this. |
| pub const TEST_CERT_PIK_VERSION: u64 = 42; |
| pub const TEST_CERT_PSK_VERSION: u64 = 42; |
| |
| /// Returns the contents of a test data file. |
| /// |
| /// Panicks if the requested file cannot be read. |
| /// |
| /// # Arguments |
| /// * `path`: file path relative to libgbl's `testdata/` directory. |
| fn testdata(path: &str) -> Vec<u8> { |
| let full_path = Path::new("external/gbl/libgbl/testdata").join(path); |
| fs::read(full_path).unwrap() |
| } |
| |
| /// Creates and returns a configured avb `TestOps`. |
| /// |
| /// The initial state will verify successfully with: |
| /// * a valid vbmeta image in the `vbmeta` partition, containing a hash descriptor for the |
| /// `TEST_ZIRCON_PARTITION_NAME` partition |
| /// * an image in the `TEST_ZIRCON_PARTITION_NAME` partition matching the vbmeta hash |
| /// * no preloaded partition data |
| /// * a public key matching the vbmeta image |
| /// * a valid vbmeta rollback index |
| /// * a locked bootloader state |
| /// |
| /// The caller can modify any of this state as needed for their particular test. |
| fn test_avb_ops() -> TestOps<'static> { |
| let mut avb_ops = TestOps::default(); |
| |
| avb_ops.add_partition(TEST_ZIRCON_PARTITION_NAME, testdata(TEST_ZIRCON_IMAGE_PATH)); |
| avb_ops.add_partition("vbmeta", testdata(TEST_ZIRCON_VBMETA_PATH)); |
| avb_ops.default_vbmeta_key = Some(FakeVbmetaKey::Avb { |
| public_key: testdata(TEST_PUBLIC_KEY_PATH), |
| public_key_metadata: None, |
| }); |
| avb_ops.rollbacks.insert(TEST_VBMETA_ROLLBACK_LOCATION, 0); |
| avb_ops.unlock_state = Ok(false); |
| |
| avb_ops |
| } |
| |
| /// Similar to `test_avb_ops()`, but with the avb_cert extension enabled. |
| fn test_avb_cert_ops() -> TestOps<'static> { |
| let mut avb_ops = test_avb_ops(); |
| |
| // Replace vbmeta with the cert-signed version. |
| avb_ops.add_partition("vbmeta", testdata(TEST_ZIRCON_VBMETA_CERT_PATH)); |
| |
| // Tell `avb_ops` to use cert APIs and to route the default key through cert validation. |
| avb_ops.use_cert = true; |
| avb_ops.default_vbmeta_key = Some(FakeVbmetaKey::Cert); |
| |
| // Add the permanent attributes. |
| let perm_attr_bytes = testdata(TEST_PERMANENT_ATTRIBUTES_PATH); |
| let perm_attr_hash = testdata(TEST_PERMANENT_ATTRIBUTES_HASH_PATH); |
| avb_ops.cert_permanent_attributes = |
| Some(CertPermanentAttributes::read_from(&perm_attr_bytes[..]).unwrap()); |
| avb_ops.cert_permanent_attributes_hash = Some(perm_attr_hash.try_into().unwrap()); |
| |
| // Add the rollbacks for the cert keys. |
| avb_ops.rollbacks.insert(avb::CERT_PIK_VERSION_LOCATION, TEST_CERT_PIK_VERSION); |
| avb_ops.rollbacks.insert(avb::CERT_PSK_VERSION_LOCATION, TEST_CERT_PSK_VERSION); |
| |
| avb_ops |
| } |
| |
| #[test] |
| fn test_load_and_verify_image_success() { |
| let mut gbl_ops = FakeGblOps::default(); |
| let mut gbl = Gbl::new(&mut gbl_ops); |
| let mut avb_ops = test_avb_ops(); |
| |
| let res = gbl.load_and_verify_image( |
| &mut avb_ops, |
| &mut [&TEST_ZIRCON_PARTITION_NAME_CSTR], |
| SlotVerifyFlags::AVB_SLOT_VERIFY_FLAGS_NONE, |
| None, |
| ); |
| assert!(res.is_ok()); |
| } |
| |
| #[test] |
| fn test_load_and_verify_image_verification_error() { |
| let mut gbl_ops = FakeGblOps::default(); |
| let mut gbl = Gbl::new(&mut gbl_ops); |
| let mut avb_ops = test_avb_ops(); |
| |
| // Modify the kernel image, it should now fail to validate against the vbmeta image. |
| avb_ops.partitions.get_mut(TEST_ZIRCON_PARTITION_NAME).unwrap().contents.as_mut_vec()[0] ^= |
| 0x01; |
| |
| let res = gbl.load_and_verify_image( |
| &mut avb_ops, |
| &mut [&TEST_ZIRCON_PARTITION_NAME_CSTR], |
| SlotVerifyFlags::AVB_SLOT_VERIFY_FLAGS_NONE, |
| None, |
| ); |
| assert_eq!( |
| res.unwrap_err(), |
| IntegrationError::AvbSlotVerifyError(SlotVerifyError::Verification(None)) |
| ); |
| } |
| |
| #[test] |
| fn test_load_and_verify_image_io_error() { |
| let mut gbl_ops = FakeGblOps::default(); |
| let mut gbl = Gbl::new(&mut gbl_ops); |
| let mut avb_ops = test_avb_ops(); |
| |
| // Erase the fake rollbacks, which will result in an I/O error when attempting to access. |
| avb_ops.rollbacks.clear(); |
| |
| let res = gbl.load_and_verify_image( |
| &mut avb_ops, |
| &mut [&TEST_ZIRCON_PARTITION_NAME_CSTR], |
| SlotVerifyFlags::AVB_SLOT_VERIFY_FLAGS_NONE, |
| None, |
| ); |
| assert_eq!(res.unwrap_err(), IntegrationError::AvbSlotVerifyError(SlotVerifyError::Io)); |
| } |
| |
| #[test] |
| fn test_load_and_verify_image_with_cert_success() { |
| let mut gbl_ops = FakeGblOps::default(); |
| let mut gbl = Gbl::new(&mut gbl_ops); |
| let mut avb_ops = test_avb_cert_ops(); |
| |
| let res = gbl.load_and_verify_image( |
| &mut avb_ops, |
| &mut [&TEST_ZIRCON_PARTITION_NAME_CSTR], |
| SlotVerifyFlags::AVB_SLOT_VERIFY_FLAGS_NONE, |
| None, |
| ); |
| assert!(res.is_ok()); |
| } |
| |
| #[test] |
| fn test_load_and_verify_image_with_cert_permanent_attribute_mismatch_error() { |
| let mut gbl_ops = FakeGblOps::default(); |
| let mut gbl = Gbl::new(&mut gbl_ops); |
| let mut avb_ops = test_avb_cert_ops(); |
| |
| // Swap in the corrupted permanent attributes, which should cause the vbmeta image to fail |
| // validation due to key mismatch. |
| let perm_attr_bytes = testdata(TEST_BAD_PERMANENT_ATTRIBUTES_PATH); |
| let perm_attr_hash = testdata(TEST_BAD_PERMANENT_ATTRIBUTES_HASH_PATH); |
| avb_ops.cert_permanent_attributes = |
| Some(CertPermanentAttributes::read_from(&perm_attr_bytes[..]).unwrap()); |
| avb_ops.cert_permanent_attributes_hash = Some(perm_attr_hash.try_into().unwrap()); |
| |
| let res = gbl.load_and_verify_image( |
| &mut avb_ops, |
| &mut [&TEST_ZIRCON_PARTITION_NAME_CSTR], |
| SlotVerifyFlags::AVB_SLOT_VERIFY_FLAGS_NONE, |
| None, |
| ); |
| assert_eq!( |
| res.unwrap_err(), |
| IntegrationError::AvbSlotVerifyError(SlotVerifyError::PublicKeyRejected) |
| ); |
| } |
| } |