aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Pursell <dpursell@google.com>2024-01-13 01:47:56 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2024-01-13 01:47:56 +0000
commit3d2e86dce23f58565d5c7272c4b81230515dbf42 (patch)
tree1b85d48a870612502934f4dfea8aa416e0872e20
parent82fbb05de31a07f087c8cf9b9402b372d1817326 (diff)
parentd9fc570a092965715874c63b89663f1ad3b95da1 (diff)
downloadavb-3d2e86dce23f58565d5c7272c4b81230515dbf42.tar.gz
libavb_rs: add hash descriptor support am: d9fc570a09
Original change: https://android-review.googlesource.com/c/platform/external/avb/+/2857728 Change-Id: Iaeef32277ae245ae996d773d69f5e55b03ab17f7 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-rw-r--r--rust/Android.bp103
-rw-r--r--rust/src/descriptor/hash.rs154
-rw-r--r--rust/src/descriptor/mod.rs15
-rw-r--r--rust/src/descriptor/util.rs68
-rw-r--r--rust/src/lib.rs4
-rw-r--r--rust/tests/verify_tests.rs36
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()
+ }
+ )
+}