Merge "Sync condition of microdroid_vbmeta with microdroid_super" into main
diff --git a/libs/bssl/src/err.rs b/libs/bssl/src/err.rs
index 60bab98..a53ac8c 100644
--- a/libs/bssl/src/err.rs
+++ b/libs/bssl/src/err.rs
@@ -14,27 +14,90 @@
 
 //! Wrappers of the error handling functions in BoringSSL err.h.
 
+use alloc::string::{String, ToString};
 use bssl_avf_error::{CipherError, EcError, EcdsaError, GlobalError, ReasonCode};
-use bssl_sys::{self, ERR_get_error, ERR_GET_LIB_RUST, ERR_GET_REASON_RUST};
+use bssl_sys::{
+    self, ERR_get_error_line, ERR_lib_error_string, ERR_reason_error_string, ERR_GET_LIB_RUST,
+    ERR_GET_REASON_RUST,
+};
+use core::ffi::{c_char, CStr};
+use core::ptr;
+use log::{error, info};
 
 const NO_ERROR_REASON_CODE: i32 = 0;
 
-/// Returns the reason code for the least recent error and removes that
-/// error from the error queue.
-pub(crate) fn get_error_reason_code() -> ReasonCode {
-    let packed_error = get_packed_error();
+/// Processes the error queue till it is empty, logs the information for all the errors in
+/// the queue from the least recent to the most recent, and returns the reason code for the
+/// most recent error.
+pub(crate) fn process_error_queue() -> ReasonCode {
+    let mut reason_code = ReasonCode::NoError;
+    loop {
+        let code = process_least_recent_error();
+        if code == ReasonCode::NoError {
+            break;
+        }
+        reason_code = code;
+    }
+    reason_code
+}
+
+/// Removes the least recent error in the error queue and logs the error information.
+///
+/// Returns the reason code for the least recent error.
+fn process_least_recent_error() -> ReasonCode {
+    let mut file = ptr::null();
+    let mut line = 0;
+    // SAFETY: This function only reads the error queue and writes to the given
+    // pointers. It doesn't retain any references to the pointers.
+    let packed_error = unsafe { ERR_get_error_line(&mut file, &mut line) };
     let reason = get_reason(packed_error);
+    if reason == NO_ERROR_REASON_CODE {
+        info!("No error in the BoringSSL error queue");
+        return ReasonCode::NoError;
+    }
+
+    // SAFETY: Any non-null result is expected to point to a global const C string.
+    let file = unsafe { cstr_to_string(file, "<unknown file>") };
+    error!(
+        "BoringSSL error: {}:{}: lib = {}, reason = {}",
+        file,
+        line,
+        lib_error_string(packed_error),
+        reason_error_string(packed_error),
+    );
+
     let lib = get_lib(packed_error);
     map_to_reason_code(reason, lib)
 }
 
-/// Returns the packed error code for the least recent error and removes that
-/// error from the error queue.
+fn lib_error_string(packed_error: u32) -> String {
+    // SAFETY: This function only reads the given error code and returns a
+    // pointer to a static string.
+    let p = unsafe { ERR_lib_error_string(packed_error) };
+    // SAFETY: Any non-null result is expected to point to a global const C string.
+    unsafe { cstr_to_string(p, "<unknown library>") }
+}
+
+fn reason_error_string(packed_error: u32) -> String {
+    // SAFETY: This function only reads the given error code and returns a
+    // pointer to a static string.
+    let p = unsafe { ERR_reason_error_string(packed_error) };
+    // SAFETY: Any non-null result is expected to point to a global const C string.
+    unsafe { cstr_to_string(p, "<unknown reason>") }
+}
+
+/// Converts a C string pointer to a Rust string.
 ///
-/// Returns 0 if there are no errors in the queue.
-fn get_packed_error() -> u32 {
-    // SAFETY: This function only reads the error queue.
-    unsafe { ERR_get_error() }
+/// # Safety
+///
+/// The caller needs to ensure that the pointer is null or points to a valid C string.
+unsafe fn cstr_to_string(p: *const c_char, default: &str) -> String {
+    if p.is_null() {
+        return default.to_string();
+    }
+    // Safety: Safe given the requirements of this function.
+    let s = unsafe { CStr::from_ptr(p) };
+    s.to_str().unwrap_or(default).to_string()
 }
 
 fn get_reason(packed_error: u32) -> i32 {
diff --git a/libs/bssl/src/util.rs b/libs/bssl/src/util.rs
index 880c85b..ddb6c6b 100644
--- a/libs/bssl/src/util.rs
+++ b/libs/bssl/src/util.rs
@@ -14,14 +14,14 @@
 
 //! Utility functions.
 
-use crate::err::get_error_reason_code;
+use crate::err::process_error_queue;
 use bssl_avf_error::{ApiName, Error, Result};
 use log::error;
 
 pub(crate) fn check_int_result(ret: i32, api_name: ApiName) -> Result<()> {
     match ret {
         1 => Ok(()),
-        0 => Err(Error::CallFailed(api_name, get_error_reason_code())),
+        0 => Err(Error::CallFailed(api_name, process_error_queue())),
         _ => {
             error!(
                 "Received a return value ({}) other than 0 or 1 from the BoringSSL API: {:?}",
@@ -33,5 +33,5 @@
 }
 
 pub(crate) fn to_call_failed_error(api_name: ApiName) -> Error {
-    Error::CallFailed(api_name, get_error_reason_code())
+    Error::CallFailed(api_name, process_error_queue())
 }
diff --git a/libs/cborutil/src/lib.rs b/libs/cborutil/src/lib.rs
index 6e834f1..4d308c1 100644
--- a/libs/cborutil/src/lib.rs
+++ b/libs/cborutil/src/lib.rs
@@ -21,7 +21,7 @@
 use alloc::string::String;
 use alloc::vec::Vec;
 use ciborium::value::{Integer, Value};
-use coset::{CoseError, CoseKey, Label, Result};
+use coset::{CborSerializable, CoseError, CoseKey, Label, Result};
 use log::error;
 use serde::{de::DeserializeOwned, Serialize};
 
@@ -43,6 +43,11 @@
     }
 }
 
+/// Parses the given CBOR-encoded byte slice as a value array.
+pub fn parse_value_array(data: &[u8], context: &'static str) -> Result<Vec<Value>> {
+    value_to_array(Value::from_slice(data)?, context)
+}
+
 /// Converts the provided value `v` to a value array.
 pub fn value_to_array(v: Value, context: &'static str) -> Result<Vec<Value>> {
     v.into_array().map_err(|e| to_unexpected_item_error(&e, "array", context))
diff --git a/libs/libfdt/src/iterators.rs b/libs/libfdt/src/iterators.rs
index a524655..7406164 100644
--- a/libs/libfdt/src/iterators.rs
+++ b/libs/libfdt/src/iterators.rs
@@ -330,7 +330,7 @@
 }
 
 impl<'a> DescendantsIterator<'a> {
-    pub(crate) fn new(node: &'a FdtNode) -> Self {
+    pub(crate) fn new(node: &FdtNode<'a>) -> Self {
         Self { node: Some((*node, 0)) }
     }
 }
diff --git a/libs/libfdt/src/lib.rs b/libs/libfdt/src/lib.rs
index 7eb08b2..d90f5f0 100644
--- a/libs/libfdt/src/lib.rs
+++ b/libs/libfdt/src/lib.rs
@@ -487,7 +487,7 @@
     }
 
     /// Returns an iterator of descendants
-    pub fn descendants(&'a self) -> DescendantsIterator<'a> {
+    pub fn descendants(&self) -> DescendantsIterator<'a> {
         DescendantsIterator::new(self)
     }
 
@@ -811,6 +811,41 @@
         Ok(next_offset.map(|offset| Self { fdt: self.fdt, offset }))
     }
 
+    fn next_node_offset(&self, depth: usize) -> Result<Option<(c_int, usize)>> {
+        let mut next_depth: c_int = depth.try_into().or(Err(FdtError::BadValue))?;
+        // SAFETY: Accesses (read-only) are constrained to the DT totalsize.
+        let ret = unsafe {
+            libfdt_bindgen::fdt_next_node(self.fdt.as_ptr(), self.offset, &mut next_depth)
+        };
+        let Ok(next_depth) = usize::try_from(next_depth) else {
+            return Ok(None);
+        };
+        Ok(fdt_err_or_option(ret)?.map(|offset| (offset, next_depth)))
+    }
+
+    /// Returns the next node
+    pub fn next_node(self, depth: usize) -> Result<Option<(Self, usize)>> {
+        Ok(self
+            .next_node_offset(depth)?
+            .map(|(offset, next_depth)| (FdtNodeMut { fdt: self.fdt, offset }, next_depth)))
+    }
+
+    /// Deletes this and returns the next node
+    pub fn delete_and_next_node(mut self, depth: usize) -> Result<Option<(Self, usize)>> {
+        // Skip all would-be-removed descendants.
+        let mut iter = self.next_node_offset(depth)?;
+        while let Some((descendant_offset, descendant_depth)) = iter {
+            if descendant_depth <= depth {
+                break;
+            }
+            let descendant = FdtNodeMut { fdt: self.fdt, offset: descendant_offset };
+            iter = descendant.next_node_offset(descendant_depth)?;
+        }
+        // SAFETY: This consumes self, so invalid node wouldn't be used any further
+        unsafe { self.nop_self()? };
+        Ok(iter.map(|(offset, next_depth)| (FdtNodeMut { fdt: self.fdt, offset }, next_depth)))
+    }
+
     fn parent(&'a self) -> Result<FdtNode<'a>> {
         // SAFETY: Accesses (read-only) are constrained to the DT totalsize.
         let ret = unsafe { libfdt_bindgen::fdt_parent_offset(self.fdt.as_ptr(), self.offset) };
diff --git a/libs/libfdt/tests/api_test.rs b/libs/libfdt/tests/api_test.rs
index e68557f..08fb8a5 100644
--- a/libs/libfdt/tests/api_test.rs
+++ b/libs/libfdt/tests/api_test.rs
@@ -399,3 +399,94 @@
 
     assert_eq!(expected_names, subnode_names);
 }
+
+#[test]
+fn node_mut_delete_and_next_node() {
+    let mut data = fs::read(TEST_TREE_PHANDLE_PATH).unwrap();
+    let fdt = Fdt::from_mut_slice(&mut data).unwrap();
+
+    let expected_nodes = vec![
+        (Ok(cstr!("node_b")), 1),
+        (Ok(cstr!("node_c")), 1),
+        (Ok(cstr!("node_z")), 1),
+        (Ok(cstr!("node_za")), 2),
+        (Ok(cstr!("node_zb")), 2),
+        (Ok(cstr!("__symbols__")), 1),
+    ];
+
+    let mut expected_nodes_iter = expected_nodes.iter();
+    let mut iter = fdt.root_mut().unwrap().next_node(0).unwrap();
+    while let Some((node, depth)) = iter {
+        let node_name = node.as_node().name();
+        if node_name == Ok(cstr!("node_a")) || node_name == Ok(cstr!("node_zz")) {
+            iter = node.delete_and_next_node(depth).unwrap();
+        } else {
+            // Note: Checking name here is easier than collecting names and assert_eq!(),
+            //       because we can't keep name references while iterating with FdtNodeMut.
+            let expected_node = expected_nodes_iter.next();
+            assert_eq!(expected_node, Some(&(node_name, depth)));
+            iter = node.next_node(depth).unwrap();
+        }
+    }
+    assert_eq!(None, expected_nodes_iter.next());
+
+    let root = fdt.root().unwrap();
+    let all_descendants: Vec<_> =
+        root.descendants().map(|(node, depth)| (node.name(), depth)).collect();
+    assert_eq!(expected_nodes, all_descendants);
+}
+
+#[test]
+#[ignore] // Borrow checker test. Compilation success is sufficient.
+fn node_name_lifetime() {
+    let data = fs::read(TEST_TREE_PHANDLE_PATH).unwrap();
+    let fdt = Fdt::from_slice(&data).unwrap();
+
+    let name = {
+        let root = fdt.root().unwrap();
+        root.name()
+        // Make root to be dropped
+    };
+    assert_eq!(Ok(cstr!("")), name);
+}
+
+#[test]
+#[ignore] // Borrow checker test. Compilation success is sufficient.
+fn node_subnode_lifetime() {
+    let data = fs::read(TEST_TREE_PHANDLE_PATH).unwrap();
+    let fdt = Fdt::from_slice(&data).unwrap();
+
+    let name = {
+        let node_a = {
+            let root = fdt.root().unwrap();
+            root.subnode(cstr!("node_a")).unwrap()
+            // Make root to be dropped
+        };
+        assert_ne!(None, node_a);
+        node_a.unwrap().name()
+        // Make node_a to be dropped
+    };
+    assert_eq!(Ok(cstr!("node_a")), name);
+}
+
+#[test]
+#[ignore] // Borrow checker test. Compilation success is sufficient.
+fn node_descendants_lifetime() {
+    let data = fs::read(TEST_TREE_PHANDLE_PATH).unwrap();
+    let fdt = Fdt::from_slice(&data).unwrap();
+
+    let first_descendant_name = {
+        let (first_descendant, _) = {
+            let mut descendants_iter = {
+                let root = fdt.root().unwrap();
+                root.descendants()
+                // Make root to be dropped
+            };
+            descendants_iter.next().unwrap()
+            // Make descendants_iter to be dropped
+        };
+        first_descendant.name()
+        // Make first_descendant to be dropped
+    };
+    assert_eq!(Ok(cstr!("node_a")), first_descendant_name);
+}
diff --git a/pvmfw/src/config.rs b/pvmfw/src/config.rs
index 3f78a88..7b548ce 100644
--- a/pvmfw/src/config.rs
+++ b/pvmfw/src/config.rs
@@ -84,6 +84,7 @@
     const MAGIC: u32 = u32::from_ne_bytes(*b"pvmf");
     const VERSION_1_0: Version = Version { major: 1, minor: 0 };
     const VERSION_1_1: Version = Version { major: 1, minor: 1 };
+    const VERSION_1_2: Version = Version { major: 1, minor: 2 };
 
     pub fn total_size(&self) -> usize {
         self.total_size as usize
@@ -105,8 +106,9 @@
         let last_entry = match self.version {
             Self::VERSION_1_0 => Entry::DebugPolicy,
             Self::VERSION_1_1 => Entry::VmDtbo,
+            Self::VERSION_1_2 => Entry::VmBaseDtbo,
             v @ Version { major: 1, .. } => {
-                const LATEST: Version = Header::VERSION_1_1;
+                const LATEST: Version = Header::VERSION_1_2;
                 warn!("Parsing unknown config data version {v} as version {LATEST}");
                 return Ok(Entry::COUNT);
             }
@@ -122,6 +124,7 @@
     Bcc,
     DebugPolicy,
     VmDtbo,
+    VmBaseDtbo,
     #[allow(non_camel_case_types)] // TODO: Use mem::variant_count once stable.
     _VARIANT_COUNT,
 }
@@ -129,7 +132,8 @@
 impl Entry {
     const COUNT: usize = Self::_VARIANT_COUNT as usize;
 
-    const ALL_ENTRIES: [Entry; Self::COUNT] = [Self::Bcc, Self::DebugPolicy, Self::VmDtbo];
+    const ALL_ENTRIES: [Entry; Self::COUNT] =
+        [Self::Bcc, Self::DebugPolicy, Self::VmDtbo, Self::VmBaseDtbo];
 }
 
 #[derive(Default)]
@@ -137,6 +141,7 @@
     pub bcc: &'a mut [u8],
     pub debug_policy: Option<&'a [u8]>,
     pub vm_dtbo: Option<&'a mut [u8]>,
+    pub vm_base_dtbo: Option<&'a [u8]>,
 }
 
 #[repr(packed)]
@@ -285,13 +290,15 @@
                 entries[i] = Some(chunk);
             }
         }
-        let [bcc, debug_policy, vm_dtbo] = entries;
+        let [bcc, debug_policy, vm_dtbo, vm_base_dtbo] = entries;
 
         // The platform BCC has always been required.
         let bcc = bcc.unwrap();
 
         // We have no reason to mutate so drop the `mut`.
         let debug_policy = debug_policy.map(|x| &*x);
-        Entries { bcc, debug_policy, vm_dtbo }
+        let vm_base_dtbo = vm_base_dtbo.map(|x| &*x);
+
+        Entries { bcc, debug_policy, vm_dtbo, vm_base_dtbo }
     }
 }
diff --git a/pvmfw/src/entry.rs b/pvmfw/src/entry.rs
index 8c4396d..8eca7a1 100644
--- a/pvmfw/src/entry.rs
+++ b/pvmfw/src/entry.rs
@@ -88,6 +88,7 @@
         kernel: usize,
         kernel_size: usize,
         vm_dtbo: Option<&mut [u8]>,
+        vm_base_dtbo: Option<&[u8]>,
     ) -> Result<Self, RebootReason> {
         let fdt_size = NonZeroUsize::new(crosvm::FDT_MAX_SIZE).unwrap();
         // TODO - Only map the FDT as read-only, until we modify it right before jump_to_payload()
@@ -101,7 +102,7 @@
         // SAFETY: The tracker validated the range to be in main memory, mapped, and not overlap.
         let fdt = unsafe { slice::from_raw_parts_mut(range.start as *mut u8, range.len()) };
 
-        let info = fdt::sanitize_device_tree(fdt, vm_dtbo)?;
+        let info = fdt::sanitize_device_tree(fdt, vm_dtbo, vm_base_dtbo)?;
         let fdt = libfdt::Fdt::from_mut_slice(fdt).map_err(|e| {
             error!("Failed to load sanitized FDT: {e}");
             RebootReason::InvalidFdt
@@ -227,7 +228,13 @@
         Some(memory::appended_payload_range()),
     ));
 
-    let slices = MemorySlices::new(fdt, payload, payload_size, config_entries.vm_dtbo)?;
+    let slices = MemorySlices::new(
+        fdt,
+        payload,
+        payload_size,
+        config_entries.vm_dtbo,
+        config_entries.vm_base_dtbo,
+    )?;
 
     // This wrapper allows main() to be blissfully ignorant of platform details.
     let next_bcc = crate::main(
diff --git a/pvmfw/src/fdt.rs b/pvmfw/src/fdt.rs
index b53e452..33a5055 100644
--- a/pvmfw/src/fdt.rs
+++ b/pvmfw/src/fdt.rs
@@ -19,6 +19,7 @@
 use crate::helpers::GUEST_PAGE_SIZE;
 use crate::Box;
 use crate::RebootReason;
+use alloc::collections::BTreeMap;
 use alloc::ffi::CString;
 use alloc::vec::Vec;
 use core::cmp::max;
@@ -200,27 +201,47 @@
     Ok(())
 }
 
-fn read_vendor_hashtree_descriptor_root_digest_from(fdt: &Fdt) -> libfdt::Result<Option<Vec<u8>>> {
+/// Read candidate properties' names from DT which could be overlaid
+fn parse_vm_base_dtbo(fdt: &Fdt) -> libfdt::Result<BTreeMap<CString, Vec<u8>>> {
+    let mut property_map = BTreeMap::new();
     if let Some(avf_node) = fdt.node(cstr!("/avf"))? {
-        if let Some(vendor_hashtree_descriptor_root_digest) =
-            avf_node.getprop(cstr!("vendor_hashtree_descriptor_root_digest"))?
-        {
-            return Ok(Some(vendor_hashtree_descriptor_root_digest.to_vec()));
+        for property in avf_node.properties()? {
+            let name = property.name()?;
+            let value = property.value()?;
+            property_map.insert(
+                CString::new(name.to_bytes()).map_err(|_| FdtError::BadValue)?,
+                value.to_vec(),
+            );
         }
     }
-    Ok(None)
+    Ok(property_map)
 }
 
-fn patch_vendor_hashtree_descriptor_root_digest(
-    fdt: &mut Fdt,
-    vendor_hashtree_descriptor_root_digest: &[u8],
+/// Overlay VM base DTBO into VM DT based on the props_info. Property is overlaid in vm_dt only
+/// when it exists both in vm_base_dtbo and props_info. If the values mismatch, it returns error.
+fn apply_vm_base_dtbo(
+    vm_dt: &mut Fdt,
+    vm_base_dtbo: &Fdt,
+    props_info: &BTreeMap<CString, Vec<u8>>,
 ) -> libfdt::Result<()> {
-    let mut root_node = fdt.root_mut()?;
-    let mut avf_node = root_node.add_subnode(cstr!("/avf"))?;
-    avf_node.setprop(
-        cstr!("vendor_hashtree_descriptor_root_digest"),
-        vendor_hashtree_descriptor_root_digest,
-    )?;
+    let mut root_vm_dt = vm_dt.root_mut()?;
+    let mut avf_vm_dt = root_vm_dt.add_subnode(cstr!("avf"))?;
+    // TODO(b/318431677): Validate nodes beyond /fragment@0/__overlay__/avf and use apply_overlay.
+    let avf_vm_base_dtbo =
+        vm_base_dtbo.node(cstr!("/fragment@0/__overlay__/avf"))?.ok_or(FdtError::NotFound)?;
+    for (name, value) in props_info.iter() {
+        if let Some(value_in_vm_base_dtbo) = avf_vm_base_dtbo.getprop(name)? {
+            if value != value_in_vm_base_dtbo {
+                error!(
+                    "Property mismatches while applying overlay VM base DTBO. \
+                    Name:{:?}, Value from host as hex:{:x?}, Value from VM base DTBO as hex:{:x?}",
+                    name, value, value_in_vm_base_dtbo
+                );
+                return Err(FdtError::BadValue);
+            }
+            avf_vm_dt.setprop(name, value_in_vm_base_dtbo)?;
+        }
+    }
     Ok(())
 }
 
@@ -616,7 +637,7 @@
     serial_info: SerialInfo,
     pub swiotlb_info: SwiotlbInfo,
     device_assignment: Option<DeviceAssignmentInfo>,
-    vendor_hashtree_descriptor_root_digest: Option<Vec<u8>>,
+    vm_base_dtbo_props_info: BTreeMap<CString, Vec<u8>>,
 }
 
 impl DeviceTreeInfo {
@@ -630,6 +651,7 @@
 pub fn sanitize_device_tree(
     fdt: &mut [u8],
     vm_dtbo: Option<&mut [u8]>,
+    vm_base_dtbo: Option<&[u8]>,
 ) -> Result<DeviceTreeInfo, RebootReason> {
     let fdt = Fdt::from_mut_slice(fdt).map_err(|e| {
         error!("Failed to load FDT: {e}");
@@ -673,6 +695,18 @@
         }
     }
 
+    if let Some(vm_base_dtbo) = vm_base_dtbo {
+        let vm_base_dtbo = Fdt::from_slice(vm_base_dtbo).map_err(|e| {
+            error!("Failed to load VM base DTBO: {e}");
+            RebootReason::InvalidFdt
+        })?;
+
+        apply_vm_base_dtbo(fdt, vm_base_dtbo, &info.vm_base_dtbo_props_info).map_err(|e| {
+            error!("Failed to apply VM base DTBO: {e}");
+            RebootReason::InvalidFdt
+        })?;
+    }
+
     patch_device_tree(fdt, &info)?;
 
     // TODO(b/317201360): Ensure no overlapping in <reg> among devices
@@ -746,19 +780,10 @@
         None => None,
     };
 
-    // TODO(b/285854379) : A temporary solution lives. This is for enabling
-    // microdroid vendor partition for non-protected VM as well. When passing
-    // DT path containing vendor_hashtree_descriptor_root_digest via fstab, init
-    // stage will check if vendor_hashtree_descriptor_root_digest exists in the
-    // init stage, regardless the protection. Adding this temporary solution
-    // will prevent fatal in init stage for protected VM. However, this data is
-    // not trustable without validating root digest of vendor hashtree
-    // descriptor comes from ABL.
-    let vendor_hashtree_descriptor_root_digest =
-        read_vendor_hashtree_descriptor_root_digest_from(fdt).map_err(|e| {
-            error!("Failed to read vendor_hashtree_descriptor_root_digest from DT: {e}");
-            RebootReason::InvalidFdt
-        })?;
+    let vm_base_dtbo_props_info = parse_vm_base_dtbo(fdt).map_err(|e| {
+        error!("Failed to read names of properties under /avf from DT: {e}");
+        RebootReason::InvalidFdt
+    })?;
 
     Ok(DeviceTreeInfo {
         kernel_range,
@@ -770,7 +795,7 @@
         serial_info,
         swiotlb_info,
         device_assignment,
-        vendor_hashtree_descriptor_root_digest,
+        vm_base_dtbo_props_info,
     })
 }
 
@@ -823,15 +848,6 @@
             RebootReason::InvalidFdt
         })?;
     }
