Merge changes from topics "derive-microdroid-vendor-dice-node-for-real", "rollback-index" into main

* changes:
  Actually derive microdroid vendor dice node
  libvbmeta: add getter for rollback_index
diff --git a/libs/dice/driver/src/lib.rs b/libs/dice/driver/src/lib.rs
index 79edb51..b5c1f12 100644
--- a/libs/dice/driver/src/lib.rs
+++ b/libs/dice/driver/src/lib.rs
@@ -65,6 +65,7 @@
 
     /// Creates a new dice driver from the given driver_path.
     pub fn new(driver_path: &Path, is_strict_boot: bool) -> Result<Self> {
+        log::info!("Creating DiceDriver backed by {driver_path:?} driver");
         if driver_path.exists() {
             log::info!("Using DICE values from driver");
         } else if is_strict_boot {
@@ -107,6 +108,7 @@
 
     /// Create a new dice driver that reads dice_artifacts from the given file.
     pub fn from_file(file_path: &Path) -> Result<Self> {
+        log::info!("Creating DiceDriver backed by {file_path:?} file");
         let file =
             fs::File::open(file_path).map_err(|error| Error::new(error).context("open file"))?;
         let dice_artifacts = serde_cbor::from_reader(file)
@@ -149,11 +151,18 @@
             &input_values,
         )
         .context("DICE derive from driver")?;
-        if let Self::Real { driver_path, .. } = &self {
-            // Writing to the device wipes the artifacts. The string is ignored by the driver but
-            // included for documentation.
-            fs::write(driver_path, "wipe")
-                .map_err(|err| Error::new(err).context("Wiping driver"))?;
+        match &self {
+            Self::Real { driver_path, .. } => {
+                // Writing to the device wipes the artifacts. The string is ignored by the driver
+                // but included for documentation.
+                fs::write(driver_path, "wipe")
+                    .map_err(|err| Error::new(err).context("Wiping driver"))?;
+            }
+            Self::FromFile { file_path, .. } => {
+                fs::remove_file(file_path)
+                    .map_err(|err| Error::new(err).context("Deleting file"))?;
+            }
+            Self::Fake { .. } => (),
         }
         Ok(next_dice_artifacts)
     }
@@ -176,6 +185,11 @@
 #[cfg(test)]
 mod tests {
     use super::*;
+    use core::ffi::CStr;
+    use diced_open_dice::{
+        hash, retry_bcc_format_config_descriptor, DiceConfigValues, HIDDEN_SIZE,
+    };
+    use std::fs::File;
 
     fn assert_eq_bytes(expected: &[u8], actual: &[u8]) {
         assert_eq!(
@@ -204,4 +218,34 @@
 
         Ok(())
     }
+
+    #[test]
+    fn test_dice_driver_from_file_deletes_file_after_derive() -> Result<()> {
+        let tmp_dir = tempfile::tempdir()?;
+
+        let file_path = tmp_dir.path().join("test-dice-chain.raw");
+
+        {
+            let dice_artifacts = diced_sample_inputs::make_sample_bcc_and_cdis()?;
+            let file = File::create(&file_path)?;
+            serde_cbor::to_writer(file, &dice_artifacts)?;
+        }
+
+        let dice = DiceDriver::from_file(&file_path)?;
+
+        let values = DiceConfigValues {
+            component_name: Some(CStr::from_bytes_with_nul(b"test\0")?),
+            ..Default::default()
+        };
+        let desc = retry_bcc_format_config_descriptor(&values)?;
+        let code_hash = hash(&String::from("test code hash").into_bytes())?;
+        let authority_hash = hash(&String::from("test authority hash").into_bytes())?;
+        let hidden = [0; HIDDEN_SIZE];
+
+        let _ = dice.derive(code_hash, &desc, authority_hash, false, hidden)?;
+
+        assert!(!file_path.exists());
+
+        Ok(())
+    }
 }
diff --git a/libs/vbmeta/Android.bp b/libs/vbmeta/Android.bp
index 4fb6ae4..9a7375d 100644
--- a/libs/vbmeta/Android.bp
+++ b/libs/vbmeta/Android.bp
@@ -35,6 +35,8 @@
         ":avb_testkey_rsa2048",
         ":avb_testkey_rsa4096",
         ":avb_testkey_rsa8192",
+        ":test_microdroid_vendor_image",
+        ":test_microdroid_vendor_image_no_rollback_index",
     ],
     required: ["avbtool"],
     test_suites: ["general-tests"],
diff --git a/libs/vbmeta/src/lib.rs b/libs/vbmeta/src/lib.rs
index 1a40e45..a15f699 100644
--- a/libs/vbmeta/src/lib.rs
+++ b/libs/vbmeta/src/lib.rs
@@ -148,6 +148,11 @@
         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
@@ -283,4 +288,19 @@
     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(())
+    }
 }
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index 169ecae..33d98dc 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -55,6 +55,7 @@
     properties: [
         "deps",
         "dirs",
+        "multilib",
     ],
 }
 
