Extract library for VBMeta image handling
The APEX handling library included VBMeta image verification and parsing
but split this out to its own library so that it can be used by other
components too. The new library hides the libavb FFI to provide a safe
interface.
The library is not complete, it only knows about hashtree descriptors
and its root digest, but other aspects can be exposed as there is need
for them.
Test: atest libvbmeta_rust.test
Test: atest libapexutil_rust.test
Bug: 234564414
Change-Id: Ie176b816f63d2ff7f75deab6c07e1f9bb2e54594
diff --git a/libs/vbmeta/src/lib.rs b/libs/vbmeta/src/lib.rs
new file mode 100644
index 0000000..2d3463c
--- /dev/null
+++ b/libs/vbmeta/src/lib.rs
@@ -0,0 +1,292 @@
+// Copyright 2022, 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.
+
+//! A library to verify and parse VBMeta images.
+
+mod descriptor;
+
+use avb_bindgen::{
+ avb_footer_validate_and_byteswap, avb_vbmeta_image_header_to_host_byte_order,
+ avb_vbmeta_image_verify, AvbAlgorithmType_AVB_ALGORITHM_TYPE_NONE, AvbFooter,
+ AvbVBMetaImageHeader, AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH,
+ AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER,
+ AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_OK,
+ AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_OK_NOT_SIGNED,
+ AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_SIGNATURE_MISMATCH,
+ AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_UNSUPPORTED_VERSION,
+};
+use std::fs::File;
+use std::io::{self, Read, Seek, SeekFrom};
+use std::mem::{size_of, MaybeUninit};
+use std::os::raw::c_uint;
+use std::path::Path;
+use std::ptr::null_mut;
+use std::slice;
+use thiserror::Error;
+
+pub use crate::descriptor::{Descriptor, Descriptors};
+
+/// Errors from parsing a VBMeta image.
+#[derive(Debug, Error)]
+pub enum VbMetaImageParseError {
+ /// There was an IO error.
+ #[error("IO error")]
+ Io(#[from] io::Error),
+ /// The image footer was invalid.
+ #[error("Invalid footer")]
+ InvalidFooter,
+ /// The image header was invalid.
+ #[error("Invalid header")]
+ InvalidHeader,
+ /// The image version is not supported.
+ #[error("Unsupported version")]
+ UnsupportedVersion,
+ /// There was an invalid descriptor in the image.
+ #[error("Invalid descriptor ")]
+ InvalidDescriptor,
+}
+
+/// Errors from verifying a VBMeta image.
+#[derive(Debug, Error)]
+pub enum VbMetaImageVerificationError {
+ /// There was an error parsing the VBMeta image.
+ #[error("Cannot parse VBMeta image")]
+ ParseError(#[from] VbMetaImageParseError),
+ /// The VBMeta image hash did not validate.
+ #[error("Hash mismatch")]
+ HashMismatch,
+ /// The VBMeta image signature did not validate.
+ #[error("Signature mismatch")]
+ SignatureMismatch,
+ /// An unexpected libavb error code was returned.
+ #[error("Unknown libavb error: {0}")]
+ UnknownLibavbError(c_uint),
+}
+
+/// A VBMeta Image.
+pub struct VbMetaImage {
+ header: AvbVBMetaImageHeader,
+ data: Box<[u8]>,
+}
+
+impl VbMetaImage {
+ /// Load and verify a VBMeta image from the given path.
+ pub fn verify_path<P: AsRef<Path>>(path: P) -> Result<Self, VbMetaImageVerificationError> {
+ let file = File::open(path).map_err(VbMetaImageParseError::Io)?;
+ let size = file.metadata().map_err(VbMetaImageParseError::Io)?.len();
+ Self::verify_reader_region(file, 0, size)
+ }
+
+ /// Load and verify a VBMeta image from a region within a reader.
+ pub fn verify_reader_region<R: Read + Seek>(
+ mut image: R,
+ offset: u64,
+ size: u64,
+ ) -> Result<Self, VbMetaImageVerificationError> {
+ // Check for a footer in the image or assume it's an entire VBMeta image.
+ image.seek(SeekFrom::Start(offset + size)).map_err(VbMetaImageParseError::Io)?;
+ let footer = read_avb_footer(&mut image).map_err(VbMetaImageParseError::Io)?;
+ let (vbmeta_offset, vbmeta_size) = if let Some(footer) = footer {
+ if footer.vbmeta_offset > size || footer.vbmeta_size > size - footer.vbmeta_offset {
+ return Err(VbMetaImageParseError::InvalidFooter.into());
+ }
+ (footer.vbmeta_offset, footer.vbmeta_size)
+ } else {
+ (0, size)
+ };
+ image.seek(SeekFrom::Start(offset + vbmeta_offset)).map_err(VbMetaImageParseError::Io)?;
+ // Verify the image before examining it to check the size.
+ let mut data = vec![0u8; vbmeta_size as usize];
+ image.read_exact(&mut data).map_err(VbMetaImageParseError::Io)?;
+ verify_vbmeta_image(&data)?;
+ // SAFETY: the image has been verified so we know there is a valid header at the start.
+ let header = unsafe {
+ let mut header = MaybeUninit::uninit();
+ let src = data.as_ptr() as *const _ as *const AvbVBMetaImageHeader;
+ avb_vbmeta_image_header_to_host_byte_order(src, header.as_mut_ptr());
+ header.assume_init()
+ };
+ // Calculate the true size of the verified image data.
+ let vbmeta_size = (size_of::<AvbVBMetaImageHeader>() as u64)
+ + header.authentication_data_block_size
+ + header.auxiliary_data_block_size;
+ data.truncate(vbmeta_size as usize);
+ Ok(Self { header, data: data.into_boxed_slice() })
+ }
+
+ /// Get the public key that verified the VBMeta image. If the image was not signed, there
+ /// is no such public key.
+ pub fn public_key(&self) -> Option<&[u8]> {
+ if self.header.algorithm_type == AvbAlgorithmType_AVB_ALGORITHM_TYPE_NONE {
+ return None;
+ }
+ let begin = size_of::<AvbVBMetaImageHeader>()
+ + self.header.authentication_data_block_size as usize
+ + self.header.public_key_offset as usize;
+ let end = begin + self.header.public_key_size as usize;
+ Some(&self.data[begin..end])
+ }
+
+ /// Get the descriptors of the VBMeta image.
+ pub fn descriptors(&self) -> Result<Descriptors<'_>, VbMetaImageParseError> {
+ Descriptors::from_image(&self.data)
+ }
+
+ /// Get the raw VBMeta image.
+ pub fn data(&self) -> &[u8] {
+ &self.data
+ }
+}
+
+/// Verify the data as a VBMeta image, translating errors that arise.
+fn verify_vbmeta_image(data: &[u8]) -> Result<(), VbMetaImageVerificationError> {
+ // SAFETY: the function only reads from the provided data and the NULL pointers disable the
+ // output arguments.
+ let res = unsafe { avb_vbmeta_image_verify(data.as_ptr(), data.len(), null_mut(), null_mut()) };
+ #[allow(non_upper_case_globals)]
+ match res {
+ AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_OK
+ | AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_OK_NOT_SIGNED => Ok(()),
+ AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER => {
+ Err(VbMetaImageParseError::InvalidHeader.into())
+ }
+ AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_UNSUPPORTED_VERSION => {
+ Err(VbMetaImageParseError::UnsupportedVersion.into())
+ }
+ AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH => {
+ Err(VbMetaImageVerificationError::HashMismatch)
+ }
+ AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_SIGNATURE_MISMATCH => {
+ Err(VbMetaImageVerificationError::SignatureMismatch)
+ }
+ err => Err(VbMetaImageVerificationError::UnknownLibavbError(err)),
+ }
+}
+
+/// Read the AVB footer, if present, given a reader that's positioned at the end of the image.
+fn read_avb_footer<R: Read + Seek>(image: &mut R) -> io::Result<Option<AvbFooter>> {
+ image.seek(SeekFrom::Current(-(size_of::<AvbFooter>() as i64)))?;
+ // SAFETY: the slice is the same size as the struct which only contains simple data types.
+ let mut footer = unsafe {
+ let mut footer = MaybeUninit::<AvbFooter>::uninit();
+ let footer_slice =
+ slice::from_raw_parts_mut(&mut footer as *mut _ as *mut u8, size_of::<AvbFooter>());
+ image.read_exact(footer_slice)?;
+ footer.assume_init()
+ };
+ // Check the magic matches "AVBf" to suppress misleading logs from libavb.
+ const AVB_FOOTER_MAGIC: [u8; 4] = [0x41, 0x56, 0x42, 0x66];
+ if footer.magic != AVB_FOOTER_MAGIC {
+ return Ok(None);
+ }
+ // SAFETY: the function updates the struct in-place.
+ if unsafe { avb_footer_validate_and_byteswap(&footer, &mut footer) } {
+ Ok(Some(footer))
+ } else {
+ Ok(None)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use anyhow::{Context, Result};
+ use std::fs::{self, OpenOptions};
+ use std::os::unix::fs::FileExt;
+ use std::process::Command;
+ use tempfile::TempDir;
+
+ #[test]
+ fn test_unsigned_image() -> Result<()> {
+ let test_dir = TempDir::new().unwrap();
+ let test_file = test_dir.path().join("test.img");
+ let mut cmd = Command::new("./avbtool");
+ cmd.args([
+ "make_vbmeta_image",
+ "--output",
+ test_file.to_str().unwrap(),
+ "--algorithm",
+ "NONE",
+ ]);
+ let status = cmd.status().context("make_vbmeta_image")?;
+ assert!(status.success());
+ let vbmeta = VbMetaImage::verify_path(test_file).context("verify_path")?;
+ assert!(vbmeta.public_key().is_none());
+ Ok(())
+ }
+
+ fn test_signed_image(algorithm: &str, key: &str) -> Result<()> {
+ let test_dir = TempDir::new().unwrap();
+ let test_file = test_dir.path().join("test.img");
+ let mut cmd = Command::new("./avbtool");
+ cmd.args([
+ "make_vbmeta_image",
+ "--output",
+ test_file.to_str().unwrap(),
+ "--algorithm",
+ algorithm,
+ "--key",
+ key,
+ ]);
+ let status = cmd.status().context("make_vbmeta_image")?;
+ assert!(status.success());
+ let vbmeta = VbMetaImage::verify_path(&test_file).context("verify_path")?;
+
+ // The image should contain the public part of the key pair.
+ let pubkey = vbmeta.public_key().unwrap();
+ let test_pubkey_file = test_dir.path().join("test.pubkey");
+ let mut cmd = Command::new("./avbtool");
+ cmd.args([
+ "extract_public_key",
+ "--key",
+ key,
+ "--output",
+ test_pubkey_file.to_str().unwrap(),
+ ]);
+ let status = cmd.status().context("extract_public_key")?;
+ assert!(status.success());
+ assert_eq!(pubkey, fs::read(test_pubkey_file).context("read public key")?);
+
+ // Flip a byte to make verification fail.
+ let file = OpenOptions::new()
+ .read(true)
+ .write(true)
+ .open(&test_file)
+ .context("open image to flip byte")?;
+ let mut data = [0; 1];
+ file.read_exact_at(&mut data, 81).context("read byte from image to flip")?;
+ data[0] = !data[0];
+ file.write_all_at(&data, 81).context("write flipped byte to image")?;
+ assert!(matches!(
+ VbMetaImage::verify_path(test_file),
+ Err(VbMetaImageVerificationError::HashMismatch)
+ ));
+ Ok(())
+ }
+
+ #[test]
+ fn test_rsa2048_signed_image() -> Result<()> {
+ test_signed_image("SHA256_RSA2048", "tests/data/testkey_rsa2048.pem")
+ }
+
+ #[test]
+ fn test_rsa4096_signed_image() -> Result<()> {
+ test_signed_image("SHA256_RSA4096", "tests/data/testkey_rsa4096.pem")
+ }
+
+ #[test]
+ fn test_rsa8192_signed_image() -> Result<()> {
+ test_signed_image("SHA256_RSA8192", "tests/data/testkey_rsa8192.pem")
+ }
+}