Merge "Add `libcuttlefish_host_config` to static libs"
diff --git a/compos/composd_cmd/Android.bp b/compos/composd_cmd/Android.bp
index 54b0bad..77caad8 100644
--- a/compos/composd_cmd/Android.bp
+++ b/compos/composd_cmd/Android.bp
@@ -2,8 +2,8 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-rust_binary {
-    name: "composd_cmd",
+rust_defaults {
+    name: "composd_cmd_defaults",
     srcs: ["composd_cmd.rs"],
     edition: "2021",
     rustlibs: [
@@ -12,8 +12,14 @@
         "libbinder_rs",
         "libclap",
         "libcompos_common",
+        "libhypervisor_props",
     ],
     prefer_rlib: true,
+}
+
+rust_binary {
+    name: "composd_cmd",
+    defaults: ["composd_cmd_defaults"],
     apex_available: [
         "com.android.compos",
     ],
@@ -21,15 +27,6 @@
 
 rust_test {
     name: "composd_cmd.test",
-    srcs: ["composd_cmd.rs"],
-    edition: "2021",
-    rustlibs: [
-        "android.system.composd-rust",
-        "libanyhow",
-        "libbinder_rs",
-        "libclap",
-        "libcompos_common",
-    ],
-    prefer_rlib: true,
+    defaults: ["composd_cmd_defaults"],
     test_suites: ["general-tests"],
 }
diff --git a/compos/composd_cmd/composd_cmd.rs b/compos/composd_cmd/composd_cmd.rs
index 19c3720..6d096a1 100644
--- a/compos/composd_cmd/composd_cmd.rs
+++ b/compos/composd_cmd/composd_cmd.rs
@@ -128,6 +128,12 @@
         &Strong<dyn ICompilationTaskCallback>,
     ) -> BinderResult<Strong<dyn ICompilationTask>>,
 {
+    if !hypervisor_props::is_any_vm_supported()? {
+        // Give up now, before trying to start composd, or we may end up waiting forever
+        // as it repeatedly starts and then aborts (b/254599807).
+        bail!("Device doesn't support protected or non-protected VMs")
+    }
+
     let service = wait_for_interface::<dyn IIsolatedCompilationService>("android.system.composd")
         .context("Failed to connect to composd service")?;
 
diff --git a/javalib/api/system-current.txt b/javalib/api/system-current.txt
index 3170d43..d9bafa1 100644
--- a/javalib/api/system-current.txt
+++ b/javalib/api/system-current.txt
@@ -85,8 +85,8 @@
     method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setVmOutputCaptured(boolean);
   }
 
-  public final class VirtualMachineDescriptor implements java.io.Closeable android.os.Parcelable {
-    method public void close() throws java.io.IOException;
+  public final class VirtualMachineDescriptor implements java.lang.AutoCloseable android.os.Parcelable {
+    method public void close();
     method public int describeContents();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.system.virtualmachine.VirtualMachineDescriptor> CREATOR;
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index 9902cb5..a6b3ed6 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -377,7 +377,7 @@
     }
 
     /**
-     * Builds a virtual machine from an {@link VirtualMachineDescriptor} object and associates it
+     * Creates a virtual machine from an {@link VirtualMachineDescriptor} object and associates it
      * with the given name.
      *
      * <p>The new virtual machine will be in the same state as the descriptor indicates.
@@ -416,8 +416,6 @@
                     }
                     vm.importEncryptedStoreFrom(vmDescriptor.getEncryptedStoreFd());
                 }
-            } catch (IOException e) {
-                throw new VirtualMachineException(e);
             }
             return vm;
         } catch (VirtualMachineException | RuntimeException e) {
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java b/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java
index 1304af3..710925d 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java
@@ -25,7 +25,6 @@
 import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
 
-import java.io.Closeable;
 import java.io.IOException;
 
 /**
@@ -37,9 +36,8 @@
  *
  * @hide
  */
-// TODO(b/268613460): should implement autocloseable.
 @SystemApi
-public final class VirtualMachineDescriptor implements Parcelable, Closeable {
+public final class VirtualMachineDescriptor implements Parcelable, AutoCloseable {
     private volatile boolean mClosed = false;
     @NonNull private final ParcelFileDescriptor mConfigFd;
     @NonNull private final ParcelFileDescriptor mInstanceImgFd;
@@ -120,13 +118,21 @@
                 ParcelFileDescriptor.class.getClassLoader(), ParcelFileDescriptor.class);
     }
 
+    /**
+     * Release any resources held by this descriptor. Calling {@code close} on an already-closed
+     * descriptor has no effect.
+     */
     @Override
-    public void close() throws IOException {
+    public void close() {
         mClosed = true;
         // Let the compiler do the work: close everything, throw if any of them fail, skipping null.
         try (mConfigFd;
                 mInstanceImgFd;
-                mEncryptedStoreFd) {}
+                mEncryptedStoreFd) {
+        } catch (IOException ignored) {
+            // PFD already swallows exceptions from closing the fd. There's no reason to propagate
+            // this to the caller.
+        }
     }
 
     private void checkNotClosed() {
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
index 7c9af63..b7ea22c 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
@@ -196,7 +196,8 @@
      *
      * <p>NOTE: This method may block and should not be called on the main thread.
      *
-     * @throws VirtualMachineException if the VM cannot be imported.
+     * @throws VirtualMachineException if the VM cannot be imported or the {@code
+     *     VirtualMachineDescriptor} has already been closed.
      * @hide
      */
     @NonNull
diff --git a/libs/apkverify/src/ziputil.rs b/libs/apkverify/src/ziputil.rs
index cc8bc58..5e513a7 100644
--- a/libs/apkverify/src/ziputil.rs
+++ b/libs/apkverify/src/ziputil.rs
@@ -18,9 +18,12 @@
 
 use anyhow::{ensure, Result};
 use bytes::{Buf, BufMut};
-use std::io::{Read, Seek, SeekFrom};
+use std::io::{Read, Seek};
 use zip::ZipArchive;
 
+#[cfg(test)]
+use std::io::SeekFrom;
+
 const EOCD_SIZE_WITHOUT_COMMENT: usize = 22;
 const EOCD_CENTRAL_DIRECTORY_SIZE_FIELD_OFFSET: usize = 12;
 const EOCD_CENTRAL_DIRECTORY_OFFSET_FIELD_OFFSET: usize = 16;
@@ -45,7 +48,7 @@
     // retrieve reader back
     reader = archive.into_inner();
     // the current position should point EOCD offset
-    let eocd_offset = reader.seek(SeekFrom::Current(0))? as u32;
+    let eocd_offset = reader.stream_position()? as u32;
     let mut eocd = vec![0u8; eocd_size];
     reader.read_exact(&mut eocd)?;
     ensure!(
diff --git a/libs/dice/Android.bp b/libs/dice/Android.bp
deleted file mode 100644
index 71cf0f1..0000000
--- a/libs/dice/Android.bp
+++ /dev/null
@@ -1,24 +0,0 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-rust_library_rlib {
-    name: "libdice_nostd",
-    crate_name: "dice",
-    srcs: ["src/lib.rs"],
-    edition: "2021",
-    no_stdlibs: true,
-    prefer_rlib: true,
-    stdlibs: ["libcore.rust_sysroot"],
-    rustlibs: [
-        "libdiced_open_dice_nostd",
-        "libopen_dice_cbor_bindgen_nostd",
-        "libopen_dice_bcc_bindgen_nostd",
-    ],
-    whole_static_libs: [
-        "libopen_dice_bcc",
-        "libopen_dice_cbor",
-        "libcrypto_baremetal",
-    ],
-    apex_available: ["com.android.virt"],
-}
diff --git a/libs/dice/src/bcc.rs b/libs/dice/src/bcc.rs
deleted file mode 100644
index a7ef882..0000000
--- a/libs/dice/src/bcc.rs
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright 2022 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.
- */
-
-//! Wrapper around dice/android/bcc.h.
-
-use core::mem;
-use core::ptr;
-
-use open_dice_bcc_bindgen::BccHandoverMainFlow;
-use open_dice_bcc_bindgen::BccHandoverParse;
-
-use crate::check_result;
-use crate::Cdi;
-use crate::DiceError;
-use crate::InputValues;
-use crate::Result;
-
-/// Boot Chain Certificate handover format combining the BCC and CDIs in a single CBOR object.
-#[derive(Clone, Debug)]
-pub struct Handover<'a> {
-    buffer: &'a [u8],
-    /// Attestation CDI.
-    pub cdi_attest: &'a Cdi,
-    /// Sealing CDI.
-    pub cdi_seal: &'a Cdi,
-    /// Boot Chain Certificate (optional).
-    pub bcc: Option<&'a [u8]>,
-}
-
-impl<'a> Handover<'a> {
-    /// Validates and extracts the fields of a BCC handover buffer.
-    pub fn new(buffer: &'a [u8]) -> Result<Self> {
-        let mut cdi_attest: *const u8 = ptr::null();
-        let mut cdi_seal: *const u8 = ptr::null();
-        let mut bcc: *const u8 = ptr::null();
-        let mut bcc_size: usize = 0;
-
-        // SAFETY - The buffer is only read and never stored and the returned pointers should all
-        // point within the address range of the buffer or be NULL.
-        check_result(unsafe {
-            BccHandoverParse(
-                buffer.as_ptr(),
-                buffer.len(),
-                &mut cdi_attest as *mut *const u8,
-                &mut cdi_seal as *mut *const u8,
-                &mut bcc as *mut *const u8,
-                &mut bcc_size as *mut usize,
-            )
-        })?;
-
-        let cdi_attest = {
-            let i = index_from_ptr(buffer, cdi_attest).ok_or(DiceError::PlatformError)?;
-            let s = buffer.get(i..(i + mem::size_of::<Cdi>())).ok_or(DiceError::PlatformError)?;
-            s.try_into().map_err(|_| DiceError::PlatformError)?
-        };
-        let cdi_seal = {
-            let i = index_from_ptr(buffer, cdi_seal).ok_or(DiceError::PlatformError)?;
-            let s = buffer.get(i..(i + mem::size_of::<Cdi>())).ok_or(DiceError::PlatformError)?;
-            s.try_into().map_err(|_| DiceError::PlatformError)?
-        };
-        let bcc = if bcc.is_null() {
-            None
-        } else {
-            let i = index_from_ptr(buffer, bcc).ok_or(DiceError::PlatformError)?;
-            Some(buffer.get(i..(i + bcc_size)).ok_or(DiceError::PlatformError)?)
-        };
-
-        Ok(Self { buffer, cdi_attest, cdi_seal, bcc })
-    }
-
-    /// Executes the main BCC handover flow.
-    pub fn main_flow(&self, input_values: &InputValues, buffer: &mut [u8]) -> Result<usize> {
-        let context = ptr::null_mut();
-        let mut size: usize = 0;
-        // SAFETY - The function only reads `self.buffer`, writes to `buffer` within its bounds,
-        // reads `input_values` as a constant input and doesn't store any pointer.
-        check_result(unsafe {
-            BccHandoverMainFlow(
-                context,
-                self.buffer.as_ptr(),
-                self.buffer.len(),
-                input_values.as_ptr(),
-                buffer.len(),
-                buffer.as_mut_ptr(),
-                &mut size as *mut usize,
-            )
-        })?;
-
-        Ok(size)
-    }
-}
-
-fn index_from_ptr(slice: &[u8], pointer: *const u8) -> Option<usize> {
-    if slice.as_ptr_range().contains(&pointer) {
-        (pointer as usize).checked_sub(slice.as_ptr() as usize)
-    } else {
-        None
-    }
-}
diff --git a/libs/dice/src/lib.rs b/libs/dice/src/lib.rs
deleted file mode 100644
index 6870eeb..0000000
--- a/libs/dice/src/lib.rs
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright 2022 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.
- */
-
-//! Bare metal wrapper around libopen_dice.
-
-#![no_std]
-
-pub use diced_open_dice::{
-    bcc_format_config_descriptor, check_result, Cdi, Config, DiceError, DiceMode, Hash,
-    InputValues, Result, CDI_SIZE, HASH_SIZE, HIDDEN_SIZE,
-};
-
-pub mod bcc;
diff --git a/libs/hypervisor_props/Android.bp b/libs/hypervisor_props/Android.bp
new file mode 100644
index 0000000..af08b01
--- /dev/null
+++ b/libs/hypervisor_props/Android.bp
@@ -0,0 +1,18 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_library {
+    name: "libhypervisor_props",
+    crate_name: "hypervisor_props",
+    srcs: ["src/lib.rs"],
+    edition: "2021",
+    rustlibs: [
+        "libanyhow",
+        "librustutils",
+    ],
+    apex_available: [
+        "com.android.compos",
+        "com.android.virt",
+    ],
+}
diff --git a/libs/hypervisor_props/src/lib.rs b/libs/hypervisor_props/src/lib.rs
new file mode 100644
index 0000000..120a48c
--- /dev/null
+++ b/libs/hypervisor_props/src/lib.rs
@@ -0,0 +1,40 @@
+// 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.
+
+//! Access to hypervisor capabilities via system properties set by the bootloader.
+
+use anyhow::{Error, Result};
+use rustutils::system_properties;
+
+/// Returns whether there is a hypervisor present that supports non-protected VMs.
+pub fn is_vm_supported() -> Result<bool> {
+    system_properties::read_bool("ro.boot.hypervisor.vm.supported", false).map_err(Error::new)
+}
+
+/// Returns whether there is a hypervisor present that supports protected VMs.
+pub fn is_protected_vm_supported() -> Result<bool> {
+    system_properties::read_bool("ro.boot.hypervisor.protected_vm.supported", false)
+        .map_err(Error::new)
+}
+
+/// Returns whether there is a hypervisor present that supports any sort of VM, either protected
+/// or non-protected.
+pub fn is_any_vm_supported() -> Result<bool> {
+    is_vm_supported().and_then(|ok| if ok { Ok(true) } else { is_protected_vm_supported() })
+}
+
+/// Returns the version of the hypervisor, if there is one.
+pub fn version() -> Result<Option<String>> {
+    system_properties::read("ro.boot.hypervisor.version").map_err(Error::new)
+}
diff --git a/microdroid/initrd/src/main.rs b/microdroid/initrd/src/main.rs
index c5515af..8f06f09 100644
--- a/microdroid/initrd/src/main.rs
+++ b/microdroid/initrd/src/main.rs
@@ -90,7 +90,7 @@
 
     let initrd_size: usize = initrd_bc_size - bc_size - INITRD_FOOTER_LEN;
 
-    initrd_bc.seek(SeekFrom::Start(0))?;
+    initrd_bc.rewind()?;
     copyfile2file(&mut initrd_bc, &mut initrd, initrd_size)?;
     copyfile2file(&mut initrd_bc, &mut bootconfig, bc_size)?;
     Ok(())
diff --git a/microdroid_manager/src/dice.rs b/microdroid_manager/src/dice.rs
index 9a2648f..fd22198 100644
--- a/microdroid_manager/src/dice.rs
+++ b/microdroid_manager/src/dice.rs
@@ -14,10 +14,11 @@
 
 //! Logic for handling the DICE values and boot operations.
 
-use anyhow::{bail, Context, Error, Result};
+use anyhow::{anyhow, bail, Context, Error, Result};
 use byteorder::{NativeEndian, ReadBytesExt};
 use diced_open_dice::{
-    retry_bcc_main_flow, Cdi, Config, DiceMode, Hash, Hidden, InputValues, OwnedDiceArtifacts,
+    bcc_handover_parse, retry_bcc_main_flow, BccHandover, Cdi, Config, DiceMode, Hash, Hidden,
+    InputValues, OwnedDiceArtifacts,
 };
 use keystore2_crypto::ZVec;
 use libc::{c_void, mmap, munmap, MAP_FAILED, MAP_PRIVATE, PROT_READ};
@@ -47,9 +48,7 @@
         driver_path: PathBuf,
         mmap_addr: *mut c_void,
         mmap_size: usize,
-        cdi_attest: &'a Cdi,
-        cdi_seal: &'a Cdi,
-        bcc: &'a [u8],
+        bcc_handover: BccHandover<'a>,
     },
     Fake(OwnedDiceArtifacts),
 }
@@ -86,27 +85,13 @@
         // accessible and not referenced from anywhere else.
         let mmap_buf =
             unsafe { slice::from_raw_parts((mmap_addr as *const u8).as_ref().unwrap(), mmap_size) };
-        // Very inflexible parsing / validation of the BccHandover data. Assumes deterministically
-        // encoded CBOR.
-        //
-        // BccHandover = {
-        //   1 : bstr .size 32,     ; CDI_Attest
-        //   2 : bstr .size 32,     ; CDI_Seal
-        //   3 : Bcc,               ; Certificate chain
-        // }
-        if mmap_buf[0..4] != [0xa3, 0x01, 0x58, 0x20]
-            || mmap_buf[36..39] != [0x02, 0x58, 0x20]
-            || mmap_buf[71] != 0x03
-        {
-            bail!("BccHandover format mismatch");
-        }
+        let bcc_handover =
+            bcc_handover_parse(mmap_buf).map_err(|_| anyhow!("Failed to parse Bcc Handover"))?;
         Ok(Self::Real {
             driver_path: driver_path.to_path_buf(),
             mmap_addr,
             mmap_size,
-            cdi_attest: mmap_buf[4..36].try_into().unwrap(),
-            cdi_seal: mmap_buf[39..71].try_into().unwrap(),
-            bcc: &mmap_buf[72..],
+            bcc_handover,
         })
     }
 
