Merge "Refactor Dice policy test" into main
diff --git a/Android.bp b/Android.bp
index 64d193a..550a6be 100644
--- a/Android.bp
+++ b/Android.bp
@@ -14,18 +14,7 @@
 // limitations under the License.
 
 package {
-    default_applicable_licenses: ["packages_modules_Virtualization_license"],
-}
-
-// Added automatically by a large-scale-change
-// See: http://go/android-license-faq
-license {
-    name: "packages_modules_Virtualization_license",
-    visibility: [":__subpackages__"],
-    license_kinds: [
-        "legacy_unencumbered",
-    ],
-    // large-scale-change unable to identify any license_text files
+    default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
 soong_config_module_type {
diff --git a/apex/Android.bp b/apex/Android.bp
index ccbdb3b..a05f7b0 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -13,7 +13,6 @@
     config_namespace: "ANDROID",
     bool_variables: [
         "avf_enabled",
-        "avf_kernel_modules_enabled",
     ],
     properties: [
         "defaults",
@@ -30,9 +29,6 @@
                 defaults: ["com.android.virt_avf_disabled"],
             },
         },
-        avf_kernel_modules_enabled: {
-            prebuilts: ["microdroid_kernel_with_modules"],
-        },
     },
 }
 
diff --git a/apkdmverity/Android.bp b/apkdmverity/Android.bp
index cc54d2e..c4c90cd 100644
--- a/apkdmverity/Android.bp
+++ b/apkdmverity/Android.bp
@@ -14,7 +14,6 @@
         "libapkverify",
         "libbitflags",
         "libclap",
-        "libdata_model",
         "libdm_rust",
         "libitertools",
         "liblibc",
@@ -22,6 +21,7 @@
         "libnum_traits",
         "libscopeguard",
         "libuuid",
+        "libzerocopy",
     ],
     proc_macros: ["libnum_derive"],
     multilib: {
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index 675a046..16f9631 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -1382,7 +1382,7 @@
                     return ERROR_PAYLOAD_VERIFICATION_FAILED;
                 case ErrorCode.PAYLOAD_CHANGED:
                     return ERROR_PAYLOAD_CHANGED;
-                case ErrorCode.PAYLOAD_CONFIG_INVALID:
+                case ErrorCode.PAYLOAD_INVALID_CONFIG:
                     return ERROR_PAYLOAD_INVALID_CONFIG;
                 default:
                     return ERROR_UNKNOWN;
diff --git a/libs/cborutil/Android.bp b/libs/cborutil/Android.bp
new file mode 100644
index 0000000..4758c4b
--- /dev/null
+++ b/libs/cborutil/Android.bp
@@ -0,0 +1,42 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+    name: "libcbor_util_defaults",
+    crate_name: "cbor_util",
+    srcs: ["src/lib.rs"],
+    defaults: ["avf_build_flags_rust"],
+    prefer_rlib: true,
+    apex_available: [
+        "com.android.virt",
+    ],
+}
+
+rust_library_rlib {
+    name: "libcbor_util_nostd",
+    defaults: ["libcbor_util_defaults"],
+    no_stdlibs: true,
+    stdlibs: [
+        "libcompiler_builtins.rust_sysroot",
+        "libcore.rust_sysroot",
+    ],
+    rustlibs: [
+        "libciborium_nostd",
+        "libcoset_nostd",
+        "libserde_nostd",
+    ],
+}
+
+rust_library {
+    name: "libcbor_util",
+    defaults: ["libcbor_util_defaults"],
+    features: [
+        "std",
+    ],
+    rustlibs: [
+        "libciborium",
+        "libcoset",
+        "libserde",
+    ],
+}
diff --git a/service_vm/requests/src/cbor.rs b/libs/cborutil/src/lib.rs
similarity index 84%
rename from service_vm/requests/src/cbor.rs
rename to libs/cborutil/src/lib.rs
index 36492e5..2ec5af4 100644
--- a/service_vm/requests/src/cbor.rs
+++ b/libs/cborutil/src/lib.rs
@@ -14,12 +14,16 @@
 
 //! Utility functions for CBOR serialization/deserialization.
 
+#![cfg_attr(not(feature = "std"), no_std)]
+
+extern crate alloc;
+
 use alloc::vec::Vec;
 use coset::{CoseError, Result};
 use serde::{de::DeserializeOwned, Serialize};
 
 /// Serializes the given data to a CBOR-encoded byte vector.
