| // 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. |
| |
| //! Implements [Gbl::Ops] for the EFI environment. |
| |
| use crate::{ |
| efi, |
| efi_blocks::EfiGblDisk, |
| utils::{get_efi_fdt, wait_key_stroke}, |
| }; |
| use alloc::{ |
| alloc::{alloc, handle_alloc_error, Layout}, |
| vec::Vec, |
| }; |
| use arrayvec::ArrayVec; |
| use core::{ |
| cmp::min, ffi::CStr, fmt::Write, mem::MaybeUninit, num::NonZeroUsize, ops::DerefMut, ptr::null, |
| slice::from_raw_parts_mut, str::Split, |
| }; |
| use efi::{ |
| efi_print, efi_println, |
| protocol::{ |
| dt_fixup::DtFixupProtocol, gbl_efi_avb::GblAvbProtocol, |
| gbl_efi_fastboot::GblFastbootProtocol, gbl_efi_image_loading::GblImageLoadingProtocol, |
| gbl_efi_os_configuration::GblOsConfigurationProtocol, |
| }, |
| EfiEntry, |
| }; |
| use efi_types::{ |
| GblEfiAvbKeyValidationStatus, GblEfiAvbVerificationResult, GblEfiDeviceTreeMetadata, |
| GblEfiImageInfo, GblEfiVerifiedDeviceTree, PARTITION_NAME_LEN_U16, |
| }; |
| use fdt::Fdt; |
| use gbl_storage::{BlockIo, Disk, Gpt, SliceMaybeUninit}; |
| use liberror::{Error, Result}; |
| use libgbl::{ |
| constants::{ImageName, BOOTCMD_SIZE}, |
| device_tree::{ |
| DeviceTreeComponent, DeviceTreeComponentSource, DeviceTreeComponentsRegistry, |
| MAXIMUM_DEVICE_TREE_COMPONENTS, |
| }, |
| gbl_avb::state::{BootStateColor, KeyValidationStatus}, |
| ops::{ |
| AvbIoError, AvbIoResult, CertPermanentAttributes, ImageBuffer, VarInfoSender, |
| SHA256_DIGEST_SIZE, |
| }, |
| partition::GblDisk, |
| slots::{BootToken, Cursor}, |
| GblOps, Os, Result as GblResult, |
| }; |
| use safemath::SafeNum; |
| use zbi::ZbiContainer; |
| use zerocopy::AsBytes; |
| |
| fn to_avb_validation_status_or_panic(status: GblEfiAvbKeyValidationStatus) -> KeyValidationStatus { |
| match status { |
| efi_types::GBL_EFI_AVB_KEY_VALIDATION_STATUS_VALID => KeyValidationStatus::Valid, |
| efi_types::GBL_EFI_AVB_KEY_VALIDATION_STATUS_VALID_CUSTOM_KEY => { |
| KeyValidationStatus::ValidCustomKey |
| } |
| efi_types::GBL_EFI_AVB_KEY_VALIDATION_STATUS_INVALID => KeyValidationStatus::Invalid, |
| _ => panic!("Unrecognized avb key validation status: {}", status), |
| } |
| } |
| |
| fn avb_color_to_efi_color(color: BootStateColor) -> u32 { |
| match color { |
| BootStateColor::Green => efi_types::GBL_EFI_AVB_BOOT_STATE_COLOR_GREEN, |
| BootStateColor::Yellow => efi_types::GBL_EFI_AVB_BOOT_STATE_COLOR_YELLOW, |
| BootStateColor::Orange => efi_types::GBL_EFI_AVB_BOOT_STATE_COLOR_ORANGE, |
| BootStateColor::RedEio => efi_types::GBL_EFI_AVB_BOOT_STATE_COLOR_RED_EIO, |
| BootStateColor::Red => efi_types::GBL_EFI_AVB_BOOT_STATE_COLOR_RED, |
| } |
| } |
| |
| fn dt_component_to_efi_dt(component: &DeviceTreeComponent) -> GblEfiVerifiedDeviceTree { |
| let metadata = match component.source { |
| DeviceTreeComponentSource::Dtb(m) | DeviceTreeComponentSource::Dtbo(m) => m, |
| _ => Default::default(), |
| }; |
| |
| GblEfiVerifiedDeviceTree { |
| metadata: GblEfiDeviceTreeMetadata { |
| source: match component.source { |
| DeviceTreeComponentSource::Boot => efi_types::GBL_EFI_DEVICE_TREE_SOURCE_BOOT, |
| DeviceTreeComponentSource::VendorBoot => { |
| efi_types::GBL_EFI_DEVICE_TREE_SOURCE_VENDOR_BOOT |
| } |
| DeviceTreeComponentSource::Dtb(_) => efi_types::GBL_EFI_DEVICE_TREE_SOURCE_DTB, |
| DeviceTreeComponentSource::Dtbo(_) => efi_types::GBL_EFI_DEVICE_TREE_SOURCE_DTBO, |
| }, |
| id: metadata.id, |
| rev: metadata.rev, |
| custom: metadata.custom, |
| reserved: Default::default(), |
| }, |
| device_tree: component.dt.as_ptr() as _, |
| selected: component.selected, |
| } |
| } |
| |
| fn efi_error_to_avb_error(error: Error) -> AvbIoError { |
| match error { |
| // EFI_STATUS_OUT_OF_RESOURCES |
| Error::OutOfResources => AvbIoError::Oom, |
| // EFI_STATUS_DEVICE_ERROR |
| Error::DeviceError => AvbIoError::Io, |
| // EFI_STATUS_NOT_FOUND |
| Error::NotFound => AvbIoError::NoSuchValue, |
| // EFI_STATUS_END_OF_FILE |
| Error::EndOfFile => AvbIoError::RangeOutsidePartition, |
| // EFI_STATUS_INVALID_PARAMETER |
| Error::InvalidInput => AvbIoError::InvalidValueSize, |
| // EFI_STATUS_BUFFER_TOO_SMALL |
| Error::BufferTooSmall(required) => { |
| AvbIoError::InsufficientSpace(required.unwrap_or_default()) |
| } |
| // EFI_STATUS_UNSUPPORTED |
| Error::Unsupported => AvbIoError::NotImplemented, |
| _ => AvbIoError::NotImplemented, |
| } |
| } |
| |
| pub struct Ops<'a, 'b> { |
| pub efi_entry: &'a EfiEntry, |
| pub disks: &'b [EfiGblDisk<'a>], |
| pub zbi_bootloader_files_buffer: Vec<u8>, |
| pub os: Option<Os>, |
| } |
| |
| impl<'a, 'b> Ops<'a, 'b> { |
| /// Creates a new instance of [Ops] |
| pub fn new(efi_entry: &'a EfiEntry, disks: &'b [EfiGblDisk<'a>], os: Option<Os>) -> Self { |
| Self { efi_entry, disks, zbi_bootloader_files_buffer: Default::default(), os } |
| } |
| |
| /// Gets the property of an FDT node from EFI FDT. |
| /// |
| /// Returns `None` if fail to get the node |
| fn get_efi_fdt_prop(&self, path: &str, prop: &CStr) -> Option<&'a [u8]> { |
| let (_, fdt_bytes) = get_efi_fdt(&self.efi_entry)?; |
| let fdt = Fdt::new(fdt_bytes).ok()?; |
| fdt.get_property(path, prop).ok() |
| } |
| |
| /// Get buffer for partition loading and verification. |
| /// Uses GBL EFI ImageLoading protocol. |
| /// |
| /// # Arguments |
| /// * `image_name` - image name to differentiate the buffer properties. Processing is limited |
| /// to first [PARTITION_NAME_LEN_U16] symbols, and the remaining will be ignored. |
| /// * `size` - requested buffer size |
| /// |
| /// # Return |
| /// * Ok(ImageBuffer) - Return buffer for partition loading and verification. |
| /// * Err(_) - on error |
| fn get_buffer_image_loading( |
| &mut self, |
| image_name: &str, |
| size: NonZeroUsize, |
| ) -> GblResult<ImageBuffer<'static>> { |
| let mut image_type = [0u16; PARTITION_NAME_LEN_U16]; |
| image_type.iter_mut().zip(image_name.encode_utf16()).for_each(|(dst, src)| { |
| *dst = src; |
| }); |
| let image_info = GblEfiImageInfo { ImageType: image_type, SizeBytes: size.get() }; |
| let efi_image_buffer = self |
| .efi_entry |
| .system_table() |
| .boot_services() |
| .find_first_and_open::<GblImageLoadingProtocol>()? |
| .get_buffer(&image_info)?; |
| |
| // EfiImageBuffer -> ImageBuffer |
| // Make sure not to drop efi_image_buffer since we transferred ownership to ImageBuffer |
| let buffer = efi_image_buffer.take(); |
| Ok(ImageBuffer::new(buffer)) |
| } |
| |
| /// Get buffer for partition loading and verification. |
| /// Uses provided allocator. |
| /// |
| /// # Arguments |
| /// * `image_name` - image name to differentiate the buffer properties |
| /// * `size` - requested buffer size |
| /// |
| /// # Return |
| /// * Ok(ImageBuffer) - Return buffer for partition loading and verification. |
| /// * Err(_) - on error |
| // SAFETY: |
| // Allocated buffer is leaked intentionally. ImageBuffer is assumed to reference static memory. |
| // ImageBuffer is not expected to be released, and is allocated to hold data necessary for next |
| // boot stage (kernel boot). All allocated buffers are expected to be used by kernel. |
| fn allocate_image_buffer(image_name: &str, size: NonZeroUsize) -> Result<ImageBuffer<'static>> { |
| let size = match image_name { |
| "ramdisk" => (SafeNum::from(size.get()) + BOOTCMD_SIZE).try_into()?, |
| _ => size.get(), |
| }; |
| // Check for `from_raw_parts_mut()` safety requirements. |
| assert!(size < isize::MAX.try_into().unwrap()); |
| let align = ImageName::try_from(image_name).map(|i| i.alignment()).unwrap_or(1); |
| |
| let layout = Layout::from_size_align(size, align).or(Err(Error::InvalidAlignment))?; |
| // SAFETY: |
| // `layout.size()` is checked to be not zero. |
| let ptr = unsafe { alloc(layout) } as *mut MaybeUninit<u8>; |
| if ptr.is_null() { |
| handle_alloc_error(layout); |
| } |
| |
| // SAFETY: |
| // `ptr` is checked to be not Null. |
| // `ptr` is a valid pointer to start of a single memory region of `size`-bytes because it |
| // was just returned by alloc. Buffer alignment requirement for u8 is 1-byte which is |
| // always the case. |
| // `alloc()` makes sure there is no other allocation of the same memory region until |
| // current one is released. |
| // `size` is a valid size of the memory region since `alloc()` succeeded. |
| // |
| // Total size of buffer is not greater than `isize::MAX` since it is checked at the |
| // beginning of the function. |
| // |
| // `ptr + size` doesn't wrap since it is returned from alloc and it didn't fail. |
| let buf = unsafe { from_raw_parts_mut(ptr, size) }; |
| |
| Ok(ImageBuffer::new(buf)) |
| } |
| } |
| |
| impl Write for Ops<'_, '_> { |
| fn write_str(&mut self, s: &str) -> core::fmt::Result { |
| efi_print!(self.efi_entry, "{}", s); |
| Ok(()) |
| } |
| } |
| |
| impl<'a, 'b, 'd> GblOps<'b, 'd> for Ops<'a, 'b> |
| where |
| Self: 'b, |
| { |
| fn console_out(&mut self) -> Option<&mut dyn Write> { |
| Some(self) |
| } |
| |
| /// UEFI console uses \r\n newline. |
| fn console_newline(&self) -> &'static str { |
| "\r\n" |
| } |
| |
| fn should_stop_in_fastboot(&mut self) -> Result<bool> { |
| // TODO(b/349829690): also query GblSlotProtocol.get_boot_reason() for board-specific |
| // fastboot triggers. |
| |
| // TODO(b/366520234): Switch to use GblSlotProtocol.should_stop_in_fastboot once available. |
| match self.get_efi_fdt_prop("gbl", c"stop-in-fastboot") { |
| Some(v) => return Ok(*v.get(0).unwrap_or(&0) == 1), |
| _ => {} |
| } |
| |
| efi_println!(self.efi_entry, "Press Backspace to enter fastboot"); |
| let found = wait_key_stroke( |
| self.efi_entry, |
| |key| key.unicode_char == 0x08 || (key.unicode_char == 0x0 && key.scan_code == 0x08), |
| 2000, |
| ); |
| if matches!(found, Ok(true)) { |
| efi_println!(self.efi_entry, "Backspace pressed, entering fastboot"); |
| } |
| // TODO(b/358377120): pass the UEFI error when liberror::Error support lands. |
| found.or(Err(Error::Other(Some("wait for key stroke error")))) |
| } |
| |
| /// Reboots the system into the last set boot mode. |
| fn reboot(&mut self) { |
| self.efi_entry.system_table().runtime_services().cold_reset(); |
| } |
| |
| fn disks( |
| &self, |
| ) -> &'b [GblDisk< |
| Disk<impl BlockIo + 'b, impl DerefMut<Target = [u8]> + 'b>, |
| Gpt<impl DerefMut<Target = [u8]> + 'b>, |
| >] { |
| self.disks |
| } |
| |
| fn expected_os(&mut self) -> Result<Option<Os>> { |
| Ok(self.os) |
| } |
| |
| fn zircon_add_device_zbi_items( |
| &mut self, |
| container: &mut ZbiContainer<&mut [u8]>, |
| ) -> Result<()> { |
| // TODO(b/353272981): Switch to use OS configuration protocol once it is implemented on |
| // existing platforms such as VIM3. |
| Ok(match self.get_efi_fdt_prop("zircon", c"zbi-blob") { |
| Some(blob) => container.extend_unaligned(blob).map_err(|_| "Failed to append ZBI")?, |
| _ => efi_println!(self.efi_entry, "No device ZBI items.\r\n"), |
| }) |
| } |
| |
| fn get_zbi_bootloader_files_buffer(&mut self) -> Option<&mut [u8]> { |
| // Switches to use get_image_buffer once available. |
| const DEFAULT_SIZE: usize = 4096; |
| if self.zbi_bootloader_files_buffer.is_empty() { |
| self.zbi_bootloader_files_buffer.resize(DEFAULT_SIZE, 0); |
| } |
| Some(self.zbi_bootloader_files_buffer.as_mut_slice()) |
| } |
| |
| fn load_slot_interface<'c>( |
| &'c mut self, |
| _: &'c mut dyn FnMut(&mut [u8]) -> Result<()>, |
| _: BootToken, |
| ) -> GblResult<Cursor<'c>> { |
| unimplemented!(); |
| } |
| |
| fn avb_read_is_device_unlocked(&mut self) -> AvbIoResult<bool> { |
| match self.efi_entry.system_table().boot_services().find_first_and_open::<GblAvbProtocol>() |
| { |
| Ok(protocol) => protocol.read_is_device_unlocked().map_err(efi_error_to_avb_error), |
| Err(_) => Err(AvbIoError::NotImplemented), |
| } |
| } |
| |
| fn avb_read_rollback_index(&mut self, rollback_index_location: usize) -> AvbIoResult<u64> { |
| match self.efi_entry.system_table().boot_services().find_first_and_open::<GblAvbProtocol>() |
| { |
| Ok(protocol) => protocol |
| .read_rollback_index(rollback_index_location) |
| .map_err(efi_error_to_avb_error), |
| Err(_) => Err(AvbIoError::NotImplemented), |
| } |
| } |
| |
| fn avb_write_rollback_index( |
| &mut self, |
| rollback_index_location: usize, |
| index: u64, |
| ) -> AvbIoResult<()> { |
| match self.efi_entry.system_table().boot_services().find_first_and_open::<GblAvbProtocol>() |
| { |
| Ok(protocol) => protocol |
| .write_rollback_index(rollback_index_location, index) |
| .map_err(efi_error_to_avb_error), |
| Err(_) => Err(AvbIoError::NotImplemented), |
| } |
| } |
| |
| fn avb_validate_vbmeta_public_key( |
| &self, |
| public_key: &[u8], |
| public_key_metadata: Option<&[u8]>, |
| ) -> AvbIoResult<KeyValidationStatus> { |
| match self.efi_entry.system_table().boot_services().find_first_and_open::<GblAvbProtocol>() |
| { |
| Ok(protocol) => protocol |
| .validate_vbmeta_public_key(public_key, public_key_metadata) |
| .map(to_avb_validation_status_or_panic) |
| .map_err(efi_error_to_avb_error), |
| Err(_) => Err(AvbIoError::NotImplemented), |
| } |
| } |
| |
| fn avb_cert_read_permanent_attributes( |
| &mut self, |
| attributes: &mut CertPermanentAttributes, |
| ) -> AvbIoResult<()> { |
| // TODO(b/337846185): Switch to use GBL Verified Boot EFI protocol when available. |
| let perm_attr = self |
| .get_efi_fdt_prop("gbl", c"avb-cert-permanent-attributes") |
| .ok_or(AvbIoError::NotImplemented)?; |
| attributes.as_bytes_mut().clone_from_slice(perm_attr); |
| Ok(()) |
| } |
| |
| fn avb_cert_read_permanent_attributes_hash(&mut self) -> AvbIoResult<[u8; SHA256_DIGEST_SIZE]> { |
| // TODO(b/337846185): Switch to use GBL Verified Boot EFI protocol when available. |
| let hash = self |
| .get_efi_fdt_prop("gbl", c"avb-cert-permanent-attributes-hash") |
| .ok_or(AvbIoError::NotImplemented)?; |
| Ok(hash.try_into().map_err(|_| AvbIoError::Io)?) |
| } |
| |
| fn avb_handle_verification_result( |
| &mut self, |
| color: BootStateColor, |
| boot_os_version: Option<&[u8]>, |
| boot_security_patch: Option<&[u8]>, |
| system_os_version: Option<&[u8]>, |
| system_security_patch: Option<&[u8]>, |
| vendor_os_version: Option<&[u8]>, |
| vendor_security_patch: Option<&[u8]>, |
| ) -> AvbIoResult<()> { |
| match self.efi_entry.system_table().boot_services().find_first_and_open::<GblAvbProtocol>() |
| { |
| Ok(protocol) => protocol |
| .handle_verification_result(&GblEfiAvbVerificationResult { |
| color: avb_color_to_efi_color(color), |
| boot_version: boot_os_version.map_or(null(), |p| p.as_ptr()), |
| boot_security_patch: boot_security_patch.map_or(null(), |p| p.as_ptr()), |
| system_version: system_os_version.map_or(null(), |p| p.as_ptr()), |
| system_security_patch: system_security_patch.map_or(null(), |p| p.as_ptr()), |
| vendor_version: vendor_os_version.map_or(null(), |p| p.as_ptr()), |
| vendor_security_patch: vendor_security_patch.map_or(null(), |p| p.as_ptr()), |
| }) |
| .map_err(efi_error_to_avb_error), |
| _ => Ok(()), |
| } |
| } |
| |
| fn get_image_buffer( |
| &mut self, |
| image_name: &str, |
| size: NonZeroUsize, |
| ) -> GblResult<ImageBuffer<'d>> { |
| self.get_buffer_image_loading(image_name, size) |
| .or(Self::allocate_image_buffer(image_name, size) |
| .map_err(|e| libgbl::IntegrationError::UnificationError(e))) |
| } |
| |
| fn get_custom_device_tree(&mut self) -> Option<&'a [u8]> { |
| // On Cuttlefish, the device tree comes from the UEFI config tables. |
| // TODO(b/353272981): once we've settled on the device tree UEFI protocol, use that |
| // instead to provide a Cuttlefish-specific backend. |
| Some(get_efi_fdt(&self.efi_entry)?.1) |
| } |
| |
| fn fixup_os_commandline<'c>( |
| &mut self, |
| commandline: &CStr, |
| fixup_buffer: &'c mut [u8], |
| ) -> Result<Option<&'c str>> { |
| Ok( |
| match self |
| .efi_entry |
| .system_table() |
| .boot_services() |
| .find_first_and_open::<GblOsConfigurationProtocol>() |
| { |
| Ok(protocol) => { |
| protocol.fixup_kernel_commandline(commandline, fixup_buffer)?; |
| Some(CStr::from_bytes_until_nul(&fixup_buffer[..])?.to_str()?) |
| } |
| _ => None, |
| }, |
| ) |
| } |
| |
| fn fixup_bootconfig<'c>( |
| &mut self, |
| bootconfig: &[u8], |
| fixup_buffer: &'c mut [u8], |
| ) -> Result<Option<&'c [u8]>> { |
| Ok( |
| match self |
| .efi_entry |
| .system_table() |
| .boot_services() |
| .find_first_and_open::<GblOsConfigurationProtocol>() |
| { |
| Ok(protocol) => { |
| let fixup_size = protocol.fixup_bootconfig(bootconfig, fixup_buffer)?; |
| Some(&fixup_buffer[..fixup_size]) |
| } |
| _ => None, |
| }, |
| ) |
| } |
| |
| fn fixup_device_tree(&mut self, device_tree: &mut [u8]) -> Result<()> { |
| if let Ok(protocol) = |
| self.efi_entry.system_table().boot_services().find_first_and_open::<DtFixupProtocol>() |
| { |
| protocol.fixup(device_tree)?; |
| } |
| |
| Ok(()) |
| } |
| |
| fn select_device_trees( |
| &mut self, |
| components_registry: &mut DeviceTreeComponentsRegistry, |
| ) -> Result<()> { |
| match self |
| .efi_entry |
| .system_table() |
| .boot_services() |
| .find_first_and_open::<GblOsConfigurationProtocol>() |
| { |
| Ok(protocol) => { |
| // Protocol detected, convert to UEFI types. |
| let mut uefi_components: ArrayVec<_, MAXIMUM_DEVICE_TREE_COMPONENTS> = |
| components_registry |
| .components() |
| .map(|component| dt_component_to_efi_dt(component)) |
| .collect(); |
| |
| protocol.select_device_trees(&mut uefi_components[..])?; |
| |
| // Propagate selections to the components_registry. |
| components_registry |
| .components_mut() |
| .zip(uefi_components.iter_mut()) |
| .enumerate() |
| .for_each(|(index, (component, uefi_component))| { |
| if uefi_component.selected { |
| efi_println!( |
| self.efi_entry, |
| "Device tree component at index {} got selected by UEFI call. \ |
| Source: {}", |
| index, |
| component.source |
| ); |
| } |
| component.selected = uefi_component.selected; |
| }); |
| |
| Ok(()) |
| } |
| _ => components_registry.autoselect(), |
| } |
| } |
| |
| fn fastboot_variable( |
| &mut self, |
| name: &str, |
| args: Split<'_, char>, |
| out: &mut [u8], |
| ) -> Result<usize> { |
| self.efi_entry |
| .system_table() |
| .boot_services() |
| .find_first_and_open::<GblFastbootProtocol>()? |
| .get_var(name, args, out) |
| } |
| |
| async fn fastboot_send_all_variables(&mut self, sender: &mut impl VarInfoSender) -> Result<()> { |
| let mut out = [0u8; fastboot::MAX_RESPONSE_SIZE]; |
| let protocol = self |
| .efi_entry |
| .system_table() |
| .boot_services() |
| .find_first_and_open::<GblFastbootProtocol>()?; |
| for ele in protocol.var_iter()? { |
| let (name, args, val) = ele.get(&mut out[..])?; |
| sender.send_var_info(name, args, val).await?; |
| } |
| Ok(()) |
| } |
| } |
| |
| /// Inherits everything from `ops` but override a few such as read boot_a from |
| /// bootimg_buffer, |
| /// avb_write_rollback_index(), slot operation etc |
| pub struct RambootOps<'b, T> { |
| pub ops: &'b mut T, |
| pub bootimg_buffer: &'b mut [u8], |
| } |
| |
| impl<'a, 'd, T: GblOps<'a, 'd>> GblOps<'a, 'd> for RambootOps<'_, T> { |
| fn console_out(&mut self) -> Option<&mut dyn Write> { |
| self.ops.console_out() |
| } |
| |
| fn should_stop_in_fastboot(&mut self) -> Result<bool> { |
| self.ops.should_stop_in_fastboot() |
| } |
| |
| fn reboot(&mut self) { |
| self.ops.reboot() |
| } |
| |
| fn disks( |
| &self, |
| ) -> &'a [GblDisk< |
| Disk<impl BlockIo + 'a, impl DerefMut<Target = [u8]> + 'a>, |
| Gpt<impl DerefMut<Target = [u8]> + 'a>, |
| >] { |
| self.ops.disks() |
| } |
| |
| fn expected_os(&mut self) -> Result<Option<Os>> { |
| self.ops.expected_os() |
| } |
| |
| fn zircon_add_device_zbi_items( |
| &mut self, |
| container: &mut ZbiContainer<&mut [u8]>, |
| ) -> Result<()> { |
| self.ops.zircon_add_device_zbi_items(container) |
| } |
| |
| fn get_zbi_bootloader_files_buffer(&mut self) -> Option<&mut [u8]> { |
| self.ops.get_zbi_bootloader_files_buffer() |
| } |
| |
| fn load_slot_interface<'c>( |
| &'c mut self, |
| _fnmut: &'c mut dyn FnMut(&mut [u8]) -> Result<()>, |
| _boot_token: BootToken, |
| ) -> GblResult<Cursor<'c>> { |
| self.ops.load_slot_interface(_fnmut, _boot_token) |
| } |
| |
| fn avb_read_is_device_unlocked(&mut self) -> AvbIoResult<bool> { |
| self.ops.avb_read_is_device_unlocked() |
| } |
| |
| fn avb_read_rollback_index(&mut self, _rollback_index_location: usize) -> AvbIoResult<u64> { |
| self.ops.avb_read_rollback_index(_rollback_index_location) |
| } |
| |
| fn avb_write_rollback_index(&mut self, _: usize, _: u64) -> AvbIoResult<()> { |
| // We don't want to persist AVB related data such as updating antirollback indices. |
| Ok(()) |
| } |
| |
| fn avb_cert_read_permanent_attributes( |
| &mut self, |
| attributes: &mut CertPermanentAttributes, |
| ) -> AvbIoResult<()> { |
| self.ops.avb_cert_read_permanent_attributes(attributes) |
| } |
| |
| fn avb_cert_read_permanent_attributes_hash(&mut self) -> AvbIoResult<[u8; SHA256_DIGEST_SIZE]> { |
| self.ops.avb_cert_read_permanent_attributes_hash() |
| } |
| |
| fn get_image_buffer( |
| &mut self, |
| image_name: &str, |
| size: NonZeroUsize, |
| ) -> GblResult<ImageBuffer<'d>> { |
| self.ops.get_image_buffer(image_name, size) |
| } |
| |
| fn get_custom_device_tree(&mut self) -> Option<&'a [u8]> { |
| self.ops.get_custom_device_tree() |
| } |
| |
| fn fixup_os_commandline<'c>( |
| &mut self, |
| commandline: &CStr, |
| fixup_buffer: &'c mut [u8], |
| ) -> Result<Option<&'c str>> { |
| self.ops.fixup_os_commandline(commandline, fixup_buffer) |
| } |
| |
| fn fixup_bootconfig<'c>( |
| &mut self, |
| bootconfig: &[u8], |
| fixup_buffer: &'c mut [u8], |
| ) -> Result<Option<&'c [u8]>> { |
| self.ops.fixup_bootconfig(bootconfig, fixup_buffer) |
| } |
| |
| fn fixup_device_tree(&mut self, device_tree: &mut [u8]) -> Result<()> { |
| self.ops.fixup_device_tree(device_tree) |
| } |
| |
| fn select_device_trees( |
| &mut self, |
| components_registry: &mut DeviceTreeComponentsRegistry, |
| ) -> Result<()> { |
| self.ops.select_device_trees(components_registry) |
| } |
| |
| fn read_from_partition_sync( |
| &mut self, |
| part: &str, |
| off: u64, |
| out: &mut (impl SliceMaybeUninit + ?Sized), |
| ) -> Result<()> { |
| if part == "boot_a" { |
| let len = min(self.bootimg_buffer.len() - off as usize, out.len()); |
| out.clone_from_slice(&self.bootimg_buffer[off as usize..off as usize + len]); |
| Ok(()) |
| } else { |
| self.ops.read_from_partition_sync(part, off, out) |
| } |
| } |
| |
| fn avb_handle_verification_result( |
| &mut self, |
| color: BootStateColor, |
| boot_os_version: Option<&[u8]>, |
| boot_security_patch: Option<&[u8]>, |
| system_os_version: Option<&[u8]>, |
| system_security_patch: Option<&[u8]>, |
| vendor_os_version: Option<&[u8]>, |
| vendor_security_patch: Option<&[u8]>, |
| ) -> AvbIoResult<()> { |
| self.ops.avb_handle_verification_result( |
| color, |
| boot_os_version, |
| boot_security_patch, |
| system_os_version, |
| system_security_patch, |
| vendor_os_version, |
| vendor_security_patch, |
| ) |
| } |
| |
| fn fastboot_variable( |
| &mut self, |
| name: &str, |
| args: Split<'_, char>, |
| out: &mut [u8], |
| ) -> Result<usize> { |
| self.ops.fastboot_variable(name, args, out) |
| } |
| |
| async fn fastboot_send_all_variables(&mut self, sender: &mut impl VarInfoSender) -> Result<()> { |
| self.ops.fastboot_send_all_variables(sender).await |
| } |
| |
| fn avb_validate_vbmeta_public_key( |
| &self, |
| public_key: &[u8], |
| public_key_metadata: Option<&[u8]>, |
| ) -> AvbIoResult<KeyValidationStatus> { |
| self.ops.avb_validate_vbmeta_public_key(public_key, public_key_metadata) |
| } |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use super::*; |
| use efi_mocks::{protocol::gbl_efi_avb::GblAvbProtocol, MockEfi}; |
| use mockall::predicate::eq; |
| |
| #[test] |
| fn ops_write_trait() { |
| let mut mock_efi = MockEfi::new(); |
| |
| mock_efi.con_out.expect_write_str().with(eq("foo bar")).return_const(Ok(())); |
| let installed = mock_efi.install(); |
| |
| let mut ops = Ops::new(installed.entry(), &[], None); |
| |
| assert!(write!(&mut ops, "{} {}", "foo", "bar").is_ok()); |
| } |
| |
| #[test] |
| fn ops_avb_validate_vbmeta_public_key_returns_valid() { |
| let mut mock_efi = MockEfi::new(); |
| let mut avb = GblAvbProtocol::default(); |
| avb.validate_vbmeta_public_key_result = |
| Some(Ok(efi_types::GBL_EFI_AVB_KEY_VALIDATION_STATUS_VALID)); |
| mock_efi.boot_services.expect_find_first_and_open::<GblAvbProtocol>().return_const(Ok(avb)); |
| |
| let installed = mock_efi.install(); |
| let ops = Ops::new(installed.entry(), &[], None); |
| |
| assert_eq!(ops.avb_validate_vbmeta_public_key(&[], None), Ok(KeyValidationStatus::Valid)); |
| } |
| |
| #[test] |
| fn ops_avb_validate_vbmeta_public_key_returns_valid_custom_key() { |
| let mut mock_efi = MockEfi::new(); |
| let mut avb = GblAvbProtocol::default(); |
| avb.validate_vbmeta_public_key_result = |
| Some(Ok(efi_types::GBL_EFI_AVB_KEY_VALIDATION_STATUS_VALID_CUSTOM_KEY)); |
| mock_efi.boot_services.expect_find_first_and_open::<GblAvbProtocol>().return_const(Ok(avb)); |
| |
| let installed = mock_efi.install(); |
| let ops = Ops::new(installed.entry(), &[], None); |
| |
| assert_eq!( |
| ops.avb_validate_vbmeta_public_key(&[], None), |
| Ok(KeyValidationStatus::ValidCustomKey) |
| ); |
| } |
| |
| #[test] |
| fn ops_avb_validate_vbmeta_public_key_returns_invalid() { |
| let mut mock_efi = MockEfi::new(); |
| let mut avb = GblAvbProtocol::default(); |
| avb.validate_vbmeta_public_key_result = |
| Some(Ok(efi_types::GBL_EFI_AVB_KEY_VALIDATION_STATUS_INVALID)); |
| mock_efi.boot_services.expect_find_first_and_open::<GblAvbProtocol>().return_const(Ok(avb)); |
| |
| let installed = mock_efi.install(); |
| let ops = Ops::new(installed.entry(), &[], None); |
| |
| assert_eq!(ops.avb_validate_vbmeta_public_key(&[], None), Ok(KeyValidationStatus::Invalid)); |
| } |
| |
| #[test] |
| fn ops_avb_validate_vbmeta_public_key_failed_error_mapped() { |
| let mut mock_efi = MockEfi::new(); |
| let mut avb = GblAvbProtocol::default(); |
| avb.validate_vbmeta_public_key_result = Some(Err(Error::OutOfResources)); |
| mock_efi.boot_services.expect_find_first_and_open::<GblAvbProtocol>().return_const(Ok(avb)); |
| |
| let installed = mock_efi.install(); |
| let ops = Ops::new(installed.entry(), &[], None); |
| |
| assert_eq!(ops.avb_validate_vbmeta_public_key(&[], None), Err(AvbIoError::Oom)); |
| } |
| |
| #[test] |
| fn ops_avb_validate_vbmeta_public_key_protocol_not_found_mapped_to_not_implemented() { |
| let mut mock_efi = MockEfi::new(); |
| mock_efi |
| .boot_services |
| .expect_find_first_and_open::<GblAvbProtocol>() |
| .returning(|| Err(Error::NotFound)); |
| |
| let installed = mock_efi.install(); |
| let ops = Ops::new(installed.entry(), &[], None); |
| |
| assert_eq!(ops.avb_validate_vbmeta_public_key(&[], None), Err(AvbIoError::NotImplemented)); |
| } |
| |
| #[test] |
| fn ops_avb_read_is_device_unlocked_returns_true() { |
| let mut mock_efi = MockEfi::new(); |
| let mut avb = GblAvbProtocol::default(); |
| avb.read_is_device_unlocked_result = Some(Ok(true)); |
| mock_efi.boot_services.expect_find_first_and_open::<GblAvbProtocol>().return_const(Ok(avb)); |
| |
| let installed = mock_efi.install(); |
| let mut ops = Ops::new(installed.entry(), &[], None); |
| |
| assert_eq!(ops.avb_read_is_device_unlocked(), Ok(true)); |
| } |
| |
| #[test] |
| fn ops_avb_read_is_device_unlocked_returns_false() { |
| let mut mock_efi = MockEfi::new(); |
| let mut avb = GblAvbProtocol::default(); |
| avb.read_is_device_unlocked_result = Some(Ok(false)); |
| mock_efi.boot_services.expect_find_first_and_open::<GblAvbProtocol>().return_const(Ok(avb)); |
| |
| let installed = mock_efi.install(); |
| let mut ops = Ops::new(installed.entry(), &[], None); |
| |
| assert_eq!(ops.avb_read_is_device_unlocked(), Ok(false)); |
| } |
| |
| #[test] |
| fn ops_avb_read_is_device_unlocked_protocol_not_found() { |
| let mut mock_efi = MockEfi::new(); |
| mock_efi |
| .boot_services |
| .expect_find_first_and_open::<GblAvbProtocol>() |
| .returning(|| Err(Error::NotFound)); |
| |
| let installed = mock_efi.install(); |
| let mut ops = Ops::new(installed.entry(), &[], None); |
| |
| assert_eq!(ops.avb_read_is_device_unlocked(), Err(AvbIoError::NotImplemented)); |
| } |
| |
| #[test] |
| fn ops_avb_read_rollback_index_success() { |
| let mut mock_efi = MockEfi::new(); |
| let mut avb = GblAvbProtocol::default(); |
| avb.read_rollback_index_result = Some(Ok(12345)); |
| mock_efi.boot_services.expect_find_first_and_open::<GblAvbProtocol>().return_const(Ok(avb)); |
| |
| let installed = mock_efi.install(); |
| let mut ops = Ops::new(installed.entry(), &[], None); |
| |
| assert_eq!(ops.avb_read_rollback_index(0), Ok(12345)); |
| } |
| |
| #[test] |
| fn ops_avb_read_rollback_index_error() { |
| let mut mock_efi = MockEfi::new(); |
| let mut avb = GblAvbProtocol::default(); |
| avb.read_rollback_index_result = Some(Err(Error::OutOfResources)); |
| mock_efi.boot_services.expect_find_first_and_open::<GblAvbProtocol>().return_const(Ok(avb)); |
| |
| let installed = mock_efi.install(); |
| let mut ops = Ops::new(installed.entry(), &[], None); |
| |
| assert_eq!(ops.avb_read_rollback_index(0), Err(AvbIoError::Oom)); |
| } |
| |
| #[test] |
| fn ops_avb_read_rollback_index_protocol_not_found() { |
| let mut mock_efi = MockEfi::new(); |
| mock_efi |
| .boot_services |
| .expect_find_first_and_open::<GblAvbProtocol>() |
| .returning(|| Err(Error::NotFound)); |
| |
| let installed = mock_efi.install(); |
| let mut ops = Ops::new(installed.entry(), &[], None); |
| |
| assert_eq!(ops.avb_read_rollback_index(0), Err(AvbIoError::NotImplemented)); |
| } |
| |
| #[test] |
| fn ops_avb_write_rollback_index_success() { |
| let mut mock_efi = MockEfi::new(); |
| let mut avb = GblAvbProtocol::default(); |
| avb.write_rollback_index_result = Some(Ok(())); |
| mock_efi.boot_services.expect_find_first_and_open::<GblAvbProtocol>().return_const(Ok(avb)); |
| |
| let installed = mock_efi.install(); |
| let mut ops = Ops::new(installed.entry(), &[], None); |
| |
| assert!(ops.avb_write_rollback_index(0, 12345).is_ok()); |
| } |
| |
| #[test] |
| fn ops_avb_write_rollback_index_error() { |
| let mut mock_efi = MockEfi::new(); |
| let mut avb = GblAvbProtocol::default(); |
| avb.write_rollback_index_result = Some(Err(Error::InvalidInput)); |
| mock_efi.boot_services.expect_find_first_and_open::<GblAvbProtocol>().return_const(Ok(avb)); |
| |
| let installed = mock_efi.install(); |
| let mut ops = Ops::new(installed.entry(), &[], None); |
| |
| assert_eq!(ops.avb_write_rollback_index(0, 12345), Err(AvbIoError::InvalidValueSize)); |
| } |
| |
| #[test] |
| fn ops_avb_write_rollback_index_protocol_not_found() { |
| let mut mock_efi = MockEfi::new(); |
| mock_efi |
| .boot_services |
| .expect_find_first_and_open::<GblAvbProtocol>() |
| .returning(|| Err(Error::NotFound)); |
| |
| let installed = mock_efi.install(); |
| let mut ops = Ops::new(installed.entry(), &[], None); |
| |
| assert_eq!(ops.avb_write_rollback_index(0, 12345), Err(AvbIoError::NotImplemented)); |
| } |
| } |