@@ -115,7 +100,7 @@
         // directly, so we have the chance to rotate the key if needed. A salt isn't needed as the
         // input key material is already cryptographically strong.
         let cdi_seal = match self {
-            Self::Real { cdi_seal, .. } => cdi_seal,
+            Self::Real { bcc_handover, .. } => bcc_handover.cdi_seal,
             Self::Fake(fake) => &fake.cdi_values.cdi_seal,
         };
         let salt = &[];
@@ -138,7 +123,11 @@
             hidden,
         );
         let (cdi_attest, cdi_seal, bcc) = match &self {
-            Self::Real { cdi_attest, cdi_seal, bcc, .. } => (*cdi_attest, *cdi_seal, *bcc),
+            Self::Real { bcc_handover, .. } => (
+                bcc_handover.cdi_attest,
+                bcc_handover.cdi_seal,
+                bcc_handover.bcc.ok_or_else(|| anyhow!("bcc is none"))?,
+            ),
             Self::Fake(fake) => {
                 (&fake.cdi_values.cdi_attest, &fake.cdi_values.cdi_seal, fake.bcc.as_slice())
             }
diff --git a/pvmfw/Android.bp b/pvmfw/Android.bp
index 0d6a9a4..7561800 100644
--- a/pvmfw/Android.bp
+++ b/pvmfw/Android.bp
@@ -14,7 +14,6 @@
     rustlibs: [
         "libaarch64_paging",
         "libbuddy_system_allocator",
-        "libdice_nostd", // TODO(b/267575445): Remove this library once the migration is done.
         "libdiced_open_dice_nostd",
         "libfdtpci",
         "liblibfdt",
@@ -65,6 +64,22 @@
     },
 }
 
