// 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, AvbFooter, AvbVBMetaImageHeader, AvbVBMetaVerifyResult, }; use std::fs::File; use std::io::{self, Read, Seek, SeekFrom}; use std::mem::{size_of, transmute, MaybeUninit}; use std::path::Path; use std::ptr::null_mut; 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, } /// 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>(path: P) -> Result { 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( mut image: R, offset: u64, size: u64, ) -> Result { // 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 (vbmeta_offset, vbmeta_size) = match read_avb_footer(&mut image) { Ok(footer) => { if footer.vbmeta_offset > size || footer.vbmeta_size > size - footer.vbmeta_offset { return Err(VbMetaImageParseError::InvalidFooter.into()); } (footer.vbmeta_offset, footer.vbmeta_size) } Err(VbMetaImageParseError::InvalidFooter) => (0, size), Err(e) => { return Err(e.into()); } }; 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::() 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 as u32 { return None; } let begin = size_of::() + 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 hash of the verified data in the VBMeta image from the authentication block. If the /// image was not signed, there might not be a hash and, if there is, it's not known to be /// correct. pub fn hash(&self) -> Option<&[u8]> { if self.header.algorithm_type == AvbAlgorithmType::AVB_ALGORITHM_TYPE_NONE as u32 { return None; } let begin = size_of::() + self.header.hash_offset as usize; let end = begin + self.header.hash_size as usize; Some(&self.data[begin..end]) } /// Get the descriptors of the VBMeta image. pub fn descriptors(&self) -> Result, VbMetaImageParseError> { Descriptors::from_image(&self.data) } /// Returns the rollback_index of the VBMeta image. pub fn rollback_index(&self) -> u64 { self.header.rollback_index } /// 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()) }; 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) } } } /// Read the AVB footer, if present, given a reader that's positioned at the end of the image. fn read_avb_footer(image: &mut R) -> Result { image.seek(SeekFrom::Current(-(size_of::() as i64)))?; let mut raw_footer = [0u8; size_of::()]; image.read_exact(&mut raw_footer)?; // SAFETY: the slice is the same size as the struct which only contains simple data types. let mut footer = unsafe { transmute::<[u8; size_of::()], AvbFooter>(raw_footer) }; // SAFETY: the function updates the struct in-place. if unsafe { avb_footer_validate_and_byteswap(&footer, &mut footer) } { Ok(footer) } else { Err(VbMetaImageParseError::InvalidFooter) } } #[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 unsigned_image_does_not_have_public_key() -> 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 signed_image_has_valid_vbmeta(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<()> { signed_image_has_valid_vbmeta("SHA256_RSA2048", "data/testkey_rsa2048.pem") } #[test] fn test_rsa4096_signed_image() -> Result<()> { signed_image_has_valid_vbmeta("SHA256_RSA4096", "data/testkey_rsa4096.pem") } #[test] fn test_rsa8192_signed_image() -> Result<()> { signed_image_has_valid_vbmeta("SHA256_RSA8192", "data/testkey_rsa8192.pem") } #[test] fn test_rollback_index() -> Result<()> { let vbmeta = VbMetaImage::verify_path("test_microdroid_vendor_image.img")?; assert_eq!(5, vbmeta.rollback_index()); Ok(()) } #[test] fn test_rollback_index_default_zero() -> Result<()> { let vbmeta = VbMetaImage::verify_path("test_microdroid_vendor_image_no_rollback_index.img")?; assert_eq!(0, vbmeta.rollback_index()); Ok(()) } }