• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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