diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-11-24 00:04:27 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-11-24 00:04:27 +0000 |
commit | 47d8f9f0810d175f2f768ea9ebf1415c6587a851 (patch) | |
tree | 965847d835efc13f2452a2b152355d06a0be7d5a | |
parent | 4c57089f935fb47f9fa8b2ab84f8e1f6d788d85e (diff) | |
parent | 277a56813492dbcbc5527a0503841515623da8bb (diff) | |
download | avb-47d8f9f0810d175f2f768ea9ebf1415c6587a851.tar.gz |
Snap for 11139387 from 277a56813492dbcbc5527a0503841515623da8bb to 24Q1-release
Change-Id: Ibe701dfaf0e649ee2325cacfd4166ded8cbffa23
-rw-r--r-- | rust/Android.bp | 97 | ||||
-rw-r--r-- | rust/src/error.rs | 160 | ||||
-rw-r--r-- | rust/src/lib.rs | 4 | ||||
-rw-r--r-- | rust/src/ops.rs | 12 | ||||
-rw-r--r-- | rust/src/verify.rs | 419 | ||||
-rw-r--r-- | rust/tests/tests.rs | 1 | ||||
-rw-r--r-- | rust/tests/verify_tests.rs | 543 |
7 files changed, 1208 insertions, 28 deletions
diff --git a/rust/Android.bp b/rust/Android.bp index 2d5730e..b4d20e3 100644 --- a/rust/Android.bp +++ b/rust/Android.bp @@ -31,6 +31,7 @@ rust_defaults { source_stem: "bindings", bindgen_flags: [ "--constified-enum-module AvbDescriptorTag", + "--bitfield-enum=AvbSlotVerifyFlags", "--default-enum-style rust", "--allowlist-type=AvbDescriptorTag", "--allowlist-function=.*", @@ -172,9 +173,18 @@ rust_library { ], } +// device test defaults. rust_defaults { name: "libavb_rs_test.defaults", srcs: ["tests/tests.rs"], + data: [ + ":avb_testkey_rsa4096_pub_bin", + ":avbrs_test_image", + ":avbrs_test_image_with_vbmeta_footer", + ":avbrs_test_vbmeta", + ":avbrs_test_vbmeta_2_parts", + ":avbrs_test_vbmeta_persistent_digest", + ], test_suites: ["general-tests"], } @@ -189,8 +199,93 @@ rust_test { rust_test { name: "libavb_rs_uuid_test", defaults: [ - "libavb_rs_test.defaults", "libavb_rs.uuid.defaults", + "libavb_rs_test.defaults", ], rustlibs: ["libavb_rs_uuid"], } + +// Test images for verification. + +// Unsigned 16KiB test image. +genrule { + name: "avbrs_test_image", + tools: ["avbtool"], + out: ["test_image.img"], + cmd: "$(location avbtool) generate_test_image --image_size 16384 --output $(out)", +} + +// Unsigned vbmeta blob containing the test image descriptor for partition name "test_part". +avb_gen_vbmeta_image { + name: "avbrs_test_image_descriptor", + src: ":avbrs_test_image", + partition_name: "test_part", + salt: "0000", +} + +// Unsigned vbmeta blob containing the test image descriptor for partition name "test_part_2". +avb_gen_vbmeta_image { + name: "avbrs_test_image_descriptor_2", + src: ":avbrs_test_image", + partition_name: "test_part_2", + salt: "0001", +} + +// Unsigned vbmeta blob containing a persistent digest descriptor for partition name +// "test_part_persistent_digest". +// +// Currently this is the only in-tree usage of persistent digests, but if anyone else needs it +// later on it may be worth folding support for this into the `avb_gen_vbmeta_image` rule. +genrule { + name: "avbrs_test_image_descriptor_persistent_digest", + tools: ["avbtool"], + srcs: [":avbrs_test_image"], + out: ["avbrs_test_image_descriptor_persistent_digest.img"], + cmd: "$(location avbtool) add_hash_footer --image $(location :avbrs_test_image) --partition_name test_part_persistent_digest --dynamic_partition_size --do_not_append_vbmeta_image --use_persistent_digest --output_vbmeta_image $(out)", +} + +// Standalone vbmeta image signing the test image descriptor. +genrule { + name: "avbrs_test_vbmeta", + tools: ["avbtool"], + srcs: [ + ":avbrs_test_image_descriptor", + ":avb_testkey_rsa4096", + ], + out: ["test_vbmeta.img"], + cmd: "$(location avbtool) make_vbmeta_image --key $(location :avb_testkey_rsa4096) --algorithm SHA512_RSA4096 --include_descriptors_from_image $(location :avbrs_test_image_descriptor) --output $(out)", +} + +// Standalone vbmeta image signing the test image descriptors for "test_part" and "test_part_2". +genrule { + name: "avbrs_test_vbmeta_2_parts", + tools: ["avbtool"], + srcs: [ + ":avbrs_test_image_descriptor", + ":avbrs_test_image_descriptor_2", + ":avb_testkey_rsa4096", + ], + out: ["test_vbmeta_2_parts.img"], + cmd: "$(location avbtool) make_vbmeta_image --key $(location :avb_testkey_rsa4096) --algorithm SHA512_RSA4096 --include_descriptors_from_image $(location :avbrs_test_image_descriptor) --include_descriptors_from_image $(location :avbrs_test_image_descriptor_2) --output $(out)", +} + +// Standalone vbmeta image signing the test image persistent digest descriptor. +genrule { + name: "avbrs_test_vbmeta_persistent_digest", + tools: ["avbtool"], + srcs: [ + ":avbrs_test_image_descriptor_persistent_digest", + ":avb_testkey_rsa4096", + ], + out: ["test_vbmeta_persistent_digest.img"], + cmd: "$(location avbtool) make_vbmeta_image --key $(location :avb_testkey_rsa4096) --algorithm SHA512_RSA4096 --include_descriptors_from_image $(location :avbrs_test_image_descriptor_persistent_digest) --output $(out)", +} + +// Combined test image + signed vbmeta footer for "test_part". +avb_add_hash_footer { + name: "avbrs_test_image_with_vbmeta_footer", + src: ":avbrs_test_image", + partition_name: "test_part", + private_key: ":avb_testkey_rsa4096", + salt: "A000", +} diff --git a/rust/src/error.rs b/rust/src/error.rs index 51c3f2f..b5d781e 100644 --- a/rust/src/error.rs +++ b/rust/src/error.rs @@ -30,13 +30,13 @@ // wrapped in a Rust `Result<>` in public API. // * `Result<T, *Error>`: top-level `Result<>` type used in this library's public API. -use avb_bindgen::{AvbIOResult, AvbSlotVerifyResult}; - +use crate::SlotVerifyData; +use avb_bindgen::{AvbIOResult, AvbSlotVerifyResult, AvbVBMetaVerifyResult}; use core::{fmt, str::Utf8Error}; /// `AvbSlotVerifyResult` error wrapper. -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum SlotVerifyError { +#[derive(Debug, PartialEq, Eq)] +pub enum SlotVerifyError<'a> { /// `AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_ARGUMENT` InvalidArgument, /// `AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA` @@ -52,10 +52,35 @@ pub enum SlotVerifyError { /// `AVB_SLOT_VERIFY_RESULT_ERROR_UNSUPPORTED_VERSION` UnsupportedVersion, /// `AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION` - Verification, + /// + /// This verification error can contain the resulting `SlotVerifyData` if the + /// `AllowVerificationError` flag was passed into `slot_verify()`. + Verification(Option<SlotVerifyData<'a>>), + /// Unexpected internal error. This does not have a corresponding libavb error code. + Internal, +} + +impl<'a> SlotVerifyError<'a> { + /// Returns a copy of this error without any contained `SlotVerifyData`. + /// + /// This can simplify usage if the user doesn't care about the `SlotVerifyData` by turning the + /// current lifetime bound into `'static`. + pub fn without_verify_data(&self) -> SlotVerifyError<'static> { + match self { + Self::InvalidArgument => SlotVerifyError::InvalidArgument, + Self::InvalidMetadata => SlotVerifyError::InvalidMetadata, + Self::Io => SlotVerifyError::Io, + Self::Oom => SlotVerifyError::Oom, + Self::PublicKeyRejected => SlotVerifyError::PublicKeyRejected, + Self::RollbackIndex => SlotVerifyError::RollbackIndex, + Self::UnsupportedVersion => SlotVerifyError::UnsupportedVersion, + Self::Verification(_) => SlotVerifyError::Verification(None), + Self::Internal => SlotVerifyError::Internal, + } + } } -impl fmt::Display for SlotVerifyError { +impl<'a> fmt::Display for SlotVerifyError<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::InvalidArgument => write!(f, "Invalid parameters"), @@ -65,22 +90,28 @@ impl fmt::Display for SlotVerifyError { Self::PublicKeyRejected => write!(f, "Public key rejected or data not signed"), Self::RollbackIndex => write!(f, "Rollback index violation"), Self::UnsupportedVersion => write!(f, "Unsupported vbmeta version"), - Self::Verification => write!(f, "Verification failure"), + Self::Verification(_) => write!(f, "Verification failure"), + Self::Internal => write!(f, "Internal error"), } } } -// Converts a bindgen `AvbSlotVerifyResult` enum to a `Result<>`, mapping -// `AVB_SLOT_VERIFY_RESULT_OK` to the Rust equivalent `Ok(())` and errors to the corresponding -// `Err(SlotVerifyError)`. -// -// This function is also important to serve as a compile-time check that we're handling all the -// libavb enums; if a new one is added to (or removed from) the C code, this will fail to compile -// until it is updated to match. -// -// TODO(b/290110273): this can be limited to pub(crate) once we've moved the full libavb wrapper -// here. -pub fn slot_verify_enum_to_result(result: AvbSlotVerifyResult) -> Result<(), SlotVerifyError> { +/// Converts a bindgen `AvbSlotVerifyResult` enum to a `Result<>`, mapping +/// `AVB_SLOT_VERIFY_RESULT_OK` to the Rust equivalent `Ok(())` and errors to the corresponding +/// `Err(SlotVerifyError)`. +/// +/// A `Verification` error returned here will always have a `None` `SlotVerifyData`; the data should +/// be added in later if it exists. +/// +/// This function is also important to serve as a compile-time check that we're handling all the +/// libavb enums; if a new one is added to (or removed from) the C code, this will fail to compile +/// until it is updated to match. +/// +/// TODO(b/290110273): this can be limited to pub(crate) once we've moved the full libavb wrapper +/// here. +pub fn slot_verify_enum_to_result( + result: AvbSlotVerifyResult, +) -> Result<(), SlotVerifyError<'static>> { match result { AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_OK => Ok(()), AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_ARGUMENT => { @@ -101,7 +132,7 @@ pub fn slot_verify_enum_to_result(result: AvbSlotVerifyResult) -> Result<(), Slo Err(SlotVerifyError::UnsupportedVersion) } AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION => { - Err(SlotVerifyError::Verification) + Err(SlotVerifyError::Verification(None)) } } } @@ -203,6 +234,63 @@ pub fn result_to_io_enum(result: Result<(), IoError>) -> AvbIOResult { result.map_or_else(|e| e.into(), |_| AvbIOResult::AVB_IO_RESULT_OK) } +/// `AvbVBMetaVerifyResult` error wrapper. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum VbmetaVerifyError { + /// `AVB_VBMETA_VERIFY_RESULT_OK_NOT_SIGNED` + NotSigned, + /// `AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER` + InvalidVbmetaHeader, + /// `AVB_VBMETA_VERIFY_RESULT_UNSUPPORTED_VERSION` + UnsupportedVersion, + /// `AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH` + HashMismatch, + /// `AVB_VBMETA_VERIFY_RESULT_SIGNATURE_MISMATCH` + SignatureMismatch, +} + +impl fmt::Display for VbmetaVerifyError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::NotSigned => write!(f, "vbmeta is unsigned"), + Self::InvalidVbmetaHeader => write!(f, "invalid vbmeta header"), + Self::UnsupportedVersion => write!(f, "unsupported vbmeta version"), + Self::HashMismatch => write!(f, "vbmeta hash mismatch"), + Self::SignatureMismatch => write!(f, "vbmeta signature mismatch"), + } + } +} + +// Converts a bindgen `AvbVBMetaVerifyResult` enum to a `Result<>`, mapping +// `AVB_VBMETA_VERIFY_RESULT_OK` to the Rust equivalent `Ok(())` and errors to the corresponding +// `Err(SlotVerifyError)`. +// +// This function is also important to serve as a compile-time check that we're handling all the +// libavb enums; if a new one is added to (or removed from) the C code, this will fail to compile +// until it is updated to match. +pub fn vbmeta_verify_enum_to_result( + result: AvbVBMetaVerifyResult, +) -> Result<(), VbmetaVerifyError> { + match result { + AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_OK => Ok(()), + AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_OK_NOT_SIGNED => { + Err(VbmetaVerifyError::NotSigned) + } + AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER => { + Err(VbmetaVerifyError::InvalidVbmetaHeader) + } + AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_UNSUPPORTED_VERSION => { + Err(VbmetaVerifyError::UnsupportedVersion) + } + AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH => { + Err(VbmetaVerifyError::HashMismatch) + } + AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_SIGNATURE_MISMATCH => { + Err(VbmetaVerifyError::SignatureMismatch) + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -212,21 +300,21 @@ mod tests { // The actual error message can change as needed, the point of the test is just to make sure // the fmt::Display trait is properly implemented. assert_eq!( - format!("{}", SlotVerifyError::Verification), + format!("{}", SlotVerifyError::Verification(None)), "Verification failure" ); } #[test] fn test_SlotVerifyError_from_raw() { - assert_eq!( + assert!(matches!( slot_verify_enum_to_result(AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_OK), Ok(()) - ); - assert_eq!( + )); + assert!(matches!( slot_verify_enum_to_result(AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_ERROR_IO), Err(SlotVerifyError::Io) - ); + )); } #[test] @@ -256,4 +344,28 @@ mod tests { AvbIOResult::AVB_IO_RESULT_ERROR_IO ); } + + #[test] + fn test_VbmetaVerifyError_display() { + // The actual error message can change as needed, the point of the test is just to make sure + // the fmt::Display trait is properly implemented. + assert_eq!( + format!("{}", VbmetaVerifyError::NotSigned), + "vbmeta is unsigned" + ); + } + + #[test] + fn test_VbmetaVerifyError_from_raw() { + assert_eq!( + vbmeta_verify_enum_to_result(AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_OK), + Ok(()) + ); + assert_eq!( + vbmeta_verify_enum_to_result( + AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH + ), + Err(VbmetaVerifyError::HashMismatch) + ); + } } diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 9c4c843..c580497 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -28,9 +28,13 @@ mod error; mod ops; +mod verify; pub use error::{IoError, SlotVerifyError}; pub use ops::{Ops, PublicKeyForPartitionInfo}; +pub use verify::{ + slot_verify, HashtreeErrorMode, PartitionData, SlotVerifyData, SlotVerifyFlags, VbmetaData, +}; /// APIs that will eventually be internal-only to this library, but while this library is split need /// to be exposed externally. diff --git a/rust/src/ops.rs b/rust/src/ops.rs index 74f12f5..5846b17 100644 --- a/rust/src/ops.rs +++ b/rust/src/ops.rs @@ -279,10 +279,16 @@ pub struct PublicKeyForPartitionInfo { /// perform `Ops` (Rust) /// callback /// ``` -struct UserData<'a>(&'a mut dyn Ops); +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. -struct ScopedAvbOps<'a> { +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 @@ -291,7 +297,7 @@ struct ScopedAvbOps<'a> { } impl<'a> ScopedAvbOps<'a> { - fn new(user_data: &'a mut UserData<'a>) -> Self { + 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 diff --git a/rust/src/verify.rs b/rust/src/verify.rs new file mode 100644 index 0000000..62ecf0b --- /dev/null +++ b/rust/src/verify.rs @@ -0,0 +1,419 @@ +// 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, + VbmetaVerifyError, + }, + ops, IoError, Ops, +}; +use avb_bindgen::{ + avb_slot_verify, avb_slot_verify_data_free, AvbPartitionData, AvbSlotVerifyData, AvbVBMetaData, +}; +use core::{ + ffi::{c_char, CStr}, + fmt, + marker::PhantomData, + ptr::{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) -> Result<(), SlotVerifyError<'static>> { + 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) -> Result<(), SlotVerifyError<'static>> { + 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) -> Result<(), VbmetaVerifyError> { + 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<'a> 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) -> Result<(), SlotVerifyError<'static>> { + 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) -> Result<(), SlotVerifyError<'static>> { + 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<'a> 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. +#[derive(PartialEq, Eq)] +pub struct SlotVerifyData<'a> { + /// Internally owns the underlying data and deletes it on drop. + raw_data: NonNull<AvbSlotVerifyData>, + + /// This provides the necessary lifetime information so the compiler can make sure that + /// the `Ops` stays alive at least as long as we do. + _ops: PhantomData<&'a dyn Ops>, +} + +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, + ) -> Result<Self, SlotVerifyError<'static>> { + let ret = Self { + raw_data: NonNull::new(data).ok_or(SlotVerifyError::Internal)?, + _ops: PhantomData, + }; + + // 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, +) -> Result<SlotVerifyData<'a>, SlotVerifyError<'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), + } +} diff --git a/rust/tests/tests.rs b/rust/tests/tests.rs index a9df1c4..0e9b3e2 100644 --- a/rust/tests/tests.rs +++ b/rust/tests/tests.rs @@ -13,3 +13,4 @@ // limitations under the License. mod test_ops; +mod verify_tests; diff --git a/rust/tests/verify_tests.rs b/rust/tests/verify_tests.rs new file mode 100644 index 0000000..0482ae7 --- /dev/null +++ b/rust/tests/verify_tests.rs @@ -0,0 +1,543 @@ +// 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. + +//! libavb_rs verification tests. + +use crate::test_ops::TestOps; +use avb::{ + slot_verify, HashtreeErrorMode, IoError, SlotVerifyData, SlotVerifyError, SlotVerifyFlags, +}; +use std::{ffi::CString, fs}; +#[cfg(feature = "uuid")] +use uuid::{uuid, Uuid}; + +// These constants must match the values used to create the images in Android.bp. +const TEST_IMAGE_PATH: &str = "test_image.img"; +const TEST_IMAGE_SIZE: usize = 16 * 1024; +const TEST_VBMETA_PATH: &str = "test_vbmeta.img"; +const TEST_VBMETA_2_PARTITIONS_PATH: &str = "test_vbmeta_2_parts.img"; +const TEST_VBMETA_PERSISTENT_DIGEST_PATH: &str = "test_vbmeta_persistent_digest.img"; +const TEST_IMAGE_WITH_VBMETA_FOOTER_PATH: &str = "avbrs_test_image_with_vbmeta_footer.img"; +const TEST_PUBLIC_KEY_PATH: &str = "data/testkey_rsa4096_pub.bin"; +const TEST_PARTITION_NAME: &str = "test_part"; +const TEST_PARTITION_SLOT_C_NAME: &str = "test_part_c"; +const TEST_PARTITION_2_NAME: &str = "test_part_2"; +const TEST_PARTITION_PERSISTENT_DIGEST_NAME: &str = "test_part_persistent_digest"; +const TEST_VBMETA_ROLLBACK_LOCATION: usize = 0; // Default value, we don't explicitly set this. + +/// Initializes a `TestOps` object such that verification will succeed on `TEST_PARTITION_NAME`. +fn test_ops_one_image_one_vbmeta() -> TestOps { + 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()); + ops.add_vbmeta_key(fs::read(TEST_PUBLIC_KEY_PATH).unwrap(), None, true); + ops.rollbacks.insert(TEST_VBMETA_ROLLBACK_LOCATION, 0); + ops.unlock_state = Ok(false); + ops +} + +/// Calls `slot_verify()` using standard args for `test_ops_one_image_one_vbmeta()` setup. +fn verify_one_image_one_vbmeta<'a>( + ops: &'a mut TestOps, +) -> Result<SlotVerifyData<'a>, SlotVerifyError<'a>> { + slot_verify( + ops, + &[&CString::new(TEST_PARTITION_NAME).unwrap()], + None, + SlotVerifyFlags::AVB_SLOT_VERIFY_FLAGS_NONE, + HashtreeErrorMode::AVB_HASHTREE_ERROR_MODE_EIO, + ) +} + +/// 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 { + 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. + ops.add_partition(TEST_PARTITION_2_NAME, fs::read(TEST_IMAGE_PATH).unwrap()); + ops.add_partition("vbmeta", fs::read(TEST_VBMETA_2_PARTITIONS_PATH).unwrap()); + ops +} + +/// Calls `slot_verify()` using standard args for `test_ops_two_images_one_vbmeta()` setup. +fn verify_two_images_one_vbmeta<'a>( + ops: &'a mut TestOps, +) -> Result<SlotVerifyData<'a>, SlotVerifyError<'a>> { + slot_verify( + ops, + &[ + &CString::new(TEST_PARTITION_NAME).unwrap(), + &CString::new(TEST_PARTITION_2_NAME).unwrap(), + ], + None, + SlotVerifyFlags::AVB_SLOT_VERIFY_FLAGS_NONE, + HashtreeErrorMode::AVB_HASHTREE_ERROR_MODE_EIO, + ) +} + +/// Initializes a `TestOps` object such that verification will succeed on +/// `TEST_PARTITION_PERSISTENT_DIGEST_NAME`. +fn test_ops_persistent_digest(image: Vec<u8>) -> TestOps { + let mut ops = test_ops_one_image_one_vbmeta(); + ops.partitions.clear(); + // Use the vbmeta image with the persistent digest descriptor. + ops.add_partition( + "vbmeta", + fs::read(TEST_VBMETA_PERSISTENT_DIGEST_PATH).unwrap(), + ); + // Register the image contents to be stored via persistent digest. + ops.add_partition(TEST_PARTITION_PERSISTENT_DIGEST_NAME, image); + ops +} + +/// Calls `slot_verify()` using standard args for `test_ops_persistent_digest()` setup. +fn verify_persistent_digest<'a>( + ops: &'a mut TestOps, +) -> Result<SlotVerifyData<'a>, SlotVerifyError<'a>> { + slot_verify( + ops, + &[&CString::new(TEST_PARTITION_PERSISTENT_DIGEST_NAME).unwrap()], + None, + SlotVerifyFlags::AVB_SLOT_VERIFY_FLAGS_NONE, + HashtreeErrorMode::AVB_HASHTREE_ERROR_MODE_EIO, + ) +} + +/// 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; +} + +/// Returns the persistent value name for `TEST_PARTITION_PERSISTENT_DIGEST_NAME`. +fn persistent_digest_value_name() -> String { + // This exact format is a libavb implementation detail but is unlikely to change. If it does + // just update this format to match. + format!("avb.persistent_digest.{TEST_PARTITION_PERSISTENT_DIGEST_NAME}") +} + +#[test] +fn one_image_one_vbmeta_passes_verification_with_correct_data() { + let mut ops = test_ops_one_image_one_vbmeta(); + + let result = verify_one_image_one_vbmeta(&mut ops); + + // Make sure the resulting `SlotVerifyData` looks correct. + let data = result.unwrap(); + assert_eq!(data.ab_suffix().to_bytes(), b""); + // We don't care about the exact commandline, just search for a substring we know will + // exist to make sure the commandline is being provided to the caller correctly. + assert!(data + .cmdline() + .to_str() + .unwrap() + .contains("androidboot.vbmeta.device_state=locked")); + assert_eq!(data.rollback_indexes(), &[0; 32]); + assert_eq!( + data.resolved_hashtree_error_mode(), + HashtreeErrorMode::AVB_HASHTREE_ERROR_MODE_EIO + ); + + // Check the `VbmetaData` struct looks correct. + assert_eq!(data.vbmeta_data().len(), 1); + let vbmeta_data = &data.vbmeta_data()[0]; + assert_eq!(vbmeta_data.partition_name().to_str().unwrap(), "vbmeta"); + assert_eq!(vbmeta_data.data(), fs::read(TEST_VBMETA_PATH).unwrap()); + assert_eq!(vbmeta_data.verify_result(), Ok(())); + + // Check the `PartitionData` struct looks correct. + assert_eq!(data.partition_data().len(), 1); + let partition_data = &data.partition_data()[0]; + assert_eq!( + partition_data.partition_name().to_str().unwrap(), + TEST_PARTITION_NAME + ); + assert_eq!(partition_data.data(), fs::read(TEST_IMAGE_PATH).unwrap()); + assert!(!partition_data.preloaded()); + assert!(partition_data.verify_result().is_ok()); +} + +#[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; + + let result = verify_one_image_one_vbmeta(&mut ops); + + let data = result.unwrap(); + let partition_data = &data.partition_data()[0]; + assert!(partition_data.preloaded()); +} + +#[test] +fn slotted_partition_passes_verification() { + let mut ops = test_ops_one_image_one_vbmeta(); + // Move the partitions to a "_c" slot. + ops.partitions.clear(); + ops.add_partition( + TEST_PARTITION_SLOT_C_NAME, + fs::read(TEST_IMAGE_PATH).unwrap(), + ); + ops.add_partition("vbmeta_c", fs::read(TEST_VBMETA_PATH).unwrap()); + + let result = slot_verify( + &mut ops, + &[&CString::new(TEST_PARTITION_NAME).unwrap()], + Some(&CString::new("_c").unwrap()), + SlotVerifyFlags::AVB_SLOT_VERIFY_FLAGS_NONE, + HashtreeErrorMode::AVB_HASHTREE_ERROR_MODE_EIO, + ); + + let data = result.unwrap(); + assert_eq!(data.ab_suffix().to_bytes(), b"_c"); +} + +#[test] +fn two_images_one_vbmeta_passes_verification() { + let mut ops = test_ops_two_images_one_vbmeta(); + + let result = verify_two_images_one_vbmeta(&mut ops); + + // We should still only have 1 `VbmetaData` since we only used 1 vbmeta image, but it + // signed 2 partitions so we should have 2 `PartitionData` objects. + let data = result.unwrap(); + assert_eq!(data.vbmeta_data().len(), 1); + assert_eq!(data.partition_data().len(), 2); + assert_eq!( + data.partition_data()[0].partition_name().to_str().unwrap(), + TEST_PARTITION_NAME + ); + assert_eq!( + data.partition_data()[1].partition_name().to_str().unwrap(), + TEST_PARTITION_2_NAME + ); +} + +#[test] +fn combined_image_vbmeta_partition_passes_verification() { + let mut ops = test_ops_one_image_one_vbmeta(); + ops.partitions.clear(); + // Register the single combined image + vbmeta in `TEST_PARTITION_NAME`. + ops.add_partition( + TEST_PARTITION_NAME, + fs::read(TEST_IMAGE_WITH_VBMETA_FOOTER_PATH).unwrap(), + ); + // For a combined image we need to register the public key specifically for this partition. + ops.add_vbmeta_key_for_partition( + fs::read(TEST_PUBLIC_KEY_PATH).unwrap(), + None, + true, + TEST_PARTITION_NAME, + TEST_VBMETA_ROLLBACK_LOCATION as u32, + ); + + let result = slot_verify( + &mut ops, + &[&CString::new(TEST_PARTITION_NAME).unwrap()], + None, + // Tell libavb that the vbmeta image is embedded, not in its own partition. + SlotVerifyFlags::AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION, + HashtreeErrorMode::AVB_HASHTREE_ERROR_MODE_EIO, + ); + + let data = result.unwrap(); + + // Vbmeta should indicate that it came from `TEST_PARTITION_NAME`. + assert_eq!(data.vbmeta_data().len(), 1); + let vbmeta_data = &data.vbmeta_data()[0]; + assert_eq!( + vbmeta_data.partition_name().to_str().unwrap(), + TEST_PARTITION_NAME + ); + + // Partition should indicate that it came from `TEST_PARTITION_NAME`, but only contain the + // image contents. + assert_eq!(data.partition_data().len(), 1); + let partition_data = &data.partition_data()[0]; + assert_eq!( + partition_data.partition_name().to_str().unwrap(), + TEST_PARTITION_NAME + ); + assert_eq!(partition_data.data(), fs::read(TEST_IMAGE_PATH).unwrap()); +} + +#[test] +fn persistent_digest_verification_updates_persistent_value() { + // With persistent digests, the image hash isn't stored in the descriptor, but is instead + // calculated on-demand and stored into a named persistent value. So our test image can contain + // anything, but does have to match the size indicated by the descriptor. + let image_contents = vec![0xAAu8; TEST_IMAGE_SIZE]; + let mut ops = test_ops_persistent_digest(image_contents.clone()); + + { + let result = verify_persistent_digest(&mut ops); + let data = result.unwrap(); + assert_eq!(data.partition_data()[0].data(), image_contents); + } // Drop `result` here so it releases `ops` and we can use it again. + + assert!(ops + .persistent_values + .contains_key(&persistent_digest_value_name())); +} + +#[cfg(feature = "uuid")] +#[test] +fn successful_verification_substitutes_partition_guid() { + let mut ops = test_ops_one_image_one_vbmeta(); + ops.partitions.get_mut("vbmeta").unwrap().uuid = uuid!("01234567-89ab-cdef-0123-456789abcdef"); + + let result = verify_one_image_one_vbmeta(&mut ops); + + let data = result.unwrap(); + assert!(data + .cmdline() + .to_str() + .unwrap() + .contains("androidboot.vbmeta.device=PARTUUID=01234567-89ab-cdef-0123-456789abcdef")); +} + +#[test] +fn corrupted_image_fails_verification() { + let mut ops = test_ops_one_image_one_vbmeta(); + modify_partition_contents(&mut ops, TEST_PARTITION_NAME); + + let result = verify_one_image_one_vbmeta(&mut ops); + + let error = result.unwrap_err(); + assert!(matches!(error, SlotVerifyError::Verification(None))); +} + +#[test] +fn read_partition_callback_error_fails_verification() { + let mut ops = test_ops_one_image_one_vbmeta(); + ops.partitions.remove(TEST_PARTITION_NAME); + + let result = verify_one_image_one_vbmeta(&mut ops); + + let error = result.unwrap_err(); + assert!(matches!(error, SlotVerifyError::Io)); +} + +#[test] +fn undersized_partition_fails_verification() { + let mut ops = test_ops_one_image_one_vbmeta(); + ops.partitions + .get_mut(TEST_PARTITION_NAME) + .unwrap() + .contents + .pop(); + + let result = verify_one_image_one_vbmeta(&mut ops); + + let error = result.unwrap_err(); + assert!(matches!(error, SlotVerifyError::Io)); +} + +#[test] +fn corrupted_vbmeta_fails_verification() { + let mut ops = test_ops_one_image_one_vbmeta(); + modify_partition_contents(&mut ops, "vbmeta"); + + let result = verify_one_image_one_vbmeta(&mut ops); + + let error = result.unwrap_err(); + assert!(matches!(error, SlotVerifyError::InvalidMetadata)); +} + +#[test] +fn rollback_violation_fails_verification() { + let mut ops = test_ops_one_image_one_vbmeta(); + // Device with rollback = 1 should refuse to boot image with rollback = 0. + ops.rollbacks.insert(TEST_VBMETA_ROLLBACK_LOCATION, 1); + + let result = verify_one_image_one_vbmeta(&mut ops); + + let error = result.unwrap_err(); + assert!(matches!(error, SlotVerifyError::RollbackIndex)); +} + +#[test] +fn rollback_callback_error_fails_verification() { + let mut ops = test_ops_one_image_one_vbmeta(); + ops.rollbacks.clear(); + + let result = verify_one_image_one_vbmeta(&mut ops); + + let error = result.unwrap_err(); + assert!(matches!(error, SlotVerifyError::Io)); +} + +#[test] +fn untrusted_vbmeta_keys_fails_verification() { + let mut ops = test_ops_one_image_one_vbmeta(); + ops.add_vbmeta_key(fs::read(TEST_PUBLIC_KEY_PATH).unwrap(), None, false); + + let result = verify_one_image_one_vbmeta(&mut ops); + + let error = result.unwrap_err(); + assert!(matches!(error, SlotVerifyError::PublicKeyRejected)); +} + +#[test] +fn vbmeta_keys_callback_error_fails_verification() { + let mut ops = test_ops_one_image_one_vbmeta(); + ops.vbmeta_keys.clear(); + + let result = verify_one_image_one_vbmeta(&mut ops); + + let error = result.unwrap_err(); + assert!(matches!(error, SlotVerifyError::Io)); +} + +#[test] +fn unlock_state_callback_error_fails_verification() { + let mut ops = test_ops_one_image_one_vbmeta(); + ops.unlock_state = Err(IoError::Io); + + let result = verify_one_image_one_vbmeta(&mut ops); + + let error = result.unwrap_err(); + assert!(matches!(error, SlotVerifyError::Io)); +} + +#[test] +fn persistent_digest_mismatch_fails_verification() { + let image_contents = vec![0xAAu8; TEST_IMAGE_SIZE]; + let mut ops = test_ops_persistent_digest(image_contents.clone()); + // Put in an incorrect persistent digest; `slot_verify()` should detect the mismatch and fail. + ops.add_persistent_value(&persistent_digest_value_name(), Ok(b"incorrect_digest")); + // Make a copy so we can verify the persistent values don't change on failure. + let original_persistent_values = ops.persistent_values.clone(); + + assert!(verify_persistent_digest(&mut ops).is_err()); + + // Persistent value should be unchanged. + assert_eq!(ops.persistent_values, original_persistent_values); +} + +#[test] +fn persistent_digest_callback_error_fails_verification() { + let image_contents = vec![0xAAu8; TEST_IMAGE_SIZE]; + let mut ops = test_ops_persistent_digest(image_contents.clone()); + ops.add_persistent_value(&persistent_digest_value_name(), Err(IoError::NoSuchValue)); + + let result = verify_persistent_digest(&mut ops); + + let error = result.unwrap_err(); + assert!(matches!(error, SlotVerifyError::Io)); +} + +#[test] +fn corrupted_image_with_allow_verification_error_flag_fails_verification_with_data() { + let mut ops = test_ops_one_image_one_vbmeta(); + modify_partition_contents(&mut ops, TEST_PARTITION_NAME); + + let result = slot_verify( + &mut ops, + &[&CString::new(TEST_PARTITION_NAME).unwrap()], + None, + // Pass the flag to allow verification errors. + SlotVerifyFlags::AVB_SLOT_VERIFY_FLAGS_ALLOW_VERIFICATION_ERROR, + HashtreeErrorMode::AVB_HASHTREE_ERROR_MODE_EIO, + ); + + // Verification should fail, but with the `AVB_SLOT_VERIFY_FLAGS_ALLOW_VERIFICATION_ERROR` flag + // it should give us back the verification data. + let error = result.unwrap_err(); + let data = match error { + SlotVerifyError::Verification(Some(data)) => data, + _ => panic!("Expected verification data to exist"), + }; + + // vbmeta verification should have succeeded since that image was still correct. + assert_eq!(data.vbmeta_data().len(), 1); + assert_eq!(data.vbmeta_data()[0].verify_result(), Ok(())); + // Partition verification should have failed since we modified the image. + assert_eq!(data.partition_data().len(), 1); + assert!(matches!( + data.partition_data()[0].verify_result(), + Err(SlotVerifyError::Verification(None)) + )); +} + +#[test] +fn one_image_one_vbmeta_verification_data_display() { + let mut ops = test_ops_one_image_one_vbmeta(); + + let result = verify_one_image_one_vbmeta(&mut ops); + + let data = result.unwrap(); + assert_eq!( + format!("{data}"), + r#"slot: "", vbmeta: ["vbmeta": Ok(())], images: ["test_part": Ok(())]"# + ); +} + +#[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 result = verify_one_image_one_vbmeta(&mut ops); + + let data = result.unwrap(); + assert_eq!( + format!("{data}"), + r#"slot: "", vbmeta: ["vbmeta": Ok(())], images: ["test_part"(p): Ok(())]"# + ); +} + +#[test] +fn two_images_one_vbmeta_verification_data_display() { + let mut ops = test_ops_two_images_one_vbmeta(); + + let result = verify_two_images_one_vbmeta(&mut ops); + + let data = result.unwrap(); + assert_eq!( + format!("{data}"), + r#"slot: "", vbmeta: ["vbmeta": Ok(())], images: ["test_part": Ok(()), "test_part_2": Ok(())]"# + ); +} + +#[test] +fn corrupted_image_verification_data_display() { + let mut ops = test_ops_one_image_one_vbmeta(); + modify_partition_contents(&mut ops, TEST_PARTITION_NAME); + + let result = slot_verify( + &mut ops, + &[&CString::new(TEST_PARTITION_NAME).unwrap()], + None, + SlotVerifyFlags::AVB_SLOT_VERIFY_FLAGS_ALLOW_VERIFICATION_ERROR, + HashtreeErrorMode::AVB_HASHTREE_ERROR_MODE_EIO, + ); + + let error = result.unwrap_err(); + let data = match error { + SlotVerifyError::Verification(Some(data)) => data, + _ => panic!("Expected verification data to exist"), + }; + assert_eq!( + format!("{data}"), + r#"slot: "", vbmeta: ["vbmeta": Ok(())], images: ["test_part": Err(Verification(None))]"# + ); +} |