diff options
author | David Pursell <dpursell@google.com> | 2023-11-20 18:24:07 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2023-11-20 18:24:07 +0000 |
commit | c18366ce8733dee96de2842d9de5998207f27e59 (patch) | |
tree | 25cdeab81e2ff48f0feb83d3bf5be07c39bf0251 | |
parent | 50c08b6398cada603bc138a70cc0f78956e7237c (diff) | |
parent | c1723dae8be20b9e8eca24160e6b891c538f434e (diff) | |
download | avb-c18366ce8733dee96de2842d9de5998207f27e59.tar.gz |
Merge changes I26462d60,Ied33940b into main am: d4384906c9 am: 229b7dafa9 am: c1723dae8b
Original change: https://android-review.googlesource.com/c/platform/external/avb/+/2831118
Change-Id: I928b724f1becd262f2eeb65cd15342177ffff44e
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-rw-r--r-- | rust/Android.bp | 24 | ||||
-rw-r--r-- | rust/src/ops.rs | 1190 | ||||
-rw-r--r-- | rust/tests/test_ops.rs | 325 | ||||
-rw-r--r-- | rust/tests/tests.rs | 15 |
4 files changed, 402 insertions, 1152 deletions
diff --git a/rust/Android.bp b/rust/Android.bp index 8f6bcdb..2d5730e 100644 --- a/rust/Android.bp +++ b/rust/Android.bp @@ -172,23 +172,25 @@ rust_library { ], } -// device test: no std, no features. -// Note: we are testing the nostd version of the library but the tests -// themselves do use std. +rust_defaults { + name: "libavb_rs_test.defaults", + srcs: ["tests/tests.rs"], + test_suites: ["general-tests"], +} + +// device test: no features. rust_test { name: "libavb_rs_test", - defaults: [ - "libavb_rs_nostd.defaults", - ], - test_suites: ["general-tests"], + defaults: ["libavb_rs_test.defaults"], + rustlibs: ["libavb_rs"], } -// device test: no std, UUID feature. +// device test: UUID feature. rust_test { name: "libavb_rs_uuid_test", defaults: [ - "libavb_rs_nostd.defaults", - "libavb_rs_nostd.uuid.defaults", + "libavb_rs_test.defaults", + "libavb_rs.uuid.defaults", ], - test_suites: ["general-tests"], + rustlibs: ["libavb_rs_uuid"], } diff --git a/rust/src/ops.rs b/rust/src/ops.rs index 8289816..74f12f5 100644 --- a/rust/src/ops.rs +++ b/rust/src/ops.rs @@ -706,7 +706,6 @@ unsafe fn try_read_is_device_unlocked(ops: *mut AvbOps, out_is_unlocked: *mut bo /// Wraps a callback to convert the given `Result<>` to raw `AvbIOResult` for libavb. /// /// See corresponding `try_*` function docs. -#[cfg(feature = "uuid")] unsafe extern "C" fn get_unique_guid_for_partition( ops: *mut AvbOps, partition: *const c_char, @@ -721,24 +720,12 @@ unsafe extern "C" fn get_unique_guid_for_partition( )) } -/// When compiled without the `uuid` feature this callback is not used, just return error. -#[cfg(not(feature = "uuid"))] -unsafe extern "C" fn get_unique_guid_for_partition( - ops: *mut AvbOps, - partition: *const c_char, - guid_buf: *mut c_char, - guid_buf_size: usize, -) -> AvbIOResult { - AvbIOResult::AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION -} - /// Bounces the C callback into the user-provided Rust implementation. /// /// # Safety /// * `ops` must have been created via `ScopedAvbOps`. /// * `partition` must adhere to the requirements of `CStr::from_ptr()`. /// * `guid_buf` must adhere to the requirements of `slice::from_raw_parts_mut()`. -#[cfg(feature = "uuid")] unsafe fn try_get_unique_guid_for_partition( ops: *mut AvbOps, partition: *const c_char, @@ -749,40 +736,66 @@ unsafe fn try_get_unique_guid_for_partition( check_nonnull(guid_buf)?; // SAFETY: - // * we only use `ops` objects created via `ScopedAvbOps` as required. - // * `ops` is only extracted once and is dropped at the end of the callback. - let ops = unsafe { as_ops(ops) }?; - // SAFETY: // * we've checked that the pointer is non-NULL. // * libavb gives us a properly-allocated and nul-terminated `partition`. // * the string contents are not modified while the returned `&CStr` exists. // * the returned `&CStr` is not held past the scope of this callback. let partition = unsafe { CStr::from_ptr(partition) }; - let guid = ops.get_unique_guid_for_partition(partition)?; - - // Write the UUID string to a uuid buffer which is guaranteed to be large enough, then use - // `CString` to apply nul-termination. - // This does allocate memory, but it's short-lived and discarded as soon as we copy the - // properly-terminated string back to the buffer. - let mut encode_buffer = uuid::Uuid::encode_buffer(); - let guid_str = guid.as_hyphenated().encode_lower(&mut encode_buffer); - let guid_cstring = alloc::ffi::CString::new(guid_str.as_bytes()).or(Err(IoError::Io))?; - let guid_bytes = guid_cstring.to_bytes_with_nul(); - - if guid_buf_size < guid_bytes.len() { - // This would indicate some internal error - the uuid library needs more - // space to print the UUID string than libavb provided. - return Err(IoError::Oom); - } - // SAFETY: // * we've checked that the pointer is non-NULL. // * libavb gives us a properly-allocated `guid_buf` with size `guid_buf_size`. // * we only access the contents via the returned slice. // * the returned slice is not held past the scope of this callback. let buffer = unsafe { slice::from_raw_parts_mut(guid_buf as *mut u8, guid_buf_size) }; - buffer[..guid_bytes.len()].copy_from_slice(guid_bytes); - Ok(()) + + // Initialize the output buffer to the empty string. + if buffer.is_empty() { + return Err(IoError::Oom); + } + buffer[0] = b'\0'; + + #[cfg(feature = "uuid")] + { + // SAFETY: + // * we only use `ops` objects created via `ScopedAvbOps` as required. + // * `ops` is only extracted once and is dropped at the end of the callback. + let ops = unsafe { as_ops(ops) }?; + let guid = ops.get_unique_guid_for_partition(partition)?; + + // Write the UUID string to a uuid buffer which is guaranteed to be large enough, then use + // `CString` to apply nul-termination. + // This does allocate memory, but it's short-lived and discarded as soon as we copy the + // properly-terminated string back to the buffer. + let mut encode_buffer = uuid::Uuid::encode_buffer(); + let guid_str = guid.as_hyphenated().encode_lower(&mut encode_buffer); + let guid_cstring = alloc::ffi::CString::new(guid_str.as_bytes()).or(Err(IoError::Io))?; + let guid_bytes = guid_cstring.to_bytes_with_nul(); + + if buffer.len() < guid_bytes.len() { + // This would indicate some internal error - the uuid library needs more + // space to print the UUID string than libavb provided. + return Err(IoError::Oom); + } + buffer[..guid_bytes.len()].copy_from_slice(guid_bytes); + Ok(()) + } + + #[cfg(not(feature = "uuid"))] + { + // The user doesn't need this feature, but libavb may still attempt to inject the vbmeta + // partition GUID into the commandline, depending on the verification flags. In this case + // the function needs to return success or verification will fail, but leaving the buffer + // as the empty string is sufficient since nobody will be reading the value. + // + // We restrict this to only "vbmeta*" partitions because if we return success for all + // partitions, libavb will also try to inject a "system" partition GUID into the + // commandline, which the user doesn't need. + partition + .to_bytes() + .starts_with(b"vbmeta") + .then_some(()) + .ok_or(IoError::NoSuchPartition) + } } /// Wraps a callback to convert the given `Result<>` to raw `AvbIOResult` for libavb. @@ -1064,1108 +1077,3 @@ unsafe fn try_validate_public_key_for_partition( } Ok(()) } - -#[cfg(test)] -mod tests { - use super::*; - - use std::collections::HashMap; - use std::ffi::CString; - #[cfg(feature = "uuid")] - use uuid::uuid; - - /// Length of a UUID C-string representation including hyphens and a null-terminator. - #[cfg(feature = "uuid")] - const UUID_CSTRING_LENGTH: usize = uuid::fmt::Hyphenated::LENGTH + 1; - - /// Represents a single fake partition. - #[derive(Default)] - struct FakePartition { - contents: Vec<u8>, // Partition contents - preloaded: bool, // Whether it should report as preloaded or not - #[cfg(feature = "uuid")] - uuid: Uuid, // Partition UUID - } - - /// Fake vbmeta key state. - struct FakeVbmetaKeyState { - /// Key trust & rollback index info. - info: PublicKeyForPartitionInfo, - /// If specified, indicates the specific partition this vbmeta is tied to (for - /// `validate_public_key_for_partition()`). - for_partition: Option<&'static str>, - } - - /// Ops implementation for testing. - /// - /// In addition to being used to exercise individual callback wrappers, this will be used for - /// full verification tests so behavior needs to be correct. - struct TestOps { - /// Partitions to provide to libavb callbacks. - partitions: HashMap<&'static str, FakePartition>, - /// Vbmeta public keys as a map of {(key, metadata): state}. Querying unknown keys will - /// return `IoError::Io`. - /// - /// See `add_vbmeta_key*()` functions for simpler wrappers to inject these keys. - vbmeta_keys: HashMap<(&'static [u8], Option<&'static [u8]>), FakeVbmetaKeyState>, - /// Rollback indices. Accessing unknown locations will return `IoError::Io`. - rollbacks: HashMap<usize, u64>, - /// Unlock state. Set an error to simulate IoError during access. - unlock_state: Result<bool>, - /// Persistent named values. Set an error to simulate `IoError` during access. Writing - /// a non-existent persistent value will create it; to simulate `NoSuchValue` instead, - /// create an entry with `Err(IoError::NoSuchValue)` as the value. - persistent_values: HashMap<String, Result<Vec<u8>>>, - } - - impl TestOps { - /// Adds a partition with the given contents. - /// - /// Reduces boilerplate a bit by taking in a raw array and returning a &mut so tests can - /// do something like this: - /// - /// ``` - /// test_ops.add_partition("foo", [1, 2, 3, 4]); - /// test_ops.add_partition("bar", [0, 0]).preloaded = true; - /// ``` - fn add_partition<const N: usize>( - &mut self, - name: &'static str, - contents: [u8; N], - ) -> &mut FakePartition { - self.partitions.insert( - name, - FakePartition { - contents: contents.into(), - ..Default::default() - }, - ); - self.partitions.get_mut(name).unwrap() - } - - /// Adds a persistent value with the given state. - /// - /// Reduces boilerplate by allowing array input: - /// - /// ``` - /// test_ops.add_persistent_value("foo", Ok(b"contents")); - /// test_ops.add_persistent_value("bar", Err(IoError::NoSuchValue)); - /// ``` - fn add_persistent_value(&mut self, name: &str, contents: Result<&[u8]>) { - self.persistent_values - .insert(name.into(), contents.map(|b| b.into())); - } - - /// Adds a fake vbmeta key not tied to any partition. - fn add_vbmeta_key( - &mut self, - key: &'static [u8], - metadata: Option<&'static [u8]>, - trusted: bool, - ) { - self.vbmeta_keys.insert( - (key, metadata), - FakeVbmetaKeyState { - // `rollback_index_location` doesn't matter in this case, it will be read from - // the vbmeta blob. - info: PublicKeyForPartitionInfo { - trusted, - rollback_index_location: 0, - }, - for_partition: None, - }, - ); - } - - /// Adds a fake vbmeta key tied to the given partition and rollback index location. - fn add_vbmeta_key_for_partition( - &mut self, - key: &'static [u8], - metadata: Option<&'static [u8]>, - trusted: bool, - partition: &'static str, - rollback_index_location: u32, - ) { - self.vbmeta_keys.insert( - (key, metadata), - FakeVbmetaKeyState { - info: PublicKeyForPartitionInfo { - trusted, - rollback_index_location, - }, - for_partition: Some(partition), - }, - ); - } - } - - impl Default for TestOps { - fn default() -> Self { - Self { - partitions: HashMap::new(), - vbmeta_keys: HashMap::new(), - rollbacks: HashMap::new(), - unlock_state: Err(IoError::Io), - persistent_values: HashMap::new(), - } - } - } - - impl Ops for TestOps { - fn read_from_partition( - &mut self, - partition: &CStr, - offset: i64, - buffer: &mut [u8], - ) -> Result<usize> { - let partition = self - .partitions - .get(partition.to_str()?) - .ok_or(IoError::NoSuchPartition)?; - - // We should never be trying to read a preloaded partition from disk since we already - // have it available in memory. - assert!(!partition.preloaded); - - let contents = &partition.contents; - - // Negative offset means count backwards from the end. - let offset = { - if offset < 0 { - offset - .checked_add(i64::try_from(contents.len()).unwrap()) - .unwrap() - } else { - offset - } - }; - if offset < 0 { - return Err(IoError::RangeOutsidePartition); - } - let offset = usize::try_from(offset).unwrap(); - - if offset >= contents.len() { - return Err(IoError::RangeOutsidePartition); - } - - // Truncating is allowed for reads past the partition end. - let end = min(offset.checked_add(buffer.len()).unwrap(), contents.len()); - let bytes_read = end - offset; - - buffer[..bytes_read].copy_from_slice(&contents[offset..end]); - Ok(bytes_read) - } - - fn get_preloaded_partition(&mut self, partition: &CStr) -> Result<&[u8]> { - match self.partitions.get(partition.to_str()?) { - Some(FakePartition { - contents, - preloaded: true, - .. - }) => Ok(&contents[..]), - _ => Err(IoError::NotImplemented), - } - } - - fn validate_vbmeta_public_key( - &mut self, - public_key: &[u8], - public_key_metadata: Option<&[u8]>, - ) -> Result<bool> { - self.vbmeta_keys - .get(&(public_key, public_key_metadata)) - .ok_or(IoError::Io) - .map(|k| k.info.trusted) - } - - fn read_rollback_index(&mut self, location: usize) -> Result<u64> { - self.rollbacks.get(&location).ok_or(IoError::Io).copied() - } - - fn write_rollback_index(&mut self, location: usize, index: u64) -> Result<()> { - *(self.rollbacks.get_mut(&location).ok_or(IoError::Io)?) = index; - Ok(()) - } - - fn read_is_device_unlocked(&mut self) -> Result<bool> { - self.unlock_state.clone() - } - - #[cfg(feature = "uuid")] - fn get_unique_guid_for_partition(&mut self, partition: &CStr) -> Result<Uuid> { - self.partitions - .get(partition.to_str()?) - .map(|p| p.uuid) - .ok_or(IoError::NoSuchPartition) - } - - fn get_size_of_partition(&mut self, partition: &CStr) -> Result<u64> { - self.partitions - .get(partition.to_str()?) - .map(|p| u64::try_from(p.contents.len()).unwrap()) - .ok_or(IoError::NoSuchPartition) - } - - fn read_persistent_value(&mut self, name: &CStr, value: &mut [u8]) -> Result<usize> { - match self - .persistent_values - .get(name.to_str()?) - .ok_or(IoError::NoSuchValue)? - { - // If we were given enough space, write the value contents. - Ok(contents) if contents.len() <= value.len() => { - value[..contents.len()].clone_from_slice(contents); - Ok(contents.len()) - } - // Not enough space, tell the caller how much we need. - Ok(contents) => Err(IoError::InsufficientSpace(contents.len())), - // Simulated error, return it. - Err(e) => Err(e.clone()), - } - } - - fn write_persistent_value(&mut self, name: &CStr, value: &[u8]) -> Result<()> { - let name = name.to_str()?; - - // If the test requested a simulated error on this value, return it. - if let Some(Err(e)) = self.persistent_values.get(name) { - return Err(e.clone()); - } - - self.persistent_values - .insert(name.to_string(), Ok(value.to_vec())); - Ok(()) - } - - fn erase_persistent_value(&mut self, name: &CStr) -> Result<()> { - let name = name.to_str()?; - - // If the test requested a simulated error on this value, return it. - if let Some(Err(e)) = self.persistent_values.get(name) { - return Err(e.clone()); - } - - self.persistent_values.remove(name); - Ok(()) - } - - fn validate_public_key_for_partition( - &mut self, - partition: &CStr, - public_key: &[u8], - public_key_metadata: Option<&[u8]>, - ) -> Result<PublicKeyForPartitionInfo> { - let key = self - .vbmeta_keys - .get(&(public_key, public_key_metadata)) - .ok_or(IoError::Io)?; - - if let Some(for_partition) = key.for_partition { - if (for_partition == partition.to_str()?) { - // The key is registered for this partition; return its info. - return Ok(key.info); - } - } - - // No match. - Err(IoError::Io) - } - } - - /// Calls the `read_from_partition()` C callback the same way libavb would. - fn call_read_from_partition( - ops: &mut TestOps, - partition: &str, - offset: i64, - num_bytes: usize, - buffer: &mut [u8], - out_num_read: &mut usize, - ) -> AvbIOResult { - let mut user_data = UserData(ops); - let mut scoped_ops = ScopedAvbOps::new(&mut user_data); - let avb_ops = scoped_ops.as_mut(); - let part_name = CString::new(partition).unwrap(); - - // SAFETY: we've properly created and initialized all the raw pointers being passed into - // this C function. - unsafe { - avb_ops.read_from_partition.unwrap()( - avb_ops, - part_name.as_ptr(), - offset, - num_bytes, - buffer.as_mut_ptr() as *mut c_void, - out_num_read, - ) - } - } - - /// Calls the `get_preloaded_partition()` C callback the same way libavb would. - /// - /// # Safety - /// If `ops` provides preloaded data, `out_buffer` will become an alias to this data. The - /// lifetime bounds will ensure `ops` outlives `out_buffer`, but the caller must ensure the - /// preloaded data is not modified while `out_buffer` lives. - unsafe fn call_get_preloaded_partition<'a, 'b>( - ops: &'a mut TestOps, - partition: &str, - num_bytes: usize, - out_buffer: &mut &'b mut [u8], - out_num_bytes_preloaded: &mut usize, - ) -> AvbIOResult - where - 'a: 'b, - { - let mut user_data = UserData(ops); - let mut scoped_ops = ScopedAvbOps::new(&mut user_data); - let avb_ops = scoped_ops.as_mut(); - let part_name = CString::new(partition).unwrap(); - let mut out_ptr: *mut u8 = ptr::null_mut(); - - // SAFETY: - // * We've properly created and initialized all the raw pointers being passed in - // * We've set up lifetimes such that the `TestOps` which owns the data will outlive - // `out_buffer` which wraps the data. - let result = unsafe { - avb_ops.get_preloaded_partition.unwrap()( - avb_ops, - part_name.as_ptr(), - num_bytes, - &mut out_ptr, - out_num_bytes_preloaded, - ) - }; - - // If preload failed, libavb will see the null buffer and go to `read_from_partition()`. - // For our purposes we return `NO_SUCH_PARTITION` so we can detect and test this case. - if out_ptr.is_null() { - return AvbIOResult::AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION; - } - - // SAFETY: we've properly created the `out` variables. - *out_buffer = unsafe { slice::from_raw_parts_mut(out_ptr, *out_num_bytes_preloaded) }; - result - } - - /// Calls the `validate_vbmeta_public_key()` C callback the same way libavb would. - fn call_validate_vbmeta_public_key( - ops: &mut TestOps, - public_key: &[u8], - public_key_metadata: Option<&[u8]>, - out_is_trusted: &mut bool, - ) -> AvbIOResult { - let mut user_data = UserData(ops); - let mut scoped_ops = ScopedAvbOps::new(&mut user_data); - let avb_ops = scoped_ops.as_mut(); - let (metadata_ptr, metadata_size) = - public_key_metadata.map_or((ptr::null(), 0), |m| (m.as_ptr(), m.len())); - - // SAFETY: we've properly created and initialized all the raw pointers being passed in. - unsafe { - avb_ops.validate_vbmeta_public_key.unwrap()( - avb_ops, - public_key.as_ptr(), - public_key.len(), - metadata_ptr, - metadata_size, - out_is_trusted, - ) - } - } - - /// Calls the `read_rollback_index()` C callback the same way libavb would. - fn call_read_rollback_index( - ops: &mut impl Ops, - rollback_index_location: usize, - out_index: &mut u64, - ) -> AvbIOResult { - let mut user_data = UserData(ops); - let mut scoped_ops = ScopedAvbOps::new(&mut user_data); - let avb_ops = scoped_ops.as_mut(); - - // SAFETY: we've properly created and initialized all the raw pointers being passed in. - unsafe { avb_ops.read_rollback_index.unwrap()(avb_ops, rollback_index_location, out_index) } - } - - /// Calls the `write_rollback_index()` C callback the same way libavb would. - fn call_write_rollback_index( - ops: &mut impl Ops, - rollback_index_location: usize, - index: u64, - ) -> AvbIOResult { - let mut user_data = UserData(ops); - let mut scoped_ops = ScopedAvbOps::new(&mut user_data); - let avb_ops = scoped_ops.as_mut(); - - // SAFETY: we've properly created and initialized all the raw pointers being passed in. - unsafe { avb_ops.write_rollback_index.unwrap()(avb_ops, rollback_index_location, index) } - } - - /// Calls the `read_is_device_unlocked()` C callback the same way libavb would. - fn call_read_is_device_unlocked(ops: &mut impl Ops, out_is_unlocked: &mut bool) -> AvbIOResult { - let mut user_data = UserData(ops); - let mut scoped_ops = ScopedAvbOps::new(&mut user_data); - let avb_ops = scoped_ops.as_mut(); - - // SAFETY: we've properly created and initialized all the raw pointers being passed in. - unsafe { avb_ops.read_is_device_unlocked.unwrap()(avb_ops, out_is_unlocked) } - } - - /// Calls the `get_unique_guid_for_partition()` C callback the same way libavb would. - fn call_get_unique_guid_for_partition( - ops: &mut impl Ops, - partition: &str, - out_guid_str: &mut [u8], - ) -> AvbIOResult { - let mut user_data = UserData(ops); - let mut scoped_ops = ScopedAvbOps::new(&mut user_data); - let avb_ops = scoped_ops.as_mut(); - let part_name = CString::new(partition).unwrap(); - - // SAFETY: we've properly created and initialized all the raw pointers being passed in. - unsafe { - avb_ops.get_unique_guid_for_partition.unwrap()( - avb_ops, - part_name.as_ptr(), - out_guid_str.as_mut_ptr() as *mut _, - out_guid_str.len(), - ) - } - } - - /// Calls the `get_size_of_partition()` C callback the same way libavb would. - fn call_get_size_of_partition( - ops: &mut impl Ops, - partition: &str, - out_size: &mut u64, - ) -> AvbIOResult { - let mut user_data = UserData(ops); - let mut scoped_ops = ScopedAvbOps::new(&mut user_data); - let avb_ops = scoped_ops.as_mut(); - let part_name = CString::new(partition).unwrap(); - - // SAFETY: we've properly created and initialized all the raw pointers being passed in. - unsafe { avb_ops.get_size_of_partition.unwrap()(avb_ops, part_name.as_ptr(), out_size) } - } - - /// Calls the `read_persistent_value()` C callback the same way libavb would. - fn call_read_persistent_value( - ops: &mut impl Ops, - name: &str, - out_buffer: Option<&mut [u8]>, - out_num_bytes_read: &mut usize, - ) -> AvbIOResult { - let mut user_data = UserData(ops); - let mut scoped_ops = ScopedAvbOps::new(&mut user_data); - let avb_ops = scoped_ops.as_mut(); - let name = CString::new(name).unwrap(); - let (buffer_ptr, buffer_size) = - out_buffer.map_or((ptr::null_mut(), 0), |b| (b.as_mut_ptr(), b.len())); - - // SAFETY: we've properly created and initialized all the raw pointers being passed in. - unsafe { - avb_ops.read_persistent_value.unwrap()( - avb_ops, - name.as_ptr(), - buffer_size, - buffer_ptr, - out_num_bytes_read, - ) - } - } - - /// Calls the `write_persistent_value()` C callback the same way libavb would. - fn call_write_persistent_value( - ops: &mut impl Ops, - name: &str, - value: Option<&[u8]>, - ) -> AvbIOResult { - let mut user_data = UserData(ops); - let mut scoped_ops = ScopedAvbOps::new(&mut user_data); - let avb_ops = scoped_ops.as_mut(); - let name = CString::new(name).unwrap(); - let (value_ptr, value_size) = value.map_or((ptr::null(), 0), |v| (v.as_ptr(), v.len())); - - // SAFETY: we've properly created and initialized all the raw pointers being passed in. - unsafe { - avb_ops.write_persistent_value.unwrap()(avb_ops, name.as_ptr(), value_size, value_ptr) - } - } - - fn call_validate_public_key_for_partition( - ops: &mut impl Ops, - partition: &str, - public_key: &[u8], - public_key_metadata: Option<&[u8]>, - out_is_trusted: &mut bool, - out_rollback_index_location: &mut u32, - ) -> AvbIOResult { - let mut user_data = UserData(ops); - let mut scoped_ops = ScopedAvbOps::new(&mut user_data); - let avb_ops = scoped_ops.as_mut(); - let part_name = CString::new(partition).unwrap(); - let (metadata_ptr, metadata_size) = - public_key_metadata.map_or((ptr::null(), 0), |m| (m.as_ptr(), m.len())); - - // SAFETY: we've properly created and initialized all the raw pointers being passed in. - unsafe { - avb_ops.validate_public_key_for_partition.unwrap()( - avb_ops, - part_name.as_ptr(), - public_key.as_ptr(), - public_key.len(), - metadata_ptr, - metadata_size, - out_is_trusted, - out_rollback_index_location, - ) - } - } - - #[test] - fn test_read_from_partition() { - let mut ops = TestOps::default(); - ops.add_partition("foo", [1, 2, 3, 4]); - - let mut buffer: [u8; 8] = [0; 8]; - let mut bytes_read: usize = 0; - let result = call_read_from_partition(&mut ops, "foo", 0, 4, &mut buffer, &mut bytes_read); - - assert_eq!(result, AvbIOResult::AVB_IO_RESULT_OK); - assert_eq!(bytes_read, 4); - assert_eq!(buffer, [1, 2, 3, 4, 0, 0, 0, 0]); - } - - #[test] - fn test_read_from_partition_with_offset() { - let mut ops = TestOps::default(); - ops.add_partition("foo", [1, 2, 3, 4]); - - let mut buffer: [u8; 8] = [0; 8]; - let mut bytes_read: usize = 0; - let result = call_read_from_partition(&mut ops, "foo", 1, 2, &mut buffer, &mut bytes_read); - - assert_eq!(result, AvbIOResult::AVB_IO_RESULT_OK); - assert_eq!(bytes_read, 2); - assert_eq!(buffer, [2, 3, 0, 0, 0, 0, 0, 0]); - } - - #[test] - fn test_read_from_partition_negative_offset() { - let mut ops = TestOps::default(); - ops.add_partition("foo", [1, 2, 3, 4]); - - let mut buffer: [u8; 8] = [0; 8]; - let mut bytes_read: usize = 0; - let result = call_read_from_partition(&mut ops, "foo", -2, 2, &mut buffer, &mut bytes_read); - - assert_eq!(result, AvbIOResult::AVB_IO_RESULT_OK); - assert_eq!(bytes_read, 2); - assert_eq!(buffer, [3, 4, 0, 0, 0, 0, 0, 0]); - } - - #[test] - fn test_read_from_partition_truncate() { - let mut ops = TestOps::default(); - ops.add_partition("foo", [1, 2, 3, 4]); - - let mut buffer: [u8; 8] = [0; 8]; - let mut bytes_read: usize = 0; - let result = call_read_from_partition(&mut ops, "foo", 0, 8, &mut buffer, &mut bytes_read); - - assert_eq!(result, AvbIOResult::AVB_IO_RESULT_OK); - assert_eq!(bytes_read, 4); - assert_eq!(buffer, [1, 2, 3, 4, 0, 0, 0, 0]); - } - - #[test] - fn test_read_from_partition_unknown() { - let mut ops = TestOps::default(); - ops.add_partition("foo", [1, 2, 3, 4]); - - let mut buffer: [u8; 8] = [0; 8]; - let mut bytes_read: usize = 10; - let result = call_read_from_partition(&mut ops, "bar", 0, 8, &mut buffer, &mut bytes_read); - - assert_eq!(result, AvbIOResult::AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION); - assert_eq!(bytes_read, 0); - assert_eq!(buffer, [0; 8]); - } - - #[test] - fn test_get_preloaded_partition() { - let mut ops = TestOps::default(); - ops.add_partition("foo_preload", [1, 2, 3, 4]).preloaded = true; - - let mut contents: &mut [u8] = &mut []; - let mut size: usize = 0; - // SAFETY: preloaded data remain valid and unmodified while `contents` exists. - let result = unsafe { - call_get_preloaded_partition(&mut ops, "foo_preload", 4, &mut contents, &mut size) - }; - - assert_eq!(result, AvbIOResult::AVB_IO_RESULT_OK); - assert_eq!(size, 4); - assert_eq!(contents, [1, 2, 3, 4]); - } - - #[test] - fn test_get_preloaded_partition_truncate() { - let mut ops = TestOps::default(); - ops.add_partition("foo_preload", [1, 2, 3, 4]).preloaded = true; - - let mut contents: &mut [u8] = &mut []; - let mut size: usize = 0; - // SAFETY: preloaded data remain valid and unmodified while `contents` exists. - let result = unsafe { - call_get_preloaded_partition(&mut ops, "foo_preload", 2, &mut contents, &mut size) - }; - - assert_eq!(result, AvbIOResult::AVB_IO_RESULT_OK); - assert_eq!(size, 2); - assert_eq!(contents, [1, 2]); - } - - #[test] - fn test_get_preloaded_partition_unknown() { - let mut ops = TestOps::default(); - ops.add_partition("foo_preload", [1, 2, 3, 4]).preloaded = true; - - let mut contents: &mut [u8] = &mut []; - let mut size: usize = 10; - // SAFETY: requested preloaded data does not exist, no data alias is created. - let result = - unsafe { call_get_preloaded_partition(&mut ops, "bar", 4, &mut contents, &mut size) }; - - assert_eq!(result, AvbIOResult::AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION); - assert_eq!(size, 0); - assert_eq!(contents, []); - } - - #[test] - fn test_validate_vbmeta_public_key() { - let mut ops = TestOps::default(); - ops.add_vbmeta_key(b"testkey", None, true); - - let mut is_trusted = false; - let result = call_validate_vbmeta_public_key(&mut ops, b"testkey", None, &mut is_trusted); - - assert_eq!(result, AvbIOResult::AVB_IO_RESULT_OK); - assert!(is_trusted); - } - - #[test] - fn test_validate_vbmeta_public_key_with_metadata() { - let mut ops = TestOps::default(); - ops.add_vbmeta_key(b"testkey", Some(b"testmeta"), true); - - let mut is_trusted = false; - let result = call_validate_vbmeta_public_key( - &mut ops, - b"testkey", - Some(b"testmeta"), - &mut is_trusted, - ); - - assert_eq!(result, AvbIOResult::AVB_IO_RESULT_OK); - assert!(is_trusted); - } - - #[test] - fn test_validate_vbmeta_public_key_rejected() { - let mut ops = TestOps::default(); - ops.add_vbmeta_key(b"testkey", None, false); - - let mut is_trusted = true; - let result = call_validate_vbmeta_public_key(&mut ops, b"testkey", None, &mut is_trusted); - - assert_eq!(result, AvbIOResult::AVB_IO_RESULT_OK); - assert!(!is_trusted); - } - - #[test] - fn test_validate_vbmeta_public_key_error() { - let mut ops = TestOps::default(); - - let mut is_trusted = true; - let result = call_validate_vbmeta_public_key(&mut ops, b"testkey", None, &mut is_trusted); - - assert_eq!(result, AvbIOResult::AVB_IO_RESULT_ERROR_IO); - assert!(!is_trusted); - } - - #[test] - fn test_read_rollback_index() { - let mut ops = TestOps::default(); - ops.rollbacks.insert(10, 20); - - let mut index = 0u64; - assert_eq!( - call_read_rollback_index(&mut ops, 10, &mut index), - AvbIOResult::AVB_IO_RESULT_OK - ); - assert_eq!(index, 20); - } - - #[test] - fn test_read_rollback_index_not_found() { - let mut ops = TestOps::default(); - - let mut index = 30u64; - assert_eq!( - call_read_rollback_index(&mut ops, 10, &mut index), - AvbIOResult::AVB_IO_RESULT_ERROR_IO - ); - assert_eq!(index, 0); - } - - #[test] - fn test_write_rollback_index() { - let mut ops = TestOps::default(); - ops.rollbacks.insert(10, 20); - - assert_eq!( - call_write_rollback_index(&mut ops, 10, 30), - AvbIOResult::AVB_IO_RESULT_OK - ); - assert_eq!(*ops.rollbacks.get(&10).unwrap(), 30); - } - - #[test] - fn test_read_is_device_unlocked_yes() { - let mut ops = TestOps::default(); - ops.unlock_state = Ok(true); - - let mut unlocked = false; - assert_eq!( - call_read_is_device_unlocked(&mut ops, &mut unlocked), - AvbIOResult::AVB_IO_RESULT_OK - ); - assert_eq!(unlocked, true); - } - - #[test] - fn test_read_is_device_unlocked_no() { - let mut ops = TestOps::default(); - ops.unlock_state = Ok(false); - - let mut unlocked = true; - assert_eq!( - call_read_is_device_unlocked(&mut ops, &mut unlocked), - AvbIOResult::AVB_IO_RESULT_OK - ); - assert_eq!(unlocked, false); - } - - #[test] - fn test_read_is_device_unlocked_error() { - let mut ops = TestOps::default(); - ops.unlock_state = Err(IoError::Io); - - let mut unlocked = true; - assert_eq!( - call_read_is_device_unlocked(&mut ops, &mut unlocked), - AvbIOResult::AVB_IO_RESULT_ERROR_IO - ); - assert_eq!(unlocked, false); - } - - #[cfg(feature = "uuid")] - #[test] - fn test_get_unique_guid_for_partition() { - let mut ops = TestOps::default(); - ops.add_partition("foo", []).uuid = uuid!("01234567-89ab-cdef-0123-456789abcdef"); - - let mut uuid_str = [b'?'; UUID_CSTRING_LENGTH]; - assert_eq!( - call_get_unique_guid_for_partition(&mut ops, "foo", &mut uuid_str[..]), - AvbIOResult::AVB_IO_RESULT_OK - ); - assert_eq!( - String::from_utf8(uuid_str.into()).unwrap(), - "01234567-89ab-cdef-0123-456789abcdef\0" - ) - } - - #[cfg(feature = "uuid")] - #[test] - fn test_get_unique_guid_for_partition_unknown() { - let mut ops = TestOps::default(); - - let mut uuid_str = [b'?'; UUID_CSTRING_LENGTH]; - assert_eq!( - call_get_unique_guid_for_partition(&mut ops, "foo", &mut uuid_str[..]), - AvbIOResult::AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION - ); - } - - #[cfg(feature = "uuid")] - #[test] - fn test_get_unique_guid_for_partition_undersize_buffer() { - let mut ops = TestOps::default(); - ops.add_partition("foo", []).uuid = uuid!("01234567-89ab-cdef-0123-456789abcdef"); - - let mut uuid_str = [b'?'; UUID_CSTRING_LENGTH - 1]; - assert_eq!( - call_get_unique_guid_for_partition(&mut ops, "foo", &mut uuid_str[..]), - AvbIOResult::AVB_IO_RESULT_ERROR_OOM - ); - } - - #[cfg(not(feature = "uuid"))] - #[test] - fn test_get_unique_guid_for_partition_not_implemented() { - let mut ops = TestOps::default(); - ops.add_partition("foo", []); - - // Without the `uuid` feature enabled, get_unique_guid_for_partition() should - // unconditionally fail without trying to call any Ops functions. - let mut uuid_str = [b'?'; 0]; - assert_eq!( - call_get_unique_guid_for_partition(&mut ops, "foo", &mut uuid_str[..]), - AvbIOResult::AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION - ); - } - - #[test] - fn test_get_size_of_partition() { - let mut ops = TestOps::default(); - ops.add_partition("foo", [1, 2, 3, 4]); - - let mut size = 0; - let result = call_get_size_of_partition(&mut ops, "foo", &mut size); - - assert_eq!(result, AvbIOResult::AVB_IO_RESULT_OK); - assert_eq!(size, 4); - } - - #[test] - fn test_get_size_of_partition_unknown() { - let mut ops = TestOps::default(); - - let mut size = 10; - let result = call_get_size_of_partition(&mut ops, "foo", &mut size); - - assert_eq!(result, AvbIOResult::AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION); - assert_eq!(size, 0); - } - - #[test] - fn test_read_persistent_value() { - let mut ops = TestOps::default(); - ops.add_persistent_value("foo", Ok(b"1234")); - - let mut size = 0; - let mut buffer = [b'.'; 8]; - let result = call_read_persistent_value(&mut ops, "foo", Some(&mut buffer), &mut size); - - assert_eq!(result, AvbIOResult::AVB_IO_RESULT_OK); - assert_eq!(size, 4); - assert_eq!(&buffer[..], b"1234...."); - } - - #[test] - fn test_read_persistent_value_buffer_too_small() { - let mut ops = TestOps::default(); - ops.add_persistent_value("foo", Ok(b"1234")); - - let mut size = 0; - let mut buffer = [b'.'; 2]; - let result = call_read_persistent_value(&mut ops, "foo", Some(&mut buffer), &mut size); - - assert_eq!(result, AvbIOResult::AVB_IO_RESULT_ERROR_INSUFFICIENT_SPACE); - assert_eq!(size, 4); - assert_eq!(&buffer[..], b".."); - } - - #[test] - fn test_read_persistent_value_buffer_null() { - let mut ops = TestOps::default(); - ops.add_persistent_value("foo", Ok(b"1234")); - - let mut size = 0; - let result = call_read_persistent_value(&mut ops, "foo", None, &mut size); - - assert_eq!(result, AvbIOResult::AVB_IO_RESULT_ERROR_INSUFFICIENT_SPACE); - assert_eq!(size, 4); - } - - #[test] - fn test_read_persistent_value_error() { - let mut ops = TestOps::default(); - ops.add_persistent_value("foo", Err(IoError::Io)); - - let mut size = 10; - let mut buffer = [b'.'; 8]; - let result = call_read_persistent_value(&mut ops, "foo", Some(&mut buffer), &mut size); - - assert_eq!(result, AvbIOResult::AVB_IO_RESULT_ERROR_IO); - assert_eq!(size, 0); - assert_eq!(&buffer[..], b"........"); - } - - #[test] - fn test_read_persistent_value_unknown() { - let mut ops = TestOps::default(); - - let mut size = 10; - let mut buffer = [b'.'; 8]; - let result = call_read_persistent_value(&mut ops, "foo", Some(&mut buffer), &mut size); - - assert_eq!(result, AvbIOResult::AVB_IO_RESULT_ERROR_NO_SUCH_VALUE); - assert_eq!(size, 0); - assert_eq!(&buffer[..], b"........"); - } - - #[test] - fn test_write_persistent_value() { - let mut ops = TestOps::default(); - - let result = call_write_persistent_value(&mut ops, "foo", Some(b"1234")); - - assert_eq!(result, AvbIOResult::AVB_IO_RESULT_OK); - assert_eq!( - ops.persistent_values.get("foo").unwrap().as_ref().unwrap(), - b"1234" - ); - } - - #[test] - fn test_write_persistent_value_overwrite_existing() { - let mut ops = TestOps::default(); - ops.add_persistent_value("foo", Ok(b"1234")); - - let result = call_write_persistent_value(&mut ops, "foo", Some(b"5678")); - - assert_eq!(result, AvbIOResult::AVB_IO_RESULT_OK); - assert_eq!( - ops.persistent_values.get("foo").unwrap().as_ref().unwrap(), - b"5678" - ); - } - - #[test] - fn test_write_persistent_value_erase_existing() { - let mut ops = TestOps::default(); - ops.add_persistent_value("foo", Ok(b"1234")); - - let result = call_write_persistent_value(&mut ops, "foo", None); - - assert_eq!(result, AvbIOResult::AVB_IO_RESULT_OK); - assert!(ops.persistent_values.is_empty()); - } - - #[test] - fn test_write_persistent_value_error() { - let mut ops = TestOps::default(); - ops.add_persistent_value("foo", Err(IoError::NoSuchValue)); - - let result = call_write_persistent_value(&mut ops, "foo", Some(b"1234")); - - assert_eq!(result, AvbIOResult::AVB_IO_RESULT_ERROR_NO_SUCH_VALUE); - } - - #[test] - fn test_validate_public_key_for_partition() { - let mut ops = TestOps::default(); - ops.add_vbmeta_key_for_partition(b"testkey", None, true, "foo", 1); - - let mut is_trusted = false; - let mut rollback_location = 0; - let result = call_validate_public_key_for_partition( - &mut ops, - "foo", - b"testkey", - None, - &mut is_trusted, - &mut rollback_location, - ); - - assert_eq!(result, AvbIOResult::AVB_IO_RESULT_OK); - assert!(is_trusted); - assert_eq!(rollback_location, 1); - } - - #[test] - fn test_validate_public_key_for_partition_with_metadata() { - let mut ops = TestOps::default(); - ops.add_vbmeta_key_for_partition(b"testkey", Some(b"testmeta"), true, "foo", 1); - - let mut is_trusted = false; - let mut rollback_location = 0; - let result = call_validate_public_key_for_partition( - &mut ops, - "foo", - b"testkey", - Some(b"testmeta"), - &mut is_trusted, - &mut rollback_location, - ); - - assert_eq!(result, AvbIOResult::AVB_IO_RESULT_OK); - assert!(is_trusted); - assert_eq!(rollback_location, 1); - } - - #[test] - fn test_validate_public_key_for_partition_rejected() { - let mut ops = TestOps::default(); - ops.add_vbmeta_key_for_partition(b"testkey", None, false, "foo", 1); - - let mut is_trusted = true; - let mut rollback_location = 0; - let result = call_validate_public_key_for_partition( - &mut ops, - "foo", - b"testkey", - None, - &mut is_trusted, - &mut rollback_location, - ); - - assert_eq!(result, AvbIOResult::AVB_IO_RESULT_OK); - assert!(!is_trusted); - assert_eq!(rollback_location, 1); - } - - #[test] - fn test_validate_public_key_for_partition_unknown_key() { - let mut ops = TestOps::default(); - - let mut is_trusted = true; - let mut rollback_location = 10; - let result = call_validate_public_key_for_partition( - &mut ops, - "foo", - b"testkey", - None, - &mut is_trusted, - &mut rollback_location, - ); - - assert_ne!(result, AvbIOResult::AVB_IO_RESULT_OK); - assert!(!is_trusted); - assert_eq!(rollback_location, 0); - } - - #[test] - fn test_validate_public_key_for_partition_unknown_partition() { - let mut ops = TestOps::default(); - ops.add_vbmeta_key_for_partition(b"testkey", None, true, "foo", 1); - - let mut is_trusted = true; - let mut rollback_location = 10; - let result = call_validate_public_key_for_partition( - &mut ops, - "bar", - b"testkey", - None, - &mut is_trusted, - &mut rollback_location, - ); - - assert_ne!(result, AvbIOResult::AVB_IO_RESULT_OK); - assert!(!is_trusted); - assert_eq!(rollback_location, 0); - } -} diff --git a/rust/tests/test_ops.rs b/rust/tests/test_ops.rs new file mode 100644 index 0000000..1e1ad1b --- /dev/null +++ b/rust/tests/test_ops.rs @@ -0,0 +1,325 @@ +// Copyright 2023, The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Provides `avb::Ops` test fixtures. + +use avb::{IoError, Ops, PublicKeyForPartitionInfo}; +use std::{cmp::min, collections::HashMap, ffi::CStr}; +#[cfg(feature = "uuid")] +use uuid::Uuid; + +/// Common `Result` type for `IoError` errors. +type Result<T> = core::result::Result<T, IoError>; + +/// Represents a single fake partition. +#[derive(Default)] +pub struct FakePartition { + /// Partition contents. + pub contents: Vec<u8>, + + /// Whether the partition should report as preloaded or not. + pub preloaded: bool, + + /// Partition UUID. + #[cfg(feature = "uuid")] + pub uuid: Uuid, +} + +/// Fake vbmeta key state. +pub struct FakeVbmetaKeyState { + /// Key trust & rollback index info. + pub info: PublicKeyForPartitionInfo, + + /// If specified, indicates the specific partition this vbmeta is tied to (for + /// `validate_public_key_for_partition()`). + pub for_partition: Option<&'static str>, +} + +/// Fake `Ops` test fixture. +/// +/// The user is expected to set up the internal values to the desired device state - disk contents, +/// rollback indices, etc. This class then uses this state to implement the avb callback operations. +pub struct TestOps { + /// Partitions to provide to libavb callbacks. + pub partitions: HashMap<&'static str, FakePartition>, + + /// Vbmeta public keys as a map of {(key, metadata): state}. Querying unknown keys will + /// return `IoError::Io`. + /// + /// See `add_vbmeta_key*()` functions for simpler wrappers to inject these keys. + pub vbmeta_keys: HashMap<(Vec<u8>, Option<Vec<u8>>), FakeVbmetaKeyState>, + + /// Rollback indices. Accessing unknown locations will return `IoError::Io`. + pub rollbacks: HashMap<usize, u64>, + + /// Unlock state. Set an error to simulate IoError during access. + pub unlock_state: Result<bool>, + + /// Persistent named values. Set an error to simulate `IoError` during access. Writing + /// a non-existent persistent value will create it; to simulate `NoSuchValue` instead, + /// create an entry with `Err(IoError::NoSuchValue)` as the value. + pub persistent_values: HashMap<String, Result<Vec<u8>>>, +} + +impl TestOps { + /// Adds a partition with the given contents. + /// + /// Reduces boilerplate a bit by taking in a raw array and returning a &mut so tests can + /// do something like this: + /// + /// ``` + /// test_ops.add_partition("foo", [1, 2, 3, 4]); + /// test_ops.add_partition("bar", [0, 0]).preloaded = true; + /// ``` + pub fn add_partition<T: Into<Vec<u8>>>( + &mut self, + name: &'static str, + contents: T, + ) -> &mut FakePartition { + self.partitions.insert( + name, + FakePartition { + contents: contents.into(), + ..Default::default() + }, + ); + self.partitions.get_mut(name).unwrap() + } + + /// Adds a persistent value with the given state. + /// + /// Reduces boilerplate by allowing array input: + /// + /// ``` + /// test_ops.add_persistent_value("foo", Ok(b"contents")); + /// test_ops.add_persistent_value("bar", Err(IoError::NoSuchValue)); + /// ``` + pub fn add_persistent_value(&mut self, name: &str, contents: Result<&[u8]>) { + self.persistent_values + .insert(name.into(), contents.map(|b| b.into())); + } + + /// Adds a fake vbmeta key not tied to any partition. + pub fn add_vbmeta_key(&mut self, key: Vec<u8>, metadata: Option<Vec<u8>>, trusted: bool) { + self.vbmeta_keys.insert( + (key, metadata), + FakeVbmetaKeyState { + // `rollback_index_location` doesn't matter in this case, it will be read from + // the vbmeta blob. + info: PublicKeyForPartitionInfo { + trusted, + rollback_index_location: 0, + }, + for_partition: None, + }, + ); + } + + /// Adds a fake vbmeta key tied to the given partition and rollback index location. + pub fn add_vbmeta_key_for_partition( + &mut self, + key: Vec<u8>, + metadata: Option<Vec<u8>>, + trusted: bool, + partition: &'static str, + rollback_index_location: u32, + ) { + self.vbmeta_keys.insert( + (key, metadata), + FakeVbmetaKeyState { + info: PublicKeyForPartitionInfo { + trusted, + rollback_index_location, + }, + for_partition: Some(partition), + }, + ); + } +} + +impl Default for TestOps { + fn default() -> Self { + Self { + partitions: HashMap::new(), + vbmeta_keys: HashMap::new(), + rollbacks: HashMap::new(), + unlock_state: Err(IoError::Io), + persistent_values: HashMap::new(), + } + } +} + +impl Ops for TestOps { + fn read_from_partition( + &mut self, + partition: &CStr, + offset: i64, + buffer: &mut [u8], + ) -> Result<usize> { + let partition = self + .partitions + .get(partition.to_str()?) + .ok_or(IoError::NoSuchPartition)?; + + // We should never be trying to read a preloaded partition from disk since we already + // have it available in memory. + assert!(!partition.preloaded); + + let contents = &partition.contents; + + // Negative offset means count backwards from the end. + let offset = { + if offset < 0 { + offset + .checked_add(i64::try_from(contents.len()).unwrap()) + .unwrap() + } else { + offset + } + }; + if offset < 0 { + return Err(IoError::RangeOutsidePartition); + } + let offset = usize::try_from(offset).unwrap(); + + if offset >= contents.len() { + return Err(IoError::RangeOutsidePartition); + } + + // Truncating is allowed for reads past the partition end. + let end = min(offset.checked_add(buffer.len()).unwrap(), contents.len()); + let bytes_read = end - offset; + + buffer[..bytes_read].copy_from_slice(&contents[offset..end]); + Ok(bytes_read) + } + + fn get_preloaded_partition(&mut self, partition: &CStr) -> Result<&[u8]> { + match self.partitions.get(partition.to_str()?) { + Some(FakePartition { + contents, + preloaded: true, + .. + }) => Ok(&contents[..]), + _ => Err(IoError::NotImplemented), + } + } + + fn validate_vbmeta_public_key( + &mut self, + public_key: &[u8], + public_key_metadata: Option<&[u8]>, + ) -> Result<bool> { + self.vbmeta_keys + // The compiler can't match (&[u8], Option<&[u8]>) to keys of type + // (Vec<u8>, Option<Vec<u8>>) so we turn the &[u8] into vectors here. This is a bit + // inefficient, but it's simple which is more important for tests than efficiency. + .get(&(public_key.to_vec(), public_key_metadata.map(|m| m.to_vec()))) + .ok_or(IoError::Io) + .map(|k| k.info.trusted) + } + + fn read_rollback_index(&mut self, location: usize) -> Result<u64> { + self.rollbacks.get(&location).ok_or(IoError::Io).copied() + } + + fn write_rollback_index(&mut self, location: usize, index: u64) -> Result<()> { + *(self.rollbacks.get_mut(&location).ok_or(IoError::Io)?) = index; + Ok(()) + } + + fn read_is_device_unlocked(&mut self) -> Result<bool> { + self.unlock_state.clone() + } + + #[cfg(feature = "uuid")] + fn get_unique_guid_for_partition(&mut self, partition: &CStr) -> Result<Uuid> { + self.partitions + .get(partition.to_str()?) + .map(|p| p.uuid) + .ok_or(IoError::NoSuchPartition) + } + + fn get_size_of_partition(&mut self, partition: &CStr) -> Result<u64> { + self.partitions + .get(partition.to_str()?) + .map(|p| u64::try_from(p.contents.len()).unwrap()) + .ok_or(IoError::NoSuchPartition) + } + + fn read_persistent_value(&mut self, name: &CStr, value: &mut [u8]) -> Result<usize> { + match self + .persistent_values + .get(name.to_str()?) + .ok_or(IoError::NoSuchValue)? + { + // If we were given enough space, write the value contents. + Ok(contents) if contents.len() <= value.len() => { + value[..contents.len()].clone_from_slice(contents); + Ok(contents.len()) + } + // Not enough space, tell the caller how much we need. + Ok(contents) => Err(IoError::InsufficientSpace(contents.len())), + // Simulated error, return it. + Err(e) => Err(e.clone()), + } + } + + fn write_persistent_value(&mut self, name: &CStr, value: &[u8]) -> Result<()> { + let name = name.to_str()?; + + // If the test requested a simulated error on this value, return it. + if let Some(Err(e)) = self.persistent_values.get(name) { + return Err(e.clone()); + } + + self.persistent_values + .insert(name.to_string(), Ok(value.to_vec())); + Ok(()) + } + + fn erase_persistent_value(&mut self, name: &CStr) -> Result<()> { + let name = name.to_str()?; + + // If the test requested a simulated error on this value, return it. + if let Some(Err(e)) = self.persistent_values.get(name) { + return Err(e.clone()); + } + + self.persistent_values.remove(name); + Ok(()) + } + + fn validate_public_key_for_partition( + &mut self, + partition: &CStr, + public_key: &[u8], + public_key_metadata: Option<&[u8]>, + ) -> Result<PublicKeyForPartitionInfo> { + let key = self + .vbmeta_keys + .get(&(public_key.to_vec(), public_key_metadata.map(|m| m.to_vec()))) + .ok_or(IoError::Io)?; + + if let Some(for_partition) = key.for_partition { + if (for_partition == partition.to_str()?) { + // The key is registered for this partition; return its info. + return Ok(key.info); + } + } + + // No match. + Err(IoError::Io) + } +} diff --git a/rust/tests/tests.rs b/rust/tests/tests.rs new file mode 100644 index 0000000..a9df1c4 --- /dev/null +++ b/rust/tests/tests.rs @@ -0,0 +1,15 @@ +// Copyright 2023, The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +mod test_ops; |