diff options
author | David Pursell <dpursell@google.com> | 2024-04-03 10:09:03 -0700 |
---|---|---|
committer | David Pursell <dpursell@google.com> | 2024-04-23 10:03:07 -0700 |
commit | ea6cfa7402d5786e65c1a89c0dcff69181baebf6 (patch) | |
tree | df2640c80e39b19b0302788c3436d064bd2621cc | |
parent | 1f25d8a42a4e7120d63b2b10b2bf4b3722cc5efd (diff) | |
download | avb-ea6cfa7402d5786e65c1a89c0dcff69181baebf6.tar.gz |
libavb_rs: split Ops/CertOps into separate traits
CertOps is now a separate trait rather than a supertrait of Ops, which
is a little simpler and allows more usage flexibility at the cost of
runtime checking for cert support rather than compile-time.
Additionally, cleans up the internal bookkeeping mechanisms to simplify
`ScopedAvbOps` and `UserData` into a single `OpsBridge` struct.
Bug: b/320543206
Test: atest libavb_rs_test libavb_rs_uuid_test libavb_rs_unittest libavb_rs_uuid_unittest
Change-Id: I2f4ba35f55a8610929dcb57d96dcd133b06a7aea
-rw-r--r-- | rust/src/cert.rs | 17 | ||||
-rw-r--r-- | rust/src/ops.rs | 206 | ||||
-rw-r--r-- | rust/src/verify.rs | 9 |
3 files changed, 124 insertions, 108 deletions
diff --git a/rust/src/cert.rs b/rust/src/cert.rs index adf682e..52be5bf 100644 --- a/rust/src/cert.rs +++ b/rust/src/cert.rs @@ -107,8 +107,8 @@ pub const CERT_PIK_VERSION_LOCATION: usize = avb_bindgen::AVB_CERT_PIK_VERSION_L /// If using libavb_cert, make sure no vbmetas use this location, it must be reserved for the PSK. pub const CERT_PSK_VERSION_LOCATION: usize = avb_bindgen::AVB_CERT_PSK_VERSION_LOCATION as usize; -/// libavb_cert additional callbacks. -pub trait CertOps<'a>: Ops<'a> { +/// libavb_cert extension callbacks. +pub trait CertOps { /// Reads the device's permanent attributes. /// /// The full permanent attributes are not required to be securely stored; corruption of this @@ -198,14 +198,14 @@ pub trait CertOps<'a>: Ops<'a> { /// specific partitions. /// /// # Arguments -/// * `ops`: the `CertOps` callback implementations. +/// * `ops`: the `Ops` callback implementations, which must provide a `cert_ops()` implementation. /// * `public_key`: the public key. /// * `public_key_metadata`: public key metadata. /// /// # Returns /// True if the given key is valid, false if it is not, `IoError` on error. pub fn cert_validate_vbmeta_public_key( - _ops: &mut dyn CertOps, + _ops: &mut dyn Ops, _public_key: &[u8], _public_key_metadata: Option<&[u8]>, ) -> IoResult<bool> { @@ -237,15 +237,18 @@ pub fn cert_generate_unlock_challenge( /// device's permanent attributes. /// /// # Arguments -/// * `cert_ops`: the `CertOps` callback implementations. +/// * `ops`: the `Ops` callback implementations, which must provide a `cert_ops()` implementation. /// * `credential`: the signed unlock credential to verify. /// /// # Returns /// * `Ok(true)` if the credential validated /// * `Ok(false)` if it failed validation -/// * `Err(IoError)` on `cert_ops` failure +/// * `Err(IoError)` on `ops` failure pub fn cert_validate_unlock_credential( - _cert_ops: &mut dyn CertOps, + // Note: in the libavb C API this function takes an `AvbCertOps` rather than `AvbOps`, but + // the implementation requires both, so we need an `Ops` here. This is also more consistent + // with `validate_vbmeta_public_key()` which similarly requires both but takes `AvbOps`. + _ops: &mut dyn Ops, _credential: &CertUnlockCredential, ) -> IoResult<bool> { // TODO(b/320543206): implement diff --git a/rust/src/ops.rs b/rust/src/ops.rs index 10f2b1a..88e9d95 100644 --- a/rust/src/ops.rs +++ b/rust/src/ops.rs @@ -19,12 +19,13 @@ extern crate alloc; -use crate::{error::result_to_io_enum, IoError, IoResult}; -use avb_bindgen::{AvbIOResult, AvbOps}; +use crate::{error::result_to_io_enum, CertOps, IoError, IoResult}; +use avb_bindgen::{AvbCertOps, AvbIOResult, AvbOps}; use core::{ cmp::min, ffi::{c_char, c_void, CStr}, - marker::PhantomData, + marker::PhantomPinned, + pin::Pin, ptr, slice, }; #[cfg(feature = "uuid")] @@ -238,6 +239,22 @@ pub trait Ops<'a> { public_key: &[u8], public_key_metadata: Option<&[u8]>, ) -> IoResult<PublicKeyForPartitionInfo>; + + /// Returns the libavb_cert certificate ops if supported. + /// + /// The libavb_cert extension provides some additional key management and authentication support + /// APIs, see the cert module documentation for more info. + /// + /// The default implementation returns `None` to disable cert APIs. + /// + /// Commonly when using certs the same struct will implement both `Ops` and `CertOps`, in which + /// case this can just return `Some(self)`. + /// + /// # Returns + /// The `CertOps` object, or `None` if not supported. + fn cert_ops(&mut self) -> Option<&mut dyn CertOps> { + None + } } /// Info returned from `validate_public_key_for_partition()`. @@ -249,80 +266,48 @@ pub struct PublicKeyForPartitionInfo { pub rollback_index_location: u32, } -/// Helper to pass user-provided `Ops` through libavb via the `user_data` pointer. +/// Provides the logic to bridge between the libavb C ops and our Rust ops. /// -/// 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. +/// This struct internally owns the C ops structs, and borrows ownership of the Rust `Ops`. This +/// allows us to translate in both directions, from Rust -> C to call into libavb and then back +/// from C -> Rust when servicing the callbacks. /// -/// 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`. +/// The general control flow look like this: /// -/// 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 +/// ```ignore +/// user calls a Rust API with their `Ops` { +/// we create `OpsBridge` wrapping `Ops` and the C structs +/// we call into C libavb API { +/// libavb makes C ops callback { +/// we retrieve `OpsBridge` from the callback `user_data` param +/// we make the corresponding Rust `Ops` callback +/// } +/// ... libavb makes more callbacks as needed ... +/// } +/// } /// ``` /// /// # Lifetimes /// * `'o`: lifetime of the `Ops` object /// * `'p`: lifetime of any preloaded data provided by `Ops` -pub(crate) struct UserData<'o, 'p>(&'o mut dyn Ops<'p>); - -impl<'o, 'p> UserData<'o, 'p> { - pub(crate) fn new(ops: &'o mut dyn Ops<'p>) -> Self { - Self(ops) - } -} - -/// Wraps the C `AvbOps` struct with lifetime information for the compiler. -pub(crate) struct ScopedAvbOps<'o, 'p> { - /// `AvbOps` holds a raw pointer to `UserData` with no lifetime information. +pub(crate) struct OpsBridge<'o, 'p> { + /// C `AvbOps` holds a raw pointer to the `OpsBridge` so we can retrieve it during callbacks. 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<'o, 'p>>, + /// When using libavb_cert, C `AvbOps`/`AvbCertOps` hold circular pointers to each other. + cert_ops: AvbCertOps, + /// Rust `Ops` implementation, which may also provide Rust `CertOps`. + rust_ops: &'o mut dyn Ops<'p>, + /// Remove the `Unpin` trait to indicate this type has address-sensitive state. + _pin: PhantomPinned, } -impl<'o, 'p> ScopedAvbOps<'o, 'p> { - pub(crate) fn new(user_data: &mut UserData<'o, 'p>) -> Self { +impl<'o, 'p> OpsBridge<'o, 'p> { + pub(crate) fn new(ops: &'o mut dyn Ops<'p>) -> 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. - cert_ops: ptr::null_mut(), // TODO: support optional libavb_cert. + user_data: ptr::null_mut(), // Set at the time of use. + ab_ops: ptr::null_mut(), // Deprecated, no need to support. + cert_ops: ptr::null_mut(), // Set at the time of use. 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. @@ -336,14 +321,43 @@ impl<'o, 'p> ScopedAvbOps<'o, 'p> { write_persistent_value: Some(write_persistent_value), validate_public_key_for_partition: Some(validate_public_key_for_partition), }, - _user_data: PhantomData, + cert_ops: AvbCertOps { + ops: ptr::null_mut(), // Set at the time of use. + // TODO(b/320543206): implement the callbacks. + read_permanent_attributes: None, + read_permanent_attributes_hash: None, + set_key_version: None, + get_random: None, + }, + rust_ops: ops, + _pin: PhantomPinned, } } -} -impl<'o, 'p> AsMut<AvbOps> for ScopedAvbOps<'o, 'p> { - fn as_mut(&mut self) -> &mut AvbOps { - &mut self.avb_ops + /// Initializes and returns the C `AvbOps` structure from an `OpsBridge`. + /// + /// If the contained `Ops` supports `CertOps`, the returned `AvbOps` will also be configured + /// properly for libavb_cert. + /// + /// Pinning is necessary here because the returned `AvbOps` contains pointers into `self`, so + /// we cannot allow `self` to subsequently move or else the pointers would become invalid. + /// + /// # Returns + /// The C `AvbOps` struct to make libavb calls with. + pub(crate) fn init_and_get_c_ops<'a>(self: Pin<&'a mut Self>) -> &'a mut AvbOps { + // SAFETY: we do not move out of `self_mut`, but only set pointers to pinned addresses. + let self_mut = unsafe { self.get_unchecked_mut() }; + + // Set the C `user_data` to point back to us so we can retrieve ourself in callbacks. + self_mut.avb_ops.user_data = self_mut as *mut _ as *mut _; + + // If the `Ops` supports certs, set up the necessary additional pointer tracking. + if self_mut.rust_ops.cert_ops().is_some() { + self_mut.avb_ops.cert_ops = &mut self_mut.cert_ops; + self_mut.cert_ops.ops = &mut self_mut.avb_ops; + } + + &mut self_mut.avb_ops } } @@ -359,7 +373,7 @@ impl<'o, 'p> AsMut<AvbOps> for ScopedAvbOps<'o, 'p> { /// The Rust `Ops` extracted from `avb_ops.user_data`. /// /// # Safety -/// * only call this function on an `AvbOps` created via `ScopedAvbOps` +/// * only call this function on an `AvbOps` created via `OpsBridge` /// * drop all references to the returned `Ops` and preloaded data before returning control to /// libavb or calling this function again /// @@ -389,11 +403,11 @@ unsafe fn as_ops<'o, 'p>(avb_ops: *mut AvbOps) -> IoResult<&'o mut dyn Ops<'p>> // 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 + // Cast the void* `user_data` back to a OpsBridge*. + let bridge = avb_ops.user_data as *mut OpsBridge; + // SAFETY: we created this OpsBridge 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) + Ok(unsafe { bridge.as_mut() }.ok_or(IoError::Io)?.rust_ops) } /// Converts a non-NULL `ptr` to `()`, NULL to `Err(IoError::Io)`. @@ -431,7 +445,7 @@ unsafe extern "C" fn read_from_partition( /// Bounces the C callback into the user-provided Rust implementation. /// /// # Safety -/// * `ops` must have been created via `ScopedAvbOps`. +/// * `ops` must have been created via `OpsBridge`. /// * `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()`. @@ -454,7 +468,7 @@ unsafe fn try_read_from_partition( unsafe { ptr::write(out_num_read, 0) }; // SAFETY: - // * we only use `ops` objects created via `ScopedAvbOps` as required. + // * we only use `ops` objects created via `OpsBridge` as required. // * `ops` is only extracted once and is dropped at the end of the callback. let ops = unsafe { as_ops(ops) }?; // SAFETY: @@ -503,7 +517,7 @@ unsafe extern "C" fn get_preloaded_partition( /// Bounces the C callback into the user-provided Rust implementation. /// /// # Safety -/// * `ops` must have been created via `ScopedAvbOps`. +/// * `ops` must have been created via `OpsBridge`. /// * `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 @@ -529,7 +543,7 @@ unsafe fn try_get_preloaded_partition( } // SAFETY: - // * we only use `ops` objects created via `ScopedAvbOps` as required. + // * we only use `ops` objects created via `OpsBridge` as required. // * `ops` is only extracted once and is dropped at the end of the callback. let ops = unsafe { as_ops(ops) }?; // SAFETY: @@ -592,7 +606,7 @@ unsafe extern "C" fn validate_vbmeta_public_key( /// Bounces the C callback into the user-provided Rust implementation. /// /// # Safety -/// * `ops` must have been created via `ScopedAvbOps`. +/// * `ops` must have been created via `OpsBridge`. /// * `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( @@ -613,7 +627,7 @@ unsafe fn try_validate_vbmeta_public_key( unsafe { ptr::write(out_is_trusted, false) }; // SAFETY: - // * we only use `ops` objects created via `ScopedAvbOps` as required. + // * we only use `ops` objects created via `OpsBridge` as required. // * `ops` is only extracted once and is dropped at the end of the callback. let ops = unsafe { as_ops(ops) }?; // SAFETY: @@ -662,7 +676,7 @@ unsafe extern "C" fn read_rollback_index( /// Bounces the C callback into the user-provided Rust implementation. /// /// # Safety -/// * `ops` must have been created via `ScopedAvbOps`. +/// * `ops` must have been created via `OpsBridge`. /// * `out_rollback_index` must adhere to the requirements of `ptr::write()`. unsafe fn try_read_rollback_index( ops: *mut AvbOps, @@ -678,7 +692,7 @@ unsafe fn try_read_rollback_index( unsafe { ptr::write(out_rollback_index, 0) }; // SAFETY: - // * we only use `ops` objects created via `ScopedAvbOps` as required. + // * we only use `ops` objects created via `OpsBridge` 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)?; @@ -711,14 +725,14 @@ unsafe extern "C" fn write_rollback_index( /// Bounces the C callback into the user-provided Rust implementation. /// /// # Safety -/// * `ops` must have been created via `ScopedAvbOps`. +/// * `ops` must have been created via `OpsBridge`. 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. + // * we only use `ops` objects created via `OpsBridge` 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) @@ -738,7 +752,7 @@ unsafe extern "C" fn read_is_device_unlocked( /// Bounces the C callback into the user-provided Rust implementation. /// /// # Safety -/// * `ops` must have been created via `ScopedAvbOps`. +/// * `ops` must have been created via `OpsBridge`. /// * `out_is_unlocked` must adhere to the requirements of `ptr::write()`. unsafe fn try_read_is_device_unlocked( ops: *mut AvbOps, @@ -753,7 +767,7 @@ unsafe fn try_read_is_device_unlocked( unsafe { ptr::write(out_is_unlocked, false) }; // SAFETY: - // * we only use `ops` objects created via `ScopedAvbOps` as required. + // * we only use `ops` objects created via `OpsBridge` 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()?; @@ -791,7 +805,7 @@ unsafe extern "C" fn get_unique_guid_for_partition( /// gives the empty string for all partitions. /// /// # Safety -/// * `ops` must have been created via `ScopedAvbOps`. +/// * `ops` must have been created via `OpsBridge`. /// * `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( @@ -838,7 +852,7 @@ unsafe fn try_get_unique_guid_for_partition( let partition = unsafe { CStr::from_ptr(partition) }; // SAFETY: - // * we only use `ops` objects created via `ScopedAvbOps` as required. + // * we only use `ops` objects created via `OpsBridge` 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)?; @@ -884,7 +898,7 @@ unsafe extern "C" fn get_size_of_partition( /// Bounces the C callback into the user-provided Rust implementation. /// /// # Safety -/// * `ops` must have been created via `ScopedAvbOps`. +/// * `ops` must have been created via `OpsBridge`. /// * `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( @@ -902,7 +916,7 @@ unsafe fn try_get_size_of_partition( unsafe { ptr::write(out_size_num_bytes, 0) }; // SAFETY: - // * we only use `ops` objects created via `ScopedAvbOps` as required. + // * we only use `ops` objects created via `OpsBridge` as required. // * `ops` is only extracted once and is dropped at the end of the callback. let ops = unsafe { as_ops(ops) }?; // SAFETY: @@ -945,7 +959,7 @@ unsafe extern "C" fn read_persistent_value( /// Bounces the C callback into the user-provided Rust implementation. /// /// # Safety -/// * `ops` must have been created via `ScopedAvbOps`. +/// * `ops` must have been created via `OpsBridge`. /// * `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()`. @@ -966,7 +980,7 @@ unsafe fn try_read_persistent_value( unsafe { ptr::write(out_num_bytes_read, 0) }; // SAFETY: - // * we only use `ops` objects created via `ScopedAvbOps` as required. + // * we only use `ops` objects created via `OpsBridge` as required. // * `ops` is only extracted once and is dropped at the end of the callback. let ops = unsafe { as_ops(ops) }?; // SAFETY: @@ -1017,7 +1031,7 @@ unsafe extern "C" fn write_persistent_value( /// Bounces the C callback into the user-provided Rust implementation. /// /// # Safety -/// * `ops` must have been created via `ScopedAvbOps`. +/// * `ops` must have been created via `OpsBridge`. /// * `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( @@ -1029,7 +1043,7 @@ unsafe fn try_write_persistent_value( check_nonnull(name)?; // SAFETY: - // * we only use `ops` objects created via `ScopedAvbOps` as required. + // * we only use `ops` objects created via `OpsBridge` as required. // * `ops` is only extracted once and is dropped at the end of the callback. let ops = unsafe { as_ops(ops) }?; // SAFETY: @@ -1084,7 +1098,7 @@ unsafe extern "C" fn validate_public_key_for_partition( /// Bounces the C callback into the user-provided Rust implementation. /// /// # Safety -/// * `ops` must have been created via `ScopedAvbOps`. +/// * `ops` must have been created via `OpsBridge`. /// * `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()`. @@ -1114,7 +1128,7 @@ unsafe fn try_validate_public_key_for_partition( } // SAFETY: - // * we only use `ops` objects created via `ScopedAvbOps` as required. + // * we only use `ops` objects created via `OpsBridge` as required. // * `ops` is only extracted once and is dropped at the end of the callback. let ops = unsafe { as_ops(ops) }?; // SAFETY: diff --git a/rust/src/verify.rs b/rust/src/verify.rs index 0098644..b718d63 100644 --- a/rust/src/verify.rs +++ b/rust/src/verify.rs @@ -35,6 +35,7 @@ use core::{ ffi::{c_char, CStr}, fmt, marker::PhantomData, + pin::pin, ptr::{self, null, null_mut, NonNull}, slice, }; @@ -399,10 +400,6 @@ pub fn slot_verify<'a>( 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 @@ -420,6 +417,7 @@ pub fn slot_verify<'a>( // 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 ops_bridge = pin!(ops::OpsBridge::new(ops)); let mut out_data: *mut AvbSlotVerifyData = null_mut(); // Call the libavb verification function. @@ -429,11 +427,12 @@ pub fn slot_verify<'a>( // or else the memory will leak. // // SAFETY: + // * `ops_bridge.init_and_get_c_ops()` gives us a valid `AvbOps`. // * 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, + ops_bridge.init_and_get_c_ops(), partitions_array.as_ptr(), ab_suffix.as_ptr(), flags, |