Merge "[service-vm] Identitfy service VM in pvmfw with avb property"
diff --git a/pvmfw/avb/Android.bp b/pvmfw/avb/Android.bp
index 90f3971..5353a21 100644
--- a/pvmfw/avb/Android.bp
+++ b/pvmfw/avb/Android.bp
@@ -42,6 +42,10 @@
":test_image_with_non_initrd_hashdesc",
":test_image_with_initrd_and_non_initrd_desc",
":test_image_with_prop_desc",
+ ":test_image_with_service_vm_prop",
+ ":test_image_with_unknown_vm_type_prop",
+ ":test_image_with_multiple_props",
+ ":test_image_with_duplicated_capability",
":unsigned_test_image",
],
prefer_rlib: true,
@@ -127,6 +131,66 @@
}
avb_add_hash_footer {
+ name: "test_image_with_service_vm_prop",
+ src: ":unsigned_test_image",
+ partition_name: "boot",
+ private_key: ":pvmfw_sign_key",
+ salt: "2131",
+ props: [
+ {
+ name: "com.android.virt.cap",
+ value: "remote_attest",
+ },
+ ],
+}
+
+avb_add_hash_footer {
+ name: "test_image_with_unknown_vm_type_prop",
+ src: ":unsigned_test_image",
+ partition_name: "boot",
+ private_key: ":pvmfw_sign_key",
+ salt: "2132",
+ props: [
+ {
+ name: "com.android.virt.cap",
+ value: "foo",
+ },
+ ],
+}
+
+avb_add_hash_footer {
+ name: "test_image_with_multiple_props",
+ src: ":unsigned_test_image",
+ partition_name: "boot",
+ private_key: ":pvmfw_sign_key",
+ salt: "2133",
+ props: [
+ {
+ name: "com.android.virt.cap",
+ value: "remote_attest",
+ },
+ {
+ name: "another_vm_type",
+ value: "foo_vm",
+ },
+ ],
+}
+
+avb_add_hash_footer {
+ name: "test_image_with_duplicated_capability",
+ src: ":unsigned_test_image",
+ partition_name: "boot",
+ private_key: ":pvmfw_sign_key",
+ salt: "2134",
+ props: [
+ {
+ name: "com.android.virt.cap",
+ value: "remote_attest|remote_attest|remote_attest",
+ },
+ ],
+}
+
+avb_add_hash_footer {
name: "test_image_with_one_hashdesc",
src: ":unsigned_test_image",
partition_name: "boot",
diff --git a/pvmfw/avb/src/descriptor/collection.rs b/pvmfw/avb/src/descriptor/collection.rs
index e5ddda9..c6698c0 100644
--- a/pvmfw/avb/src/descriptor/collection.rs
+++ b/pvmfw/avb/src/descriptor/collection.rs
@@ -16,6 +16,7 @@
use super::common::get_valid_descriptor;
use super::hash::HashDescriptor;
+use super::property::PropertyDescriptor;
use crate::error::{AvbIOError, AvbSlotVerifyError};
use crate::partition::PartitionName;
use crate::utils::{self, is_not_null, to_usize, usize_checked_add};
@@ -31,6 +32,7 @@
#[derive(Default)]
pub(crate) struct Descriptors<'a> {
hash_descriptors: ArrayVec<[HashDescriptor<'a>; PartitionName::NUM_OF_KNOWN_PARTITIONS]>,
+ prop_descriptor: Option<PropertyDescriptor<'a>>,
}
impl<'a> Descriptors<'a> {
@@ -79,9 +81,18 @@
.ok_or(AvbSlotVerifyError::InvalidMetadata)
}
+ pub(crate) fn has_property_descriptor(&self) -> bool {
+ self.prop_descriptor.is_some()
+ }
+
+ pub(crate) fn find_property_value(&self, key: &[u8]) -> Option<&[u8]> {
+ self.prop_descriptor.as_ref().filter(|desc| desc.key == key).map(|desc| desc.value)
+ }
+
fn push(&mut self, descriptor: Descriptor<'a>) -> utils::Result<()> {
match descriptor {
Descriptor::Hash(d) => self.push_hash_descriptor(d),
+ Descriptor::Property(d) => self.push_property_descriptor(d),
}
}
@@ -92,6 +103,17 @@
self.hash_descriptors.push(descriptor);
Ok(())
}
+
+ fn push_property_descriptor(
+ &mut self,
+ descriptor: PropertyDescriptor<'a>,
+ ) -> utils::Result<()> {
+ if self.prop_descriptor.is_some() {
+ return Err(AvbIOError::Io);
+ }
+ self.prop_descriptor.replace(descriptor);
+ Ok(())
+ }
}
/// # Safety
@@ -139,6 +161,7 @@
enum Descriptor<'a> {
Hash(HashDescriptor<'a>),
+ Property(PropertyDescriptor<'a>),
}
impl<'a> Descriptor<'a> {
@@ -165,6 +188,13 @@
let descriptor = unsafe { HashDescriptor::from_descriptor_ptr(descriptor, data)? };
Ok(Self::Hash(descriptor))
}
+ Ok(AvbDescriptorTag::AVB_DESCRIPTOR_TAG_PROPERTY) => {
+ // SAFETY: It is safe because the caller ensures that `descriptor` is a non-null
+ // pointer pointing to a valid struct.
+ let descriptor =
+ unsafe { PropertyDescriptor::from_descriptor_ptr(descriptor, data)? };
+ Ok(Self::Property(descriptor))
+ }
_ => Err(AvbIOError::NoSuchValue),
}
}
diff --git a/pvmfw/avb/src/descriptor/mod.rs b/pvmfw/avb/src/descriptor/mod.rs
index 384498d..a3db0e5 100644
--- a/pvmfw/avb/src/descriptor/mod.rs
+++ b/pvmfw/avb/src/descriptor/mod.rs
@@ -17,6 +17,7 @@
mod collection;
mod common;
mod hash;
+mod property;
pub(crate) use collection::Descriptors;
pub use hash::Digest;
diff --git a/pvmfw/avb/src/descriptor/property.rs b/pvmfw/avb/src/descriptor/property.rs
new file mode 100644
index 0000000..589f2b2
--- /dev/null
+++ b/pvmfw/avb/src/descriptor/property.rs
@@ -0,0 +1,92 @@
+// 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.
+
+//! Structs and functions relating to the property descriptor.
+
+use super::common::get_valid_descriptor;
+use crate::error::AvbIOError;
+use crate::utils::{self, to_usize, usize_checked_add};
+use avb_bindgen::{
+ avb_property_descriptor_validate_and_byteswap, AvbDescriptor, AvbPropertyDescriptor,
+};
+use core::mem::size_of;
+
+pub(super) struct PropertyDescriptor<'a> {
+ pub(super) key: &'a [u8],
+ pub(super) value: &'a [u8],
+}
+
+impl<'a> PropertyDescriptor<'a> {
+ /// # Safety
+ ///
+ /// Behavior is undefined if any of the following conditions are violated:
+ /// * The `descriptor` pointer must be non-null and point to a valid `AvbDescriptor`.
+ pub(super) unsafe fn from_descriptor_ptr(
+ descriptor: *const AvbDescriptor,
+ data: &'a [u8],
+ ) -> utils::Result<Self> {
+ // SAFETY: It is safe as the raw pointer `descriptor` is non-null and points to
+ // a valid `AvbDescriptor`.
+ let h = unsafe { PropertyDescriptorHeader::from_descriptor_ptr(descriptor)? };
+ let key = Self::get_valid_slice(data, h.key_start(), h.key_end()?)?;
+ let value = Self::get_valid_slice(data, h.value_start()?, h.value_end()?)?;
+ Ok(Self { key, value })
+ }
+
+ fn get_valid_slice(data: &[u8], start: usize, end: usize) -> utils::Result<&[u8]> {
+ const NUL_BYTE: u8 = b'\0';
+
+ match data.get(end) {
+ Some(&NUL_BYTE) => data.get(start..end).ok_or(AvbIOError::RangeOutsidePartition),
+ _ => Err(AvbIOError::NoSuchValue),
+ }
+ }
+}
+
+struct PropertyDescriptorHeader(AvbPropertyDescriptor);
+
+impl PropertyDescriptorHeader {
+ /// # Safety
+ ///
+ /// Behavior is undefined if any of the following conditions are violated:
+ /// * The `descriptor` pointer must be non-null and point to a valid `AvbDescriptor`.
+ unsafe fn from_descriptor_ptr(descriptor: *const AvbDescriptor) -> utils::Result<Self> {
+ // SAFETY: It is safe as the raw pointer `descriptor` is non-null and points to
+ // a valid `AvbDescriptor`.
+ unsafe {
+ get_valid_descriptor(
+ descriptor as *const AvbPropertyDescriptor,
+ avb_property_descriptor_validate_and_byteswap,
+ )
+ .map(Self)
+ }
+ }
+
+ fn key_start(&self) -> usize {
+ size_of::<AvbPropertyDescriptor>()
+ }
+
+ fn key_end(&self) -> utils::Result<usize> {
+ usize_checked_add(self.key_start(), to_usize(self.0.key_num_bytes)?)
+ }
+
+ fn value_start(&self) -> utils::Result<usize> {
+ // There is a NUL byte between key and value.
+ usize_checked_add(self.key_end()?, 1)
+ }
+
+ fn value_end(&self) -> utils::Result<usize> {
+ usize_checked_add(self.value_start()?, to_usize(self.0.value_num_bytes)?)
+ }
+}
diff --git a/pvmfw/avb/src/error.rs b/pvmfw/avb/src/error.rs
index 1add368..5e3822f 100644
--- a/pvmfw/avb/src/error.rs
+++ b/pvmfw/avb/src/error.rs
@@ -41,6 +41,8 @@
Verification,
/// VBMeta has invalid descriptors.
InvalidDescriptors(AvbIOError),
+ /// Unknown vbmeta property
+ UnknownVbmetaProperty,
}
impl fmt::Display for AvbSlotVerifyError {
@@ -60,6 +62,7 @@
Self::InvalidDescriptors(e) => {
write!(f, "VBMeta has invalid descriptors. Error: {:?}", e)
}
+ Self::UnknownVbmetaProperty => write!(f, "Unknown vbmeta property"),
}
}
}
diff --git a/pvmfw/avb/src/lib.rs b/pvmfw/avb/src/lib.rs
index 9ad7fc3..fdab990 100644
--- a/pvmfw/avb/src/lib.rs
+++ b/pvmfw/avb/src/lib.rs
@@ -16,6 +16,8 @@
#![cfg_attr(not(test), no_std)]
+extern crate alloc;
+
mod descriptor;
mod error;
mod ops;
@@ -25,4 +27,4 @@
pub use descriptor::Digest;
pub use error::{AvbIOError, AvbSlotVerifyError};
-pub use verify::{verify_payload, DebugLevel, VerifiedBootData};
+pub use verify::{verify_payload, Capability, DebugLevel, VerifiedBootData};
diff --git a/pvmfw/avb/src/verify.rs b/pvmfw/avb/src/verify.rs
index ded0766..c5ed8cc 100644
--- a/pvmfw/avb/src/verify.rs
+++ b/pvmfw/avb/src/verify.rs
@@ -18,6 +18,8 @@
use crate::error::AvbSlotVerifyError;
use crate::ops::{Ops, Payload};
use crate::partition::PartitionName;
+use alloc::vec;
+use alloc::vec::Vec;
use avb_bindgen::{AvbPartitionData, AvbVBMetaData};
use core::ffi::c_char;
@@ -32,6 +34,8 @@
pub initrd_digest: Option<Digest>,
/// Trusted public key.
pub public_key: &'a [u8],
+ /// VM capabilities.
+ pub capabilities: Vec<Capability>,
}
/// This enum corresponds to the `DebugLevel` in `VirtualMachineConfig`.
@@ -43,6 +47,35 @@
Full,
}
+/// VM Capability.
+#[derive(Debug, PartialEq, Eq)]
+pub enum Capability {
+ /// Remote attestation.
+ RemoteAttest,
+}
+
+impl Capability {
+ const KEY: &[u8] = b"com.android.virt.cap";
+ const REMOTE_ATTEST: &[u8] = b"remote_attest";
+ const SEPARATOR: u8 = b'|';
+
+ fn get_capabilities(property_value: &[u8]) -> Result<Vec<Self>, AvbSlotVerifyError> {
+ let mut res = Vec::new();
+
+ for v in property_value.split(|b| *b == Self::SEPARATOR) {
+ let cap = match v {
+ Self::REMOTE_ATTEST => Self::RemoteAttest,
+ _ => return Err(AvbSlotVerifyError::UnknownVbmetaProperty),
+ };
+ if res.contains(&cap) {
+ return Err(AvbSlotVerifyError::InvalidMetadata);
+ }
+ res.push(cap);
+ }
+ Ok(res)
+ }
+}
+
fn verify_only_one_vbmeta_exists(
vbmeta_images: &[AvbVBMetaData],
) -> Result<(), AvbSlotVerifyError> {
@@ -95,6 +128,20 @@
}
}
+/// Verifies that the vbmeta contains at most one property descriptor and it indicates the
+/// vm type is service VM.
+fn verify_property_and_get_capabilities(
+ descriptors: &Descriptors,
+) -> Result<Vec<Capability>, AvbSlotVerifyError> {
+ if !descriptors.has_property_descriptor() {
+ return Ok(vec![]);
+ }
+ descriptors
+ .find_property_value(Capability::KEY)
+ .ok_or(AvbSlotVerifyError::UnknownVbmetaProperty)
+ .and_then(Capability::get_capabilities)
+}
+
/// Verifies the payload (signed kernel + initrd) against the trusted public key.
pub fn verify_payload<'a>(
kernel: &[u8],
@@ -113,6 +160,7 @@
// which is returned by `avb_slot_verify()` when the verification succeeds. It is
// guaranteed by libavb to be non-null and to point to a valid VBMeta structure.
let descriptors = unsafe { Descriptors::from_vbmeta(vbmeta_image)? };
+ let capabilities = verify_property_and_get_capabilities(&descriptors)?;
let kernel_descriptor = descriptors.find_hash_descriptor(PartitionName::Kernel)?;
if initrd.is_none() {
@@ -122,6 +170,7 @@
kernel_digest: *kernel_descriptor.digest,
initrd_digest: None,
public_key: trusted_public_key,
+ capabilities,
});
}
@@ -146,5 +195,6 @@
kernel_digest: *kernel_descriptor.digest,
initrd_digest: Some(*initrd_descriptor.digest),
public_key: trusted_public_key,
+ capabilities,
})
}
diff --git a/pvmfw/avb/tests/api_test.rs b/pvmfw/avb/tests/api_test.rs
index a4a33f7..aa9ed36 100644
--- a/pvmfw/avb/tests/api_test.rs
+++ b/pvmfw/avb/tests/api_test.rs
@@ -18,12 +18,18 @@
use anyhow::{anyhow, Result};
use avb_bindgen::{AvbFooter, AvbVBMetaImageHeader};
-use pvmfw_avb::{verify_payload, AvbIOError, AvbSlotVerifyError, DebugLevel, VerifiedBootData};
+use pvmfw_avb::{
+ verify_payload, AvbIOError, AvbSlotVerifyError, Capability, DebugLevel, VerifiedBootData,
+};
use std::{fs, mem::size_of, ptr};
use utils::*;
const TEST_IMG_WITH_ONE_HASHDESC_PATH: &str = "test_image_with_one_hashdesc.img";
const TEST_IMG_WITH_PROP_DESC_PATH: &str = "test_image_with_prop_desc.img";
+const TEST_IMG_WITH_SERVICE_VM_PROP_PATH: &str = "test_image_with_service_vm_prop.img";
+const TEST_IMG_WITH_UNKNOWN_VM_TYPE_PROP_PATH: &str = "test_image_with_unknown_vm_type_prop.img";
+const TEST_IMG_WITH_MULTIPLE_PROPS_PATH: &str = "test_image_with_multiple_props.img";
+const TEST_IMG_WITH_DUPLICATED_CAP_PATH: &str = "test_image_with_duplicated_capability.img";
const TEST_IMG_WITH_NON_INITRD_HASHDESC_PATH: &str = "test_image_with_non_initrd_hashdesc.img";
const TEST_IMG_WITH_INITRD_AND_NON_INITRD_DESC_PATH: &str =
"test_image_with_initrd_and_non_initrd_desc.img";
@@ -67,6 +73,7 @@
kernel_digest,
initrd_digest: None,
public_key: &public_key,
+ capabilities: vec![],
};
assert_eq!(expected_boot_data, verified_boot_data);
@@ -94,12 +101,65 @@
}
#[test]
+fn payload_expecting_no_initrd_passes_verification_with_service_vm_prop() -> Result<()> {
+ let public_key = load_trusted_public_key()?;
+ let verified_boot_data = verify_payload(
+ &fs::read(TEST_IMG_WITH_SERVICE_VM_PROP_PATH)?,
+ /*initrd=*/ None,
+ &public_key,
+ )
+ .map_err(|e| anyhow!("Verification failed. Error: {}", e))?;
+
+ let kernel_digest = hash(&[&hex::decode("2131")?, &fs::read(UNSIGNED_TEST_IMG_PATH)?]);
+ let expected_boot_data = VerifiedBootData {
+ debug_level: DebugLevel::None,
+ kernel_digest,
+ initrd_digest: None,
+ public_key: &public_key,
+ capabilities: vec![Capability::RemoteAttest],
+ };
+ assert_eq!(expected_boot_data, verified_boot_data);
+
+ Ok(())
+}
+
+#[test]
+fn payload_with_unknown_vm_type_fails_verification_with_no_initrd() -> Result<()> {
+ assert_payload_verification_fails(
+ &fs::read(TEST_IMG_WITH_UNKNOWN_VM_TYPE_PROP_PATH)?,
+ /*initrd=*/ None,
+ &load_trusted_public_key()?,
+ AvbSlotVerifyError::UnknownVbmetaProperty,
+ )
+}
+
+#[test]
+fn payload_with_multiple_props_fails_verification_with_no_initrd() -> Result<()> {
+ assert_payload_verification_fails(
+ &fs::read(TEST_IMG_WITH_MULTIPLE_PROPS_PATH)?,
+ /*initrd=*/ None,
+ &load_trusted_public_key()?,
+ AvbSlotVerifyError::InvalidDescriptors(AvbIOError::Io),
+ )
+}
+
+#[test]
+fn payload_with_duplicated_capability_fails_verification_with_no_initrd() -> Result<()> {
+ assert_payload_verification_fails(
+ &fs::read(TEST_IMG_WITH_DUPLICATED_CAP_PATH)?,
+ /*initrd=*/ None,
+ &load_trusted_public_key()?,
+ AvbSlotVerifyError::InvalidMetadata,
+ )
+}
+
+#[test]
fn payload_with_prop_descriptor_fails_verification_with_no_initrd() -> Result<()> {
assert_payload_verification_fails(
&fs::read(TEST_IMG_WITH_PROP_DESC_PATH)?,
/*initrd=*/ None,
&load_trusted_public_key()?,
- AvbSlotVerifyError::InvalidDescriptors(AvbIOError::NoSuchValue),
+ AvbSlotVerifyError::UnknownVbmetaProperty,
)
}
diff --git a/pvmfw/avb/tests/utils.rs b/pvmfw/avb/tests/utils.rs
index 6713846..79fdfff 100644
--- a/pvmfw/avb/tests/utils.rs
+++ b/pvmfw/avb/tests/utils.rs
@@ -116,6 +116,7 @@
kernel_digest,
initrd_digest,
public_key: &public_key,
+ capabilities: vec![],
};
assert_eq!(expected_boot_data, verified_boot_data);
diff --git a/pvmfw/src/main.rs b/pvmfw/src/main.rs
index fdc9407..9afd816 100644
--- a/pvmfw/src/main.rs
+++ b/pvmfw/src/main.rs
@@ -53,6 +53,7 @@
use libfdt::Fdt;
use log::{debug, error, info, trace, warn};
use pvmfw_avb::verify_payload;
+use pvmfw_avb::Capability;
use pvmfw_avb::DebugLevel;
use pvmfw_embedded_key::PUBLIC_KEY;
@@ -105,6 +106,10 @@
RebootReason::PayloadVerificationError
})?;
+ if verified_boot_data.capabilities.contains(&Capability::RemoteAttest) {
+ info!("Service VM capable of remote attestation detected");
+ }
+
let next_bcc = heap::aligned_boxed_slice(NEXT_BCC_SIZE, GUEST_PAGE_SIZE).ok_or_else(|| {
error!("Failed to allocate the next-stage BCC");
RebootReason::InternalError
diff --git a/rialto/Android.bp b/rialto/Android.bp
index eb49395..59f8ba2 100644
--- a/rialto/Android.bp
+++ b/rialto/Android.bp
@@ -64,6 +64,12 @@
partition_name: "boot",
private_key: ":rialto_sign_key",
salt: rialto_salt,
+ props: [
+ {
+ name: "com.android.virt.cap",
+ value: "remote_attest",
+ },
+ ],
enabled: false,
arch: {
arm64: {