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: {