Support amending vendor hashtree digest when virtmgr constructs DTBO based on host DT

Bug: 325555638
Test: atest MicrodroidTests#configuringVendorDiskImageRequiresCustomPermission
Test: atest MicrodroidTests#bootsWithVendorPartition
Test: atest MicrodroidTests#bootsWithCustomVendorPartitionForNonPvm
Test: atest MicrodroidTests#bootFailsWithCustomVendorPartitionForPvm
Test: atest MicrodroidTests#creationFailsWithUnsignedVendorPartition
Test: atest virtualizationmanager_device_test

Change-Id: I3a008c4bbb34d7673d843c18c0b479212b925065
diff --git a/virtualizationmanager/Android.bp b/virtualizationmanager/Android.bp
index bb6ccb7..d8f8209 100644
--- a/virtualizationmanager/Android.bp
+++ b/virtualizationmanager/Android.bp
@@ -42,6 +42,7 @@
         "libcommand_fds",
         "libdisk",
         "libglob",
+        "libhex",
         "libhypervisor_props",
         "liblazy_static",
         "liblibc",
@@ -62,6 +63,7 @@
         "libshared_child",
         "libstatslog_virtualization_rust",
         "libtombstoned_client_rust",
+        "libvbmeta_rust",
         "libvm_control",
         "libvmconfig",
         "libzip",
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index 88700ec..c32960b 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -87,6 +87,7 @@
 use std::os::unix::raw::pid_t;
 use std::path::{Path, PathBuf};
 use std::sync::{Arc, Mutex, Weak};
+use vbmeta::VbMetaImage;
 use vmconfig::VmConfig;
 use vsock::VsockStream;
 use zip::ZipArchive;
@@ -381,6 +382,23 @@
             None
         };
 
+        let vendor_hashtree_digest = extract_vendor_hashtree_digest(config)
+            .context("Failed to extract vendor hashtree digest")
+            .or_service_specific_exception(-1)?;
+
+        let trusted_props = if let Some(ref vendor_hashtree_digest) = vendor_hashtree_digest {
+            info!(
+                "Passing vendor hashtree digest to pvmfw. This will be rejected if it doesn't \
+                match the trusted digest in the pvmfw config, causing the VM to fail to start."
+            );
+            vec![(
+                cstr!("vendor_hashtree_descriptor_root_digest"),
+                vendor_hashtree_digest.as_slice(),
+            )]
+        } else {
+            vec![]
+        };
+
         let untrusted_props = if cfg!(llpvm_changes) {
             // TODO(b/291213394): Replace this with a per-VM instance Id.
             let instance_id = b"sixtyfourbyteslonghardcoded_indeed_sixtyfourbyteslonghardcoded_h";
@@ -389,17 +407,23 @@
             vec![]
         };
 
-        let device_tree_overlay = if host_ref_dt.is_some() || !untrusted_props.is_empty() {
-            let dt_output = temporary_directory.join(VM_DT_OVERLAY_PATH);
-            let mut data = [0_u8; VM_DT_OVERLAY_MAX_SIZE];
-            let fdt = create_device_tree_overlay(&mut data, host_ref_dt, &untrusted_props)
+        let device_tree_overlay =
+            if host_ref_dt.is_some() || !untrusted_props.is_empty() || !trusted_props.is_empty() {
+                let dt_output = temporary_directory.join(VM_DT_OVERLAY_PATH);
+                let mut data = [0_u8; VM_DT_OVERLAY_MAX_SIZE];
+                let fdt = create_device_tree_overlay(
+                    &mut data,
+                    host_ref_dt,
+                    &untrusted_props,
+                    &trusted_props,
+                )
                 .map_err(|e| anyhow!("Failed to create DT overlay, {e:?}"))
                 .or_service_specific_exception(-1)?;
-            fs::write(&dt_output, fdt.as_slice()).or_service_specific_exception(-1)?;
-            Some(File::open(dt_output).or_service_specific_exception(-1)?)
-        } else {
-            None
-        };
+                fs::write(&dt_output, fdt.as_slice()).or_service_specific_exception(-1)?;
+                Some(File::open(dt_output).or_service_specific_exception(-1)?)
+            } else {
+                None
+            };
 
         let debug_level = match config {
             VirtualMachineConfig::AppConfig(config) => config.debugLevel,
@@ -601,6 +625,32 @@
     }
 }
 
