xref: /aosp_15_r20/bootable/libbootloader/gbl/libgbl/src/slots.rs (revision 5225e6b173e52d2efc6bcf950c27374fd72adabc)
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 /// Export the default implementation
16 pub mod fuchsia;
17 
18 /// Reference Android implementation
19 pub mod android;
20 
21 /// Generic functionality for partition backed ABR schemes
22 pub mod partition;
23 
24 use core::mem::size_of;
25 use liberror::Error;
26 
27 /// A type safe container for describing the number of retries a slot has left
28 /// before it becomes unbootable.
29 /// Slot tries can only be compared to, assigned to, or assigned from other
30 /// tries.
31 #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
32 pub struct Tries(usize);
33 
34 impl From<usize> for Tries {
from(u: usize) -> Self35     fn from(u: usize) -> Self {
36         Self(u)
37     }
38 }
39 impl From<u8> for Tries {
from(u: u8) -> Self40     fn from(u: u8) -> Self {
41         Self(u.into())
42     }
43 }
44 
45 /// A type safe container for describing the priority of a slot.
46 /// Slot priorities can only be compared to, assigned to, or assigned from
47 /// other priorities.
48 #[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
49 pub struct Priority(usize);
50 
51 impl From<usize> for Priority {
from(u: usize) -> Self52     fn from(u: usize) -> Self {
53         Self(u)
54     }
55 }
56 impl From<u8> for Priority {
from(u: u8) -> Self57     fn from(u: u8) -> Self {
58         Self(u.into())
59     }
60 }
61 
62 /// A type safe container for describing a slot's suffix.
63 #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
64 pub struct Suffix(pub(crate) char);
65 
66 impl Suffix {
67     // We want lexigraphically lower suffixes
68     // to have higher priority.
69     // A cheater way to do this is to compare
70     // their negative values.
71     // A char is 4 bytes, and a signed 64 bit int
72     // can comfortably contain the negative of a
73     // number represented by an unsigned 32 bit int.
rank(&self) -> i6474     fn rank(&self) -> i64 {
75         -i64::from(u32::from(self.0))
76     }
77 }
78 
79 impl From<char> for Suffix {
from(c: char) -> Self80     fn from(c: char) -> Self {
81         Self(c)
82     }
83 }
84 
85 impl TryFrom<usize> for Suffix {
86     type Error = Error;
87 
try_from(value: usize) -> Result<Self, Self::Error>88     fn try_from(value: usize) -> Result<Self, Self::Error> {
89         u32::try_from(value).ok().and_then(char::from_u32).ok_or(Error::InvalidInput).map(Self)
90     }
91 }
92 
93 impl TryFrom<u32> for Suffix {
94     type Error = Error;
95 
try_from(value: u32) -> Result<Self, Self::Error>96     fn try_from(value: u32) -> Result<Self, Self::Error> {
97         char::from_u32(value).ok_or(Error::InvalidInput).map(Self)
98     }
99 }
100 
101 // Includes a null terminator
102 const SUFFIX_CSTR_MAX_BYTES: usize = size_of::<Suffix>() + 1;
103 
104 /// A buffer large enough to contain the serialized representation of a Suffix.
105 /// Can be turned into a &Cstr like so:
106 ///
107 /// let suffix: Suffix = 'a'.into();
108 /// let buffer: SuffixBytes = suffix.into();
109 /// let cstr = CStr::from_bytes_until_nul(&buffer)?;
110 pub type SuffixBytes = [u8; SUFFIX_CSTR_MAX_BYTES];
111 
112 impl From<Suffix> for SuffixBytes {
from(val: Suffix) -> Self113     fn from(val: Suffix) -> Self {
114         let mut buffer: Self = Default::default();
115         let _ = val.0.encode_utf8(&mut buffer);
116         buffer
117     }
118 }
119 
120 /// Slot metadata describing why that slot is unbootable.
121 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
122 pub enum UnbootableReason {
123     /// No information is given about why this slot is not bootable.
124     Unknown,
125     /// This slot has exhausted its retry budget and cannot be booted.
126     NoMoreTries,
127     /// As part of a system update, the update agent downloads
128     /// an updated image and stores it into a slot other than the current
129     /// active slot.
130     SystemUpdate,
131     /// This slot has been marked unbootable by user request,
132     /// usually as part of a system test.
133     UserRequested,
134     /// This slot has failed a verification check as part of
135     /// Android Verified Boot.
136     VerificationFailure,
137 }
138 
139 impl Default for UnbootableReason {
default() -> Self140     fn default() -> Self {
141         Self::Unknown
142     }
143 }
144 
145 impl From<u8> for UnbootableReason {
from(val: u8) -> Self146     fn from(val: u8) -> Self {
147         match val {
148             1 => Self::NoMoreTries,
149             2 => Self::SystemUpdate,
150             3 => Self::UserRequested,
151             4 => Self::VerificationFailure,
152             _ => Self::Unknown,
153         }
154     }
155 }
156 
157 impl From<UnbootableReason> for u8 {
from(reason: UnbootableReason) -> Self158     fn from(reason: UnbootableReason) -> Self {
159         match reason {
160             UnbootableReason::Unknown => 0,
161             UnbootableReason::NoMoreTries => 1,
162             UnbootableReason::SystemUpdate => 2,
163             UnbootableReason::UserRequested => 3,
164             UnbootableReason::VerificationFailure => 4,
165         }
166     }
167 }
168 
169 /// Describes whether a slot has successfully booted and, if not,
170 /// why it is not a valid boot target OR the number of attempts it has left.
171 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
172 pub enum Bootability {
173     /// This slot has successfully booted.
174     Successful,
175     /// This slot cannot be booted.
176     Unbootable(UnbootableReason),
177     /// This slot has not successfully booted yet but has
178     /// one or more attempts left before either successfully booting,
179     /// and being marked successful, or failing, and being marked
180     /// unbootable due to having no more tries.
181     Retriable(Tries),
182 }
183 
184 impl Default for Bootability {
default() -> Self185     fn default() -> Self {
186         Self::Retriable(7u8.into())
187     }
188 }
189 
190 /// User-visible representation of a boot slot.
191 /// Describes the slot's moniker (i.e. the suffix),
192 /// its priority,
193 /// and information about its bootability.
194 ///
195 /// Note: structures that implement Manager will probably have a different
196 /// internal representation for slots and will convert and return Slot structures
197 /// on the fly as part of iteration.
198 #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
199 pub struct Slot {
200     /// The partition suffix for the slot.
201     pub suffix: Suffix,
202     /// The slot's priority for booting.
203     pub priority: Priority,
204     /// Information about a slot's boot eligibility and history.
205     pub bootability: Bootability,
206 }
207 
208 impl Slot {
209     /// Returns whether a slot is a valid boot target,
210     /// i.e. return true if its bootability is not Unbootable.
is_bootable(&self) -> bool211     pub fn is_bootable(&self) -> bool {
212         !matches!(self.bootability, Bootability::Unbootable(_))
213     }
214 }
215 
216 /// Describes the platform recovery mode boot target.
217 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
218 pub enum RecoveryTarget {
219     /// The platform uses a dedicated recovery slot with special semantics.
220     /// It can't be marked unbootable, has unlimited retries,
221     /// and often doesn't have an explicit metadata entry.
222     Dedicated,
223     /// The platform enters recovery mode by booting to a regular slot
224     /// but with a special commandline and ramdisk.
225     Slotted(Slot),
226 }
227 
228 /// Describes a system's boot target, which can be a regular boot to a slot
229 /// or a recovery boot.
230 /// Whether the recovery boot target is a dedicated slot or a regular slot
231 /// with a special command line is platform specific.
232 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
233 pub enum BootTarget {
234     /// The system will attempt a normal boot to the given slot.
235     NormalBoot(Slot),
236     /// The system will attempt a recovery boot.
237     ///
238     /// Some platforms, such as Fuchsia, have dedicated recovery partitions with
239     /// special semantics. On these platforms, Recovery contains None.
240     ///
241     /// Other platforms, such as Android, do not have dedicated recovery partitions.
242     /// They enter recovery mode by attempting to boot a regular slot with a special
243     /// kernel command line and ramdisk.
244     /// Under these circomstances, Recovery contains the slot that will be used for recovery.
245     Recovery(RecoveryTarget),
246 }
247 
248 impl BootTarget {
249     /// Gets the suffix for a particular boot target.
250     /// Implemented for BootTarget instead of slot in order to handle
251     /// Fuchsia's recovery partition.
suffix(&self) -> Suffix252     pub fn suffix(&self) -> Suffix {
253         match self {
254             Self::NormalBoot(slot) | Self::Recovery(RecoveryTarget::Slotted(slot)) => slot.suffix,
255             Self::Recovery(RecoveryTarget::Dedicated) => 'r'.into(),
256         }
257     }
258 }
259 
260 #[doc(hidden)]
261 pub mod private {
262     use super::*;
263 
264     #[doc(hidden)]
265     pub trait SlotGet {
266         /// Given an index, returns the Slot that corresponds to that index,
267         /// or Error if the index is out of bounds.
268         /// This is intended to abstract storage details for structs that impl Manager.
269         /// Most implementors will use some other, internal representation for slots,
270         /// and will dynamically create and return Slots on the fly.
271         ///
272         /// This method is a helper, implementation detail for SlotIterator.
273         /// It is not intended to be called by other parts of GBL or other users.
get_slot_by_number(&self, number: usize) -> Result<Slot, Error>274         fn get_slot_by_number(&self, number: usize) -> Result<Slot, Error>;
275     }
276 }
277 
278 /// A helper structure for iterating over slots.
279 pub struct SlotIterator<'a> {
280     count: usize,
281     slot_getter: &'a dyn private::SlotGet,
282 }
283 
284 impl<'a> SlotIterator<'a> {
285     /// Constructor for SlotIterator
new(intf: &'a dyn private::SlotGet) -> Self286     pub fn new(intf: &'a dyn private::SlotGet) -> Self {
287         Self { count: 0, slot_getter: intf }
288     }
289 }
290 
291 impl<'a> Iterator for SlotIterator<'a> {
292     type Item = Slot;
293 
next(&mut self) -> Option<Self::Item>294     fn next(&mut self) -> Option<Self::Item> {
295         let maybe_slot = self.slot_getter.get_slot_by_number(self.count).ok();
296         if maybe_slot.is_some() {
297             self.count += 1;
298         }
299         maybe_slot
300     }
301 }
302 
303 /// Describe a oneshot boot target.
304 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
305 pub enum OneShot {
306     /// The bootloader will stop in some kind of interactive mode.
307     /// This can be Fastboot, a TUI boot menu, or something similar.
308     Bootloader,
309     /// The system will continue to the specified recovery target.
310     Continue(RecoveryTarget),
311 }
312 
313 /// Opaque boot token generated by `mark_boot_attempt` and consumed by `kernel_jump`.
314 /// Used to mandate that `mark_boot_attempt` is called **exactly** once continuing boot.
315 ///
316 /// Custom structs that implement Manager should take a BootToken as an injected parameter
317 /// on construction and return it on the first successful call to mark_boot_attempt.
318 #[derive(Debug, PartialEq, Eq)]
319 pub struct BootToken(pub(crate) ());
320 
321 /// The boot slot manager trait.
322 /// Responsible for setting boot slot policy and abstracting over on-disk/in-memory
323 /// representation of slot metadata.
324 pub trait Manager: private::SlotGet {
325     /// Returns an iterator over all regular slots on the system.
slots_iter(&self) -> SlotIterator326     fn slots_iter(&self) -> SlotIterator;
327 
328     /// Returns the current active slot,
329     /// or Recovery if the system will try to boot to recovery.
get_boot_target(&self) -> Result<BootTarget, Error>330     fn get_boot_target(&self) -> Result<BootTarget, Error>;
331 
332     /// Returns the slot last set active.
333     /// Note that this is different from get_boot_target in that
334     /// the slot last set active cannot be Recovery.
get_slot_last_set_active(&self) -> Result<Slot, Error>335     fn get_slot_last_set_active(&self) -> Result<Slot, Error> {
336         self.slots_iter()
337             .max_by_key(|slot| (slot.priority, slot.suffix.rank()))
338             .ok_or(Error::Other(Some("Couldn't get slot last set active")))
339     }
340 
341     /// Updates internal metadata (usually the retry count)
342     /// indicating that the system will have tried to boot the current active slot.
343     /// Returns Ok(BootToken) on success to verify that boot attempt metadata has been updated.
344     /// The token must be consumed by `kernel_jump`.
345     ///
346     /// If the current boot target is a recovery target,
347     /// or if the oneshot target is a recovery target,
348     /// no metadata is updated but the boot token is still returned.
349     ///
350     /// Returns Err if `mark_boot_attempt` has already been called.
351     ///
352     /// Note: mark_boot_attempt is NOT idempotent.
353     /// It is intended to be called EXACTLY once,
354     /// right before jumping into the kernel.
mark_boot_attempt(&mut self) -> Result<BootToken, Error>355     fn mark_boot_attempt(&mut self) -> Result<BootToken, Error>;
356 
357     /// Attempts to set the active slot.
358     ///
359     /// Can return Err if the designated slot does not exist,
360     /// if the bootloader does not have permission to set slots active,
361     /// or for other, backend policy reasons.
set_active_slot(&mut self, slot_suffix: Suffix) -> Result<(), Error>362     fn set_active_slot(&mut self, slot_suffix: Suffix) -> Result<(), Error>;
363 
364     /// Attempts to mark a slot as unbootable.
set_slot_unbootable( &mut self, slot_suffix: Suffix, reason: UnbootableReason, ) -> Result<(), Error>365     fn set_slot_unbootable(
366         &mut self,
367         slot_suffix: Suffix,
368         reason: UnbootableReason,
369     ) -> Result<(), Error>;
370 
371     /// Default for initial tries
get_max_retries(&self) -> Result<Tries, Error>372     fn get_max_retries(&self) -> Result<Tries, Error> {
373         Ok(7u8.into())
374     }
375 
376     /// Optional oneshot boot support
377 
378     /// Gets the current oneshot boot status,
379     /// or None if the system will try to boot normally.
380     ///
381     /// Oneshots are a special feature for temporarily bypassing
382     /// normal boot flow logic.
383     /// This can be used as part of device flashing, for tests, or interactive development.
get_oneshot_status(&self) -> Option<OneShot>384     fn get_oneshot_status(&self) -> Option<OneShot> {
385         None
386     }
387 
388     /// Attempts to set the oneshot boot status.
389     ///
390     /// Returns Err if the system does not support oneshot boot,
391     /// if the designated slot does not exist,
392     /// or for other, backend reasons.
set_oneshot_status(&mut self, _: OneShot) -> Result<(), Error>393     fn set_oneshot_status(&mut self, _: OneShot) -> Result<(), Error> {
394         Err(Error::OperationProhibited)
395     }
396 
397     /// Clears the oneshot status.
clear_oneshot_status(&mut self)398     fn clear_oneshot_status(&mut self);
399 
400     /// If the slot manager caches changes before writing to a backing store,
401     /// writes back and sets the cache status to clean.
402     /// The implementation is responsible for handling any errors,
403     /// e.g. ignoring, logging, or aborting.
404     ///
405     /// This is useful for partition based slot setups,
406     /// where we do not write back every interaction in order to coalesce writes
407     /// and preserve disk lifetime.
write_back(&mut self, _: &mut dyn FnMut(&mut [u8]) -> Result<(), Error>)408     fn write_back(&mut self, _: &mut dyn FnMut(&mut [u8]) -> Result<(), Error>) {}
409 }
410 
411 /// RAII helper object for coalescing changes.
412 pub struct Cursor<'a> {
413     /// The backing manager for slot metadata.
414     pub ctx: &'a mut dyn Manager,
415     /// User provided closure for persisting slot metadata bytes.
416     pub persist: &'a mut dyn FnMut(&mut [u8]) -> Result<(), Error>,
417 }
418 
419 impl Drop for Cursor<'_> {
drop(&mut self)420     fn drop(&mut self) {
421         self.ctx.write_back(&mut self.persist);
422     }
423 }
424 
425 /// Contains information of the platform's slot scheme.
426 #[derive(Default, Debug, Copy, Clone)]
427 pub struct SlotsMetadata {
428     /// Number of slots on this platform.
429     pub slot_count: usize,
430 }
431 
432 #[cfg(test)]
433 mod test {
434     use super::*;
435     use core::ffi::CStr;
436 
437     #[test]
test_suffix_to_cstr()438     fn test_suffix_to_cstr() {
439         let normal: Suffix = 'a'.into();
440         let normal_buffer: SuffixBytes = normal.into();
441         let normal_cstr = CStr::from_bytes_until_nul(&normal_buffer);
442         assert!(normal_cstr.is_ok());
443 
444         // All UTF-8 characters are at most 4 bytes.
445         // The in-memory representation as a chr or Suffix
446         // uses all 4 bytes regardless of the length of the serialized
447         // representation, but we need to make sure that buffer for
448         // the serialized suffix can handle that too.
449         // All emoji are 4 bytes when encoded as UTF-8,
450         // so they're a reasonable test.
451         let squid: Suffix = '��'.into();
452         let squid_buffer: SuffixBytes = squid.into();
453         let squid_cstr = CStr::from_bytes_until_nul(&squid_buffer);
454         assert!(squid_cstr.is_ok());
455     }
456 }
457