-pub(crate) fn serialize<T: ?Sized + Serialize>(v: &T) -> Result<Vec<u8>> {
+pub fn serialize<T: ?Sized + Serialize>(v: &T) -> Result<Vec<u8>> {
     let mut data = Vec::new();
     ciborium::into_writer(v, &mut data)?;
     Ok(data)
@@ -27,7 +31,7 @@
 
 /// Deserializes the given type from a CBOR-encoded byte slice, failing if any extra
 /// data remains after the type has been read.
-pub(crate) fn deserialize<T: DeserializeOwned>(mut data: &[u8]) -> Result<T> {
+pub fn deserialize<T: DeserializeOwned>(mut data: &[u8]) -> Result<T> {
     let res = ciborium::from_reader(&mut data)?;
     if data.is_empty() {
         Ok(res)
diff --git a/libs/devicemapper/Android.bp b/libs/devicemapper/Android.bp
index 8f9c25c..5332469 100644
--- a/libs/devicemapper/Android.bp
+++ b/libs/devicemapper/Android.bp
@@ -13,10 +13,10 @@
         "libanyhow",
         "libbitflags",
         "liblibc",
-        "libdata_model",
         "libhex",
         "libnix",
         "libuuid",
+        "libzerocopy",
     ],
     multilib: {
         lib32: {
diff --git a/libs/devicemapper/src/crypt.rs b/libs/devicemapper/src/crypt.rs
index 8281b34..36c45c7 100644
--- a/libs/devicemapper/src/crypt.rs
+++ b/libs/devicemapper/src/crypt.rs
@@ -20,10 +20,10 @@
 use crate::DmTargetSpec;
 
 use anyhow::{ensure, Context, Result};
-use data_model::DataInit;
 use std::io::Write;
 use std::mem::size_of;
 use std::path::Path;
+use zerocopy::AsBytes;
 
 const SECTOR_SIZE: u64 = 512;
 
@@ -174,7 +174,7 @@
         header.next = aligned_size as u32;
 
         let mut buf = Vec::with_capacity(aligned_size);
-        buf.write_all(header.as_slice())?;
+        buf.write_all(header.as_bytes())?;
         buf.write_all(body.as_bytes())?;
         buf.write_all(vec![0; padding].as_slice())?;
 
diff --git a/libs/devicemapper/src/lib.rs b/libs/devicemapper/src/lib.rs
index 868ac5a..8d004f9 100644
--- a/libs/devicemapper/src/lib.rs
+++ b/libs/devicemapper/src/lib.rs
@@ -32,12 +32,13 @@
 #![cfg_attr(test, allow(unused))]
 
 use anyhow::{Context, Result};
-use data_model::DataInit;
 use std::fs::{File, OpenOptions};
 use std::io::Write;
 use std::mem::size_of;
 use std::os::unix::io::AsRawFd;
 use std::path::{Path, PathBuf};
+use zerocopy::AsBytes;
+use zerocopy::FromZeroes;
 
 /// Exposes DmCryptTarget & related builder
 pub mod crypt;
@@ -87,7 +88,7 @@
 // `DmTargetSpec` is the header of the data structure for a device-mapper target. When doing the
 // ioctl, one of more `DmTargetSpec` (and its body) are appened to the `DmIoctl` struct.
 #[repr(C)]
-#[derive(Copy, Clone)]
+#[derive(Copy, Clone, AsBytes, FromZeroes)]
 struct DmTargetSpec {
     sector_start: u64,
     length: u64, // number of 512 sectors
@@ -96,13 +97,9 @@
     target_type: [u8; DM_MAX_TYPE_NAME],
 }
 
-// SAFETY: C struct is safe to be initialized from raw data
-unsafe impl DataInit for DmTargetSpec {}
-
 impl DmTargetSpec {
     fn new(target_type: &str) -> Result<Self> {
-        // safe because the size of the array is the same as the size of the struct
-        let mut spec: Self = *DataInit::from_mut_slice(&mut [0; size_of::<Self>()]).unwrap();
+        let mut spec = Self::new_zeroed();
         spec.target_type.as_mut().write_all(target_type.as_bytes())?;
         Ok(spec)
     }
@@ -110,8 +107,7 @@
 
 impl DmIoctl {
     fn new(name: &str) -> Result<DmIoctl> {
-        // safe because the size of the array is the same as the size of the struct
-        let mut data: Self = *DataInit::from_mut_slice(&mut [0; size_of::<Self>()]).unwrap();
+        let mut data: Self = Self::new_zeroed();
         data.version[0] = DM_VERSION_MAJOR;
         data.version[1] = DM_VERSION_MINOR;
         data.version[2] = DM_VERSION_PATCHLEVEL;
@@ -202,7 +198,7 @@
         }
 
         let mut payload = Vec::with_capacity(payload_size);
-        payload.extend_from_slice(data.as_slice());
+        payload.extend_from_slice(data.as_bytes());
         payload.extend_from_slice(target);
         dm_table_load(self, payload.as_mut_ptr() as *mut DmIoctl)
             .context("failed to load table")?;
diff --git a/libs/devicemapper/src/loopdevice.rs b/libs/devicemapper/src/loopdevice.rs
index 16b5b65..9dc722b 100644
--- a/libs/devicemapper/src/loopdevice.rs
+++ b/libs/devicemapper/src/loopdevice.rs
@@ -25,15 +25,14 @@
 
 use crate::util::*;
 use anyhow::{Context, Result};
-use data_model::DataInit;
 use libc::O_DIRECT;
 use std::fs::{File, OpenOptions};
-use std::mem::size_of;
 use std::os::unix::fs::OpenOptionsExt;
 use std::os::unix::io::AsRawFd;
 use std::path::{Path, PathBuf};
 use std::thread;
 use std::time::{Duration, Instant};
+use zerocopy::FromZeroes;
 
 use crate::loopdevice::sys::*;
 
@@ -122,9 +121,7 @@
         .custom_flags(if direct_io { O_DIRECT } else { 0 })
         .open(&path)
         .context(format!("failed to open {:?}", path.as_ref()))?;
-    // safe because the size of the array is the same as the size of the struct
-    let mut config: loop_config =
-        *DataInit::from_mut_slice(&mut [0; size_of::<loop_config>()]).unwrap();
+    let mut config = loop_config::new_zeroed();
     config.fd = backing_file.as_raw_fd() as u32;
     config.block_size = 4096;
     config.info.lo_offset = offset;
diff --git a/libs/devicemapper/src/loopdevice/sys.rs b/libs/devicemapper/src/loopdevice/sys.rs
index a35eaa9..ce4ef61 100644
--- a/libs/devicemapper/src/loopdevice/sys.rs
+++ b/libs/devicemapper/src/loopdevice/sys.rs
@@ -15,7 +15,7 @@
  */
 
 use bitflags::bitflags;
-use data_model::DataInit;
+use zerocopy::FromZeroes;
 
 // This UAPI is copied and converted from include/uapi/linux/loop.h Note that this module doesn't
 // implement all the features introduced in loop(4). Only the features that are required to support
@@ -28,7 +28,7 @@
 pub const LOOP_CLR_FD: libc::c_ulong = 0x4C01;
 
 #[repr(C)]
-#[derive(Copy, Clone)]
+#[derive(Copy, Clone, FromZeroes)]
 pub struct loop_config {
     pub fd: u32,
     pub block_size: u32,
@@ -36,11 +36,8 @@
     pub reserved: [u64; 8],
 }
 
-// SAFETY: C struct is safe to be initialized from raw data
-unsafe impl DataInit for loop_config {}
-
 #[repr(C)]
-#[derive(Copy, Clone)]
+#[derive(Copy, Clone, FromZeroes)]
 pub struct loop_info64 {
     pub lo_device: u64,
     pub lo_inode: u64,
@@ -57,13 +54,12 @@
     pub lo_init: [u64; 2],
 }
 
-// SAFETY: C struct is safe to be initialized from raw data
-unsafe impl DataInit for loop_info64 {}
+#[repr(transparent)]
+#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, FromZeroes)]
+pub struct Flag(u32);
 
 bitflags! {
-    #[repr(transparent)]
-    #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
-    pub struct Flag: u32 {
+    impl Flag: u32 {
         const LO_FLAGS_READ_ONLY = 1 << 0;
         const LO_FLAGS_AUTOCLEAR = 1 << 2;
         const LO_FLAGS_PARTSCAN = 1 << 3;
diff --git a/libs/devicemapper/src/sys.rs b/libs/devicemapper/src/sys.rs
index 3cecde3..bd1e165 100644
--- a/libs/devicemapper/src/sys.rs
+++ b/libs/devicemapper/src/sys.rs
@@ -15,7 +15,8 @@
  */
 
 use bitflags::bitflags;
-use data_model::DataInit;
+use zerocopy::AsBytes;
+use zerocopy::FromZeroes;
 
 // UAPI for device mapper can be found at include/uapi/linux/dm-ioctl.h
 
@@ -44,7 +45,7 @@
 }
 
 #[repr(C)]
-#[derive(Copy, Clone)]
+#[derive(Copy, Clone, AsBytes, FromZeroes)]
 pub struct DmIoctl {
     pub version: [u32; 3],
     pub data_size: u32,
@@ -60,9 +61,6 @@
     pub data: [u8; 7],
 }
 
-// SAFETY: C struct is safe to be initialized from raw data
-unsafe impl DataInit for DmIoctl {}
-
 pub const DM_VERSION_MAJOR: u32 = 4;
 pub const DM_VERSION_MINOR: u32 = 0;
 pub const DM_VERSION_PATCHLEVEL: u32 = 0;
@@ -71,10 +69,12 @@
 pub const DM_UUID_LEN: usize = 129;
 pub const DM_MAX_TYPE_NAME: usize = 16;
 
+#[repr(transparent)]
+#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, AsBytes, FromZeroes)]
+pub struct Flag(u32);
+
 bitflags! {
-    #[repr(transparent)]
-    #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
-    pub struct Flag: u32 {
+    impl Flag: u32 {
         const DM_READONLY_FLAG = 1 << 0;
         const DM_SUSPEND_FLAG = 1 << 1;
         const DM_PERSISTENT_DEV_FLAG = 1 << 3;
diff --git a/libs/devicemapper/src/verity.rs b/libs/devicemapper/src/verity.rs
index e0c5e52..24584f8 100644
--- a/libs/devicemapper/src/verity.rs
+++ b/libs/devicemapper/src/verity.rs
@@ -19,10 +19,10 @@
 // which is then given to `DeviceMapper` to create a mapper device.
 
 use anyhow::{bail, Context, Result};
-use data_model::DataInit;
 use std::io::Write;
 use std::mem::size_of;
 use std::path::Path;
+use zerocopy::AsBytes;
 
 use crate::util::*;
 use crate::DmTargetSpec;
@@ -195,7 +195,7 @@
         header.next = aligned_size as u32;
 
         let mut buf = Vec::with_capacity(aligned_size);
-        buf.write_all(header.as_slice())?;
+        buf.write_all(header.as_bytes())?;
         buf.write_all(body.as_bytes())?;
         buf.write_all(vec![0; padding].as_slice())?;
         Ok(DmVerityTarget(buf.into_boxed_slice()))
diff --git a/libs/dice/open_dice/src/ops.rs b/libs/dice/open_dice/src/ops.rs
index 7174d3e..47b5244 100644
--- a/libs/dice/open_dice/src/ops.rs
+++ b/libs/dice/open_dice/src/ops.rs
@@ -95,6 +95,10 @@
 ///
 /// The corresponding public key is included in the leaf certificate of the DICE chain
 /// contained in `dice_artifacts`.
+///
+/// Refer to the following documentation for more information about CDI_Leaf_Priv:
+///
+/// security/rkp/aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl
 pub fn derive_cdi_leaf_priv(dice_artifacts: &dyn DiceArtifacts) -> Result<PrivateKey> {
     let cdi_priv_key_seed = derive_cdi_private_key_seed(dice_artifacts.cdi_attest())?;
     let (_, private_key) = keypair_from_seed(cdi_priv_key_seed.as_array())?;
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index b494cfa..42ff4b0 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -471,46 +471,3 @@
         },
     },
 }
-
-flag_aware_avb_add_hash_footer {
-    name: "microdroid_kernel_with_modules_signed",
-    src: ":empty_file",
-    filename: "microdroid_kernel_with_modules",
-    partition_name: "boot",
-    private_key: ":microdroid_sign_key",
-    salt: bootloader_salt,
-    enabled: false,
-    arch: {
-        arm64: {
-            src: ":microdroid_kernel_with_modules_prebuilts-6.1-arm64",
-            enabled: true,
-        },
-    },
-    include_descriptors_from_images: [
-        ":microdroid_initrd_normal_hashdesc",
-        ":microdroid_initrd_debug_hashdesc",
-    ],
-    // Below are properties that are conditionally set depending on value of build flags.
-    soong_config_variables: {
-        release_avf_enable_llpvm_changes: {
-            rollback_index: 1,
-            props: [
-                {
-                    name: "com.android.virt.cap",
-                    value: "secretkeeper_protection",
-                },
-            ],
-        },
-    },
-}
-
-prebuilt_etc {
-    name: "microdroid_kernel_with_modules",
-    src: ":empty_file",
-    relative_install_path: "fs",
-    arch: {
-        arm64: {
-            src: ":microdroid_kernel_with_modules_signed",
-        },
-    },
-}
diff --git a/microdroid/kernel/with-modules/Android.bp b/microdroid/kernel/with-modules/Android.bp
deleted file mode 100644
index f1ec06e..0000000
--- a/microdroid/kernel/with-modules/Android.bp
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (C) 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 {
-    default_applicable_licenses: ["microdroid_kernel_with_modules_prebuilts_6.1_arm64_license"],
-}
-
-// See: http://go/android-license-faq
-license {
-    name: "microdroid_kernel_with_modules_prebuilts_6.1_arm64_license",
-    visibility: [":__subpackages__"],
-    license_kinds: [
-        "SPDX-license-identifier-GPL-2.0-only",
-    ],
-    // large-scale-change unable to identify any license_text files
-}
-
-filegroup {
-    name: "microdroid_kernel_with_modules_prebuilts-6.1-arm64",
-    srcs: ["kernel_with_modules-6.1"],
-}
diff --git a/microdroid/kernel/with-modules/README.md b/microdroid/kernel/with-modules/README.md
deleted file mode 100644
index 46f6a59..0000000
--- a/microdroid/kernel/with-modules/README.md
+++ /dev/null
@@ -1,10 +0,0 @@
-# Microdroid kernel with modules
-
-This directory contains prebuilts of the Microdroid kernel with support for
-loading vendor modules. Only arm64 architecture is supported.
-
-NOTE: the prebuilt was generated on a local machine, and can only be used on
-development devices.
-
-NOTE: this feature is WIP and kernel is subject to change.
-
diff --git a/microdroid/kernel/with-modules/kernel_with_modules-6.1 b/microdroid/kernel/with-modules/kernel_with_modules-6.1
deleted file mode 100755
index b732ce3..0000000
--- a/microdroid/kernel/with-modules/kernel_with_modules-6.1
+++ /dev/null
Binary files differ
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 8175753..7ba54f8 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -92,7 +92,7 @@
     #[error("Payload verification has failed: {0}")]
     PayloadVerificationFailed(String),
     #[error("Payload config is invalid: {0}")]
-    InvalidConfig(String),
+    PayloadInvalidConfig(String),
 }
 
 fn translate_error(err: &Error) -> (ErrorCode, String) {
@@ -102,8 +102,8 @@
             MicrodroidError::PayloadVerificationFailed(msg) => {
                 (ErrorCode::PAYLOAD_VERIFICATION_FAILED, msg.to_string())
             }
-            MicrodroidError::InvalidConfig(msg) => {
-                (ErrorCode::PAYLOAD_CONFIG_INVALID, msg.to_string())
+            MicrodroidError::PayloadInvalidConfig(msg) => {
+                (ErrorCode::PAYLOAD_INVALID_CONFIG, msg.to_string())
             }
 
             // Connection failure won't be reported to VS; return the default value
@@ -126,7 +126,7 @@
             MicrodroidError::PayloadVerificationFailed(_) => {
                 "MICRODROID_PAYLOAD_VERIFICATION_FAILED"
             }
-            MicrodroidError::InvalidConfig(_) => "MICRODROID_INVALID_PAYLOAD_CONFIG",
+            MicrodroidError::PayloadInvalidConfig(_) => "MICRODROID_INVALID_PAYLOAD_CONFIG",
         })
     } else {
         // Send context information back after a separator, to ease diagnosis.
@@ -326,12 +326,14 @@
         if is_new_instance() {
             ensure!(
                 saved_data.is_none(),
-                MicrodroidError::InvalidConfig("Found instance data on first boot.".to_string())
+                MicrodroidError::PayloadInvalidConfig(
+                    "Found instance data on first boot.".to_string()
+                )
             );
         } else {
             ensure!(
                 saved_data.is_some(),
-                MicrodroidError::InvalidConfig("Instance data not found.".to_string())
+                MicrodroidError::PayloadInvalidConfig("Instance data not found.".to_string())
             );
         };
     }
@@ -367,7 +369,7 @@
     };
 
     let payload_metadata = metadata.payload.ok_or_else(|| {
-        MicrodroidError::InvalidConfig("No payload config in metadata".to_string())
+        MicrodroidError::PayloadInvalidConfig("No payload config in metadata".to_string())
     })?;
 
     // To minimize the exposure to untrusted data, derive dice profile as soon as possible.
@@ -407,7 +409,7 @@
     let task = config
         .task
         .as_ref()
-        .ok_or_else(|| MicrodroidError::InvalidConfig("No task in VM config".to_string()))?;
+        .ok_or_else(|| MicrodroidError::PayloadInvalidConfig("No task in VM config".to_string()))?;
 
     ensure!(
         config.extra_apks.len() == verified_data.extra_apks_data.len(),
diff --git a/pvmfw/README.md b/pvmfw/README.md
index 698972a..124ef89 100644
--- a/pvmfw/README.md
+++ b/pvmfw/README.md
@@ -42,7 +42,7 @@
 
 [AVF]: https://source.android.com/docs/core/virtualization
 [why-avf]: https://source.android.com/docs/core/virtualization/whyavf
-[BCC]: https://pigweed.googlesource.com/open-dice/+/master/src/android/README.md
+[BCC]: https://pigweed.googlesource.com/open-dice/+/refs/heads/main/docs/android.md
 [pKVM]: https://source.android.com/docs/core/virtualization/architecture#hypervisor
 [open-dice]: https://pigweed.googlesource.com/open-dice/+/refs/heads/main/docs/specification.md
 
@@ -191,7 +191,7 @@
   pvmfw will provision assigned devices with the VM DTBO.
 
 [header]: src/config.rs
-[DTBO]: https://android.googlesource.com/platform/external/dtc/+/refs/heads/master/Documentation/dt-object-internal.txt
+[DTBO]: https://android.googlesource.com/platform/external/dtc/+/refs/heads/main/Documentation/dt-object-internal.txt
 [debug_policy]: ../docs/debug/README.md#debug-policy
 
 #### Virtual Platform Boot Certificate Chain Handover
@@ -416,20 +416,34 @@
 `avb_add_hash_footer` Soong module (see [how we sign the Microdroid
 kernel][soong-udroid]).
 
-[soong-udroid]: https://cs.android.com/android/platform/superproject/+/master:packages/modules/Virtualization/microdroid/Android.bp;l=427;drc=ca0049be4d84897b8c9956924cfae506773103eb
+[soong-udroid]: https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Virtualization/microdroid/Android.bp;l=425;drc=b94a5cf516307c4279f6c16a63803527a8affc6d
 
 ## Development
 
 For faster iteration, you can build pvmfw, adb-push it to the device, and use
 it directly for a new pVM, without having to flash it to the physical
-partition. To do that, set the system property `hypervisor.pvmfw.path` to point
-to the pvmfw image you pushed as shown below:
+partition. To do that, the binary image composition performed by ABL described
+above must be replicated to produce a single file containing the pvmfw binary
+and its configuration data.
+
+As a quick prototyping solution, a valid BCC (such as the [bcc.dat] test file)
+can be appended to the `pvmfw.bin` image with `pvmfw-tool`.
 
 ```shell
-m pvmfw_img
-adb push out/target/product/generic_arm64/system/etc/pvmfw.img /data/local/tmp/pvmfw.img
+m pvmfw-tool pvmfw_bin
+PVMFW_BIN=${ANDROID_PRODUCT_OUT}/system/etc/pvmfw.bin
+BCC_DAT=${ANDROID_BUILD_TOP}/packages/modules/Virtualization/tests/pvmfw/assets/bcc.dat
+
+pvmfw-tool custom_pvmfw ${PVMFW_BIN} ${BCC_DAT}
+```
+
+The result can then be pushed to the device. Pointing the system property
+`hypervisor.pvmfw.path` to it will cause AVF to use that image as pvmfw:
+
+```shell
+adb push custom_pvmfw /data/local/tmp/pvmfw
 adb root
-adb shell setprop hypervisor.pvmfw.path /data/local/tmp/pvmfw.img
+adb shell setprop hypervisor.pvmfw.path /data/local/tmp/pvmfw
 ```
 
 Then run a protected VM, for example:
@@ -439,3 +453,5 @@
 ```
 
 Note: `adb root` is required to set the system property.
+
+[bcc.dat]: https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Virtualization/tests/pvmfw/assets/bcc.dat
diff --git a/pvmfw/src/config.rs b/pvmfw/src/config.rs
index 926f42b..78b6323 100644
--- a/pvmfw/src/config.rs
+++ b/pvmfw/src/config.rs
@@ -16,8 +16,10 @@
 
 use core::fmt;
 use core::mem;
+use core::num::NonZeroUsize;
 use core::ops::Range;
 use core::result;
+use core::slice;
 use log::{info, warn};
 use static_assertions::const_assert_eq;
 use vmbase::util::RangeExt;
@@ -53,6 +55,8 @@
     MissingEntry(Entry),
     /// Range described by entry does not fit within config data.
     EntryOutOfBounds(Entry, Range<usize>, Range<usize>),
+    /// Entries are in out of order
+    EntryOutOfOrder,
 }
 
 impl fmt::Display for Error {
@@ -70,6 +74,7 @@
                     "Entry {entry:?} out of bounds: {range:#x?} must be within range {limits:#x?}"
                 )
             }
+            Self::EntryOutOfOrder => write!(f, "Entries are out of order"),
         }
     }
 }
@@ -133,19 +138,6 @@
     size: u32,
 }
 
-impl HeaderEntry {
-    pub fn as_range(&self) -> Option<Range<usize>> {
-        let size = usize::try_from(self.size).unwrap();
-        if size != 0 {
-            let offset = self.offset.try_into().unwrap();
-            // Allow overflows here for the Range to properly describe the entry (validated later).
-            Some(offset..(offset + size))
-        } else {
-            None
-        }
-    }
-}
-
 #[repr(C, packed)]
 #[derive(Clone, Copy, Debug, Eq, FromZeroes, FromBytes, PartialEq)]
 pub struct Version {
@@ -161,10 +153,38 @@
     }
 }
 
+/// Range with non-empty length.
+#[derive(Debug, Copy, Clone)]
+struct NonEmptyRange {
+    start: usize,
+    size: NonZeroUsize,
+}
+
+impl NonEmptyRange {
+    pub fn new(start: usize, size: usize) -> Option<Self> {
+        // Ensure end() is safe.
+        start.checked_add(size).unwrap();
+
+        Some(Self { start, size: NonZeroUsize::new(size)? })
+    }
+
+    fn end(&self) -> usize {
+        self.start + self.len()
+    }
+
+    fn len(&self) -> usize {
+        self.size.into()
+    }
+
+    fn as_range(&self) -> Range<usize> {
+        self.start..self.end()
+    }
+}
+
 #[derive(Debug)]
 pub struct Config<'a> {
     body: &'a mut [u8],
-    ranges: [Option<Range<usize>>; Entry::COUNT],
+    ranges: [Option<NonEmptyRange>; Entry::COUNT],
 }
 
 impl<'a> Config<'a> {
@@ -209,68 +229,64 @@
         // Validate that we won't get an invalid alignment in the following due to padding to u64.
         const_assert_eq!(mem::size_of::<HeaderEntry>() % mem::size_of::<u64>(), 0);
 
+        // Ensure entries are in the body.
         let limits = header.body_lowest_bound()?..total_size;
-        let ranges = [
-            // TODO: Find a way to do this programmatically even if the trait
-            // `core::marker::Copy` is not implemented for `core::ops::Range<usize>`.
-            Self::validated_body_range(Entry::Bcc, &header_entries, &limits)?,
-            Self::validated_body_range(Entry::DebugPolicy, &header_entries, &limits)?,
-            Self::validated_body_range(Entry::VmDtbo, &header_entries, &limits)?,
-        ];
+        let mut ranges: [Option<NonEmptyRange>; Entry::COUNT] = [None; Entry::COUNT];
+        let mut last_end = 0;
+        for entry in [Entry::Bcc, Entry::DebugPolicy, Entry::VmDtbo] {
+            let Some(header_entry) = header_entries.get(entry as usize) else { continue };
+            let entry_offset = header_entry.offset.try_into().unwrap();
+            let entry_size = header_entry.size.try_into().unwrap();
+            let Some(range) = NonEmptyRange::new(entry_offset, entry_size) else { continue };
+            let range = range.as_range();
+            if !range.is_within(&limits) {
+                return Err(Error::EntryOutOfBounds(entry, range, limits));
+            }
+
+            if last_end > range.start {
+                return Err(Error::EntryOutOfOrder);
+            }
+            last_end = range.end;
+
+            ranges[entry as usize] = NonEmptyRange::new(
+                entry_offset - limits.start, // is_within() validates safety of this.
+                entry_size,
+            );
+        }
+        // Ensures that BCC exists.
+        ranges[Entry::Bcc as usize].ok_or(Error::MissingEntry(Entry::Bcc))?;
 
         Ok(Self { body, ranges })
     }
 
     /// Get slice containing the platform BCC.
-    pub fn get_entries(&mut self) -> Result<(&mut [u8], Option<&mut [u8]>)> {
+    pub fn get_entries(&mut self) -> (&mut [u8], Option<&mut [u8]>) {
         // This assumes that the blobs are in-order w.r.t. the entries.
-        let bcc_range = self.get_entry_range(Entry::Bcc).ok_or(Error::MissingEntry(Entry::Bcc))?;
+        let bcc_range = self.get_entry_range(Entry::Bcc);
         let dp_range = self.get_entry_range(Entry::DebugPolicy);
         let vm_dtbo_range = self.get_entry_range(Entry::VmDtbo);
         // TODO(b/291191157): Provision device assignment with this.
         if let Some(vm_dtbo_range) = vm_dtbo_range {
             info!("Found VM DTBO at {:?}", vm_dtbo_range);
         }
-        let bcc_start = bcc_range.start;
-        let bcc_end = bcc_range.len();
-        let (_, rest) = self.body.split_at_mut(bcc_start);
-        let (bcc, rest) = rest.split_at_mut(bcc_end);
 
-        let dp = if let Some(dp_range) = dp_range {
-            let dp_start = dp_range.start.checked_sub(bcc_range.end).unwrap();
-            let dp_end = dp_range.len();
-            let (_, rest) = rest.split_at_mut(dp_start);
-            let (dp, _) = rest.split_at_mut(dp_end);
-            Some(dp)
-        } else {
-            None
-        };
-
-        Ok((bcc, dp))
-    }
-
-    pub fn get_entry_range(&self, entry: Entry) -> Option<Range<usize>> {
-        self.ranges[entry as usize].clone()
-    }
-
-    fn validated_body_range(
-        entry: Entry,
-        header_entries: &[HeaderEntry],
-        limits: &Range<usize>,
-    ) -> Result<Option<Range<usize>>> {
-        if let Some(header_entry) = header_entries.get(entry as usize) {
-            if let Some(r) = header_entry.as_range() {
-                return if r.start <= r.end && r.is_within(limits) {
-                    let start = r.start - limits.start;
-                    let end = r.end - limits.start;
-
-                    Ok(Some(start..end))
-                } else {
-                    Err(Error::EntryOutOfBounds(entry, r, limits.clone()))
-                };
-            }
+        // SAFETY: When instantiate, ranges are validated to be in the body range without
+        // overlapping.
+        unsafe {
+            let ptr = self.body.as_mut_ptr() as usize;
+            (
+                Self::from_raw_range_mut(ptr, bcc_range.unwrap()),
+                dp_range.map(|dp_range| Self::from_raw_range_mut(ptr, dp_range)),
+            )
         }
+    }
 
-        Ok(None)
+    fn get_entry_range(&self, entry: Entry) -> Option<NonEmptyRange> {
+        self.ranges[entry as usize]
+    }
+
+    unsafe fn from_raw_range_mut(ptr: usize, range: NonEmptyRange) -> &'a mut [u8] {
+        // SAFETY: The caller must ensure that the range is valid from ptr.
+        unsafe { slice::from_raw_parts_mut((ptr + range.start) as *mut u8, range.end()) }
     }
 }
diff --git a/pvmfw/src/entry.rs b/pvmfw/src/entry.rs
index 3efa61e..9c929a9 100644
--- a/pvmfw/src/entry.rs
+++ b/pvmfw/src/entry.rs
@@ -207,10 +207,7 @@
         RebootReason::InvalidConfig
     })?;
 
-    let (bcc_slice, debug_policy) = appended.get_entries().map_err(|e| {
-        error!("Failed to obtained the config entries: {e}");
-        RebootReason::InvalidConfig
-    })?;
+    let (bcc_slice, debug_policy) = appended.get_entries();
 
     // Up to this point, we were using the built-in static (from .rodata) page tables.
     MEMORY.lock().replace(MemoryTracker::new(
@@ -430,10 +427,10 @@
         }
     }
 
-    fn get_entries(&mut self) -> config::Result<(&mut [u8], Option<&mut [u8]>)> {
+    fn get_entries(&mut self) -> (&mut [u8], Option<&mut [u8]>) {
         match self {
             Self::Config(ref mut cfg) => cfg.get_entries(),
-            Self::LegacyBcc(ref mut bcc) => Ok((bcc, None)),
+            Self::LegacyBcc(ref mut bcc) => (bcc, None),
         }
     }
 }
diff --git a/service_vm/README.md b/service_vm/README.md
new file mode 100644
index 0000000..3d94f78
--- /dev/null
+++ b/service_vm/README.md
@@ -0,0 +1,45 @@
+# Service VM
+
+The Service VM is a lightweight, bare-metal virtual machine specifically
+designed to run various services for other virtual machines. It fulfills the
+following requirements:
+
+-   Only one instance of the Service VM is allowed to run at any given time.
+-   The *secret* contained within the instance image of the Service VM remains
+    unchanged during updates of both the client VMs and the Service VM.
+
+The secret is an encrypted random array that can only be decrypted by
+[pVM Firmware][pvmfw]. It is incorporated into the [CDI values][cdi] calculation
+of each VM loaded by pVM Firmware to ensure consistent CDI values for the VM
+across all reboots.
+
+[cdi]: https://android.googlesource.com/platform/external/open-dice/+/main/docs/specification.md#CDI-Values
+[pvmfw]: https://android.googlesource.com/platform/packages/modules/Virtualization/+/main/pvmfw/README.md
+
+## RKP VM (Remote Key Provisioning Virtual Machine)
+
+The RKP VM is a key dependency of the Service VM. It is a virtual machine that
+undergoes validation by the [RKP][rkp] Server and acts as a remotely provisioned
+component for verifying the integrity of other virtual machines.
+
+[rkp]: https://source.android.com/docs/core/ota/modular-system/remote-key-provisioning
+
+### RKP VM attestation
+
+The RKP VM is recognized and attested by the RKP server, which acts as a trusted
+entity responsible for verifying the DICE chain of the RKP VM. This verification
+ensures that the RKP VM is operating on a genuine device.
+Additionally, the RKP VM is validated by the pVM Firmware, as part of the
+verified boot process.
+
+### Client VM attestation
+
+Once the RKP VM is successfully attested, it assumes the role of a trusted
+platform to attest client VMs. It leverages its trusted status to validate the
+integrity of the [DICE chain][open-dice] associated with each client VM. This
+validation process verifies that the client VMs are running in the expected
+[Microdroid][microdroid] VM environment, and certifies the payload executed
+within the VM. Currently, only Microdroid VMs are supported.
+
+[open-dice]: https://android.googlesource.com/platform/external/open-dice/+/main/docs/android.md
+[microdroid]: https://android.googlesource.com/platform/packages/modules/Virtualization/+/main/microdroid/README.md
diff --git a/service_vm/requests/Android.bp b/service_vm/requests/Android.bp
index f85064a..ecede8b 100644
--- a/service_vm/requests/Android.bp
+++ b/service_vm/requests/Android.bp
@@ -18,6 +18,7 @@
     rustlibs: [
         "libbssl_avf_error_nostd",
         "libbssl_avf_nostd",
+        "libcbor_util_nostd",
         "libciborium_nostd",
         "libcoset_nostd",
         "libdiced_open_dice_nostd",
diff --git a/service_vm/requests/src/keyblob.rs b/service_vm/requests/src/keyblob.rs
index a714edd..456c879 100644
--- a/service_vm/requests/src/keyblob.rs
+++ b/service_vm/requests/src/keyblob.rs
@@ -14,7 +14,6 @@
 
 //! Handles the encryption and decryption of the key blob.
 
-use crate::cbor;
 use alloc::vec;
 use alloc::vec::Vec;
 use bssl_avf::{hkdf, rand_bytes, Aead, AeadContext, Digester, AES_GCM_NONCE_LENGTH};
@@ -70,16 +69,6 @@
             Self::V1(blob) => blob.decrypt_private_key(kek_secret),
         }
     }
-
-    // TODO(b/241428146): This function will be used once the retrieval mechanism is available.
-    #[cfg(test)]
-    pub(crate) fn from_cbor_slice(slice: &[u8]) -> coset::Result<Self> {
-        cbor::deserialize(slice)
-    }
-
-    pub(crate) fn to_cbor_vec(&self) -> coset::Result<Vec<u8>> {
-        cbor::serialize(&self)
-    }
 }
 
 impl EncryptedKeyBlobV1 {
@@ -136,8 +125,9 @@
 
     #[test]
     fn decrypting_keyblob_succeeds_with_the_same_kek() -> Result<()> {
-        let encrypted_key_blob = EncryptedKeyBlob::new(&TEST_KEY, &TEST_SECRET1)?.to_cbor_vec()?;
-        let encrypted_key_blob = EncryptedKeyBlob::from_cbor_slice(&encrypted_key_blob)?;
+        let encrypted_key_blob =
+            cbor_util::serialize(&EncryptedKeyBlob::new(&TEST_KEY, &TEST_SECRET1)?)?;
+        let encrypted_key_blob: EncryptedKeyBlob = cbor_util::deserialize(&encrypted_key_blob)?;
         let decrypted_key = encrypted_key_blob.decrypt_private_key(&TEST_SECRET1)?;
 
         assert_eq!(TEST_KEY, decrypted_key.as_slice());
@@ -146,8 +136,9 @@
 
     #[test]
     fn decrypting_keyblob_fails_with_a_different_kek() -> Result<()> {
-        let encrypted_key_blob = EncryptedKeyBlob::new(&TEST_KEY, &TEST_SECRET1)?.to_cbor_vec()?;
-        let encrypted_key_blob = EncryptedKeyBlob::from_cbor_slice(&encrypted_key_blob)?;
+        let encrypted_key_blob =
+            cbor_util::serialize(&EncryptedKeyBlob::new(&TEST_KEY, &TEST_SECRET1)?)?;
+        let encrypted_key_blob: EncryptedKeyBlob = cbor_util::deserialize(&encrypted_key_blob)?;
         let err = encrypted_key_blob.decrypt_private_key(&TEST_SECRET2).unwrap_err();
 
         let expected_err: RequestProcessingError =
diff --git a/service_vm/requests/src/lib.rs b/service_vm/requests/src/lib.rs
index 6fa6e0b..e3c5794 100644
--- a/service_vm/requests/src/lib.rs
+++ b/service_vm/requests/src/lib.rs
@@ -19,7 +19,6 @@
 extern crate alloc;
 
 mod api;
-mod cbor;
 mod keyblob;
 mod pub_key;
 mod rkp;
diff --git a/service_vm/requests/src/rkp.rs b/service_vm/requests/src/rkp.rs
index 933737c..8d7d771 100644
--- a/service_vm/requests/src/rkp.rs
+++ b/service_vm/requests/src/rkp.rs
@@ -15,7 +15,6 @@
 //! This module contains functions related to the attestation of the
 //! service VM via the RKP (Remote Key Provisioning) server.
 
-use crate::cbor;
 use crate::keyblob::EncryptedKeyBlob;
 use crate::pub_key::{build_maced_public_key, validate_public_key};
 use alloc::string::String;
@@ -51,7 +50,8 @@
     let key_blob =
         EncryptedKeyBlob::new(ec_key.private_key()?.as_slice(), dice_artifacts.cdi_seal())?;
 
-    let key_pair = EcdsaP256KeyPair { maced_public_key, key_blob: key_blob.to_cbor_vec()? };
+    let key_pair =
+        EcdsaP256KeyPair { maced_public_key, key_blob: cbor_util::serialize(&key_blob)? };
     Ok(key_pair)
 }
 
@@ -81,7 +81,7 @@
         // TODO(b/299256925): Add device info in CBOR format here.
         Value::Array(public_keys),
     ])?;
-    let csr_payload = cbor::serialize(&csr_payload)?;
+    let csr_payload = cbor_util::serialize(&csr_payload)?;
 
     // Builds `SignedData`.
     let signed_data_payload =
@@ -93,14 +93,14 @@
     // Check http://b/301574013#comment3 for more information.
     let uds_certs = Value::Map(Vec::new());
     let dice_cert_chain = dice_artifacts.bcc().ok_or(RequestProcessingError::MissingDiceChain)?;
-    let dice_cert_chain: Value = cbor::deserialize(dice_cert_chain)?;
+    let dice_cert_chain: Value = cbor_util::deserialize(dice_cert_chain)?;
     let auth_req = cbor!([
         Value::Integer(AUTH_REQ_SCHEMA_V1.into()),
         uds_certs,
         dice_cert_chain,
         signed_data,
     ])?;
-    Ok(cbor::serialize(&auth_req)?)
+    Ok(cbor_util::serialize(&auth_req)?)
 }
 
 fn derive_hmac_key(dice_artifacts: &dyn DiceArtifacts) -> Result<Zeroizing<[u8; HMAC_KEY_LENGTH]>> {
@@ -122,7 +122,7 @@
     let protected = HeaderBuilder::new().algorithm(signing_algorithm).build();
     let signed_data = CoseSign1Builder::new()
         .protected(protected)
-        .payload(cbor::serialize(payload)?)
+        .payload(cbor_util::serialize(payload)?)
         .try_create_signature(&[], |message| sign_message(message, &cdi_leaf_priv))?
         .build();
     Ok(signed_data)
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index 692b1b8..001dfeb 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -765,16 +765,22 @@
 
         // Check if the native library in the APK is has correct filesystem info
         final String[] abis = microdroid.run("getprop", "ro.product.cpu.abilist").split(",");
-        assertThat(abis).hasLength(1);
+        assertWithMessage("Incorrect ABI list").that(abis).hasLength(1);
 
         // Check that no denials have happened so far
-        assertThat(android.tryRun("egrep", "'avc:[[:space:]]{1,2}denied'", LOG_PATH)).isNull();
-        assertThat(android.tryRun("egrep", "'avc:[[:space:]]{1,2}denied'", CONSOLE_PATH)).isNull();
+        assertWithMessage("Unexpected denials during VM boot")
+                .that(android.tryRun("egrep", "'avc:[[:space:]]{1,2}denied'", LOG_PATH))
+                .isNull();
+        assertWithMessage("Unexpected denials during VM boot")
+                .that(android.tryRun("egrep", "'avc:[[:space:]]{1,2}denied'", CONSOLE_PATH))
+                .isNull();
 
         assertThat(getDeviceNumCpus(microdroid)).isEqualTo(getDeviceNumCpus(android));
 
         // Check that selinux is enabled
-        assertThat(microdroid.run("getenforce")).isEqualTo("Enforcing");
+        assertWithMessage("SELinux should be in enforcing mode")
+                .that(microdroid.run("getenforce"))
+                .isEqualTo("Enforcing");
 
         // TODO(b/176805428): adb is broken for nested VM
         if (!isCuttlefish()) {
diff --git a/tests/pvmfw/tools/PvmfwTool.java b/tests/pvmfw/tools/PvmfwTool.java
index e4b6020..62c641b 100644
--- a/tests/pvmfw/tools/PvmfwTool.java
+++ b/tests/pvmfw/tools/PvmfwTool.java
@@ -25,13 +25,13 @@
 public class PvmfwTool {
     public static void printUsage() {
         System.out.println("pvmfw-tool: Appends pvmfw.bin and config payloads.");
-        System.out.println("Requires BCC and debug policy dtbo files");
+        System.out.println("Requires BCC and optional debug policy dtbo files");
         System.out.println("");
-        System.out.println("Usage: pvmfw-tool <pvmfw_with_config> <pvmfw_bin> <bcc.dat> <dp.dtbo>");
+        System.out.println("Usage: pvmfw-tool <out> <pvmfw.bin> <bcc.dat> [<dp.dtbo>]");
     }
 
     public static void main(String[] args) {
-        if (args.length != 4) {
+        if (args.length != 4 && args.length != 3) {
             printUsage();
             System.exit(1);
         }
@@ -39,11 +39,14 @@
         File out = new File(args[0]);
         File pvmfw_bin = new File(args[1]);
         File bcc_dat = new File(args[2]);
-        File dtbo = new File(args[3]);
 
         try {
-            Pvmfw pvmfw = new Pvmfw.Builder(pvmfw_bin, bcc_dat).setDebugPolicyOverlay(dtbo).build();
-            pvmfw.serialize(out);
+            Pvmfw.Builder builder = new Pvmfw.Builder(pvmfw_bin, bcc_dat);
+            if (args.length == 4) {
+                File dtbo = new File(args[3]);
+                builder.setDebugPolicyOverlay(dtbo);
+            }
+            builder.build().serialize(out);
         } catch (IOException e) {
             e.printStackTrace();
             printUsage();
diff --git a/tests/pvmfw/tools/pvmfw-tool-manifest.txt b/tests/pvmfw/tools/pvmfw-tool-manifest.txt
index dc71fd2..8b2535a 100644
--- a/tests/pvmfw/tools/pvmfw-tool-manifest.txt
+++ b/tests/pvmfw/tools/pvmfw-tool-manifest.txt
@@ -1,2 +1,2 @@
 Manifest-Version: 1.0
-Main-Class: com.android.microdroid.PvmfwTool
+Main-Class: com.android.pvmfw.PvmfwTool
diff --git a/tests/vendor_images/Android.bp b/tests/vendor_images/Android.bp
index 09c657c..e5f863a 100644
--- a/tests/vendor_images/Android.bp
+++ b/tests/vendor_images/Android.bp
@@ -2,8 +2,16 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
+prebuilt_etc {
+    name: "vendor_sign_key",
+    src: ":avb_testkey_rsa4096",
+    installable: false,
+}
+
 android_filesystem {
     name: "test_microdroid_vendor_image",
     type: "ext4",
     file_contexts: ":microdroid_vendor_file_contexts.gen",
+    use_avb: true,
+    avb_private_key: ":vendor_sign_key",
 }
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index 684aa64..5283ffe 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -474,7 +474,7 @@
                 .into_iter()
                 .map(|x| VfioDevice {
                     sysfs_path: PathBuf::from(&x.sysfsPath),
-                    dtbo_node: x.dtboNode,
+                    dtbo_label: x.dtboLabel,
                 })
                 .collect::<Vec<_>>()
         } else {
diff --git a/virtualizationmanager/src/crosvm.rs b/virtualizationmanager/src/crosvm.rs
index b053d99..bb6066f 100644
--- a/virtualizationmanager/src/crosvm.rs
+++ b/virtualizationmanager/src/crosvm.rs
@@ -128,7 +128,7 @@
 #[derive(Clone, Debug)]
 pub struct VfioDevice {
     pub sysfs_path: PathBuf,
-    pub dtbo_node: String,
+    pub dtbo_label: String,
 }
 
 /// The lifecycle state which the payload in the VM has reported itself to be in.
@@ -716,7 +716,7 @@
     }
 
     if let Some(p) = path.to_str() {
-        Ok(format!("--vfio={p},iommu=viommu,dt-symbol={0}", device.dtbo_node))
+        Ok(format!("--vfio={p},iommu=viommu,dt-symbol={0}", device.dtbo_label))
     } else {
         bail!("invalid path {path:?}");
     }
diff --git a/virtualizationservice/aidl/android/system/virtualizationcommon/ErrorCode.aidl b/virtualizationservice/aidl/android/system/virtualizationcommon/ErrorCode.aidl
index 04b9749..cf1e251 100644
--- a/virtualizationservice/aidl/android/system/virtualizationcommon/ErrorCode.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationcommon/ErrorCode.aidl
@@ -39,5 +39,5 @@
     /**
      * Error code indicating that the payload config is invalid.
      */
-    PAYLOAD_CONFIG_INVALID = 3,
+    PAYLOAD_INVALID_CONFIG = 3,
 }
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
index f3a7617..172dc59 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
@@ -25,7 +25,7 @@
 interface IVirtualizationServiceInternal {
     parcelable BoundDevice {
         String sysfsPath;
-        String dtboNode;
+        String dtboLabel;
     }
     /**
      * Removes the memlock rlimit of the calling process.
diff --git a/virtualizationservice/assignable_devices.xsd b/virtualizationservice/assignable_devices.xsd
index 8f43019..2fbc1c9 100644
--- a/virtualizationservice/assignable_devices.xsd
+++ b/virtualizationservice/assignable_devices.xsd
@@ -25,7 +25,7 @@
     </xs:element>
     <xs:complexType name="device">
         <xs:attribute name="kind" type="xs:string"/>
-        <xs:attribute name="dtbo_node" type="xs:string"/>
+        <xs:attribute name="dtbo_label" type="xs:string"/>
         <xs:attribute name="sysfs_path" type="xs:string"/>
     </xs:complexType>
 </xs:schema>
diff --git a/virtualizationservice/schema/current.txt b/virtualizationservice/schema/current.txt
index ef99294..6e3fbb6 100644
--- a/virtualizationservice/schema/current.txt
+++ b/virtualizationservice/schema/current.txt
@@ -3,10 +3,10 @@
 
   public class Device {
     ctor public Device();
-    method public String getDtbo_node();
+    method public String getDtbo_label();
     method public String getKind();
     method public String getSysfs_path();
-    method public void setDtbo_node(String);
+    method public void setDtbo_label(String);
     method public void setKind(String);
     method public void setSysfs_path(String);
   }
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index ed5c513..a19ecd2 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -209,7 +209,7 @@
             .into_iter()
             .filter_map(|x| {
                 if devices.contains(&x.sysfs_path) {
-                    Some(BoundDevice { sysfsPath: x.sysfs_path, dtboNode: x.dtbo_node })
+                    Some(BoundDevice { sysfsPath: x.sysfs_path, dtboLabel: x.dtbo_label })
                 } else {
                     None
                 }
@@ -222,7 +222,7 @@
 #[derive(Debug, Deserialize)]
 struct Device {
     kind: String,
-    dtbo_node: String,
+    dtbo_label: String,
     sysfs_path: String,
 }
 
diff --git a/virtualizationservice/vfio_handler/src/aidl.rs b/virtualizationservice/vfio_handler/src/aidl.rs
index 618c165..2968ff9 100644
--- a/virtualizationservice/vfio_handler/src/aidl.rs
+++ b/virtualizationservice/vfio_handler/src/aidl.rs
@@ -239,7 +239,7 @@
     if dt_table_header.magic.get() != DT_TABLE_MAGIC
         || dt_table_header.header_size.get() as usize != size_of::<DtTableHeader>()
     {
-        return Err(anyhow!("DtTableHeader is invalid")).or_service_specific_exception(-1)?;
+        return Err(anyhow!("DtTableHeader is invalid")).or_service_specific_exception(-1);
     }
     Ok(dt_table_header)
 }
@@ -250,7 +250,7 @@
     index: u32,
 ) -> binder::Result<DtTableEntry> {
     if index >= header.dt_entry_count.get() {
-        return Err(anyhow!("Invalid dtbo index {index}")).or_service_specific_exception(-1)?;
+        return Err(anyhow!("Invalid dtbo index {index}")).or_service_specific_exception(-1);
     }
     let Some(prev_dt_entry_total_size) = header.dt_entry_size.get().checked_mul(index) else {
         return Err(anyhow!("Unexpected arithmetic result"))
diff --git a/vmbase/src/entry.rs b/vmbase/src/entry.rs
index 2ff66cc..b19efce 100644
--- a/vmbase/src/entry.rs
+++ b/vmbase/src/entry.rs
@@ -53,8 +53,14 @@
     // We keep a null byte at the top of the stack guard to act as a string terminator.
     let random_guard = &mut stack_guard[..(SIZE_OF_STACK_GUARD - 1)];
 
-    rand::init().expect("Failed to initialize a source of entropy");
-    rand::fill_with_entropy(random_guard).expect("Failed to get stack canary entropy");
+    if let Err(e) = rand::init() {
+        panic!("Failed to initialize a source of entropy: {e}");
+    }
+
+    if let Err(e) = rand::fill_with_entropy(random_guard) {
+        panic!("Failed to get stack canary entropy: {e}");
+    }
+
     bionic::__get_tls().stack_guard = u64::from_ne_bytes(stack_guard);
 
     // Note: If rust_entry ever returned (which it shouldn't by being -> !), the compiler-injected
diff --git a/vmbase/src/heap.rs b/vmbase/src/heap.rs
index c8b76ac..ec03d38 100644
--- a/vmbase/src/heap.rs
+++ b/vmbase/src/heap.rs
@@ -81,9 +81,7 @@
 
 #[no_mangle]
 unsafe extern "C" fn calloc(nmemb: usize, size: usize) -> *mut c_void {
-    let Some(size) = nmemb.checked_mul(size) else {
-        return ptr::null_mut()
-    };
+    let Some(size) = nmemb.checked_mul(size) else { return ptr::null_mut() };
     allocate(size, true).map_or(ptr::null_mut(), |p| p.cast::<c_void>().as_ptr())
 }
 
diff --git a/vmclient/src/error_code.rs b/vmclient/src/error_code.rs
index 10e6d4d..59b25a0 100644
--- a/vmclient/src/error_code.rs
+++ b/vmclient/src/error_code.rs
@@ -28,7 +28,7 @@
     PayloadChanged,
 
     /// Error code indicating that the payload config is invalid.
-    PayloadConfigInvalid,
+    PayloadInvalidConfig,
 
     /// Payload sent a death reason which was not recognised by the client library.
     Unrecognised(AidlErrorCode),
@@ -40,7 +40,7 @@
             AidlErrorCode::UNKNOWN => Self::Unknown,
             AidlErrorCode::PAYLOAD_VERIFICATION_FAILED => Self::PayloadVerificationFailed,
             AidlErrorCode::PAYLOAD_CHANGED => Self::PayloadChanged,
-            AidlErrorCode::PAYLOAD_CONFIG_INVALID => Self::PayloadConfigInvalid,
+            AidlErrorCode::PAYLOAD_INVALID_CONFIG => Self::PayloadInvalidConfig,
             _ => Self::Unrecognised(error_code),
         }
     }