+fn extract_vendor_hashtree_digest(config: &VirtualMachineConfig) -> Result<Option<Vec<u8>>> {
+    let VirtualMachineConfig::AppConfig(config) = config else {
+        return Ok(None);
+    };
+    let Some(custom_config) = &config.customConfig else {
+        return Ok(None);
+    };
+    let Some(file) = custom_config.vendorImage.as_ref() else {
+        return Ok(None);
+    };
+
+    let file = clone_file(file)?;
+    let size =
+        file.metadata().context("Failed to get metadata from microdroid vendor image")?.len();
+    let vbmeta = VbMetaImage::verify_reader_region(&file, 0, size)
+        .context("Failed to get vbmeta from microdroid-vendor.img")?;
+
+    for descriptor in vbmeta.descriptors()?.iter() {
+        if let vbmeta::Descriptor::Hashtree(_) = descriptor {
+            let root_digest = hex::encode(descriptor.to_hashtree()?.root_digest());
+            return Ok(Some(root_digest.as_bytes().to_vec()));
+        }
+    }
+    Err(anyhow!("No hashtree digest is extracted from microdroid vendor image"))
+}
+
 fn write_zero_filler(zero_filler_path: &Path) -> Result<()> {
     let file = OpenOptions::new()
         .create_new(true)
diff --git a/virtualizationmanager/src/dt_overlay.rs b/virtualizationmanager/src/dt_overlay.rs
index 71d3a26..b39ba3a 100644
--- a/virtualizationmanager/src/dt_overlay.rs
+++ b/virtualizationmanager/src/dt_overlay.rs
@@ -31,15 +31,18 @@
 /// * `dt_path` - (Optional) Path to (proc style) device tree to be included in the overlay.
 /// * `untrusted_props` - Include a property in /avf/untrusted node. This node is used to specify
 ///   host provided properties such as `instance-id`.
+/// * `trusted_props` - Include a property in /avf node. This overwrites nodes included with
+///   `dt_path`. In pVM, pvmfw will reject if it doesn't match the value in pvmfw config.
 ///
-/// Example: with `create_device_tree_overlay(_, _, [("instance-id", _),])`
+/// Example: with `create_device_tree_overlay(_, _, [("instance-id", _),], [("digest", _),])`
 /// ```
 ///   {
 ///     fragment@0 {
 ///         target-path = "/";
 ///         __overlay__ {
 ///             avf {
-///                 untrusted { instance-id = [0x01 0x23 .. ] }
+///                 digest = [ 0xaa 0xbb .. ]
+///                 untrusted { instance-id = [ 0x01 0x23 .. ] }
 ///               }
 ///             };
 ///         };
@@ -50,36 +53,54 @@
     buffer: &'a mut [u8],
     dt_path: Option<&'a Path>,
     untrusted_props: &[(&'a CStr, &'a [u8])],
+    trusted_props: &[(&'a CStr, &'a [u8])],
 ) -> Result<&'a mut Fdt> {