+// Provide pvmfw.bin binary regardless of the architecture for building test.
+// Note that skipping tests on unsupported device is easy
+// while configuring server configuration to make such tests to run on working
+// devices.
+prebuilt_etc {
+    name: "pvmfw_test",
+    filename: "pvmfw_test.bin",
+    target: {
+        android_arm64: {
+            src: ":pvmfw_bin",
+        },
+    },
+    src: "empty_file",
+    installable: false,
+}
+
 prebuilt_etc {
     name: "pvmfw_embedded_key",
     src: ":avb_testkey_rsa4096_pub_bin",
diff --git a/pvmfw/avb/src/verify.rs b/pvmfw/avb/src/verify.rs
index 1a79c83..b03506c 100644
--- a/pvmfw/avb/src/verify.rs
+++ b/pvmfw/avb/src/verify.rs
@@ -22,14 +22,16 @@
 use core::ffi::c_char;
 
 /// Verified data returned when the payload verification succeeds.
-#[derive(Debug)]
-pub struct VerifiedBootData {
+#[derive(Debug, PartialEq, Eq)]
+pub struct VerifiedBootData<'a> {
     /// DebugLevel of the VM.
     pub debug_level: DebugLevel,
     /// Kernel digest.
     pub kernel_digest: Digest,
     /// Initrd digest if initrd exists.
     pub initrd_digest: Option<Digest>,
+    /// Trusted public key.
+    pub public_key: &'a [u8],
 }
 
 /// This enum corresponds to the `DebugLevel` in `VirtualMachineConfig`.
@@ -94,11 +96,11 @@
 }
 
 /// Verifies the payload (signed kernel + initrd) against the trusted public key.
-pub fn verify_payload(
+pub fn verify_payload<'a>(
     kernel: &[u8],
     initrd: Option<&[u8]>,
-    trusted_public_key: &[u8],
-) -> Result<VerifiedBootData, AvbSlotVerifyError> {
+    trusted_public_key: &'a [u8],
+) -> Result<VerifiedBootData<'a>, AvbSlotVerifyError> {
     let mut payload = Payload::new(kernel, initrd, trusted_public_key);
     let mut ops = Ops::from(&mut payload);
     let kernel_verify_result = ops.verify_partition(PartitionName::Kernel.as_cstr())?;
@@ -119,6 +121,7 @@
             debug_level: DebugLevel::None,
             kernel_digest: kernel_descriptor.digest,
             initrd_digest: None,
+            public_key: trusted_public_key,
         });
     }
 
@@ -142,5 +145,6 @@
         debug_level,
         kernel_digest: kernel_descriptor.digest,
         initrd_digest: Some(initrd_descriptor.digest),
+        public_key: trusted_public_key,
     })
 }
diff --git a/pvmfw/avb/tests/api_test.rs b/pvmfw/avb/tests/api_test.rs
index 261d8a8..78f274a 100644
--- a/pvmfw/avb/tests/api_test.rs
+++ b/pvmfw/avb/tests/api_test.rs
@@ -18,7 +18,7 @@
 
 use anyhow::{anyhow, Result};
 use avb_bindgen::{AvbFooter, AvbVBMetaImageHeader};
-use pvmfw_avb::{verify_payload, AvbSlotVerifyError, DebugLevel};
+use pvmfw_avb::{verify_payload, AvbSlotVerifyError, DebugLevel, VerifiedBootData};
 use std::{fs, mem::size_of, ptr};
 use utils::*;
 