-    if let Some(vendor_hashtree_descriptor_root_digest) =
-        &info.vendor_hashtree_descriptor_root_digest
-    {
-        patch_vendor_hashtree_descriptor_root_digest(fdt, vendor_hashtree_descriptor_root_digest)
-            .map_err(|e| {
-            error!("Failed to patch vendor_hashtree_descriptor_root_digest to DT: {e}");
-            RebootReason::InvalidFdt
-        })?;
-    }
 
     Ok(())
 }
diff --git a/service_vm/requests/src/client_vm.rs b/service_vm/requests/src/client_vm.rs
index 6ebed50..d4474cf 100644
--- a/service_vm/requests/src/client_vm.rs
+++ b/service_vm/requests/src/client_vm.rs
@@ -22,7 +22,7 @@
 use crate::keyblob::decrypt_private_key;
 use alloc::vec::Vec;
 use bssl_avf::{rand_bytes, sha256, Digester, EcKey, PKey};
-use cbor_util::value_to_array;
+use cbor_util::parse_value_array;
 use ciborium::value::Value;
 use core::result;
 use coset::{AsCborValue, CborSerializable, CoseSign, CoseSign1};
@@ -53,10 +53,8 @@
     // Validates the prefix of the Client VM DICE chain in the CSR.
     let service_vm_dice_chain =
         dice_artifacts.bcc().ok_or(RequestProcessingError::MissingDiceChain)?;
