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