[service-vm] Identitfy service VM in pvmfw with avb property

The kernel footer only accepted hash descriptor prior to this change.
With this change, at most one property descriptor is allow to
indicate that this VM is a service VM.

Test: atest libpvmfw_avb.integration_test
Bug: 279557218
Change-Id: Ied476eba2e88be63ab78eae7ed05512a97406ec2
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 2e9dd99..910a782 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
@@ -140,6 +162,7 @@
 
 enum Descriptor<'a> {
     Hash(HashDescriptor<'a>),
+    Property(PropertyDescriptor<'a>),
 }
 
 impl<'a> Descriptor<'a> {
@@ -166,6 +189,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 cf81563..5f5cde8 100644
--- a/rialto/Android.bp
+++ b/rialto/Android.bp
@@ -62,6 +62,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: {