1 // Copyright 2023, The Android Open Source Project 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 //! GblOps trait that defines GBL callbacks. 16 17 pub use crate::image_buffer::ImageBuffer; 18 use crate::{ 19 error::Result as GblResult, 20 fuchsia_boot::GblAbrOps, 21 gbl_avb::state::{BootStateColor, KeyValidationStatus}, 22 partition::{check_part_unique, read_unique_partition, write_unique_partition, GblDisk}, 23 }; 24 pub use abr::{set_one_shot_bootloader, set_one_shot_recovery, SlotIndex}; 25 use core::{ffi::CStr, fmt::Write, num::NonZeroUsize, ops::DerefMut, result::Result}; 26 use gbl_async::block_on; 27 use gbl_storage::SliceMaybeUninit; 28 use libutils::aligned_subslice; 29 30 // Re-exports of types from other dependencies that appear in the APIs of this library. 31 pub use avb::{ 32 CertPermanentAttributes, IoError as AvbIoError, IoResult as AvbIoResult, SHA256_DIGEST_SIZE, 33 }; 34 pub use gbl_storage::{BlockIo, Disk, Gpt}; 35 use liberror::Error; 36 pub use slots::{Slot, SlotsMetadata}; 37 pub use zbi::{ZbiContainer, ZBI_ALIGNMENT_USIZE}; 38 39 use super::device_tree; 40 use super::slots; 41 42 /// Target Type of OS to boot. 43 #[derive(PartialEq, Debug, Copy, Clone)] 44 pub enum Os { 45 /// Android 46 Android, 47 /// Fuchsia 48 Fuchsia, 49 } 50 51 /// Contains reboot reasons for instructing GBL to boot to different modes. 52 #[derive(PartialEq, Debug, Copy, Clone)] 53 pub enum RebootReason { 54 /// Normal boot. 55 Normal, 56 /// Bootloader Fastboot mode. 57 Bootloader, 58 /// Userspace Fastboot mode. 59 FastbootD, 60 /// Recovery mode. 61 Recovery, 62 } 63 64 // https://stackoverflow.com/questions/41081240/idiomatic-callbacks-in-rust 65 // should we use traits for this? or optional/box FnMut? 66 // 67 /* TODO: b/312612203 - needed callbacks: 68 missing: 69 - key management => atx extension in callback => atx_ops: ptr::null_mut(), // support optional ATX. 70 */ 71 /// Trait that defines callbacks that can be provided to Gbl. 72 pub trait GblOps<'a, 'd> { 73 /// Gets a console for logging messages. console_out(&mut self) -> Option<&mut dyn Write>74 fn console_out(&mut self) -> Option<&mut dyn Write>; 75 76 /// The string to use for console line termination with [gbl_println!]. 77 /// 78 /// Defaults to "\n" if not overridden. console_newline(&self) -> &'static str79 fn console_newline(&self) -> &'static str { 80 "\n" 81 } 82 83 /// This method can be used to implement platform specific mechanism for deciding whether boot 84 /// should abort and enter Fastboot mode. should_stop_in_fastboot(&mut self) -> Result<bool, Error>85 fn should_stop_in_fastboot(&mut self) -> Result<bool, Error>; 86 87 /// Reboots the system into the last set boot mode. 88 /// 89 /// The method is not expected to return. Errors should be handled internally by the 90 /// implementation. In most cases, implementation should continue to reset even in the presence 91 /// of errors (users can force power cycle anyway). If there are error cases where reboot 92 /// absolutely can't be taken, implementation should hang and notify platform user in its own 93 /// way. reboot(&mut self)94 fn reboot(&mut self); 95 96 /// Reboots into recovery mode 97 /// 98 /// On success, returns a closure that performs the reboot. reboot_recovery(&mut self) -> Result<impl FnOnce() + '_, Error>99 fn reboot_recovery(&mut self) -> Result<impl FnOnce() + '_, Error> { 100 if self.expected_os_is_fuchsia()? { 101 // TODO(b/363075013): Checks and prioritizes platform specific `set_boot_reason()`. 102 set_one_shot_recovery(&mut GblAbrOps(self), true)?; 103 return Ok(|| self.reboot()); 104 } 105 Err(Error::Unsupported) 106 } 107 108 /// Reboots into bootloader fastboot mode 109 /// 110 /// On success, returns a closure that performs the reboot. reboot_bootloader(&mut self) -> Result<impl FnOnce() + '_, Error>111 fn reboot_bootloader(&mut self) -> Result<impl FnOnce() + '_, Error> { 112 if self.expected_os_is_fuchsia()? { 113 // TODO(b/363075013): Checks and prioritizes platform specific `set_boot_reason()`. 114 set_one_shot_bootloader(&mut GblAbrOps(self), true)?; 115 return Ok(|| self.reboot()); 116 } 117 Err(Error::Unsupported) 118 } 119 120 /// Returns the list of disk devices on this platform. 121 /// 122 /// Notes that the return slice doesn't capture the life time of `&self`, meaning that the slice 123 /// reference must be producible without borrowing `Self`. This is intended and necessary to 124 /// make disk IO and the rest of GblOps methods independent and parallelizable, which is 125 /// required for features such as parallell fastboot flash, download and other commands. For 126 /// implementation, this typically means that the `GblOps` object should hold a reference of the 127 /// array instead of owning it. disks( &self, ) -> &'a [GblDisk< Disk<impl BlockIo + 'a, impl DerefMut<Target = [u8]> + 'a>, Gpt<impl DerefMut<Target = [u8]> + 'a>, >]128 fn disks( 129 &self, 130 ) -> &'a [GblDisk< 131 Disk<impl BlockIo + 'a, impl DerefMut<Target = [u8]> + 'a>, 132 Gpt<impl DerefMut<Target = [u8]> + 'a>, 133 >]; 134 135 /// Reads data from a partition. read_from_partition( &mut self, part: &str, off: u64, out: &mut (impl SliceMaybeUninit + ?Sized), ) -> Result<(), Error>136 async fn read_from_partition( 137 &mut self, 138 part: &str, 139 off: u64, 140 out: &mut (impl SliceMaybeUninit + ?Sized), 141 ) -> Result<(), Error> { 142 read_unique_partition(self.disks(), part, off, out).await 143 } 144 145 /// Reads data from a partition synchronously. read_from_partition_sync( &mut self, part: &str, off: u64, out: &mut (impl SliceMaybeUninit + ?Sized), ) -> Result<(), Error>146 fn read_from_partition_sync( 147 &mut self, 148 part: &str, 149 off: u64, 150 out: &mut (impl SliceMaybeUninit + ?Sized), 151 ) -> Result<(), Error> { 152 block_on(self.read_from_partition(part, off, out)) 153 } 154 155 /// Writes data to a partition. write_to_partition( &mut self, part: &str, off: u64, data: &mut [u8], ) -> Result<(), Error>156 async fn write_to_partition( 157 &mut self, 158 part: &str, 159 off: u64, 160 data: &mut [u8], 161 ) -> Result<(), Error> { 162 write_unique_partition(self.disks(), part, off, data).await 163 } 164 165 /// Writes data to a partition synchronously. write_to_partition_sync( &mut self, part: &str, off: u64, data: &mut [u8], ) -> Result<(), Error>166 fn write_to_partition_sync( 167 &mut self, 168 part: &str, 169 off: u64, 170 data: &mut [u8], 171 ) -> Result<(), Error> { 172 block_on(self.write_to_partition(part, off, data)) 173 } 174 175 /// Returns the size of a partiiton. Returns Ok(None) if partition doesn't exist. partition_size(&mut self, part: &str) -> Result<Option<u64>, Error>176 fn partition_size(&mut self, part: &str) -> Result<Option<u64>, Error> { 177 match check_part_unique(self.disks(), part) { 178 Ok((_, p)) => Ok(Some(p.size()?)), 179 Err(Error::NotFound) => Ok(None), 180 Err(e) => Err(e), 181 } 182 } 183 184 /// Returns which OS to load, or `None` to try to auto-detect based on disk layout & contents. expected_os(&mut self) -> Result<Option<Os>, Error>185 fn expected_os(&mut self) -> Result<Option<Os>, Error>; 186 187 /// Returns if the expected_os is fuchsia expected_os_is_fuchsia(&mut self) -> Result<bool, Error>188 fn expected_os_is_fuchsia(&mut self) -> Result<bool, Error> { 189 // TODO(b/374776896): Implement auto detection. 190 Ok(self.expected_os()?.map(|v| v == Os::Fuchsia).unwrap_or(false)) 191 } 192 193 /// Adds device specific ZBI items to the given `container` zircon_add_device_zbi_items( &mut self, container: &mut ZbiContainer<&mut [u8]>, ) -> Result<(), Error>194 fn zircon_add_device_zbi_items( 195 &mut self, 196 container: &mut ZbiContainer<&mut [u8]>, 197 ) -> Result<(), Error>; 198 199 /// Gets a buffer for staging bootloader file from fastboot. 200 /// 201 /// Fuchsia uses bootloader file for staging SSH key in development flow. 202 /// 203 /// Returns `None` if the platform does not intend to support it. get_zbi_bootloader_files_buffer(&mut self) -> Option<&mut [u8]>204 fn get_zbi_bootloader_files_buffer(&mut self) -> Option<&mut [u8]>; 205 206 /// Gets the aligned part of buffer returned by `get_zbi_bootloader_files_buffer()` according to 207 /// ZBI alignment requirement. get_zbi_bootloader_files_buffer_aligned(&mut self) -> Option<&mut [u8]>208 fn get_zbi_bootloader_files_buffer_aligned(&mut self) -> Option<&mut [u8]> { 209 aligned_subslice(self.get_zbi_bootloader_files_buffer()?, ZBI_ALIGNMENT_USIZE).ok() 210 } 211 212 // TODO(b/334962570): figure out how to plumb ops-provided hash implementations into 213 // libavb. The tricky part is that libavb hashing APIs are global with no way to directly 214 // correlate the implementation to a particular [GblOps] object, so we'll probably have to 215 // create a [Context] ahead of time and store it globally for the hashing APIs to access. 216 // However this would mean that [Context] must be a standalone object and cannot hold a 217 // reference to [GblOps], which may restrict implementations. 218 // fn new_digest(&self) -> Option<Self::Context>; 219 220 /// Load and initialize a slot manager and return a cursor over the manager on success. 221 /// 222 /// # Args 223 /// 224 /// * `persist`: A user provided closure for persisting a given slot metadata bytes to storage. 225 /// * `boot_token`: A [slots::BootToken]. load_slot_interface<'b>( &'b mut self, persist: &'b mut dyn FnMut(&mut [u8]) -> Result<(), Error>, boot_token: slots::BootToken, ) -> GblResult<slots::Cursor<'b>>226 fn load_slot_interface<'b>( 227 &'b mut self, 228 persist: &'b mut dyn FnMut(&mut [u8]) -> Result<(), Error>, 229 boot_token: slots::BootToken, 230 ) -> GblResult<slots::Cursor<'b>>; 231 232 // The following is a selective subset of the interfaces in `avb::Ops` and `avb::CertOps` needed 233 // by GBL's usage of AVB. The rest of the APIs are either not relevant to or are implemented and 234 // managed by GBL APIs. 235 236 /// Returns if device is in an unlocked state. 237 /// 238 /// The interface has the same requirement as `avb::Ops::read_is_device_unlocked`. avb_read_is_device_unlocked(&mut self) -> AvbIoResult<bool>239 fn avb_read_is_device_unlocked(&mut self) -> AvbIoResult<bool>; 240 241 /// Reads the AVB rollback index at the given location 242 /// 243 /// The interface has the same requirement as `avb::Ops::read_rollback_index`. avb_read_rollback_index(&mut self, rollback_index_location: usize) -> AvbIoResult<u64>244 fn avb_read_rollback_index(&mut self, rollback_index_location: usize) -> AvbIoResult<u64>; 245 246 /// Writes the AVB rollback index at the given location. 247 /// 248 /// The interface has the same requirement as `avb::Ops::write_rollback_index`. avb_write_rollback_index( &mut self, rollback_index_location: usize, index: u64, ) -> AvbIoResult<()>249 fn avb_write_rollback_index( 250 &mut self, 251 rollback_index_location: usize, 252 index: u64, 253 ) -> AvbIoResult<()>; 254 255 /// Reads the AVB persistent value for the given name. 256 /// 257 /// The interface has the same requirement as `avb::Ops::read_persistent_value`. avb_read_persistent_value(&mut self, name: &CStr, value: &mut [u8]) -> AvbIoResult<usize>258 fn avb_read_persistent_value(&mut self, name: &CStr, value: &mut [u8]) -> AvbIoResult<usize>; 259 260 /// Writes the AVB persistent value for the given name. 261 /// 262 /// The interface has the same requirement as `avb::Ops::write_persistent_value`. avb_write_persistent_value(&mut self, name: &CStr, value: &[u8]) -> AvbIoResult<()>263 fn avb_write_persistent_value(&mut self, name: &CStr, value: &[u8]) -> AvbIoResult<()>; 264 265 /// Erases the AVB persistent value for the given name. 266 /// 267 /// The interface has the same requirement as `avb::Ops::erase_persistent_value`. avb_erase_persistent_value(&mut self, name: &CStr) -> AvbIoResult<()>268 fn avb_erase_persistent_value(&mut self, name: &CStr) -> AvbIoResult<()>; 269 270 /// Validate public key used to execute AVB. 271 /// 272 /// Used by `avb::CertOps::read_permanent_attributes_hash` so have similar requirements. avb_validate_vbmeta_public_key( &self, public_key: &[u8], public_key_metadata: Option<&[u8]>, ) -> AvbIoResult<KeyValidationStatus>273 fn avb_validate_vbmeta_public_key( 274 &self, 275 public_key: &[u8], 276 public_key_metadata: Option<&[u8]>, 277 ) -> AvbIoResult<KeyValidationStatus>; 278 279 /// Reads AVB certificate extension permanent attributes. 280 /// 281 /// The interface has the same requirement as `avb::CertOps::read_permanent_attributes`. avb_cert_read_permanent_attributes( &mut self, attributes: &mut CertPermanentAttributes, ) -> AvbIoResult<()>282 fn avb_cert_read_permanent_attributes( 283 &mut self, 284 attributes: &mut CertPermanentAttributes, 285 ) -> AvbIoResult<()>; 286 287 /// Reads AVB certificate extension permanent attributes hash. 288 /// 289 /// The interface has the same requirement as `avb::CertOps::read_permanent_attributes_hash`. avb_cert_read_permanent_attributes_hash(&mut self) -> AvbIoResult<[u8; SHA256_DIGEST_SIZE]>290 fn avb_cert_read_permanent_attributes_hash(&mut self) -> AvbIoResult<[u8; SHA256_DIGEST_SIZE]>; 291 292 /// Handle AVB result. 293 /// 294 /// Set device state (rot / version binding), show UI, etc. avb_handle_verification_result( &mut self, color: BootStateColor, digest: Option<&CStr>, 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<()>295 fn avb_handle_verification_result( 296 &mut self, 297 color: BootStateColor, 298 digest: Option<&CStr>, 299 boot_os_version: Option<&[u8]>, 300 boot_security_patch: Option<&[u8]>, 301 system_os_version: Option<&[u8]>, 302 system_security_patch: Option<&[u8]>, 303 vendor_os_version: Option<&[u8]>, 304 vendor_security_patch: Option<&[u8]>, 305 ) -> AvbIoResult<()>; 306 307 /// Get buffer for specific image of requested size. get_image_buffer( &mut self, image_name: &str, size: NonZeroUsize, ) -> GblResult<ImageBuffer<'d>>308 fn get_image_buffer( 309 &mut self, 310 image_name: &str, 311 size: NonZeroUsize, 312 ) -> GblResult<ImageBuffer<'d>>; 313 314 /// Returns the custom device tree to use, if any. 315 /// 316 /// If this returns a device tree, it will be used instead of any on-disk contents. This is 317 /// currently needed for Cuttlefish, but should not be used in production devices because this 318 /// data cannot be verified with libavb. get_custom_device_tree(&mut self) -> Option<&'a [u8]>319 fn get_custom_device_tree(&mut self) -> Option<&'a [u8]>; 320 321 /// Requests an OS command line to be used alongside the one built by GBL. 322 /// 323 /// The returned command line will be verified and appended on top of the command line 324 /// built by GBL. Refer to the behavior specified for the corresponding UEFI interface: 325 /// https://cs.android.com/android/platform/superproject/main/+/main:bootable/libbootloader/gbl/docs/gbl_os_configuration_protocol.md fixup_os_commandline<'c>( &mut self, commandline: &CStr, fixup_buffer: &'c mut [u8], ) -> Result<Option<&'c str>, Error>326 fn fixup_os_commandline<'c>( 327 &mut self, 328 commandline: &CStr, 329 fixup_buffer: &'c mut [u8], 330 ) -> Result<Option<&'c str>, Error>; 331 332 /// Requests an OS bootconfig to be used alongside the one built by GBL. 333 /// 334 /// The returned bootconfig will be verified and appended on top of the bootconfig 335 /// built by GBL. Refer to the behavior specified for the corresponding UEFI interface: 336 /// https://cs.android.com/android/platform/superproject/main/+/main:bootable/libbootloader/gbl/docs/gbl_os_configuration_protocol.md fixup_bootconfig<'c>( &mut self, bootconfig: &[u8], fixup_buffer: &'c mut [u8], ) -> Result<Option<&'c [u8]>, Error>337 fn fixup_bootconfig<'c>( 338 &mut self, 339 bootconfig: &[u8], 340 fixup_buffer: &'c mut [u8], 341 ) -> Result<Option<&'c [u8]>, Error>; 342 343 /// Selects from device tree components to build the final one. 344 /// 345 /// Provided components registry must be used to select one device tree (none is not allowed), 346 /// and any number of overlays. Refer to the behavior specified for the corresponding UEFI 347 /// interface: 348 /// https://cs.android.com/android/platform/superproject/main/+/main:bootable/libbootloader/gbl/docs/gbl_os_configuration_protocol.md select_device_trees( &mut self, components: &mut device_tree::DeviceTreeComponentsRegistry, ) -> Result<(), Error>349 fn select_device_trees( 350 &mut self, 351 components: &mut device_tree::DeviceTreeComponentsRegistry, 352 ) -> Result<(), Error>; 353 354 /// Provide writtable buffer of the device tree built by GBL. 355 /// 356 /// Modified device tree will be verified and used to boot a device. Refer to the behavior 357 /// specified for the corresponding UEFI interface: 358 /// https://cs.android.com/android/platform/superproject/main/+/main:bootable/libbootloader/gbl/docs/efi_protocols.md 359 /// https://github.com/U-Boot-EFI/EFI_DT_FIXUP_PROTOCOL fixup_device_tree(&mut self, device_tree: &mut [u8]) -> Result<(), Error>360 fn fixup_device_tree(&mut self, device_tree: &mut [u8]) -> Result<(), Error>; 361 362 /// Gets platform-specific fastboot variable. 363 /// 364 /// # Args 365 /// 366 /// * `name`: Varaiable name. 367 /// * `args`: Additional arguments. 368 /// * `out`: The output buffer for the value of the variable. Must be a ASCII string. 369 /// 370 /// # Returns 371 /// 372 /// * Returns the number of bytes written in `out` on success. fastboot_variable<'arg>( &mut self, name: &CStr, args: impl Iterator<Item = &'arg CStr> + Clone, out: &mut [u8], ) -> Result<usize, Error>373 fn fastboot_variable<'arg>( 374 &mut self, 375 name: &CStr, 376 args: impl Iterator<Item = &'arg CStr> + Clone, 377 out: &mut [u8], 378 ) -> Result<usize, Error>; 379 380 /// Iterates all fastboot variables, arguments and values. 381 /// 382 /// # Args 383 /// 384 /// * `cb`: A closure that takes 1) an array of CStr that contains the variable name followed by 385 /// any additional arguments and 2) a CStr representing the value. fastboot_visit_all_variables( &mut self, cb: impl FnMut(&[&CStr], &CStr), ) -> Result<(), Error>386 fn fastboot_visit_all_variables( 387 &mut self, 388 cb: impl FnMut(&[&CStr], &CStr), 389 ) -> Result<(), Error>; 390 391 /// Returns a [SlotsMetadata] for the platform. slots_metadata(&mut self) -> Result<SlotsMetadata, Error>392 fn slots_metadata(&mut self) -> Result<SlotsMetadata, Error>; 393 394 /// Gets the currently booted bootloader slot. 395 /// 396 /// # Returns 397 /// 398 /// * Returns Ok(Some(slot index)) if bootloader is slotted. 399 /// * Returns Ok(Errorr::Unsupported) if bootloader is not slotted. 400 /// * Returns Err() on error. get_current_slot(&mut self) -> Result<Slot, Error>401 fn get_current_slot(&mut self) -> Result<Slot, Error>; 402 403 /// Gets the slot for the next A/B decision. 404 /// 405 /// # Args 406 /// 407 /// * `mark_boot_attempt`: Passes true if the caller attempts to boot the returned slot and 408 /// would like implementation to perform necessary update to the state of slot such as retry 409 /// counter. Passes false if the caller only wants to query the slot decision and not cause 410 /// any state change. get_next_slot(&mut self, _mark_boot_attempt: bool) -> Result<Slot, Error>411 fn get_next_slot(&mut self, _mark_boot_attempt: bool) -> Result<Slot, Error>; 412 413 /// Sets the active slot for the next A/B decision. 414 /// 415 /// # Args 416 /// 417 /// * `slot`: The numeric index of the slot. set_active_slot(&mut self, _slot: u8) -> Result<(), Error>418 fn set_active_slot(&mut self, _slot: u8) -> Result<(), Error>; 419 420 /// Sets the reboot reason for the next reboot. set_reboot_reason(&mut self, _reason: RebootReason) -> Result<(), Error>421 fn set_reboot_reason(&mut self, _reason: RebootReason) -> Result<(), Error>; 422 423 /// Gets the reboot reason for this boot. get_reboot_reason(&mut self) -> Result<RebootReason, Error>424 fn get_reboot_reason(&mut self) -> Result<RebootReason, Error>; 425 } 426 427 /// Prints with `GblOps::console_out()`. 428 #[macro_export] 429 macro_rules! gbl_print { 430 ( $ops:expr, $( $x:expr ),* $(,)? ) => { 431 { 432 match $ops.console_out() { 433 Some(v) => write!(v, $($x,)*).unwrap(), 434 _ => {} 435 } 436 } 437 }; 438 } 439 440 /// Prints the given text plus a newline termination with `GblOps::console_out()`. 441 #[macro_export] 442 macro_rules! gbl_println { 443 ( $ops:expr, $( $x:expr ),* $(,)? ) => { 444 let newline = $ops.console_newline(); 445 gbl_print!($ops, $($x,)*); 446 gbl_print!($ops, "{}", newline); 447 }; 448 } 449 450 #[cfg(test)] 451 pub(crate) mod test { 452 use super::*; 453 use crate::error::IntegrationError; 454 use crate::partition::GblDisk; 455 use abr::{get_and_clear_one_shot_bootloader, get_boot_slot}; 456 use avb::{CertOps, Ops}; 457 use avb_test::TestOps as AvbTestOps; 458 use core::{ 459 fmt::Write, 460 ops::{Deref, DerefMut}, 461 str::from_utf8, 462 }; 463 use fastboot::{snprintf, FormattedBytes}; 464 use gbl_async::block_on; 465 use gbl_storage::{new_gpt_max, Disk, GptMax, RamBlockIo}; 466 use std::{ 467 collections::{HashMap, LinkedList}, 468 ffi::CString, 469 }; 470 use zbi::{ZbiFlags, ZbiType}; 471 472 /// Type of [GblDisk] in tests. 473 pub(crate) type TestGblDisk = GblDisk<Disk<RamBlockIo<Vec<u8>>, Vec<u8>>, GptMax>; 474 475 /// Backing storage for [FakeGblOps]. 476 /// 477 /// This needs to be a separate object because [GblOps] has designed its lifetimes to borrow 478 /// the [GblDisk] objects rather than own it, so that they can outlive the ops 479 /// object when necessary. 480 /// 481 /// # Example usage 482 /// ``` 483 /// let storage = FakeGblOpsStorage::default(); 484 /// storage.add_gpt_device(&gpt_disk_contents); 485 /// storage.add_raw_device(c"raw", &raw_disk_contents); 486 /// 487 /// let fake_ops = FakeGblOps(&storage); 488 /// ``` 489 #[derive(Default)] 490 pub(crate) struct FakeGblOpsStorage(pub Vec<TestGblDisk>); 491 492 impl FakeGblOpsStorage { 493 /// Adds a GPT disk. add_gpt_device(&mut self, data: impl AsRef<[u8]>)494 pub(crate) fn add_gpt_device(&mut self, data: impl AsRef<[u8]>) { 495 // For test GPT images, all block sizes are 512. 496 self.0.push(TestGblDisk::new_gpt( 497 Disk::new_ram_alloc(512, 512, data.as_ref().to_vec()).unwrap(), 498 new_gpt_max(), 499 )); 500 let _ = block_on(self.0.last().unwrap().sync_gpt()); 501 } 502 503 /// Adds a raw partition disk. add_raw_device(&mut self, name: &CStr, data: impl AsRef<[u8]>)504 pub(crate) fn add_raw_device(&mut self, name: &CStr, data: impl AsRef<[u8]>) { 505 // For raw partition, use block_size=alignment=1 for simplicity. 506 TestGblDisk::new_raw(Disk::new_ram_alloc(1, 1, data.as_ref().to_vec()).unwrap(), name) 507 .and_then(|v| Ok(self.0.push(v))) 508 .unwrap() 509 } 510 } 511 512 impl Deref for FakeGblOpsStorage { 513 type Target = [TestGblDisk]; 514 deref(&self) -> &Self::Target515 fn deref(&self) -> &Self::Target { 516 &self.0[..] 517 } 518 } 519 520 /// Fake [GblOps] implementation for testing. 521 #[derive(Default)] 522 pub(crate) struct FakeGblOps<'a, 'd> { 523 /// Partition data to expose. 524 pub partitions: &'a [TestGblDisk], 525 526 /// Test fixture for [avb::Ops] and [avb::CertOps], provided by libavb. 527 /// 528 /// We don't use all the available functionality here, in particular the backing storage 529 /// is provided by `partitions` and our custom storage APIs rather than the [AvbTestOps] 530 /// fake storage, so that we can more accurately test our storage implementation. 531 pub avb_ops: AvbTestOps<'static>, 532 533 /// Value returned by `should_stop_in_fastboot`. 534 pub stop_in_fastboot: Option<Result<bool, Error>>, 535 536 /// For returned by `fn get_zbi_bootloader_files_buffer()` 537 pub zbi_bootloader_files_buffer: Vec<u8>, 538 539 /// For checking that `Self::reboot` is called. 540 pub rebooted: bool, 541 542 /// For return by `Self::expected_os()` 543 pub os: Option<Os>, 544 545 /// For return by `Self::avb_validate_vbmeta_public_key` 546 pub avb_key_validation_status: Option<AvbIoResult<KeyValidationStatus>>, 547 548 /// For return by `Self::get_image_buffer()` 549 pub image_buffers: HashMap<String, LinkedList<ImageBuffer<'d>>>, 550 } 551 552 /// Print `console_out` output, which can be useful for debugging. 553 impl<'a, 'd> Write for FakeGblOps<'a, 'd> { write_str(&mut self, s: &str) -> Result<(), std::fmt::Error>554 fn write_str(&mut self, s: &str) -> Result<(), std::fmt::Error> { 555 Ok(print!("{s}")) 556 } 557 } 558 559 impl<'a, 'd> FakeGblOps<'a, 'd> { 560 /// For now we've just hardcoded the `zircon_add_device_zbi_items()` callback to add a 561 /// single commandline ZBI item with these contents; if necessary we can generalize this 562 /// later and allow tests to configure the ZBI modifications. 563 pub const ADDED_ZBI_COMMANDLINE_CONTENTS: &'static [u8] = b"test_zbi_item"; 564 pub const TEST_BOOTLOADER_FILE_1: &'static [u8] = b"\x06test_1foo"; 565 pub const TEST_BOOTLOADER_FILE_2: &'static [u8] = b"\x06test_2bar"; 566 pub const GBL_TEST_VAR: &'static str = "gbl-test-var"; 567 pub const GBL_TEST_VAR_VAL: &'static str = "gbl-test-var-val"; 568 new(partitions: &'a [TestGblDisk]) -> Self569 pub fn new(partitions: &'a [TestGblDisk]) -> Self { 570 let mut res = Self { 571 partitions, 572 zbi_bootloader_files_buffer: vec![0u8; 32 * 1024], 573 ..Default::default() 574 }; 575 let mut container = 576 ZbiContainer::new(res.get_zbi_bootloader_files_buffer_aligned().unwrap()).unwrap(); 577 for ele in [Self::TEST_BOOTLOADER_FILE_1, Self::TEST_BOOTLOADER_FILE_2] { 578 container 579 .create_entry_with_payload(ZbiType::BootloaderFile, 0, ZbiFlags::default(), ele) 580 .unwrap(); 581 } 582 583 res 584 } 585 586 /// Copies an entire partition contents into a vector. 587 /// 588 /// This is a common enough operation in tests that it's worth a small wrapper to provide 589 /// a more convenient API using [Vec]. 590 /// 591 /// Panics if the given partition name doesn't exist. copy_partition(&mut self, name: &str) -> Vec<u8>592 pub fn copy_partition(&mut self, name: &str) -> Vec<u8> { 593 let mut contents = 594 vec![0u8; self.partition_size(name).unwrap().unwrap().try_into().unwrap()]; 595 assert!(self.read_from_partition_sync(name, 0, &mut contents[..]).is_ok()); 596 contents 597 } 598 } 599 600 impl<'a, 'd> GblOps<'a, 'd> for FakeGblOps<'a, 'd> { console_out(&mut self) -> Option<&mut dyn Write>601 fn console_out(&mut self) -> Option<&mut dyn Write> { 602 Some(self) 603 } 604 should_stop_in_fastboot(&mut self) -> Result<bool, Error>605 fn should_stop_in_fastboot(&mut self) -> Result<bool, Error> { 606 self.stop_in_fastboot.unwrap_or(Ok(false)) 607 } 608 reboot(&mut self)609 fn reboot(&mut self) { 610 self.rebooted = true; 611 } 612 disks( &self, ) -> &'a [GblDisk< Disk<impl BlockIo + 'a, impl DerefMut<Target = [u8]> + 'a>, Gpt<impl DerefMut<Target = [u8]> + 'a>, >]613 fn disks( 614 &self, 615 ) -> &'a [GblDisk< 616 Disk<impl BlockIo + 'a, impl DerefMut<Target = [u8]> + 'a>, 617 Gpt<impl DerefMut<Target = [u8]> + 'a>, 618 >] { 619 self.partitions 620 } 621 expected_os(&mut self) -> Result<Option<Os>, Error>622 fn expected_os(&mut self) -> Result<Option<Os>, Error> { 623 Ok(self.os) 624 } 625 zircon_add_device_zbi_items( &mut self, container: &mut ZbiContainer<&mut [u8]>, ) -> Result<(), Error>626 fn zircon_add_device_zbi_items( 627 &mut self, 628 container: &mut ZbiContainer<&mut [u8]>, 629 ) -> Result<(), Error> { 630 container 631 .create_entry_with_payload( 632 ZbiType::CmdLine, 633 0, 634 ZbiFlags::default(), 635 Self::ADDED_ZBI_COMMANDLINE_CONTENTS, 636 ) 637 .unwrap(); 638 Ok(()) 639 } 640 get_zbi_bootloader_files_buffer(&mut self) -> Option<&mut [u8]>641 fn get_zbi_bootloader_files_buffer(&mut self) -> Option<&mut [u8]> { 642 Some(self.zbi_bootloader_files_buffer.as_mut_slice()) 643 } 644 load_slot_interface<'b>( &'b mut self, _: &'b mut dyn FnMut(&mut [u8]) -> Result<(), Error>, _: slots::BootToken, ) -> GblResult<slots::Cursor<'b>>645 fn load_slot_interface<'b>( 646 &'b mut self, 647 _: &'b mut dyn FnMut(&mut [u8]) -> Result<(), Error>, 648 _: slots::BootToken, 649 ) -> GblResult<slots::Cursor<'b>> { 650 unimplemented!(); 651 } 652 avb_read_is_device_unlocked(&mut self) -> AvbIoResult<bool>653 fn avb_read_is_device_unlocked(&mut self) -> AvbIoResult<bool> { 654 self.avb_ops.read_is_device_unlocked() 655 } 656 avb_read_rollback_index(&mut self, rollback_index_location: usize) -> AvbIoResult<u64>657 fn avb_read_rollback_index(&mut self, rollback_index_location: usize) -> AvbIoResult<u64> { 658 self.avb_ops.read_rollback_index(rollback_index_location) 659 } 660 avb_write_rollback_index( &mut self, rollback_index_location: usize, index: u64, ) -> AvbIoResult<()>661 fn avb_write_rollback_index( 662 &mut self, 663 rollback_index_location: usize, 664 index: u64, 665 ) -> AvbIoResult<()> { 666 self.avb_ops.write_rollback_index(rollback_index_location, index) 667 } 668 avb_validate_vbmeta_public_key( &self, _public_key: &[u8], _public_key_metadata: Option<&[u8]>, ) -> AvbIoResult<KeyValidationStatus>669 fn avb_validate_vbmeta_public_key( 670 &self, 671 _public_key: &[u8], 672 _public_key_metadata: Option<&[u8]>, 673 ) -> AvbIoResult<KeyValidationStatus> { 674 self.avb_key_validation_status.clone().unwrap() 675 } 676 avb_cert_read_permanent_attributes( &mut self, attributes: &mut CertPermanentAttributes, ) -> AvbIoResult<()>677 fn avb_cert_read_permanent_attributes( 678 &mut self, 679 attributes: &mut CertPermanentAttributes, 680 ) -> AvbIoResult<()> { 681 self.avb_ops.read_permanent_attributes(attributes) 682 } 683 avb_cert_read_permanent_attributes_hash( &mut self, ) -> AvbIoResult<[u8; SHA256_DIGEST_SIZE]>684 fn avb_cert_read_permanent_attributes_hash( 685 &mut self, 686 ) -> AvbIoResult<[u8; SHA256_DIGEST_SIZE]> { 687 self.avb_ops.read_permanent_attributes_hash() 688 } 689 avb_read_persistent_value( &mut self, name: &CStr, value: &mut [u8], ) -> AvbIoResult<usize>690 fn avb_read_persistent_value( 691 &mut self, 692 name: &CStr, 693 value: &mut [u8], 694 ) -> AvbIoResult<usize> { 695 self.avb_ops.read_persistent_value(name, value) 696 } 697 avb_write_persistent_value(&mut self, name: &CStr, value: &[u8]) -> AvbIoResult<()>698 fn avb_write_persistent_value(&mut self, name: &CStr, value: &[u8]) -> AvbIoResult<()> { 699 self.avb_ops.write_persistent_value(name, value) 700 } 701 avb_erase_persistent_value(&mut self, name: &CStr) -> AvbIoResult<()>702 fn avb_erase_persistent_value(&mut self, name: &CStr) -> AvbIoResult<()> { 703 self.avb_ops.erase_persistent_value(name) 704 } 705 avb_handle_verification_result( &mut self, _color: BootStateColor, _digest: Option<&CStr>, _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<()>706 fn avb_handle_verification_result( 707 &mut self, 708 _color: BootStateColor, 709 _digest: Option<&CStr>, 710 _boot_os_version: Option<&[u8]>, 711 _boot_security_patch: Option<&[u8]>, 712 _system_os_version: Option<&[u8]>, 713 _system_security_patch: Option<&[u8]>, 714 _vendor_os_version: Option<&[u8]>, 715 _vendor_security_patch: Option<&[u8]>, 716 ) -> AvbIoResult<()> { 717 unimplemented!(); 718 } 719 get_image_buffer( &mut self, image_name: &str, _size: NonZeroUsize, ) -> GblResult<ImageBuffer<'d>>720 fn get_image_buffer( 721 &mut self, 722 image_name: &str, 723 _size: NonZeroUsize, 724 ) -> GblResult<ImageBuffer<'d>> { 725 if let Some(buf_list) = self.image_buffers.get_mut(image_name) { 726 if let Some(buf) = buf_list.pop_front() { 727 return Ok(buf); 728 }; 729 }; 730 731 gbl_println!(self, "FakeGblOps.get_image_buffer({image_name}) no buffer for the image"); 732 Err(IntegrationError::UnificationError(Error::Other(Some( 733 "No buffer provided. Add sufficient buffers to FakeGblOps.image_buffers", 734 )))) 735 } 736 get_custom_device_tree(&mut self) -> Option<&'static [u8]>737 fn get_custom_device_tree(&mut self) -> Option<&'static [u8]> { 738 None 739 } 740 fixup_os_commandline<'c>( &mut self, _commandline: &CStr, _fixup_buffer: &'c mut [u8], ) -> Result<Option<&'c str>, Error>741 fn fixup_os_commandline<'c>( 742 &mut self, 743 _commandline: &CStr, 744 _fixup_buffer: &'c mut [u8], 745 ) -> Result<Option<&'c str>, Error> { 746 unimplemented!(); 747 } 748 fixup_bootconfig<'c>( &mut self, _bootconfig: &[u8], _fixup_buffer: &'c mut [u8], ) -> Result<Option<&'c [u8]>, Error>749 fn fixup_bootconfig<'c>( 750 &mut self, 751 _bootconfig: &[u8], 752 _fixup_buffer: &'c mut [u8], 753 ) -> Result<Option<&'c [u8]>, Error> { 754 unimplemented!(); 755 } 756 fixup_device_tree(&mut self, _: &mut [u8]) -> Result<(), Error>757 fn fixup_device_tree(&mut self, _: &mut [u8]) -> Result<(), Error> { 758 unimplemented!(); 759 } 760 select_device_trees( &mut self, _: &mut device_tree::DeviceTreeComponentsRegistry, ) -> Result<(), Error>761 fn select_device_trees( 762 &mut self, 763 _: &mut device_tree::DeviceTreeComponentsRegistry, 764 ) -> Result<(), Error> { 765 unimplemented!(); 766 } 767 fastboot_variable<'arg>( &mut self, name: &CStr, mut args: impl Iterator<Item = &'arg CStr> + Clone, out: &mut [u8], ) -> Result<usize, Error>768 fn fastboot_variable<'arg>( 769 &mut self, 770 name: &CStr, 771 mut args: impl Iterator<Item = &'arg CStr> + Clone, 772 out: &mut [u8], 773 ) -> Result<usize, Error> { 774 match name.to_str()? { 775 Self::GBL_TEST_VAR => { 776 Ok(snprintf!(out, "{}:{:?}", Self::GBL_TEST_VAR_VAL, args.next()).len()) 777 } 778 _ => Err(Error::NotFound), 779 } 780 } 781 fastboot_visit_all_variables( &mut self, mut cb: impl FnMut(&[&CStr], &CStr), ) -> Result<(), Error>782 fn fastboot_visit_all_variables( 783 &mut self, 784 mut cb: impl FnMut(&[&CStr], &CStr), 785 ) -> Result<(), Error> { 786 cb( 787 &[CString::new(Self::GBL_TEST_VAR).unwrap().as_c_str(), c"1"], 788 CString::new(format!("{}:1", Self::GBL_TEST_VAR_VAL)).unwrap().as_c_str(), 789 ); 790 cb( 791 &[CString::new(Self::GBL_TEST_VAR).unwrap().as_c_str(), c"2"], 792 CString::new(format!("{}:2", Self::GBL_TEST_VAR_VAL)).unwrap().as_c_str(), 793 ); 794 Ok(()) 795 } 796 slots_metadata(&mut self) -> Result<SlotsMetadata, Error>797 fn slots_metadata(&mut self) -> Result<SlotsMetadata, Error> { 798 unimplemented!(); 799 } 800 get_current_slot(&mut self) -> Result<Slot, Error>801 fn get_current_slot(&mut self) -> Result<Slot, Error> { 802 unimplemented!() 803 } 804 get_next_slot(&mut self, _: bool) -> Result<Slot, Error>805 fn get_next_slot(&mut self, _: bool) -> Result<Slot, Error> { 806 unimplemented!() 807 } 808 set_active_slot(&mut self, _: u8) -> Result<(), Error>809 fn set_active_slot(&mut self, _: u8) -> Result<(), Error> { 810 unimplemented!() 811 } 812 set_reboot_reason(&mut self, _: RebootReason) -> Result<(), Error>813 fn set_reboot_reason(&mut self, _: RebootReason) -> Result<(), Error> { 814 unimplemented!() 815 } 816 get_reboot_reason(&mut self) -> Result<RebootReason, Error>817 fn get_reboot_reason(&mut self) -> Result<RebootReason, Error> { 818 unimplemented!() 819 } 820 } 821 822 #[test] test_fuchsia_reboot_bootloader()823 fn test_fuchsia_reboot_bootloader() { 824 let mut storage = FakeGblOpsStorage::default(); 825 storage.add_raw_device(c"durable_boot", [0x00u8; 4 * 1024]); 826 let mut gbl_ops = FakeGblOps::new(&storage); 827 gbl_ops.os = Some(Os::Fuchsia); 828 (gbl_ops.reboot_bootloader().unwrap())(); 829 assert!(gbl_ops.rebooted); 830 assert_eq!(get_and_clear_one_shot_bootloader(&mut GblAbrOps(&mut gbl_ops)), Ok(true)); 831 } 832 833 #[test] test_non_fuchsia_reboot_bootloader()834 fn test_non_fuchsia_reboot_bootloader() { 835 let mut storage = FakeGblOpsStorage::default(); 836 storage.add_raw_device(c"durable_boot", [0x00u8; 4 * 1024]); 837 let mut gbl_ops = FakeGblOps::new(&storage); 838 gbl_ops.os = Some(Os::Android); 839 assert!(gbl_ops.reboot_bootloader().is_err_and(|e| e == Error::Unsupported)); 840 assert_eq!(get_and_clear_one_shot_bootloader(&mut GblAbrOps(&mut gbl_ops)), Ok(false)); 841 } 842 843 #[test] test_fuchsia_reboot_recovery()844 fn test_fuchsia_reboot_recovery() { 845 let mut storage = FakeGblOpsStorage::default(); 846 storage.add_raw_device(c"durable_boot", [0x00u8; 4 * 1024]); 847 let mut gbl_ops = FakeGblOps::new(&storage); 848 gbl_ops.os = Some(Os::Fuchsia); 849 (gbl_ops.reboot_recovery().unwrap())(); 850 assert!(gbl_ops.rebooted); 851 // One shot recovery is set. 852 assert_eq!(get_boot_slot(&mut GblAbrOps(&mut gbl_ops), true), (SlotIndex::R, false)); 853 assert_eq!(get_boot_slot(&mut GblAbrOps(&mut gbl_ops), true), (SlotIndex::A, false)); 854 } 855 856 #[test] test_non_fuchsia_reboot_recovery()857 fn test_non_fuchsia_reboot_recovery() { 858 let mut storage = FakeGblOpsStorage::default(); 859 storage.add_raw_device(c"durable_boot", [0x00u8; 4 * 1024]); 860 let mut gbl_ops = FakeGblOps::new(&storage); 861 gbl_ops.os = Some(Os::Android); 862 assert!(gbl_ops.reboot_recovery().is_err_and(|e| e == Error::Unsupported)); 863 // One shot recovery is not set. 864 assert_eq!(get_boot_slot(&mut GblAbrOps(&mut gbl_ops), true), (SlotIndex::A, false)); 865 } 866 } 867