| // 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. |
| |
| //! User callback APIs. |
| //! |
| //! This module is responsible for bridging the user-implemented callbacks so that they can be |
| //! written in safe Rust but libavb can call them from C. |
| |
| extern crate alloc; |
| |
| use crate::{error::result_to_io_enum, IoError, IoResult}; |
| use avb_bindgen::{AvbIOResult, AvbOps}; |
| use core::{ |
| cmp::min, |
| ffi::{c_char, c_void, CStr}, |
| marker::PhantomData, |
| ptr, slice, |
| }; |
| #[cfg(feature = "uuid")] |
| use uuid::Uuid; |
| |
| /// Base implementation-provided callbacks for verification. |
| /// |
| /// See libavb `AvbOps` for more complete documentation. |
| pub trait Ops { |
| /// Reads data from the requested partition on disk. |
| /// |
| /// # Arguments |
| /// * `partition`: partition name to read from. |
| /// * `offset`: offset in bytes within the partition to read from; a positive value indicates an |
| /// offset from the partition start, a negative value indicates a backwards offset |
| /// from the partition end. |
| /// * `buffer`: buffer to read data into. |
| /// |
| /// # Returns |
| /// The number of bytes actually read into `buffer` or an `IoError`. Reading less than |
| /// `buffer.len()` bytes is only allowed if the end of the partition was reached. |
| fn read_from_partition( |
| &mut self, |
| partition: &CStr, |
| offset: i64, |
| buffer: &mut [u8], |
| ) -> IoResult<usize>; |
| |
| /// Returns a reference to preloaded partition contents. |
| /// |
| /// This is an optional optimization if a partition has already been loaded to provide libavb |
| /// with a reference to the data rather than copying it as `read_from_partition()` would. |
| /// |
| /// May be left unimplemented if preloaded partitions are not used. |
| /// |
| /// # Arguments |
| /// * `partition`: partition name to read from. |
| /// |
| /// # Returns |
| /// * A reference to the entire partition contents if the partition has been preloaded. |
| /// * `Err<IoError::NotImplemented>` if the requested partition has not been preloaded; |
| /// verification will next attempt to load the partition via `read_from_partition()`. |
| /// * Any other `Err<IoError>` if an error occurred; verification will exit immediately. |
| fn get_preloaded_partition(&mut self, partition: &CStr) -> IoResult<&[u8]> { |
| Err(IoError::NotImplemented) |
| } |
| |
| /// Checks if the given public key is valid for vbmeta image signing. |
| /// |
| /// # Arguments |
| /// * `public_key`: the public key. |
| /// * `public_key_metadata`: public key metadata set by the `--public_key_metadata` arg in |
| /// `avbtool`, or None if no metadata was provided. |
| /// |
| /// # Returns |
| /// True if the given key is valid, false if it is not, `IoError` on error. |
| fn validate_vbmeta_public_key( |
| &mut self, |
| public_key: &[u8], |
| public_key_metadata: Option<&[u8]>, |
| ) -> IoResult<bool>; |
| |
| /// Reads the rollback index at the given location. |
| /// |
| /// # Arguments |
| /// * `rollback_index_location`: the rollback location. |
| /// |
| /// # Returns |
| /// The rollback index at this location or `IoError` on error. |
| fn read_rollback_index(&mut self, rollback_index_location: usize) -> IoResult<u64>; |
| |
| /// Writes the rollback index at the given location. |
| /// |
| /// This API is never actually used by libavb; the purpose of having it here is to group it |
| /// with `read_rollback_index()` and indicate to the implementation that it is responsible |
| /// for providing this functionality. However, it's up to the implementation to call this |
| /// function at the proper time after verification, which is a device-specific decision that |
| /// depends on things like the A/B strategy. See the libavb documentation for more information. |
| /// |
| /// # Arguments |
| /// * `rollback_index_location`: the rollback location. |
| /// * `index`: the rollback index to write. |
| /// |
| /// # Returns |
| /// Unit on success or `IoError` on error. |
| fn write_rollback_index(&mut self, rollback_index_location: usize, index: u64) -> IoResult<()>; |
| |
| /// Returns the device unlock state. |
| /// |
| /// # Returns |
| /// True if the device is unlocked, false if locked, `IoError` on error. |
| fn read_is_device_unlocked(&mut self) -> IoResult<bool>; |
| |
| /// Returns the GUID of the requested partition. |
| /// |
| /// This is only necessary if the kernel commandline requires GUID substitution, and is omitted |
| /// from the library by default to avoid unnecessary dependencies. To implement: |
| /// 1. Enable the `uuid` feature during compilation |
| /// 2. Provide the [`uuid` crate](https://docs.rs/uuid/latest/uuid/) dependency |
| /// |
| /// # Arguments |
| /// * `partition`: partition name. |
| /// |
| /// # Returns |
| /// The partition GUID or `IoError` on error. |
| #[cfg(feature = "uuid")] |
| fn get_unique_guid_for_partition(&mut self, partition: &CStr) -> IoResult<Uuid>; |
| |
| /// Returns the size of the requested partition. |
| /// |
| /// # Arguments |
| /// * `partition`: partition name. |
| /// |
| /// # Returns |
| /// The partition size in bytes or `IoError` on error. |
| fn get_size_of_partition(&mut self, partition: &CStr) -> IoResult<u64>; |
| |
| /// Reads the requested persistent value. |
| /// |
| /// This is only necessary if using persistent digests or the "managed restart and EIO" |
| /// hashtree verification mode; if verification is not using these features, this function will |
| /// never be called. |
| /// |
| /// # Arguments |
| /// * `name`: persistent value name. |
| /// * `value`: buffer to read persistent value into; if too small to hold the persistent value, |
| /// `IoError::InsufficientSpace` should be returned and this function will be called |
| /// again with an appropriately-sized buffer. This may be an empty slice if the |
| /// caller only wants to query the persistent value size. |
| /// |
| /// # Returns |
| /// * The number of bytes written into `value` on success. |
| /// * `IoError::NoSuchValue` if `name` is not a known persistent value. |
| /// * `IoError::InsufficientSpace` with the required size if the `value` buffer is too small. |
| /// * Any other `IoError` on failure. |
| fn read_persistent_value(&mut self, name: &CStr, value: &mut [u8]) -> IoResult<usize>; |
| |
| /// Writes the requested persistent value. |
| /// |
| /// This is only necessary if using persistent digests or the "managed restart and EIO" |
| /// hashtree verification mode; if verification is not using these features, this function will |
| /// never be called. |
| /// |
| /// # Arguments |
| /// * `name`: persistent value name. |
| /// * `value`: bytes to write as the new value. |
| /// |
| /// # Returns |
| /// * Unit on success. |
| /// * `IoError::NoSuchValue` if `name` is not a supported persistent value. |
| /// * `IoError::InvalidValueSize` if `value` is too large to save as a persistent value. |
| /// * Any other `IoError` on failure. |
| fn write_persistent_value(&mut self, name: &CStr, value: &[u8]) -> IoResult<()>; |
| |
| /// Erases the requested persistent value. |
| /// |
| /// This is only necessary if using persistent digests or the "managed restart and EIO" |
| /// hashtree verification mode; if verification is not using these features, this function will |
| /// never be called. |
| /// |
| /// If the requested persistent value is already erased, this function is a no-op and should |
| /// return `Ok(())`. |
| /// |
| /// # Arguments |
| /// * `name`: persistent value name. |
| /// |
| /// # Returns |
| /// * Unit on success. |
| /// * `IoError::NoSuchValue` if `name` is not a supported persistent value. |
| /// * Any other `IoError` on failure. |
| fn erase_persistent_value(&mut self, name: &CStr) -> IoResult<()>; |
| |
| /// Checks if the given public key is valid for the given partition. |
| /// |
| /// This is only used if the "no vbmeta" verification flag is passed, meaning the partitions |
| /// to verify have an embedded vbmeta image rather than locating it in a separate vbmeta |
| /// partition. If this flag is not used, the `validate_vbmeta_public_key()` callback is used |
| /// instead, and this function will never be called. |
| /// |
| /// # Arguments |
| /// * `partition`: partition name. |
| /// * `public_key`: the public key. |
| /// * `public_key_metadata`: public key metadata set by the `--public_key_metadata` arg in |
| /// `avbtool`, or None if no metadata was provided. |
| /// |
| /// # Returns |
| /// On success, returns a `PublicKeyForPartitionInfo` object indicating whether the given |
| /// key is trusted and its rollback index location. |
| /// |
| /// On failure, returns an error. |
| fn validate_public_key_for_partition( |
| &mut self, |
| partition: &CStr, |
| public_key: &[u8], |
| public_key_metadata: Option<&[u8]>, |
| ) -> IoResult<PublicKeyForPartitionInfo>; |
| } |
| |
| /// Info returned from `validare_public_key_for_partition()`. |
| #[derive(Clone, Copy, Debug)] |
| pub struct PublicKeyForPartitionInfo { |
| /// Whether the key is trusted for the given partition.. |
| pub trusted: bool, |
| /// The rollback index to use for the given partition. |
| pub rollback_index_location: u32, |
| } |
| |
| /// Helper to pass user-provided `Ops` through libavb via the `user_data` pointer. |
| /// |
| /// This is a bit tricky in Rust, we can't just cast `Ops` to `void*` and back directly because |
| /// `Ops` is a trait to be implemented by the user, which means we don't know the concrete type |
| /// at this point and must use dynamic dispatch. |
| /// |
| /// However, dynamic dispatch in Rust requires a "fat pointer" (2 pointers) which cannot be cast to |
| /// a single `void*`. So instead, we wrap the dynamic dispatch inside this struct which _can_ be |
| /// represented as a single `void*`, and then we can unwrap it again to fetch the original |
| /// `&dyn Ops`. |
| /// |
| /// A more typical approach is to use `Box` to heap-allocate the `&dyn` and then pass the `Box` |
| /// around, but we want to avoid allocation as much as possible. |
| /// |
| /// Control flow: |
| /// ``` |
| /// user libavb_rs libavb |
| /// ----------------------------------------------------------------------------------------------- |
| /// create `Ops` (Rust) with |
| /// callback implementations |
| /// ----> |
| /// `UserData::new()` wraps: |
| /// `Ops` (Rust/fat) -> |
| /// `UserData` (Rust/thin) |
| /// |
| /// `ScopedAvbOps` makes |
| /// `AvbOps` (C) containing: |
| /// 1. `UserData*` (C) |
| /// 2. our callbacks (C) |
| /// ----> |
| /// execute `AvbOps` (C) |
| /// callbacks as needed |
| /// <---- |
| /// `as_ops()` unwraps: |
| /// `AvbOps` (C) -> |
| /// `UserData` (Rust/thin) -> |
| /// `Ops` (Rust/fat) |
| /// |
| /// Convert callback data to |
| /// safe Rust |
| /// <---- |
| /// perform `Ops` (Rust) |
| /// callback |
| /// ``` |
| pub(crate) struct UserData<'a>(&'a mut dyn Ops); |
| |
| impl<'a> UserData<'a> { |
| pub(crate) fn new(ops: &'a mut dyn Ops) -> Self { |
| Self(ops) |
| } |
| } |
| |
| /// Wraps the C `AvbOps` struct with lifetime information for the compiler. |
| pub(crate) struct ScopedAvbOps<'a> { |
| /// `AvbOps` holds a raw pointer to `UserData` with no lifetime information. |
| avb_ops: AvbOps, |
| /// This provides the necessary lifetime information so the compiler can make sure that |
| /// the `UserData` stays alive at least as long as we do. |
| _user_data: PhantomData<UserData<'a>>, |
| } |
| |
| impl<'a> ScopedAvbOps<'a> { |
| pub(crate) fn new(user_data: &'a mut UserData<'a>) -> Self { |
| Self { |
| avb_ops: AvbOps { |
| // Rust won't transitively cast so we need to cast twice manually, but the compiler |
| // is smart enough to deduce the types we need. |
| user_data: user_data as *mut _ as *mut _, |
| ab_ops: ptr::null_mut(), // Deprecated, no need to support. |
| atx_ops: ptr::null_mut(), // TODO: support optional ATX. |
| read_from_partition: Some(read_from_partition), |
| get_preloaded_partition: Some(get_preloaded_partition), |
| write_to_partition: None, // Not needed, only used for deprecated A/B. |
| validate_vbmeta_public_key: Some(validate_vbmeta_public_key), |
| read_rollback_index: Some(read_rollback_index), |
| write_rollback_index: Some(write_rollback_index), |
| read_is_device_unlocked: Some(read_is_device_unlocked), |
| get_unique_guid_for_partition: Some(get_unique_guid_for_partition), |
| get_size_of_partition: Some(get_size_of_partition), |
| read_persistent_value: Some(read_persistent_value), |
| write_persistent_value: Some(write_persistent_value), |
| validate_public_key_for_partition: Some(validate_public_key_for_partition), |
| }, |
| _user_data: PhantomData, |
| } |
| } |
| } |
| |
| impl<'a> AsMut<AvbOps> for ScopedAvbOps<'a> { |
| fn as_mut(&mut self) -> &mut AvbOps { |
| &mut self.avb_ops |
| } |
| } |
| |
| /// Extracts the user-provided `Ops` from a raw `AvbOps`. |
| /// |
| /// # Arguments |
| /// * `avb_ops`: The raw `AvbOps` pointer used by libavb. |
| /// |
| /// # Returns |
| /// The Rust `Ops` extracted from `avb_ops.user_data`. |
| /// |
| /// # Safety |
| /// Only call this function on an `AvbOps` created via `ScopedAvbOps`. |
| /// |
| /// Additionally, this should be considered a mutable borrow of the contained `Ops`: |
| /// * do not return back to libavb while still holding the returned reference, or it will result |
| /// in a dangling reference |
| /// * do not call this again until the previous `Ops` goes out of scope, or it will violate Rust's |
| /// mutable borrowing rules |
| /// |
| /// In practice, these conditions are met since we call this exactly once in each callback |
| /// to extract the `Ops`, and drop it at callback completion. |
| unsafe fn as_ops<'a>(avb_ops: *mut AvbOps) -> IoResult<&'a mut dyn Ops> { |
| // SAFETY: we created this AvbOps object and passed it to libavb so we know it meets all |
| // the criteria for `as_mut()`. |
| let avb_ops = unsafe { avb_ops.as_mut() }.ok_or(IoError::Io)?; |
| // Cast the void* `user_data` back to a UserData*. |
| let user_data = avb_ops.user_data as *mut UserData; |
| // SAFETY: we created this UserData object and passed it to libavb so we know it meets all |
| // the criteria for `as_mut()`. |
| Ok(unsafe { user_data.as_mut() }.ok_or(IoError::Io)?.0) |
| } |
| |
| /// Converts a non-NULL `ptr` to `()`, NULL to `Err(IoError::Io)`. |
| fn check_nonnull<T>(ptr: *const T) -> IoResult<()> { |
| match ptr.is_null() { |
| true => Err(IoError::Io), |
| false => Ok(()), |
| } |
| } |
| |
| /// Wraps a callback to convert the given `IoResult<>` to raw `AvbIOResult` for libavb. |
| /// |
| /// See corresponding `try_*` function docs. |
| unsafe extern "C" fn read_from_partition( |
| ops: *mut AvbOps, |
| partition: *const c_char, |
| offset: i64, |
| num_bytes: usize, |
| buffer: *mut c_void, |
| out_num_read: *mut usize, |
| ) -> AvbIOResult { |
| result_to_io_enum(try_read_from_partition( |
| ops, |
| partition, |
| offset, |
| num_bytes, |
| buffer, |
| out_num_read, |
| )) |
| } |
| |
| /// 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()`. |
| /// * `buffer` must adhere to the requirements of `slice::from_raw_parts_mut()`. |
| /// * `out_num_read` must adhere to the requirements of `ptr::write()`. |
| unsafe fn try_read_from_partition( |
| ops: *mut AvbOps, |
| partition: *const c_char, |
| offset: i64, |
| num_bytes: usize, |
| buffer: *mut c_void, |
| out_num_read: *mut usize, |
| ) -> IoResult<()> { |
| check_nonnull(partition)?; |
| check_nonnull(buffer)?; |
| check_nonnull(out_num_read)?; |
| |
| // Initialize the output variables first in case something fails. |
| // SAFETY: |
| // * we've checked that the pointer is non-NULL. |
| // * libavb gives us a properly-allocated `out_num_read`. |
| unsafe { ptr::write(out_num_read, 0) }; |
| |
| // 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) }; |
| // SAFETY: |
| // * we've checked that the pointer is non-NULL. |
| // * libavb gives us a properly-allocated `buffer` with size `num_bytes`. |
| // * 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(buffer as *mut u8, num_bytes) }; |
| |
| let bytes_read = ops.read_from_partition(partition, offset, buffer)?; |
| // SAFETY: |
| // * we've checked that the pointer is non-NULL. |
| // * libavb gives us a properly-allocated `out_num_read`. |
| unsafe { ptr::write(out_num_read, bytes_read) }; |
| Ok(()) |
| } |
| |
| /// Wraps a callback to convert the given `IoResult<>` to raw `AvbIOResult` for libavb. |
| /// |
| /// See corresponding `try_*` function docs. |
| unsafe extern "C" fn get_preloaded_partition( |
| ops: *mut AvbOps, |
| partition: *const c_char, |
| num_bytes: usize, |
| out_pointer: *mut *mut u8, |
| out_num_bytes_preloaded: *mut usize, |
| ) -> AvbIOResult { |
| result_to_io_enum(try_get_preloaded_partition( |
| ops, |
| partition, |
| num_bytes, |
| out_pointer, |
| out_num_bytes_preloaded, |
| )) |
| } |
| |
| /// 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()`. |
| /// * `out_pointer` and `out_num_bytes_preloaded` must adhere to the requirements of `ptr::write()`. |
| /// * `out_pointer` will become an alias to the `ops` preloaded partition data, so the preloaded |
| /// data must remain valid and unmodified while `out_pointer` exists. |
| unsafe fn try_get_preloaded_partition( |
| ops: *mut AvbOps, |
| partition: *const c_char, |
| num_bytes: usize, |
| out_pointer: *mut *mut u8, |
| out_num_bytes_preloaded: *mut usize, |
| ) -> IoResult<()> { |
| check_nonnull(partition)?; |
| check_nonnull(out_pointer)?; |
| check_nonnull(out_num_bytes_preloaded)?; |
| |
| // Initialize the output variables first in case something fails. |
| // SAFETY: |
| // * we've checked that the pointers are non-NULL. |
| // * libavb gives us properly-aligned and sized `out` vars. |
| unsafe { |
| ptr::write(out_pointer, ptr::null_mut()); |
| ptr::write(out_num_bytes_preloaded, 0); |
| } |
| |
| // 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) }; |
| |
| match ops.get_preloaded_partition(partition) { |
| // SAFETY: |
| // * we've checked that the pointers are non-NULL. |
| // * libavb gives us properly-aligned and sized `out` vars. |
| Ok(contents) => unsafe { |
| ptr::write( |
| out_pointer, |
| // Warning: we are casting an immutable &[u8] to a mutable *u8. If libavb actually |
| // modified these contents this could cause undefined behavior, but it just reads. |
| // TODO: can we change the libavb API to take a const*? |
| contents.as_ptr() as *mut u8, |
| ); |
| ptr::write( |
| out_num_bytes_preloaded, |
| // Truncate here if necessary, we may have more preloaded data than libavb needs. |
| min(contents.len(), num_bytes), |
| ); |
| }, |
| // No-op if this partition is not preloaded, we've already reset the out variables to |
| // indicate preloaded data is not available. |
| Err(IoError::NotImplemented) => (), |
| Err(e) => return Err(e), |
| }; |
| Ok(()) |
| } |
| |
| /// Wraps a callback to convert the given `IoResult<>` to raw `AvbIOResult` for libavb. |
| /// |
| /// See corresponding `try_*` function docs. |
| unsafe extern "C" fn validate_vbmeta_public_key( |
| ops: *mut AvbOps, |
| public_key_data: *const u8, |
| public_key_length: usize, |
| public_key_metadata: *const u8, |
| public_key_metadata_length: usize, |
| out_is_trusted: *mut bool, |
| ) -> AvbIOResult { |
| result_to_io_enum(try_validate_vbmeta_public_key( |
| ops, |
| public_key_data, |
| public_key_length, |
| public_key_metadata, |
| public_key_metadata_length, |
| out_is_trusted, |
| )) |
| } |
| |
| /// Bounces the C callback into the user-provided Rust implementation. |
| /// |
| /// # Safety |
| /// * `ops` must have been created via `ScopedAvbOps`. |
| /// * `public_key_*` args must adhere to the requirements of `slice::from_raw_parts()`. |
| /// * `out_is_trusted` must adhere to the requirements of `ptr::write()`. |
| unsafe fn try_validate_vbmeta_public_key( |
| ops: *mut AvbOps, |
| public_key_data: *const u8, |
| public_key_length: usize, |
| public_key_metadata: *const u8, |
| public_key_metadata_length: usize, |
| out_is_trusted: *mut bool, |
| ) -> IoResult<()> { |
| check_nonnull(public_key_data)?; |
| check_nonnull(out_is_trusted)?; |
| |
| // Initialize the output variables first in case something fails. |
| // SAFETY: |
| // * we've checked that the pointer is non-NULL. |
| // * libavb gives us a properly-allocated `out_is_trusted`. |
| unsafe { ptr::write(out_is_trusted, false) }; |
| |
| // 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 `public_key_data` with size `public_key_length`. |
| // * we only access the contents via the returned slice. |
| // * the returned slice is not held past the scope of this callback. |
| let public_key = unsafe { slice::from_raw_parts(public_key_data, public_key_length) }; |
| let metadata = check_nonnull(public_key_metadata).ok().map( |
| // SAFETY: |
| // * we've checked that the pointer is non-NULL. |
| // * libavb gives us a properly-allocated `public_key_metadata` with size |
| // `public_key_metadata_length`. |
| // * we only access the contents via the returned slice. |
| // * the returned slice is not held past the scope of this callback. |
| |_| unsafe { slice::from_raw_parts(public_key_metadata, public_key_metadata_length) }, |
| ); |
| |
| let trusted = ops.validate_vbmeta_public_key(public_key, metadata)?; |
| |
| // SAFETY: |
| // * we've checked that the pointer is non-NULL. |
| // * libavb gives us a properly-allocated `out_is_trusted`. |
| unsafe { ptr::write(out_is_trusted, trusted) }; |
| Ok(()) |
| } |
| |
| /// Wraps a callback to convert the given `IoResult<>` to raw `AvbIOResult` for libavb. |
| /// |
| /// See corresponding `try_*` function docs. |
| unsafe extern "C" fn read_rollback_index( |
| ops: *mut AvbOps, |
| rollback_index_location: usize, |
| out_rollback_index: *mut u64, |
| ) -> AvbIOResult { |
| result_to_io_enum(try_read_rollback_index( |
| ops, |
| rollback_index_location, |
| out_rollback_index, |
| )) |
| } |
| |
| /// Bounces the C callback into the user-provided Rust implementation. |
| /// |
| /// # Safety |
| /// * `ops` must have been created via `ScopedAvbOps`. |
| /// * `out_rollback_index` must adhere to the requirements of `ptr::write()`. |
| unsafe fn try_read_rollback_index( |
| ops: *mut AvbOps, |
| rollback_index_location: usize, |
| out_rollback_index: *mut u64, |
| ) -> IoResult<()> { |
| check_nonnull(out_rollback_index)?; |
| |
| // Initialize the output variables first in case something fails. |
| // SAFETY: |
| // * we've checked that the pointer is non-NULL. |
| // * libavb gives us a properly-allocated `out_rollback_index`. |
| unsafe { ptr::write(out_rollback_index, 0) }; |
| |
| // 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 index = ops.read_rollback_index(rollback_index_location)?; |
| |
| // SAFETY: |
| // * we've checked that the pointer is non-NULL. |
| // * libavb gives us a properly-allocated `out_rollback_index`. |
| unsafe { ptr::write(out_rollback_index, index) }; |
| Ok(()) |
| } |
| |
| /// Wraps a callback to convert the given `IoResult<>` to raw `AvbIOResult` for libavb. |
| /// |
| /// See corresponding `try_*` function docs. |
| unsafe extern "C" fn write_rollback_index( |
| ops: *mut AvbOps, |
| rollback_index_location: usize, |
| rollback_index: u64, |
| ) -> AvbIOResult { |
| result_to_io_enum(try_write_rollback_index( |
| ops, |
| rollback_index_location, |
| rollback_index, |
| )) |
| } |
| |
| /// Bounces the C callback into the user-provided Rust implementation. |
| /// |
| /// # Safety |
| /// * `ops` must have been created via `ScopedAvbOps`. |
| unsafe fn try_write_rollback_index( |
| ops: *mut AvbOps, |
| rollback_index_location: usize, |
| rollback_index: u64, |
| ) -> IoResult<()> { |
| // 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) }?; |
| ops.write_rollback_index(rollback_index_location, rollback_index) |
| } |
| |
| /// Wraps a callback to convert the given `IoResult<>` to raw `AvbIOResult` for libavb. |
| /// |
| /// See corresponding `try_*` function docs. |
| unsafe extern "C" fn read_is_device_unlocked( |
| ops: *mut AvbOps, |
| out_is_unlocked: *mut bool, |
| ) -> AvbIOResult { |
| result_to_io_enum(try_read_is_device_unlocked(ops, out_is_unlocked)) |
| } |
| |
| /// Bounces the C callback into the user-provided Rust implementation. |
| /// |
| /// # Safety |
| /// * `ops` must have been created via `ScopedAvbOps`. |
| /// * `out_is_unlocked` must adhere to the requirements of `ptr::write()`. |
| unsafe fn try_read_is_device_unlocked( |
| ops: *mut AvbOps, |
| out_is_unlocked: *mut bool, |
| ) -> IoResult<()> { |
| check_nonnull(out_is_unlocked)?; |
| |
| // Initialize the output variables first in case something fails. |
| // SAFETY: |
| // * we've checked that the pointer is non-NULL. |
| // * libavb gives us a properly-allocated `out_is_unlocked`. |
| unsafe { ptr::write(out_is_unlocked, false) }; |
| |
| // 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 unlocked = ops.read_is_device_unlocked()?; |
| |
| // SAFETY: |
| // * we've checked that the pointer is non-NULL. |
| // * libavb gives us a properly-allocated `out_is_unlocked`. |
| unsafe { ptr::write(out_is_unlocked, unlocked) }; |
| Ok(()) |
| } |
| |
| /// Wraps a callback to convert the given `IoResult<>` to raw `AvbIOResult` for libavb. |
| /// |
| /// See corresponding `try_*` function docs. |
| 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 { |
| result_to_io_enum(try_get_unique_guid_for_partition( |
| ops, |
| partition, |
| guid_buf, |
| guid_buf_size, |
| )) |
| } |
| |
| /// 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()`. |
| unsafe fn try_get_unique_guid_for_partition( |
| ops: *mut AvbOps, |
| partition: *const c_char, |
| guid_buf: *mut c_char, |
| guid_buf_size: usize, |
| ) -> IoResult<()> { |
| check_nonnull(partition)?; |
| check_nonnull(guid_buf)?; |
| |
| // 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) }; |
| // 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) }; |
| |
| // Initialize the output buffer to the empty string. |
| // |
| // When the `uuid` feature is not selected, the user doesn't need commandline GUIDs but libavb |
| // may still attempt to inject the `vmbeta` or `boot` partition GUIDs into the commandline, |
| // depending on the verification settings. In order to satisfy libavb's requirements we must: |
| // * write a nul-terminated string to avoid undefined behavior (empty string is sufficient) |
| // * return `Ok(())` or verification will fail |
| 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(()) |
| } |
| |
| /// Wraps a callback to convert the given `IoResult<>` to raw `AvbIOResult` for libavb. |
| /// |
| /// See corresponding `try_*` function docs. |
| unsafe extern "C" fn get_size_of_partition( |
| ops: *mut AvbOps, |
| partition: *const c_char, |
| out_size_num_bytes: *mut u64, |
| ) -> AvbIOResult { |
| result_to_io_enum(try_get_size_of_partition( |
| ops, |
| partition, |
| out_size_num_bytes, |
| )) |
| } |
| |
| /// 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()`. |
| /// * `out_size_num_bytes` must adhere to the requirements of `ptr::write()`. |
| unsafe fn try_get_size_of_partition( |
| ops: *mut AvbOps, |
| partition: *const c_char, |
| out_size_num_bytes: *mut u64, |
| ) -> IoResult<()> { |
| check_nonnull(partition)?; |
| check_nonnull(out_size_num_bytes)?; |
| |
| // Initialize the output variables first in case something fails. |
| // SAFETY: |
| // * we've checked that the pointer is non-NULL. |
| // * libavb gives us a properly-allocated `out_size_num_bytes`. |
| unsafe { ptr::write(out_size_num_bytes, 0) }; |
| |
| // 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 size = ops.get_size_of_partition(partition)?; |
| |
| // SAFETY: |
| // * we've checked that the pointer is non-NULL. |
| // * libavb gives us a properly-allocated `out_size_num_bytes`. |
| unsafe { ptr::write(out_size_num_bytes, size) }; |
| Ok(()) |
| } |
| |
| /// Wraps a callback to convert the given `IoResult<>` to raw `AvbIOResult` for libavb. |
| /// |
| /// See corresponding `try_*` function docs. |
| unsafe extern "C" fn read_persistent_value( |
| ops: *mut AvbOps, |
| name: *const c_char, |
| buffer_size: usize, |
| out_buffer: *mut u8, |
| out_num_bytes_read: *mut usize, |
| ) -> AvbIOResult { |
| result_to_io_enum(try_read_persistent_value( |
| ops, |
| name, |
| buffer_size, |
| out_buffer, |
| out_num_bytes_read, |
| )) |
| } |
| |
| /// Bounces the C callback into the user-provided Rust implementation. |
| /// |
| /// # Safety |
| /// * `ops` must have been created via `ScopedAvbOps`. |
| /// * `name` must adhere to the requirements of `CStr::from_ptr()`. |
| /// * `out_buffer` must adhere to the requirements of `slice::from_raw_parts_mut()`. |
| /// * `out_num_bytes_read` must adhere to the requirements of `ptr::write()`. |
| unsafe fn try_read_persistent_value( |
| ops: *mut AvbOps, |
| name: *const c_char, |
| buffer_size: usize, |
| out_buffer: *mut u8, |
| out_num_bytes_read: *mut usize, |
| ) -> IoResult<()> { |
| check_nonnull(name)?; |
| check_nonnull(out_num_bytes_read)?; |
| |
| // Initialize the output variables first in case something fails. |
| // SAFETY: |
| // * we've checked that the pointer is non-NULL. |
| // * libavb gives us a properly-allocated `out_num_bytes_read`. |
| unsafe { ptr::write(out_num_bytes_read, 0) }; |
| |
| // 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 `name`. |
| // * the string contents are not modified while the returned `&CStr` exists. |
| // * the returned `&CStr` is not held past the scope of this callback. |
| let name = unsafe { CStr::from_ptr(name) }; |
| let mut empty: [u8; 0] = []; |
| let value = match out_buffer.is_null() { |
| // NULL buffer => empty slice, used to just query the value size. |
| true => &mut empty, |
| false => { |
| // SAFETY: |
| // * we've checked that the pointer is non-NULL. |
| // * libavb gives us a properly-allocated `out_buffer` with size `buffer_size`. |
| // * we only access the contents via the returned slice. |
| // * the returned slice is not held past the scope of this callback. |
| unsafe { slice::from_raw_parts_mut(out_buffer, buffer_size) } |
| } |
| }; |
| |
| let result = ops.read_persistent_value(name, value); |
| // On success or insufficient space we need to write the property size back. |
| if let Ok(size) | Err(IoError::InsufficientSpace(size)) = result { |
| // SAFETY: |
| // * we've checked that the pointer is non-NULL. |
| // * libavb gives us a properly-allocated `out_num_bytes_read`. |
| unsafe { ptr::write(out_num_bytes_read, size) }; |
| }; |
| // We've written the size back and can drop it now. |
| result.map(|_| ()) |
| } |
| |
| /// Wraps a callback to convert the given `IoResult<>` to raw `AvbIOResult` for libavb. |
| /// |
| /// See corresponding `try_*` function docs. |
| unsafe extern "C" fn write_persistent_value( |
| ops: *mut AvbOps, |
| name: *const c_char, |
| value_size: usize, |
| value: *const u8, |
| ) -> AvbIOResult { |
| result_to_io_enum(try_write_persistent_value(ops, name, value_size, value)) |
| } |
| |
| /// Bounces the C callback into the user-provided Rust implementation. |
| /// |
| /// # Safety |
| /// * `ops` must have been created via `ScopedAvbOps`. |
| /// * `name` must adhere to the requirements of `CStr::from_ptr()`. |
| /// * `out_buffer` must adhere to the requirements of `slice::from_raw_parts()`. |
| unsafe fn try_write_persistent_value( |
| ops: *mut AvbOps, |
| name: *const c_char, |
| value_size: usize, |
| value: *const u8, |
| ) -> IoResult<()> { |
| check_nonnull(name)?; |
| |
| // 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 `name`. |
| // * the string contents are not modified while the returned `&CStr` exists. |
| // * the returned `&CStr` is not held past the scope of this callback. |
| let name = unsafe { CStr::from_ptr(name) }; |
| |
| if value_size == 0 { |
| ops.erase_persistent_value(name) |
| } else { |
| check_nonnull(value)?; |
| // SAFETY: |
| // * we've checked that the pointer is non-NULL. |
| // * libavb gives us a properly-allocated `value` with size `value_size`. |
| // * we only access the contents via the returned slice. |
| // * the returned slice is not held past the scope of this callback. |
| let value = unsafe { slice::from_raw_parts(value, value_size) }; |
| ops.write_persistent_value(name, value) |
| } |
| } |
| |
| /// Wraps a callback to convert the given `IoResult<>` to raw `AvbIOResult` for libavb. |
| /// |
| /// See corresponding `try_*` function docs. |
| unsafe extern "C" fn validate_public_key_for_partition( |
| ops: *mut AvbOps, |
| partition: *const c_char, |
| public_key_data: *const u8, |
| public_key_length: usize, |
| public_key_metadata: *const u8, |
| public_key_metadata_length: usize, |
| out_is_trusted: *mut bool, |
| out_rollback_index_location: *mut u32, |
| ) -> AvbIOResult { |
| result_to_io_enum(try_validate_public_key_for_partition( |
| ops, |
| partition, |
| public_key_data, |
| public_key_length, |
| public_key_metadata, |
| public_key_metadata_length, |
| out_is_trusted, |
| out_rollback_index_location, |
| )) |
| } |
| |
| /// 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()`. |
| /// * `public_key_*` args must adhere to the requirements of `slice::from_raw_parts()`. |
| /// * `out_*` must adhere to the requirements of `ptr::write()`. |
| unsafe fn try_validate_public_key_for_partition( |
| ops: *mut AvbOps, |
| partition: *const c_char, |
| public_key_data: *const u8, |
| public_key_length: usize, |
| public_key_metadata: *const u8, |
| public_key_metadata_length: usize, |
| out_is_trusted: *mut bool, |
| out_rollback_index_location: *mut u32, |
| ) -> IoResult<()> { |
| check_nonnull(partition)?; |
| check_nonnull(public_key_data)?; |
| check_nonnull(out_is_trusted)?; |
| check_nonnull(out_rollback_index_location)?; |
| |
| // Initialize the output variables first in case something fails. |
| // SAFETY: |
| // * we've checked that the pointers are non-NULL. |
| // * libavb gives us a properly-allocated `out_*`. |
| unsafe { |
| ptr::write(out_is_trusted, false); |
| ptr::write(out_rollback_index_location, 0); |
| } |
| |
| // 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) }; |
| // SAFETY: |
| // * we've checked that the pointer is non-NULL. |
| // * libavb gives us a properly-allocated `public_key_data` with size `public_key_length`. |
| // * we only access the contents via the returned slice. |
| // * the returned slice is not held past the scope of this callback. |
| let public_key = unsafe { slice::from_raw_parts(public_key_data, public_key_length) }; |
| let metadata = check_nonnull(public_key_metadata).ok().map( |
| // SAFETY: |
| // * we've checked that the pointer is non-NULL. |
| // * libavb gives us a properly-allocated `public_key_metadata` with size |
| // `public_key_metadata_length`. |
| // * we only access the contents via the returned slice. |
| // * the returned slice is not held past the scope of this callback. |
| |_| unsafe { slice::from_raw_parts(public_key_metadata, public_key_metadata_length) }, |
| ); |
| |
| let key_info = ops.validate_public_key_for_partition(partition, public_key, metadata)?; |
| |
| // SAFETY: |
| // * we've checked that the pointers are non-NULL. |
| // * libavb gives us a properly-allocated `out_*`. |
| unsafe { |
| ptr::write(out_is_trusted, key_info.trusted); |
| ptr::write( |
| out_rollback_index_location, |
| key_info.rollback_index_location, |
| ); |
| } |
| Ok(()) |
| } |