-    let service_vm_dice_chain =
-        value_to_array(Value::from_slice(service_vm_dice_chain)?, "service_vm_dice_chain")?;
-    let client_vm_dice_chain =
-        value_to_array(Value::from_slice(&csr.dice_cert_chain)?, "client_vm_dice_chain")?;
+    let service_vm_dice_chain = parse_value_array(service_vm_dice_chain, "service_vm_dice_chain")?;
+    let client_vm_dice_chain = parse_value_array(&csr.dice_cert_chain, "client_vm_dice_chain")?;
     validate_client_vm_dice_chain_prefix_match(&client_vm_dice_chain, &service_vm_dice_chain)?;
     // Validates the signatures in the Client VM DICE chain and extracts the partially decoded
     // DiceChainEntryPayloads.
diff --git a/tests/benchmark/assets/vm_config_gki-android14-6.1.json b/tests/benchmark/assets/vm_config_gki-android14-6.1.json
new file mode 100644
index 0000000..c4fdc6e
--- /dev/null
+++ b/tests/benchmark/assets/vm_config_gki-android14-6.1.json
@@ -0,0 +1,10 @@
+{
+  "os": {
+    "name": "microdroid_gki-android14-6.1"
+  },
+  "task": {
+    "type": "microdroid_launcher",
+    "command": "MicrodroidIdleNativeLib.so"
+  },
+  "export_tombstones": true
+}
diff --git a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
index 5d3ce9e..e31a55d 100644
--- a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
+++ b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
@@ -248,6 +248,16 @@
     }
 
     @Test