@@ -53,17 +53,23 @@
 
 #[test]
 fn payload_expecting_no_initrd_passes_verification_with_no_initrd() -> Result<()> {
+    let public_key = load_trusted_public_key()?;
     let verified_boot_data = verify_payload(
         &fs::read(TEST_IMG_WITH_ONE_HASHDESC_PATH)?,
         /*initrd=*/ None,
-        &load_trusted_public_key()?,
+        &public_key,
     )
     .map_err(|e| anyhow!("Verification failed. Error: {}", e))?;
 
-    assert_eq!(DebugLevel::None, verified_boot_data.debug_level);
-    let digest = hash(&[&hex::decode("1111")?, &fs::read(UNSIGNED_TEST_IMG_PATH)?]);
-    assert_eq!(digest, verified_boot_data.kernel_digest);
-    assert!(verified_boot_data.initrd_digest.is_none());
+    let kernel_digest = hash(&[&hex::decode("1111")?, &fs::read(UNSIGNED_TEST_IMG_PATH)?]);
+    let expected_boot_data = VerifiedBootData {
+        debug_level: DebugLevel::None,
+        kernel_digest,
+        initrd_digest: None,
+        public_key: &public_key,
+    };
+    assert_eq!(expected_boot_data, verified_boot_data);
+
     Ok(())
 }
 
diff --git a/pvmfw/avb/tests/utils.rs b/pvmfw/avb/tests/utils.rs
index 8756d06..6713846 100644
--- a/pvmfw/avb/tests/utils.rs
+++ b/pvmfw/avb/tests/utils.rs
@@ -22,7 +22,7 @@
     AvbVBMetaImageHeader,
 };
 use openssl::sha;
-use pvmfw_avb::{verify_payload, AvbSlotVerifyError, DebugLevel, Digest};
+use pvmfw_avb::{verify_payload, AvbSlotVerifyError, DebugLevel, Digest, VerifiedBootData};
 use std::{
     fs,
     mem::{size_of, transmute, MaybeUninit},
@@ -102,23 +102,23 @@
     initrd_salt: &[u8],
     expected_debug_level: DebugLevel,
 ) -> Result<()> {
+    let public_key = load_trusted_public_key()?;
     let kernel = load_latest_signed_kernel()?;
-    let verified_boot_data = verify_payload(&kernel, Some(initrd), &load_trusted_public_key()?)
+    let verified_boot_data = verify_payload(&kernel, Some(initrd), &public_key)
         .map_err(|e| anyhow!("Verification failed. Error: {}", e))?;
 
-    assert_eq!(expected_debug_level, verified_boot_data.debug_level);
-
     let footer = extract_avb_footer(&kernel)?;
-    assert_eq!(
-        hash(&[&hash(&[b"bootloader"]), &kernel[..usize::try_from(footer.original_image_size)?]]),
-        verified_boot_data.kernel_digest,
-        "Kernel digest is not equal to the expected."
-    );
-    assert_eq!(
-        hash(&[&hash(&[initrd_salt]), initrd,]),
-        verified_boot_data.initrd_digest.unwrap(),
-        "initrd digest is not equal to the expected."
-    );
+    let kernel_digest =
+        hash(&[&hash(&[b"bootloader"]), &kernel[..usize::try_from(footer.original_image_size)?]]);
+    let initrd_digest = Some(hash(&[&hash(&[initrd_salt]), initrd]));
+    let expected_boot_data = VerifiedBootData {
+        debug_level: expected_debug_level,
+        kernel_digest,
+        initrd_digest,
+        public_key: &public_key,
+    };
+    assert_eq!(expected_boot_data, verified_boot_data);
+
     Ok(())
 }
 
diff --git a/pvmfw/empty_file b/pvmfw/empty_file
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/pvmfw/empty_file
diff --git a/pvmfw/src/dice.rs b/pvmfw/src/dice.rs
index e354666..14f522f 100644
--- a/pvmfw/src/dice.rs
+++ b/pvmfw/src/dice.rs
@@ -19,11 +19,10 @@
 use core::ffi::CStr;
 use core::mem::size_of;
 use core::slice;
-use dice::bcc::Handover;
-use dice::Config;
-use dice::DiceMode;
-use dice::InputValues;
-use diced_open_dice::{bcc_format_config_descriptor, hash, HIDDEN_SIZE};
+
+use diced_open_dice::{
+    bcc_format_config_descriptor, hash, Config, DiceMode, Hash, InputValues, HIDDEN_SIZE,
+};
 use pvmfw_avb::{DebugLevel, Digest, VerifiedBootData};
 
 fn to_dice_mode(debug_level: DebugLevel) -> DiceMode {
@@ -33,7 +32,7 @@
     }
 }
 
-fn to_dice_hash(verified_boot_data: &VerifiedBootData) -> dice::Result<dice::Hash> {
+fn to_dice_hash(verified_boot_data: &VerifiedBootData) -> diced_open_dice::Result<Hash> {
     let mut digests = [0u8; size_of::<Digest>() * 2];
     digests[..size_of::<Digest>()].copy_from_slice(&verified_boot_data.kernel_digest);
     if let Some(initrd_digest) = verified_boot_data.initrd_digest {
@@ -42,35 +41,43 @@
     hash(&digests)
 }
 
-/// Derive the VM-specific secrets and certificate through DICE.
-pub fn derive_next_bcc(
-    bcc: &Handover,
-    next_bcc: &mut [u8],
-    verified_boot_data: &VerifiedBootData,
-    authority: &[u8],
-) -> dice::Result<usize> {
-    let code_hash = to_dice_hash(verified_boot_data)?;
-    let auth_hash = hash(authority)?;
-    let mode = to_dice_mode(verified_boot_data.debug_level);
-    let component_name = CStr::from_bytes_with_nul(b"vm_entry\0").unwrap();
-    let mut config_descriptor_buffer = [0; 128];
-    let config_descriptor_size = bcc_format_config_descriptor(
-        Some(component_name),
-        None,  // component_version
-        false, // resettable
-        &mut config_descriptor_buffer,
-    )?;
-    let config = &config_descriptor_buffer[..config_descriptor_size];
+pub struct PartialInputs {
+    code_hash: Hash,
+    auth_hash: Hash,
+    mode: DiceMode,
+}
 
-    let input_values = InputValues::new(
-        code_hash,
-        Config::Descriptor(config),
-        auth_hash,
-        mode,
-        [0u8; HIDDEN_SIZE], // TODO(b/249723852): Get salt from instance.img (virtio-blk) and/or TRNG.
-    );
+impl PartialInputs {
+    pub fn new(data: &VerifiedBootData) -> diced_open_dice::Result<Self> {
+        let code_hash = to_dice_hash(data)?;
+        let auth_hash = hash(data.public_key)?;
+        let mode = to_dice_mode(data.debug_level);
 
-    bcc.main_flow(&input_values, next_bcc)
+        Ok(Self { code_hash, auth_hash, mode })
+    }
+
+    pub fn into_input_values(
+        self,
+        salt: &[u8; HIDDEN_SIZE],
+    ) -> diced_open_dice::Result<InputValues> {
+        let component_name = CStr::from_bytes_with_nul(b"vm_entry\0").unwrap();
+        let mut config_descriptor_buffer = [0; 128];
+        let config_descriptor_size = bcc_format_config_descriptor(
+            Some(component_name),
+            None,  // component_version
+            false, // resettable
+            &mut config_descriptor_buffer,
+        )?;
+        let config = &config_descriptor_buffer[..config_descriptor_size];
+
+        Ok(InputValues::new(
+            self.code_hash,
+            Config::Descriptor(config),
+            self.auth_hash,
+            self.mode,
+            *salt,
+        ))
+    }
 }
 
 /// Flushes data caches over the provided address range.
diff --git a/pvmfw/src/entry.rs b/pvmfw/src/entry.rs
index 530449c..ddde50a 100644
--- a/pvmfw/src/entry.rs
+++ b/pvmfw/src/entry.rs
@@ -25,7 +25,6 @@
 use core::arch::asm;
 use core::num::NonZeroUsize;
 use core::slice;
-use dice::bcc::Handover;
 use log::debug;
 use log::error;
 use log::info;
@@ -243,10 +242,6 @@
     })?;
 
     let bcc_slice = appended.get_bcc_mut();
-    let bcc = Handover::new(bcc_slice).map_err(|e| {
-        error!("Invalid BCC Handover: {e:?}");
-        RebootReason::InvalidBcc
-    })?;
 
     debug!("Activating dynamic page table...");
     // SAFETY - page_table duplicates the static mappings for everything that the Rust code is
@@ -258,7 +253,7 @@
     let slices = MemorySlices::new(fdt, payload, payload_size, &mut memory)?;
 
     // This wrapper allows main() to be blissfully ignorant of platform details.
-    crate::main(slices.fdt, slices.kernel, slices.ramdisk, &bcc, &mut memory)?;
+    crate::main(slices.fdt, slices.kernel, slices.ramdisk, bcc_slice, &mut memory)?;
 
     helpers::flushed_zeroize(bcc_slice);
     helpers::flush(slices.fdt.as_slice());
diff --git a/pvmfw/src/main.rs b/pvmfw/src/main.rs
index be5a16a..ba26114 100644
--- a/pvmfw/src/main.rs
+++ b/pvmfw/src/main.rs
@@ -38,7 +38,7 @@
 use alloc::boxed::Box;
 
 use crate::{
-    dice::derive_next_bcc,
+    dice::PartialInputs,
     entry::RebootReason,
     fdt::add_dice_node,
     helpers::flush,
@@ -46,7 +46,7 @@
     memory::MemoryTracker,
     virtio::pci::{self, find_virtio_devices},
 };
-use ::dice::bcc;
+use diced_open_dice::{bcc_handover_main_flow, bcc_handover_parse, HIDDEN_SIZE};
 use fdtpci::{PciError, PciInfo};
 use libfdt::Fdt;
 use log::{debug, error, info, trace};
@@ -59,7 +59,7 @@
     fdt: &mut Fdt,
     signed_kernel: &[u8],
     ramdisk: Option<&[u8]>,
-    bcc: &bcc::Handover,
+    current_bcc_handover: &[u8],
     memory: &mut MemoryTracker,
 ) -> Result<(), RebootReason> {
     info!("pVM firmware");
@@ -71,7 +71,11 @@
     } else {
         debug!("Ramdisk: None");
     }
