diff options
author | David Pursell <dpursell@google.com> | 2024-01-13 03:31:00 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2024-01-13 03:31:00 +0000 |
commit | 8a0c7a23e6fec34f9a4a178f0207db8ffdc6bfbe (patch) | |
tree | 1b85d48a870612502934f4dfea8aa416e0872e20 | |
parent | 5e753bd9ef01e5a170cb2288fded8ca51632d0d0 (diff) | |
parent | b39f815a905d132e721249502eca925ffb8badfa (diff) | |
download | avb-8a0c7a23e6fec34f9a4a178f0207db8ffdc6bfbe.tar.gz |
libavb_rs: add hash descriptor support am: d9fc570a09 am: 3d2e86dce2 am: b39f815a90
Original change: https://android-review.googlesource.com/c/platform/external/avb/+/2857728
Change-Id: I5964eccb146156201b485af0029e5a743b6b6dd7
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-rw-r--r-- | rust/Android.bp | 103 | ||||
-rw-r--r-- | rust/src/descriptor/hash.rs | 154 | ||||
-rw-r--r-- | rust/src/descriptor/mod.rs | 15 | ||||
-rw-r--r-- | rust/src/descriptor/util.rs | 68 | ||||
-rw-r--r-- | rust/src/lib.rs | 4 | ||||
-rw-r--r-- | rust/tests/verify_tests.rs | 36 |
6 files changed, 355 insertions, 25 deletions
diff --git a/rust/Android.bp b/rust/Android.bp index 517d87a..7ef3df3 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,112 @@ rust_defaults { ], source_stem: "bindings", bindgen_flags: [ - "--constified-enum-module AvbDescriptorTag", + "--constified-enum-module=AvbDescriptorTag", + "--bitfield-enum=AvbHashDescriptorFlags", "--bitfield-enum=AvbSlotVerifyFlags", "--default-enum-style rust", + "--with-derive-default", + "--with-derive-custom=Avb.*Descriptor=FromZeroes,FromBytes", "--allowlist-type=AvbDescriptorTag", + "--allowlist-type=AvbHashDescriptorFlags", "--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. @@ -99,6 +159,7 @@ rust_defaults { no_stdlibs: true, rustlibs: [ "libavb_bindgen_nostd", + "libzerocopy_nostd_noalloc", ], whole_static_libs: [ "libavb_baremetal", @@ -115,6 +176,7 @@ rust_defaults { host_supported: true, rustlibs: [ "libavb_bindgen", + "libzerocopy", ], whole_static_libs: [ "libavb", @@ -203,6 +265,7 @@ rust_defaults { ":avbrs_test_vbmeta_2_parts", ":avbrs_test_vbmeta_persistent_digest", ], + rustlibs: ["libhex"], test_suites: ["general-tests"], clippy_lints: "android", lints: "android", @@ -240,7 +303,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". @@ -248,7 +311,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 diff --git a/rust/src/descriptor/hash.rs b/rust/src/descriptor/hash.rs new file mode 100644 index 0000000..edc49ec --- /dev/null +++ b/rust/src/descriptor/hash.rs @@ -0,0 +1,154 @@ +// 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::split_slice, DescriptorError, DescriptorResult}; +use avb_bindgen::{avb_hash_descriptor_validate_and_byteswap, AvbHashDescriptor}; +use core::{ffi::CStr, str::from_utf8}; +use zerocopy::Ref; + +/// `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], +} + +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> { + let (raw_header, remainder) = Ref::<_, AvbHashDescriptor>::new_from_prefix(contents) + .ok_or(DescriptorError::InvalidHeader)?; + let raw_header = raw_header.into_ref(); + + let header = { + let mut header = AvbHashDescriptor::default(); + // SAFETY: + // * `avb_hash_descriptor_validate_and_byteswap()` checks the validity of the fields + // * even if `raw_header` is corrupted somehow, this will only give bogus header values + // as output which will be caught below; it will never try to access memory outside + // of the header. + if !unsafe { avb_hash_descriptor_validate_and_byteswap(raw_header, &mut header) } { + return Err(DescriptorError::InvalidHeader); + } + header + }; + + // Descriptor body contains: name + salt + digest. + let (partition_name, remainder) = split_slice(remainder, header.partition_name_len)?; + let (salt, remainder) = split_slice(remainder, header.salt_len)?; + let (digest, _) = split_slice(remainder, header.digest_len)?; + + // Extract the hash algorithm from the original contents since the temporary `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(&raw_header.hash_algorithm) + .map_err(|_| DescriptorError::InvalidValue)? + .to_str() + .map_err(|_| DescriptorError::InvalidUtf8)?; + + Ok(Self { + image_size: header.image_size, + hash_algorithm, + flags: HashDescriptorFlags(header.flags), + partition_name: from_utf8(partition_name).map_err(|_| DescriptorError::InvalidUtf8)?, + salt, + digest, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::mem::size_of; + + /// A valid hash descriptor in raw big-endian format. + /// + /// It's fairly complicated to generate a descriptor programmatically, but for the purposes + /// of these tests we don't care about the specific values, so this is just hardcoded. + /// Actually extracting data from a descriptor is checked in the integration tests. + const TEST_HASH_DESCRIPTOR: &[u8] = &[ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x73, 0x68, 0x61, 0x32, 0x35, 0x36, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0x65, 0x73, + 0x74, 0x5F, 0x70, 0x61, 0x72, 0x74, 0x10, 0x00, 0x89, 0xE6, 0xFD, 0x31, 0x42, 0x91, 0x7B, + 0x8C, 0x34, 0xAC, 0x7D, 0x30, 0x89, 0x7A, 0x90, 0x7A, 0x71, 0xBD, 0x3B, 0xF5, 0xD9, 0xB3, + 0x9D, 0x00, 0xBF, 0x93, 0x8B, 0x41, 0xDC, 0xF3, 0xB8, 0x4F, 0x00, + ]; + + #[test] + fn new_hash_descriptor_success() { + let descriptor = HashDescriptor::new(TEST_HASH_DESCRIPTOR); + assert!(descriptor.is_ok()); + } + + #[test] + fn new_hash_descriptor_too_short_header_fails() { + let bad_header_size = size_of::<AvbHashDescriptor>() - 1; + assert_eq!( + HashDescriptor::new(&TEST_HASH_DESCRIPTOR[..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_HASH_DESCRIPTOR.len() - 2; + assert_eq!( + HashDescriptor::new(&TEST_HASH_DESCRIPTOR[..bad_contents_size]).unwrap_err(), + DescriptorError::InvalidSize + ); + } +} diff --git a/rust/src/descriptor/mod.rs b/rust/src/descriptor/mod.rs index 279f425..fdc4956 100644 --- a/rust/src/descriptor/mod.rs +++ b/rust/src/descriptor/mod.rs @@ -19,6 +19,9 @@ extern crate alloc; +mod hash; +mod util; + use crate::VbmetaData; use alloc::vec::Vec; use avb_bindgen::{ @@ -26,6 +29,8 @@ use avb_bindgen::{ }; use core::{ffi::c_void, mem::size_of, slice}; +pub use hash::{HashDescriptor, HashDescriptorFlags}; + /// A single descriptor. // TODO(b/290110273): add support for full descriptor contents. #[derive(Debug)] @@ -35,7 +40,7 @@ pub enum Descriptor<'a> { /// Wraps `AvbHashtreeDescriptor`. Hashtree(&'a [u8]), /// Wraps `AvbHashDescriptor`. - Hash(&'a [u8]), + Hash(HashDescriptor<'a>), /// Wraps `AvbKernelCmdlineDescriptor`. KernelCommandline(&'a [u8]), /// Wraps `AvbChainPartitionDescriptor`. @@ -51,6 +56,10 @@ pub enum DescriptorError { 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, } /// `Result` type for `DescriptorError` errors. @@ -96,7 +105,9 @@ impl<'a> Descriptor<'a> { match descriptor.tag.try_into() { Ok(AvbDescriptorTag::AVB_DESCRIPTOR_TAG_PROPERTY) => Ok(Descriptor::Property(contents)), Ok(AvbDescriptorTag::AVB_DESCRIPTOR_TAG_HASHTREE) => Ok(Descriptor::Hashtree(contents)), - Ok(AvbDescriptorTag::AVB_DESCRIPTOR_TAG_HASH) => Ok(Descriptor::Hash(contents)), + Ok(AvbDescriptorTag::AVB_DESCRIPTOR_TAG_HASH) => { + Ok(Descriptor::Hash(HashDescriptor::new(contents)?)) + } Ok(AvbDescriptorTag::AVB_DESCRIPTOR_TAG_KERNEL_CMDLINE) => { Ok(Descriptor::KernelCommandline(contents)) } diff --git a/rust/src/descriptor/util.rs b/rust/src/descriptor/util.rs new file mode 100644 index 0000000..ada80fc --- /dev/null +++ b/rust/src/descriptor/util.rs @@ -0,0 +1,68 @@ +// 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}; + +/// 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)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[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)); + } +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 18f0f26..8da12fd 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -31,7 +31,9 @@ mod error; mod ops; mod verify; -pub use descriptor::{Descriptor, DescriptorError, DescriptorResult}; +pub use descriptor::{ + Descriptor, DescriptorError, DescriptorResult, HashDescriptor, HashDescriptorFlags, +}; pub use error::{ IoError, IoResult, SlotVerifyError, SlotVerifyNoDataResult, SlotVerifyResult, VbmetaVerifyError, VbmetaVerifyResult, diff --git a/rust/tests/verify_tests.rs b/rust/tests/verify_tests.rs index fff7796..36a4647 100644 --- a/rust/tests/verify_tests.rs +++ b/rust/tests/verify_tests.rs @@ -16,9 +16,10 @@ use crate::test_ops::TestOps; use avb::{ - slot_verify, HashtreeErrorMode, IoError, SlotVerifyData, SlotVerifyError, SlotVerifyFlags, - SlotVerifyResult, + slot_verify, Descriptor, HashDescriptor, HashDescriptorFlags, HashtreeErrorMode, IoError, + SlotVerifyData, SlotVerifyError, SlotVerifyFlags, SlotVerifyResult, }; +use hex::decode; use std::{ffi::CString, fs}; #[cfg(feature = "uuid")] use uuid::uuid; @@ -26,6 +27,11 @@ 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"; +// Expected digest determined by examining the vbmeta image with `avbtool info_image`. +const TEST_IMAGE_DIGEST_HEX: &str = + "89e6fd3142917b8c34ac7d30897a907a71bd3bf5d9b39d00bf938b41dcf3b84f"; +const TEST_IMAGE_HASH_ALGO: &str = "sha256"; // Default value, we don't explicitly set this. 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"; @@ -622,3 +628,29 @@ fn two_images_gives_two_descriptors() { let data = result.unwrap(); assert_eq!(data.vbmeta_data()[0].descriptors().unwrap().len(), 2); } + +#[test] +fn verify_hash_descriptor() { + let mut ops = test_ops_one_image_one_vbmeta(); + + let result = verify_one_image_one_vbmeta(&mut ops); + + let data = result.unwrap(); + let descriptors = &data.vbmeta_data()[0].descriptors().unwrap(); + let descriptor = match &descriptors[0] { + Descriptor::Hash(d) => d, + d => panic!("Expected hash descriptor, got {:?}", d), + }; + + assert_eq!( + descriptor, + &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() + } + ) +} |