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