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 //! Provides `avb::Ops` test fixtures. 16 17 use avb::{ 18 cert_validate_vbmeta_public_key, CertOps, CertPermanentAttributes, IoError, IoResult, Ops, 19 PublicKeyForPartitionInfo, SHA256_DIGEST_SIZE, 20 }; 21 use std::{cmp::min, collections::HashMap, ffi::CStr}; 22 #[cfg(feature = "uuid")] 23 use uuid::Uuid; 24 25 /// Where the fake partition contents come from. 26 pub enum PartitionContents<'a> { 27 /// Read on-demand from disk. 28 FromDisk(Vec<u8>), 29 /// Preloaded and passed in. 30 Preloaded(&'a [u8]), 31 } 32 33 impl<'a> PartitionContents<'a> { 34 /// Returns the partition data. as_slice(&self) -> &[u8]35 pub fn as_slice(&self) -> &[u8] { 36 match self { 37 Self::FromDisk(v) => v, 38 Self::Preloaded(c) => c, 39 } 40 } 41 42 /// Returns a mutable reference to the `FromDisk` data for test modification. Panicks if the 43 /// data is actually `Preloaded` instead. as_mut_vec(&mut self) -> &mut Vec<u8>44 pub fn as_mut_vec(&mut self) -> &mut Vec<u8> { 45 match self { 46 Self::FromDisk(v) => v, 47 Self::Preloaded(_) => panic!("Cannot mutate preloaded partition data"), 48 } 49 } 50 } 51 52 /// Represents a single fake partition. 53 pub struct FakePartition<'a> { 54 /// Partition contents, either preloaded or read on-demand. 55 pub contents: PartitionContents<'a>, 56 57 /// Partition UUID. 58 #[cfg(feature = "uuid")] 59 pub uuid: Uuid, 60 } 61 62 impl<'a> FakePartition<'a> { new(contents: PartitionContents<'a>) -> Self63 fn new(contents: PartitionContents<'a>) -> Self { 64 Self { 65 contents, 66 #[cfg(feature = "uuid")] 67 uuid: Default::default(), 68 } 69 } 70 } 71 72 /// Fake vbmeta key. 73 pub enum FakeVbmetaKey { 74 /// Standard AVB validation using a hardcoded key; if the signing key matches these contents 75 /// it is accepted, otherwise it's rejected. 76 Avb { 77 /// Expected public key contents. 78 public_key: Vec<u8>, 79 /// Expected public key metadata contents. 80 public_key_metadata: Option<Vec<u8>>, 81 }, 82 /// libavb_cert validation using the permanent attributes. 83 Cert, 84 } 85 86 /// Fake `Ops` test fixture. 87 /// 88 /// The user is expected to set up the internal values to the desired device state - disk contents, 89 /// rollback indices, etc. This class then uses this state to implement the avb callback operations. 90 pub struct TestOps<'a> { 91 /// Partitions to provide to libavb callbacks. 92 pub partitions: HashMap<&'static str, FakePartition<'a>>, 93 94 /// Default vbmeta key to use for the `validate_vbmeta_public_key()` callback, or `None` to 95 /// return `IoError::Io` when accessing this key. 96 pub default_vbmeta_key: Option<FakeVbmetaKey>, 97 98 /// Additional vbmeta keys for the `validate_public_key_for_partition()` callback. 99 /// 100 /// Stored as a map of {partition_name: (key, rollback_location)}. Querying keys for partitions 101 /// not in this map will return `IoError::Io`. 102 pub vbmeta_keys_for_partition: HashMap<&'static str, (FakeVbmetaKey, u32)>, 103 104 /// Rollback indices. Set an error to simulate `IoError` during access. Writing a non-existent 105 /// rollback index value will create it; to simulate `NoSuchValue` instead, create an entry 106 /// with `Err(IoError::NoSuchValue)` as the value. 107 pub rollbacks: HashMap<usize, IoResult<u64>>, 108 109 /// Unlock state. Set an error to simulate IoError during access. 110 pub unlock_state: IoResult<bool>, 111 112 /// Persistent named values. Set an error to simulate `IoError` during access. Writing 113 /// a non-existent persistent value will create it; to simulate `NoSuchValue` instead, 114 /// create an entry with `Err(IoError::NoSuchValue)` as the value. 115 pub persistent_values: HashMap<String, IoResult<Vec<u8>>>, 116 117 /// Set to true to enable `CertOps`; defaults to false. 118 pub use_cert: bool, 119 120 /// Cert permanent attributes, or `None` to trigger `IoError` on access. 121 pub cert_permanent_attributes: Option<CertPermanentAttributes>, 122 123 /// Cert permament attributes hash, or `None` to trigger `IoError` on access. 124 pub cert_permanent_attributes_hash: Option<[u8; SHA256_DIGEST_SIZE]>, 125 126 /// Cert key versions; will be updated by the `set_key_version()` cert callback. 127 pub cert_key_versions: HashMap<usize, u64>, 128 129 /// Fake RNG values to provide, or `IoError` if there aren't enough. 130 pub cert_fake_rng: Vec<u8>, 131 } 132 133 impl<'a> TestOps<'a> { 134 /// Adds a fake on-disk partition with the given contents. 135 /// 136 /// Reduces boilerplate a bit by taking in a raw array and returning a &mut so tests can 137 /// do something like this: 138 /// 139 /// ``` 140 /// test_ops.add_partition("foo", [1, 2, 3, 4]); 141 /// test_ops.add_partition("bar", [0, 0]).uuid = uuid!(...); 142 /// ``` add_partition<T: Into<Vec<u8>>>( &mut self, name: &'static str, contents: T, ) -> &mut FakePartition<'a>143 pub fn add_partition<T: Into<Vec<u8>>>( 144 &mut self, 145 name: &'static str, 146 contents: T, 147 ) -> &mut FakePartition<'a> { 148 self.partitions.insert( 149 name, 150 FakePartition::new(PartitionContents::FromDisk(contents.into())), 151 ); 152 self.partitions.get_mut(name).unwrap() 153 } 154 155 /// Adds a preloaded partition with the given contents. 156 /// 157 /// Same a `add_partition()` except that the preloaded data is not owned by 158 /// the `TestOps` but passed in, which means it can outlive `TestOps`. add_preloaded_partition( &mut self, name: &'static str, contents: &'a [u8], ) -> &mut FakePartition<'a>159 pub fn add_preloaded_partition( 160 &mut self, 161 name: &'static str, 162 contents: &'a [u8], 163 ) -> &mut FakePartition<'a> { 164 self.partitions.insert( 165 name, 166 FakePartition::new(PartitionContents::Preloaded(contents)), 167 ); 168 self.partitions.get_mut(name).unwrap() 169 } 170 171 /// Adds a persistent value with the given state. 172 /// 173 /// Reduces boilerplate by allowing array input: 174 /// 175 /// ``` 176 /// test_ops.add_persistent_value("foo", Ok(b"contents")); 177 /// test_ops.add_persistent_value("bar", Err(IoError::NoSuchValue)); 178 /// ``` add_persistent_value(&mut self, name: &str, contents: IoResult<&[u8]>)179 pub fn add_persistent_value(&mut self, name: &str, contents: IoResult<&[u8]>) { 180 self.persistent_values 181 .insert(name.into(), contents.map(|b| b.into())); 182 } 183 184 /// Internal helper to validate a vbmeta key. validate_fake_key( &mut self, partition: Option<&str>, public_key: &[u8], public_key_metadata: Option<&[u8]>, ) -> IoResult<bool>185 fn validate_fake_key( 186 &mut self, 187 partition: Option<&str>, 188 public_key: &[u8], 189 public_key_metadata: Option<&[u8]>, 190 ) -> IoResult<bool> { 191 let fake_key = match partition { 192 None => self.default_vbmeta_key.as_ref(), 193 Some(p) => self.vbmeta_keys_for_partition.get(p).map(|(key, _)| key), 194 } 195 .ok_or(IoError::Io)?; 196 197 match fake_key { 198 FakeVbmetaKey::Avb { 199 public_key: expected_key, 200 public_key_metadata: expected_metadata, 201 } => { 202 // avb: only accept if it matches the hardcoded key + metadata. 203 Ok(expected_key == public_key 204 && expected_metadata.as_deref() == public_key_metadata) 205 } 206 FakeVbmetaKey::Cert => { 207 // avb_cert: forward to the cert helper function. 208 cert_validate_vbmeta_public_key(self, public_key, public_key_metadata) 209 } 210 } 211 } 212 } 213 214 impl Default for TestOps<'_> { default() -> Self215 fn default() -> Self { 216 Self { 217 partitions: HashMap::new(), 218 default_vbmeta_key: None, 219 vbmeta_keys_for_partition: HashMap::new(), 220 rollbacks: HashMap::new(), 221 unlock_state: Err(IoError::Io), 222 persistent_values: HashMap::new(), 223 use_cert: false, 224 cert_permanent_attributes: None, 225 cert_permanent_attributes_hash: None, 226 cert_key_versions: HashMap::new(), 227 cert_fake_rng: Vec::new(), 228 } 229 } 230 } 231 232 impl<'a> Ops<'a> for TestOps<'a> { read_from_partition( &mut self, partition: &CStr, offset: i64, buffer: &mut [u8], ) -> IoResult<usize>233 fn read_from_partition( 234 &mut self, 235 partition: &CStr, 236 offset: i64, 237 buffer: &mut [u8], 238 ) -> IoResult<usize> { 239 let contents = self 240 .partitions 241 .get(partition.to_str()?) 242 .ok_or(IoError::NoSuchPartition)? 243 .contents 244 .as_slice(); 245 246 // Negative offset means count backwards from the end. 247 let offset = { 248 if offset < 0 { 249 offset 250 .checked_add(i64::try_from(contents.len()).unwrap()) 251 .unwrap() 252 } else { 253 offset 254 } 255 }; 256 if offset < 0 { 257 return Err(IoError::RangeOutsidePartition); 258 } 259 let offset = usize::try_from(offset).unwrap(); 260 261 if offset >= contents.len() { 262 return Err(IoError::RangeOutsidePartition); 263 } 264 265 // Truncating is allowed for reads past the partition end. 266 let end = min(offset.checked_add(buffer.len()).unwrap(), contents.len()); 267 let bytes_read = end - offset; 268 269 buffer[..bytes_read].copy_from_slice(&contents[offset..end]); 270 Ok(bytes_read) 271 } 272 get_preloaded_partition(&mut self, partition: &CStr) -> IoResult<&'a [u8]>273 fn get_preloaded_partition(&mut self, partition: &CStr) -> IoResult<&'a [u8]> { 274 match self.partitions.get(partition.to_str()?) { 275 Some(FakePartition { 276 contents: PartitionContents::Preloaded(preloaded), 277 .. 278 }) => Ok(&preloaded[..]), 279 _ => Err(IoError::NotImplemented), 280 } 281 } 282 validate_vbmeta_public_key( &mut self, public_key: &[u8], public_key_metadata: Option<&[u8]>, ) -> IoResult<bool>283 fn validate_vbmeta_public_key( 284 &mut self, 285 public_key: &[u8], 286 public_key_metadata: Option<&[u8]>, 287 ) -> IoResult<bool> { 288 self.validate_fake_key(None, public_key, public_key_metadata) 289 } 290 read_rollback_index(&mut self, location: usize) -> IoResult<u64>291 fn read_rollback_index(&mut self, location: usize) -> IoResult<u64> { 292 self.rollbacks.get(&location).ok_or(IoError::Io)?.clone() 293 } 294 write_rollback_index(&mut self, location: usize, index: u64) -> IoResult<()>295 fn write_rollback_index(&mut self, location: usize, index: u64) -> IoResult<()> { 296 if let Some(Err(e)) = self.rollbacks.get(&location) { 297 return Err(e.clone()); 298 } 299 300 self.rollbacks.insert(location, Ok(index)); 301 Ok(()) 302 } 303 read_is_device_unlocked(&mut self) -> IoResult<bool>304 fn read_is_device_unlocked(&mut self) -> IoResult<bool> { 305 self.unlock_state.clone() 306 } 307 308 #[cfg(feature = "uuid")] get_unique_guid_for_partition(&mut self, partition: &CStr) -> IoResult<Uuid>309 fn get_unique_guid_for_partition(&mut self, partition: &CStr) -> IoResult<Uuid> { 310 self.partitions 311 .get(partition.to_str()?) 312 .map(|p| p.uuid) 313 .ok_or(IoError::NoSuchPartition) 314 } 315 get_size_of_partition(&mut self, partition: &CStr) -> IoResult<u64>316 fn get_size_of_partition(&mut self, partition: &CStr) -> IoResult<u64> { 317 self.partitions 318 .get(partition.to_str()?) 319 .map(|p| u64::try_from(p.contents.as_slice().len()).unwrap()) 320 .ok_or(IoError::NoSuchPartition) 321 } 322 read_persistent_value(&mut self, name: &CStr, value: &mut [u8]) -> IoResult<usize>323 fn read_persistent_value(&mut self, name: &CStr, value: &mut [u8]) -> IoResult<usize> { 324 match self 325 .persistent_values 326 .get(name.to_str()?) 327 .ok_or(IoError::NoSuchValue)? 328 { 329 // If we were given enough space, write the value contents. 330 Ok(contents) if contents.len() <= value.len() => { 331 value[..contents.len()].clone_from_slice(contents); 332 Ok(contents.len()) 333 } 334 // Not enough space, tell the caller how much we need. 335 Ok(contents) => Err(IoError::InsufficientSpace(contents.len())), 336 // Simulated error, return it. 337 Err(e) => Err(e.clone()), 338 } 339 } 340 write_persistent_value(&mut self, name: &CStr, value: &[u8]) -> IoResult<()>341 fn write_persistent_value(&mut self, name: &CStr, value: &[u8]) -> IoResult<()> { 342 let name = name.to_str()?; 343 344 // If the test requested a simulated error on this value, return it. 345 if let Some(Err(e)) = self.persistent_values.get(name) { 346 return Err(e.clone()); 347 } 348 349 self.persistent_values 350 .insert(name.to_string(), Ok(value.to_vec())); 351 Ok(()) 352 } 353 erase_persistent_value(&mut self, name: &CStr) -> IoResult<()>354 fn erase_persistent_value(&mut self, name: &CStr) -> IoResult<()> { 355 let name = name.to_str()?; 356 357 // If the test requested a simulated error on this value, return it. 358 if let Some(Err(e)) = self.persistent_values.get(name) { 359 return Err(e.clone()); 360 } 361 362 self.persistent_values.remove(name); 363 Ok(()) 364 } 365 validate_public_key_for_partition( &mut self, partition: &CStr, public_key: &[u8], public_key_metadata: Option<&[u8]>, ) -> IoResult<PublicKeyForPartitionInfo>366 fn validate_public_key_for_partition( 367 &mut self, 368 partition: &CStr, 369 public_key: &[u8], 370 public_key_metadata: Option<&[u8]>, 371 ) -> IoResult<PublicKeyForPartitionInfo> { 372 let partition = partition.to_str()?; 373 374 let rollback_index_location = self 375 .vbmeta_keys_for_partition 376 .get(partition) 377 .ok_or(IoError::Io)? 378 .1; 379 380 Ok(PublicKeyForPartitionInfo { 381 trusted: self.validate_fake_key(Some(partition), public_key, public_key_metadata)?, 382 rollback_index_location, 383 }) 384 } 385 cert_ops(&mut self) -> Option<&mut dyn CertOps>386 fn cert_ops(&mut self) -> Option<&mut dyn CertOps> { 387 match self.use_cert { 388 true => Some(self), 389 false => None, 390 } 391 } 392 } 393 394 impl<'a> CertOps for TestOps<'a> { read_permanent_attributes( &mut self, attributes: &mut CertPermanentAttributes, ) -> IoResult<()>395 fn read_permanent_attributes( 396 &mut self, 397 attributes: &mut CertPermanentAttributes, 398 ) -> IoResult<()> { 399 *attributes = self.cert_permanent_attributes.ok_or(IoError::Io)?; 400 Ok(()) 401 } 402 read_permanent_attributes_hash(&mut self) -> IoResult<[u8; SHA256_DIGEST_SIZE]>403 fn read_permanent_attributes_hash(&mut self) -> IoResult<[u8; SHA256_DIGEST_SIZE]> { 404 self.cert_permanent_attributes_hash.ok_or(IoError::Io) 405 } 406 set_key_version(&mut self, rollback_index_location: usize, key_version: u64)407 fn set_key_version(&mut self, rollback_index_location: usize, key_version: u64) { 408 self.cert_key_versions 409 .insert(rollback_index_location, key_version); 410 } 411 get_random(&mut self, bytes: &mut [u8]) -> IoResult<()>412 fn get_random(&mut self, bytes: &mut [u8]) -> IoResult<()> { 413 if bytes.len() > self.cert_fake_rng.len() { 414 return Err(IoError::Io); 415 } 416 417 let leftover = self.cert_fake_rng.split_off(bytes.len()); 418 bytes.copy_from_slice(&self.cert_fake_rng[..]); 419 self.cert_fake_rng = leftover; 420 Ok(()) 421 } 422 } 423