xref: /aosp_15_r20/external/avb/rust/tests/test_ops.rs (revision d289c2ba6de359471b23d594623b906876bc48a0)
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