diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2024-02-02 23:46:15 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2024-02-02 23:46:15 +0000 |
commit | 7c5fc412e814b2c5e27fca1e3db7ef9fecec6b7f (patch) | |
tree | f021df2b77ad341d7518458b5d970654e32daca3 | |
parent | a195c4b02a745998aae433e226ce736fe4986538 (diff) | |
parent | b798e6083f6468128d51e38b015829eb63943e35 (diff) | |
download | avb-7c5fc412e814b2c5e27fca1e3db7ef9fecec6b7f.tar.gz |
Snap for 11400057 from b798e6083f6468128d51e38b015829eb63943e35 to simpleperf-release
Change-Id: I289f74643f2f1862b23b9646900c8c74f73310ae
-rw-r--r-- | rust/Android.bp | 257 | ||||
-rw-r--r-- | rust/TEST_MAPPING | 6 | ||||
-rw-r--r-- | rust/src/descriptor/chain.rs | 117 | ||||
-rw-r--r-- | rust/src/descriptor/commandline.rs | 102 | ||||
-rw-r--r-- | rust/src/descriptor/hash.rs | 131 | ||||
-rw-r--r-- | rust/src/descriptor/hashtree.rs | 157 | ||||
-rw-r--r-- | rust/src/descriptor/mod.rs | 271 | ||||
-rw-r--r-- | rust/src/descriptor/property.rs | 108 | ||||
-rw-r--r-- | rust/src/descriptor/util.rs | 178 | ||||
-rw-r--r-- | rust/src/error.rs | 96 | ||||
-rw-r--r-- | rust/src/lib.rs | 22 | ||||
-rw-r--r-- | rust/src/ops.rs | 292 | ||||
-rw-r--r-- | rust/src/verify.rs | 60 | ||||
-rw-r--r-- | rust/testdata/chain_partition_descriptor.bin | bin | 0 -> 2160 bytes | |||
-rw-r--r-- | rust/testdata/hash_descriptor.bin | bin | 0 -> 176 bytes | |||
-rw-r--r-- | rust/testdata/hashtree_descriptor.bin | bin | 0 -> 240 bytes | |||
-rw-r--r-- | rust/testdata/kernel_commandline_descriptor.bin | bin | 0 -> 64 bytes | |||
-rw-r--r-- | rust/testdata/property_descriptor.bin | bin | 0 -> 64 bytes | |||
-rw-r--r-- | rust/tests/test_ops.rs | 37 | ||||
-rw-r--r-- | rust/tests/verify_tests.rs | 271 |
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 Binary files differnew file mode 100644 index 0000000..5331273 --- /dev/null +++ b/rust/testdata/chain_partition_descriptor.bin diff --git a/rust/testdata/hash_descriptor.bin b/rust/testdata/hash_descriptor.bin Binary files differnew file mode 100644 index 0000000..940732e --- /dev/null +++ b/rust/testdata/hash_descriptor.bin diff --git a/rust/testdata/hashtree_descriptor.bin b/rust/testdata/hashtree_descriptor.bin Binary files differnew file mode 100644 index 0000000..cf2b264 --- /dev/null +++ b/rust/testdata/hashtree_descriptor.bin diff --git a/rust/testdata/kernel_commandline_descriptor.bin b/rust/testdata/kernel_commandline_descriptor.bin Binary files differnew file mode 100644 index 0000000..f6f3fde --- /dev/null +++ b/rust/testdata/kernel_commandline_descriptor.bin diff --git a/rust/testdata/property_descriptor.bin b/rust/testdata/property_descriptor.bin Binary files differnew file mode 100644 index 0000000..ea78832 --- /dev/null +++ b/rust/testdata/property_descriptor.bin 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))); +} |