aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--rust/src/ops.rs73
-rw-r--r--rust/src/verify.rs44
-rw-r--r--rust/tests/test_ops.rs105
-rw-r--r--rust/tests/verify_tests.rs96
4 files changed, 236 insertions, 82 deletions
diff --git a/rust/src/ops.rs b/rust/src/ops.rs
index e030dda..4b67b0f 100644
--- a/rust/src/ops.rs
+++ b/rust/src/ops.rs
@@ -33,7 +33,19 @@ use uuid::Uuid;
/// Base implementation-provided callbacks for verification.
///
/// See libavb `AvbOps` for more complete documentation.
-pub trait Ops {
+///
+/// # Lifetimes
+/// The trait lifetime `'a` indicates the lifetime of any preloaded partition data.
+///
+/// Preloading partitions is an optional feature which allows libavb to use data already loaded to
+/// RAM rather than allocating memory itself and loading data from disk. Preloading changes the
+/// data ownership model so that the verification result borrows this existing data rather than
+/// allocating and owning the data itself. Because of this borrow, we need the lifetime here to
+/// ensure that the underlying data outlives the verification result object.
+///
+/// If `get_preloaded_partition()` is left unimplemented, all data is loaded and owned by the
+/// verification result rather than borrowed, and this trait lifetime can be `'static`.
+pub trait Ops<'a> {
/// Reads data from the requested partition on disk.
///
/// # Arguments
@@ -68,7 +80,7 @@ pub trait Ops {
/// * `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]> {
+ fn get_preloaded_partition(&mut self, _partition: &CStr) -> IoResult<&'a [u8]> {
Err(IoError::NotImplemented)
}
@@ -276,25 +288,29 @@ pub struct PublicKeyForPartitionInfo {
/// perform `Ops` (Rust)
/// callback
/// ```
-pub(crate) struct UserData<'a>(&'a mut dyn Ops);
+///
+/// # 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<'a> UserData<'a> {
- pub(crate) fn new(ops: &'a mut dyn Ops) -> Self {
+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<'a> {
+pub(crate) struct ScopedAvbOps<'o, 'p> {
/// `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>>,
+ _user_data: PhantomData<UserData<'o, 'p>>,
}
-impl<'a> ScopedAvbOps<'a> {
- pub(crate) fn new(user_data: &'a mut UserData<'a>) -> Self {
+impl<'o, 'p> ScopedAvbOps<'o, 'p> {
+ pub(crate) fn new(user_data: &mut UserData<'o, 'p>) -> Self {
Self {
avb_ops: AvbOps {
// Rust won't transitively cast so we need to cast twice manually, but the compiler
@@ -320,7 +336,7 @@ impl<'a> ScopedAvbOps<'a> {
}
}
-impl<'a> AsMut<AvbOps> for ScopedAvbOps<'a> {
+impl<'o, 'p> AsMut<AvbOps> for ScopedAvbOps<'o, 'p> {
fn as_mut(&mut self) -> &mut AvbOps {
&mut self.avb_ops
}
@@ -328,6 +344,9 @@ impl<'a> AsMut<AvbOps> for ScopedAvbOps<'a> {
/// Extracts the user-provided `Ops` from a raw `AvbOps`.
///
+/// This function is used in libavb callbacks to bridge libavb's raw C `AvbOps` struct to our Rust
+/// implementation.
+///
/// # Arguments
/// * `avb_ops`: The raw `AvbOps` pointer used by libavb.
///
@@ -335,17 +354,33 @@ impl<'a> AsMut<AvbOps> for ScopedAvbOps<'a> {
/// 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 `ScopedAvbOps`
+/// * drop all references to the returned `Ops` and preloaded data before returning control to
+/// libavb or calling this function again
+///
+/// In practice, these conditions are met since we call this at most once in each callback
+/// to extract the `Ops`, and drop the references at callback completion.
///
-/// 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
+/// # Lifetimes
+/// * `'o`: lifetime of the `Ops` object
+/// * `'p`: lifetime of any preloaded data provided by `Ops`
///
-/// 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> {
+/// It's difficult to accurately provide the lifetimes when calling this function, since we are in
+/// a C callback which provides no lifetime information in the args. We solve this in the safety
+/// requirements by requiring the caller to drop both references before returning, which is always
+/// a subset of the actual object lifetimes as the objects must remain valid while libavb is
+/// actively using them:
+///
+/// ```ignore
+/// ops/preloaded lifetime { // Actual 'o/'p start
+/// call into libavb {
+/// libavb callbacks {
+/// as_ops() // as_ops() 'o/'p start
+/// } // as_ops() 'o/'p end
+/// }
+/// } // Actual 'o/'p end
+/// ```
+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)?;
diff --git a/rust/src/verify.rs b/rust/src/verify.rs
index e972226..0098644 100644
--- a/rust/src/verify.rs
+++ b/rust/src/verify.rs
@@ -34,6 +34,7 @@ use avb_bindgen::{
use core::{
ffi::{c_char, CStr},
fmt,
+ marker::PhantomData,
ptr::{self, null, null_mut, NonNull},
slice,
};
@@ -200,13 +201,25 @@ impl fmt::Debug for PartitionData {
/// Wraps a raw C `AvbSlotVerifyData` struct.
///
/// This provides a Rust safe view over the raw data; no copies are made.
+///
+/// # Lifetimes
+/// * `'a`: the lifetime of any preloaded partition data borrowed from an `Ops<'a>` object.
+///
+/// If the `Ops` doesn't provide any preloaded data, `SlotVerifyData` doesn't borrow anything
+/// and instead allocates and owns all data internally, freeing it accordingly on `Drop`. In this
+/// case, `'a` can be `'static` which imposes no lifetime restrictions on `SlotVerifyData`.
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,
+ /// This provides the necessary lifetimes so the compiler can make sure that the preloaded
+ /// partition data stays alive at least as long as we do, since the underlying
+ /// `AvbSlotVerifyData` may wrap this data rather than making a copy.
+ //
+ // We do not want to actually borrow an `Ops` here, since in some cases `Ops` is just a
+ // temporary object and may go out of scope before us. The only shared data is the preloaded
+ // partition contents, not the entire `Ops` object.
+ _preloaded: PhantomData<&'a [u8]>,
}
// Useful so that `SlotVerifyError`, which may hold a `SlotVerifyData`, can derive `PartialEq`.
@@ -225,24 +238,28 @@ impl<'a> SlotVerifyData<'a> {
/// The returned `SlotVerifyData` will take ownership of the given `AvbSlotVerifyData` and
/// properly release the allocated memory when it drops.
///
+ /// If `ops` provided any preloaded data, the returned `SlotVerifyData` also borrows the data to
+ /// account for the underlying `AvbSlotVerifyData` holding a pointer to it. If there was no
+ /// preloaded data, then `SlotVerifyData` owns all its data.
+ ///
/// # 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.
+ /// * `data`: a `AvbSlotVerifyData` object created by libavb using `ops`.
+ /// * `ops`: the user-provided `Ops` object that was used for verification; only used here to
+ /// grab the preloaded data lifetime.
///
/// # Returns
/// The new object, or `Err(SlotVerifyError::Internal)` if the data looks invalid.
///
/// # Safety
- /// * `data` must be a valid `AvbSlotVerifyData` object created by libavb
+ /// * `data` must be a valid `AvbSlotVerifyData` object created by libavb using `ops`.
/// * after calling this function, do not access `data` except through the returned object
unsafe fn new(
data: *mut AvbSlotVerifyData,
- ops: &'a mut dyn Ops,
+ _ops: &dyn Ops<'a>,
) -> SlotVerifyNoDataResult<Self> {
let ret = Self {
raw_data: NonNull::new(data).ok_or(SlotVerifyError::Internal)?,
- _ops: ops,
+ _preloaded: PhantomData,
};
// Validate all the contained data here so accessors will never fail.
@@ -319,6 +336,7 @@ impl<'a> SlotVerifyData<'a> {
}
}
+/// Frees any internally-allocated and owned data.
impl<'a> Drop for SlotVerifyData<'a> {
fn drop(&mut self) {
// SAFETY:
@@ -371,11 +389,11 @@ impl<'a> fmt::Debug for SlotVerifyData<'a> {
/// 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.
+/// A returned `SlotVerifyData` will also borrow any preloaded data provided by `ops`. The `ops`
+/// object itself can go out of scope, but any preloaded data it could provide must outlive the
+/// returned object.
pub fn slot_verify<'a>(
- ops: &'a mut dyn Ops,
+ ops: &mut dyn Ops<'a>,
requested_partitions: &[&CStr],
ab_suffix: Option<&CStr>,
flags: SlotVerifyFlags,
diff --git a/rust/tests/test_ops.rs b/rust/tests/test_ops.rs
index 1c62c4d..d752650 100644
--- a/rust/tests/test_ops.rs
+++ b/rust/tests/test_ops.rs
@@ -19,20 +19,53 @@ use std::{cmp::min, collections::HashMap, ffi::CStr};
#[cfg(feature = "uuid")]
use uuid::Uuid;
-/// Represents a single fake partition.
-#[derive(Default)]
-pub struct FakePartition {
- /// Partition contents.
- pub contents: Vec<u8>,
+/// Where the fake partition contents come from.
+pub enum PartitionContents<'a> {
+ /// Read on-demand from disk.
+ FromDisk(Vec<u8>),
+ /// Preloaded and passed in.
+ Preloaded(&'a [u8]),
+}
+
+impl<'a> PartitionContents<'a> {
+ /// Returns the partition data.
+ pub fn as_slice(&self) -> &[u8] {
+ match self {
+ Self::FromDisk(v) => v,
+ Self::Preloaded(c) => c,
+ }
+ }
+
+ /// Returns a mutable reference to the `FromDisk` data for test modification. Panicks if the
+ /// data is actually `Preloaded` instead.
+ pub fn as_mut_vec(&mut self) -> &mut Vec<u8> {
+ match self {
+ Self::FromDisk(v) => v,
+ Self::Preloaded(_) => panic!("Cannot mutate preloaded partition data"),
+ }
+ }
+}
- /// Whether the partition should report as preloaded or not.
- pub preloaded: bool,
+/// Represents a single fake partition.
+pub struct FakePartition<'a> {
+ /// Partition contents, either preloaded or read on-demand.
+ pub contents: PartitionContents<'a>,
/// Partition UUID.
#[cfg(feature = "uuid")]
pub uuid: Uuid,
}
+impl<'a> FakePartition<'a> {
+ fn new(contents: PartitionContents<'a>) -> Self {
+ Self {
+ contents,
+ #[cfg(feature = "uuid")]
+ uuid: Default::default(),
+ }
+ }
+}
+
/// Fake vbmeta key state.
pub struct FakeVbmetaKeyState {
/// Key trust & rollback index info.
@@ -47,9 +80,9 @@ pub struct FakeVbmetaKeyState {
///
/// 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 {
+pub struct TestOps<'a> {
/// Partitions to provide to libavb callbacks.
- pub partitions: HashMap<&'static str, FakePartition>,
+ pub partitions: HashMap<&'static str, FakePartition<'a>>,
/// Vbmeta public keys as a map of {(key, metadata): state}. Querying unknown keys will
/// return `IoError::Io`.
@@ -69,27 +102,40 @@ pub struct TestOps {
pub persistent_values: HashMap<String, IoResult<Vec<u8>>>,
}
-impl TestOps {
- /// Adds a partition with the given contents.
+impl<'a> TestOps<'a> {
+ /// Adds a fake on-disk 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;
+ /// test_ops.add_partition("bar", [0, 0]).uuid = uuid!(...);
/// ```
pub fn add_partition<T: Into<Vec<u8>>>(
&mut self,
name: &'static str,
contents: T,
- ) -> &mut FakePartition {
+ ) -> &mut FakePartition<'a> {
self.partitions.insert(
name,
- FakePartition {
- contents: contents.into(),
- ..Default::default()
- },
+ FakePartition::new(PartitionContents::FromDisk(contents.into())),
+ );
+ self.partitions.get_mut(name).unwrap()
+ }
+
+ /// Adds a preloaded partition with the given contents.
+ ///
+ /// Same a `add_partition()` except that the preloaded data is not owned by
+ /// the `TestOps` but passed in, which means it can outlive `TestOps`.
+ pub fn add_preloaded_partition(
+ &mut self,
+ name: &'static str,
+ contents: &'a [u8],
+ ) -> &mut FakePartition<'a> {
+ self.partitions.insert(
+ name,
+ FakePartition::new(PartitionContents::Preloaded(contents)),
);
self.partitions.get_mut(name).unwrap()
}
@@ -145,7 +191,7 @@ impl TestOps {
}
}
-impl Default for TestOps {
+impl Default for TestOps<'_> {
fn default() -> Self {
Self {
partitions: HashMap::new(),
@@ -157,23 +203,19 @@ impl Default for TestOps {
}
}
-impl Ops for TestOps {
+impl<'a> Ops<'a> for TestOps<'a> {
fn read_from_partition(
&mut self,
partition: &CStr,
offset: i64,
buffer: &mut [u8],
) -> IoResult<usize> {
- let partition = self
+ let contents = 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;
+ .ok_or(IoError::NoSuchPartition)?
+ .contents
+ .as_slice();
// Negative offset means count backwards from the end.
let offset = {
@@ -202,13 +244,12 @@ impl Ops for TestOps {
Ok(bytes_read)
}
- fn get_preloaded_partition(&mut self, partition: &CStr) -> IoResult<&[u8]> {
+ fn get_preloaded_partition(&mut self, partition: &CStr) -> IoResult<&'a [u8]> {
match self.partitions.get(partition.to_str()?) {
Some(FakePartition {
- contents,
- preloaded: true,
+ contents: PartitionContents::Preloaded(preloaded),
..
- }) => Ok(&contents[..]),
+ }) => Ok(&preloaded[..]),
_ => Err(IoError::NotImplemented),
}
}
@@ -251,7 +292,7 @@ impl Ops for TestOps {
fn get_size_of_partition(&mut self, partition: &CStr) -> IoResult<u64> {
self.partitions
.get(partition.to_str()?)
- .map(|p| u64::try_from(p.contents.len()).unwrap())
+ .map(|p| u64::try_from(p.contents.as_slice().len()).unwrap())
.ok_or(IoError::NoSuchPartition)
}
diff --git a/rust/tests/verify_tests.rs b/rust/tests/verify_tests.rs
index 015bc9f..2e4aaad 100644
--- a/rust/tests/verify_tests.rs
+++ b/rust/tests/verify_tests.rs
@@ -66,7 +66,7 @@ const TEST_HASHTREE_DIGEST_HEX: &str = "5373fc4ee3dd898325eeeffb5a1dbb041900c5f1
const TEST_HASHTREE_ALGORITHM: &str = "sha1";
/// Initializes a `TestOps` object such that verification will succeed on `TEST_PARTITION_NAME`.
-fn test_ops_one_image_one_vbmeta() -> TestOps {
+fn test_ops_one_image_one_vbmeta<'a>() -> TestOps<'a> {
let mut ops = TestOps::default();
ops.add_partition(TEST_PARTITION_NAME, fs::read(TEST_IMAGE_PATH).unwrap());
ops.add_partition("vbmeta", fs::read(TEST_VBMETA_PATH).unwrap());
@@ -77,7 +77,9 @@ fn test_ops_one_image_one_vbmeta() -> TestOps {
}
/// Calls `slot_verify()` using standard args for `test_ops_one_image_one_vbmeta()` setup.
-fn verify_one_image_one_vbmeta(ops: &mut TestOps) -> SlotVerifyResult<SlotVerifyData> {
+fn verify_one_image_one_vbmeta<'a>(
+ ops: &mut TestOps<'a>,
+) -> SlotVerifyResult<'a, SlotVerifyData<'a>> {
slot_verify(
ops,
&[&CString::new(TEST_PARTITION_NAME).unwrap()],
@@ -89,7 +91,7 @@ fn verify_one_image_one_vbmeta(ops: &mut TestOps) -> SlotVerifyResult<SlotVerify
/// Initializes a `TestOps` object such that verification will succeed on `TEST_PARTITION_NAME` and
/// `TEST_PARTITION_2_NAME`.
-fn test_ops_two_images_one_vbmeta() -> TestOps {
+fn test_ops_two_images_one_vbmeta<'a>() -> TestOps<'a> {
let mut ops = test_ops_one_image_one_vbmeta();
// Add in the contents of the second partition and overwrite the vbmeta partition to
// include both partition descriptors.
@@ -99,7 +101,7 @@ fn test_ops_two_images_one_vbmeta() -> TestOps {
}
/// Calls `slot_verify()` for both test partitions.
-fn verify_two_images(ops: &mut TestOps) -> SlotVerifyResult<SlotVerifyData> {
+fn verify_two_images<'a>(ops: &mut TestOps<'a>) -> SlotVerifyResult<'a, SlotVerifyData<'a>> {
slot_verify(
ops,
&[
@@ -114,7 +116,7 @@ fn verify_two_images(ops: &mut TestOps) -> SlotVerifyResult<SlotVerifyData> {
/// Initializes a `TestOps` object such that verification will succeed on the `boot` partition with
/// a combined image + vbmeta.
-fn test_ops_boot_partition() -> TestOps {
+fn test_ops_boot_partition<'a>() -> TestOps<'a> {
let mut ops = test_ops_one_image_one_vbmeta();
ops.partitions.clear();
ops.add_partition(
@@ -125,7 +127,7 @@ fn test_ops_boot_partition() -> TestOps {
}
/// Calls `slot_verify()` using standard args for `test_ops_boot_partition()` setup.
-fn verify_boot_partition(ops: &mut TestOps) -> SlotVerifyResult<SlotVerifyData> {
+fn verify_boot_partition<'a>(ops: &mut TestOps<'a>) -> SlotVerifyResult<'a, SlotVerifyData<'a>> {
slot_verify(
ops,
&[&CString::new("boot").unwrap()],
@@ -140,7 +142,7 @@ fn verify_boot_partition(ops: &mut TestOps) -> SlotVerifyResult<SlotVerifyData>
/// Initializes a `TestOps` object such that verification will succeed on
/// `TEST_PARTITION_PERSISTENT_DIGEST_NAME`.
-fn test_ops_persistent_digest(image: Vec<u8>) -> TestOps {
+fn test_ops_persistent_digest<'a>(image: Vec<u8>) -> TestOps<'a> {
let mut ops = test_ops_one_image_one_vbmeta();
ops.partitions.clear();
// Use the vbmeta image with the persistent digest descriptor.
@@ -154,7 +156,7 @@ fn test_ops_persistent_digest(image: Vec<u8>) -> TestOps {
}
/// Calls `slot_verify()` using standard args for `test_ops_persistent_digest()` setup.
-fn verify_persistent_digest(ops: &mut TestOps) -> SlotVerifyResult<SlotVerifyData> {
+fn verify_persistent_digest<'a>(ops: &mut TestOps<'a>) -> SlotVerifyResult<'a, SlotVerifyData<'a>> {
slot_verify(
ops,
&[&CString::new(TEST_PARTITION_PERSISTENT_DIGEST_NAME).unwrap()],
@@ -166,7 +168,11 @@ fn verify_persistent_digest(ops: &mut TestOps) -> SlotVerifyResult<SlotVerifyDat
/// Modifies the partition contents by flipping a bit.
fn modify_partition_contents(ops: &mut TestOps, partition: &str) {
- ops.partitions.get_mut(partition).unwrap().contents[0] ^= 0x01;
+ ops.partitions
+ .get_mut(partition)
+ .unwrap()
+ .contents
+ .as_mut_vec()[0] ^= 0x01;
}
/// Returns the persistent value name for `TEST_PARTITION_PERSISTENT_DIGEST_NAME`.
@@ -220,11 +226,9 @@ fn one_image_one_vbmeta_passes_verification_with_correct_data() {
#[test]
fn preloaded_image_passes_verification() {
let mut ops = test_ops_one_image_one_vbmeta();
- // Mark the image partition to be preloaded.
- ops.partitions
- .get_mut(TEST_PARTITION_NAME)
- .unwrap()
- .preloaded = true;
+ // Use preloaded data instead for the test partition.
+ let preloaded = fs::read(TEST_IMAGE_PATH).unwrap();
+ ops.add_preloaded_partition(TEST_PARTITION_NAME, &preloaded);
let result = verify_one_image_one_vbmeta(&mut ops);
@@ -233,6 +237,63 @@ fn preloaded_image_passes_verification() {
assert!(partition_data.preloaded());
}
+// When all images are loaded from disk (rather than preloaded), libavb allocates memory itself for
+// the data, so there is no shared ownership; the returned verification data owns the image data
+// and can hold onto it even after the `ops` goes away.
+#[test]
+fn verification_data_from_disk_can_outlive_ops() {
+ let result = {
+ let mut ops = test_ops_one_image_one_vbmeta();
+ verify_one_image_one_vbmeta(&mut ops)
+ };
+
+ let data = result.unwrap();
+
+ // The verification data owns the images and we can still access them.
+ assert_eq!(
+ data.partition_data()[0].data(),
+ fs::read(TEST_IMAGE_PATH).unwrap()
+ );
+}
+
+// When preloaded data is passed into ops but outlives it, we can also continue to access it from
+// the verification data after the ops goes away. The ops was only borrowing it, and now the
+// verification data continues to borrow it.
+#[test]
+fn verification_data_preloaded_can_outlive_ops() {
+ let preloaded = fs::read(TEST_IMAGE_PATH).unwrap();
+
+ let result = {
+ let mut ops = test_ops_one_image_one_vbmeta();
+ ops.add_preloaded_partition(TEST_PARTITION_NAME, &preloaded);
+ verify_one_image_one_vbmeta(&mut ops)
+ };
+
+ let data = result.unwrap();
+
+ // The verification data is borrowing the preloaded images and we can still access them.
+ assert_eq!(data.partition_data()[0].data(), preloaded);
+}
+
+// When preloaded data is passed into ops but also goes out of scope, the verification data loses
+// access to it, violating lifetime rules.
+//
+// Our lifetimes *must* be configured such that this does not compile, since `result` is borrowing
+// `preloaded` which has gone out of scope.
+//
+// TODO: figure out how to make a compile-fail test; for now we just have to manually test by
+// un-commenting the code.
+// #[test]
+// fn verification_data_preloaded_cannot_outlive_result() {
+// let result = {
+// let preloaded = fs::read(TEST_IMAGE_PATH).unwrap();
+// let mut ops = test_ops_one_image_one_vbmeta();
+// ops.add_preloaded_partition(TEST_PARTITION_NAME, &preloaded);
+// verify_one_image_one_vbmeta(&mut ops)
+// };
+// result.unwrap();
+// }
+
#[test]
fn slotted_partition_passes_verification() {
let mut ops = test_ops_one_image_one_vbmeta();
@@ -427,6 +488,7 @@ fn undersized_partition_fails_verification() {
.get_mut(TEST_PARTITION_NAME)
.unwrap()
.contents
+ .as_mut_vec()
.pop();
let result = verify_one_image_one_vbmeta(&mut ops);
@@ -578,10 +640,8 @@ fn one_image_one_vbmeta_verification_data_display() {
#[test]
fn preloaded_image_verification_data_display() {
let mut ops = test_ops_one_image_one_vbmeta();
- ops.partitions
- .get_mut(TEST_PARTITION_NAME)
- .unwrap()
- .preloaded = true;
+ let preloaded = fs::read(TEST_IMAGE_PATH).unwrap();
+ ops.add_preloaded_partition(TEST_PARTITION_NAME, &preloaded);
let result = verify_one_image_one_vbmeta(&mut ops);