-    trace!("BCC: {bcc:x?}");
+    let bcc_handover = bcc_handover_parse(current_bcc_handover).map_err(|e| {
+        error!("Invalid BCC Handover: {e:?}");
+        RebootReason::InvalidBcc
+    })?;
+    trace!("BCC: {bcc_handover:x?}");
 
     // Set up PCI bus for VirtIO devices.
     let pci_info = PciInfo::from_fdt(fdt).map_err(handle_pci_error)?;
@@ -90,13 +94,20 @@
     })?;
     // By leaking the slice, its content will be left behind for the next stage.
     let next_bcc = Box::leak(next_bcc);
-    let next_bcc_size =
-        derive_next_bcc(bcc, next_bcc, &verified_boot_data, PUBLIC_KEY).map_err(|e| {
-            error!("Failed to derive next-stage DICE secrets: {e:?}");
-            RebootReason::SecretDerivationError
-        })?;
-    trace!("Next BCC: {:x?}", bcc::Handover::new(&next_bcc[..next_bcc_size]));
 
+    let dice_inputs = PartialInputs::new(&verified_boot_data).map_err(|e| {
+        error!("Failed to compute partial DICE inputs: {e:?}");
+        RebootReason::InternalError
+    })?;
+    let salt = [0; HIDDEN_SIZE]; // TODO(b/249723852): Get from instance.img and/or TRNG.
+    let dice_inputs = dice_inputs.into_input_values(&salt).map_err(|e| {
+        error!("Failed to generate DICE inputs: {e:?}");
+        RebootReason::InternalError
+    })?;
+    let _ = bcc_handover_main_flow(current_bcc_handover, &dice_inputs, next_bcc).map_err(|e| {
+        error!("Failed to derive next-stage DICE secrets: {e:?}");
+        RebootReason::SecretDerivationError
+    })?;
     flush(next_bcc);
 
     add_dice_node(fdt, next_bcc.as_ptr() as usize, NEXT_BCC_SIZE).map_err(|e| {
diff --git a/pvmfw/src/virtio/pci.rs b/pvmfw/src/virtio/pci.rs
index d3b3124..dd9e34e 100644
--- a/pvmfw/src/virtio/pci.rs
+++ b/pvmfw/src/virtio/pci.rs
@@ -23,7 +23,10 @@
 use virtio_drivers::{
     device::blk::VirtIOBlk,
     transport::{
-        pci::{bus::PciRoot, virtio_device_type, PciTransport},
+        pci::{
+            bus::{BusDeviceIterator, PciRoot},
+            virtio_device_type, PciTransport,
+        },
         DeviceType, Transport,
     },
 };
@@ -66,31 +69,59 @@
     Ok(())
 }
 
-/// Finds VirtIO PCI devices.
-pub fn find_virtio_devices(pci_root: &mut PciRoot) -> Result<(), PciError> {
-    for (device_function, info) in pci_root.enumerate_bus(0) {
-        let (status, command) = pci_root.get_status_command(device_function);
-        debug!(
-            "Found PCI device {} at {}, status {:?} command {:?}",
-            info, device_function, status, command
-        );
-        if let Some(virtio_type) = virtio_device_type(&info) {
+struct VirtIOBlkIterator<'a> {
+    pci_root: &'a mut PciRoot,
+    bus: BusDeviceIterator,
+}
+
+impl<'a> VirtIOBlkIterator<'a> {
+    pub fn new(pci_root: &'a mut PciRoot) -> Self {
+        let bus = pci_root.enumerate_bus(0);
+        Self { pci_root, bus }
+    }
+}
+
+impl<'a> Iterator for VirtIOBlkIterator<'a> {
+    type Item = VirtIOBlk<HalImpl, PciTransport>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        loop {
+            let (device_function, info) = self.bus.next()?;
+            let (status, command) = self.pci_root.get_status_command(device_function);
+            debug!(
+                "Found PCI device {} at {}, status {:?} command {:?}",
+                info, device_function, status, command
+            );
+
+            let virtio_type = if let Some(t) = virtio_device_type(&info) {
+                t
+            } else {
+                continue;
+            };
             debug!("  VirtIO {:?}", virtio_type);
-            let mut transport = PciTransport::new::<HalImpl>(pci_root, device_function).unwrap();
-            info!(
+
+            let mut transport =
+                PciTransport::new::<HalImpl>(self.pci_root, device_function).unwrap();
+            debug!(
                 "Detected virtio PCI device with device type {:?}, features {:#018x}",
                 transport.device_type(),
                 transport.read_device_features(),
             );
+
             if virtio_type == DeviceType::Block {
-                let mut blk =
-                    VirtIOBlk::<HalImpl, _>::new(transport).expect("failed to create blk driver");
-                info!("Found {} KiB block device.", blk.capacity() * 512 / 1024);
-                let mut data = [0; 512];
-                blk.read_block(0, &mut data).expect("Failed to read block device");
+                return Some(Self::Item::new(transport).expect("failed to create blk driver"));
             }
         }
     }
+}
+
+/// Finds VirtIO PCI devices.
+pub fn find_virtio_devices(pci_root: &mut PciRoot) -> Result<(), PciError> {
+    for mut blk in VirtIOBlkIterator::new(pci_root) {
+        info!("Found {} KiB block device.", blk.capacity() * 512 / 1024);
+        let mut data = [0; 512];
+        blk.read_block(0, &mut data).expect("Failed to read block device");
+    }
 
     Ok(())
 }
diff --git a/tests/hostside/Android.bp b/tests/hostside/Android.bp
index 6e0cf5a..046a9d6 100644
--- a/tests/hostside/Android.bp
+++ b/tests/hostside/Android.bp
@@ -2,6 +2,26 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
+genrule_defaults {
+    name: "test_avf_debug_policy_overlay",
+    tools: ["dtc"],
+    cmd: "$(location dtc) -I dts -O dtb $(in) -o $(out)",
+}
+
+genrule {
+    name: "test_avf_debug_policy_with_ramdump",
+    defaults: ["test_avf_debug_policy_overlay"],
+    srcs: ["assets/avf_debug_policy_with_ramdump.dts"],
+    out: ["avf_debug_policy_with_ramdump.dtbo"],
+}
+
+genrule {
+    name: "test_avf_debug_policy_without_ramdump",
+    defaults: ["test_avf_debug_policy_overlay"],
+    srcs: ["assets/avf_debug_policy_without_ramdump.dts"],
+    out: ["avf_debug_policy_without_ramdump.dtbo"],
+}
+
 java_test_host {
     name: "MicrodroidHostTestCases",
     srcs: ["java/**/*.java"],
@@ -10,6 +30,7 @@
         "general-tests",
     ],
     libs: [
+        "androidx.annotation_annotation",
         "tradefed",
     ],
     static_libs: [
@@ -23,6 +44,10 @@
         ":microdroid_general_sepolicy.conf",
         ":test.com.android.virt.pem",
         ":test2.com.android.virt.pem",
+        ":pvmfw_test",
+        ":test_avf_debug_policy_with_ramdump",
+        ":test_avf_debug_policy_without_ramdump",
+        "assets/bcc.dat",
     ],
     data_native_bins: [
         "sepolicy-analyze",
diff --git a/tests/hostside/assets/avf_debug_policy_with_ramdump.dts b/tests/hostside/assets/avf_debug_policy_with_ramdump.dts
new file mode 100644
index 0000000..f1a5196
--- /dev/null
+++ b/tests/hostside/assets/avf_debug_policy_with_ramdump.dts
@@ -0,0 +1,18 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+    fragment@avf {
+        target-path = "/";
+
+        __overlay__ {
+            avf {
+                guest {
+                    common {
+                        ramdump = <1>;
+                    };
+                };
+            };
+        };
+    };
+};
\ No newline at end of file
diff --git a/tests/hostside/assets/avf_debug_policy_without_ramdump.dts b/tests/hostside/assets/avf_debug_policy_without_ramdump.dts
new file mode 100644
index 0000000..5b15d51
--- /dev/null
+++ b/tests/hostside/assets/avf_debug_policy_without_ramdump.dts
@@ -0,0 +1,18 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+    fragment@avf {
+        target-path = "/";
+
+        __overlay__ {
+            avf {
+                guest {
+                    common {
+                        ramdump = <0>;
+                    };
+                };
+            };
+        };
+    };
+};
\ No newline at end of file
diff --git a/tests/hostside/assets/bcc.dat b/tests/hostside/assets/bcc.dat
new file mode 100644
index 0000000..7ab71f1
--- /dev/null
+++ b/tests/hostside/assets/bcc.dat
Binary files differ
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index a780a8b..726fb4a 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -73,6 +73,7 @@
 import java.io.PipedOutputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -607,7 +608,8 @@
                 .isTrue();
     }
 
-    private boolean isTombstoneGeneratedWithCrashPayload(boolean debuggable) throws Exception {
+    private boolean isTombstoneGeneratedWithVmRunApp(boolean debuggable, String... additionalArgs)
+            throws Exception {
         // we can't use microdroid builder as it wants ADB connection (debuggable)
         CommandRunner android = new CommandRunner(getDevice());
 
@@ -617,20 +619,27 @@
         final String apkPath = getPathForPackage(PACKAGE_NAME);
         final String idsigPath = TEST_ROOT + "idsig";
         final String instanceImgPath = TEST_ROOT + "instance.img";
-        android.run(
-                VIRT_APEX + "bin/vm",
-                "run-app",
-                "--payload-binary-name",
-                "MicrodroidCrashNativeLib.so",
-                "--debug",
-                debuggable ? "full" : "none",
-                apkPath,
-                idsigPath,
-                instanceImgPath);
+        List<String> cmd =
+                new ArrayList<>(
+                        Arrays.asList(
+                                VIRT_APEX + "bin/vm",
+                                "run-app",
+                                "--debug",
+                                debuggable ? "full" : "none",
+                                apkPath,
+                                idsigPath,
+                                instanceImgPath));
+        Collections.addAll(cmd, additionalArgs);
 
+        android.run(cmd.toArray(new String[0]));
         return isTombstoneReceivedFromHostLogcat();
     }
 
+    private boolean isTombstoneGeneratedWithCrashPayload(boolean debuggable) throws Exception {
+        return isTombstoneGeneratedWithVmRunApp(
+                debuggable, "--payload-binary-name", "MicrodroidCrashNativeLib.so");
+    }
+
     @Test
     public void testTombstonesAreGeneratedWithCrashPayload() throws Exception {
         assertThat(isTombstoneGeneratedWithCrashPayload(true /* debuggable */)).isTrue();
@@ -641,6 +650,21 @@
         assertThat(isTombstoneGeneratedWithCrashPayload(false /* debuggable */)).isFalse();
     }
 
+    private boolean isTombstoneGeneratedWithCrashConfig(boolean debuggable) throws Exception {
+        return isTombstoneGeneratedWithVmRunApp(
+                debuggable, "--config-path", "assets/vm_config_crash.json");
+    }
+
+    @Test
+    public void testTombstonesAreGeneratedWithCrashConfig() throws Exception {
+        assertThat(isTombstoneGeneratedWithCrashConfig(true /* debuggable */)).isTrue();
+    }
+
+    @Test
+    public void testTombstonesAreNotGeneratedWithCrashConfigWhenNonDebuggable() throws Exception {
+        assertThat(isTombstoneGeneratedWithCrashConfig(false /* debuggable */)).isFalse();
+    }
+
     @Test
     public void testTelemetryPushedAtoms() throws Exception {
         // Reset statsd config and report before the test
diff --git a/tests/hostside/java/com/android/microdroid/test/PvmfwDebugPolicyHostTests.java b/tests/hostside/java/com/android/microdroid/test/PvmfwDebugPolicyHostTests.java
new file mode 100644
index 0000000..bda52dd
--- /dev/null
+++ b/tests/hostside/java/com/android/microdroid/test/PvmfwDebugPolicyHostTests.java
@@ -0,0 +1,201 @@
+/*
+ * 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.
+ */
+
+package com.android.microdroid.test;
+
+import static com.android.tradefed.device.TestDevice.MicrodroidBuilder;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.microdroid.test.host.CommandRunner;
+import com.android.microdroid.test.host.MicrodroidHostTestCaseBase;
+import com.android.microdroid.test.host.Pvmfw;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.TestDevice;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.util.FileUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.Objects;
+import java.io.FileNotFoundException;
+
+/** Tests debug policy of pvmfw.bin with custom debug policy */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class PvmfwDebugPolicyHostTests extends MicrodroidHostTestCaseBase {
+    @NonNull private static final String PVMFW_FILE_NAME = "pvmfw_test.bin";
+    @NonNull private static final String BCC_FILE_NAME = "bcc.dat";
+    @NonNull private static final String PACKAGE_FILE_NAME = "MicrodroidTestApp.apk";
+    @NonNull private static final String PACKAGE_NAME = "com.android.microdroid.test";
+    @NonNull private static final String MICRODROID_DEBUG_LEVEL = "full";
+    @NonNull private static final String MICRODROID_CONFIG_PATH = "assets/vm_config_apex.json";
+    private static final int BOOT_COMPLETE_TIMEOUT = 30000; // 30 seconds
+
+    @NonNull private static final String CUSTOM_PVMFW_FILE_PREFIX = "pvmfw";
+    @NonNull private static final String CUSTOM_PVMFW_FILE_SUFFIX = ".bin";
+    @NonNull private static final String CUSTOM_PVMFW_IMG_PATH = TEST_ROOT + "/" + PVMFW_FILE_NAME;
+    @NonNull private static final String CUSTOM_PVMFW_IMG_PATH_PROP = "hypervisor.pvmfw.path";
+
+    @NonNull private static final String MICRODROID_CMDLINE_PATH = "/proc/cmdline";
+    @NonNull private static final String MICRODROID_DT_ROOT_PATH = "/proc/device-tree";
+
+    @NonNull
+    private static final String MICRODROID_DT_BOOTARGS_PATH =
+            MICRODROID_DT_ROOT_PATH + "/chosen/bootargs";
+
+    @NonNull
+    private static final String MICRODROID_DT_RAMDUMP_PATH =
+            MICRODROID_DT_ROOT_PATH + "/avf/guest/common/ramdump";
+
+    @NonNull private static final String HEX_STRING_ZERO = "00000000";
+    @NonNull private static final String HEX_STRING_ONE = "00000001";
+
+    @Nullable private static File mPvmfwBinFileOnHost;
+    @Nullable private static File mBccFileOnHost;
+
+    @Nullable private TestDevice mAndroidDevice;
+    @Nullable private ITestDevice mMicrodroidDevice;
+    @Nullable private File mCustomPvmfwBinFileOnHost;
+
+    @Before
+    public void setUp() throws Exception {
+        mAndroidDevice = (TestDevice) Objects.requireNonNull(getDevice());
+        assumeTrue(
+                "Skip if protected VMs are not supported",
+                mAndroidDevice.supportsMicrodroid(/* protectedVm= */ true));
+
+        mAndroidDevice.enableAdbRoot();
+
+        // tradefed copies the test artfacts under /tmp when running tests,
+        // so we should *find* the artifacts with the file name.
+        mPvmfwBinFileOnHost =
+                getTestInformation().getDependencyFile(PVMFW_FILE_NAME, /* targetFirst= */ false);
+        mBccFileOnHost =
+                getTestInformation().getDependencyFile(BCC_FILE_NAME, /* targetFirst= */ false);
+
+        // Check device capability
+        testIfDeviceIsCapable(mAndroidDevice);
+        assumeTrue(
+                "Protected VMs are not supported",
+                mAndroidDevice.supportsMicrodroid(/*protectedVm=*/ true));
+
+        // Prepare for loading pvmfw.bin
+        // File will be setup in individual test,
+        // and then pushed to device in launchProtectedVmAndWaitForBootCompleted.
+        mCustomPvmfwBinFileOnHost =
+                FileUtil.createTempFile(CUSTOM_PVMFW_FILE_PREFIX, CUSTOM_PVMFW_FILE_SUFFIX);
+        mAndroidDevice.setProperty(CUSTOM_PVMFW_IMG_PATH_PROP, CUSTOM_PVMFW_IMG_PATH);
+
+        // Prepare for launching microdroid
+        mAndroidDevice.installPackage(findTestFile(PACKAGE_FILE_NAME), /* reinstall */ false);
+        prepareVirtualizationTestSetup(mAndroidDevice);
+        mMicrodroidDevice = null;
+    }
+
+    @After
+    public void shutdown() throws Exception {
+        if (!mAndroidDevice.supportsMicrodroid(/* protectedVm= */ true)) {
+            return;
+        }
+        if (mMicrodroidDevice != null) {
+            mAndroidDevice.shutdownMicrodroid(mMicrodroidDevice);
+            mMicrodroidDevice = null;
+        }
+        mAndroidDevice.uninstallPackage(PACKAGE_NAME);
+
+        // Cleanup for custom pvmfw.bin
+        mAndroidDevice.setProperty(CUSTOM_PVMFW_IMG_PATH_PROP, "");
+        FileUtil.deleteFile(mCustomPvmfwBinFileOnHost);
+
+        cleanUpVirtualizationTestSetup(mAndroidDevice);
+
+        mAndroidDevice.disableAdbRoot();
+    }
+
+    @Test
+    public void testRamdump() throws Exception {
+        Pvmfw pvmfw = createPvmfw("avf_debug_policy_with_ramdump.dtbo");
+        pvmfw.serialize(mCustomPvmfwBinFileOnHost);
+        mMicrodroidDevice = launchProtectedVmAndWaitForBootCompleted();
+
+        assertThat(readMicrodroidFileAsString(MICRODROID_CMDLINE_PATH)).contains("crashkernel=");
+        assertThat(readMicrodroidFileAsString(MICRODROID_DT_BOOTARGS_PATH))
+                .contains("crashkernel=");
+        assertThat(readMicrodroidFileAsHexString(MICRODROID_DT_RAMDUMP_PATH))
+                .isEqualTo(HEX_STRING_ONE);
+    }
+
+    @Test
+    public void testNoRamdump() throws Exception {
+        Pvmfw pvmfw = createPvmfw("avf_debug_policy_without_ramdump.dtbo");
+        pvmfw.serialize(mCustomPvmfwBinFileOnHost);
+        mMicrodroidDevice = launchProtectedVmAndWaitForBootCompleted();
+
+        assertThat(readMicrodroidFileAsString(MICRODROID_CMDLINE_PATH))
+                .doesNotContain("crashkernel=");
+        assertThat(readMicrodroidFileAsString(MICRODROID_DT_BOOTARGS_PATH))
+                .doesNotContain("crashkernel=");
+        assertThat(readMicrodroidFileAsHexString(MICRODROID_DT_RAMDUMP_PATH))
+                .isEqualTo(HEX_STRING_ZERO);
+    }
+
+    @NonNull
+    private String readMicrodroidFileAsString(@NonNull String path)
+            throws DeviceNotAvailableException {
+        return new CommandRunner(mMicrodroidDevice).run("cat", path);
+    }
+
+    @NonNull
+    private String readMicrodroidFileAsHexString(@NonNull String path)
+            throws DeviceNotAvailableException {
+        return new CommandRunner(mMicrodroidDevice).run("xxd", "-p", path);
+    }
+
+    @NonNull
+    private Pvmfw createPvmfw(@NonNull String debugPolicyFileName) throws FileNotFoundException {
+        File file =
+                getTestInformation()
+                        .getDependencyFile(debugPolicyFileName, /* targetFirst= */ false);
+        return new Pvmfw.Builder(mPvmfwBinFileOnHost, mBccFileOnHost)
+                .setDebugPolicyOverlay(file)
+                .build();
+    }
+
+    private ITestDevice launchProtectedVmAndWaitForBootCompleted()
+            throws DeviceNotAvailableException {
+        mMicrodroidDevice =
+                MicrodroidBuilder.fromDevicePath(
+                                getPathForPackage(PACKAGE_NAME), MICRODROID_CONFIG_PATH)
+                        .debugLevel(MICRODROID_DEBUG_LEVEL)
+                        .protectedVm(/* protectedVm= */ true)
+                        .addBootFile(mCustomPvmfwBinFileOnHost, PVMFW_FILE_NAME)
+                        .build(mAndroidDevice);
+        assertThat(mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT)).isTrue();
+        assertThat(mMicrodroidDevice.enableAdbRoot()).isTrue();
+
+        return mMicrodroidDevice;
+    }
+}
diff --git a/tests/testapk/assets/vm_config_crash.json b/tests/testapk/assets/vm_config_crash.json
new file mode 100644
index 0000000..ce6af80
--- /dev/null
+++ b/tests/testapk/assets/vm_config_crash.json
@@ -0,0 +1,9 @@
+{
+  "os": {
+    "name": "microdroid"
+  },
+  "task": {
+    "type": "microdroid_launcher",
+    "command": "MicrodroidCrashNativeLib.so"
+  }
+}
diff --git a/virtualizationmanager/Android.bp b/virtualizationmanager/Android.bp
index a436cea..e82797e 100644
--- a/virtualizationmanager/Android.bp
+++ b/virtualizationmanager/Android.bp
@@ -32,6 +32,7 @@
         "libclap",
         "libcommand_fds",
         "libdisk",
+        "libhypervisor_props",
         "liblazy_static",
         "liblibc",
         "liblog_rust",
diff --git a/virtualizationmanager/src/main.rs b/virtualizationmanager/src/main.rs
index dca64cb..3f0b64b 100644
--- a/virtualizationmanager/src/main.rs
+++ b/virtualizationmanager/src/main.rs
@@ -23,7 +23,7 @@
 
 use crate::aidl::{GLOBAL_SERVICE, VirtualizationService};
 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::IVirtualizationService::BnVirtualizationService;
-use anyhow::{bail, Context};
+use anyhow::{bail, Context, Result};
 use binder::{BinderFeatures, ProcessState};
 use lazy_static::lazy_static;
 use log::{info, Level};
@@ -33,7 +33,6 @@
 use nix::fcntl::{fcntl, F_GETFD, F_SETFD, FdFlag};
 use nix::unistd::{Pid, Uid};
 use std::os::unix::raw::{pid_t, uid_t};
-use rustutils::system_properties;
 
 const LOG_TAG: &str = "virtmgr";
 
@@ -92,9 +91,15 @@
     Ok(unsafe { OwnedFd::from_raw_fd(raw_fd) })
 }
 
-fn is_property_set(name: &str) -> bool {
-    system_properties::read_bool(name, false)
-        .unwrap_or_else(|e| panic!("Failed to read {name}: {e:?}"))
+fn check_vm_support() -> Result<()> {
+    if hypervisor_props::is_any_vm_supported()? {
+        Ok(())
+    } else {
+        // This should never happen, it indicates a misconfigured device where the virt APEX
+        // is present but VMs are not supported. If it does happen, fail fast to avoid wasting
+        // resources trying.
+        bail!("Device doesn't support protected or non-protected VMs")
+    }
 }
 
 fn main() {
@@ -105,14 +110,7 @@
             .with_log_id(android_logger::LogId::System),
     );
 
-    let non_protected_vm_supported = is_property_set("ro.boot.hypervisor.vm.supported");
-    let protected_vm_supported = is_property_set("ro.boot.hypervisor.protected_vm.supported");
-    if !non_protected_vm_supported && !protected_vm_supported {
-        // This should never happen, it indicates a misconfigured device where the virt APEX
-        // is present but VMs are not supported. If it does happen, fail fast to avoid wasting
-        // resources trying.
-        panic!("Device doesn't support protected or unprotected VMs");
-    }
+    check_vm_support().unwrap();
 
     let args = Args::parse();
 
diff --git a/vm/Android.bp b/vm/Android.bp
index e217786..50e68cc 100644
--- a/vm/Android.bp
+++ b/vm/Android.bp
@@ -15,11 +15,11 @@
         "libclap",
         "libenv_logger",
         "libglob",
+        "libhypervisor_props",
         "liblibc",
         "liblog_rust",
         "libmicrodroid_payload_config",
         "librand",
-        "librustutils",
         "libserde_json",
         "libserde",
         "libvmconfig",
diff --git a/vm/src/main.rs b/vm/src/main.rs
index e1c3413..6c08a19 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -23,12 +23,11 @@
     PartitionType::PartitionType, VirtualMachineAppConfig::DebugLevel::DebugLevel,
 };
 use anyhow::{Context, Error};
-use binder::ProcessState;
+use binder::{ProcessState, Strong};
 use clap::Parser;
 use create_idsig::command_create_idsig;
 use create_partition::command_create_partition;
 use run::{command_run, command_run_app, command_run_microdroid};
-use rustutils::system_properties;
 use std::path::{Path, PathBuf};
 
 #[derive(Debug)]
@@ -230,6 +229,12 @@
     }
 }
 
