xref: /aosp_15_r20/bootable/libbootloader/gbl/libefi/src/ab_slots.rs (revision 5225e6b173e52d2efc6bcf950c27374fd72adabc)
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