aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-11-24 00:04:27 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-11-24 00:04:27 +0000
commit47d8f9f0810d175f2f768ea9ebf1415c6587a851 (patch)
tree965847d835efc13f2452a2b152355d06a0be7d5a
parent4c57089f935fb47f9fa8b2ab84f8e1f6d788d85e (diff)
parent277a56813492dbcbc5527a0503841515623da8bb (diff)
downloadavb-47d8f9f0810d175f2f768ea9ebf1415c6587a851.tar.gz
Snap for 11139387 from 277a56813492dbcbc5527a0503841515623da8bb to 24Q1-release
Change-Id: Ibe701dfaf0e649ee2325cacfd4166ded8cbffa23
-rw-r--r--rust/Android.bp97
-rw-r--r--rust/src/error.rs160
-rw-r--r--rust/src/lib.rs4
-rw-r--r--rust/src/ops.rs12
-rw-r--r--rust/src/verify.rs419
-rw-r--r--rust/tests/tests.rs1
-rw-r--r--rust/tests/verify_tests.rs543
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))]"#
+ );
+}