+fn get_service() -> Result<Strong<dyn IVirtualizationService>, Error> {
+    let virtmgr =
+        vmclient::VirtualizationService::new().context("Failed to spawn VirtualizationService")?;
+    virtmgr.connect().context("Failed to connect to VirtualizationService")
+}
+
 fn main() -> Result<(), Error> {
     env_logger::init();
     let opt = Opt::parse();
@@ -237,10 +242,6 @@
     // We need to start the thread pool for Binder to work properly, especially link_to_death.
     ProcessState::start_thread_pool();
 
-    let virtmgr =
-        vmclient::VirtualizationService::new().context("Failed to spawn VirtualizationService")?;
-    let service = virtmgr.connect().context("Failed to connect to VirtualizationService")?;
-
     match opt {
         Opt::RunApp {
             name,
@@ -261,7 +262,7 @@
             extra_idsigs,
         } => command_run_app(
             name,
-            service.as_ref(),
+            get_service()?.as_ref(),
             &apk,
             &idsig,
             &instance,
@@ -292,7 +293,7 @@
             task_profiles,
         } => command_run_microdroid(
             name,
-            service.as_ref(),
+            get_service()?.as_ref(),
             work_dir,
             storage.as_deref(),
             storage_size,
@@ -307,7 +308,7 @@
         Opt::Run { name, config, cpu_topology, task_profiles, console, log } => {
             command_run(
                 name,
-                service.as_ref(),
+                get_service()?.as_ref(),
                 &config,
                 console.as_deref(),
                 log.as_deref(),
@@ -316,12 +317,14 @@
                 task_profiles,
             )
         }
-        Opt::List => command_list(service.as_ref()),
+        Opt::List => command_list(get_service()?.as_ref()),
         Opt::Info => command_info(),
         Opt::CreatePartition { path, size, partition_type } => {
-            command_create_partition(service.as_ref(), &path, size, partition_type)
+            command_create_partition(get_service()?.as_ref(), &path, size, partition_type)
         }
-        Opt::CreateIdsig { apk, path } => command_create_idsig(service.as_ref(), &apk, &path),
+        Opt::CreateIdsig { apk, path } => {
+            command_create_idsig(get_service()?.as_ref(), &apk, &path)
+        }
     }
 }
 
@@ -334,10 +337,8 @@
 
 /// Print information about supported VM types.
 fn command_info() -> Result<(), Error> {
-    let non_protected_vm_supported =
-        system_properties::read_bool("ro.boot.hypervisor.vm.supported", false)?;
-    let protected_vm_supported =
-        system_properties::read_bool("ro.boot.hypervisor.protected_vm.supported", false)?;
+    let non_protected_vm_supported = hypervisor_props::is_vm_supported()?;
+    let protected_vm_supported = hypervisor_props::is_protected_vm_supported()?;
     match (non_protected_vm_supported, protected_vm_supported) {
         (false, false) => println!("VMs are not supported."),
         (false, true) => println!("Only protected VMs are supported."),
@@ -345,7 +346,7 @@
         (true, true) => println!("Both protected and non-protected VMs are supported."),
     }
 
-    if let Some(version) = system_properties::read("ro.boot.hypervisor.version")? {
+    if let Some(version) = hypervisor_props::version()? {
         println!("Hypervisor version: {}", version);
     } else {
         println!("Hypervisor version not set.");