1 // Copyright 2024, 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 extern crate gbl_storage; 16 extern crate libgbl as gbl; 17 18 use core::convert::TryInto; 19 use gbl::slots::{ 20 BootTarget, BootToken, Manager, OneShot, RecoveryTarget, Slot, SlotIterator, Suffix, Tries, 21 UnbootableReason, 22 }; 23 use liberror::{Error, Result}; 24 25 use efi_types::{ 26 GBL_EFI_BOOT_REASON_BOOTLOADER as REASON_BOOTLOADER, 27 GBL_EFI_BOOT_REASON_EMPTY_BOOT_REASON as REASON_EMPTY, 28 GBL_EFI_BOOT_REASON_RECOVERY as REASON_RECOVERY, 29 }; 30 31 use crate::protocol::{gbl_efi_ab_slot as ab_slot, Protocol}; 32 33 const SUBREASON_BUF_LEN: usize = 64; 34 35 /// Implementation for A/B slot manager based on custom EFI protocol. 36 pub struct ABManager<'a> { 37 protocol: Protocol<'a, ab_slot::GblSlotProtocol>, 38 boot_token: Option<BootToken>, 39 last_set_active_idx: Option<u8>, 40 } 41 42 impl<'a> ABManager<'a> { 43 #[cfg(test)] new_without_token(protocol: Protocol<'a, ab_slot::GblSlotProtocol>) -> Self44 fn new_without_token(protocol: Protocol<'a, ab_slot::GblSlotProtocol>) -> Self { 45 Self { protocol, boot_token: None, last_set_active_idx: None } 46 } 47 } 48 49 impl gbl::slots::private::SlotGet for ABManager<'_> { get_slot_by_number(&self, number: usize) -> Result<Slot>50 fn get_slot_by_number(&self, number: usize) -> Result<Slot> { 51 let idx = u8::try_from(number).or(Err(Error::BadIndex(number)))?; 52 let info = self.protocol.get_slot_info(idx).or(Err(Error::BadIndex(number)))?; 53 info.try_into() 54 } 55 } 56 57 impl Manager for ABManager<'_> { get_boot_target(&self) -> Result<BootTarget>58 fn get_boot_target(&self) -> Result<BootTarget> { 59 let slot = self.get_slot_last_set_active()?; 60 let mut subreason = [0u8; SUBREASON_BUF_LEN]; 61 let (reason, _) = self.protocol.get_boot_reason(subreason.as_mut_slice())?; 62 // Don't currently care about the subreason 63 // CStr::from_bytes_until_nul(&subreason[..strlen])? 64 let target = match reason { 65 REASON_RECOVERY => BootTarget::Recovery(RecoveryTarget::Slotted(slot)), 66 _ => BootTarget::NormalBoot(slot), 67 }; 68 Ok(target) 69 } 70 slots_iter(&self) -> SlotIterator71 fn slots_iter(&self) -> SlotIterator { 72 SlotIterator::new(self) 73 } 74 get_slot_last_set_active(&self) -> Result<Slot>75 fn get_slot_last_set_active(&self) -> Result<Slot> { 76 use gbl::slots::private::SlotGet; 77 78 if let Some(idx) = self.last_set_active_idx { 79 self.get_slot_by_number(idx.into()) 80 } else { 81 self.protocol.get_current_slot()?.try_into() 82 } 83 } 84 mark_boot_attempt(&mut self) -> Result<BootToken>85 fn mark_boot_attempt(&mut self) -> Result<BootToken> { 86 self.boot_token.take().ok_or(Error::OperationProhibited) 87 } 88 set_active_slot(&mut self, slot_suffix: Suffix) -> Result<()>89 fn set_active_slot(&mut self, slot_suffix: Suffix) -> Result<()> { 90 let idx: u8 = self 91 .slots_iter() 92 .position(|s| s.suffix == slot_suffix) 93 .ok_or(Error::InvalidInput)? 94 .try_into() 95 // This 'or' is technically unreachable because the protocol 96 // can't give us an index larger than a u8. 97 .or(Err(Error::Other(None)))?; 98 self.protocol.set_active_slot(idx).or(Err(Error::Other(None))).and_then(|_| { 99 self.last_set_active_idx = Some(idx); 100 Ok(()) 101 }) 102 } 103 set_slot_unbootable(&mut self, slot_suffix: Suffix, reason: UnbootableReason) -> Result<()>104 fn set_slot_unbootable(&mut self, slot_suffix: Suffix, reason: UnbootableReason) -> Result<()> { 105 let idx: u8 = self 106 .slots_iter() 107 .position(|s| s.suffix == slot_suffix) 108 .ok_or(Error::InvalidInput)? 109 .try_into() 110 // This 'or' is technically unreachable because the protocol 111 // can't give us an index larger than a u8. 112 .or(Err(Error::Other(None)))?; 113 self.protocol.set_slot_unbootable(idx, u8::from(reason).into()) 114 } 115 get_max_retries(&self) -> Result<Tries>116 fn get_max_retries(&self) -> Result<Tries> { 117 Ok(self.protocol.load_boot_data()?.max_retries.into()) 118 } 119 get_oneshot_status(&self) -> Option<OneShot>120 fn get_oneshot_status(&self) -> Option<OneShot> { 121 let mut subreason = [0u8; SUBREASON_BUF_LEN]; 122 let (reason, _) = self.protocol.get_boot_reason(subreason.as_mut_slice()).ok()?; 123 // Currently we only care if the primary boot reason is BOOTLOADER. 124 // CStr::from_bytes_until_nul(&subreason[..strlen]).ok()? 125 match reason { 126 REASON_BOOTLOADER => Some(OneShot::Bootloader), 127 _ => None, 128 } 129 } 130 set_oneshot_status(&mut self, os: OneShot) -> Result<()>131 fn set_oneshot_status(&mut self, os: OneShot) -> Result<()> { 132 // Android doesn't have a concept of OneShot to recovery, 133 // and the subreason shouldn't matter. 134 match os { 135 OneShot::Bootloader => { 136 self.protocol.set_boot_reason(REASON_BOOTLOADER, &[]).or(Err(Error::Other(None))) 137 } 138 _ => Err(Error::OperationProhibited), 139 } 140 } 141 clear_oneshot_status(&mut self)142 fn clear_oneshot_status(&mut self) { 143 let mut subreason = [0u8; SUBREASON_BUF_LEN]; 144 // Only clear if the boot reason is the one we care about. 145 // CStr::from_bytes_until_nul(&subreason[..strlen]).or(Err(Error::Other))? 146 if let Ok((REASON_BOOTLOADER, _)) = self.protocol.get_boot_reason(subreason.as_mut_slice()) 147 { 148 let _ = self.protocol.set_boot_reason(REASON_EMPTY, &[]); 149 } 150 } 151 write_back(&mut self, _: &mut dyn FnMut(&mut [u8]) -> Result<()>)152 fn write_back(&mut self, _: &mut dyn FnMut(&mut [u8]) -> Result<()>) { 153 // Note: `expect` instead of swallowing the error. 154 // It is important that changes are not silently dropped. 155 self.protocol.flush().expect("could not write back modifications to slot metadata"); 156 } 157 } 158 159 #[cfg(test)] 160 mod test { 161 extern crate avb_sysdeps; 162 163 use super::*; 164 use crate::protocol::Protocol; 165 use crate::test::*; 166 use crate::EfiEntry; 167 use efi_types::{ 168 EfiStatus, GblEfiABSlotProtocol, GblEfiSlotInfo, GblEfiSlotMetadataBlock, 169 EFI_STATUS_INVALID_PARAMETER, EFI_STATUS_SUCCESS, 170 GBL_EFI_BOOT_REASON_EMPTY_BOOT_REASON as REASON_EMPTY, 171 GBL_EFI_BOOT_REASON_RECOVERY as REASON_RECOVERY, 172 GBL_EFI_BOOT_REASON_WATCHDOG as REASON_WATCHDOG, 173 }; 174 use gbl::{ 175 ops::{ 176 AvbIoResult, CertPermanentAttributes, RebootReason, SlotsMetadata, SHA256_DIGEST_SIZE, 177 }, 178 partition::GblDisk, 179 slots::{Bootability, Cursor, RecoveryTarget, UnbootableReason}, 180 Gbl, GblOps, Os, Result as GblResult, 181 }; 182 use gbl_storage::{BlockIo, BlockIoNull, Disk, Gpt}; 183 use libgbl::{ 184 device_tree::DeviceTreeComponentsRegistry, 185 gbl_avb::state::{BootStateColor, KeyValidationStatus}, 186 ops::ImageBuffer, 187 }; 188 // TODO(b/350526796): use ptr.is_aligned() when Rust 1.79 is in Android 189 use core::ops::DerefMut; 190 use std::{ 191 ffi::CStr, 192 fmt::Write, 193 mem::align_of, 194 num::NonZeroUsize, 195 sync::atomic::{AtomicBool, AtomicU32, Ordering}, 196 }; 197 use zbi::ZbiContainer; 198 199 // The thread-local atomics are an ugly, ugly hack to pass state between 200 // the protocol method functions and the rest of the test body. 201 // Because the variables are thread-local, it is safe to run tests concurrently 202 // so long as they establish correct initial values. 203 // Also, because no atomic is being read or written to by more than one thread, 204 // Ordering::Relaxed is perfectly fine. 205 thread_local! { 206 static ATOMIC: AtomicBool = AtomicBool::new(false); 207 } 208 209 thread_local! { 210 static BOOT_REASON: AtomicU32 = AtomicU32::new(REASON_EMPTY); 211 } 212 213 // This provides reasonable defaults for all tests that need to get slot info. 214 // 215 // SAFETY: checks that `info` is properly aligned and not null. 216 // Caller must make sure `info` points to a valid GblEfiSlotInfo struct. get_info( _: *mut GblEfiABSlotProtocol, idx: u8, info: *mut GblEfiSlotInfo, ) -> EfiStatus217 unsafe extern "C" fn get_info( 218 _: *mut GblEfiABSlotProtocol, 219 idx: u8, 220 info: *mut GblEfiSlotInfo, 221 ) -> EfiStatus { 222 // TODO(b/350526796): use ptr.is_aligned() when Rust 1.79 is in Android 223 if !info.is_null() && (info as usize) % align_of::<GblEfiSlotInfo>() == 0 && idx < 3 { 224 let slot_info = GblEfiSlotInfo { 225 suffix: ('a' as u8 + idx).into(), 226 unbootable_reason: 0, 227 priority: idx + 1, 228 tries: idx, 229 successful: 2 & idx, 230 }; 231 unsafe { *info = slot_info }; 232 EFI_STATUS_SUCCESS 233 } else { 234 EFI_STATUS_INVALID_PARAMETER 235 } 236 } 237 flush(_: *mut GblEfiABSlotProtocol) -> EfiStatus238 extern "C" fn flush(_: *mut GblEfiABSlotProtocol) -> EfiStatus { 239 ATOMIC.with(|a| a.store(true, Ordering::Relaxed)); 240 EFI_STATUS_SUCCESS 241 } 242 243 struct TestGblOps<'a> { 244 manager: ABManager<'a>, 245 } 246 247 impl<'a> TestGblOps<'a> { new(protocol: Protocol<'a, ab_slot::GblSlotProtocol>) -> Self248 fn new(protocol: Protocol<'a, ab_slot::GblSlotProtocol>) -> Self { 249 Self { manager: ABManager::new_without_token(protocol) } 250 } 251 } 252 253 impl<'a, 'd> GblOps<'a, 'd> for TestGblOps<'_> { console_out(&mut self) -> Option<&mut dyn Write>254 fn console_out(&mut self) -> Option<&mut dyn Write> { 255 unimplemented!(); 256 } 257 should_stop_in_fastboot(&mut self) -> Result<bool>258 fn should_stop_in_fastboot(&mut self) -> Result<bool> { 259 unimplemented!(); 260 } 261 reboot(&mut self)262 fn reboot(&mut self) { 263 unimplemented!(); 264 } 265 disks( &self, ) -> &'a [GblDisk< Disk<impl BlockIo + 'a, impl DerefMut<Target = [u8]> + 'a>, Gpt<impl DerefMut<Target = [u8]> + 'a>, >]266 fn disks( 267 &self, 268 ) -> &'a [GblDisk< 269 Disk<impl BlockIo + 'a, impl DerefMut<Target = [u8]> + 'a>, 270 Gpt<impl DerefMut<Target = [u8]> + 'a>, 271 >] { 272 &[] as &[GblDisk<Disk<BlockIoNull, &mut [u8]>, Gpt<&mut [u8]>>] 273 } 274 expected_os(&mut self) -> Result<Option<Os>>275 fn expected_os(&mut self) -> Result<Option<Os>> { 276 Ok(None) 277 } 278 zircon_add_device_zbi_items(&mut self, _: &mut ZbiContainer<&mut [u8]>) -> Result<()>279 fn zircon_add_device_zbi_items(&mut self, _: &mut ZbiContainer<&mut [u8]>) -> Result<()> { 280 unimplemented!(); 281 } 282 get_zbi_bootloader_files_buffer(&mut self) -> Option<&mut [u8]>283 fn get_zbi_bootloader_files_buffer(&mut self) -> Option<&mut [u8]> { 284 None 285 } 286 load_slot_interface<'b>( &'b mut self, persist: &'b mut dyn FnMut(&mut [u8]) -> Result<()>, boot_token: BootToken, ) -> GblResult<Cursor<'b>>287 fn load_slot_interface<'b>( 288 &'b mut self, 289 persist: &'b mut dyn FnMut(&mut [u8]) -> Result<()>, 290 boot_token: BootToken, 291 ) -> GblResult<Cursor<'b>> { 292 self.manager.boot_token = Some(boot_token); 293 Ok(Cursor { ctx: &mut self.manager, persist }) 294 } 295 avb_read_is_device_unlocked(&mut self) -> AvbIoResult<bool>296 fn avb_read_is_device_unlocked(&mut self) -> AvbIoResult<bool> { 297 unimplemented!(); 298 } 299 avb_read_rollback_index(&mut self, _rollback_index_location: usize) -> AvbIoResult<u64>300 fn avb_read_rollback_index(&mut self, _rollback_index_location: usize) -> AvbIoResult<u64> { 301 unimplemented!(); 302 } 303 avb_write_rollback_index( &mut self, _rollback_index_location: usize, _index: u64, ) -> AvbIoResult<()>304 fn avb_write_rollback_index( 305 &mut self, 306 _rollback_index_location: usize, 307 _index: u64, 308 ) -> AvbIoResult<()> { 309 unimplemented!(); 310 } 311 avb_validate_vbmeta_public_key( &self, _public_key: &[u8], _public_key_metadata: Option<&[u8]>, ) -> AvbIoResult<KeyValidationStatus>312 fn avb_validate_vbmeta_public_key( 313 &self, 314 _public_key: &[u8], 315 _public_key_metadata: Option<&[u8]>, 316 ) -> AvbIoResult<KeyValidationStatus> { 317 unimplemented!(); 318 } 319 avb_cert_read_permanent_attributes( &mut self, _attributes: &mut CertPermanentAttributes, ) -> AvbIoResult<()>320 fn avb_cert_read_permanent_attributes( 321 &mut self, 322 _attributes: &mut CertPermanentAttributes, 323 ) -> AvbIoResult<()> { 324 unimplemented!(); 325 } 326 avb_cert_read_permanent_attributes_hash( &mut self, ) -> AvbIoResult<[u8; SHA256_DIGEST_SIZE]>327 fn avb_cert_read_permanent_attributes_hash( 328 &mut self, 329 ) -> AvbIoResult<[u8; SHA256_DIGEST_SIZE]> { 330 unimplemented!(); 331 } 332 avb_read_persistent_value( &mut self, _name: &CStr, _value: &mut [u8], ) -> AvbIoResult<usize>333 fn avb_read_persistent_value( 334 &mut self, 335 _name: &CStr, 336 _value: &mut [u8], 337 ) -> AvbIoResult<usize> { 338 unimplemented!(); 339 } 340 avb_write_persistent_value(&mut self, _name: &CStr, _value: &[u8]) -> AvbIoResult<()>341 fn avb_write_persistent_value(&mut self, _name: &CStr, _value: &[u8]) -> AvbIoResult<()> { 342 unimplemented!(); 343 } 344 avb_erase_persistent_value(&mut self, _name: &CStr) -> AvbIoResult<()>345 fn avb_erase_persistent_value(&mut self, _name: &CStr) -> AvbIoResult<()> { 346 unimplemented!(); 347 } 348 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<()>349 fn avb_handle_verification_result( 350 &mut self, 351 _color: BootStateColor, 352 _digest: Option<&CStr>, 353 _boot_os_version: Option<&[u8]>, 354 _boot_security_patch: Option<&[u8]>, 355 _system_os_version: Option<&[u8]>, 356 _system_security_patch: Option<&[u8]>, 357 _vendor_os_version: Option<&[u8]>, 358 _vendor_security_patch: Option<&[u8]>, 359 ) -> AvbIoResult<()> { 360 unimplemented!(); 361 } 362 get_image_buffer( &mut self, _image_name: &str, _size: NonZeroUsize, ) -> GblResult<ImageBuffer<'d>>363 fn get_image_buffer( 364 &mut self, 365 _image_name: &str, 366 _size: NonZeroUsize, 367 ) -> GblResult<ImageBuffer<'d>> { 368 unimplemented!(); 369 } 370 get_custom_device_tree(&mut self) -> Option<&'a [u8]>371 fn get_custom_device_tree(&mut self) -> Option<&'a [u8]> { 372 unimplemented!(); 373 } 374 fixup_os_commandline<'c>( &mut self, _commandline: &CStr, _fixup_buffer: &'c mut [u8], ) -> Result<Option<&'c str>>375 fn fixup_os_commandline<'c>( 376 &mut self, 377 _commandline: &CStr, 378 _fixup_buffer: &'c mut [u8], 379 ) -> Result<Option<&'c str>> { 380 unimplemented!(); 381 } 382 fixup_bootconfig<'c>( &mut self, _bootconfig: &[u8], _fixup_buffer: &'c mut [u8], ) -> Result<Option<&'c [u8]>>383 fn fixup_bootconfig<'c>( 384 &mut self, 385 _bootconfig: &[u8], 386 _fixup_buffer: &'c mut [u8], 387 ) -> Result<Option<&'c [u8]>> { 388 unimplemented!(); 389 } 390 fixup_device_tree(&mut self, _device_tree: &mut [u8]) -> Result<()>391 fn fixup_device_tree(&mut self, _device_tree: &mut [u8]) -> Result<()> { 392 unimplemented!(); 393 } 394 select_device_trees( &mut self, _components: &mut DeviceTreeComponentsRegistry, ) -> Result<()>395 fn select_device_trees( 396 &mut self, 397 _components: &mut DeviceTreeComponentsRegistry, 398 ) -> Result<()> { 399 unimplemented!(); 400 } 401 fastboot_variable<'arg>( &mut self, _: &CStr, _: impl Iterator<Item = &'arg CStr> + Clone, _: &mut [u8], ) -> Result<usize>402 fn fastboot_variable<'arg>( 403 &mut self, 404 _: &CStr, 405 _: impl Iterator<Item = &'arg CStr> + Clone, 406 _: &mut [u8], 407 ) -> Result<usize> { 408 unimplemented!() 409 } 410 fastboot_visit_all_variables(&mut self, _: impl FnMut(&[&CStr], &CStr)) -> Result<()>411 fn fastboot_visit_all_variables(&mut self, _: impl FnMut(&[&CStr], &CStr)) -> Result<()> { 412 unimplemented!() 413 } 414 slots_metadata(&mut self) -> Result<SlotsMetadata>415 fn slots_metadata(&mut self) -> Result<SlotsMetadata> { 416 unimplemented!(); 417 } 418 get_current_slot(&mut self) -> Result<Slot>419 fn get_current_slot(&mut self) -> Result<Slot> { 420 unimplemented!() 421 } 422 get_next_slot(&mut self, _: bool) -> Result<Slot>423 fn get_next_slot(&mut self, _: bool) -> Result<Slot> { 424 unimplemented!() 425 } 426 set_active_slot(&mut self, _: u8) -> Result<()>427 fn set_active_slot(&mut self, _: u8) -> Result<()> { 428 unimplemented!() 429 } 430 set_reboot_reason(&mut self, _: RebootReason) -> Result<()>431 fn set_reboot_reason(&mut self, _: RebootReason) -> Result<()> { 432 unimplemented!() 433 } 434 get_reboot_reason(&mut self) -> Result<RebootReason>435 fn get_reboot_reason(&mut self) -> Result<RebootReason> { 436 unimplemented!() 437 } 438 } 439 440 #[test] test_manager_flush_on_close()441 fn test_manager_flush_on_close() { 442 ATOMIC.with(|a| a.store(false, Ordering::Relaxed)); 443 run_test(|image_handle, systab_ptr| { 444 let mut ab = GblEfiABSlotProtocol { flush: Some(flush), ..Default::default() }; 445 let efi_entry = EfiEntry { image_handle, systab_ptr }; 446 let protocol = generate_protocol::<ab_slot::GblSlotProtocol>(&efi_entry, &mut ab); 447 448 { 449 let mut persist = |_: &mut [u8]| Ok(()); 450 let mut test_ops = TestGblOps::new(protocol); 451 let mut gbl = Gbl::<TestGblOps>::new(&mut test_ops); 452 let _ = gbl.load_slot_interface(&mut persist).unwrap(); 453 } 454 }); 455 assert!(ATOMIC.with(|a| a.load(Ordering::Relaxed))); 456 } 457 458 #[test] test_iterator()459 fn test_iterator() { 460 run_test(|image_handle, systab_ptr| { 461 let mut ab = GblEfiABSlotProtocol { 462 get_slot_info: Some(get_info), 463 flush: Some(flush), 464 ..Default::default() 465 }; 466 let efi_entry = EfiEntry { image_handle, systab_ptr }; 467 let protocol = generate_protocol::<ab_slot::GblSlotProtocol>(&efi_entry, &mut ab); 468 let mut persist = |_: &mut [u8]| Ok(()); 469 let mut test_ops = TestGblOps::new(protocol); 470 let mut gbl = Gbl::<TestGblOps>::new(&mut test_ops); 471 let cursor = gbl.load_slot_interface(&mut persist).unwrap(); 472 473 let slots: Vec<Slot> = cursor.ctx.slots_iter().collect(); 474 assert_eq!( 475 slots, 476 vec![ 477 Slot { 478 suffix: 'a'.into(), 479 priority: 1usize.into(), 480 bootability: Bootability::Unbootable(UnbootableReason::Unknown), 481 }, 482 Slot { 483 suffix: 'b'.into(), 484 priority: 2usize.into(), 485 bootability: Bootability::Retriable(1usize.into()), 486 }, 487 Slot { 488 suffix: 'c'.into(), 489 priority: 3usize.into(), 490 bootability: Bootability::Successful, 491 } 492 ] 493 ) 494 }); 495 } 496 497 #[test] test_active_slot()498 fn test_active_slot() { 499 // SAFETY: verfies that `info` properly aligned and not null. 500 // It is the callers responsibility to make sure 501 // that `info` points to a valid GblEfiSlotInfo. 502 unsafe extern "C" fn get_current_slot( 503 _: *mut GblEfiABSlotProtocol, 504 info: *mut GblEfiSlotInfo, 505 ) -> EfiStatus { 506 // TODO(b/350526796): use ptr.is_aligned() when Rust 1.79 is in Android 507 if info.is_null() || (info as usize) % align_of::<GblEfiSlotInfo>() != 0 { 508 return EFI_STATUS_INVALID_PARAMETER; 509 } 510 let slot_info = GblEfiSlotInfo { 511 suffix: 'a' as u32, 512 unbootable_reason: 0, 513 priority: 7, 514 tries: 15, 515 successful: 1, 516 }; 517 518 unsafe { *info = slot_info }; 519 EFI_STATUS_SUCCESS 520 } 521 522 // SAFETY: verifies that `reason` and `subreason_size` are aligned and not null. 523 // It is the caller's responsibility to make sure that `reason` 524 // and `subreason_size` point to valid output parameters. 525 unsafe extern "C" fn get_boot_reason( 526 _: *mut GblEfiABSlotProtocol, 527 reason: *mut u32, 528 subreason_size: *mut usize, 529 _subreason: *mut u8, 530 ) -> EfiStatus { 531 if reason.is_null() 532 || subreason_size.is_null() 533 // TODO(b/350526796): use ptr.is_aligned() when Rust 1.79 is in Android 534 || (reason as usize) % align_of::<u32>() != 0 535 || (subreason_size as usize) % align_of::<usize>() != 0 536 { 537 return EFI_STATUS_INVALID_PARAMETER; 538 } 539 540 unsafe { 541 *reason = BOOT_REASON.with(|r| r.load(Ordering::Relaxed)); 542 *subreason_size = 0; 543 } 544 EFI_STATUS_SUCCESS 545 } 546 547 BOOT_REASON.with(|r| r.store(REASON_EMPTY, Ordering::Relaxed)); 548 run_test(|image_handle, systab_ptr| { 549 let mut ab = GblEfiABSlotProtocol { 550 get_current_slot: Some(get_current_slot), 551 get_boot_reason: Some(get_boot_reason), 552 flush: Some(flush), 553 ..Default::default() 554 }; 555 let efi_entry = EfiEntry { image_handle, systab_ptr }; 556 let protocol = generate_protocol::<ab_slot::GblSlotProtocol>(&efi_entry, &mut ab); 557 let mut persist = |_: &mut [u8]| Ok(()); 558 let mut test_ops = TestGblOps::new(protocol); 559 let mut gbl = Gbl::<TestGblOps>::new(&mut test_ops); 560 let cursor = gbl.load_slot_interface(&mut persist).unwrap(); 561 562 let slot = Slot { 563 suffix: 'a'.into(), 564 priority: 7usize.into(), 565 bootability: Bootability::Successful, 566 }; 567 assert_eq!(cursor.ctx.get_boot_target().unwrap(), BootTarget::NormalBoot(slot)); 568 assert_eq!(cursor.ctx.get_slot_last_set_active().unwrap(), slot); 569 570 BOOT_REASON.with(|r| r.store(REASON_RECOVERY, Ordering::Relaxed)); 571 572 assert_eq!( 573 cursor.ctx.get_boot_target().unwrap(), 574 BootTarget::Recovery(RecoveryTarget::Slotted(slot)) 575 ); 576 }); 577 } 578 579 #[test] test_mark_boot_attempt()580 fn test_mark_boot_attempt() { 581 run_test(|image_handle, systab_ptr| { 582 let mut ab = GblEfiABSlotProtocol { flush: Some(flush), ..Default::default() }; 583 let efi_entry = EfiEntry { image_handle, systab_ptr }; 584 let protocol = generate_protocol::<ab_slot::GblSlotProtocol>(&efi_entry, &mut ab); 585 let mut persist = |_: &mut [u8]| Ok(()); 586 let mut test_ops = TestGblOps::new(protocol); 587 let mut gbl = Gbl::<TestGblOps>::new(&mut test_ops); 588 let cursor = gbl.load_slot_interface(&mut persist).unwrap(); 589 assert!(cursor.ctx.mark_boot_attempt().is_ok()); 590 591 assert_eq!(cursor.ctx.mark_boot_attempt(), Err(Error::OperationProhibited)); 592 }); 593 } 594 595 #[test] test_get_max_retries()596 fn test_get_max_retries() { 597 // SAFETY: verifies that `meta` is properly aligned and not null. 598 // It is the caller's responsibility to make sure that `meta` points to 599 // a valid GblEfiSlotMetadataBlock. 600 unsafe extern "C" fn load_boot_data( 601 _: *mut GblEfiABSlotProtocol, 602 meta: *mut GblEfiSlotMetadataBlock, 603 ) -> EfiStatus { 604 // TODO(b/350526796): use ptr.is_aligned() when Rust 1.79 is in Android 605 if meta.is_null() || (meta as usize) % align_of::<GblEfiSlotMetadataBlock>() != 0 { 606 return EFI_STATUS_INVALID_PARAMETER; 607 } 608 609 let meta_block = GblEfiSlotMetadataBlock { 610 unbootable_metadata: 1, 611 max_retries: 66, 612 slot_count: 32, // why not? 613 merge_status: 0, 614 }; 615 616 unsafe { *meta = meta_block }; 617 EFI_STATUS_SUCCESS 618 } 619 620 run_test(|image_handle, systab_ptr| { 621 let mut ab = GblEfiABSlotProtocol { 622 load_boot_data: Some(load_boot_data), 623 flush: Some(flush), 624 ..Default::default() 625 }; 626 let efi_entry = EfiEntry { image_handle, systab_ptr }; 627 let protocol = generate_protocol::<ab_slot::GblSlotProtocol>(&efi_entry, &mut ab); 628 let mut persist = |_: &mut [u8]| Ok(()); 629 let mut test_ops = TestGblOps::new(protocol); 630 let mut gbl = Gbl::<TestGblOps>::new(&mut test_ops); 631 let cursor = gbl.load_slot_interface(&mut persist).unwrap(); 632 assert_eq!(cursor.ctx.get_max_retries().unwrap(), 66usize.into()); 633 }); 634 } 635 636 #[test] test_set_active_slot()637 fn test_set_active_slot() { 638 extern "C" fn set_active_slot(_: *mut GblEfiABSlotProtocol, idx: u8) -> EfiStatus { 639 // This is deliberate: we want to make sure that other logic catches 640 // 'no such slot' first but we also want to verify that errors propagate. 641 if idx != 2 { 642 EFI_STATUS_SUCCESS 643 } else { 644 EFI_STATUS_INVALID_PARAMETER 645 } 646 } 647 648 run_test(|image_handle, systab_ptr| { 649 let mut ab = GblEfiABSlotProtocol { 650 get_slot_info: Some(get_info), 651 set_active_slot: Some(set_active_slot), 652 flush: Some(flush), 653 ..Default::default() 654 }; 655 let efi_entry = EfiEntry { image_handle, systab_ptr }; 656 let protocol = generate_protocol::<ab_slot::GblSlotProtocol>(&efi_entry, &mut ab); 657 let mut persist = |_: &mut [u8]| Ok(()); 658 let mut test_ops = TestGblOps::new(protocol); 659 let mut gbl = Gbl::<TestGblOps>::new(&mut test_ops); 660 let cursor = gbl.load_slot_interface(&mut persist).unwrap(); 661 662 assert_eq!(cursor.ctx.set_active_slot('b'.into()), Ok(())); 663 assert_eq!(cursor.ctx.set_active_slot('c'.into()), Err(Error::Other(None))); 664 665 let bad_suffix = '$'.into(); 666 assert_eq!(cursor.ctx.set_active_slot(bad_suffix), Err(Error::InvalidInput)); 667 }); 668 } 669 670 #[test] test_set_slot_unbootable()671 fn test_set_slot_unbootable() { 672 extern "C" fn set_slot_unbootable( 673 _: *mut GblEfiABSlotProtocol, 674 idx: u8, 675 _: u32, 676 ) -> EfiStatus { 677 // Same thing here as with set_active_slot. 678 // We want to make sure that iteration over the slots 679 // catches invalid suffixes, but we also want to make sure 680 // that errors from the protocol percolate up. 681 if idx == 0 { 682 EFI_STATUS_SUCCESS 683 } else { 684 EFI_STATUS_INVALID_PARAMETER 685 } 686 } 687 688 run_test(|image_handle, systab_ptr| { 689 let mut ab = GblEfiABSlotProtocol { 690 get_slot_info: Some(get_info), 691 set_slot_unbootable: Some(set_slot_unbootable), 692 flush: Some(flush), 693 ..Default::default() 694 }; 695 let efi_entry = EfiEntry { image_handle, systab_ptr }; 696 let protocol = generate_protocol::<ab_slot::GblSlotProtocol>(&efi_entry, &mut ab); 697 let mut persist = |_: &mut [u8]| Ok(()); 698 let mut test_ops = TestGblOps::new(protocol); 699 let mut gbl = Gbl::<TestGblOps>::new(&mut test_ops); 700 let cursor = gbl.load_slot_interface(&mut persist).unwrap(); 701 702 assert_eq!( 703 cursor.ctx.set_slot_unbootable('a'.into(), UnbootableReason::SystemUpdate), 704 Ok(()) 705 ); 706 707 assert_eq!( 708 cursor.ctx.set_slot_unbootable('b'.into(), UnbootableReason::UserRequested), 709 Err(Error::InvalidInput) 710 ); 711 }); 712 } 713 714 #[test] test_oneshot()715 fn test_oneshot() { 716 // SAFETY: checks that `reason` is not null and is properly aligned. 717 // Caller must make sure reason points to a valid u32. 718 unsafe extern "C" fn get_boot_reason( 719 _: *mut GblEfiABSlotProtocol, 720 reason: *mut u32, 721 _: *mut usize, 722 _: *mut u8, 723 ) -> EfiStatus { 724 // TODO(b/350526796): use ptr.is_aligned() when Rust 1.79 is in Android 725 if reason.is_null() || (reason as usize) % align_of::<u32>() != 0 { 726 return EFI_STATUS_INVALID_PARAMETER; 727 } 728 729 unsafe { *reason = BOOT_REASON.with(|r| r.load(Ordering::Relaxed)) }; 730 731 EFI_STATUS_SUCCESS 732 } 733 734 extern "C" fn set_boot_reason( 735 _: *mut GblEfiABSlotProtocol, 736 reason: u32, 737 _: usize, 738 _: *const u8, 739 ) -> EfiStatus { 740 BOOT_REASON.with(|r| r.store(reason, Ordering::Relaxed)); 741 EFI_STATUS_SUCCESS 742 } 743 744 BOOT_REASON.with(|r| r.store(REASON_EMPTY, Ordering::Relaxed)); 745 run_test(|image_handle, systab_ptr| { 746 let mut ab = GblEfiABSlotProtocol { 747 get_boot_reason: Some(get_boot_reason), 748 set_boot_reason: Some(set_boot_reason), 749 flush: Some(flush), 750 ..Default::default() 751 }; 752 let efi_entry = EfiEntry { image_handle, systab_ptr }; 753 let protocol = generate_protocol::<ab_slot::GblSlotProtocol>(&efi_entry, &mut ab); 754 let mut persist = |_: &mut [u8]| Ok(()); 755 let mut test_ops = TestGblOps::new(protocol); 756 let mut gbl = Gbl::<TestGblOps>::new(&mut test_ops); 757 let cursor = gbl.load_slot_interface(&mut persist).unwrap(); 758 759 assert_eq!(cursor.ctx.get_oneshot_status(), None); 760 assert_eq!( 761 cursor.ctx.set_oneshot_status(OneShot::Continue(RecoveryTarget::Dedicated)), 762 Err(Error::OperationProhibited) 763 ); 764 assert_eq!(cursor.ctx.set_oneshot_status(OneShot::Bootloader), Ok(())); 765 assert_eq!(cursor.ctx.get_oneshot_status(), Some(OneShot::Bootloader)); 766 767 cursor.ctx.clear_oneshot_status(); 768 assert_eq!(cursor.ctx.get_oneshot_status(), None); 769 770 BOOT_REASON.with(|r| r.store(REASON_WATCHDOG, Ordering::Relaxed)); 771 assert_eq!(cursor.ctx.get_oneshot_status(), None); 772 cursor.ctx.clear_oneshot_status(); 773 assert_eq!(BOOT_REASON.with(|r| r.load(Ordering::Relaxed)), REASON_WATCHDOG); 774 }); 775 } 776 } 777