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