| // 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. |
| |
| //! Verification APIs. |
| //! |
| //! This module is responsible for all the conversions required to pass information between |
| //! libavb and Rust for verifying images. |
| |
| use crate::{ |
| error::{ |
| slot_verify_enum_to_result, vbmeta_verify_enum_to_result, SlotVerifyError, |
| SlotVerifyNoDataResult, SlotVerifyResult, VbmetaVerifyResult, |
| }, |
| ops, Ops, |
| }; |
| use avb_bindgen::{ |
| avb_slot_verify, avb_slot_verify_data_free, AvbPartitionData, AvbSlotVerifyData, AvbVBMetaData, |
| }; |
| use core::{ |
| ffi::{c_char, CStr}, |
| fmt, |
| ptr::{self, null, null_mut, NonNull}, |
| slice, |
| }; |
| |
| /// `AvbHashtreeErrorMode`; see libavb docs for descriptions of each mode. |
| pub use avb_bindgen::AvbHashtreeErrorMode as HashtreeErrorMode; |
| /// `AvbSlotVerifyFlags`; see libavb docs for descriptions of each flag. |
| pub use avb_bindgen::AvbSlotVerifyFlags as SlotVerifyFlags; |
| |
| /// Returns `Err(SlotVerifyError::Internal)` if the given pointer is `NULL`. |
| fn check_nonnull<T>(ptr: *const T) -> SlotVerifyNoDataResult<()> { |
| match ptr.is_null() { |
| true => Err(SlotVerifyError::Internal), |
| false => Ok(()), |
| } |
| } |
| |
| /// Wraps a raw C `AvbVBMetaData` struct. |
| /// |
| /// This provides a Rust safe view over the raw data; no copies are made. |
| // |
| // `repr(transparent)` guarantees that size and alignment match the underlying type exactly, so that |
| // we can cast the array of `AvbVBMetaData` structs directly into a slice of `VbmetaData` wrappers |
| // without allocating any additional memory. |
| #[repr(transparent)] |
| pub struct VbmetaData(AvbVBMetaData); |
| |
| impl VbmetaData { |
| /// Validates the internal data so the accessors can be fail-free. This should be called on all |
| /// `VbmetaData` objects before they are handed to the user. |
| /// |
| /// Normally this would be done in a `new()` function but we never instantiate `VbmetaData` |
| /// objects ourselves, we just cast them from the C structs provided by libavb. |
| /// |
| /// Returns `Err(SlotVerifyError::Internal)` on failure. |
| fn validate(&self) -> SlotVerifyNoDataResult<()> { |
| check_nonnull(self.0.partition_name)?; |
| check_nonnull(self.0.vbmeta_data)?; |
| Ok(()) |
| } |
| |
| /// Returns the name of the partition this vbmeta image was loaded from. |
| pub fn partition_name(&self) -> &CStr { |
| // SAFETY: |
| // * libavb gives us a properly-allocated and nul-terminated string. |
| // * the returned contents remain valid and unmodified while we exist. |
| unsafe { CStr::from_ptr(self.0.partition_name) } |
| } |
| |
| /// Returns the vbmeta image contents. |
| pub fn data(&self) -> &[u8] { |
| // SAFETY: |
| // * libavb gives us a properly-allocated byte array. |
| // * the returned contents remain valid and unmodified while we exist. |
| unsafe { slice::from_raw_parts(self.0.vbmeta_data, self.0.vbmeta_size) } |
| } |
| |
| /// Returns the vbmeta verification result. |
| pub fn verify_result(&self) -> VbmetaVerifyResult<()> { |
| vbmeta_verify_enum_to_result(self.0.verify_result) |
| } |
| } |
| |
| impl fmt::Display for VbmetaData { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| write!(f, "{:?}: {:?}", self.partition_name(), self.verify_result()) |
| } |
| } |
| |
| /// Forwards to `Display` formatting; the default `Debug` formatting implementation isn't very |
| /// useful as it's mostly raw pointer addresses. |
| impl fmt::Debug for VbmetaData { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| fmt::Display::fmt(self, f) |
| } |
| } |
| |
| /// Wraps a raw C `AvbPartitionData` struct. |
| /// |
| /// This provides a Rust safe view over the raw data; no copies are made. |
| #[repr(transparent)] |
| pub struct PartitionData(AvbPartitionData); |
| |
| impl PartitionData { |
| /// Validates the internal data so the accessors can be fail-free. This should be called on all |
| /// `PartitionData` objects before they are handed to the user. |
| /// |
| /// Normally this would be done in a `new()` function but we never instantiate `PartitionData` |
| /// objects ourselves, we just cast them from the C structs provided by libavb. |
| /// |
| /// Returns `Err(SlotVerifyError::Internal)` on failure. |
| fn validate(&self) -> SlotVerifyNoDataResult<()> { |
| check_nonnull(self.0.partition_name)?; |
| check_nonnull(self.0.data)?; |
| Ok(()) |
| } |
| |
| /// Returns the name of the partition this image was loaded from. |
| pub fn partition_name(&self) -> &CStr { |
| // SAFETY: |
| // * libavb gives us a properly-allocated and nul-terminated string. |
| // * the returned contents remain valid and unmodified while we exist. |
| unsafe { CStr::from_ptr(self.0.partition_name) } |
| } |
| |
| /// Returns the image contents. |
| pub fn data(&self) -> &[u8] { |
| // SAFETY: |
| // * libavb gives us a properly-allocated byte array. |
| // * the returned contents remain valid and unmodified while we exist. |
| unsafe { slice::from_raw_parts(self.0.data, self.0.data_size) } |
| } |
| |
| /// Returns whether this partition was preloaded via `get_preloaded_partition()`. |
| pub fn preloaded(&self) -> bool { |
| self.0.preloaded |
| } |
| |
| /// Returns the verification result for this partition. |
| /// |
| /// Only top-level `Verification` errors will contain valid `SlotVerifyData` objects, if this |
| /// individual partition returns a `Verification` error the error will always contain `None`. |
| pub fn verify_result(&self) -> SlotVerifyNoDataResult<()> { |
| slot_verify_enum_to_result(self.0.verify_result) |
| } |
| } |
| |
| /// A "(p)" after the partition name indicates a preloaded partition. |
| impl fmt::Display for PartitionData { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| write!( |
| f, |
| "{:?}{}: {:?}", |
| self.partition_name(), |
| match self.preloaded() { |
| true => "(p)", |
| false => "", |
| }, |
| self.verify_result() |
| ) |
| } |
| } |
| |
| /// Forwards to `Display` formatting; the default `Debug` formatting implementation isn't very |
| /// useful as it's mostly raw pointer addresses. |
| impl fmt::Debug for PartitionData { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| fmt::Display::fmt(self, f) |
| } |
| } |
| |
| /// Wraps a raw C `AvbSlotVerifyData` struct. |
| /// |
| /// This provides a Rust safe view over the raw data; no copies are made. |
| pub struct SlotVerifyData<'a> { |
| /// Internally owns the underlying data and deletes it on drop. |
| raw_data: NonNull<AvbSlotVerifyData>, |
| |
| /// This provides the necessary lifetime borrow so the compiler can make sure that the `Ops` |
| /// stays alive at least as long as we do, since it owns any preloaded partition data. |
| _ops: &'a dyn Ops, |
| } |
| |
| // Useful so that `SlotVerifyError`, which may hold a `SlotVerifyData`, can derive `PartialEq`. |
| impl<'a> PartialEq for SlotVerifyData<'a> { |
| fn eq(&self, other: &Self) -> bool { |
| // A `SlotVerifyData` uniquely owns the underlying data so is only equal to itself. |
| ptr::eq(self, other) |
| } |
| } |
| |
| impl<'a> Eq for SlotVerifyData<'a> {} |
| |
| impl<'a> SlotVerifyData<'a> { |
| /// Creates a `SlotVerifyData` wrapping the given raw `AvbSlotVerifyData`. |
| /// |
| /// The returned `SlotVerifyData` will take ownership of the given `AvbSlotVerifyData` and |
| /// properly release the allocated memory when it drops. |
| /// |
| /// # Arguments |
| /// * `data`: a `AvbSlotVerifyData` object created by libavb. |
| /// * `ops`: the user-provided callback ops; borrowing this here ensures that any preloaded |
| /// partition data stays unmodified while `data` is wrapping it. |
| /// |
| /// # Returns |
| /// The new object, or `Err(SlotVerifyError::Internal)` if the data looks invalid. |
| /// |
| /// # Safety |
| /// * `data` must be a valid `AvbSlotVerifyData` object created by libavb |
| /// * after calling this function, do not access `data` except through the returned object |
| unsafe fn new( |
| data: *mut AvbSlotVerifyData, |
| ops: &'a mut dyn Ops, |
| ) -> SlotVerifyNoDataResult<Self> { |
| let ret = Self { |
| raw_data: NonNull::new(data).ok_or(SlotVerifyError::Internal)?, |
| _ops: ops, |
| }; |
| |
| // Validate all the contained data here so accessors will never fail. |
| // SAFETY: `raw_data` points to a valid `AvbSlotVerifyData` object owned by us. |
| let data = unsafe { ret.raw_data.as_ref() }; |
| check_nonnull(data.ab_suffix)?; |
| check_nonnull(data.vbmeta_images)?; |
| check_nonnull(data.loaded_partitions)?; |
| check_nonnull(data.cmdline)?; |
| ret.vbmeta_data().iter().try_for_each(|v| v.validate())?; |
| ret.partition_data().iter().try_for_each(|i| i.validate())?; |
| |
| Ok(ret) |
| } |
| |
| /// Returns the slot suffix string. |
| pub fn ab_suffix(&self) -> &CStr { |
| // SAFETY: |
| // * `raw_data` points to a valid `AvbSlotVerifyData` object owned by us. |
| // * libavb gives us a properly-allocated and nul-terminated string. |
| // * the returned contents remain valid and unmodified while we exist. |
| unsafe { CStr::from_ptr(self.raw_data.as_ref().ab_suffix) } |
| } |
| |
| /// Returns the `VbmetaData` structs. |
| pub fn vbmeta_data(&self) -> &[VbmetaData] { |
| // SAFETY: |
| // * `raw_data` points to a valid `AvbSlotVerifyData` object owned by us. |
| // * libavb gives us a properly-allocated array of structs. |
| // * the returned contents remain valid and unmodified while we exist. |
| unsafe { |
| slice::from_raw_parts( |
| // `repr(transparent)` means we can cast between these types. |
| self.raw_data.as_ref().vbmeta_images as *const VbmetaData, |
| self.raw_data.as_ref().num_vbmeta_images, |
| ) |
| } |
| } |
| |
| /// Returns the `PartitionData` structs. |
| pub fn partition_data(&self) -> &[PartitionData] { |
| // SAFETY: |
| // * `raw_data` points to a valid `AvbSlotVerifyData` object owned by us. |
| // * libavb gives us a properly-allocated array of structs. |
| // * the returned contents remain valid and unmodified while we exist. |
| unsafe { |
| slice::from_raw_parts( |
| // `repr(transparent)` means we can cast between these types. |
| self.raw_data.as_ref().loaded_partitions as *const PartitionData, |
| self.raw_data.as_ref().num_loaded_partitions, |
| ) |
| } |
| } |
| |
| /// Returns the kernel commandline. |
| pub fn cmdline(&self) -> &CStr { |
| // SAFETY: |
| // * `raw_data` points to a valid `AvbSlotVerifyData` object owned by us. |
| // * libavb gives us a properly-allocated and nul-terminated string. |
| // * the returned contents remain valid and unmodified while we exist. |
| unsafe { CStr::from_ptr(self.raw_data.as_ref().cmdline) } |
| } |
| |
| /// Returns the rollback indices. |
| pub fn rollback_indexes(&self) -> &[u64] { |
| // SAFETY: `raw_data` points to a valid `AvbSlotVerifyData` object owned by us. |
| &unsafe { self.raw_data.as_ref() }.rollback_indexes[..] |
| } |
| |
| /// Returns the resolved hashtree error mode. |
| pub fn resolved_hashtree_error_mode(&self) -> HashtreeErrorMode { |
| // SAFETY: `raw_data` points to a valid `AvbSlotVerifyData` object owned by us. |
| unsafe { self.raw_data.as_ref() }.resolved_hashtree_error_mode |
| } |
| } |
| |
| impl<'a> Drop for SlotVerifyData<'a> { |
| fn drop(&mut self) { |
| // SAFETY: |
| // * `raw_data` points to a valid `AvbSlotVerifyData` object owned by us. |
| // * libavb created the object and requires us to free it by calling this function. |
| unsafe { avb_slot_verify_data_free(self.raw_data.as_ptr()) }; |
| } |
| } |
| |
| /// Implements `Display` to make it easy to print some basic information. |
| /// |
| /// This implementation will print the slot, partition name, and verification status for all |
| /// vbmetadata and images. |
| impl<'a> fmt::Display for SlotVerifyData<'a> { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| write!( |
| f, |
| "slot: {:?}, vbmeta: {:?}, images: {:?}", |
| self.ab_suffix(), |
| self.vbmeta_data(), |
| self.partition_data() |
| ) |
| } |
| } |
| |
| /// Forwards to `Display` formatting; the default `Debug` formatting implementation isn't very |
| /// useful as it's mostly raw pointer addresses. |
| impl<'a> fmt::Debug for SlotVerifyData<'a> { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| fmt::Display::fmt(self, f) |
| } |
| } |
| |
| /// Performs verification of the requested images. |
| /// |
| /// This wraps `avb_slot_verify()` for Rust, see the original docs for more details. |
| /// |
| /// # Arguments |
| /// * `ops`: implementation of the required verification callbacks. |
| /// * `requested_partition`: the set of partition names to verify. |
| /// * `ab_suffix`: the slot suffix to append to the partition names, or None. |
| /// * `flags`: flags to configure verification. |
| /// * `hashtree_error_mode`: desired error handling behavior. |
| /// |
| /// # Returns |
| /// `Ok` if verification completed successfully, the verification error otherwise. `SlotVerifyData` |
| /// will be returned in two cases: |
| /// |
| /// 1. always returned on verification success |
| /// 2. if `AllowVerificationError` is given in `flags`, it will also be returned on verification |
| /// failure |
| /// |
| /// If a `SlotVerifyData` is returned, it will borrow the provided `ops`. This is to ensure that |
| /// any data shared by `SlotVerifyData` and `ops` - in particular preloaded partition contents - |
| /// is not modified until `SlotVerifyData` is dropped. |
| pub fn slot_verify<'a>( |
| ops: &'a mut dyn Ops, |
| requested_partitions: &[&CStr], |
| ab_suffix: Option<&CStr>, |
| flags: SlotVerifyFlags, |
| hashtree_error_mode: HashtreeErrorMode, |
| ) -> SlotVerifyResult<'a, SlotVerifyData<'a>> { |
| let mut user_data = ops::UserData::new(ops); |
| let mut scoped_ops = ops::ScopedAvbOps::new(&mut user_data); |
| let avb_ops = scoped_ops.as_mut(); |
| |
| // libavb detects the size of the `requested_partitions` array by NULL termination. Expecting |
| // the Rust caller to do this would make the API much more awkward, so we populate a |
| // NULL-terminated array of c-string pointers ourselves. For now we use a fixed-sized array |
| // rather than dynamically allocating, 8 should be more than enough. |
| const MAX_PARTITION_ARRAY_SIZE: usize = 8 + 1; // Max 8 partition names + 1 for NULL terminator. |
| if requested_partitions.len() >= MAX_PARTITION_ARRAY_SIZE { |
| return Err(SlotVerifyError::Internal); |
| } |
| let mut partitions_array = [null() as *const c_char; MAX_PARTITION_ARRAY_SIZE]; |
| for (source, dest) in requested_partitions.iter().zip(partitions_array.iter_mut()) { |
| *dest = source.as_ptr(); |
| } |
| |
| // To be more Rust idiomatic we allow `ab_suffix` to be `None`, but libavb requires a valid |
| // pointer to an empty string in this case, not NULL. |
| let ab_suffix = ab_suffix.unwrap_or(CStr::from_bytes_with_nul(b"\0").unwrap()); |
| |
| let mut out_data: *mut AvbSlotVerifyData = null_mut(); |
| |
| // Call the libavb verification function. |
| // |
| // Note: do not use the `?` operator to return-early here; in some cases `out_data` will be |
| // allocated and returned even on verification failure, and we need to take ownership of it |
| // or else the memory will leak. |
| // |
| // SAFETY: |
| // * we've properly initialized all objects passed into libavb. |
| // * if `out_data` is non-null on return, we take ownership via `SlotVerifyData`. |
| let result = slot_verify_enum_to_result(unsafe { |
| avb_slot_verify( |
| avb_ops, |
| partitions_array.as_ptr(), |
| ab_suffix.as_ptr(), |
| flags, |
| hashtree_error_mode, |
| &mut out_data, |
| ) |
| }); |
| |
| // If `out_data` is non-null, take ownership so memory gets released on drop. |
| let data = match out_data.is_null() { |
| true => None, |
| // SAFETY: `out_data` was properly allocated by libavb and ownership has passed to us. |
| false => Some(unsafe { SlotVerifyData::new(out_data, ops)? }), |
| }; |
| |
| // Fold the verify data into the result. |
| match result { |
| // libavb will always provide verification data on success. |
| Ok(()) => Ok(data.unwrap()), |
| // Data may also be provided on verification failure, fold it into the error. |
| Err(SlotVerifyError::Verification(None)) => Err(SlotVerifyError::Verification(data)), |
| // No other error provides verification data. |
| Err(e) => Err(e), |
| } |
| } |