diff options
author | David Pursell <dpursell@google.com> | 2023-11-30 16:30:22 -0800 |
---|---|---|
committer | David Pursell <dpursell@google.com> | 2024-01-16 16:07:03 +0000 |
commit | 7cee66d62de50bbd3d8564feb2ed96c549ef2150 (patch) | |
tree | 720f4ee1a0b8c91d5ec5585ccc4c96b8d8457050 | |
parent | d9fc570a092965715874c63b89663f1ad3b95da1 (diff) | |
download | avb-7cee66d62de50bbd3d8564feb2ed96c549ef2150.tar.gz |
libavb_rs: add property descriptor support
Adds the PropertyDescriptor struct which provides a safe API to access
property descriptors.
Bug: b/290110273
Test: atest libavb_rs_test libavb_rs_uuid_test libavb_rs_unittest libavb_rs_uuid_unittest
Change-Id: I45ae9f1c2a79a509ab15b6f6551322c91bb2716c
-rw-r--r-- | rust/Android.bp | 13 | ||||
-rw-r--r-- | rust/src/descriptor/hash.rs | 51 | ||||
-rw-r--r-- | rust/src/descriptor/mod.rs | 10 | ||||
-rw-r--r-- | rust/src/descriptor/property.rs | 112 | ||||
-rw-r--r-- | rust/src/descriptor/util.rs | 110 | ||||
-rw-r--r-- | rust/src/lib.rs | 1 | ||||
-rw-r--r-- | rust/tests/verify_tests.rs | 33 |
7 files changed, 298 insertions, 32 deletions
diff --git a/rust/Android.bp b/rust/Android.bp index 7ef3df3..cafc675 100644 --- a/rust/Android.bp +++ b/rust/Android.bp @@ -264,6 +264,7 @@ rust_defaults { ":avbrs_test_vbmeta", ":avbrs_test_vbmeta_2_parts", ":avbrs_test_vbmeta_persistent_digest", + ":avbrs_test_vbmeta_with_property", ], rustlibs: ["libhex"], test_suites: ["general-tests"], @@ -364,6 +365,18 @@ 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)", +} + // Combined test image + signed vbmeta footer for "test_part". avb_add_hash_footer { name: "avbrs_test_image_with_vbmeta_footer", diff --git a/rust/src/descriptor/hash.rs b/rust/src/descriptor/hash.rs index edc49ec..3ad52f0 100644 --- a/rust/src/descriptor/hash.rs +++ b/rust/src/descriptor/hash.rs @@ -14,10 +14,12 @@ //! Hash descriptors. -use super::{util::split_slice, DescriptorError, DescriptorResult}; +use super::{ + util::{parse_descriptor, split_slice, ValidateAndByteswap, ValidationFunc}, + 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; @@ -50,6 +52,12 @@ pub struct HashDescriptor<'a> { 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. /// @@ -60,41 +68,26 @@ impl<'a> HashDescriptor<'a> { /// 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. + // 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(&raw_header.hash_algorithm) + let hash_algorithm = CStr::from_bytes_until_nul(&descriptor.raw_header.hash_algorithm) .map_err(|_| DescriptorError::InvalidValue)? .to_str() .map_err(|_| DescriptorError::InvalidUtf8)?; Ok(Self { - image_size: header.image_size, + image_size: descriptor.header.image_size, hash_algorithm, - flags: HashDescriptorFlags(header.flags), + flags: HashDescriptorFlags(descriptor.header.flags), partition_name: from_utf8(partition_name).map_err(|_| DescriptorError::InvalidUtf8)?, salt, digest, diff --git a/rust/src/descriptor/mod.rs b/rust/src/descriptor/mod.rs index fdc4956..ba741dd 100644 --- a/rust/src/descriptor/mod.rs +++ b/rust/src/descriptor/mod.rs @@ -20,6 +20,7 @@ extern crate alloc; mod hash; +mod property; mod util; use crate::VbmetaData; @@ -30,13 +31,14 @@ use avb_bindgen::{ use core::{ffi::c_void, mem::size_of, slice}; pub use hash::{HashDescriptor, HashDescriptorFlags}; +pub use property::PropertyDescriptor; /// A single descriptor. // TODO(b/290110273): add support for full descriptor contents. #[derive(Debug)] pub enum Descriptor<'a> { /// Wraps `AvbPropertyDescriptor`. - Property(&'a [u8]), + Property(PropertyDescriptor<'a>), /// Wraps `AvbHashtreeDescriptor`. Hashtree(&'a [u8]), /// Wraps `AvbHashDescriptor`. @@ -60,6 +62,8 @@ pub enum DescriptorError { InvalidSize, /// A field that was supposed to be valid UTF-8 was not. InvalidUtf8, + /// Descriptor contents don't match what we expect. + InvalidContents, } /// `Result` type for `DescriptorError` errors. @@ -103,7 +107,9 @@ impl<'a> Descriptor<'a> { 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(contents)), + Ok(AvbDescriptorTag::AVB_DESCRIPTOR_TAG_PROPERTY) => { + Ok(Descriptor::Property(PropertyDescriptor::new(contents)?)) + } Ok(AvbDescriptorTag::AVB_DESCRIPTOR_TAG_HASHTREE) => Ok(Descriptor::Hashtree(contents)), Ok(AvbDescriptorTag::AVB_DESCRIPTOR_TAG_HASH) => { Ok(Descriptor::Hash(HashDescriptor::new(contents)?)) diff --git a/rust/src/descriptor/property.rs b/rust/src/descriptor/property.rs new file mode 100644 index 0000000..62c3be0 --- /dev/null +++ b/rust/src/descriptor/property.rs @@ -0,0 +1,112 @@ +// 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).map_err(|_| DescriptorError::InvalidUtf8)?, + value, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::mem::size_of; + + /// A valid property descriptor in raw big-endian format. + const TEST_PROPERTY_DESCRIPTOR: &[u8] = &[ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0F, 0x74, 0x65, 0x73, 0x74, 0x5F, 0x70, 0x72, 0x6F, 0x70, 0x5F, 0x6B, 0x65, 0x79, + 0x00, 0x74, 0x65, 0x73, 0x74, 0x5F, 0x70, 0x72, 0x6F, 0x70, 0x5F, 0x76, 0x61, 0x6C, 0x75, + 0x65, 0x00, 0x00, 0x00, + ]; + + #[test] + fn new_property_descriptor_success() { + let descriptor = PropertyDescriptor::new(TEST_PROPERTY_DESCRIPTOR); + assert!(descriptor.is_ok()); + } + + #[test] + fn new_property_descriptor_too_short_header_fails() { + let bad_header_size = size_of::<AvbPropertyDescriptor>() - 1; + assert_eq!( + PropertyDescriptor::new(&TEST_PROPERTY_DESCRIPTOR[..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_PROPERTY_DESCRIPTOR.len() - 3; + assert_eq!( + PropertyDescriptor::new(&TEST_PROPERTY_DESCRIPTOR[..bad_contents_size]).unwrap_err(), + DescriptorError::InvalidSize + ); + } +} diff --git a/rust/src/descriptor/util.rs b/rust/src/descriptor/util.rs index ada80fc..e4e23fd 100644 --- a/rust/src/descriptor/util.rs +++ b/rust/src/descriptor/util.rs @@ -15,6 +15,7 @@ //! Descriptor utilities. use super::{DescriptorError, DescriptorResult}; +use zerocopy::{FromBytes, FromZeroes, Ref}; /// Splits `size` bytes off the front of `data`. /// @@ -41,9 +42,73 @@ where } } +/// 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() { @@ -65,4 +130,49 @@ mod tests { 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/lib.rs b/rust/src/lib.rs index 8da12fd..a5419f0 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -33,6 +33,7 @@ mod verify; pub use descriptor::{ Descriptor, DescriptorError, DescriptorResult, HashDescriptor, HashDescriptorFlags, + PropertyDescriptor, }; pub use error::{ IoError, IoResult, SlotVerifyError, SlotVerifyNoDataResult, SlotVerifyResult, diff --git a/rust/tests/verify_tests.rs b/rust/tests/verify_tests.rs index 36a4647..61b0576 100644 --- a/rust/tests/verify_tests.rs +++ b/rust/tests/verify_tests.rs @@ -17,7 +17,7 @@ use crate::test_ops::TestOps; use avb::{ slot_verify, Descriptor, HashDescriptor, HashDescriptorFlags, HashtreeErrorMode, IoError, - SlotVerifyData, SlotVerifyError, SlotVerifyFlags, SlotVerifyResult, + PropertyDescriptor, SlotVerifyData, SlotVerifyError, SlotVerifyFlags, SlotVerifyResult, }; use hex::decode; use std::{ffi::CString, fs}; @@ -35,6 +35,7 @@ const TEST_IMAGE_HASH_ALGO: &str = "sha256"; // Default value, we don't explicit 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_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"; @@ -44,6 +45,8 @@ const TEST_PARTITION_SLOT_C_NAME: &str = "test_part_c"; const TEST_PARTITION_2_NAME: &str = "test_part_2"; const TEST_PARTITION_PERSISTENT_DIGEST_NAME: &str = "test_part_persistent_digest"; const TEST_VBMETA_ROLLBACK_LOCATION: usize = 0; // Default value, we don't explicitly set this. +const TEST_PROPERTY_KEY: &str = "test_prop_key"; +const TEST_PROPERTY_VALUE: &[u8] = b"test_prop_value"; /// Initializes a `TestOps` object such that verification will succeed on `TEST_PARTITION_NAME`. fn test_ops_one_image_one_vbmeta() -> TestOps { @@ -654,3 +657,31 @@ fn verify_hash_descriptor() { } ) } + +#[test] +fn verify_property_descriptor() { + let mut ops = test_ops_one_image_one_vbmeta(); + // Replace vbmeta with the version containing a property descriptor. + ops.add_partition("vbmeta", fs::read(TEST_VBMETA_WITH_PROPERTY_PATH).unwrap()); + + let result = verify_one_image_one_vbmeta(&mut ops); + + let data = result.unwrap(); + let descriptors = &data.vbmeta_data()[0].descriptors().unwrap(); + let descriptor = descriptors + .iter() + .filter_map(|d| match d { + Descriptor::Property(p) => Some(p), + _ => None, + }) + .next() + .unwrap(); + + assert_eq!( + descriptor, + &PropertyDescriptor { + key: TEST_PROPERTY_KEY, + value: TEST_PROPERTY_VALUE + } + ) +} |