aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2024-02-02 23:46:15 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2024-02-02 23:46:15 +0000
commit7c5fc412e814b2c5e27fca1e3db7ef9fecec6b7f (patch)
treef021df2b77ad341d7518458b5d970654e32daca3
parenta195c4b02a745998aae433e226ce736fe4986538 (diff)
parentb798e6083f6468128d51e38b015829eb63943e35 (diff)
downloadavb-7c5fc412e814b2c5e27fca1e3db7ef9fecec6b7f.tar.gz
Snap for 11400057 from b798e6083f6468128d51e38b015829eb63943e35 to simpleperf-release
Change-Id: I289f74643f2f1862b23b9646900c8c74f73310ae
-rw-r--r--rust/Android.bp257
-rw-r--r--rust/TEST_MAPPING6
-rw-r--r--rust/src/descriptor/chain.rs117
-rw-r--r--rust/src/descriptor/commandline.rs102
-rw-r--r--rust/src/descriptor/hash.rs131
-rw-r--r--rust/src/descriptor/hashtree.rs157
-rw-r--r--rust/src/descriptor/mod.rs271
-rw-r--r--rust/src/descriptor/property.rs108
-rw-r--r--rust/src/descriptor/util.rs178
-rw-r--r--rust/src/error.rs96
-rw-r--r--rust/src/lib.rs22
-rw-r--r--rust/src/ops.rs292
-rw-r--r--rust/src/verify.rs60
-rw-r--r--rust/testdata/chain_partition_descriptor.binbin0 -> 2160 bytes
-rw-r--r--rust/testdata/hash_descriptor.binbin0 -> 176 bytes
-rw-r--r--rust/testdata/hashtree_descriptor.binbin0 -> 240 bytes
-rw-r--r--rust/testdata/kernel_commandline_descriptor.binbin0 -> 64 bytes
-rw-r--r--rust/testdata/property_descriptor.binbin0 -> 64 bytes
-rw-r--r--rust/tests/test_ops.rs37
-rw-r--r--rust/tests/verify_tests.rs271
20 files changed, 1836 insertions, 269 deletions
diff --git a/rust/Android.bp b/rust/Android.bp
index b4d20e3..c390553 100644
--- a/rust/Android.bp
+++ b/rust/Android.bp
@@ -18,7 +18,7 @@
// with so are not exposed outside of this directory; instead we will provide
// a safe higher-level Rust API.
rust_defaults {
- name: "libavb_bindgen.defaults",
+ name: "libavb_bindgen.common.defaults",
wrapper_src: "bindgen/avb.h",
crate_name: "avb_bindgen",
edition: "2021",
@@ -30,52 +30,111 @@ rust_defaults {
],
source_stem: "bindings",
bindgen_flags: [
- "--constified-enum-module AvbDescriptorTag",
- "--bitfield-enum=AvbSlotVerifyFlags",
+ "--constified-enum-module=AvbDescriptorTag",
+ "--bitfield-enum=Avb.*Flags",
"--default-enum-style rust",
+ "--with-derive-default",
+ "--with-derive-custom=Avb.*Descriptor=FromZeroes,FromBytes",
"--allowlist-type=AvbDescriptorTag",
+ "--allowlist-type=Avb.*Flags",
"--allowlist-function=.*",
"--allowlist-var=AVB.*",
"--use-core",
"--raw-line=#![no_std]",
+ "--raw-line=use zerocopy::{FromZeroes, FromBytes};",
"--ctypes-prefix=core::ffi",
],
cflags: ["-DBORINGSSL_NO_CXX"],
}
-rust_bindgen {
- name: "libavb_bindgen",
- defaults: ["libavb_bindgen.defaults"],
+// Full bindgen defaults for std targets.
+rust_defaults {
+ name: "libavb_bindgen.std.defaults",
+ defaults: ["libavb_bindgen.common.defaults"],
host_supported: true,
- static_libs: [
- "libavb",
- ],
- shared_libs: [
- "libcrypto",
- ],
- apex_available: [
- "com.android.virt",
- ],
+ static_libs: ["libavb"],
+ shared_libs: ["libcrypto"],
+ rustlibs: ["libzerocopy"],
+ apex_available: ["com.android.virt"],
}
-rust_bindgen {
- name: "libavb_bindgen_nostd",
- defaults: ["libavb_bindgen.defaults"],
+// Full bindgen default for nostd targets.
+rust_defaults {
+ name: "libavb_bindgen.nostd.defaults",
+ defaults: ["libavb_bindgen.common.defaults"],
static_libs: [
"libavb_baremetal",
"libcrypto_baremetal",
],
+ rustlibs: ["libzerocopy_nostd_noalloc"],
+}
+
+// Internal source-only bindgen with std.
+//
+// This target should only be used as `srcs`, not `rustlibs` or `rlibs`. This
+// is because the `rust_bindgen` rule intentionally only generates rlibs
+// (b/166332519), and also forces its dependencies to use rlibs. However, this
+// can create mismatched library types if the depenency is also used elsewhere
+// in a build rule as a dylib. In particular for us, libzerocopy and its own
+// dependency libbyteorder trigger this problem like so:
+//
+// build target (prefer dylib)
+// / \
+// libavb_rs (dylib) \
+// / \
+// libavb_bindgen (rlib) ... arbitrary dependency chain (dylib) ...
+// / \
+// libzerocopy (rlib) \
+// / \
+// libbyteorder (rlib) libbyteorder (dylib)
+//
+// By using it as a `srcs` instead, we can wrap it in a `rust_library` which
+// allows selecting either library type and fixes the conflict:
+//
+// build target (prefer dylib)
+// / \
+// libavb_rs (dylib) \
+// / \
+// libavb_bindgen (dylib) ... arbitrary dependency chain (dylib) ...
+// / /
+// libzerocopy (dylib) /
+// \ /
+// libbyteorder (dylib)
+//
+rust_bindgen {
+ name: "libavb_bindgen_for_srcs_only",
+ defaults: ["libavb_bindgen.std.defaults"],
}
+// Bindgen with std.
+//
+// See above for why we need a `rust_library` wrapper here.
+rust_library {
+ name: "libavb_bindgen",
+ defaults: ["libavb_bindgen.std.defaults"],
+ srcs: [":libavb_bindgen_for_srcs_only"],
+}
+
+// Bindgen nostd.
+//
+// Nostd targets always use rlibs, so we don't need a `rust_library` wrapper in
+// this case; the rlib-only bindgen target is sufficient.
+rust_bindgen {
+ name: "libavb_bindgen_nostd",
+ defaults: ["libavb_bindgen.nostd.defaults"],
+}
+
+// Bindgen auto-generated tests.
rust_test {
name: "libavb_bindgen_test",
- srcs: [":libavb_bindgen"],
+ srcs: [":libavb_bindgen_for_srcs_only"],
crate_name: "avb_bindgen_test",
edition: "2021",
test_suites: ["general-tests"],
auto_gen_config: true,
clippy_lints: "none",
lints: "none",
+ rustlibs: ["libzerocopy"],
}
// Rust library wrapping libavb C implementation.
@@ -85,8 +144,8 @@ rust_defaults {
name: "libavb_rs_common.defaults",
crate_name: "avb",
srcs: ["src/lib.rs"],
- // Require unsafe blocks for inside unsafe functions.
- flags: ["-Dunsafe_op_in_unsafe_fn"],
+ clippy_lints: "android",
+ lints: "android",
}
// No std, no features.
@@ -99,6 +158,7 @@ rust_defaults {
no_stdlibs: true,
rustlibs: [
"libavb_bindgen_nostd",
+ "libzerocopy_nostd_noalloc",
],
whole_static_libs: [
"libavb_baremetal",
@@ -115,6 +175,7 @@ rust_defaults {
host_supported: true,
rustlibs: [
"libavb_bindgen",
+ "libzerocopy",
],
whole_static_libs: [
"libavb",
@@ -173,29 +234,90 @@ rust_library {
],
}
-// device test defaults.
+// TestOps lib: std
+rust_library {
+ crate_name: "avb_test",
+ name: "libavb_test_rs_testops",
+ srcs: ["tests/test_ops.rs"],
+ clippy_lints: "android",
+ lints: "android",
+ host_supported: true,
+ rustlibs: [
+ "libavb_rs",
+ ],
+ whole_static_libs: [
+ "libavb",
+ ],
+}
+
+// "libavb_rs.defaults" plus additional unit test defaults.
+rust_defaults {
+ name: "libavb_rs_unittest.defaults",
+ defaults: ["libavb_rs.defaults"],
+ data: [":libavb_rs_example_descriptors"],
+ test_suites: ["general-tests"],
+}
+
+// Unit tests: std, no features.
+rust_test {
+ name: "libavb_rs_unittest",
+ defaults: ["libavb_rs_unittest.defaults"],
+}
+
+// Unit tests: std, UUID feature.
+rust_test {
+ name: "libavb_rs_uuid_unittest",
+ defaults: [
+ "libavb_rs_unittest.defaults",
+ "libavb_rs.uuid.defaults",
+ ],
+}
+
+// Example descriptors in binary format.
+filegroup {
+ name: "libavb_rs_example_descriptors",
+ srcs: [
+ "testdata/chain_partition_descriptor.bin",
+ "testdata/hash_descriptor.bin",
+ "testdata/hashtree_descriptor.bin",
+ "testdata/kernel_commandline_descriptor.bin",
+ "testdata/property_descriptor.bin",
+ ],
+}
+
+// Integration test defaults.
rust_defaults {
name: "libavb_rs_test.defaults",
srcs: ["tests/tests.rs"],
data: [
":avb_testkey_rsa4096_pub_bin",
+ ":avb_testkey_rsa8192_pub_bin",
":avbrs_test_image",
":avbrs_test_image_with_vbmeta_footer",
+ ":avbrs_test_image_with_vbmeta_footer_for_boot",
+ ":avbrs_test_image_with_vbmeta_footer_for_test_part_2",
":avbrs_test_vbmeta",
":avbrs_test_vbmeta_2_parts",
":avbrs_test_vbmeta_persistent_digest",
+ ":avbrs_test_vbmeta_with_chained_partition",
+ ":avbrs_test_vbmeta_with_commandline",
+ ":avbrs_test_vbmeta_with_hashtree",
+ ":avbrs_test_vbmeta_with_property",
],
+ rustlibs: ["libhex"],
test_suites: ["general-tests"],
+ clippy_lints: "android",
+ lints: "android",
}
-// device test: no features.
+// Integration test: no features.
rust_test {
name: "libavb_rs_test",
defaults: ["libavb_rs_test.defaults"],
rustlibs: ["libavb_rs"],
}
-// device test: UUID feature.
+// Integration test: UUID feature.
rust_test {
name: "libavb_rs_uuid_test",
defaults: [
@@ -220,7 +342,7 @@ avb_gen_vbmeta_image {
name: "avbrs_test_image_descriptor",
src: ":avbrs_test_image",
partition_name: "test_part",
- salt: "0000",
+ salt: "1000",
}
// Unsigned vbmeta blob containing the test image descriptor for partition name "test_part_2".
@@ -228,7 +350,7 @@ avb_gen_vbmeta_image {
name: "avbrs_test_image_descriptor_2",
src: ":avbrs_test_image",
partition_name: "test_part_2",
- salt: "0001",
+ salt: "1001",
}
// Unsigned vbmeta blob containing a persistent digest descriptor for partition name
@@ -244,6 +366,18 @@ genrule {
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)",
}
+// Unsigned vbmeta blob containing a hastree descriptor for partition name
+// "test_part_hashtree".
+genrule {
+ name: "avbrs_test_image_descriptor_hashtree",
+ tools: ["avbtool"],
+ srcs: [":avbrs_test_image"],
+ out: ["avbrs_test_image_descriptor_hashtree.img"],
+ // Generating FEC values requires the `fec` tool to be on $PATH, which does
+ // not seems to be possible here. For now pass `--do_not_generate_fec`.
+ cmd: "$(location avbtool) add_hashtree_footer --image $(location :avbrs_test_image) --partition_name test_part_hashtree --partition_size 0 --salt B000 --do_not_append_vbmeta_image --output_vbmeta_image $(out) --do_not_generate_fec",
+}
+
// Standalone vbmeta image signing the test image descriptor.
genrule {
name: "avbrs_test_vbmeta",
@@ -281,6 +415,56 @@ genrule {
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)",
}
+// Standalone vbmeta image with property descriptor "test_prop_key" = "test_prop_value".
+genrule {
+ name: "avbrs_test_vbmeta_with_property",
+ tools: ["avbtool"],
+ srcs: [
+ ":avbrs_test_image_descriptor",
+ ":avb_testkey_rsa4096",
+ ],
+ out: ["test_vbmeta_with_property.img"],
+ cmd: "$(location avbtool) make_vbmeta_image --prop test_prop_key:test_prop_value --key $(location :avb_testkey_rsa4096) --algorithm SHA512_RSA4096 --include_descriptors_from_image $(location :avbrs_test_image_descriptor) --output $(out)",
+}
+
+// Standalone vbmeta image with the test image hashtree descriptor.
+genrule {
+ name: "avbrs_test_vbmeta_with_hashtree",
+ tools: ["avbtool"],
+ srcs: [
+ ":avbrs_test_image_descriptor_hashtree",
+ ":avb_testkey_rsa4096",
+ ],
+ out: ["test_vbmeta_with_hashtree.img"],
+ cmd: "$(location avbtool) make_vbmeta_image --key $(location :avb_testkey_rsa4096) --algorithm SHA512_RSA4096 --include_descriptors_from_image $(location :avbrs_test_image_descriptor_hashtree) --output $(out)",
+}
+
+// Standalone vbmeta image with kernel commandline "test_cmdline_key=test_cmdline_value".
+genrule {
+ name: "avbrs_test_vbmeta_with_commandline",
+ tools: ["avbtool"],
+ srcs: [
+ ":avbrs_test_image_descriptor",
+ ":avb_testkey_rsa4096",
+ ],
+ out: ["test_vbmeta_with_commandline.img"],
+ cmd: "$(location avbtool) make_vbmeta_image --kernel_cmdline test_cmdline_key=test_cmdline_value --key $(location :avb_testkey_rsa4096) --algorithm SHA512_RSA4096 --include_descriptors_from_image $(location :avbrs_test_image_descriptor) --output $(out)",
+}
+
+// Standalone vbmeta image with chain descriptor to "test_part_2" with rollback
+// index 4, signed by avb_testkey_rsa8192.
+genrule {
+ name: "avbrs_test_vbmeta_with_chained_partition",
+ tools: ["avbtool"],
+ srcs: [
+ ":avbrs_test_image_descriptor",
+ ":avb_testkey_rsa4096",
+ ":avb_testkey_rsa8192_pub_bin",
+ ],
+ out: ["test_vbmeta_with_chained_partition.img"],
+ cmd: "$(location avbtool) make_vbmeta_image --chain_partition test_part_2:4:$(location :avb_testkey_rsa8192_pub_bin) --key $(location :avb_testkey_rsa4096) --algorithm SHA512_RSA4096 --include_descriptors_from_image $(location :avbrs_test_image_descriptor) --output $(out)",
+}
+
// Combined test image + signed vbmeta footer for "test_part".
avb_add_hash_footer {
name: "avbrs_test_image_with_vbmeta_footer",
@@ -289,3 +473,24 @@ avb_add_hash_footer {
private_key: ":avb_testkey_rsa4096",
salt: "A000",
}
+
+// Combined test image + signed vbmeta footer for "boot".
+avb_add_hash_footer {
+ name: "avbrs_test_image_with_vbmeta_footer_for_boot",
+ src: ":avbrs_test_image",
+ partition_name: "boot",
+ private_key: ":avb_testkey_rsa4096",
+ salt: "A001",
+}
+
+// Combined test image + signed vbmeta footer for "test_part_2" signed by
+// avb_testkey_rsa8192 with rollback index = 7.
+avb_add_hash_footer {
+ name: "avbrs_test_image_with_vbmeta_footer_for_test_part_2",
+ src: ":avbrs_test_image",
+ partition_name: "test_part_2",
+ private_key: ":avb_testkey_rsa8192",
+ algorithm: "SHA256_RSA8192",
+ salt: "A002",
+ rollback_index: 7,
+}
diff --git a/rust/TEST_MAPPING b/rust/TEST_MAPPING
index cab6ba2..711ca9e 100644
--- a/rust/TEST_MAPPING
+++ b/rust/TEST_MAPPING
@@ -7,7 +7,13 @@
"name": "libavb_rs_test"
},
{
+ "name": "libavb_rs_unittest"
+ },
+ {
"name": "libavb_rs_uuid_test"
+ },
+ {
+ "name": "libavb_rs_uuid_unittest"
}
]
} \ No newline at end of file
diff --git a/rust/src/descriptor/chain.rs b/rust/src/descriptor/chain.rs
new file mode 100644
index 0000000..c579992
--- /dev/null
+++ b/rust/src/descriptor/chain.rs
@@ -0,0 +1,117 @@
+// Copyright 2024, 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.
+
+//! Chain partition descriptors.
+
+use super::{
+ util::{parse_descriptor, split_slice, ValidateAndByteswap, ValidationFunc},
+ DescriptorResult,
+};
+use avb_bindgen::{
+ avb_chain_partition_descriptor_validate_and_byteswap, AvbChainPartitionDescriptor,
+};
+use core::str::from_utf8;
+
+/// `AvbChainPartitionDescriptorFlags`; see libavb docs for details.
+pub use avb_bindgen::AvbChainPartitionDescriptorFlags as ChainPartitionDescriptorFlags;
+
+/// Wraps a chain partition descriptor stored in a vbmeta image.
+#[derive(Debug, PartialEq, Eq)]
+pub struct ChainPartitionDescriptor<'a> {
+ /// Chained partition rollback index location.
+ pub rollback_index_location: u32,
+
+ /// Chained partition name.
+ ///
+ /// Most partition names in this library are passed as `&CStr`, but inside
+ /// descriptors the partition names are not nul-terminated making them
+ /// ineligible for use directly as `&CStr`. If `&CStr` is required, one
+ /// option is to allocate a nul-terminated copy of this string via
+ /// `CString::new()` which can then be converted to `&CStr`.
+ pub partition_name: &'a str,
+
+ /// Chained partition public key.
+ pub public_key: &'a [u8],
+
+ /// Flags.
+ pub flags: ChainPartitionDescriptorFlags,
+}
+
+// SAFETY: `VALIDATE_AND_BYTESWAP_FUNC` is the correct libavb validator for this descriptor type.
+unsafe impl ValidateAndByteswap for AvbChainPartitionDescriptor {
+ const VALIDATE_AND_BYTESWAP_FUNC: ValidationFunc<Self> =
+ avb_chain_partition_descriptor_validate_and_byteswap;
+}
+
+impl<'a> ChainPartitionDescriptor<'a> {
+ /// Extract a `ChainPartitionDescriptor` from the given descriptor contents.
+ ///
+ /// # Arguments
+ /// * `contents`: descriptor contents, including the header, in raw big-endian format.
+ ///
+ /// # Returns
+ /// The new descriptor, or `DescriptorError` if the given `contents` aren't a valid
+ /// `AvbChainPartitionDescriptor`.
+ pub(super) fn new(contents: &'a [u8]) -> DescriptorResult<Self> {
+ // Descriptor contains: header + partition name + public key.
+ let descriptor = parse_descriptor::<AvbChainPartitionDescriptor>(contents)?;
+ let (partition_name, remainder) =
+ split_slice(descriptor.body, descriptor.header.partition_name_len)?;
+ let (public_key, _) = split_slice(remainder, descriptor.header.public_key_len)?;
+
+ Ok(Self {
+ flags: ChainPartitionDescriptorFlags(descriptor.header.flags),
+ partition_name: from_utf8(partition_name)?,
+ rollback_index_location: descriptor.header.rollback_index_location,
+ public_key,
+ })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use crate::DescriptorError;
+ use std::{fs, mem::size_of};
+
+ /// A valid descriptor that we've pre-generated as test data.
+ fn test_contents() -> Vec<u8> {
+ fs::read("testdata/chain_partition_descriptor.bin").unwrap()
+ }
+
+ #[test]
+ fn new_chain_partition_descriptor_success() {
+ assert!(ChainPartitionDescriptor::new(&test_contents()).is_ok());
+ }
+
+ #[test]
+ fn new_chain_partition_descriptor_too_short_header_fails() {
+ let bad_header_size = size_of::<AvbChainPartitionDescriptor>() - 1;
+ assert_eq!(
+ ChainPartitionDescriptor::new(&test_contents()[..bad_header_size]).unwrap_err(),
+ DescriptorError::InvalidHeader
+ );
+ }
+
+ #[test]
+ fn new_chain_partition_descriptor_too_short_contents_fails() {
+ // The last byte is padding, so we need to drop 2 bytes to trigger an error.
+ let bad_contents_size = test_contents().len() - 2;
+ assert_eq!(
+ ChainPartitionDescriptor::new(&test_contents()[..bad_contents_size]).unwrap_err(),
+ DescriptorError::InvalidSize
+ );
+ }
+}
diff --git a/rust/src/descriptor/commandline.rs b/rust/src/descriptor/commandline.rs
new file mode 100644
index 0000000..a9afbb4
--- /dev/null
+++ b/rust/src/descriptor/commandline.rs
@@ -0,0 +1,102 @@
+// Copyright 2024, 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.
+
+//! Kernel commandline descriptors.
+
+use super::{
+ util::{parse_descriptor, split_slice, ValidateAndByteswap, ValidationFunc},
+ DescriptorResult,
+};
+use avb_bindgen::{
+ avb_kernel_cmdline_descriptor_validate_and_byteswap, AvbKernelCmdlineDescriptor,
+};
+use core::str::from_utf8;
+
+/// `AvbKernelCmdlineFlags`; see libavb docs for details.
+pub use avb_bindgen::AvbKernelCmdlineFlags as KernelCommandlineDescriptorFlags;
+
+/// Wraps an `AvbKernelCmdlineDescriptor` stored in a vbmeta image.
+#[derive(Debug, PartialEq, Eq)]
+pub struct KernelCommandlineDescriptor<'a> {
+ /// Flags.
+ pub flags: KernelCommandlineDescriptorFlags,
+
+ /// Kernel commandline.
+ pub commandline: &'a str,
+}
+
+// SAFETY: `VALIDATE_AND_BYTESWAP_FUNC` is the correct libavb validator for this descriptor type.
+unsafe impl ValidateAndByteswap for AvbKernelCmdlineDescriptor {
+ const VALIDATE_AND_BYTESWAP_FUNC: ValidationFunc<Self> =
+ avb_kernel_cmdline_descriptor_validate_and_byteswap;
+}
+
+impl<'a> KernelCommandlineDescriptor<'a> {
+ /// Extracts a `KernelCommandlineDescriptor` from the given descriptor contents.
+ ///
+ /// # Arguments
+ /// * `contents`: descriptor contents, including the header, in raw big-endian format.
+ ///
+ /// # Returns
+ /// The new descriptor, or `DescriptorError` if the given `contents` aren't a valid
+ /// `AvbKernelCmdlineDescriptor`.
+ pub(super) fn new(contents: &'a [u8]) -> DescriptorResult<Self> {
+ // Descriptor contains: header + commandline.
+ let descriptor = parse_descriptor::<AvbKernelCmdlineDescriptor>(contents)?;
+ let (commandline, _) =
+ split_slice(descriptor.body, descriptor.header.kernel_cmdline_length)?;
+
+ Ok(Self {
+ flags: KernelCommandlineDescriptorFlags(descriptor.header.flags),
+ commandline: from_utf8(commandline)?,
+ })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use crate::DescriptorError;
+ use std::{fs, mem::size_of};
+
+ /// A valid descriptor that we've pre-generated as test data.
+ fn test_contents() -> Vec<u8> {
+ fs::read("testdata/kernel_commandline_descriptor.bin").unwrap()
+ }
+
+ #[test]
+ fn new_commandline_descriptor_success() {
+ assert!(KernelCommandlineDescriptor::new(&test_contents()).is_ok());
+ }
+
+ #[test]
+ fn new_commandline_descriptor_too_short_header_fails() {
+ let bad_header_size = size_of::<KernelCommandlineDescriptor>() - 1;
+ assert_eq!(
+ KernelCommandlineDescriptor::new(&test_contents()[..bad_header_size]).unwrap_err(),
+ DescriptorError::InvalidHeader
+ );
+ }
+
+ #[test]
+ fn new_commandline_descriptor_too_short_contents_fails() {
+ // The last 5 bytes are padding, so we need to drop 6 bytes to trigger an error.
+ let bad_contents_size = test_contents().len() - 6;
+ assert_eq!(
+ KernelCommandlineDescriptor::new(&test_contents()[..bad_contents_size]).unwrap_err(),
+ DescriptorError::InvalidSize
+ );
+ }
+}
diff --git a/rust/src/descriptor/hash.rs b/rust/src/descriptor/hash.rs
new file mode 100644
index 0000000..be7fb5b
--- /dev/null
+++ b/rust/src/descriptor/hash.rs
@@ -0,0 +1,131 @@
+// 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.
+
+//! Hash descriptors.
+
+use super::{
+ util::{parse_descriptor, split_slice, ValidateAndByteswap, ValidationFunc},
+ DescriptorResult,
+};
+use avb_bindgen::{avb_hash_descriptor_validate_and_byteswap, AvbHashDescriptor};
+use core::{ffi::CStr, str::from_utf8};
+
+/// `AvbHashDescriptorFlags`; see libavb docs for details.
+pub use avb_bindgen::AvbHashDescriptorFlags as HashDescriptorFlags;
+
+/// Wraps a Hash descriptor stored in a vbmeta image.
+#[derive(Debug, PartialEq, Eq)]
+pub struct HashDescriptor<'a> {
+ /// The size of the hashed image.
+ pub image_size: u64,
+
+ /// Hash algorithm name.
+ pub hash_algorithm: &'a str,
+
+ /// Flags.
+ pub flags: HashDescriptorFlags,
+
+ /// Partition name.
+ ///
+ /// Most partition names in this library are passed as `&CStr`, but inside
+ /// descriptors the partition names are not nul-terminated making them
+ /// ineligible for use directly as `&CStr`. If `&CStr` is required, one
+ /// option is to allocate a nul-terminated copy of this string via
+ /// `CString::new()` which can then be converted to `&CStr`.
+ pub partition_name: &'a str,
+
+ /// Salt used to hash the image.
+ pub salt: &'a [u8],
+
+ /// Image hash digest.
+ pub digest: &'a [u8],
+}
+
+// SAFETY: `VALIDATE_AND_BYTESWAP_FUNC` is the correct libavb validator for this descriptor type.
+unsafe impl ValidateAndByteswap for AvbHashDescriptor {
+ const VALIDATE_AND_BYTESWAP_FUNC: ValidationFunc<Self> =
+ avb_hash_descriptor_validate_and_byteswap;
+}
+
+impl<'a> HashDescriptor<'a> {
+ /// Extract a `HashDescriptor` from the given descriptor contents.
+ ///
+ /// # Arguments
+ /// * `contents`: descriptor contents, including the header, in raw big-endian format.
+ ///
+ /// # Returns
+ /// The new descriptor, or `DescriptorError` if the given `contents` aren't a valid
+ /// `AvbHashDescriptor`.
+ pub(super) fn new(contents: &'a [u8]) -> DescriptorResult<Self> {
+ // Descriptor contains: header + name + salt + digest.
+ let descriptor = parse_descriptor::<AvbHashDescriptor>(contents)?;
+ let (partition_name, remainder) =
+ split_slice(descriptor.body, descriptor.header.partition_name_len)?;
+ let (salt, remainder) = split_slice(remainder, descriptor.header.salt_len)?;
+ let (digest, _) = split_slice(remainder, descriptor.header.digest_len)?;
+
+ // Extract the hash algorithm from the original raw header since the temporary
+ // byte-swapped header doesn't live past this function.
+ // The hash algorithm is a nul-terminated UTF-8 string which is identical in the raw
+ // and byteswapped headers.
+ let hash_algorithm =
+ CStr::from_bytes_until_nul(&descriptor.raw_header.hash_algorithm)?.to_str()?;
+
+ Ok(Self {
+ image_size: descriptor.header.image_size,
+ hash_algorithm,
+ flags: HashDescriptorFlags(descriptor.header.flags),
+ partition_name: from_utf8(partition_name)?,
+ salt,
+ digest,
+ })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use crate::DescriptorError;
+ use std::{fs, mem::size_of};
+
+ /// A valid descriptor that we've pre-generated as test data.
+ fn test_contents() -> Vec<u8> {
+ fs::read("testdata/hash_descriptor.bin").unwrap()
+ }
+
+ #[test]
+ fn new_hash_descriptor_success() {
+ assert!(HashDescriptor::new(&test_contents()).is_ok());
+ }
+
+ #[test]
+ fn new_hash_descriptor_too_short_header_fails() {
+ let bad_header_size = size_of::<AvbHashDescriptor>() - 1;
+ assert_eq!(
+ HashDescriptor::new(&test_contents()[..bad_header_size]).unwrap_err(),
+ DescriptorError::InvalidHeader
+ );
+ }
+
+ #[test]
+ fn new_hash_descriptor_too_short_contents_fails() {
+ // The last byte is padding, so we need to drop 2 bytes to trigger an error.
+ let bad_contents_size = test_contents().len() - 2;
+ assert_eq!(
+ HashDescriptor::new(&test_contents()[..bad_contents_size]).unwrap_err(),
+ DescriptorError::InvalidSize
+ );
+ }
+}
diff --git a/rust/src/descriptor/hashtree.rs b/rust/src/descriptor/hashtree.rs
new file mode 100644
index 0000000..cc2ee8c
--- /dev/null
+++ b/rust/src/descriptor/hashtree.rs
@@ -0,0 +1,157 @@
+// Copyright 2024, 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.
+
+//! Hashtree descriptors.
+
+use super::{
+ util::{parse_descriptor, split_slice, ValidateAndByteswap, ValidationFunc},
+ DescriptorResult,
+};
+use avb_bindgen::{avb_hashtree_descriptor_validate_and_byteswap, AvbHashtreeDescriptor};
+use core::{ffi::CStr, str::from_utf8};
+
+/// `AvbHashtreeDescriptorFlags`; see libavb docs for details.
+pub use avb_bindgen::AvbHashtreeDescriptorFlags as HashtreeDescriptorFlags;
+
+/// Wraps a Hashtree descriptor stored in a vbmeta image.
+#[derive(Debug, PartialEq, Eq)]
+pub struct HashtreeDescriptor<'a> {
+ /// DM-Verity version.
+ pub dm_verity_version: u32,
+
+ /// Hashed image size.
+ pub image_size: u64,
+
+ /// Offset to the root block of the hash tree.
+ pub tree_offset: u64,
+
+ /// Hash tree size.
+ pub tree_size: u64,
+
+ /// Data block size in bytes.
+ pub data_block_size: u32,
+
+ /// Hash block size in bytes.
+ pub hash_block_size: u32,
+
+ /// Number of forward error correction roots.
+ pub fec_num_roots: u32,
+
+ /// Offset to the forward error correction data.
+ pub fec_offset: u64,
+
+ /// Forward error correction data size.
+ pub fec_size: u64,
+
+ /// Hash algorithm name.
+ pub hash_algorithm: &'a str,
+
+ /// Flags.
+ pub flags: HashtreeDescriptorFlags,
+
+ /// Partition name.
+ pub partition_name: &'a str,
+
+ /// Salt used to hash the image.
+ pub salt: &'a [u8],
+
+ /// Image root hash digest.
+ pub root_digest: &'a [u8],
+}
+
+// SAFETY: `VALIDATE_AND_BYTESWAP_FUNC` is the correct libavb validator for this descriptor type.
+unsafe impl ValidateAndByteswap for AvbHashtreeDescriptor {
+ const VALIDATE_AND_BYTESWAP_FUNC: ValidationFunc<Self> =
+ avb_hashtree_descriptor_validate_and_byteswap;
+}
+
+impl<'a> HashtreeDescriptor<'a> {
+ /// Extract a `HashtreeDescriptor` from the given descriptor contents.
+ ///
+ /// # Arguments
+ /// * `contents`: descriptor contents, including the header, in raw big-endian format.
+ ///
+ /// # Returns
+ /// The new descriptor, or `DescriptorError` if the given `contents` aren't a valid
+ /// `AvbHashtreeDescriptor`.
+ pub(super) fn new(contents: &'a [u8]) -> DescriptorResult<Self> {
+ // Descriptor contains: header + name + salt + digest.
+ let descriptor = parse_descriptor::<AvbHashtreeDescriptor>(contents)?;
+ let (partition_name, remainder) =
+ split_slice(descriptor.body, descriptor.header.partition_name_len)?;
+ let (salt, remainder) = split_slice(remainder, descriptor.header.salt_len)?;
+ let (root_digest, _) = split_slice(remainder, descriptor.header.root_digest_len)?;
+
+ // Extract the hash algorithm from the original raw header since the temporary
+ // byte-swapped header doesn't live past this function.
+ // The hash algorithm is a nul-terminated UTF-8 string which is identical in the raw
+ // and byteswapped headers.
+ let hash_algorithm =
+ CStr::from_bytes_until_nul(&descriptor.raw_header.hash_algorithm)?.to_str()?;
+
+ Ok(Self {
+ dm_verity_version: descriptor.header.dm_verity_version,
+ image_size: descriptor.header.image_size,
+ tree_offset: descriptor.header.tree_offset,
+ tree_size: descriptor.header.tree_size,
+ data_block_size: descriptor.header.data_block_size,
+ hash_block_size: descriptor.header.hash_block_size,
+ fec_num_roots: descriptor.header.fec_num_roots,
+ fec_offset: descriptor.header.fec_offset,
+ fec_size: descriptor.header.fec_size,
+ hash_algorithm,
+ partition_name: from_utf8(partition_name)?,
+ salt,
+ root_digest,
+ flags: HashtreeDescriptorFlags(descriptor.header.flags),
+ })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use crate::DescriptorError;
+ use std::{fs, mem::size_of};
+
+ /// A valid descriptor that we've pre-generated as test data.
+ fn test_contents() -> Vec<u8> {
+ fs::read("testdata/hashtree_descriptor.bin").unwrap()
+ }
+
+ #[test]
+ fn new_hashtree_descriptor_success() {
+ assert!(HashtreeDescriptor::new(&test_contents()).is_ok());
+ }
+
+ #[test]
+ fn new_hashtree_descriptor_too_short_header_fails() {
+ let bad_header_size = size_of::<AvbHashtreeDescriptor>() - 1;
+ assert_eq!(
+ HashtreeDescriptor::new(&test_contents()[..bad_header_size]).unwrap_err(),
+ DescriptorError::InvalidHeader
+ );
+ }
+
+ #[test]
+ fn new_hashtree_descriptor_too_short_contents_fails() {
+ // The last 2 bytes are padding, so we need to drop 3 bytes to trigger an error.
+ let bad_contents_size = test_contents().len() - 3;
+ assert_eq!(
+ HashtreeDescriptor::new(&test_contents()[..bad_contents_size]).unwrap_err(),
+ DescriptorError::InvalidSize
+ );
+ }
+}
diff --git a/rust/src/descriptor/mod.rs b/rust/src/descriptor/mod.rs
new file mode 100644
index 0000000..488401e
--- /dev/null
+++ b/rust/src/descriptor/mod.rs
@@ -0,0 +1,271 @@
+// 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.
+
+//! Descriptor extraction and handling.
+//!
+//! Descriptors are information encoded into vbmeta images which can be
+//! extracted from the resulting data after performing verification.
+
+extern crate alloc;
+
+mod chain;
+mod commandline;
+mod hash;
+mod hashtree;
+mod property;
+mod util;
+
+use crate::VbmetaData;
+use alloc::vec::Vec;
+use avb_bindgen::{
+ avb_descriptor_foreach, avb_descriptor_validate_and_byteswap, AvbDescriptor, AvbDescriptorTag,
+};
+use core::{
+ ffi::{c_void, FromBytesUntilNulError},
+ mem::size_of,
+ slice,
+ str::Utf8Error,
+};
+
+pub use chain::{ChainPartitionDescriptor, ChainPartitionDescriptorFlags};
+pub use commandline::{KernelCommandlineDescriptor, KernelCommandlineDescriptorFlags};
+pub use hash::{HashDescriptor, HashDescriptorFlags};
+pub use hashtree::{HashtreeDescriptor, HashtreeDescriptorFlags};
+pub use property::PropertyDescriptor;
+
+/// A single descriptor.
+#[derive(Debug, PartialEq, Eq)]
+pub enum Descriptor<'a> {
+ /// Wraps `AvbPropertyDescriptor`.
+ Property(PropertyDescriptor<'a>),
+ /// Wraps `AvbHashtreeDescriptor`.
+ Hashtree(HashtreeDescriptor<'a>),
+ /// Wraps `AvbHashDescriptor`.
+ Hash(HashDescriptor<'a>),
+ /// Wraps `AvbKernelCmdlineDescriptor`.
+ KernelCommandline(KernelCommandlineDescriptor<'a>),
+ /// Wraps `AvbChainPartitionDescriptor`.
+ ChainPartition(ChainPartitionDescriptor<'a>),
+ /// Unknown descriptor type.
+ Unknown(&'a [u8]),
+}
+
+/// Possible errors when extracting descriptors.
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum DescriptorError {
+ /// libavb rejected the descriptor header.
+ InvalidHeader,
+ /// A value in the descriptor was invalid.
+ InvalidValue,
+ /// The descriptor claimed to be larger than the available data.
+ InvalidSize,
+ /// A field that was supposed to be valid UTF-8 was not.
+ InvalidUtf8,
+ /// Descriptor contents don't match what we expect.
+ InvalidContents,
+}
+
+impl From<Utf8Error> for DescriptorError {
+ fn from(_: Utf8Error) -> Self {
+ Self::InvalidUtf8
+ }
+}
+
+impl From<FromBytesUntilNulError> for DescriptorError {
+ fn from(_: FromBytesUntilNulError) -> Self {
+ Self::InvalidContents
+ }
+}
+
+/// `Result` type for `DescriptorError` errors.
+pub type DescriptorResult<T> = Result<T, DescriptorError>;
+
+impl<'a> Descriptor<'a> {
+ /// Extracts the fully-typed descriptor from the generic `AvbDescriptor` header.
+ ///
+ /// # Arguments
+ /// * `raw_descriptor`: the raw `AvbDescriptor` pointing into the vbmeta image.
+ ///
+ /// # Returns
+ /// The fully-typed `Descriptor`, or `DescriptorError` if parsing the descriptor failed.
+ ///
+ /// # Safety
+ /// `raw_descriptor` must point to a valid `AvbDescriptor`, including the `num_bytes_following`
+ /// data contents, that lives at least as long as `'a`.
+ unsafe fn new(raw_descriptor: *const AvbDescriptor) -> DescriptorResult<Self> {
+ // Transform header to host-endian.
+ let mut descriptor = AvbDescriptor {
+ tag: 0,
+ num_bytes_following: 0,
+ };
+ // SAFETY: both args point to valid `AvbDescriptor` objects.
+ if !unsafe { avb_descriptor_validate_and_byteswap(raw_descriptor, &mut descriptor) } {
+ return Err(DescriptorError::InvalidHeader);
+ }
+
+ // Extract the descriptor header and contents bytes. The descriptor sub-type headers
+ // include the top-level header as the first member, so we need to grab the entire
+ // descriptor including the top-level header.
+ let num_bytes_following = descriptor
+ .num_bytes_following
+ .try_into()
+ .map_err(|_| DescriptorError::InvalidValue)?;
+ let total_size = size_of::<AvbDescriptor>()
+ .checked_add(num_bytes_following)
+ .ok_or(DescriptorError::InvalidValue)?;
+
+ // SAFETY: `raw_descriptor` points to the header plus `num_bytes_following` bytes.
+ let contents = unsafe { slice::from_raw_parts(raw_descriptor as *const u8, total_size) };
+
+ match descriptor.tag.try_into() {
+ Ok(AvbDescriptorTag::AVB_DESCRIPTOR_TAG_PROPERTY) => {
+ Ok(Descriptor::Property(PropertyDescriptor::new(contents)?))
+ }
+ Ok(AvbDescriptorTag::AVB_DESCRIPTOR_TAG_HASHTREE) => {
+ Ok(Descriptor::Hashtree(HashtreeDescriptor::new(contents)?))
+ }
+ Ok(AvbDescriptorTag::AVB_DESCRIPTOR_TAG_HASH) => {
+ Ok(Descriptor::Hash(HashDescriptor::new(contents)?))
+ }
+ Ok(AvbDescriptorTag::AVB_DESCRIPTOR_TAG_KERNEL_CMDLINE) => Ok(
+ Descriptor::KernelCommandline(KernelCommandlineDescriptor::new(contents)?),
+ ),
+ Ok(AvbDescriptorTag::AVB_DESCRIPTOR_TAG_CHAIN_PARTITION) => Ok(
+ Descriptor::ChainPartition(ChainPartitionDescriptor::new(contents)?),
+ ),
+ _ => Ok(Descriptor::Unknown(contents)),
+ }
+ }
+}
+
+/// Returns a vector of descriptors extracted from the given vbmeta image.
+///
+/// # Arguments
+/// * `vbmeta`: the `VbmetaData` object to extract descriptors from.
+///
+/// # Returns
+/// The descriptors, or `DescriptorError` if any error occurred.
+///
+/// # Safety
+/// `vbmeta` must have been validated by `slot_verify()`.
+pub(crate) unsafe fn get_descriptors(vbmeta: &VbmetaData) -> DescriptorResult<Vec<Descriptor>> {
+ let mut result = Ok(Vec::new());
+
+ // Use `avb_descriptor_foreach()` to grab all the descriptor pointers in `vmbeta.data()`.
+ // This implementation processes all the descriptors immediately, so that any error is
+ // detected here and working with descriptors can be error-free.
+ //
+ // SAFETY:
+ // * the caller ensures that `vbmeta` has been validated by `slot_verify()`, which satisfies
+ // the libavb `avb_vbmeta_image_verify()` requirement.
+ // * `avb_descriptor_foreach()` ensures the validity of each descriptor pointer passed to
+ // the `fill_descriptors_vec()` callback.
+ // * our lifetimes guarantee that the raw descriptor data in `vbmeta` will remain unchanged for
+ // the lifetime of the returned `Descriptor` objects.
+ // * the `user_data` param is a valid `DescriptorResult<Vec<Descriptor>>` with no other
+ // concurrent access.
+ unsafe {
+ // We can ignore the return value of this function since we use the passed-in `result`
+ // to convey success/failure as well as more detailed error info.
+ avb_descriptor_foreach(
+ vbmeta.data().as_ptr(),
+ vbmeta.data().len(),
+ Some(fill_descriptors_vec),
+ &mut result as *mut _ as *mut c_void,
+ );
+ }
+
+ result
+}
+
+/// Adds the given descriptor to the `Vec` pointed to by `user_data`.
+///
+/// Serves as a C callback for use with `avb_descriptor_foreach()`.
+///
+/// # Returns
+/// True on success, false on failure (which will stop iteration early).
+///
+/// # Safety
+/// * `descriptor` must point to a valid `AvbDescriptor`, including the `num_bytes_following`
+/// data contents, which remains valid and unmodified for the lifetime of the `Descriptor` objects
+/// in `user_data`.
+/// * `user_data` must point to a valid `DescriptorResult<Vec<Descriptor>>` with no other concurrent
+/// access.
+unsafe extern "C" fn fill_descriptors_vec(
+ descriptor: *const AvbDescriptor,
+ user_data: *mut c_void,
+) -> bool {
+ // SAFETY: `user_data` gives exclusive access to a valid `DescriptorResult<Vec<Descriptor>>`.
+ let result = unsafe { (user_data as *mut DescriptorResult<Vec<Descriptor>>).as_mut() };
+ // We can always unwrap here because we never pass a NULL pointer as `user_data`.
+ let result = result.unwrap();
+
+ // SAFETY: caller ensures that `descriptor` points to a valid `AvbDescriptor` with header and
+ // body contents, which remains unmodified at least as long as the new `Descriptor`.
+ match unsafe { Descriptor::new(descriptor) } {
+ Ok(d) => {
+ // We can always unwrap here because this function will never be called with an error
+ // in `result`, since we stop iteration as soon as we encounter an error.
+ result.as_mut().unwrap().push(d);
+ true
+ }
+ Err(e) => {
+ // Set the error and stop iteration early.
+ *result = Err(e);
+ false
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn new_unknown_descriptor() {
+ // A fake descriptor which is valid but with an unknown tag.
+ let data: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, // tag = 0x42u64 (BE)
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, // num_bytes_following = 8u64 (BE)
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // fake contents
+ ];
+
+ // SAFETY: we've crafted a valid descriptor in `data`.
+ let descriptor = unsafe { Descriptor::new(data.as_ptr() as *const _) }.unwrap();
+
+ let contents = match descriptor {
+ Descriptor::Unknown(c) => c,
+ d => panic!("Expected Unknown descriptor, got {d:?}"),
+ };
+ assert_eq!(data, contents);
+ }
+
+ #[test]
+ fn new_invalid_descriptor_length_fails() {
+ // `avb_descriptor_validate_and_byteswap()` should detect and reject descriptors whose
+ // `num_bytes_following` is not 8-byte aligned.
+ let data: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, // tag = 0x42u64 (BE)
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, // num_bytes_following = 7u64 (BE)
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // fake contents
+ ];
+
+ assert_eq!(
+ // SAFETY: we've created an invalid descriptor in a way that should be detected and
+ // fail safely without triggering any undefined behavior.
+ unsafe { Descriptor::new(data.as_ptr() as *const _) }.unwrap_err(),
+ DescriptorError::InvalidHeader
+ );
+ }
+}
diff --git a/rust/src/descriptor/property.rs b/rust/src/descriptor/property.rs
new file mode 100644
index 0000000..c97f48f
--- /dev/null
+++ b/rust/src/descriptor/property.rs
@@ -0,0 +1,108 @@
+// 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.
+
+//! Property descriptors.
+
+use super::{
+ util::{parse_descriptor, split_slice, ValidateAndByteswap, ValidationFunc},
+ DescriptorError, DescriptorResult,
+};
+use avb_bindgen::{avb_property_descriptor_validate_and_byteswap, AvbPropertyDescriptor};
+use core::str::from_utf8;
+
+/// Checks that the first byte is nul and discards it.
+/// Returns the remainder of `bytes` on success, or `DescriptorError` if the byte wasn't nul.
+fn extract_nul(bytes: &[u8]) -> DescriptorResult<&[u8]> {
+ let (nul, remainder) = split_slice(bytes, 1)?;
+ match nul {
+ b"\0" => Ok(remainder),
+ _ => Err(DescriptorError::InvalidContents),
+ }
+}
+
+/// Wraps an `AvbPropertyDescriptor` stored in a vbmeta image.
+#[derive(Debug, PartialEq, Eq)]
+pub struct PropertyDescriptor<'a> {
+ /// Key is always UTF-8.
+ pub key: &'a str,
+
+ /// Value can be arbitrary bytes.
+ pub value: &'a [u8],
+}
+
+// SAFETY: `VALIDATE_AND_BYTESWAP_FUNC` is the correct libavb validator for this descriptor type.
+unsafe impl ValidateAndByteswap for AvbPropertyDescriptor {
+ const VALIDATE_AND_BYTESWAP_FUNC: ValidationFunc<Self> =
+ avb_property_descriptor_validate_and_byteswap;
+}
+
+impl<'a> PropertyDescriptor<'a> {
+ /// Extract a `PropertyDescriptor` from the given descriptor contents.
+ ///
+ /// # Arguments
+ /// * `contents`: descriptor contents, including the header, in raw big-endian format.
+ ///
+ /// # Returns
+ /// The new descriptor, or `DescriptorError` if the given `contents` aren't a valid
+ /// `AvbPropertyDescriptor`.
+ pub(super) fn new(contents: &'a [u8]) -> DescriptorResult<Self> {
+ // Descriptor contains: header + key + nul + value + nul.
+ let descriptor = parse_descriptor::<AvbPropertyDescriptor>(contents)?;
+ let (key, remainder) = split_slice(descriptor.body, descriptor.header.key_num_bytes)?;
+ let remainder = extract_nul(remainder)?;
+ let (value, remainder) = split_slice(remainder, descriptor.header.value_num_bytes)?;
+ extract_nul(remainder)?;
+
+ Ok(Self {
+ key: from_utf8(key)?,
+ value,
+ })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use std::{fs, mem::size_of};
+
+ /// A valid descriptor that we've pre-generated as test data.
+ fn test_contents() -> Vec<u8> {
+ fs::read("testdata/property_descriptor.bin").unwrap()
+ }
+
+ #[test]
+ fn new_property_descriptor_success() {
+ assert!(PropertyDescriptor::new(&test_contents()).is_ok());
+ }
+
+ #[test]
+ fn new_property_descriptor_too_short_header_fails() {
+ let bad_header_size = size_of::<AvbPropertyDescriptor>() - 1;
+ assert_eq!(
+ PropertyDescriptor::new(&test_contents()[..bad_header_size]).unwrap_err(),
+ DescriptorError::InvalidHeader
+ );
+ }
+
+ #[test]
+ fn new_property_descriptor_too_short_contents_fails() {
+ // The last 2 bytes are padding, so we need to drop 3 bytes to trigger an error.
+ let bad_contents_size = test_contents().len() - 3;
+ assert_eq!(
+ PropertyDescriptor::new(&test_contents()[..bad_contents_size]).unwrap_err(),
+ DescriptorError::InvalidSize
+ );
+ }
+}
diff --git a/rust/src/descriptor/util.rs b/rust/src/descriptor/util.rs
new file mode 100644
index 0000000..e4e23fd
--- /dev/null
+++ b/rust/src/descriptor/util.rs
@@ -0,0 +1,178 @@
+// 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.
+
+//! Descriptor utilities.
+
+use super::{DescriptorError, DescriptorResult};
+use zerocopy::{FromBytes, FromZeroes, Ref};
+
+/// Splits `size` bytes off the front of `data`.
+///
+/// This is a thin wrapper around `slice::split_at()` but it:
+/// 1. Returns a `DescriptorError` rather than panicking if `data` is too small.
+/// 2. Accepts a variety of `size` types since descriptors commonly use `u32` or `u64`.
+///
+/// # Arguments
+/// * `data`: descriptor data.
+/// * `size`: the number of bytes to pull off the front.
+///
+/// # Returns
+/// A tuple containing (extracted_bytes, data_remainder) on success, or
+/// `DescriptorError` if we couldn't get `size` bytes out of `data`.
+pub(super) fn split_slice<T>(data: &[u8], size: T) -> DescriptorResult<(&[u8], &[u8])>
+where
+ T: TryInto<usize>,
+{
+ let size = size.try_into().map_err(|_| DescriptorError::InvalidValue)?;
+ if size > data.len() {
+ Err(DescriptorError::InvalidSize)
+ } else {
+ Ok(data.split_at(size))
+ }
+}
+
+/// Function type for the `avb_*descriptor_validate_and_byteswap()` C functions.
+pub(super) type ValidationFunc<T> = unsafe extern "C" fn(*const T, *mut T) -> bool;
+
+/// Trait to represent an `Avb*Descriptor` type which has an associated `ValidationFunc`.
+///
+/// This allows the generic `parse_descriptor()` function to extract a descriptor header for any
+/// descriptor type.
+///
+/// To use, implement `ValidateAndByteSwap` on the `Avb*Descriptor` struct.
+///
+/// # Safety
+/// The function assigned to `VALIDATE_AND_BYTESWAP_FUNC` must be the libavb
+/// `avb_*descriptor_validate_and_byteswap()` function corresponding to the descriptor implementing
+/// this trait (e.g. `AvbHashDescriptor` -> `avb_hash_descriptor_validate_and_byteswap`).
+pub(super) unsafe trait ValidateAndByteswap {
+ /// The specific libavb validation function for this descriptor type.
+ const VALIDATE_AND_BYTESWAP_FUNC: ValidationFunc<Self>;
+}
+
+/// A descriptor that has been extracted, validated, and byteswapped.
+#[derive(Debug)]
+pub(super) struct ParsedDescriptor<'a, T> {
+ /// The original raw (big-endian) header.
+ pub raw_header: &'a T,
+ /// A copy of the header in host-endian format.
+ pub header: T,
+ /// The descriptor body contents.
+ pub body: &'a [u8],
+}
+
+/// Extracts the descriptor header from the given buffer.
+///
+/// # Arguments
+/// `data`: the descriptor contents in raw (big-endian) format.
+///
+/// # Returns
+/// A `ParsedDescriptor` on success, `DescriptorError` if `data` was too small or the header looks
+/// invalid.
+pub(super) fn parse_descriptor<T>(data: &[u8]) -> DescriptorResult<ParsedDescriptor<T>>
+where
+ T: Default + FromZeroes + FromBytes + ValidateAndByteswap,
+{
+ let (raw_header, body) =
+ Ref::<_, T>::new_from_prefix(data).ok_or(DescriptorError::InvalidHeader)?;
+ let raw_header = raw_header.into_ref();
+
+ let mut header = T::default();
+ // SAFETY:
+ // * all `VALIDATE_AND_BYTESWAP_FUNC` functions check the validity of the fields.
+ // * even if the data is corrupted somehow and is not detected by the validation logic, these
+ // functions never try to access memory outside of the header.
+ if !unsafe { T::VALIDATE_AND_BYTESWAP_FUNC(raw_header, &mut header) } {
+ return Err(DescriptorError::InvalidHeader);
+ }
+
+ Ok(ParsedDescriptor {
+ raw_header,
+ header,
+ body,
+ })
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use avb_bindgen::{avb_descriptor_validate_and_byteswap, AvbDescriptor, AvbHashDescriptor};
+ use std::mem::size_of;
+
+ #[test]
+ fn split_slice_with_various_size_types_succeeds() {
+ let data = &[1, 2, 3, 4];
+ let expected = Ok((&data[..2], &data[2..]));
+ assert_eq!(split_slice(data, 2u32), expected);
+ assert_eq!(split_slice(data, 2u64), expected);
+ assert_eq!(split_slice(data, 2usize), expected);
+ }
+
+ #[test]
+ fn split_slice_with_negative_size_fails() {
+ let data = &[1, 2, 3, 4];
+ assert_eq!(split_slice(data, -1i32), Err(DescriptorError::InvalidValue));
+ }
+
+ #[test]
+ fn split_slice_with_size_overflow_fails() {
+ let data = &[1, 2, 3, 4];
+ assert_eq!(split_slice(data, 5u32), Err(DescriptorError::InvalidSize));
+ }
+
+ // Enable `parse_descriptor()` on a generic `AvbDescriptor` of any sub-type.
+ // SAFETY: `VALIDATE_AND_BYTESWAP_FUNC` is the correct libavb validator for this descriptor.
+ unsafe impl ValidateAndByteswap for AvbDescriptor {
+ const VALIDATE_AND_BYTESWAP_FUNC: ValidationFunc<Self> =
+ avb_descriptor_validate_and_byteswap;
+ }
+
+ // Hardcoded test descriptor of custom sub-type (tag = 42).
+ const TEST_DESCRIPTOR: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, // tag = 0x42u64 (BE)
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, // num_bytes_following = 8u64 (BE)
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // fake contents
+ ];
+
+ #[test]
+ fn parse_descriptor_success() {
+ let descriptor = parse_descriptor::<AvbDescriptor>(TEST_DESCRIPTOR).unwrap();
+
+ // `assert_eq!()` cannot be used on `repr(packed)` struct fields.
+ assert!(descriptor.raw_header.tag == 0x42u64.to_be());
+ assert!(descriptor.raw_header.num_bytes_following == 8u64.to_be());
+ assert!(descriptor.header.tag == 0x42);
+ assert!(descriptor.header.num_bytes_following == 8);
+ assert_eq!(descriptor.body, &[1, 2, 3, 4, 5, 6, 7, 8]);
+ }
+
+ #[test]
+ fn parse_descriptor_buffer_too_short_failure() {
+ let bad_length = size_of::<AvbDescriptor>() - 1;
+ assert_eq!(
+ parse_descriptor::<AvbDescriptor>(&TEST_DESCRIPTOR[..bad_length]).unwrap_err(),
+ DescriptorError::InvalidHeader
+ );
+ }
+
+ #[test]
+ fn parse_descriptor_wrong_type_failure() {
+ assert_eq!(
+ // `TEST_DESCRIPTOR` is not a valid `AvbHashDescriptor`, this should fail without
+ // triggering any undefined behavior.
+ parse_descriptor::<AvbHashDescriptor>(TEST_DESCRIPTOR).unwrap_err(),
+ DescriptorError::InvalidHeader
+ );
+ }
+}
diff --git a/rust/src/error.rs b/rust/src/error.rs
index b5d781e..f60702b 100644
--- a/rust/src/error.rs
+++ b/rust/src/error.rs
@@ -60,6 +60,15 @@ pub enum SlotVerifyError<'a> {
Internal,
}
+/// `Result` type for `SlotVerifyError` errors.
+pub type SlotVerifyResult<'a, T> = Result<T, SlotVerifyError<'a>>;
+
+/// `Result` type for `SlotVerifyError` errors without any `SlotVerifyData`.
+///
+/// If the contained error will never hold a `SlotVerifyData`, this is easier to work with compared
+/// to `SlotVerifyResult` due to the static lifetime bound.
+pub type SlotVerifyNoDataResult<T> = SlotVerifyResult<'static, T>;
+
impl<'a> SlotVerifyError<'a> {
/// Returns a copy of this error without any contained `SlotVerifyData`.
///
@@ -96,7 +105,7 @@ impl<'a> fmt::Display for SlotVerifyError<'a> {
}
}
-/// Converts a bindgen `AvbSlotVerifyResult` enum to a `Result<>`, mapping
+/// Converts a bindgen `AvbSlotVerifyResult` enum to a `SlotVerifyNoDataResult<>`, mapping
/// `AVB_SLOT_VERIFY_RESULT_OK` to the Rust equivalent `Ok(())` and errors to the corresponding
/// `Err(SlotVerifyError)`.
///
@@ -106,12 +115,9 @@ impl<'a> fmt::Display for SlotVerifyError<'a> {
/// 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(
+pub(crate) fn slot_verify_enum_to_result(
result: AvbSlotVerifyResult,
-) -> Result<(), SlotVerifyError<'static>> {
+) -> SlotVerifyNoDataResult<()> {
match result {
AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_OK => Ok(()),
AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_ARGUMENT => {
@@ -159,6 +165,9 @@ pub enum IoError {
NotImplemented,
}
+/// `Result` type for `IoError` errors.
+pub type IoResult<T> = Result<T, IoError>;
+
impl fmt::Display for IoError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
@@ -180,28 +189,6 @@ impl From<Utf8Error> for IoError {
}
}
-// Converts a bindgen `AvbIOResult` enum to a `Result<>`, mapping `AVB_IO_RESULT_OK` to the Rust
-// equivalent `Ok(())` and errors to the corresponding `Err(IoError)`.
-//
-// 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.
-#[allow(dead_code)]
-pub(crate) fn io_enum_to_result(result: AvbIOResult) -> Result<(), IoError> {
- match result {
- AvbIOResult::AVB_IO_RESULT_OK => Ok(()),
- AvbIOResult::AVB_IO_RESULT_ERROR_OOM => Err(IoError::Oom),
- AvbIOResult::AVB_IO_RESULT_ERROR_IO => Err(IoError::Io),
- AvbIOResult::AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION => Err(IoError::NoSuchPartition),
- AvbIOResult::AVB_IO_RESULT_ERROR_RANGE_OUTSIDE_PARTITION => {
- Err(IoError::RangeOutsidePartition)
- }
- AvbIOResult::AVB_IO_RESULT_ERROR_NO_SUCH_VALUE => Err(IoError::NoSuchValue),
- AvbIOResult::AVB_IO_RESULT_ERROR_INVALID_VALUE_SIZE => Err(IoError::InvalidValueSize),
- AvbIOResult::AVB_IO_RESULT_ERROR_INSUFFICIENT_SPACE => Err(IoError::InsufficientSpace(0)),
- }
-}
-
// Converts our `IoError` to the bindgen `AvbIOResult` enum.
//
// Unlike `SlotVerifyError` which gets generated by libavb and passed to the caller, `IoError` is
@@ -226,11 +213,8 @@ impl From<IoError> for AvbIOResult {
}
}
-// Converts a `Result<>` to the bindgen `AvbIOResult` enum.
-//
-// TODO(b/290110273): this can be limited to pub(crate) once we've moved the full libavb wrapper
-// here.
-pub fn result_to_io_enum(result: Result<(), IoError>) -> AvbIOResult {
+/// Converts an `IoResult<>` to the bindgen `AvbIOResult` enum.
+pub(crate) fn result_to_io_enum(result: IoResult<()>) -> AvbIOResult {
result.map_or_else(|e| e.into(), |_| AvbIOResult::AVB_IO_RESULT_OK)
}
@@ -249,6 +233,9 @@ pub enum VbmetaVerifyError {
SignatureMismatch,
}
+/// `Result` type for `VbmetaVerifyError` errors.
+pub type VbmetaVerifyResult<T> = Result<T, VbmetaVerifyError>;
+
impl fmt::Display for VbmetaVerifyError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
@@ -261,16 +248,14 @@ impl fmt::Display for VbmetaVerifyError {
}
}
-// Converts a bindgen `AvbVBMetaVerifyResult` enum to a `Result<>`, mapping
+// Converts a bindgen `AvbVBMetaVerifyResult` enum to a `VbmetaVerifyResult<>`, 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> {
+pub fn vbmeta_verify_enum_to_result(result: AvbVBMetaVerifyResult) -> VbmetaVerifyResult<()> {
match result {
AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_OK => Ok(()),
AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_OK_NOT_SIGNED => {
@@ -296,7 +281,7 @@ mod tests {
use super::*;
#[test]
- fn test_SlotVerifyError_display() {
+ fn display_slot_verify_error() {
// 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!(
@@ -306,7 +291,7 @@ mod tests {
}
#[test]
- fn test_SlotVerifyError_from_raw() {
+ fn convert_slot_verify_enum_to_result() {
assert!(matches!(
slot_verify_enum_to_result(AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_OK),
Ok(())
@@ -318,7 +303,7 @@ mod tests {
}
#[test]
- fn test_IoError_display() {
+ fn display_io_error() {
// 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!(
@@ -328,16 +313,29 @@ mod tests {
}
#[test]
- fn test_IoError_from_raw() {
- assert_eq!(io_enum_to_result(AvbIOResult::AVB_IO_RESULT_OK), Ok(()));
- assert_eq!(
- io_enum_to_result(AvbIOResult::AVB_IO_RESULT_ERROR_IO),
- Err(IoError::Io)
- );
+ fn convert_io_enum_to_result() {
+ // This is a compile-time check that we handle all the `AvbIOResult` enum values. If any
+ // enums are added or removed this will break, indicating we need to update `IoError` to
+ // match.
+ assert!(match AvbIOResult::AVB_IO_RESULT_OK {
+ AvbIOResult::AVB_IO_RESULT_OK => Ok(()),
+ AvbIOResult::AVB_IO_RESULT_ERROR_OOM => Err(IoError::Oom),
+ AvbIOResult::AVB_IO_RESULT_ERROR_IO => Err(IoError::Io),
+ AvbIOResult::AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION => Err(IoError::NoSuchPartition),
+ AvbIOResult::AVB_IO_RESULT_ERROR_RANGE_OUTSIDE_PARTITION => {
+ Err(IoError::RangeOutsidePartition)
+ }
+ AvbIOResult::AVB_IO_RESULT_ERROR_NO_SUCH_VALUE => Err(IoError::NoSuchValue),
+ AvbIOResult::AVB_IO_RESULT_ERROR_INVALID_VALUE_SIZE => Err(IoError::InvalidValueSize),
+ AvbIOResult::AVB_IO_RESULT_ERROR_INSUFFICIENT_SPACE => {
+ Err(IoError::InsufficientSpace(0))
+ }
+ }
+ .is_ok());
}
#[test]
- fn test_IoError_to_raw() {
+ fn convert_io_result_to_enum() {
assert_eq!(result_to_io_enum(Ok(())), AvbIOResult::AVB_IO_RESULT_OK);
assert_eq!(
result_to_io_enum(Err(IoError::Io)),
@@ -346,7 +344,7 @@ mod tests {
}
#[test]
- fn test_VbmetaVerifyError_display() {
+ fn display_vmbeta_verify_error() {
// 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!(
@@ -356,7 +354,7 @@ mod tests {
}
#[test]
- fn test_VbmetaVerifyError_from_raw() {
+ fn convert_vbmeta_verify_enum_to_result() {
assert_eq!(
vbmeta_verify_enum_to_result(AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_OK),
Ok(())
diff --git a/rust/src/lib.rs b/rust/src/lib.rs
index c580497..c96d4e5 100644
--- a/rust/src/lib.rs
+++ b/rust/src/lib.rs
@@ -26,22 +26,22 @@
// panic_handler and eh_personality conditional on actually building a dylib.
#![cfg_attr(not(any(test, android_dylib)), no_std)]
+mod descriptor;
mod error;
mod ops;
mod verify;
-pub use error::{IoError, SlotVerifyError};
+pub use descriptor::{
+ ChainPartitionDescriptor, ChainPartitionDescriptorFlags, Descriptor, DescriptorError,
+ DescriptorResult, HashDescriptor, HashDescriptorFlags, HashtreeDescriptor,
+ HashtreeDescriptorFlags, KernelCommandlineDescriptor, KernelCommandlineDescriptorFlags,
+ PropertyDescriptor,
+};
+pub use error::{
+ IoError, IoResult, SlotVerifyError, SlotVerifyNoDataResult, SlotVerifyResult,
+ VbmetaVerifyError, VbmetaVerifyResult,
+};
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.
-//
-// TODO(b/290110273): remove this module once we've moved the full libavb wrapper here.
-pub mod internal {
- use super::*;
-
- pub use error::{result_to_io_enum, slot_verify_enum_to_result};
-}
diff --git a/rust/src/ops.rs b/rust/src/ops.rs
index 5846b17..e030dda 100644
--- a/rust/src/ops.rs
+++ b/rust/src/ops.rs
@@ -19,7 +19,7 @@
extern crate alloc;
-use crate::{error::result_to_io_enum, IoError};
+use crate::{error::result_to_io_enum, IoError, IoResult};
use avb_bindgen::{AvbIOResult, AvbOps};
use core::{
cmp::min,
@@ -30,9 +30,6 @@ use core::{
#[cfg(feature = "uuid")]
use uuid::Uuid;
-/// Common `Result` type for `IoError` errors.
-type Result<T> = core::result::Result<T, IoError>;
-
/// Base implementation-provided callbacks for verification.
///
/// See libavb `AvbOps` for more complete documentation.
@@ -54,7 +51,7 @@ pub trait Ops {
partition: &CStr,
offset: i64,
buffer: &mut [u8],
- ) -> Result<usize>;
+ ) -> IoResult<usize>;
/// Returns a reference to preloaded partition contents.
///
@@ -71,7 +68,7 @@ pub trait Ops {
/// * `Err<IoError::NotImplemented>` if the requested partition has not been preloaded;
/// verification will next attempt to load the partition via `read_from_partition()`.
/// * Any other `Err<IoError>` if an error occurred; verification will exit immediately.
- fn get_preloaded_partition(&mut self, partition: &CStr) -> Result<&[u8]> {
+ fn get_preloaded_partition(&mut self, _partition: &CStr) -> IoResult<&[u8]> {
Err(IoError::NotImplemented)
}
@@ -88,7 +85,7 @@ pub trait Ops {
&mut self,
public_key: &[u8],
public_key_metadata: Option<&[u8]>,
- ) -> Result<bool>;
+ ) -> IoResult<bool>;
/// Reads the rollback index at the given location.
///
@@ -97,7 +94,7 @@ pub trait Ops {
///
/// # Returns
/// The rollback index at this location or `IoError` on error.
- fn read_rollback_index(&mut self, rollback_index_location: usize) -> Result<u64>;
+ fn read_rollback_index(&mut self, rollback_index_location: usize) -> IoResult<u64>;
/// Writes the rollback index at the given location.
///
@@ -113,13 +110,13 @@ pub trait Ops {
///
/// # Returns
/// Unit on success or `IoError` on error.
- fn write_rollback_index(&mut self, rollback_index_location: usize, index: u64) -> Result<()>;
+ fn write_rollback_index(&mut self, rollback_index_location: usize, index: u64) -> IoResult<()>;
/// Returns the device unlock state.
///
/// # Returns
/// True if the device is unlocked, false if locked, `IoError` on error.
- fn read_is_device_unlocked(&mut self) -> Result<bool>;
+ fn read_is_device_unlocked(&mut self) -> IoResult<bool>;
/// Returns the GUID of the requested partition.
///
@@ -134,7 +131,7 @@ pub trait Ops {
/// # Returns
/// The partition GUID or `IoError` on error.
#[cfg(feature = "uuid")]
- fn get_unique_guid_for_partition(&mut self, partition: &CStr) -> Result<Uuid>;
+ fn get_unique_guid_for_partition(&mut self, partition: &CStr) -> IoResult<Uuid>;
/// Returns the size of the requested partition.
///
@@ -143,7 +140,7 @@ pub trait Ops {
///
/// # Returns
/// The partition size in bytes or `IoError` on error.
- fn get_size_of_partition(&mut self, partition: &CStr) -> Result<u64>;
+ fn get_size_of_partition(&mut self, partition: &CStr) -> IoResult<u64>;
/// Reads the requested persistent value.
///
@@ -163,7 +160,7 @@ pub trait Ops {
/// * `IoError::NoSuchValue` if `name` is not a known persistent value.
/// * `IoError::InsufficientSpace` with the required size if the `value` buffer is too small.
/// * Any other `IoError` on failure.
- fn read_persistent_value(&mut self, name: &CStr, value: &mut [u8]) -> Result<usize>;
+ fn read_persistent_value(&mut self, name: &CStr, value: &mut [u8]) -> IoResult<usize>;
/// Writes the requested persistent value.
///
@@ -180,7 +177,7 @@ pub trait Ops {
/// * `IoError::NoSuchValue` if `name` is not a supported persistent value.
/// * `IoError::InvalidValueSize` if `value` is too large to save as a persistent value.
/// * Any other `IoError` on failure.
- fn write_persistent_value(&mut self, name: &CStr, value: &[u8]) -> Result<()>;
+ fn write_persistent_value(&mut self, name: &CStr, value: &[u8]) -> IoResult<()>;
/// Erases the requested persistent value.
///
@@ -198,7 +195,7 @@ pub trait Ops {
/// * Unit on success.
/// * `IoError::NoSuchValue` if `name` is not a supported persistent value.
/// * Any other `IoError` on failure.
- fn erase_persistent_value(&mut self, name: &CStr) -> Result<()>;
+ fn erase_persistent_value(&mut self, name: &CStr) -> IoResult<()>;
/// Checks if the given public key is valid for the given partition.
///
@@ -223,7 +220,7 @@ pub trait Ops {
partition: &CStr,
public_key: &[u8],
public_key_metadata: Option<&[u8]>,
- ) -> Result<PublicKeyForPartitionInfo>;
+ ) -> IoResult<PublicKeyForPartitionInfo>;
}
/// Info returned from `validare_public_key_for_partition()`.
@@ -348,7 +345,7 @@ impl<'a> AsMut<AvbOps> for ScopedAvbOps<'a> {
///
/// In practice, these conditions are met since we call this exactly once in each callback
/// to extract the `Ops`, and drop it at callback completion.
-unsafe fn as_ops<'a>(avb_ops: *mut AvbOps) -> Result<&'a mut dyn Ops> {
+unsafe fn as_ops<'a>(avb_ops: *mut AvbOps) -> IoResult<&'a mut dyn Ops> {
// SAFETY: we created this AvbOps object and passed it to libavb so we know it meets all
// the criteria for `as_mut()`.
let avb_ops = unsafe { avb_ops.as_mut() }.ok_or(IoError::Io)?;
@@ -360,14 +357,14 @@ unsafe fn as_ops<'a>(avb_ops: *mut AvbOps) -> Result<&'a mut dyn Ops> {
}
/// Converts a non-NULL `ptr` to `()`, NULL to `Err(IoError::Io)`.
-fn check_nonnull<T>(ptr: *const T) -> Result<()> {
+fn check_nonnull<T>(ptr: *const T) -> IoResult<()> {
match ptr.is_null() {
true => Err(IoError::Io),
false => Ok(()),
}
}
-/// Wraps a callback to convert the given `Result<>` to raw `AvbIOResult` for libavb.
+/// Wraps a callback to convert the given `IoResult<>` to raw `AvbIOResult` for libavb.
///
/// See corresponding `try_*` function docs.
unsafe extern "C" fn read_from_partition(
@@ -378,14 +375,17 @@ unsafe extern "C" fn read_from_partition(
buffer: *mut c_void,
out_num_read: *mut usize,
) -> AvbIOResult {
- result_to_io_enum(try_read_from_partition(
- ops,
- partition,
- offset,
- num_bytes,
- buffer,
- out_num_read,
- ))
+ // SAFETY: see corresponding `try_*` function safety documentation.
+ unsafe {
+ result_to_io_enum(try_read_from_partition(
+ ops,
+ partition,
+ offset,
+ num_bytes,
+ buffer,
+ out_num_read,
+ ))
+ }
}
/// Bounces the C callback into the user-provided Rust implementation.
@@ -402,7 +402,7 @@ unsafe fn try_read_from_partition(
num_bytes: usize,
buffer: *mut c_void,
out_num_read: *mut usize,
-) -> Result<()> {
+) -> IoResult<()> {
check_nonnull(partition)?;
check_nonnull(buffer)?;
check_nonnull(out_num_read)?;
@@ -438,7 +438,7 @@ unsafe fn try_read_from_partition(
Ok(())
}
-/// Wraps a callback to convert the given `Result<>` to raw `AvbIOResult` for libavb.
+/// Wraps a callback to convert the given `IoResult<>` to raw `AvbIOResult` for libavb.
///
/// See corresponding `try_*` function docs.
unsafe extern "C" fn get_preloaded_partition(
@@ -448,13 +448,16 @@ unsafe extern "C" fn get_preloaded_partition(
out_pointer: *mut *mut u8,
out_num_bytes_preloaded: *mut usize,
) -> AvbIOResult {
- result_to_io_enum(try_get_preloaded_partition(
- ops,
- partition,
- num_bytes,
- out_pointer,
- out_num_bytes_preloaded,
- ))
+ // SAFETY: see corresponding `try_*` function safety documentation.
+ unsafe {
+ result_to_io_enum(try_get_preloaded_partition(
+ ops,
+ partition,
+ num_bytes,
+ out_pointer,
+ out_num_bytes_preloaded,
+ ))
+ }
}
/// Bounces the C callback into the user-provided Rust implementation.
@@ -471,7 +474,7 @@ unsafe fn try_get_preloaded_partition(
num_bytes: usize,
out_pointer: *mut *mut u8,
out_num_bytes_preloaded: *mut usize,
-) -> Result<()> {
+) -> IoResult<()> {
check_nonnull(partition)?;
check_nonnull(out_pointer)?;
check_nonnull(out_num_bytes_preloaded)?;
@@ -522,7 +525,7 @@ unsafe fn try_get_preloaded_partition(
Ok(())
}
-/// Wraps a callback to convert the given `Result<>` to raw `AvbIOResult` for libavb.
+/// Wraps a callback to convert the given `IoResult<>` to raw `AvbIOResult` for libavb.
///
/// See corresponding `try_*` function docs.
unsafe extern "C" fn validate_vbmeta_public_key(
@@ -533,14 +536,17 @@ unsafe extern "C" fn validate_vbmeta_public_key(
public_key_metadata_length: usize,
out_is_trusted: *mut bool,
) -> AvbIOResult {
- result_to_io_enum(try_validate_vbmeta_public_key(
- ops,
- public_key_data,
- public_key_length,
- public_key_metadata,
- public_key_metadata_length,
- out_is_trusted,
- ))
+ // SAFETY: see corresponding `try_*` function safety documentation.
+ unsafe {
+ result_to_io_enum(try_validate_vbmeta_public_key(
+ ops,
+ public_key_data,
+ public_key_length,
+ public_key_metadata,
+ public_key_metadata_length,
+ out_is_trusted,
+ ))
+ }
}
/// Bounces the C callback into the user-provided Rust implementation.
@@ -556,7 +562,7 @@ unsafe fn try_validate_vbmeta_public_key(
public_key_metadata: *const u8,
public_key_metadata_length: usize,
out_is_trusted: *mut bool,
-) -> Result<()> {
+) -> IoResult<()> {
check_nonnull(public_key_data)?;
check_nonnull(out_is_trusted)?;
@@ -595,7 +601,7 @@ unsafe fn try_validate_vbmeta_public_key(
Ok(())
}
-/// Wraps a callback to convert the given `Result<>` to raw `AvbIOResult` for libavb.
+/// Wraps a callback to convert the given `IoResult<>` to raw `AvbIOResult` for libavb.
///
/// See corresponding `try_*` function docs.
unsafe extern "C" fn read_rollback_index(
@@ -603,11 +609,14 @@ unsafe extern "C" fn read_rollback_index(
rollback_index_location: usize,
out_rollback_index: *mut u64,
) -> AvbIOResult {
- result_to_io_enum(try_read_rollback_index(
- ops,
- rollback_index_location,
- out_rollback_index,
- ))
+ // SAFETY: see corresponding `try_*` function safety documentation.
+ unsafe {
+ result_to_io_enum(try_read_rollback_index(
+ ops,
+ rollback_index_location,
+ out_rollback_index,
+ ))
+ }
}
/// Bounces the C callback into the user-provided Rust implementation.
@@ -619,7 +628,7 @@ unsafe fn try_read_rollback_index(
ops: *mut AvbOps,
rollback_index_location: usize,
out_rollback_index: *mut u64,
-) -> Result<()> {
+) -> IoResult<()> {
check_nonnull(out_rollback_index)?;
// Initialize the output variables first in case something fails.
@@ -641,7 +650,7 @@ unsafe fn try_read_rollback_index(
Ok(())
}
-/// Wraps a callback to convert the given `Result<>` to raw `AvbIOResult` for libavb.
+/// Wraps a callback to convert the given `IoResult<>` to raw `AvbIOResult` for libavb.
///
/// See corresponding `try_*` function docs.
unsafe extern "C" fn write_rollback_index(
@@ -649,11 +658,14 @@ unsafe extern "C" fn write_rollback_index(
rollback_index_location: usize,
rollback_index: u64,
) -> AvbIOResult {
- result_to_io_enum(try_write_rollback_index(
- ops,
- rollback_index_location,
- rollback_index,
- ))
+ // SAFETY: see corresponding `try_*` function safety documentation.
+ unsafe {
+ result_to_io_enum(try_write_rollback_index(
+ ops,
+ rollback_index_location,
+ rollback_index,
+ ))
+ }
}
/// Bounces the C callback into the user-provided Rust implementation.
@@ -664,7 +676,7 @@ unsafe fn try_write_rollback_index(
ops: *mut AvbOps,
rollback_index_location: usize,
rollback_index: u64,
-) -> Result<()> {
+) -> IoResult<()> {
// SAFETY:
// * we only use `ops` objects created via `ScopedAvbOps` as required.
// * `ops` is only extracted once and is dropped at the end of the callback.
@@ -672,14 +684,15 @@ unsafe fn try_write_rollback_index(
ops.write_rollback_index(rollback_index_location, rollback_index)
}
-/// Wraps a callback to convert the given `Result<>` to raw `AvbIOResult` for libavb.
+/// Wraps a callback to convert the given `IoResult<>` to raw `AvbIOResult` for libavb.
///
/// See corresponding `try_*` function docs.
unsafe extern "C" fn read_is_device_unlocked(
ops: *mut AvbOps,
out_is_unlocked: *mut bool,
) -> AvbIOResult {
- result_to_io_enum(try_read_is_device_unlocked(ops, out_is_unlocked))
+ // SAFETY: see corresponding `try_*` function safety documentation.
+ unsafe { result_to_io_enum(try_read_is_device_unlocked(ops, out_is_unlocked)) }
}
/// Bounces the C callback into the user-provided Rust implementation.
@@ -687,7 +700,10 @@ unsafe extern "C" fn read_is_device_unlocked(
/// # Safety
/// * `ops` must have been created via `ScopedAvbOps`.
/// * `out_is_unlocked` must adhere to the requirements of `ptr::write()`.
-unsafe fn try_read_is_device_unlocked(ops: *mut AvbOps, out_is_unlocked: *mut bool) -> Result<()> {
+unsafe fn try_read_is_device_unlocked(
+ ops: *mut AvbOps,
+ out_is_unlocked: *mut bool,
+) -> IoResult<()> {
check_nonnull(out_is_unlocked)?;
// Initialize the output variables first in case something fails.
@@ -709,7 +725,7 @@ unsafe fn try_read_is_device_unlocked(ops: *mut AvbOps, out_is_unlocked: *mut bo
Ok(())
}
-/// Wraps a callback to convert the given `Result<>` to raw `AvbIOResult` for libavb.
+/// Wraps a callback to convert the given `IoResult<>` to raw `AvbIOResult` for libavb.
///
/// See corresponding `try_*` function docs.
unsafe extern "C" fn get_unique_guid_for_partition(
@@ -718,43 +734,53 @@ unsafe extern "C" fn get_unique_guid_for_partition(
guid_buf: *mut c_char,
guid_buf_size: usize,
) -> AvbIOResult {
- result_to_io_enum(try_get_unique_guid_for_partition(
- ops,
- partition,
- guid_buf,
- guid_buf_size,
- ))
+ // SAFETY: see corresponding `try_*` function safety documentation.
+ unsafe {
+ result_to_io_enum(try_get_unique_guid_for_partition(
+ ops,
+ partition,
+ guid_buf,
+ guid_buf_size,
+ ))
+ }
}
/// Bounces the C callback into the user-provided Rust implementation.
///
+/// When the `uuid` feature is not enabled, this doesn't call into the user ops at all and instead
+/// gives the empty string for all partitions.
+///
/// # Safety
/// * `ops` must have been created via `ScopedAvbOps`.
/// * `partition` must adhere to the requirements of `CStr::from_ptr()`.
/// * `guid_buf` must adhere to the requirements of `slice::from_raw_parts_mut()`.
unsafe fn try_get_unique_guid_for_partition(
- ops: *mut AvbOps,
- partition: *const c_char,
+ #[allow(unused_variables)] ops: *mut AvbOps,
+ #[allow(unused_variables)] partition: *const c_char,
guid_buf: *mut c_char,
guid_buf_size: usize,
-) -> Result<()> {
- check_nonnull(partition)?;
+) -> IoResult<()> {
check_nonnull(guid_buf)?;
- // SAFETY:
- // * we've checked that the pointer is non-NULL.
- // * libavb gives us a properly-allocated and nul-terminated `partition`.
- // * the string contents are not modified while the returned `&CStr` exists.
- // * the returned `&CStr` is not held past the scope of this callback.
- let partition = unsafe { CStr::from_ptr(partition) };
+ // On some architectures `c_char` is `u8`, and on others `i8`. We make sure it's `u8` here
+ // since that's what `CStr::to_bytes_with_nul()` always provides.
+ #[allow(clippy::unnecessary_cast)]
+ let guid_buf = guid_buf as *mut u8;
+
// SAFETY:
// * we've checked that the pointer is non-NULL.
// * libavb gives us a properly-allocated `guid_buf` with size `guid_buf_size`.
// * we only access the contents via the returned slice.
// * the returned slice is not held past the scope of this callback.
- let buffer = unsafe { slice::from_raw_parts_mut(guid_buf as *mut u8, guid_buf_size) };
+ let buffer = unsafe { slice::from_raw_parts_mut(guid_buf, guid_buf_size) };
// Initialize the output buffer to the empty string.
+ //
+ // When the `uuid` feature is not selected, the user doesn't need commandline GUIDs but libavb
+ // may still attempt to inject the `vmbeta` or `boot` partition GUIDs into the commandline,
+ // depending on the verification settings. In order to satisfy libavb's requirements we must:
+ // * write a nul-terminated string to avoid undefined behavior (empty string is sufficient)
+ // * return `Ok(())` or verification will fail
if buffer.is_empty() {
return Err(IoError::Oom);
}
@@ -762,6 +788,15 @@ unsafe fn try_get_unique_guid_for_partition(
#[cfg(feature = "uuid")]
{
+ check_nonnull(partition)?;
+
+ // SAFETY:
+ // * we've checked that the pointer is non-NULL.
+ // * libavb gives us a properly-allocated and nul-terminated `partition`.
+ // * the string contents are not modified while the returned `&CStr` exists.
+ // * the returned `&CStr` is not held past the scope of this callback.
+ let partition = unsafe { CStr::from_ptr(partition) };
+
// SAFETY:
// * we only use `ops` objects created via `ScopedAvbOps` as required.
// * `ops` is only extracted once and is dropped at the end of the callback.
@@ -772,7 +807,7 @@ unsafe fn try_get_unique_guid_for_partition(
// `CString` to apply nul-termination.
// This does allocate memory, but it's short-lived and discarded as soon as we copy the
// properly-terminated string back to the buffer.
- let mut encode_buffer = uuid::Uuid::encode_buffer();
+ let mut encode_buffer = Uuid::encode_buffer();
let guid_str = guid.as_hyphenated().encode_lower(&mut encode_buffer);
let guid_cstring = alloc::ffi::CString::new(guid_str.as_bytes()).or(Err(IoError::Io))?;
let guid_bytes = guid_cstring.to_bytes_with_nul();
@@ -783,28 +818,12 @@ unsafe fn try_get_unique_guid_for_partition(
return Err(IoError::Oom);
}
buffer[..guid_bytes.len()].copy_from_slice(guid_bytes);
- Ok(())
}
- #[cfg(not(feature = "uuid"))]
- {
- // The user doesn't need this feature, but libavb may still attempt to inject the vbmeta
- // partition GUID into the commandline, depending on the verification flags. In this case
- // the function needs to return success or verification will fail, but leaving the buffer
- // as the empty string is sufficient since nobody will be reading the value.
- //
- // We restrict this to only "vbmeta*" partitions because if we return success for all
- // partitions, libavb will also try to inject a "system" partition GUID into the
- // commandline, which the user doesn't need.
- partition
- .to_bytes()
- .starts_with(b"vbmeta")
- .then_some(())
- .ok_or(IoError::NoSuchPartition)
- }
+ Ok(())
}
-/// Wraps a callback to convert the given `Result<>` to raw `AvbIOResult` for libavb.
+/// Wraps a callback to convert the given `IoResult<>` to raw `AvbIOResult` for libavb.
///
/// See corresponding `try_*` function docs.
unsafe extern "C" fn get_size_of_partition(
@@ -812,11 +831,14 @@ unsafe extern "C" fn get_size_of_partition(
partition: *const c_char,
out_size_num_bytes: *mut u64,
) -> AvbIOResult {
- result_to_io_enum(try_get_size_of_partition(
- ops,
- partition,
- out_size_num_bytes,
- ))
+ // SAFETY: see corresponding `try_*` function safety documentation.
+ unsafe {
+ result_to_io_enum(try_get_size_of_partition(
+ ops,
+ partition,
+ out_size_num_bytes,
+ ))
+ }
}
/// Bounces the C callback into the user-provided Rust implementation.
@@ -829,7 +851,7 @@ unsafe fn try_get_size_of_partition(
ops: *mut AvbOps,
partition: *const c_char,
out_size_num_bytes: *mut u64,
-) -> Result<()> {
+) -> IoResult<()> {
check_nonnull(partition)?;
check_nonnull(out_size_num_bytes)?;
@@ -858,7 +880,7 @@ unsafe fn try_get_size_of_partition(
Ok(())
}
-/// Wraps a callback to convert the given `Result<>` to raw `AvbIOResult` for libavb.
+/// Wraps a callback to convert the given `IoResult<>` to raw `AvbIOResult` for libavb.
///
/// See corresponding `try_*` function docs.
unsafe extern "C" fn read_persistent_value(
@@ -868,13 +890,16 @@ unsafe extern "C" fn read_persistent_value(
out_buffer: *mut u8,
out_num_bytes_read: *mut usize,
) -> AvbIOResult {
- result_to_io_enum(try_read_persistent_value(
- ops,
- name,
- buffer_size,
- out_buffer,
- out_num_bytes_read,
- ))
+ // SAFETY: see corresponding `try_*` function safety documentation.
+ unsafe {
+ result_to_io_enum(try_read_persistent_value(
+ ops,
+ name,
+ buffer_size,
+ out_buffer,
+ out_num_bytes_read,
+ ))
+ }
}
/// Bounces the C callback into the user-provided Rust implementation.
@@ -890,7 +915,7 @@ unsafe fn try_read_persistent_value(
buffer_size: usize,
out_buffer: *mut u8,
out_num_bytes_read: *mut usize,
-) -> Result<()> {
+) -> IoResult<()> {
check_nonnull(name)?;
check_nonnull(out_num_bytes_read)?;
@@ -936,7 +961,7 @@ unsafe fn try_read_persistent_value(
result.map(|_| ())
}
-/// Wraps a callback to convert the given `Result<>` to raw `AvbIOResult` for libavb.
+/// Wraps a callback to convert the given `IoResult<>` to raw `AvbIOResult` for libavb.
///
/// See corresponding `try_*` function docs.
unsafe extern "C" fn write_persistent_value(
@@ -945,7 +970,8 @@ unsafe extern "C" fn write_persistent_value(
value_size: usize,
value: *const u8,
) -> AvbIOResult {
- result_to_io_enum(try_write_persistent_value(ops, name, value_size, value))
+ // SAFETY: see corresponding `try_*` function safety documentation.
+ unsafe { result_to_io_enum(try_write_persistent_value(ops, name, value_size, value)) }
}
/// Bounces the C callback into the user-provided Rust implementation.
@@ -959,7 +985,7 @@ unsafe fn try_write_persistent_value(
name: *const c_char,
value_size: usize,
value: *const u8,
-) -> Result<()> {
+) -> IoResult<()> {
check_nonnull(name)?;
// SAFETY:
@@ -987,7 +1013,7 @@ unsafe fn try_write_persistent_value(
}
}
-/// Wraps a callback to convert the given `Result<>` to raw `AvbIOResult` for libavb.
+/// Wraps a callback to convert the given `IoResult<>` to raw `AvbIOResult` for libavb.
///
/// See corresponding `try_*` function docs.
unsafe extern "C" fn validate_public_key_for_partition(
@@ -1000,16 +1026,19 @@ unsafe extern "C" fn validate_public_key_for_partition(
out_is_trusted: *mut bool,
out_rollback_index_location: *mut u32,
) -> AvbIOResult {
- result_to_io_enum(try_validate_public_key_for_partition(
- ops,
- partition,
- public_key_data,
- public_key_length,
- public_key_metadata,
- public_key_metadata_length,
- out_is_trusted,
- out_rollback_index_location,
- ))
+ // SAFETY: see corresponding `try_*` function safety documentation.
+ unsafe {
+ result_to_io_enum(try_validate_public_key_for_partition(
+ ops,
+ partition,
+ public_key_data,
+ public_key_length,
+ public_key_metadata,
+ public_key_metadata_length,
+ out_is_trusted,
+ out_rollback_index_location,
+ ))
+ }
}
/// Bounces the C callback into the user-provided Rust implementation.
@@ -1019,6 +1048,7 @@ unsafe extern "C" fn validate_public_key_for_partition(
/// * `partition` must adhere to the requirements of `CStr::from_ptr()`.
/// * `public_key_*` args must adhere to the requirements of `slice::from_raw_parts()`.
/// * `out_*` must adhere to the requirements of `ptr::write()`.
+#[allow(clippy::too_many_arguments)] // Mirroring libavb C API.
unsafe fn try_validate_public_key_for_partition(
ops: *mut AvbOps,
partition: *const c_char,
@@ -1028,7 +1058,7 @@ unsafe fn try_validate_public_key_for_partition(
public_key_metadata_length: usize,
out_is_trusted: *mut bool,
out_rollback_index_location: *mut u32,
-) -> Result<()> {
+) -> IoResult<()> {
check_nonnull(partition)?;
check_nonnull(public_key_data)?;
check_nonnull(out_is_trusted)?;
diff --git a/rust/src/verify.rs b/rust/src/verify.rs
index 62ecf0b..e972226 100644
--- a/rust/src/verify.rs
+++ b/rust/src/verify.rs
@@ -17,21 +17,24 @@
//! This module is responsible for all the conversions required to pass information between
//! libavb and Rust for verifying images.
+extern crate alloc;
+
use crate::{
+ descriptor::{get_descriptors, Descriptor, DescriptorResult},
error::{
slot_verify_enum_to_result, vbmeta_verify_enum_to_result, SlotVerifyError,
- VbmetaVerifyError,
+ SlotVerifyNoDataResult, SlotVerifyResult, VbmetaVerifyResult,
},
- ops, IoError, Ops,
+ ops, Ops,
};
+use alloc::vec::Vec;
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},
+ ptr::{self, null, null_mut, NonNull},
slice,
};
@@ -41,7 +44,7 @@ pub use avb_bindgen::AvbHashtreeErrorMode as HashtreeErrorMode;
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>> {
+fn check_nonnull<T>(ptr: *const T) -> SlotVerifyNoDataResult<()> {
match ptr.is_null() {
true => Err(SlotVerifyError::Internal),
false => Ok(()),
@@ -66,7 +69,7 @@ impl 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>> {
+ fn validate(&self) -> SlotVerifyNoDataResult<()> {
check_nonnull(self.0.partition_name)?;
check_nonnull(self.0.vbmeta_data)?;
Ok(())
@@ -89,9 +92,21 @@ impl VbmetaData {
}
/// Returns the vbmeta verification result.
- pub fn verify_result(&self) -> Result<(), VbmetaVerifyError> {
+ pub fn verify_result(&self) -> VbmetaVerifyResult<()> {
vbmeta_verify_enum_to_result(self.0.verify_result)
}
+
+ /// Extracts the descriptors from the vbmeta image.
+ ///
+ /// Note that this function allocates memory to hold the `Descriptor` objects.
+ ///
+ /// # Returns
+ /// A vector of descriptors, or `DescriptorError` on failure.
+ pub fn descriptors(&self) -> DescriptorResult<Vec<Descriptor>> {
+ // SAFETY: the only way to get a `VbmetaData` object is via the return value of
+ // `slot_verify()`, so we know we have been properly validated.
+ unsafe { get_descriptors(self) }
+ }
}
impl fmt::Display for VbmetaData {
@@ -102,7 +117,7 @@ impl fmt::Display for VbmetaData {
/// 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 {
+impl fmt::Debug for VbmetaData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
@@ -122,7 +137,7 @@ impl 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>> {
+ fn validate(&self) -> SlotVerifyNoDataResult<()> {
check_nonnull(self.0.partition_name)?;
check_nonnull(self.0.data)?;
Ok(())
@@ -153,7 +168,7 @@ impl PartitionData {
///
/// 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>> {
+ pub fn verify_result(&self) -> SlotVerifyNoDataResult<()> {
slot_verify_enum_to_result(self.0.verify_result)
}
}
@@ -176,7 +191,7 @@ impl fmt::Display for PartitionData {
/// 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 {
+impl fmt::Debug for PartitionData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
@@ -185,16 +200,25 @@ impl<'a> fmt::Debug for PartitionData {
/// 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>,
+ /// This provides the necessary lifetime borrow so the compiler can make sure that the `Ops`
+ /// stays alive at least as long as we do, since it owns any preloaded partition data.
+ _ops: &'a dyn Ops,
}
+// Useful so that `SlotVerifyError`, which may hold a `SlotVerifyData`, can derive `PartialEq`.
+impl<'a> PartialEq for SlotVerifyData<'a> {
+ fn eq(&self, other: &Self) -> bool {
+ // A `SlotVerifyData` uniquely owns the underlying data so is only equal to itself.
+ ptr::eq(self, other)
+ }
+}
+
+impl<'a> Eq for SlotVerifyData<'a> {}
+
impl<'a> SlotVerifyData<'a> {
/// Creates a `SlotVerifyData` wrapping the given raw `AvbSlotVerifyData`.
///
@@ -215,10 +239,10 @@ impl<'a> SlotVerifyData<'a> {
unsafe fn new(
data: *mut AvbSlotVerifyData,
ops: &'a mut dyn Ops,
- ) -> Result<Self, SlotVerifyError<'static>> {
+ ) -> SlotVerifyNoDataResult<Self> {
let ret = Self {
raw_data: NonNull::new(data).ok_or(SlotVerifyError::Internal)?,
- _ops: PhantomData,
+ _ops: ops,
};
// Validate all the contained data here so accessors will never fail.
@@ -356,7 +380,7 @@ pub fn slot_verify<'a>(
ab_suffix: Option<&CStr>,
flags: SlotVerifyFlags,
hashtree_error_mode: HashtreeErrorMode,
-) -> Result<SlotVerifyData<'a>, SlotVerifyError<'a>> {
+) -> SlotVerifyResult<'a, SlotVerifyData<'a>> {
let mut user_data = ops::UserData::new(ops);
let mut scoped_ops = ops::ScopedAvbOps::new(&mut user_data);
let avb_ops = scoped_ops.as_mut();
diff --git a/rust/testdata/chain_partition_descriptor.bin b/rust/testdata/chain_partition_descriptor.bin
new file mode 100644
index 0000000..5331273
--- /dev/null
+++ b/rust/testdata/chain_partition_descriptor.bin
Binary files differ
diff --git a/rust/testdata/hash_descriptor.bin b/rust/testdata/hash_descriptor.bin
new file mode 100644
index 0000000..940732e
--- /dev/null
+++ b/rust/testdata/hash_descriptor.bin
Binary files differ
diff --git a/rust/testdata/hashtree_descriptor.bin b/rust/testdata/hashtree_descriptor.bin
new file mode 100644
index 0000000..cf2b264
--- /dev/null
+++ b/rust/testdata/hashtree_descriptor.bin
Binary files differ
diff --git a/rust/testdata/kernel_commandline_descriptor.bin b/rust/testdata/kernel_commandline_descriptor.bin
new file mode 100644
index 0000000..f6f3fde
--- /dev/null
+++ b/rust/testdata/kernel_commandline_descriptor.bin
Binary files differ
diff --git a/rust/testdata/property_descriptor.bin b/rust/testdata/property_descriptor.bin
new file mode 100644
index 0000000..ea78832
--- /dev/null
+++ b/rust/testdata/property_descriptor.bin
Binary files differ
diff --git a/rust/tests/test_ops.rs b/rust/tests/test_ops.rs
index 1e1ad1b..1c62c4d 100644
--- a/rust/tests/test_ops.rs
+++ b/rust/tests/test_ops.rs
@@ -14,14 +14,11 @@
//! Provides `avb::Ops` test fixtures.
-use avb::{IoError, Ops, PublicKeyForPartitionInfo};
+use avb::{IoError, IoResult, Ops, PublicKeyForPartitionInfo};
use std::{cmp::min, collections::HashMap, ffi::CStr};
#[cfg(feature = "uuid")]
use uuid::Uuid;
-/// Common `Result` type for `IoError` errors.
-type Result<T> = core::result::Result<T, IoError>;
-
/// Represents a single fake partition.
#[derive(Default)]
pub struct FakePartition {
@@ -64,12 +61,12 @@ pub struct TestOps {
pub rollbacks: HashMap<usize, u64>,
/// Unlock state. Set an error to simulate IoError during access.
- pub unlock_state: Result<bool>,
+ pub unlock_state: IoResult<bool>,
/// Persistent named values. Set an error to simulate `IoError` during access. Writing
/// a non-existent persistent value will create it; to simulate `NoSuchValue` instead,
/// create an entry with `Err(IoError::NoSuchValue)` as the value.
- pub persistent_values: HashMap<String, Result<Vec<u8>>>,
+ pub persistent_values: HashMap<String, IoResult<Vec<u8>>>,
}
impl TestOps {
@@ -105,7 +102,7 @@ impl TestOps {
/// test_ops.add_persistent_value("foo", Ok(b"contents"));
/// test_ops.add_persistent_value("bar", Err(IoError::NoSuchValue));
/// ```
- pub fn add_persistent_value(&mut self, name: &str, contents: Result<&[u8]>) {
+ pub fn add_persistent_value(&mut self, name: &str, contents: IoResult<&[u8]>) {
self.persistent_values
.insert(name.into(), contents.map(|b| b.into()));
}
@@ -166,7 +163,7 @@ impl Ops for TestOps {
partition: &CStr,
offset: i64,
buffer: &mut [u8],
- ) -> Result<usize> {
+ ) -> IoResult<usize> {
let partition = self
.partitions
.get(partition.to_str()?)
@@ -205,7 +202,7 @@ impl Ops for TestOps {
Ok(bytes_read)
}
- fn get_preloaded_partition(&mut self, partition: &CStr) -> Result<&[u8]> {
+ fn get_preloaded_partition(&mut self, partition: &CStr) -> IoResult<&[u8]> {
match self.partitions.get(partition.to_str()?) {
Some(FakePartition {
contents,
@@ -220,7 +217,7 @@ impl Ops for TestOps {
&mut self,
public_key: &[u8],
public_key_metadata: Option<&[u8]>,
- ) -> Result<bool> {
+ ) -> IoResult<bool> {
self.vbmeta_keys
// The compiler can't match (&[u8], Option<&[u8]>) to keys of type
// (Vec<u8>, Option<Vec<u8>>) so we turn the &[u8] into vectors here. This is a bit
@@ -230,35 +227,35 @@ impl Ops for TestOps {
.map(|k| k.info.trusted)
}
- fn read_rollback_index(&mut self, location: usize) -> Result<u64> {
+ fn read_rollback_index(&mut self, location: usize) -> IoResult<u64> {
self.rollbacks.get(&location).ok_or(IoError::Io).copied()
}
- fn write_rollback_index(&mut self, location: usize, index: u64) -> Result<()> {
+ fn write_rollback_index(&mut self, location: usize, index: u64) -> IoResult<()> {
*(self.rollbacks.get_mut(&location).ok_or(IoError::Io)?) = index;
Ok(())
}
- fn read_is_device_unlocked(&mut self) -> Result<bool> {
+ fn read_is_device_unlocked(&mut self) -> IoResult<bool> {
self.unlock_state.clone()
}
#[cfg(feature = "uuid")]
- fn get_unique_guid_for_partition(&mut self, partition: &CStr) -> Result<Uuid> {
+ fn get_unique_guid_for_partition(&mut self, partition: &CStr) -> IoResult<Uuid> {
self.partitions
.get(partition.to_str()?)
.map(|p| p.uuid)
.ok_or(IoError::NoSuchPartition)
}
- fn get_size_of_partition(&mut self, partition: &CStr) -> Result<u64> {
+ fn get_size_of_partition(&mut self, partition: &CStr) -> IoResult<u64> {
self.partitions
.get(partition.to_str()?)
.map(|p| u64::try_from(p.contents.len()).unwrap())
.ok_or(IoError::NoSuchPartition)
}
- fn read_persistent_value(&mut self, name: &CStr, value: &mut [u8]) -> Result<usize> {
+ fn read_persistent_value(&mut self, name: &CStr, value: &mut [u8]) -> IoResult<usize> {
match self
.persistent_values
.get(name.to_str()?)
@@ -276,7 +273,7 @@ impl Ops for TestOps {
}
}
- fn write_persistent_value(&mut self, name: &CStr, value: &[u8]) -> Result<()> {
+ fn write_persistent_value(&mut self, name: &CStr, value: &[u8]) -> IoResult<()> {
let name = name.to_str()?;
// If the test requested a simulated error on this value, return it.
@@ -289,7 +286,7 @@ impl Ops for TestOps {
Ok(())
}
- fn erase_persistent_value(&mut self, name: &CStr) -> Result<()> {
+ fn erase_persistent_value(&mut self, name: &CStr) -> IoResult<()> {
let name = name.to_str()?;
// If the test requested a simulated error on this value, return it.
@@ -306,14 +303,14 @@ impl Ops for TestOps {
partition: &CStr,
public_key: &[u8],
public_key_metadata: Option<&[u8]>,
- ) -> Result<PublicKeyForPartitionInfo> {
+ ) -> IoResult<PublicKeyForPartitionInfo> {
let key = self
.vbmeta_keys
.get(&(public_key.to_vec(), public_key_metadata.map(|m| m.to_vec())))
.ok_or(IoError::Io)?;
if let Some(for_partition) = key.for_partition {
- if (for_partition == partition.to_str()?) {
+ if for_partition == partition.to_str()? {
// The key is registered for this partition; return its info.
return Ok(key.info);
}
diff --git a/rust/tests/verify_tests.rs b/rust/tests/verify_tests.rs
index 0482ae7..015bc9f 100644
--- a/rust/tests/verify_tests.rs
+++ b/rust/tests/verify_tests.rs
@@ -16,25 +16,54 @@
use crate::test_ops::TestOps;
use avb::{
- slot_verify, HashtreeErrorMode, IoError, SlotVerifyData, SlotVerifyError, SlotVerifyFlags,
+ slot_verify, ChainPartitionDescriptor, ChainPartitionDescriptorFlags, Descriptor,
+ HashDescriptor, HashDescriptorFlags, HashtreeDescriptor, HashtreeDescriptorFlags,
+ HashtreeErrorMode, IoError, KernelCommandlineDescriptor, KernelCommandlineDescriptorFlags,
+ PropertyDescriptor, SlotVerifyData, SlotVerifyError, SlotVerifyFlags, SlotVerifyResult,
};
+use hex::decode;
use std::{ffi::CString, fs};
#[cfg(feature = "uuid")]
-use uuid::{uuid, Uuid};
+use 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_IMAGE_SALT_HEX: &str = "1000";
+const TEST_HASHTREE_SALT_HEX: &str = "B000";
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_VBMETA_WITH_PROPERTY_PATH: &str = "test_vbmeta_with_property.img";
+const TEST_VBMETA_WITH_HASHTREE_PATH: &str = "test_vbmeta_with_hashtree.img";
+const TEST_VBMETA_WITH_COMMANDLINE_PATH: &str = "test_vbmeta_with_commandline.img";
+const TEST_VBMETA_WITH_CHAINED_PARTITION_PATH: &str = "test_vbmeta_with_chained_partition.img";
const TEST_IMAGE_WITH_VBMETA_FOOTER_PATH: &str = "avbrs_test_image_with_vbmeta_footer.img";
+const TEST_IMAGE_WITH_VBMETA_FOOTER_FOR_BOOT_PATH: &str =
+ "avbrs_test_image_with_vbmeta_footer_for_boot.img";
+const TEST_IMAGE_WITH_VBMETA_FOOTER_FOR_TEST_PART_2: &str =
+ "avbrs_test_image_with_vbmeta_footer_for_test_part_2.img";
const TEST_PUBLIC_KEY_PATH: &str = "data/testkey_rsa4096_pub.bin";
+const TEST_PUBLIC_KEY_RSA8192_PATH: &str = "data/testkey_rsa8192_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_PARTITION_HASH_TREE_NAME: &str = "test_part_hashtree";
const TEST_VBMETA_ROLLBACK_LOCATION: usize = 0; // Default value, we don't explicitly set this.
+const TEST_PROPERTY_KEY: &str = "test_prop_key";
+const TEST_PROPERTY_VALUE: &[u8] = b"test_prop_value";
+const TEST_KERNEL_COMMANDLINE: &str = "test_cmdline_key=test_cmdline_value";
+const TEST_CHAINED_PARTITION_ROLLBACK_LOCATION: usize = 4;
+const TEST_CHAINED_PARTITION_ROLLBACK_INDEX: u64 = 7;
+
+// Expected values determined by examining the vbmeta image with `avbtool info_image`.
+// Images can be found in <out>/soong/.intermediates/external/avb/rust/.
+const TEST_IMAGE_DIGEST_HEX: &str =
+ "89e6fd3142917b8c34ac7d30897a907a71bd3bf5d9b39d00bf938b41dcf3b84f";
+const TEST_IMAGE_HASH_ALGO: &str = "sha256";
+const TEST_HASHTREE_DIGEST_HEX: &str = "5373fc4ee3dd898325eeeffb5a1dbb041900c5f1";
+const TEST_HASHTREE_ALGORITHM: &str = "sha1";
/// Initializes a `TestOps` object such that verification will succeed on `TEST_PARTITION_NAME`.
fn test_ops_one_image_one_vbmeta() -> TestOps {
@@ -48,9 +77,7 @@ fn test_ops_one_image_one_vbmeta() -> TestOps {
}
/// Calls `slot_verify()` using standard args for `test_ops_one_image_one_vbmeta()` setup.
-fn verify_one_image_one_vbmeta<'a>(
- ops: &'a mut TestOps,
-) -> Result<SlotVerifyData<'a>, SlotVerifyError<'a>> {
+fn verify_one_image_one_vbmeta(ops: &mut TestOps) -> SlotVerifyResult<SlotVerifyData> {
slot_verify(
ops,
&[&CString::new(TEST_PARTITION_NAME).unwrap()],
@@ -71,10 +98,8 @@ fn test_ops_two_images_one_vbmeta() -> TestOps {
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>> {
+/// Calls `slot_verify()` for both test partitions.
+fn verify_two_images(ops: &mut TestOps) -> SlotVerifyResult<SlotVerifyData> {
slot_verify(
ops,
&[
@@ -87,6 +112,32 @@ fn verify_two_images_one_vbmeta<'a>(
)
}
+/// Initializes a `TestOps` object such that verification will succeed on the `boot` partition with
+/// a combined image + vbmeta.
+fn test_ops_boot_partition() -> TestOps {
+ let mut ops = test_ops_one_image_one_vbmeta();
+ ops.partitions.clear();
+ ops.add_partition(
+ "boot",
+ fs::read(TEST_IMAGE_WITH_VBMETA_FOOTER_FOR_BOOT_PATH).unwrap(),
+ );
+ ops
+}
+
+/// Calls `slot_verify()` using standard args for `test_ops_boot_partition()` setup.
+fn verify_boot_partition(ops: &mut TestOps) -> SlotVerifyResult<SlotVerifyData> {
+ slot_verify(
+ ops,
+ &[&CString::new("boot").unwrap()],
+ None,
+ // libavb has some special-case handling to automatically detect a combined image + vbmeta
+ // in the `boot` partition; don't pass the `AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION` flag
+ // so we can test this behavior.
+ 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 {
@@ -103,9 +154,7 @@ fn test_ops_persistent_digest(image: Vec<u8>) -> TestOps {
}
/// Calls `slot_verify()` using standard args for `test_ops_persistent_digest()` setup.
-fn verify_persistent_digest<'a>(
- ops: &'a mut TestOps,
-) -> Result<SlotVerifyData<'a>, SlotVerifyError<'a>> {
+fn verify_persistent_digest(ops: &mut TestOps) -> SlotVerifyResult<SlotVerifyData> {
slot_verify(
ops,
&[&CString::new(TEST_PARTITION_PERSISTENT_DIGEST_NAME).unwrap()],
@@ -211,7 +260,7 @@ fn slotted_partition_passes_verification() {
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);
+ let result = verify_two_images(&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.
@@ -276,6 +325,27 @@ fn combined_image_vbmeta_partition_passes_verification() {
assert_eq!(partition_data.data(), fs::read(TEST_IMAGE_PATH).unwrap());
}
+// Validate the custom behavior if the combined image + vbmeta live in the `boot` partition.
+#[test]
+fn vbmeta_with_boot_partition_passes_verification() {
+ let mut ops = test_ops_boot_partition();
+
+ let result = verify_boot_partition(&mut ops);
+
+ let data = result.unwrap();
+
+ // Vbmeta should indicate that it came from `boot`.
+ assert_eq!(data.vbmeta_data().len(), 1);
+ let vbmeta_data = &data.vbmeta_data()[0];
+ assert_eq!(vbmeta_data.partition_name().to_str().unwrap(), "boot");
+
+ // Partition should indicate that it came from `boot`, 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(), "boot");
+ 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
@@ -311,6 +381,23 @@ fn successful_verification_substitutes_partition_guid() {
.contains("androidboot.vbmeta.device=PARTUUID=01234567-89ab-cdef-0123-456789abcdef"));
}
+#[cfg(feature = "uuid")]
+#[test]
+fn successful_verification_substitutes_boot_partition_guid() {
+ let mut ops = test_ops_boot_partition();
+ ops.partitions.get_mut("boot").unwrap().uuid = uuid!("01234567-89ab-cdef-0123-456789abcdef");
+
+ let result = verify_boot_partition(&mut ops);
+
+ let data = result.unwrap();
+ // In this case libavb substitutes the `boot` partition GUID in for `vbmeta`.
+ 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();
@@ -509,7 +596,7 @@ fn preloaded_image_verification_data_display() {
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 result = verify_two_images(&mut ops);
let data = result.unwrap();
assert_eq!(
@@ -541,3 +628,159 @@ fn corrupted_image_verification_data_display() {
r#"slot: "", vbmeta: ["vbmeta": Ok(())], images: ["test_part": Err(Verification(None))]"#
);
}
+
+#[test]
+fn one_image_gives_single_descriptor() {
+ let mut ops = test_ops_one_image_one_vbmeta();
+
+ let result = verify_one_image_one_vbmeta(&mut ops);
+
+ let data = result.unwrap();
+ assert_eq!(data.vbmeta_data()[0].descriptors().unwrap().len(), 1);
+}
+
+#[test]
+fn two_images_gives_two_descriptors() {
+ let mut ops = test_ops_two_images_one_vbmeta();
+
+ let result = verify_two_images(&mut ops);
+
+ let data = result.unwrap();
+ assert_eq!(data.vbmeta_data()[0].descriptors().unwrap().len(), 2);
+}
+
+/// Runs verification on the given contents and checks for a resulting descriptor.
+///
+/// This test helper performs the following steps:
+///
+/// 1. set up a `TestOps` for the default test image/vbmeta
+/// 2. replace the vbmeta image with the contents at `vbmeta_path`
+/// 3. run verification
+/// 4. check that the given `descriptor` exists in the verification data
+fn verify_and_find_descriptor(vbmeta_path: &str, expected_descriptor: &Descriptor) {
+ let mut ops = test_ops_one_image_one_vbmeta();
+
+ // Replace the vbmeta image with the requested variation.
+ ops.add_partition("vbmeta", fs::read(vbmeta_path).unwrap());
+
+ let result = verify_one_image_one_vbmeta(&mut ops);
+
+ let data = result.unwrap();
+ let descriptors = &data.vbmeta_data()[0].descriptors().unwrap();
+ assert!(descriptors.contains(expected_descriptor));
+}
+
+#[test]
+fn verify_hash_descriptor() {
+ verify_and_find_descriptor(
+ // The standard vbmeta image should contain the hash descriptor.
+ TEST_VBMETA_PATH,
+ &Descriptor::Hash(HashDescriptor {
+ image_size: TEST_IMAGE_SIZE as u64,
+ hash_algorithm: TEST_IMAGE_HASH_ALGO,
+ flags: HashDescriptorFlags(0),
+ partition_name: TEST_PARTITION_NAME,
+ salt: &decode(TEST_IMAGE_SALT_HEX).unwrap(),
+ digest: &decode(TEST_IMAGE_DIGEST_HEX).unwrap(),
+ }),
+ );
+}
+
+#[test]
+fn verify_property_descriptor() {
+ verify_and_find_descriptor(
+ TEST_VBMETA_WITH_PROPERTY_PATH,
+ &Descriptor::Property(PropertyDescriptor {
+ key: TEST_PROPERTY_KEY,
+ value: TEST_PROPERTY_VALUE,
+ }),
+ );
+}
+
+#[test]
+fn verify_hashtree_descriptor() {
+ verify_and_find_descriptor(
+ TEST_VBMETA_WITH_HASHTREE_PATH,
+ &Descriptor::Hashtree(HashtreeDescriptor {
+ dm_verity_version: 1,
+ image_size: TEST_IMAGE_SIZE as u64,
+ tree_offset: TEST_IMAGE_SIZE as u64,
+ tree_size: 4096,
+ data_block_size: 4096,
+ hash_block_size: 4096,
+ fec_num_roots: 0,
+ fec_offset: 0,
+ fec_size: 0,
+ hash_algorithm: TEST_HASHTREE_ALGORITHM,
+ flags: HashtreeDescriptorFlags(0),
+ partition_name: TEST_PARTITION_HASH_TREE_NAME,
+ salt: &decode(TEST_HASHTREE_SALT_HEX).unwrap(),
+ root_digest: &decode(TEST_HASHTREE_DIGEST_HEX).unwrap(),
+ }),
+ );
+}
+
+#[test]
+fn verify_kernel_commandline_descriptor() {
+ verify_and_find_descriptor(
+ TEST_VBMETA_WITH_COMMANDLINE_PATH,
+ &Descriptor::KernelCommandline(KernelCommandlineDescriptor {
+ flags: KernelCommandlineDescriptorFlags(0),
+ commandline: TEST_KERNEL_COMMANDLINE,
+ }),
+ );
+}
+
+#[test]
+fn verify_chain_partition_descriptor() {
+ let mut ops = test_ops_two_images_one_vbmeta();
+
+ // Set up the fake ops to contain:
+ // * the default test image in TEST_PARTITION_NAME
+ // * a signed test image with vbmeta footer in TEST_PARTITION_2_NAME
+ // * a vbmeta image in "vbmeta" which:
+ // * signs the default TEST_PARTITION_NAME image
+ // * chains to TEST_PARTITION_2_NAME
+ //
+ // Since this is an unusual configuration, it's simpler to just set it up manually here
+ // rather than try to adapt `verify_and_find_descriptor()` for this one case.
+ ops.add_partition(
+ "vbmeta",
+ fs::read(TEST_VBMETA_WITH_CHAINED_PARTITION_PATH).unwrap(),
+ );
+ // Replace the chained partition with the combined image + vbmeta footer.
+ ops.add_partition(
+ TEST_PARTITION_2_NAME,
+ fs::read(TEST_IMAGE_WITH_VBMETA_FOOTER_FOR_TEST_PART_2).unwrap(),
+ );
+ // Add the rollback index for the chained partition's location.
+ ops.rollbacks.insert(
+ TEST_CHAINED_PARTITION_ROLLBACK_LOCATION,
+ TEST_CHAINED_PARTITION_ROLLBACK_INDEX,
+ );
+
+ let result = verify_two_images(&mut ops);
+
+ let data = result.unwrap();
+ // We should have two vbmeta images - one from the "vbmeta" partition, the other embedded
+ // in the footer of TEST_PARTITION_2_NAME.
+ let vbmetas = data.vbmeta_data();
+ assert_eq!(vbmetas.len(), 2);
+ // Search for the main vbmeta so we don't assume any particular order.
+ let main_vbmeta = vbmetas
+ .iter()
+ .find(|v| v.partition_name().to_str().unwrap() == "vbmeta")
+ .unwrap();
+
+ // The main vbmeta should contain the chain descriptor.
+ let expected = ChainPartitionDescriptor {
+ rollback_index_location: TEST_CHAINED_PARTITION_ROLLBACK_LOCATION as u32,
+ partition_name: TEST_PARTITION_2_NAME,
+ public_key: &fs::read(TEST_PUBLIC_KEY_RSA8192_PATH).unwrap(),
+ flags: ChainPartitionDescriptorFlags(0),
+ };
+ assert!(main_vbmeta
+ .descriptors()
+ .unwrap()
+ .contains(&Descriptor::ChainPartition(expected)));
+}