@@ -157,9 +158,11 @@
     // Below are dependencies that are conditionally enabled depending on value of build flags.
     soong_config_variables: {
         release_avf_enable_dice_changes: {
-            deps: [
-                "derive_microdroid_vendor_dice_node",
-            ],
+            multilib: {
+                lib64: {
+                    deps: ["derive_microdroid_vendor_dice_node"],
+                },
+            },
             dirs: [
                 "microdroid_resources",
             ],
diff --git a/microdroid/derive_microdroid_vendor_dice_node/Android.bp b/microdroid/derive_microdroid_vendor_dice_node/Android.bp
index de1bef7..8b79aad 100644
--- a/microdroid/derive_microdroid_vendor_dice_node/Android.bp
+++ b/microdroid/derive_microdroid_vendor_dice_node/Android.bp
@@ -10,9 +10,20 @@
     rustlibs: [
         "libanyhow",
         "libclap",
+        "libcstr",
+        "libdice_driver",
+        "libdiced_open_dice",
+        "libdm_rust",
+        "libserde_cbor",
+        "libvbmeta_rust",
     ],
     bootstrap: true,
     prefer_rlib: true,
+    multilib: {
+        lib32: {
+            enabled: false,
+        },
+    },
 }
 
 rust_binary {
diff --git a/microdroid/derive_microdroid_vendor_dice_node/src/main.rs b/microdroid/derive_microdroid_vendor_dice_node/src/main.rs
index 1d5db0d..c7bc3f5 100644
--- a/microdroid/derive_microdroid_vendor_dice_node/src/main.rs
+++ b/microdroid/derive_microdroid_vendor_dice_node/src/main.rs
@@ -14,9 +14,19 @@
 
 //! Derives microdroid vendor dice node.
 
-use anyhow::Error;
+use anyhow::{bail, Context, Result};
 use clap::Parser;
-use std::path::PathBuf;
+use cstr::cstr;
+use dice_driver::DiceDriver;
+use diced_open_dice::{
+    hash, retry_bcc_format_config_descriptor, DiceConfigValues, OwnedDiceArtifacts, HIDDEN_SIZE,
+};
+use dm::util::blkgetsize64;
+use std::fs::{read_link, File};
+use std::path::{Path, PathBuf};
+use vbmeta::VbMetaImage;
+
+const AVF_STRICT_BOOT: &str = "/proc/device-tree/chosen/avf,strict-boot";
 
 #[derive(Parser)]
 struct Args {
@@ -31,8 +41,74 @@
     output: PathBuf,
 }
 
-fn main() -> Result<(), Error> {
+// TODO(ioffe): move to a library to reuse same code here, in microdroid_manager and in
+// first_stage_init.
+fn is_strict_boot() -> bool {
+    Path::new(AVF_STRICT_BOOT).exists()
+}
+
+fn build_descriptor(vbmeta: &VbMetaImage) -> Result<Vec<u8>> {
+    let values = DiceConfigValues {
+        component_name: Some(cstr!("Microdroid vendor")),
+        security_version: Some(vbmeta.rollback_index()),
+        ..Default::default()
+    };
+    Ok(retry_bcc_format_config_descriptor(&values)?)
+}
+
+// TODO(ioffe): move to libvbmeta.
+fn find_root_digest(vbmeta: &VbMetaImage) -> Result<Option<Vec<u8>>> {
+    for descriptor in vbmeta.descriptors()?.iter() {
+        if let vbmeta::Descriptor::Hashtree(_) = descriptor {
+            return Ok(Some(descriptor.to_hashtree()?.root_digest().to_vec()));
+        }
+    }
+    Ok(None)
+}
+
+fn dice_derivation(dice: DiceDriver, vbmeta: &VbMetaImage) -> Result<OwnedDiceArtifacts> {
+    let authority_hash = if let Some(pubkey) = vbmeta.public_key() {
+        hash(pubkey).context("hash pubkey")?
+    } else {
+        bail!("no public key")
+    };
+    let code_hash = if let Some(root_digest) = find_root_digest(vbmeta)? {
+        hash(root_digest.as_ref()).context("hash root_digest")?
+    } else {
+        bail!("no hashtree")
+    };
+    let desc = build_descriptor(vbmeta).context("build descriptor")?;
+    let hidden = [0; HIDDEN_SIZE];
+    // The microdroid vendor partition doesn't contribute to the debuggability of the VM, and it is
+    // a bit tricky to propagate the info on whether the VM is debuggable to
+    // derive_microdroid_dice_node binary. Given these, we just always set `is_debuggable: false`
+    // for the "Microdroid vendor" dice node. The adjacent dice nodes (pvmfw & payload) provide the
+    // accurate information on whether VM is debuggable.
+    dice.derive(code_hash, &desc, authority_hash, /* debug= */ false, hidden)
+}
+
+fn extract_vbmeta(block_dev: &Path) -> Result<VbMetaImage> {
+    let size = blkgetsize64(block_dev).context("blkgetsize64  failed")?;
+    let file = File::open(block_dev).context("open failed")?;
+    let vbmeta = VbMetaImage::verify_reader_region(file, 0, size)?;
+    Ok(vbmeta)
+}
+
+fn try_main() -> Result<()> {
     let args = Args::parse();
-    eprintln!("{:?} {:?} {:?}", args.dice_driver, args.microdroid_vendor_disk_image, args.output);
+    let dice =
+        DiceDriver::new(&args.dice_driver, is_strict_boot()).context("Failed to load DICE")?;
+    let path = read_link(args.microdroid_vendor_disk_image).context("failed to read symlink")?;
+    let vbmeta = extract_vbmeta(&path).context("failed to extract vbmeta")?;
+    let dice_artifacts = dice_derivation(dice, &vbmeta).context("failed to derive dice chain")?;
+    let file = File::create(&args.output).context("failed to create output")?;
+    serde_cbor::to_writer(file, &dice_artifacts).context("failed to write dice artifacts")?;
     Ok(())
 }
+
+fn main() {
+    if let Err(e) = try_main() {
+        eprintln!("failed with {:?}", e);
+        std::process::exit(1);
+    }
+}
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 2386bd4..7da9ea4 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -43,6 +43,7 @@
 use log::{error, info};
 use microdroid_metadata::{Metadata, PayloadMetadata};
 use microdroid_payload_config::{ApkConfig, OsConfig, Task, TaskType, VmPayloadConfig};
+use nix::mount::{umount2, MntFlags};
 use nix::sys::signal::Signal;
 use payload::load_metadata;
 use rpcbinder::RpcSession;
@@ -86,6 +87,8 @@
 const ENCRYPTEDSTORE_BACKING_DEVICE: &str = "/dev/block/by-name/encryptedstore";
 const ENCRYPTEDSTORE_KEYSIZE: usize = 32;
 
+const DICE_CHAIN_FILE: &str = "/microdroid_resources/dice_chain.raw";
+
 #[derive(thiserror::Error, Debug)]
 enum MicrodroidError {
     #[error("Cannot connect to virtualization service: {0}")]
@@ -301,8 +304,13 @@
     vm_payload_service_fd: OwnedFd,
 ) -> Result<i32> {
     let metadata = load_metadata().context("Failed to load payload metadata")?;
-    let dice = DiceDriver::new(Path::new("/dev/open-dice0"), is_strict_boot())
-        .context("Failed to load DICE")?;
+    let dice = if Path::new(DICE_CHAIN_FILE).exists() {
+        DiceDriver::from_file(Path::new(DICE_CHAIN_FILE))
+            .context("Failed to load DICE from file")?
+    } else {
+        DiceDriver::new(Path::new("/dev/open-dice0"), is_strict_boot())
+            .context("Failed to load DICE from driver")?
+    };
 
     // Microdroid skips checking payload against instance image iff the device supports
     // secretkeeper. In that case Microdroid use VmSecret::V2, which provide protection against
@@ -328,6 +336,10 @@
 
         // Start apexd to activate APEXes. This may allow code within them to run.
         system_properties::write("ctl.start", "apexd-vm")?;
+
+        // Unmounting /microdroid_resources is a defence-in-depth effort to ensure that payload
+        // can't get hold of dice chain stored there.
+        umount2("/microdroid_resources", MntFlags::MNT_DETACH)?;
     }
 
     // Run encryptedstore binary to prepare the storage
diff --git a/tests/vendor_images/Android.bp b/tests/vendor_images/Android.bp
index 26dbc01..66f0219 100644
--- a/tests/vendor_images/Android.bp
+++ b/tests/vendor_images/Android.bp
@@ -15,6 +15,16 @@
     file_contexts: ":microdroid_vendor_file_contexts.gen",
     use_avb: true,
     avb_private_key: ":vendor_sign_key",
+    rollback_index: 5,
+}
+
+android_filesystem {
+    name: "test_microdroid_vendor_image_no_rollback_index",
+    partition_name: "microdroid-vendor",
+    type: "ext4",
+    file_contexts: ":microdroid_vendor_file_contexts.gen",
+    use_avb: true,
+    avb_private_key: ":vendor_sign_key",
 }
 
 android_filesystem {