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. Accessing unknown locations will return `IoError::Io`. 105 pub rollbacks: HashMap<usize, u64>, 106 107 /// Unlock state. Set an error to simulate IoError during access. 108 pub unlock_state: IoResult<bool>, 109 110 /// Persistent named values. Set an error to simulate `IoError` during access. Writing 111 /// a non-existent persistent value will create it; to simulate `NoSuchValue` instead, 112 /// create an entry with `Err(IoError::NoSuchValue)` as the value. 113 pub persistent_values: HashMap<String, IoResult<Vec<u8>>>, 114 115 /// Set to true to enable `CertOps`; defaults to false. 116 pub use_cert: bool, 117 118 /// Cert permanent attributes, or `None` to trigger `IoError` on access. 119 pub cert_permanent_attributes: Option<CertPermanentAttributes>, 120 121 /// Cert permament attributes hash, or `None` to trigger `IoError` on access. 122 pub cert_permanent_attributes_hash: Option<[u8; SHA256_DIGEST_SIZE]>, 123 124 /// Cert key versions; will be updated by the `set_key_version()` cert callback. 125 pub cert_key_versions: HashMap<usize, u64>, 126 127 /// Fake RNG values to provide, or `IoError` if there aren't enough. 128 pub cert_fake_rng: Vec<u8>, 129 } 130 131 impl<'a> TestOps<'a> { 132 /// Adds a fake on-disk partition with the given contents. 133 /// 134 /// Reduces boilerplate a bit by taking in a raw array and returning a &mut so tests can 135 /// do something like this: 136 /// 137 /// ``` 138 /// test_ops.add_partition("foo", [1, 2, 3, 4]); 139 /// test_ops.add_partition("bar", [0, 0]).uuid = uuid!(...); 140 /// ``` add_partition<T: Into<Vec<u8>>>( &mut self, name: &'static str, contents: T, ) -> &mut FakePartition<'a>141 pub fn add_partition<T: Into<Vec<u8>>>( 142 &mut self, 143 name: &'static str, 144 contents: T, 145 ) -> &mut FakePartition<'a> { 146 self.partitions.insert( 147 name, 148 FakePartition::new(PartitionContents::FromDisk(contents.into())), 149 ); 150 self.partitions.get_mut(name).unwrap() 151 } 152 153 /// Adds a preloaded partition with the given contents. 154 /// 155 /// Same a `add_partition()` except that the preloaded data is not owned by 156 /// 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>157 pub fn add_preloaded_partition( 158 &mut self, 159 name: &'static str, 160 contents: &'a [u8], 161 ) -> &mut FakePartition<'a> { 162 self.partitions.insert( 163 name, 164 FakePartition::new(PartitionContents::Preloaded(contents)), 165 ); 166 self.partitions.get_mut(name).unwrap() 167 } 168 169 /// Adds a persistent value with the given state. 170 /// 171 /// Reduces boilerplate by allowing array input: 172 /// 173 /// ``` 174 /// test_ops.add_persistent_value("foo", Ok(b"contents")); 175 /// test_ops.add_persistent_value("bar", Err(IoError::NoSuchValue)); 176 /// ``` add_persistent_value(&mut self, name: &str, contents: IoResult<&[u8]>)177 pub fn add_persistent_value(&mut self, name: &str, contents: IoResult<&[u8]>) { 178 self.persistent_values 179 .insert(name.into(), contents.map(|b| b.into())); 180 } 181 182 /// 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>183 fn validate_fake_key( 184 &mut self, 185 partition: Option<&str>, 186 public_key: &[u8], 187 public_key_metadata: Option<&[u8]>, 188 ) -> IoResult<bool> { 189 let fake_key = match partition { 190 None => self.default_vbmeta_key.as_ref(), 191 Some(p) => self.vbmeta_keys_for_partition.get(p).map(|(key, _)| key), 192 } 193 .ok_or(IoError::Io)?; 194 195 match fake_key { 196 FakeVbmetaKey::Avb { 197 public_key: expected_key, 198 public_key_metadata: expected_metadata, 199 } => { 200 // avb: only accept if it matches the hardcoded key + metadata. 201 Ok(expected_key == public_key 202 && expected_metadata.as_deref() == public_key_metadata) 203 } 204 FakeVbmetaKey::Cert => { 205 // avb_cert: forward to the cert helper function. 206 cert_validate_vbmeta_public_key(self, public_key, public_key_metadata) 207 } 208 } 209 } 210 } 211 212 impl Default for TestOps<'_> { default() -> Self213 fn default() -> Self { 214 Self { 215 partitions: HashMap::new(), 216 default_vbmeta_key: None, 217 vbmeta_keys_for_partition: HashMap::new(), 218 rollbacks: HashMap::new(), 219 unlock_state: Err(IoError::Io), 220 persistent_values: HashMap::new(), 221 use_cert: false, 222 cert_permanent_attributes: None, 223 cert_permanent_attributes_hash: None, 224 cert_key_versions: HashMap::new(), 225 cert_fake_rng: Vec::new(), 226 } 227 } 228 } 229 230 impl<'a> Ops<'a> for TestOps<'a> { read_from_partition( &mut self, partition: &CStr, offset: i64, buffer: &mut [u8], ) -> IoResult<usize>231 fn read_from_partition( 232 &mut self, 233 partition: &CStr, 234 offset: i64, 235 buffer: &mut [u8], 236 ) -> IoResult<usize> { 237 let contents = self 238 .partitions 239 .get(partition.to_str()?) 240 .ok_or(IoError::NoSuchPartition)? 241 .contents 242 .as_slice(); 243 244 // Negative offset means count backwards from the end. 245 let offset = { 246 if offset < 0 { 247 offset 248 .checked_add(i64::try_from(contents.len()).unwrap()) 249 .unwrap() 250 } else { 251 offset 252 } 253 }; 254 if offset < 0 { 255 return Err(IoError::RangeOutsidePartition); 256 } 257 let offset = usize::try_from(offset).unwrap(); 258 259 if offset >= contents.len() { 260 return Err(IoError::RangeOutsidePartition); 261 } 262 263 // Truncating is allowed for reads past the partition end. 264 let end = min(offset.checked_add(buffer.len()).unwrap(), contents.len()); 265 let bytes_read = end - offset; 266 267 buffer[..bytes_read].copy_from_slice(&contents[offset..end]); 268 Ok(bytes_read) 269 } 270 get_preloaded_partition(&mut self, partition: &CStr) -> IoResult<&'a [u8]>271 fn get_preloaded_partition(&mut self, partition: &CStr) -> IoResult<&'a [u8]> { 272 match self.partitions.get(partition.to_str()?) { 273 Some(FakePartition { 274 contents: PartitionContents::Preloaded(preloaded), 275 .. 276 }) => Ok(&preloaded[..]), 277 _ => Err(IoError::NotImplemented), 278 } 279 } 280 validate_vbmeta_public_key( &mut self, public_key: &[u8], public_key_metadata: Option<&[u8]>, ) -> IoResult<bool>281 fn validate_vbmeta_public_key( 282 &mut self, 283 public_key: &[u8], 284 public_key_metadata: Option<&[u8]>, 285 ) -> IoResult<bool> { 286 self.validate_fake_key(None, public_key, public_key_metadata) 287 } 288 read_rollback_index(&mut self, location: usize) -> IoResult<u64>289 fn read_rollback_index(&mut self, location: usize) -> IoResult<u64> { 290 self.rollbacks.get(&location).ok_or(IoError::Io).copied() 291 } 292 write_rollback_index(&mut self, location: usize, index: u64) -> IoResult<()>293 fn write_rollback_index(&mut self, location: usize, index: u64) -> IoResult<()> { 294 *(self.rollbacks.get_mut(&location).ok_or(IoError::Io)?) = index; 295 Ok(()) 296 } 297 read_is_device_unlocked(&mut self) -> IoResult<bool>298 fn read_is_device_unlocked(&mut self) -> IoResult<bool> { 299 self.unlock_state.clone() 300 } 301 302 #[cfg(feature = "uuid")] get_unique_guid_for_partition(&mut self, partition: &CStr) -> IoResult<Uuid>303 fn get_unique_guid_for_partition(&mut self, partition: &CStr) -> IoResult<Uuid> { 304 self.partitions 305 .get(partition.to_str()?) 306 .map(|p| p.uuid) 307 .ok_or(IoError::NoSuchPartition) 308 } 309 get_size_of_partition(&mut self, partition: &CStr) -> IoResult<u64>310 fn get_size_of_partition(&mut self, partition: &CStr) -> IoResult<u64> { 311 self.partitions 312 .get(partition.to_str()?) 313 .map(|p| u64::try_from(p.contents.as_slice().len()).unwrap()) 314 .ok_or(IoError::NoSuchPartition) 315 } 316 read_persistent_value(&mut self, name: &CStr, value: &mut [u8]) -> IoResult<usize>317 fn read_persistent_value(&mut self, name: &CStr, value: &mut [u8]) -> IoResult<usize> { 318 match self 319 .persistent_values 320 .get(name.to_str()?) 321 .ok_or(IoError::NoSuchValue)? 322 { 323 // If we were given enough space, write the value contents. 324 Ok(contents) if contents.len() <= value.len() => { 325 value[..contents.len()].clone_from_slice(contents); 326 Ok(contents.len()) 327 } 328 // Not enough space, tell the caller how much we need. 329 Ok(contents) => Err(IoError::InsufficientSpace(contents.len())), 330 // Simulated error, return it. 331 Err(e) => Err(e.clone()), 332 } 333 } 334 write_persistent_value(&mut self, name: &CStr, value: &[u8]) -> IoResult<()>335 fn write_persistent_value(&mut self, name: &CStr, value: &[u8]) -> IoResult<()> { 336 let name = name.to_str()?; 337 338 // If the test requested a simulated error on this value, return it. 339 if let Some(Err(e)) = self.persistent_values.get(name) { 340 return Err(e.clone()); 341 } 342 343 self.persistent_values 344 .insert(name.to_string(), Ok(value.to_vec())); 345 Ok(()) 346 } 347 erase_persistent_value(&mut self, name: &CStr) -> IoResult<()>348 fn erase_persistent_value(&mut self, name: &CStr) -> IoResult<()> { 349 let name = name.to_str()?; 350 351 // If the test requested a simulated error on this value, return it. 352 if let Some(Err(e)) = self.persistent_values.get(name) { 353 return Err(e.clone()); 354 } 355 356 self.persistent_values.remove(name); 357 Ok(()) 358 } 359 validate_public_key_for_partition( &mut self, partition: &CStr, public_key: &[u8], public_key_metadata: Option<&[u8]>, ) -> IoResult<PublicKeyForPartitionInfo>360 fn validate_public_key_for_partition( 361 &mut self, 362 partition: &CStr, 363 public_key: &[u8], 364 public_key_metadata: Option<&[u8]>, 365 ) -> IoResult<PublicKeyForPartitionInfo> { 366 let partition = partition.to_str()?; 367 368 let rollback_index_location = self 369 .vbmeta_keys_for_partition 370 .get(partition) 371 .ok_or(IoError::Io)? 372 .1; 373 374 Ok(PublicKeyForPartitionInfo { 375 trusted: self.validate_fake_key(Some(partition), public_key, public_key_metadata)?, 376 rollback_index_location, 377 }) 378 } 379 cert_ops(&mut self) -> Option<&mut dyn CertOps>380 fn cert_ops(&mut self) -> Option<&mut dyn CertOps> { 381 match self.use_cert { 382 true => Some(self), 383 false => None, 384 } 385 } 386 } 387 388 impl<'a> CertOps for TestOps<'a> { read_permanent_attributes( &mut self, attributes: &mut CertPermanentAttributes, ) -> IoResult<()>389 fn read_permanent_attributes( 390 &mut self, 391 attributes: &mut CertPermanentAttributes, 392 ) -> IoResult<()> { 393 *attributes = self.cert_permanent_attributes.ok_or(IoError::Io)?; 394 Ok(()) 395 } 396 read_permanent_attributes_hash(&mut self) -> IoResult<[u8; SHA256_DIGEST_SIZE]>397 fn read_permanent_attributes_hash(&mut self) -> IoResult<[u8; SHA256_DIGEST_SIZE]> { 398 self.cert_permanent_attributes_hash.ok_or(IoError::Io) 399 } 400 set_key_version(&mut self, rollback_index_location: usize, key_version: u64)401 fn set_key_version(&mut self, rollback_index_location: usize, key_version: u64) { 402 self.cert_key_versions 403 .insert(rollback_index_location, key_version); 404 } 405 get_random(&mut self, bytes: &mut [u8]) -> IoResult<()>406 fn get_random(&mut self, bytes: &mut [u8]) -> IoResult<()> { 407 if bytes.len() > self.cert_fake_rng.len() { 408 return Err(IoError::Io); 409 } 410 411 let leftover = self.cert_fake_rng.split_off(bytes.len()); 412 bytes.copy_from_slice(&self.cert_fake_rng[..]); 413 self.cert_fake_rng = leftover; 414 Ok(()) 415 } 416 } 417