+    public void testMicrodroidGkiBootTime()
+            throws VirtualMachineException, InterruptedException, IOException {
+        runBootTimeTest(
+                "test_vm_boot_time",
+                "assets/vm_config_gki-android14-6.1.json",
+                /* reportDetailed */ false,
+                (builder) -> builder.setCpuTopology(CPU_TOPOLOGY_ONE_CPU));
+    }
+
+    @Test
     public void testMicrodroidHostCpuTopologyBootTime()
             throws VirtualMachineException, InterruptedException, IOException {
         runBootTimeTest(
@@ -258,6 +268,16 @@
     }
 
     @Test
+    public void testMicrodroidGkiHostCpuTopologyBootTime()
+            throws VirtualMachineException, InterruptedException, IOException {
+        runBootTimeTest(
+                "test_vm_boot_time_host_topology",
+                "assets/vm_config_gki-android14-6.1.json",
+                /* reportDetailed */ false,
+                (builder) -> builder.setCpuTopology(CPU_TOPOLOGY_MATCH_HOST));
+    }
+
+    @Test
     public void testMicrodroidDebugBootTime()
             throws VirtualMachineException, InterruptedException, IOException {
         runBootTimeTest(
@@ -268,6 +288,16 @@
     }
 
     @Test
+    public void testMicrodroidGkiDebugBootTime()
+            throws VirtualMachineException, InterruptedException, IOException {
+        runBootTimeTest(
+                "test_vm_boot_time_debug",
+                "assets/vm_config_gki-android14-6.1.json",
+                /* reportDetailed */ true,
+                (builder) -> builder);
+    }
+
+    @Test
     public void testMicrodroidDebugBootTime_withVendorPartition() throws Exception {
         assume().withMessage(
                         "Cuttlefish doesn't support device tree under"
diff --git a/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java b/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java
index f5656e2..b176cfc 100644
--- a/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java
+++ b/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java
@@ -45,7 +45,6 @@
 import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.regex.Matcher;
@@ -413,11 +412,16 @@
 
         for (int round = 0; round < ROUND_COUNT; ++round) {
             reInstallApex(REINSTALL_APEX_TIMEOUT_SEC);
-            if (isWithCompos) {
-                compileStagedApex(COMPILE_STAGED_APEX_TIMEOUT_SEC);
+            try {
+                if (isWithCompos) {
+                    compileStagedApex(COMPILE_STAGED_APEX_TIMEOUT_SEC);
+                }
+            } finally {
+                // If compilation fails, we still have a staged APEX, and we need to reboot to
+                // clean that up for further tests.
+                getDevice().nonBlockingReboot();
+                waitForBootCompleted();
             }
-            getDevice().nonBlockingReboot();
-            waitForBootCompleted();
 
             double elapsedSec = getDmesgBootTime();
             bootDmesgTime.add(elapsedSec);
@@ -458,8 +462,9 @@
             try {
                 CommandRunner android = new CommandRunner(getDevice());
 
-                String result = android.run(
-                        COMPOSD_CMD_BIN + " staged-apex-compile");
+                String result =
+                        android.runWithTimeout(
+                                3 * 60 * 1000, COMPOSD_CMD_BIN + " staged-apex-compile");
                 assertWithMessage("Failed to compile staged APEX. Reason: " + result)
                     .that(result).ignoringCase().contains("all ok");
 
diff --git a/tests/hostside/helper/java/com/android/microdroid/test/host/CommandRunner.java b/tests/hostside/helper/java/com/android/microdroid/test/host/CommandRunner.java
index 846531d..242dbde 100644
--- a/tests/hostside/helper/java/com/android/microdroid/test/host/CommandRunner.java
+++ b/tests/hostside/helper/java/com/android/microdroid/test/host/CommandRunner.java
@@ -66,9 +66,7 @@
 
     public String runWithTimeout(long timeoutMillis, String... cmd)
             throws DeviceNotAvailableException {
-        CommandResult result =
-                mDevice.executeShellV2Command(
-                        join(cmd), timeoutMillis, java.util.concurrent.TimeUnit.MILLISECONDS);
+        CommandResult result = runForResultWithTimeout(timeoutMillis, cmd);
         if (result.getStatus() != CommandStatus.SUCCESS) {
             fail(join(cmd) + " has failed: " + result);
         }
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index a54a22a..1fa0976 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -435,7 +435,7 @@
 
     @Test
     @CddTest(requirements = {"9.17/C-2-1", "9.17/C-2-2", "9.17/C-2-6"})
-    public void protectedVmWithImageSignedWithDifferentKeyRunsPvmfw() throws Exception {
+    public void protectedVmWithImageSignedWithDifferentKeyFailsToVerifyPayload() throws Exception {
         // Arrange
         assumeProtectedVm();
         File key = findTestFile("test.com.android.virt.pem");
@@ -452,8 +452,9 @@
         vmInfo.mProcess.waitFor(5L, TimeUnit.SECONDS);
         String consoleLog = getDevice().pullFileContents(CONSOLE_PATH);
         assertWithMessage("pvmfw should start").that(consoleLog).contains("pVM firmware");
-        // TODO(b/256148034): Asserts that pvmfw run fails when this verification is implemented.
-        // Also rename the test.
+        assertWithMessage("pvmfw should fail to verify the payload")
+                .that(consoleLog)
+                .contains("Failed to verify the payload");
         vmInfo.mProcess.destroy();
     }