-    if dt_path.is_none() && untrusted_props.is_empty() {
+    if dt_path.is_none() && untrusted_props.is_empty() && trusted_props.is_empty() {
         return Err(anyhow!("Expected at least one device tree addition"));
     }
 
     let fdt =
         Fdt::create_empty_tree(buffer).map_err(|e| anyhow!("Failed to create empty Fdt: {e:?}"))?;
-    let root = fdt.root_mut().map_err(|e| anyhow!("Failed to get root: {e:?}"))?;
-    let mut node =
-        root.add_subnode(cstr!("fragment@0")).map_err(|e| anyhow!("Failed to fragment: {e:?}"))?;
-    node.setprop(cstr!("target-path"), b"/\0")
-        .map_err(|e| anyhow!("Failed to set target-path: {e:?}"))?;
-    let node = node
+    let root = fdt.root_mut().map_err(|e| anyhow!("Failed to get root node: {e:?}"))?;
+    let mut fragment = root
+        .add_subnode(cstr!("fragment@0"))
+        .map_err(|e| anyhow!("Failed to add fragment node: {e:?}"))?;
+    fragment
+        .setprop(cstr!("target-path"), b"/\0")
+        .map_err(|e| anyhow!("Failed to set target-path property: {e:?}"))?;
+    let overlay = fragment
         .add_subnode(cstr!("__overlay__"))
-        .map_err(|e| anyhow!("Failed to __overlay__ node: {e:?}"))?;
+        .map_err(|e| anyhow!("Failed to add __overlay__ node: {e:?}"))?;
+    let avf =
+        overlay.add_subnode(AVF_NODE_NAME).map_err(|e| anyhow!("Failed to add avf node: {e:?}"))?;
 
     if !untrusted_props.is_empty() {
-        let mut node = node
-            .add_subnode(AVF_NODE_NAME)
-            .map_err(|e| anyhow!("Failed to add avf node: {e:?}"))?
+        let mut untrusted = avf
             .add_subnode(UNTRUSTED_NODE_NAME)
-            .map_err(|e| anyhow!("Failed to add /avf/untrusted node: {e:?}"))?;
+            .map_err(|e| anyhow!("Failed to add untrusted node: {e:?}"))?;
         for (name, value) in untrusted_props {
-            node.setprop(name, value).map_err(|e| anyhow!("Failed to set property: {e:?}"))?;
+            untrusted
+                .setprop(name, value)
+                .map_err(|e| anyhow!("Failed to set untrusted property: {e:?}"))?;
         }
     }
 
+    // Read dt_path from host DT and overlay onto fdt.
     if let Some(path) = dt_path {
         fdt.overlay_onto(cstr!("/fragment@0/__overlay__"), path)?;
     }
+
+    if !trusted_props.is_empty() {
+        let mut avf = fdt
+            .node_mut(cstr!("/fragment@0/__overlay__/avf"))
+            .map_err(|e| anyhow!("Failed to search avf node: {e:?}"))?
+            .ok_or(anyhow!("Failed to get avf node"))?;
+        for (name, value) in trusted_props {
+            avf.setprop(name, value)
+                .map_err(|e| anyhow!("Failed to set trusted property: {e:?}"))?;
+        }
+    }
+
     fdt.pack().map_err(|e| anyhow!("Failed to pack DT overlay, {e:?}"))?;
 
     Ok(fdt)
@@ -92,7 +113,7 @@
     #[test]
     fn empty_overlays_not_allowed() {
         let mut buffer = vec![0_u8; VM_DT_OVERLAY_MAX_SIZE];
-        let res = create_device_tree_overlay(&mut buffer, None, &[]);
+        let res = create_device_tree_overlay(&mut buffer, None, &[], &[]);
         assert!(res.is_err());
     }
 
@@ -102,7 +123,8 @@
         let prop_name = cstr!("XOXO");
         let prop_val_input = b"OXOX";
         let fdt =
-            create_device_tree_overlay(&mut buffer, None, &[(prop_name, prop_val_input)]).unwrap();
+            create_device_tree_overlay(&mut buffer, None, &[(prop_name, prop_val_input)], &[])
+                .unwrap();
 
         let prop_value_dt = fdt
             .node(cstr!("/fragment@0/__overlay__/avf/untrusted"))
@@ -113,4 +135,23 @@
             .expect("Prop not found!");
         assert_eq!(prop_value_dt, prop_val_input, "Unexpected property value");
     }
+
+    #[test]
+    fn trusted_prop_test() {
+        let mut buffer = vec![0_u8; VM_DT_OVERLAY_MAX_SIZE];
+        let prop_name = cstr!("XOXOXO");
+        let prop_val_input = b"OXOXOX";
+        let fdt =
+            create_device_tree_overlay(&mut buffer, None, &[], &[(prop_name, prop_val_input)])
+                .unwrap();
+
+        let prop_value_dt = fdt
+            .node(cstr!("/fragment@0/__overlay__/avf"))
+            .unwrap()
+            .expect("/avf node doesn't exist")
+            .getprop(prop_name)
+            .unwrap()
+            .expect("Prop not found!");
+        assert_eq!(prop_value_dt, prop_val_input, "Unexpected property value");
+    }
 }