Merge "[pvmfw] Use AEAD wrapper from libbssl_avf" into main
diff --git a/apex/Android.bp b/apex/Android.bp
index acbc0a1..b09cf58 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -143,10 +143,10 @@
         },
         release_avf_enable_vendor_modules: {
             prebuilts: [
-                "microdroid_gki-6.1_initrd_debuggable",
-                "microdroid_gki-6.1_initrd_normal",
-                "microdroid_gki-6.1_kernel",
-                "microdroid_gki-6.1.json",
+                "microdroid_gki-android14-6.1_initrd_debuggable",
+                "microdroid_gki-android14-6.1_initrd_normal",
+                "microdroid_gki-android14-6.1_kernel",
+                "microdroid_gki-android14-6.1.json",
             ],
         },
         release_avf_enable_remote_attestation: {
diff --git a/apex/sign_virt_apex.py b/apex/sign_virt_apex.py
index 0c5bc72..b21a355 100644
--- a/apex/sign_virt_apex.py
+++ b/apex/sign_virt_apex.py
@@ -108,6 +108,7 @@
         action='store_true',
         help='This will NOT update the vbmeta related bootconfigs while signing the apex.\
             Used for testing only!!')
+    parser.add_argument('--do_not_validate_avb_version', action='store_true', help='Do not validate the avb_version when updating vbmeta bootconfig. Only use in tests!')
     args = parser.parse_args(argv)
     # preprocess --key_override into a map
     args.key_overrides = {}
@@ -328,7 +329,8 @@
             detach_bootconfigs(initrd, tmp_initrd, tmp_bc)
             bc_file = open(tmp_bc, "rt", encoding="utf-8")
             bc_data = bc_file.read()
-            validate_avb_version(bc_data)
+            if not args.do_not_validate_avb_version:
+                validate_avb_version(bc_data)
             bc_data = update_vbmeta_digest(bc_data)
             bc_data = update_vbmeta_size(bc_data)
             bc_file.close()
@@ -411,7 +413,7 @@
     RunCommand(args, cmd)
 
 
-gki_versions = ['6.1']
+gki_versions = ['android14-6.1']
 
 # dict of (key, file) for re-sign/verification. keys are un-versioned for readability.
 virt_apex_non_gki_files = {
diff --git a/libs/bssl/error/src/lib.rs b/libs/bssl/error/src/lib.rs
index 7f01c6c..82a2d5e 100644
--- a/libs/bssl/error/src/lib.rs
+++ b/libs/bssl/error/src/lib.rs
@@ -91,6 +91,7 @@
     ECDSA_sign,
     ECDSA_size,
     ECDSA_verify,
+    ED25519_verify,
     EVP_AEAD_CTX_new,
     EVP_AEAD_CTX_open,
     EVP_AEAD_CTX_seal,
diff --git a/libs/bssl/src/curve25519.rs b/libs/bssl/src/curve25519.rs
new file mode 100644
index 0000000..499a3d0
--- /dev/null
+++ b/libs/bssl/src/curve25519.rs
@@ -0,0 +1,39 @@
+// 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.
+
+//! Wrappers of the Curve25519 related functions in BoringSSL curve25519.h.
+
+use crate::util::check_int_result;
+use bssl_avf_error::{ApiName, Result};
+
+const ED25519_PUBLIC_KEY_LEN: usize = bssl_ffi::ED25519_PUBLIC_KEY_LEN as usize;
+const ED25519_SIGNATURE_LEN: usize = bssl_ffi::ED25519_SIGNATURE_LEN as usize;
+
+/// Verifies the signature of a message with the given ED25519 public key.
+pub fn ed25519_verify(
+    message: &[u8],
+    signature: &[u8; ED25519_SIGNATURE_LEN],
+    public_key: &[u8; ED25519_PUBLIC_KEY_LEN],
+) -> Result<()> {
+    // SAFETY: The function only reads the parameters within their bounds.
+    let ret = unsafe {
+        bssl_ffi::ED25519_verify(
+            message.as_ptr(),
+            message.len(),
+            signature.as_ptr(),
+            public_key.as_ptr(),
+        )
+    };
+    check_int_result(ret, ApiName::ED25519_verify)
+}
diff --git a/libs/bssl/src/lib.rs b/libs/bssl/src/lib.rs
index a420168..ad51b61 100644
--- a/libs/bssl/src/lib.rs
+++ b/libs/bssl/src/lib.rs
@@ -21,6 +21,7 @@
 mod aead;
 mod cbb;
 mod cbs;
+mod curve25519;
 mod digest;
 mod ec_key;
 mod err;
@@ -36,6 +37,7 @@
 pub use aead::{Aead, AeadContext, AES_GCM_NONCE_LENGTH};
 pub use cbb::CbbFixed;
 pub use cbs::Cbs;
+pub use curve25519::ed25519_verify;
 pub use digest::Digester;
 pub use ec_key::{EcKey, ZVec};
 pub use evp::{PKey, PKeyType};
diff --git a/libs/bssl/tests/eckey_test.rs b/libs/bssl/tests/eckey_test.rs
index 9c7eb4f..3c0e45d 100644
--- a/libs/bssl/tests/eckey_test.rs
+++ b/libs/bssl/tests/eckey_test.rs
@@ -15,8 +15,8 @@
 use bssl_avf::{sha256, ApiName, Digester, EcKey, EcdsaError, Error, PKey, Result};
 use coset::CborSerializable;
 use spki::{
-    der::{AnyRef, Decode},
-    AlgorithmIdentifier, ObjectIdentifier, SubjectPublicKeyInfo,
+    der::{AnyRef, Decode, Encode},
+    AlgorithmIdentifier, ObjectIdentifier, SubjectPublicKeyInfoRef,
 };
 
 /// OID value for general-use NIST EC keys held in PKCS#8 and X.509; see RFC 5480 s2.1.1.
@@ -46,13 +46,14 @@
     let pkey: PKey = ec_key.try_into()?;
     let subject_public_key_info = pkey.subject_public_key_info()?;
 
-    let subject_public_key_info = SubjectPublicKeyInfo::from_der(&subject_public_key_info).unwrap();
+    let subject_public_key_info =
+        SubjectPublicKeyInfoRef::from_der(&subject_public_key_info).unwrap();
     let expected_algorithm = AlgorithmIdentifier {
         oid: X509_NIST_OID,
         parameters: Some(AnyRef::from(&ALGO_PARAM_P256_OID)),
     };
     assert_eq!(expected_algorithm, subject_public_key_info.algorithm);
-    assert!(!subject_public_key_info.subject_public_key.to_vec().is_empty());
+    assert!(!subject_public_key_info.subject_public_key.to_der().unwrap().is_empty());
     Ok(())
 }
 
diff --git a/libs/dice/open_dice/Android.bp b/libs/dice/open_dice/Android.bp
index 646080d..2d0f52c 100644
--- a/libs/dice/open_dice/Android.bp
+++ b/libs/dice/open_dice/Android.bp
@@ -27,12 +27,14 @@
     ],
     visibility: [
         "//packages/modules/Virtualization:__subpackages__",
+        "//system/authgraph/tests:__subpackages__",
     ],
 }
 
 rust_library {
     name: "libdiced_open_dice",
     defaults: ["libdiced_open_dice_defaults"],
+    host_supported: true,
     vendor_available: true,
     rustlibs: [
         "libopen_dice_android_bindgen",
@@ -54,6 +56,7 @@
     ],
     visibility: [
         "//packages/modules/Virtualization:__subpackages__",
+        "//system/authgraph/tests:__subpackages__",
     ],
     apex_available: [
         "//apex_available:platform",
diff --git a/libs/libfdt/src/lib.rs b/libs/libfdt/src/lib.rs
index aae75f7..7eb08b2 100644
--- a/libs/libfdt/src/lib.rs
+++ b/libs/libfdt/src/lib.rs
@@ -527,6 +527,32 @@
             Ok(None)
         }
     }
+
+    /// Returns the subnode of the given name. The name doesn't need to be nul-terminated.
+    pub fn subnode(&self, name: &CStr) -> Result<Option<Self>> {
+        let offset = self.subnode_offset(name.to_bytes())?;
+        Ok(offset.map(|offset| Self { fdt: self.fdt, offset }))
+    }
+
+    /// Returns the subnode of the given name bytes
+    pub fn subnode_with_name_bytes(&self, name: &[u8]) -> Result<Option<Self>> {
+        let offset = self.subnode_offset(name)?;
+        Ok(offset.map(|offset| Self { fdt: self.fdt, offset }))
+    }
+
+    fn subnode_offset(&self, name: &[u8]) -> Result<Option<c_int>> {
+        let namelen = name.len().try_into().unwrap();
+        // SAFETY: Accesses are constrained to the DT totalsize (validated by ctor).
+        let ret = unsafe {
+            libfdt_bindgen::fdt_subnode_offset_namelen(
+                self.fdt.as_ptr(),
+                self.offset,
+                name.as_ptr().cast::<_>(),
+                namelen,
+            )
+        };
+        fdt_err_or_option(ret)
+    }
 }
 
 impl<'a> PartialEq for FdtNode<'a> {
@@ -751,24 +777,38 @@
         fdt_err(ret)
     }
 
-    /// Returns the subnode of the given name with len.
-    pub fn subnode_with_namelen(&'a mut self, name: &CStr, namelen: usize) -> Result<Option<Self>> {
-        let offset = self.subnode_offset(&name.to_bytes()[..namelen])?;
-        Ok(offset.map(|offset| Self { fdt: self.fdt, offset }))
+    /// Returns the first subnode of this
+    pub fn first_subnode(&'a mut self) -> Result<Option<Self>> {
+        // SAFETY: Accesses (read-only) are constrained to the DT totalsize.
+        let ret = unsafe { libfdt_bindgen::fdt_first_subnode(self.fdt.as_ptr(), self.offset) };
+
+        Ok(fdt_err_or_option(ret)?.map(|offset| Self { fdt: self.fdt, offset }))
     }
 
-    fn subnode_offset(&self, name: &[u8]) -> Result<Option<c_int>> {
-        let namelen = name.len().try_into().unwrap();
-        // SAFETY: Accesses are constrained to the DT totalsize (validated by ctor).
-        let ret = unsafe {
-            libfdt_bindgen::fdt_subnode_offset_namelen(
-                self.fdt.as_ptr(),
-                self.offset,
-                name.as_ptr().cast::<_>(),
-                namelen,
-            )
-        };
-        fdt_err_or_option(ret)
+    /// Returns the next subnode that shares the same parent with this
+    pub fn next_subnode(self) -> Result<Option<Self>> {
+        // SAFETY: Accesses (read-only) are constrained to the DT totalsize.
+        let ret = unsafe { libfdt_bindgen::fdt_next_subnode(self.fdt.as_ptr(), self.offset) };
+
+        Ok(fdt_err_or_option(ret)?.map(|offset| Self { fdt: self.fdt, offset }))
+    }
+
+    /// Deletes the current node and returns the next subnode
+    pub fn delete_and_next_subnode(mut self) -> Result<Option<Self>> {
+        // SAFETY: Accesses (read-only) are constrained to the DT totalsize.
+        let ret = unsafe { libfdt_bindgen::fdt_next_subnode(self.fdt.as_ptr(), self.offset) };
+
+        let next_offset = fdt_err_or_option(ret)?;
+
+        if Some(self.offset) == next_offset {
+            return Err(FdtError::Internal);
+        }
+
+        // SAFETY: nop_self() only touches bytes of the self and its properties and subnodes, and
+        // doesn't alter any other blob in the tree. self.fdt and next_offset would remain valid.
+        unsafe { self.nop_self()? };
+
+        Ok(next_offset.map(|offset| Self { fdt: self.fdt, offset }))
     }
 
     fn parent(&'a self) -> Result<FdtNode<'a>> {
diff --git a/libs/libfdt/tests/api_test.rs b/libs/libfdt/tests/api_test.rs
index d5d6ece..e68557f 100644
--- a/libs/libfdt/tests/api_test.rs
+++ b/libs/libfdt/tests/api_test.rs
@@ -262,14 +262,15 @@
     let subnode_name = cstr!("123456789");
 
     for len in 0..subnode_name.to_bytes().len() {
-        let mut node = fdt.node_mut(node_path).unwrap().unwrap();
-        assert!(node.subnode_with_namelen(subnode_name, len).unwrap().is_none());
+        let name = &subnode_name.to_bytes()[0..len];
+        let node = fdt.node(node_path).unwrap().unwrap();
+        assert_eq!(Ok(None), node.subnode_with_name_bytes(name));
 
         let mut node = fdt.node_mut(node_path).unwrap().unwrap();
         node.add_subnode_with_namelen(subnode_name, len).unwrap();
 
-        let mut node = fdt.node_mut(node_path).unwrap().unwrap();
-        assert!(node.subnode_with_namelen(subnode_name, len).unwrap().is_some());
+        let node = fdt.node(node_path).unwrap().unwrap();
+        assert_ne!(Ok(None), node.subnode_with_name_bytes(name));
     }
 
     let node_path = node_path.to_str().unwrap();
@@ -283,6 +284,48 @@
 }
 
 #[test]
+fn node_subnode() {
+    let data = fs::read(TEST_TREE_PHANDLE_PATH).unwrap();
+    let fdt = Fdt::from_slice(&data).unwrap();
+
+    let name = cstr!("node_a");
+    let root = fdt.root().unwrap();
+    let node = root.subnode(name).unwrap();
+    assert_ne!(None, node);
+    let node = node.unwrap();
+
+    assert_eq!(Ok(name), node.name());
+}
+
+#[test]
+fn node_subnode_with_name_bytes() {
+    let data = fs::read(TEST_TREE_PHANDLE_PATH).unwrap();
+    let fdt = Fdt::from_slice(&data).unwrap();
+
+    let name = b"node_aaaaa";
+    let root = fdt.root().unwrap();
+    let node = root.subnode_with_name_bytes(&name[0..6]).unwrap();
+    assert_ne!(None, node);
+    let node = node.unwrap();
+
+    assert_eq!(Ok(cstr!("node_a")), node.name());
+}
+
+#[test]
+fn node_subnode_borrow_checker() {
+    let data = fs::read(TEST_TREE_PHANDLE_PATH).unwrap();
+    let fdt = Fdt::from_slice(&data).unwrap();
+
+    let name = cstr!("node_a");
+    let node = {
+        let root = fdt.root().unwrap();
+        root.subnode(name).unwrap().unwrap()
+    };
+
+    assert_eq!(Ok(name), node.name());
+}
+
+#[test]
 fn fdt_symbols() {
     let mut data = fs::read(TEST_TREE_PHANDLE_PATH).unwrap();
     let fdt = Fdt::from_mut_slice(&mut data).unwrap();
@@ -328,3 +371,31 @@
         ]
     );
 }
+
+#[test]
+fn node_mut_delete_and_next_subnode() {
+    let mut data = fs::read(TEST_TREE_PHANDLE_PATH).unwrap();
+    let fdt = Fdt::from_mut_slice(&mut data).unwrap();
+
+    let mut root = fdt.root_mut().unwrap();
+    let mut subnode_iter = root.first_subnode().unwrap();
+
+    while let Some(subnode) = subnode_iter {
+        if subnode.as_node().name() == Ok(cstr!("node_z")) {
+            subnode_iter = subnode.delete_and_next_subnode().unwrap();
+        } else {
+            subnode_iter = subnode.next_subnode().unwrap();
+        }
+    }
+
+    let root = fdt.root().unwrap();
+    let expected_names = vec![
+        Ok(cstr!("node_a")),
+        Ok(cstr!("node_b")),
+        Ok(cstr!("node_c")),
+        Ok(cstr!("__symbols__")),
+    ];
+    let subnode_names: Vec<_> = root.subnodes().unwrap().map(|node| node.name()).collect();
+
+    assert_eq!(expected_names, subnode_names);
+}
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index c1caa56..f98f3af 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -557,17 +557,17 @@
 }
 
 ///////////////////////////////////////
-// GKI-6.1 modules
+// GKI-android14-6.1 modules
 ///////////////////////////////////////
 prebuilt_etc {
-    name: "microdroid_gki-6.1.json",
-    src: "microdroid_gki-6.1.json",
+    name: "microdroid_gki-android14-6.1.json",
+    src: "microdroid_gki-android14-6.1.json",
 }
 
 avb_add_hash_footer {
-    name: "microdroid_gki-6.1_kernel_signed",
+    name: "microdroid_gki-android14-6.1_kernel_signed",
     defaults: ["microdroid_kernel_signed_defaults"],
-    filename: "microdroid_gki-6.1_kernel",
+    filename: "microdroid_gki-android14-6.1_kernel",
     arch: {
         arm64: {
             src: ":microdroid_gki_kernel_prebuilts-6.1-arm64",
@@ -577,33 +577,33 @@
         },
     },
     include_descriptors_from_images: [
-        ":microdroid_gki-6.1_initrd_normal_hashdesc",
-        ":microdroid_gki-6.1_initrd_debug_hashdesc",
+        ":microdroid_gki-android14-6.1_initrd_normal_hashdesc",
+        ":microdroid_gki-android14-6.1_initrd_debug_hashdesc",
     ],
 }
 
 prebuilt_etc {
-    name: "microdroid_gki-6.1_kernel",
+    name: "microdroid_gki-android14-6.1_kernel",
     src: ":empty_file",
     relative_install_path: "fs",
     arch: {
         arm64: {
-            src: ":microdroid_gki-6.1_kernel_signed",
+            src: ":microdroid_gki-android14-6.1_kernel_signed",
         },
         x86_64: {
-            src: ":microdroid_gki-6.1_kernel_signed",
+            src: ":microdroid_gki-android14-6.1_kernel_signed",
         },
     },
 }
 
 avb_gen_vbmeta_image {
-    name: "microdroid_gki-6.1_initrd_normal_hashdesc",
+    name: "microdroid_gki-android14-6.1_initrd_normal_hashdesc",
     defaults: ["microdroid_initrd_normal_defaults"],
-    src: ":microdroid_gki-6.1_initrd_normal",
+    src: ":microdroid_gki-android14-6.1_initrd_normal",
 }
 
 avb_gen_vbmeta_image {
-    name: "microdroid_gki-6.1_initrd_debug_hashdesc",
+    name: "microdroid_gki-android14-6.1_initrd_debug_hashdesc",
     defaults: ["microdroid_initrd_debug_defaults"],
-    src: ":microdroid_gki-6.1_initrd_debuggable",
+    src: ":microdroid_gki-android14-6.1_initrd_debuggable",
 }
diff --git a/microdroid/initrd/Android.bp b/microdroid/initrd/Android.bp
index 8df4c0f..ec971fa 100644
--- a/microdroid/initrd/Android.bp
+++ b/microdroid/initrd/Android.bp
@@ -41,7 +41,7 @@
 }
 
 genrule {
-    name: "microdroid_gki-6.1_initrd_gen_arm64",
+    name: "microdroid_gki-android14-6.1_initrd_gen_arm64",
     srcs: [
         ":microdroid_ramdisk",
         ":microdroid_fstab_ramdisk",
@@ -52,7 +52,7 @@
 }
 
 genrule {
-    name: "microdroid_gki-6.1_initrd_gen_x86_64",
+    name: "microdroid_gki-android14-6.1_initrd_gen_x86_64",
     srcs: [
         ":microdroid_ramdisk",
         ":microdroid_fstab_ramdisk",
@@ -96,13 +96,13 @@
 }
 
 genrule {
-    name: "microdroid_gki-6.1_initrd_debuggable_arm64",
+    name: "microdroid_gki-android14-6.1_initrd_debuggable_arm64",
     tools: ["initrd_bootconfig"],
     srcs: [
-        ":microdroid_gki-6.1_initrd_gen_arm64",
+        ":microdroid_gki-android14-6.1_initrd_gen_arm64",
         ":microdroid_bootconfig_debuggable_src",
     ] + bootconfigs_arm64,
-    out: ["microdroid_gki-6.1_initrd_debuggable_arm64"],
+    out: ["microdroid_gki-android14-6.1_initrd_debuggable_arm64"],
     cmd: "$(location initrd_bootconfig) attach --output $(out) $(in)",
 }
 
@@ -118,13 +118,13 @@
 }
 
 genrule {
-    name: "microdroid_gki-6.1_initrd_debuggable_x86_64",
+    name: "microdroid_gki-android14-6.1_initrd_debuggable_x86_64",
     tools: ["initrd_bootconfig"],
     srcs: [
-        ":microdroid_gki-6.1_initrd_gen_x86_64",
+        ":microdroid_gki-android14-6.1_initrd_gen_x86_64",
         ":microdroid_bootconfig_debuggable_src",
     ] + bootconfigs_x86_64,
-    out: ["microdroid_gki-6.1_initrd_debuggable_x86_64"],
+    out: ["microdroid_gki-android14-6.1_initrd_debuggable_x86_64"],
     cmd: "$(location initrd_bootconfig) attach --output $(out) $(in)",
 }
 
@@ -140,13 +140,13 @@
 }
 
 genrule {
-    name: "microdroid_gki-6.1_initrd_normal_arm64",
+    name: "microdroid_gki-android14-6.1_initrd_normal_arm64",
     tools: ["initrd_bootconfig"],
     srcs: [
-        ":microdroid_gki-6.1_initrd_gen_arm64",
+        ":microdroid_gki-android14-6.1_initrd_gen_arm64",
         ":microdroid_bootconfig_normal_src",
     ] + bootconfigs_arm64,
-    out: ["microdroid_gki-6.1_initrd_normal_arm64"],
+    out: ["microdroid_gki-android14-6.1_initrd_normal_arm64"],
     cmd: "$(location initrd_bootconfig) attach --output $(out) $(in)",
 }
 
@@ -162,13 +162,13 @@
 }
 
 genrule {
-    name: "microdroid_gki-6.1_initrd_normal_x86_64",
+    name: "microdroid_gki-android14-6.1_initrd_normal_x86_64",
     tools: ["initrd_bootconfig"],
     srcs: [
-        ":microdroid_gki-6.1_initrd_gen_x86_64",
+        ":microdroid_gki-android14-6.1_initrd_gen_x86_64",
         ":microdroid_bootconfig_normal_src",
     ] + bootconfigs_x86_64,
-    out: ["microdroid_gki-6.1_initrd_normal_x86_64"],
+    out: ["microdroid_gki-android14-6.1_initrd_normal_x86_64"],
     cmd: "$(location initrd_bootconfig) attach --output $(out) $(in)",
 }
 
@@ -188,18 +188,18 @@
 }
 
 prebuilt_etc {
-    name: "microdroid_gki-6.1_initrd_debuggable",
+    name: "microdroid_gki-android14-6.1_initrd_debuggable",
     // We don't have ramdisk for architectures other than x86_64 & arm64
     src: ":empty_file",
     arch: {
         x86_64: {
-            src: ":microdroid_gki-6.1_initrd_debuggable_x86_64",
+            src: ":microdroid_gki-android14-6.1_initrd_debuggable_x86_64",
         },
         arm64: {
-            src: ":microdroid_gki-6.1_initrd_debuggable_arm64",
+            src: ":microdroid_gki-android14-6.1_initrd_debuggable_arm64",
         },
     },
-    filename: "microdroid_gki-6.1_initrd_debuggable.img",
+    filename: "microdroid_gki-android14-6.1_initrd_debuggable.img",
 }
 
 prebuilt_etc {
@@ -218,16 +218,16 @@
 }
 
 prebuilt_etc {
-    name: "microdroid_gki-6.1_initrd_normal",
+    name: "microdroid_gki-android14-6.1_initrd_normal",
     // We don't have ramdisk for architectures other than x86_64 & arm64
     src: ":empty_file",
     arch: {
         x86_64: {
-            src: ":microdroid_gki-6.1_initrd_normal_x86_64",
+            src: ":microdroid_gki-android14-6.1_initrd_normal_x86_64",
         },
         arm64: {
-            src: ":microdroid_gki-6.1_initrd_normal_arm64",
+            src: ":microdroid_gki-android14-6.1_initrd_normal_arm64",
         },
     },
-    filename: "microdroid_gki-6.1_initrd_normal.img",
+    filename: "microdroid_gki-android14-6.1_initrd_normal.img",
 }
diff --git a/microdroid/microdroid_gki-6.1.json b/microdroid/microdroid_gki-android14-6.1.json
similarity index 82%
rename from microdroid/microdroid_gki-6.1.json
rename to microdroid/microdroid_gki-android14-6.1.json
index 2115e51..9392fae 100644
--- a/microdroid/microdroid_gki-6.1.json
+++ b/microdroid/microdroid_gki-android14-6.1.json
@@ -1,5 +1,5 @@
 {
-  "kernel": "/apex/com.android.virt/etc/fs/microdroid_gki-6.1_kernel",
+  "kernel": "/apex/com.android.virt/etc/fs/microdroid_gki-android14-6.1_kernel",
   "disks": [
     {
       "partitions": [
diff --git a/microdroid_manager/Android.bp b/microdroid_manager/Android.bp
index 8481edf..cb3b2aa 100644
--- a/microdroid_manager/Android.bp
+++ b/microdroid_manager/Android.bp
@@ -5,7 +5,10 @@
 rust_defaults {
     name: "microdroid_manager_defaults",
     crate_name: "microdroid_manager",
-    defaults: ["avf_build_flags_rust"],
+    defaults: [
+        "avf_build_flags_rust",
+        "secretkeeper_use_latest_hal_aidl_rust",
+    ],
     srcs: ["src/main.rs"],
     edition: "2021",
     prefer_rlib: true,
@@ -43,6 +46,8 @@
         "libprotobuf",
         "librpcbinder_rs",
         "librustutils",
+        "libsecretkeeper_client",
+        "libsecretkeeper_comm_nostd",
         "libscopeguard",
         "libserde",
         "libserde_cbor",
@@ -51,6 +56,7 @@
         "libuuid",
         "libvsock",
         "librand",
+        "libzeroize",
     ],
     init_rc: ["microdroid_manager.rc"],
     multilib: {
diff --git a/microdroid_manager/src/dice.rs b/microdroid_manager/src/dice.rs
index 0cf7013..a8b88aa 100644
--- a/microdroid_manager/src/dice.rs
+++ b/microdroid_manager/src/dice.rs
@@ -107,7 +107,7 @@
     apks.chain(apexes).collect()
 }
 
-// Returns a configuration descriptor of the given payload. See vm_config.cddl for a definition
+// Returns a configuration descriptor of the given payload. See vm_config.cddl for the definition
 // of the format.
 fn format_payload_config_descriptor(
     payload: &PayloadMetadata,
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 9e167a4..c94a937 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -105,7 +105,6 @@
             MicrodroidError::PayloadInvalidConfig(msg) => {
                 (ErrorCode::PAYLOAD_INVALID_CONFIG, msg.to_string())
             }
-
             // Connection failure won't be reported to VS; return the default value
             MicrodroidError::FailedToConnectToVirtualizationService(msg) => {
                 (ErrorCode::UNKNOWN, msg.to_string())
@@ -282,7 +281,8 @@
     // To minimize the exposure to untrusted data, derive dice profile as soon as possible.
     info!("DICE derivation for payload");
     let dice_artifacts = dice_derivation(dice, &instance_data, &payload_metadata)?;
-    let vm_secret = VmSecret::new(dice_artifacts).context("Failed to create VM secrets")?;
+    let vm_secret =
+        VmSecret::new(dice_artifacts, service).context("Failed to create VM secrets")?;
 
     if cfg!(dice_changes) {
         // Now that the DICE derivation is done, it's ok to allow payload code to run.
diff --git a/microdroid_manager/src/vm_config.cddl b/microdroid_manager/src/vm_config.cddl
index 052262d..8508e8f 100644
--- a/microdroid_manager/src/vm_config.cddl
+++ b/microdroid_manager/src/vm_config.cddl
@@ -11,6 +11,10 @@
 
 ; The configuration descriptor node for a Microdroid VM, with extensions to describe the contents
 ; of the VM payload.
+; The subcomponents describe the APKs and then the APEXes that are part of the VM. The main APK
+; is first, followed by any extra APKs in the order they are specified in the VM config.
+; The APEXes are listed in the order specified when the VM is created, which is normally alphabetic
+; order by name.
 VmConfigDescriptor = {
     -70002 : "Microdroid payload",      ; Component name
     (? -71000: tstr //                  ; Path to the payload config file
@@ -23,9 +27,30 @@
 }
 
 ; Describes a unit of code (e.g. an APK or an APEX) present inside the VM.
+;
+; For an APK, the fields are as follows:
+; - Component name: The string "apk:" followed by the package name.
+; - Security version: The long version code from the APK manifest
+;   (https://developer.android.com/reference/android/content/pm/PackageInfo#getLongVersionCode()).
+; - Code hash: This is the root hash of a Merkle tree computed over all bytes of the APK, as used
+;   in the APK Signature Scheme v4 (https://source.android.com/docs/security/features/apksigning/v4)
+;   with empty salt and using SHA-256 as the hash algorithm.
+; - Authority hash: The SHA-512 hash of the DER representation of the X.509 certificate for the
+;   public key used to sign the APK.
+;
+; For an APEX, they are as follows:
+; - Component name: The string "apex:" followed by the APEX name as specified in the APEX Manifest
+;   (see https://source.android.com/docs/core/ota/apex).
+; - Security version: The version number from the APEX Manifest.
+; - Code hash: The root hash of the apex_payload.img file within the APEX, taken from the first
+;   hashtree descriptor in the VBMeta image
+;   (see https://android.googlesource.com/platform/external/avb/+/master/README.md).
+; - Authority hash: The SHA-512 hash of the public key used to sign the file system image in the
+;   APEX (as stored in the apex_pubkey file). The format is as described for AvbRSAPublicKeyHeader
+;   in https://cs.android.com/android/platform/superproject/main/+/main:external/avb/libavb/avb_crypto.h.
 SubcomponentDescriptor = {
   1: tstr,                              ; Component name
   2: uint,                              ; Security version
-  ? 3: bstr,                            ; Code hash
+  3: bstr,                              ; Code hash
   4: bstr,                              ; Authority hash
 }
diff --git a/microdroid_manager/src/vm_secret.rs b/microdroid_manager/src/vm_secret.rs
index d84c2e2..df5d318 100644
--- a/microdroid_manager/src/vm_secret.rs
+++ b/microdroid_manager/src/vm_secret.rs
@@ -14,18 +14,28 @@
 
 //! Class for encapsulating & managing represent VM secrets.
 
-use anyhow::Result;
+use anyhow::{anyhow, ensure, Result};
+use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::IVirtualMachineService;
+use android_hardware_security_secretkeeper::aidl::android::hardware::security::secretkeeper::ISecretkeeper::ISecretkeeper;
+use secretkeeper_comm::data_types::request::Request;
+use binder::{Strong};
+use coset::CborSerializable;
 use diced_open_dice::{DiceArtifacts, OwnedDiceArtifacts};
 use keystore2_crypto::ZVec;
 use openssl::hkdf::hkdf;
 use openssl::md::Md;
 use openssl::sha;
+use secretkeeper_client::SkSession;
+use secretkeeper_comm::data_types::{Id, ID_SIZE, Secret, SECRET_SIZE};
+use secretkeeper_comm::data_types::response::Response;
+use secretkeeper_comm::data_types::packet::{ResponsePacket, ResponseType};
+use secretkeeper_comm::data_types::request_response_impl::{
+    StoreSecretRequest, GetSecretResponse, GetSecretRequest};
+use secretkeeper_comm::data_types::error::SecretkeeperError;
+use zeroize::Zeroizing;
 
 const ENCRYPTEDSTORE_KEY_IDENTIFIER: &str = "encryptedstore_key";
 
-// Size of the secret stored in Secretkeeper.
-const SK_SECRET_SIZE: usize = 64;
-
 // Generated using hexdump -vn32 -e'14/1 "0x%02X, " 1 "\n"' /dev/urandom
 const SALT_ENCRYPTED_STORE: &[u8] = &[
     0xFC, 0x1D, 0x35, 0x7B, 0x96, 0xF3, 0xEF, 0x17, 0x78, 0x7D, 0x70, 0xED, 0xEA, 0xFE, 0x1D, 0x6F,
@@ -36,6 +46,24 @@
     0x55, 0xF8, 0x08, 0x23, 0x81, 0x5F, 0xF5, 0x16, 0x20, 0x3E, 0xBE, 0xBA, 0xB7, 0xA8, 0x43, 0x92,
 ];
 
+// TODO(b/291213394): Remove this once policy is generated from dice_chain
+const HYPOTHETICAL_DICE_POLICY: [u8; 43] = [
+    0x83, 0x01, 0x81, 0x83, 0x01, 0x80, 0xA1, 0x01, 0x00, 0x82, 0x83, 0x01, 0x81, 0x01, 0x73, 0x74,
+    0x65, 0x73, 0x74, 0x69, 0x6E, 0x67, 0x5F, 0x64, 0x69, 0x63, 0x65, 0x5F, 0x70, 0x6F, 0x6C, 0x69,
+    0x63, 0x79, 0x83, 0x02, 0x82, 0x03, 0x18, 0x64, 0x19, 0xE9, 0x75,
+];
+// TODO(b/291213394): Differentiate the Id of nPVM based on 'salt'
+const ID_NP_VM: [u8; ID_SIZE] = [
+    0xF1, 0xB2, 0xED, 0x3B, 0xD1, 0xBD, 0xF0, 0x7D, 0xE1, 0xF0, 0x01, 0xFC, 0x61, 0x71, 0xD3, 0x42,
+    0xE5, 0x8A, 0xAF, 0x33, 0x6C, 0x11, 0xDC, 0xC8, 0x6F, 0xAE, 0x12, 0x5C, 0x26, 0x44, 0x6B, 0x86,
+    0xCC, 0x24, 0xFD, 0xBF, 0x91, 0x4A, 0x54, 0x84, 0xF9, 0x01, 0x59, 0x25, 0x70, 0x89, 0x38, 0x8D,
+    0x5E, 0xE6, 0x91, 0xDF, 0x68, 0x60, 0x69, 0x26, 0xBE, 0xFE, 0x79, 0x58, 0xF7, 0xEA, 0x81, 0x7D,
+];
+const SKP_SECRET_NP_VM: [u8; SECRET_SIZE] = [
+    0xA9, 0x89, 0x97, 0xFE, 0xAE, 0x97, 0x55, 0x4B, 0x32, 0x35, 0xF0, 0xE8, 0x93, 0xDA, 0xEA, 0x24,
+    0x06, 0xAC, 0x36, 0x8B, 0x3C, 0x95, 0x50, 0x16, 0x67, 0x71, 0x65, 0x26, 0xEB, 0xD0, 0xC3, 0x98,
+];
+
 pub enum VmSecret {
     // V2 secrets are derived from 2 independently secured secrets:
     //      1. Secretkeeper protected secrets (skp secret).
@@ -54,15 +82,47 @@
     V1 { dice: OwnedDiceArtifacts },
 }
 
+fn get_id() -> [u8; ID_SIZE] {
+    if super::is_strict_boot() {
+        todo!("Id for protected VM is not implemented");
+    } else {
+        ID_NP_VM
+    }
+}
+
 impl VmSecret {
-    pub fn new(dice_artifacts: OwnedDiceArtifacts) -> Result<VmSecret> {
-        if is_sk_supported() {
-            // TODO(b/291213394): Change this to real Sk protected secret.
-            let fake_skp_secret = ZVec::new(SK_SECRET_SIZE)?;
-            return Ok(Self::V2 { dice: dice_artifacts, skp_secret: fake_skp_secret });
+    pub fn new(
+        dice_artifacts: OwnedDiceArtifacts,
+        vm_service: &Strong<dyn IVirtualMachineService>,
+    ) -> Result<VmSecret> {
+        ensure!(dice_artifacts.bcc().is_some(), "Dice chain missing");
+
+        if let Some(sk_service) = is_sk_supported(vm_service)? {
+            let id = get_id();
+            let mut skp_secret = Zeroizing::new([0u8; SECRET_SIZE]);
+            if super::is_strict_boot() {
+                if super::is_new_instance() {
+                    *skp_secret = rand::random();
+                    store_secret(sk_service.clone(), id, skp_secret.clone(), &dice_artifacts)?;
+                } else {
+                    // Subsequent run of the pVM -> get the secret stored in Secretkeeper.
+                    *skp_secret = get_secret(sk_service.clone(), id, &dice_artifacts)?;
+                }
+            } else {
+                // TODO(b/291213394): Non protected VM don't need to use Secretkeeper, remove this
+                // once we have sufficient testing on protected VM.
+                store_secret(sk_service.clone(), id, SKP_SECRET_NP_VM.into(), &dice_artifacts)?;
+                *skp_secret = get_secret(sk_service.clone(), id, &dice_artifacts)?;
+            }
+            return Ok(Self::V2 {
+                dice: dice_artifacts,
+                skp_secret: ZVec::try_from(skp_secret.to_vec())?,
+            });
         }
+        //  Use V1 secrets if Secretkeeper is not supported.
         Ok(Self::V1 { dice: dice_artifacts })
     }
+
     pub fn dice(&self) -> &OwnedDiceArtifacts {
         match self {
             Self::V2 { dice, .. } => dice,
@@ -94,13 +154,87 @@
     }
 }
 
-// Does the hardware support Secretkeeper.
-fn is_sk_supported() -> bool {
-    if cfg!(llpvm_changes) {
-        return false;
+fn store_secret(
+    secretkeeper: binder::Strong<dyn ISecretkeeper>,
+    id: [u8; ID_SIZE],
+    secret: Zeroizing<[u8; SECRET_SIZE]>,
+    _dice_chain: &OwnedDiceArtifacts,
+) -> Result<()> {
+    // Start a new secretkeeper session!
+    let session = SkSession::new(secretkeeper).map_err(anyhow_err)?;
+    let store_request = StoreSecretRequest {
+        id: Id(id),
+        secret: Secret(*secret),
+        // TODO(b/291233371): Construct policy out of dice_chain.
+        sealing_policy: HYPOTHETICAL_DICE_POLICY.to_vec(),
     };
-    // TODO(b/292209416): This value should be extracted from device tree.
-    // Note: this does not affect the security of pVM. pvmfw & microdroid_manager continue to block
-    // upgraded images. Setting this true is equivalent to including constant salt in vm secrets.
-    true
+    log::info!("Secretkeeper operation: {:?}", store_request);
+
+    let store_request = store_request.serialize_to_packet().to_vec().map_err(anyhow_err)?;
+    let store_response = session.secret_management_request(&store_request).map_err(anyhow_err)?;
+    let store_response = ResponsePacket::from_slice(&store_response).map_err(anyhow_err)?;
+    let response_type = store_response.response_type().map_err(anyhow_err)?;
+    ensure!(
+        response_type == ResponseType::Success,
+        "Secretkeeper store failed with error: {:?}",
+        *SecretkeeperError::deserialize_from_packet(store_response).map_err(anyhow_err)?
+    );
+    Ok(())
+}
+
+fn get_secret(
+    secretkeeper: binder::Strong<dyn ISecretkeeper>,
+    id: [u8; ID_SIZE],
+    _dice_chain: &OwnedDiceArtifacts,
+) -> Result<[u8; SECRET_SIZE]> {
+    // Start a new secretkeeper session!
+    let session = SkSession::new(secretkeeper).map_err(anyhow_err)?;
+    let get_request = GetSecretRequest {
+        id: Id(id),
+        // TODO(b/291233371): Construct policy out of dice_chain.
+        updated_sealing_policy: None,
+    };
+    log::info!("Secretkeeper operation: {:?}", get_request);
+
+    let get_request = get_request.serialize_to_packet().to_vec().map_err(anyhow_err)?;
+    let get_response = session.secret_management_request(&get_request).map_err(anyhow_err)?;
+    let get_response = ResponsePacket::from_slice(&get_response).map_err(anyhow_err)?;
+    let response_type = get_response.response_type().map_err(anyhow_err)?;
+    ensure!(
+        response_type == ResponseType::Success,
+        "Secretkeeper get failed with error: {:?}",
+        *SecretkeeperError::deserialize_from_packet(get_response).map_err(anyhow_err)?
+    );
+    let get_response =
+        *GetSecretResponse::deserialize_from_packet(get_response).map_err(anyhow_err)?;
+    Ok(get_response.secret.0)
+}
+
+#[inline]
+fn anyhow_err<E: core::fmt::Debug>(err: E) -> anyhow::Error {
+    anyhow!("{:?}", err)
+}
+
+// Get the secretkeeper connection if supported. Host can be consulted whether the device supports
+// secretkeeper but that should be used with caution for protected VM.
+fn is_sk_supported(
+    host: &Strong<dyn IVirtualMachineService>,
+) -> Result<Option<Strong<dyn ISecretkeeper>>> {
+    let sk = if cfg!(llpvm_changes) {
+        if super::is_strict_boot() {
+            // TODO: For protected VM check for Secretkeeper authentication data in device tree.
+            None
+        } else {
+            // For non-protected VM, believe what host claims.
+            host.getSecretkeeper()
+                // TODO rename this error!
+                .map_err(|e| {
+                    super::MicrodroidError::FailedToConnectToVirtualizationService(e.to_string())
+                })?
+        }
+    } else {
+        // LLPVM flag is disabled
+        None
+    };
+    Ok(sk)
 }
diff --git a/pvmfw/Android.bp b/pvmfw/Android.bp
index d267e2e..37d8ac9 100644
--- a/pvmfw/Android.bp
+++ b/pvmfw/Android.bp
@@ -26,6 +26,7 @@
         "libpvmfw_avb_nostd",
         "libpvmfw_embedded_key",
         "libpvmfw_fdt_template",
+        "libservice_vm_version",
         "libsmccc",
         "libstatic_assertions",
         "libtinyvec_nostd",
@@ -73,6 +74,7 @@
     srcs: ["src/device_assignment.rs"],
     defaults: ["libpvmfw.test.defaults"],
     rustlibs: [
+        "libhyp",
         "liblibfdt",
         "liblog_rust",
         "libpvmfw_fdt_template",
@@ -84,6 +86,7 @@
         ":test_pvmfw_devices_with_multiple_devices_iommus",
         ":test_pvmfw_devices_with_iommu_sharing",
         ":test_pvmfw_devices_with_iommu_id_conflict",
+        ":test_pvmfw_devices_without_device",
         ":test_pvmfw_devices_without_iommus",
     ],
     // To use libpvmfw_fdt_template for testing
@@ -142,6 +145,13 @@
 }
 
 genrule {
+    name: "test_pvmfw_devices_without_device",
+    defaults: ["test_device_assignment_dts_to_dtb"],
+    srcs: ["testdata/test_pvmfw_devices_without_device.dts"],
+    out: ["test_pvmfw_devices_without_device.dtb"],
+}
+
+genrule {
     name: "test_pvmfw_devices_with_multiple_devices_iommus",
     defaults: ["test_device_assignment_dts_to_dtb"],
     srcs: ["testdata/test_pvmfw_devices_with_multiple_devices_iommus.dts"],
diff --git a/pvmfw/src/config.rs b/pvmfw/src/config.rs
index 4957df2..3f78a88 100644
--- a/pvmfw/src/config.rs
+++ b/pvmfw/src/config.rs
@@ -19,11 +19,10 @@
 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;
-use zerocopy::{FromBytes, FromZeroes, LayoutVerified};
+use zerocopy::{FromBytes, FromZeroes};
 
 /// Configuration data header.
 #[repr(C, packed)]
@@ -129,6 +128,15 @@
 
 impl Entry {
     const COUNT: usize = Self::_VARIANT_COUNT as usize;
+
+    const ALL_ENTRIES: [Entry; Self::COUNT] = [Self::Bcc, Self::DebugPolicy, Self::VmDtbo];
+}
+
+#[derive(Default)]
+pub struct Entries<'a> {
+    pub bcc: &'a mut [u8],
+    pub debug_policy: Option<&'a [u8]>,
+    pub vm_dtbo: Option<&'a mut [u8]>,
 }
 
 #[repr(packed)]
@@ -196,7 +204,7 @@
         }
 
         let (header, rest) =
-            LayoutVerified::<_, Header>::new_from_prefix(bytes).ok_or(Error::HeaderMisaligned)?;
+            zerocopy::Ref::<_, Header>::new_from_prefix(bytes).ok_or(Error::HeaderMisaligned)?;
         let header = header.into_ref();
 
         if header.magic != Header::MAGIC {
@@ -223,7 +231,7 @@
         };
 
         let (header_entries, body) =
-            LayoutVerified::<_, [HeaderEntry]>::new_slice_from_prefix(rest, header.entry_count()?)
+            zerocopy::Ref::<_, [HeaderEntry]>::new_slice_from_prefix(rest, header.entry_count()?)
                 .ok_or(Error::BufferTooSmall)?;
 
         // Validate that we won't get an invalid alignment in the following due to padding to u64.
@@ -233,7 +241,7 @@
         let limits = header.body_lowest_bound()?..total_size;
         let mut ranges: [Option<NonEmptyRange>; Entry::COUNT] = [None; Entry::COUNT];
         let mut last_end = 0;
-        for entry in [Entry::Bcc, Entry::DebugPolicy, Entry::VmDtbo] {
+        for entry in Entry::ALL_ENTRIES {
             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();
@@ -259,35 +267,31 @@
         Ok(Self { body, ranges })
     }
 
-    /// Get slice containing the platform BCC.
-    pub fn get_entries(&mut self) -> (&mut [u8], Option<&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);
-        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);
+    /// Locate the various config entries.
+    pub fn get_entries(self) -> Entries<'a> {
+        // We require the blobs to be in the same order as the `Entry` enum (and this is checked
+        // in `new` above)
+        // So we can just work through the body range and split off the parts we are interested in.
+        let mut offset = 0;
+        let mut body = self.body;
+
+        let mut entries: [Option<&mut [u8]>; Entry::COUNT] = Default::default();
+        for (i, range) in self.ranges.iter().enumerate() {
+            if let Some(range) = range {
+                body = &mut body[range.start - offset..];
+                let (chunk, rest) = body.split_at_mut(range.len());
+                offset = range.end();
+                body = rest;
+                entries[i] = Some(chunk);
+            }
         }
+        let [bcc, debug_policy, vm_dtbo] = entries;
 
-        // 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)),
-                vm_dtbo_range.map(|vm_dtbo_range| Self::from_raw_range_mut(ptr, vm_dtbo_range)),
-            )
-        }
-    }
+        // The platform BCC has always been required.
+        let bcc = bcc.unwrap();
 
-    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.len()) }
+        // We have no reason to mutate so drop the `mut`.
+        let debug_policy = debug_policy.map(|x| &*x);
+        Entries { bcc, debug_policy, vm_dtbo }
     }
 }
diff --git a/pvmfw/src/device_assignment.rs b/pvmfw/src/device_assignment.rs
index 14f1fe5..8d4d840 100644
--- a/pvmfw/src/device_assignment.rs
+++ b/pvmfw/src/device_assignment.rs
@@ -27,7 +27,9 @@
 use core::ffi::CStr;
 use core::iter::Iterator;
 use core::mem;
-use libfdt::{Fdt, FdtError, FdtNode, Phandle};
+use hyp::DeviceAssigningHypervisor;
+use libfdt::{Fdt, FdtError, FdtNode, Phandle, Reg};
+use log::error;
 
 // TODO(b/308694211): Use cstr! from vmbase instead.
 macro_rules! cstr {
@@ -47,10 +49,12 @@
 /// Errors in device assignment.
 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
 pub enum DeviceAssignmentError {
-    // Invalid VM DTBO
+    /// Invalid VM DTBO
     InvalidDtbo,
     /// Invalid __symbols__
     InvalidSymbols,
+    /// Invalid <reg>
+    InvalidReg,
     /// Invalid <interrupts>
     InvalidInterrupts,
     /// Invalid <iommus>
@@ -83,6 +87,7 @@
                 f,
                 "Invalid property in /__symbols__. Must point to valid assignable device node."
             ),
+            Self::InvalidReg => write!(f, "Invalid <reg>"),
             Self::InvalidInterrupts => write!(f, "Invalid <interrupts>"),
             Self::InvalidIommus => write!(f, "Invalid <iommus>"),
             Self::InvalidPvIommu => write!(f, "Invalid pvIOMMU node"),
@@ -178,6 +183,15 @@
     }
 }
 
+fn is_overlayable_node(dtbo_path: &CStr) -> bool {
+    dtbo_path
+        .to_bytes()
+        .split(|char| *char == b'/')
+        .filter(|&component| !component.is_empty())
+        .nth(1)
+        .map_or(false, |name| name == b"__overlay__")
+}
+
 impl AsRef<Fdt> for VmDtbo {
     fn as_ref(&self) -> &Fdt {
         &self.0
@@ -214,6 +228,36 @@
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
 struct Vsid(u32);
 
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+struct DeviceReg {
+    addr: u64,
+    size: u64,
+}
+
+impl TryFrom<Reg<u64>> for DeviceReg {
+    type Error = DeviceAssignmentError;
+
+    fn try_from(reg: Reg<u64>) -> Result<Self> {
+        Ok(Self { addr: reg.addr, size: reg.size.ok_or(DeviceAssignmentError::InvalidReg)? })
+    }
+}
+
+fn parse_node_reg(node: &FdtNode) -> Result<Vec<DeviceReg>> {
+    node.reg()?
+        .ok_or(DeviceAssignmentError::InvalidReg)?
+        .map(DeviceReg::try_from)
+        .collect::<Result<Vec<_>>>()
+}
+
+fn to_be_bytes(reg: &[DeviceReg]) -> Vec<u8> {
+    let mut reg_cells = vec![];
+    for x in reg {
+        reg_cells.extend_from_slice(&x.addr.to_be_bytes());
+        reg_cells.extend_from_slice(&x.size.to_be_bytes());
+    }
+    reg_cells
+}
+
 /// Assigned device information parsed from crosvm DT.
 /// Keeps everything in the owned data because underlying FDT will be reused for platform DT.
 #[derive(Debug, Eq, PartialEq)]
@@ -223,7 +267,7 @@
     // DTBO node path of the assigned device (e.g. "/fragment@rng/__overlay__/rng")
     dtbo_node_path: CString,
     // <reg> property from the crosvm DT
-    reg: Vec<u8>,
+    reg: Vec<DeviceReg>,
     // <interrupts> property from the crosvm DT
     interrupts: Vec<u8>,
     // Parsed <iommus> property from the crosvm DT. Tuple of PvIommu and vSID.
@@ -231,6 +275,22 @@
 }
 
 impl AssignedDeviceInfo {
+    fn parse_reg(
+        node: &FdtNode,
+        hypervisor: &dyn DeviceAssigningHypervisor,
+    ) -> Result<Vec<DeviceReg>> {
+        let device_reg = parse_node_reg(node)?;
+        // TODO(b/277993056): Valid the result back with physical reg
+        for reg in &device_reg {
+            hypervisor.get_phys_mmio_token(reg.addr, reg.size).map_err(|e| {
+                let name = node.name();
+                error!("Failed to validate device <reg>, error={e:?}, name={name:?}, reg={reg:?}");
+                DeviceAssignmentError::InvalidReg
+            })?;
+        }
+        Ok(device_reg)
+    }
+
     fn parse_interrupts(node: &FdtNode) -> Result<Vec<u8>> {
         // Validation: Validate if interrupts cell numbers are multiple of #interrupt-cells.
         // We can't know how many interrupts would exist.
@@ -250,6 +310,7 @@
     fn parse_iommus(
         node: &FdtNode,
         pviommus: &BTreeMap<Phandle, PvIommu>,
+        hypervisor: &dyn DeviceAssigningHypervisor,
     ) -> Result<Vec<(PvIommu, Vsid)>> {
         let mut iommus = vec![];
         let Some(mut cells) = node.getprop_cells(cstr!("iommus"))? else {
@@ -266,6 +327,15 @@
             };
             let vsid = Vsid(cell);
 
+            // TODO(b/277993056): Valid the result back with phys iommu id and sid..
+            hypervisor
+                .get_phys_iommu_token(pviommu.id.into(), vsid.0.into())
+                .map_err(|e| {
+                    let name = node.name().unwrap_or_default();
+                    error!("Failed to validate device <iommus>, error={e:?}, name={name:?}, pviommu={pviommu:?}, vsid={:?}", vsid.0);
+                    DeviceAssignmentError::InvalidIommus
+                })?;
+
             iommus.push((*pviommu, vsid));
         }
         Ok(iommus)
@@ -276,27 +346,21 @@
         vm_dtbo: &VmDtbo,
         dtbo_node_path: &CStr,
         pviommus: &BTreeMap<Phandle, PvIommu>,
+        hypervisor: &dyn DeviceAssigningHypervisor,
     ) -> Result<Option<Self>> {
         let node_path = vm_dtbo.locate_overlay_target_path(dtbo_node_path)?;
 
         let Some(node) = fdt.node(&node_path)? else { return Ok(None) };
 
-        // TODO(b/277993056): Validate reg with HVC, and keep reg with FdtNode::reg()
-        let reg = node.getprop(cstr!("reg")).unwrap().unwrap();
+        let reg = Self::parse_reg(&node, hypervisor)?;
         let interrupts = Self::parse_interrupts(&node)?;
-        let iommus = Self::parse_iommus(&node, pviommus)?;
-        Ok(Some(Self {
-            node_path,
-            dtbo_node_path: dtbo_node_path.into(),
-            reg: reg.to_vec(),
-            interrupts,
-            iommus,
-        }))
+        let iommus = Self::parse_iommus(&node, pviommus, hypervisor)?;
+        Ok(Some(Self { node_path, dtbo_node_path: dtbo_node_path.into(), reg, interrupts, iommus }))
     }
 
     fn patch(&self, fdt: &mut Fdt, pviommu_phandles: &BTreeMap<PvIommu, Phandle>) -> Result<()> {
         let mut dst = fdt.node_mut(&self.node_path)?.unwrap();
-        dst.setprop(cstr!("reg"), &self.reg)?;
+        dst.setprop(cstr!("reg"), &to_be_bytes(&self.reg))?;
         dst.setprop(cstr!("interrupts"), &self.interrupts)?;
         let mut iommus = Vec::with_capacity(8 * self.iommus.len());
         for (pviommu, vsid) in &self.iommus {
@@ -339,7 +403,11 @@
     /// Parses fdt and vm_dtbo, and creates new DeviceAssignmentInfo
     // TODO(b/277993056): Parse __local_fixups__
     // TODO(b/277993056): Parse __fixups__
-    pub fn parse(fdt: &Fdt, vm_dtbo: &VmDtbo) -> Result<Option<Self>> {
+    pub fn parse(
+        fdt: &Fdt,
+        vm_dtbo: &VmDtbo,
+        hypervisor: &dyn DeviceAssigningHypervisor,
+    ) -> Result<Option<Self>> {
         let Some(symbols_node) = vm_dtbo.as_ref().symbols()? else {
             // /__symbols__ should contain all assignable devices.
             // If empty, then nothing can be assigned.
@@ -358,8 +426,11 @@
             let symbol_prop_value = symbol_prop.value()?;
             let dtbo_node_path = CStr::from_bytes_with_nul(symbol_prop_value)
                 .or(Err(DeviceAssignmentError::InvalidSymbols))?;
+            if !is_overlayable_node(dtbo_node_path) {
+                continue;
+            }
             let assigned_device =
-                AssignedDeviceInfo::parse(fdt, vm_dtbo, dtbo_node_path, &pviommus)?;
+                AssignedDeviceInfo::parse(fdt, vm_dtbo, dtbo_node_path, &pviommus, hypervisor)?;
             if let Some(assigned_device) = assigned_device {
                 assigned_devices.push(assigned_device);
             } else {
@@ -369,7 +440,15 @@
         if assigned_devices.is_empty() {
             return Ok(None);
         }
+
+        // Clean up any nodes that wouldn't be overlaid but may contain reference to filtered nodes.
+        // Otherwise, `fdt_apply_overlay()` would fail because of missing phandle reference.
         filtered_dtbo_paths.push(CString::new("/__symbols__").unwrap());
+        // TODO(b/277993056): Also filter other unused nodes/props in __local_fixups__
+        filtered_dtbo_paths.push(CString::new("/__local_fixups__/host").unwrap());
+
+        // Note: Any node without __overlay__ will be ignored by fdt_apply_overlay,
+        // so doesn't need to be filtered.
 
         Ok(Some(Self { pviommus: unique_pviommus, assigned_devices, filtered_dtbo_paths }))
     }
@@ -390,22 +469,6 @@
             node.nop()?;
         }
 
-        // Filters pvmfw-specific properties in assigned device node.
-        const FILTERED_VM_DTBO_PROP: [&CStr; 3] = [
-            cstr!("android,pvmfw,phy-reg"),
-            cstr!("android,pvmfw,phy-iommu"),
-            cstr!("android,pvmfw,phy-sid"),
-        ];
-        for assigned_device in &self.assigned_devices {
-            let mut node = vm_dtbo.node_mut(&assigned_device.dtbo_node_path).unwrap().unwrap();
-            for prop in FILTERED_VM_DTBO_PROP {
-                match node.nop_property(prop) {
-                    Err(FdtError::NotFound) => Ok(()), // allows not exists
-                    other => other,
-                }?;
-            }
-        }
-
         Ok(())
     }
 
@@ -446,19 +509,42 @@
 #[cfg(test)]
 mod tests {
     use super::*;
-    use alloc::collections::BTreeSet;
+    use alloc::collections::{BTreeMap, BTreeSet};
     use std::fs;
 
     const VM_DTBO_FILE_PATH: &str = "test_pvmfw_devices_vm_dtbo.dtbo";
     const VM_DTBO_WITHOUT_SYMBOLS_FILE_PATH: &str =
         "test_pvmfw_devices_vm_dtbo_without_symbols.dtbo";
     const FDT_WITHOUT_IOMMUS_FILE_PATH: &str = "test_pvmfw_devices_without_iommus.dtb";
+    const FDT_WITHOUT_DEVICE_FILE_PATH: &str = "test_pvmfw_devices_without_device.dtb";
     const FDT_FILE_PATH: &str = "test_pvmfw_devices_with_rng.dtb";
     const FDT_WITH_MULTIPLE_DEVICES_IOMMUS_FILE_PATH: &str =
         "test_pvmfw_devices_with_multiple_devices_iommus.dtb";
     const FDT_WITH_IOMMU_SHARING: &str = "test_pvmfw_devices_with_iommu_sharing.dtb";
     const FDT_WITH_IOMMU_ID_CONFLICT: &str = "test_pvmfw_devices_with_iommu_id_conflict.dtb";
 
+    #[derive(Debug, Default)]
+    struct MockHypervisor {
+        mmio_tokens: BTreeMap<(u64, u64), u64>,
+        iommu_tokens: BTreeMap<(u64, u64), (u64, u64)>,
+    }
+
+    impl DeviceAssigningHypervisor for MockHypervisor {
+        fn get_phys_mmio_token(&self, base_ipa: u64, size: u64) -> hyp::Result<u64> {
+            Ok(*self.mmio_tokens.get(&(base_ipa, size)).ok_or(hyp::Error::KvmError(
+                hyp::KvmError::InvalidParameter,
+                0xc6000012, /* VENDOR_HYP_KVM_DEV_REQ_MMIO_FUNC_ID */
+            ))?)
+        }
+
+        fn get_phys_iommu_token(&self, pviommu_id: u64, vsid: u64) -> hyp::Result<(u64, u64)> {
+            Ok(*self.iommu_tokens.get(&(pviommu_id, vsid)).ok_or(hyp::Error::KvmError(
+                hyp::KvmError::InvalidParameter,
+                0xc6000013, /* VENDOR_HYP_KVM_DEV_REQ_DMA_FUNC_ID */
+            ))?)
+        }
+    }
+
     #[derive(Debug, Eq, PartialEq)]
     struct AssignedDeviceNode {
         path: CString,
@@ -473,8 +559,7 @@
                 return Err(FdtError::NotFound.into());
             };
 
-            // TODO(b/277993056): Replace DeviceAssignmentError::Internal
-            let reg = node.getprop(cstr!("reg"))?.ok_or(DeviceAssignmentError::Internal)?;
+            let reg = node.getprop(cstr!("reg"))?.ok_or(DeviceAssignmentError::InvalidReg)?;
             let interrupts = node
                 .getprop(cstr!("interrupts"))?
                 .ok_or(DeviceAssignmentError::InvalidInterrupts)?;
@@ -524,6 +609,12 @@
         v
     }
 
+    impl From<[u64; 2]> for DeviceReg {
+        fn from(fdt_cells: [u64; 2]) -> Self {
+            DeviceReg { addr: fdt_cells[0], size: fdt_cells[1] }
+        }
+    }
+
     #[test]
     fn device_info_new_without_symbols() {
         let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
@@ -531,7 +622,20 @@
         let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
         let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
 
-        let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap();
+        let hypervisor: MockHypervisor = Default::default();
+        let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap();
+        assert_eq!(device_info, None);
+    }
+
+    #[test]
+    fn device_info_new_without_device() {
+        let mut fdt_data = fs::read(FDT_WITHOUT_DEVICE_FILE_PATH).unwrap();
+        let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
+        let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
+        let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
+
+        let hypervisor: MockHypervisor = Default::default();
+        let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap();
         assert_eq!(device_info, None);
     }
 
@@ -542,12 +646,16 @@
         let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
         let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
 
-        let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap().unwrap();
+        let hypervisor = MockHypervisor {
+            mmio_tokens: [((0x9, 0xFF), 0x300)].into(),
+            iommu_tokens: BTreeMap::new(),
+        };
+        let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
 
         let expected = [AssignedDeviceInfo {
-            node_path: CString::new("/backlight").unwrap(),
-            dtbo_node_path: cstr!("/fragment@backlight/__overlay__/backlight").into(),
-            reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
+            node_path: CString::new("/bus0/backlight").unwrap(),
+            dtbo_node_path: cstr!("/fragment@backlight/__overlay__/bus0/backlight").into(),
+            reg: vec![[0x9, 0xFF].into()],
             interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
             iommus: vec![],
         }];
@@ -562,12 +670,16 @@
         let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
         let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
 
-        let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap().unwrap();
+        let hypervisor = MockHypervisor {
+            mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
+            iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
+        };
+        let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
 
         let expected = [AssignedDeviceInfo {
             node_path: CString::new("/rng").unwrap(),
             dtbo_node_path: cstr!("/fragment@rng/__overlay__/rng").into(),
-            reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
+            reg: vec![[0x9, 0xFF].into()],
             interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
             iommus: vec![(PvIommu { id: 0x4 }, Vsid(0xFF0))],
         }];
@@ -583,7 +695,8 @@
         let fdt = Fdt::create_empty_tree(&mut fdt_data).unwrap();
         let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
 
-        let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap();
+        let hypervisor: MockHypervisor = Default::default();
+        let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap();
         assert_eq!(device_info, None);
     }
 
@@ -594,7 +707,11 @@
         let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
         let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
 
-        let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap().unwrap();
+        let hypervisor = MockHypervisor {
+            mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
+            iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
+        };
+        let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
         device_info.filter(vm_dtbo).unwrap();
 
         let vm_dtbo = vm_dtbo.as_mut();
@@ -608,7 +725,8 @@
         let led = vm_dtbo.node(cstr!("/fragment@led/__overlay__/led")).unwrap();
         assert_eq!(led, None);
 
-        let backlight = vm_dtbo.node(cstr!("/fragment@backlight/__overlay__/backlight")).unwrap();
+        let backlight =
+            vm_dtbo.node(cstr!("/fragment@backlight/__overlay__/bus0/backlight")).unwrap();
         assert_eq!(backlight, None);
 
         let symbols_node = vm_dtbo.symbols().unwrap();
@@ -624,7 +742,11 @@
         let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
         let platform_dt = Fdt::create_empty_tree(data.as_mut_slice()).unwrap();
 
-        let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap().unwrap();
+        let hypervisor = MockHypervisor {
+            mmio_tokens: [((0x9, 0xFF), 0x300)].into(),
+            iommu_tokens: BTreeMap::new(),
+        };
+        let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
         device_info.filter(vm_dtbo).unwrap();
 
         // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
@@ -633,6 +755,10 @@
         }
         device_info.patch(platform_dt).unwrap();
 
+        let rng_node = platform_dt.node(cstr!("/bus0/backlight")).unwrap().unwrap();
+        let phandle = rng_node.getprop_u32(cstr!("phandle")).unwrap();
+        assert_ne!(None, phandle);
+
         // Note: Intentionally not using AssignedDeviceNode for matching all props.
         type FdtResult<T> = libfdt::Result<T>;
         let expected: Vec<(FdtResult<&CStr>, FdtResult<Vec<u8>>)> = vec![
@@ -640,10 +766,10 @@
             (Ok(cstr!("compatible")), Ok(Vec::from(*b"android,backlight\0"))),
             (Ok(cstr!("interrupts")), Ok(into_fdt_prop(vec![0x0, 0xF, 0x4]))),
             (Ok(cstr!("iommus")), Ok(Vec::new())),
+            (Ok(cstr!("phandle")), Ok(into_fdt_prop(vec![phandle.unwrap()]))),
             (Ok(cstr!("reg")), Ok(into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]))),
         ];
 
-        let rng_node = platform_dt.node(cstr!("/backlight")).unwrap().unwrap();
         let mut properties: Vec<_> = rng_node
             .properties()
             .unwrap()
@@ -669,7 +795,11 @@
         let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
         platform_dt.unpack().unwrap();
 
-        let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap().unwrap();
+        let hypervisor = MockHypervisor {
+            mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
+            iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
+        };
+        let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
         device_info.filter(vm_dtbo).unwrap();
 
         // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
@@ -703,7 +833,21 @@
         let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
         platform_dt.unpack().unwrap();
 
-        let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap().unwrap();
+        let hypervisor = MockHypervisor {
+            mmio_tokens: [
+                ((0x9, 0xFF), 0x12F00000),
+                ((0x100, 0x1000), 0xF00000),
+                ((0x200, 0x1000), 0xF10000),
+            ]
+            .into(),
+            iommu_tokens: [
+                ((0x4, 0xFF0), (0x12E40000, 3)),
+                ((0x40, 0xFFA), (0x40000, 0x4)),
+                ((0x50, 0xFFB), (0x50000, 0x5)),
+            ]
+            .into(),
+        };
+        let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
         device_info.filter(vm_dtbo).unwrap();
 
         // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
@@ -721,7 +865,7 @@
             },
             AssignedDeviceNode {
                 path: CString::new("/light").unwrap(),
-                reg: into_fdt_prop(vec![0x100, 0x9]),
+                reg: into_fdt_prop(vec![0x0, 0x100, 0x0, 0x1000, 0x0, 0x200, 0x0, 0x1000]),
                 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x5]),
                 iommus: vec![0x40, 0xFFA, 0x50, 0xFFB],
             },
@@ -746,7 +890,11 @@
         let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
         platform_dt.unpack().unwrap();
 
-        let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap().unwrap();
+        let hypervisor = MockHypervisor {
+            mmio_tokens: [((0x9, 0xFF), 0x12F00000), ((0x100, 0x9), 0x12000000)].into(),
+            iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 3))].into(),
+        };
+        let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
         device_info.filter(vm_dtbo).unwrap();
 
         // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
@@ -764,7 +912,7 @@
             },
             AssignedDeviceNode {
                 path: CString::new("/led").unwrap(),
-                reg: into_fdt_prop(vec![0x100, 0x9]),
+                reg: into_fdt_prop(vec![0x0, 0x100, 0x0, 0x9]),
                 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x5]),
                 iommus: vec![0x4, 0xFF0],
             },
@@ -786,8 +934,44 @@
         let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
         let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
 
-        let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo);
+        let hypervisor = MockHypervisor {
+            mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
+            iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
+        };
+        let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
 
         assert_eq!(device_info, Err(DeviceAssignmentError::DuplicatedPvIommuIds));
     }
+
+    #[test]
+    fn device_info_invalid_reg() {
+        let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
+        let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
+        let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
+        let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
+
+        let hypervisor = MockHypervisor {
+            mmio_tokens: BTreeMap::new(),
+            iommu_tokens: [((0x4, 0xFF0), (0x12E40000, 0x3))].into(),
+        };
+        let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
+
+        assert_eq!(device_info, Err(DeviceAssignmentError::InvalidReg));
+    }
+
+    #[test]
+    fn device_info_invalid_iommus() {
+        let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
+        let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
+        let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
+        let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
+
+        let hypervisor = MockHypervisor {
+            mmio_tokens: [((0x9, 0xFF), 0x12F00000)].into(),
+            iommu_tokens: BTreeMap::new(),
+        };
+        let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
+
+        assert_eq!(device_info, Err(DeviceAssignmentError::InvalidIommus));
+    }
 }
diff --git a/pvmfw/src/entry.rs b/pvmfw/src/entry.rs
index 65c8f26..2475f32 100644
--- a/pvmfw/src/entry.rs
+++ b/pvmfw/src/entry.rs
@@ -212,12 +212,12 @@
     // then remapped by `init_page_table()`.
     let appended_data = unsafe { get_appended_data_slice() };
 
-    let mut appended = AppendedPayload::new(appended_data).ok_or_else(|| {
+    let appended = AppendedPayload::new(appended_data).ok_or_else(|| {
         error!("No valid configuration found");
         RebootReason::InvalidConfig
     })?;
 
-    let (bcc_slice, debug_policy, vm_dtbo) = appended.get_entries();
+    let config_entries = appended.get_entries();
 
     // Up to this point, we were using the built-in static (from .rodata) page tables.
     MEMORY.lock().replace(MemoryTracker::new(
@@ -227,13 +227,19 @@
         Some(memory::appended_payload_range()),
     ));
 
-    let slices = MemorySlices::new(fdt, payload, payload_size, vm_dtbo)?;
+    let slices = MemorySlices::new(fdt, payload, payload_size, config_entries.vm_dtbo)?;
 
     // This wrapper allows main() to be blissfully ignorant of platform details.
-    let next_bcc = crate::main(slices.fdt, slices.kernel, slices.ramdisk, bcc_slice, debug_policy)?;
+    let next_bcc = crate::main(
+        slices.fdt,
+        slices.kernel,
+        slices.ramdisk,
+        config_entries.bcc,
+        config_entries.debug_policy,
+    )?;
 
     // Writable-dirty regions will be flushed when MemoryTracker is dropped.
-    bcc_slice.zeroize();
+    config_entries.bcc.zeroize();
 
     info!("Expecting a bug making MMIO_GUARD_UNMAP return NOT_SUPPORTED on success");
     MEMORY.lock().as_mut().unwrap().mmio_unmap_all().map_err(|e| {
@@ -437,10 +443,10 @@
         }
     }
 
-    fn get_entries(&mut self) -> (&mut [u8], Option<&mut [u8]>, Option<&mut [u8]>) {
+    fn get_entries(self) -> config::Entries<'a> {
         match self {
-            Self::Config(ref mut cfg) => cfg.get_entries(),
-            Self::LegacyBcc(ref mut bcc) => (bcc, None, None),
+            Self::Config(cfg) => cfg.get_entries(),
+            Self::LegacyBcc(bcc) => config::Entries { bcc, ..Default::default() },
         }
     }
 }
diff --git a/pvmfw/src/fdt.rs b/pvmfw/src/fdt.rs
index 7a89b75..2a6819b 100644
--- a/pvmfw/src/fdt.rs
+++ b/pvmfw/src/fdt.rs
@@ -15,8 +15,7 @@
 //! High-level FDT functions.
 
 use crate::bootargs::BootArgsIterator;
-use crate::device_assignment::DeviceAssignmentInfo;
-use crate::device_assignment::VmDtbo;
+use crate::device_assignment::{DeviceAssignmentInfo, VmDtbo};
 use crate::helpers::GUEST_PAGE_SIZE;
 use crate::Box;
 use crate::RebootReason;
@@ -201,19 +200,27 @@
     Ok(())
 }
 
-fn read_vendor_public_key_from(fdt: &Fdt) -> libfdt::Result<Option<Vec<u8>>> {
+fn read_vendor_hashtree_descriptor_root_digest_from(fdt: &Fdt) -> libfdt::Result<Option<Vec<u8>>> {
     if let Some(avf_node) = fdt.node(cstr!("/avf"))? {
-        if let Some(vendor_public_key) = avf_node.getprop(cstr!("vendor_public_key"))? {
-            return Ok(Some(vendor_public_key.to_vec()));
+        if let Some(vendor_hashtree_descriptor_root_digest) =
+            avf_node.getprop(cstr!("vendor_hashtree_descriptor_root_digest"))?
+        {
+            return Ok(Some(vendor_hashtree_descriptor_root_digest.to_vec()));
         }
     }
     Ok(None)
 }
 
-fn patch_vendor_public_key(fdt: &mut Fdt, vendor_public_key: &[u8]) -> libfdt::Result<()> {
+fn patch_vendor_hashtree_descriptor_root_digest(
+    fdt: &mut Fdt,
+    vendor_hashtree_descriptor_root_digest: &[u8],
+) -> libfdt::Result<()> {
     let mut root_node = fdt.root_mut()?;
     let mut avf_node = root_node.add_subnode(cstr!("/avf"))?;
-    avf_node.setprop(cstr!("vendor_public_key"), vendor_public_key)?;
+    avf_node.setprop(
+        cstr!("vendor_hashtree_descriptor_root_digest"),
+        vendor_hashtree_descriptor_root_digest,
+    )?;
     Ok(())
 }
 
@@ -609,7 +616,7 @@
     serial_info: SerialInfo,
     pub swiotlb_info: SwiotlbInfo,
     device_assignment: Option<DeviceAssignmentInfo>,
-    vendor_public_key: Option<Vec<u8>>,
+    vendor_hashtree_descriptor_root_digest: Option<Vec<u8>>,
 }
 
 impl DeviceTreeInfo {
@@ -721,24 +728,35 @@
     validate_swiotlb_info(&swiotlb_info, &memory_range)?;
 
     let device_assignment = match vm_dtbo {
-        Some(vm_dtbo) => DeviceAssignmentInfo::parse(fdt, vm_dtbo).map_err(|e| {
-            error!("Failed to parse device assignment from DT and VM DTBO: {e}");
-            RebootReason::InvalidFdt
-        })?,
+        Some(vm_dtbo) => {
+            if let Some(hypervisor) = hyp::get_device_assigner() {
+                DeviceAssignmentInfo::parse(fdt, vm_dtbo, hypervisor).map_err(|e| {
+                    error!("Failed to parse device assignment from DT and VM DTBO: {e}");
+                    RebootReason::InvalidFdt
+                })?
+            } else {
+                warn!(
+                    "Device assignment is ignored because device assigning hypervisor is missing"
+                );
+                None
+            }
+        }
         None => None,
     };
 
     // TODO(b/285854379) : A temporary solution lives. This is for enabling
     // microdroid vendor partition for non-protected VM as well. When passing
-    // DT path containing vendor_public_key via fstab, init stage will check
-    // if vendor_public_key exists in the init stage, regardless the protection.
-    // Adding this temporary solution will prevent fatal in init stage for
-    // protected VM. However, this data is not trustable without validating
-    // with vendor public key value comes from ABL.
-    let vendor_public_key = read_vendor_public_key_from(fdt).map_err(|e| {
-        error!("Failed to read vendor_public_key from DT: {e}");
-        RebootReason::InvalidFdt
-    })?;
+    // DT path containing vendor_hashtree_descriptor_root_digest via fstab, init
+    // stage will check if vendor_hashtree_descriptor_root_digest exists in the
+    // init stage, regardless the protection. Adding this temporary solution
+    // will prevent fatal in init stage for protected VM. However, this data is
+    // not trustable without validating root digest of vendor hashtree
+    // descriptor comes from ABL.
+    let vendor_hashtree_descriptor_root_digest =
+        read_vendor_hashtree_descriptor_root_digest_from(fdt).map_err(|e| {
+            error!("Failed to read vendor_hashtree_descriptor_root_digest from DT: {e}");
+            RebootReason::InvalidFdt
+        })?;
 
     Ok(DeviceTreeInfo {
         kernel_range,
@@ -750,7 +768,7 @@
         serial_info,
         swiotlb_info,
         device_assignment,
-        vendor_public_key,
+        vendor_hashtree_descriptor_root_digest,
     })
 }
 
@@ -803,9 +821,12 @@
             RebootReason::InvalidFdt
         })?;
     }
-    if let Some(vendor_public_key) = &info.vendor_public_key {
-        patch_vendor_public_key(fdt, vendor_public_key).map_err(|e| {
-            error!("Failed to patch vendor_public_key to DT: {e}");
+    if let Some(vendor_hashtree_descriptor_root_digest) =
+        &info.vendor_hashtree_descriptor_root_digest
+    {
+        patch_vendor_hashtree_descriptor_root_digest(fdt, vendor_hashtree_descriptor_root_digest)
+            .map_err(|e| {
+            error!("Failed to patch vendor_hashtree_descriptor_root_digest to DT: {e}");
             RebootReason::InvalidFdt
         })?;
     }
@@ -819,7 +840,7 @@
     bcc: &[u8],
     new_instance: bool,
     strict_boot: bool,
-    debug_policy: Option<&mut [u8]>,
+    debug_policy: Option<&[u8]>,
     debuggable: bool,
     kaslr_seed: u64,
 ) -> libfdt::Result<()> {
diff --git a/pvmfw/src/instance.rs b/pvmfw/src/instance.rs
index 28e9ca3..e98f663 100644
--- a/pvmfw/src/instance.rs
+++ b/pvmfw/src/instance.rs
@@ -126,6 +126,17 @@
             let decrypted = aead_ctx.open(payload, nonce, ad, &mut entry)?;
 
             let body = EntryBody::read_from(decrypted).unwrap();
+            if dice_inputs.rkp_vm_marker {
+                // The RKP VM is allowed to run if it has passed the verified boot check and
+                // contains the expected version in its AVB footer.
+                // The comparison below with the previous boot information is skipped to enable the
+                // simultaneous update of the pvmfw and RKP VM.
+                // For instance, when both the pvmfw and RKP VM are updated, the code hash of the
+                // RKP VM will differ from the one stored in the instance image. In this case, the
+                // RKP VM is still allowed to run.
+                // This ensures that the updated RKP VM will retain the same CDIs in the next stage.
+                return Ok((false, body.salt));
+            }
             if body.code_hash != dice_inputs.code_hash {
                 Err(Error::RecordedCodeHashMismatch)
             } else if body.auth_hash != dice_inputs.auth_hash {
diff --git a/pvmfw/src/main.rs b/pvmfw/src/main.rs
index 8d872d4..f80bae1 100644
--- a/pvmfw/src/main.rs
+++ b/pvmfw/src/main.rs
@@ -62,7 +62,7 @@
     signed_kernel: &[u8],
     ramdisk: Option<&[u8]>,
     current_bcc_handover: &[u8],
-    mut debug_policy: Option<&mut [u8]>,
+    mut debug_policy: Option<&[u8]>,
 ) -> Result<Range<usize>, RebootReason> {
     info!("pVM firmware");
     debug!("FDT: {:?}", fdt.as_ptr());
@@ -114,6 +114,17 @@
 
     if verified_boot_data.has_capability(Capability::RemoteAttest) {
         info!("Service VM capable of remote attestation detected");
+        if service_vm_version::VERSION != verified_boot_data.rollback_index {
+            // For RKP VM, we only boot if the version in the AVB footer of its kernel matches
+            // the one embedded in pvmfw at build time.
+            // This prevents the pvmfw from booting a roll backed RKP VM.
+            error!(
+                "Service VM version mismatch: expected {}, found {}",
+                service_vm_version::VERSION,
+                verified_boot_data.rollback_index
+            );
+            return Err(RebootReason::InvalidPayload);
+        }
     }
 
     if verified_boot_data.has_capability(Capability::SecretkeeperProtection) {
diff --git a/pvmfw/testdata/test_pvmfw_devices_vm_dtbo.dts b/pvmfw/testdata/test_pvmfw_devices_vm_dtbo.dts
index da08694..91693f7 100644
--- a/pvmfw/testdata/test_pvmfw_devices_vm_dtbo.dts
+++ b/pvmfw/testdata/test_pvmfw_devices_vm_dtbo.dts
@@ -1,61 +1,118 @@
 /dts-v1/;
-/plugin/;
 
 / {
-	fragment@rng {
-		target-path = "/";
-		__overlay__ {
-			rng {
-				compatible = "android,rng";
-				android,rng,ignore-gctrl-reset;
-				android,pvmfw,phy-reg = <0x0 0x12F00000 0x1000>;
-				android,pvmfw,phy-iommu = <0x0 0x12E40000>;
-				android,pvmfw,phy-sid = <3>;
-			};
-		};
-	};
-
-	fragment@sensor {
-		target-path = "/";
-		__overlay__ {
-			light {
-				compatible = "android,light";
-				version = <0x1 0x2>;
-				android,pvmfw,phy-reg = <0x0 0xF00000 0x1000>;
-				android,pvmfw,phy-iommu = <0x0 0x40000>, <0x0 0x50000>;
-				android,pvmfw,phy-sid = <4>, <5>;
-			};
-		};
-	};
-
-	fragment@led {
-		target-path = "/";
-		__overlay__ {
-			led {
-				compatible = "android,led";
-				prop = <0x555>;
-				android,pvmfw,phy-reg = <0x0 0x12000000 0x1000>;
-				android,pvmfw,phy-iommu = <0x0 0x12E40000>;
-				android,pvmfw,phy-sid = <3>;
-			};
-		};
-	};
-
-	fragment@backlight {
-		target-path = "/";
-		__overlay__ {
-			backlight {
-				compatible = "android,backlight";
-				android,backlight,ignore-gctrl-reset;
-				android,pvmfw,phy-reg = <0x0 0x300 0x100>;
-			};
-		};
-	};
-
-	__symbols__ {
-		rng = "/fragment@rng/__overlay__/rng";
-		sensor = "/fragment@sensor/__overlay__/light";
-		led = "/fragment@led/__overlay__/led";
-		backlight = "/fragment@backlight/__overlay__/backlight";
-	};
+    host {
+        #address-cells = <0x2>;
+        #size-cells = <0x1>;
+        rng {
+            reg = <0x0 0x12f00000 0x1000>;
+            iommus = <0x1 0x3>;
+            android,pvmfw,target = <0x2>;
+        };
+        light {
+            reg = <0x0 0x00f00000 0x1000>, <0x0 0x00f10000 0x1000>;
+            iommus = <0x3 0x4>, <0x4 0x5>;
+            android,pvmfw,target = <0x5>;
+        };
+        led {
+            reg = <0x0 0x12000000 0x1000>;
+            iommus = <0x1 0x3>;
+            android,pvmfw,target = <0x6>;
+        };
+        bus0 {
+            #address-cells = <0x1>;
+            #size-cells = <0x1>;
+            backlight {
+                reg = <0x300 0x100>;
+                android,pvmfw,target = <0x7>;
+            };
+        };
+        iommu0 {
+            #iommu-cells = <0x1>;
+            android,pvmfw,token = <0x0 0x12e40000>;
+            phandle = <0x1>;
+        };
+        iommu1 {
+            #iommu-cells = <0x1>;
+            android,pvmfw,token = <0x0 0x40000>;
+            phandle = <0x3>;
+        };
+        iommu2 {
+            #iommu-cells = <0x1>;
+            android,pvmfw,token = <0x0 0x50000>;
+            phandle = <0x4>;
+        };
+    };
+    fragment@rng {
+        target-path = "/";
+        __overlay__ {
+            rng {
+                compatible = "android,rng";
+                android,rng,ignore-gctrl-reset;
+                phandle = <0x2>;
+            };
+        };
+    };
+    fragment@sensor {
+        target-path = "/";
+        __overlay__ {
+            light {
+                compatible = "android,light";
+                version = <0x1 0x2>;
+                phandle = <0x5>;
+            };
+        };
+    };
+    fragment@led {
+        target-path = "/";
+        __overlay__ {
+            led {
+                compatible = "android,led";
+                prop = <0x555>;
+                phandle = <0x6>;
+            };
+        };
+    };
+    fragment@backlight {
+        target-path = "/";
+        __overlay__ {
+            bus0 {
+                backlight {
+                    compatible = "android,backlight";
+                    android,backlight,ignore-gctrl-reset;
+                    phandle = <0x7>;
+                };
+            };
+        };
+    };
+    __symbols__ {
+        iommu0 = "/host/iommu0";
+        iommu1 = "/host/iommu1";
+        iommu2 = "/host/iommu2";
+        rng = "/fragment@rng/__overlay__/rng";
+        light = "/fragment@sensor/__overlay__/light";
+        led = "/fragment@led/__overlay__/led";
+        backlight = "/fragment@backlight/__overlay__/bus0/backlight";
+    };
+    __local_fixups__ {
+        host {
+            rng {
+                iommus = <0x0>;
+                android,pvmfw,target = <0x0>;
+            };
+            light {
+                iommus = <0x0 0x8>;
+                android,pvmfw,target = <0x0>;
+            };
+            led {
+                iommus = <0x0>;
+                android,pvmfw,target = <0x0>;
+            };
+            bus0 {
+                backlight {
+                    android,pvmfw,target = <0x0>;
+                };
+            };
+        };
+    };
 };
diff --git a/pvmfw/testdata/test_pvmfw_devices_vm_dtbo_without_symbols.dts b/pvmfw/testdata/test_pvmfw_devices_vm_dtbo_without_symbols.dts
index 18b9e79..2bc8081 100644
--- a/pvmfw/testdata/test_pvmfw_devices_vm_dtbo_without_symbols.dts
+++ b/pvmfw/testdata/test_pvmfw_devices_vm_dtbo_without_symbols.dts
@@ -1,43 +1,114 @@
 /dts-v1/;
-/plugin/;
 
 / {
-	fragment@rng {
-		target-path = "/";
-		__overlay__ {
-			rng {
-				compatible = "android,rng";
-				android,rng,ignore-gctrl-reset;
-				android,pvmfw,phy-reg = <0x0 0x12F00000 0x1000>;
-				android,pvmfw,phy-iommu = <0x0 0x12E40000>;
-				android,pvmfw,phy-sid = <3>;
-			};
-		};
-	};
-
-	fragment@sensor {
-		target-path = "/";
-		__overlay__ {
-			light {
-				compatible = "android,light";
-				version = <0x1 0x2>;
-				android,pvmfw,phy-reg = <0x0 0xF00000 0x1000>;
-				android,pvmfw,phy-iommu = <0x0 0x40000>, <0x0 0x50000>;
-				android,pvmfw,phy-sid = <4>, <5>;
-			};
-		};
-	};
-
-	fragment@led {
-		target-path = "/";
-		__overlay__ {
-			led {
-				compatible = "android,led";
-				prop;
-				android,pvmfw,phy-reg = <0x0 0x12F00000 0x1000>;
-				android,pvmfw,phy-iommu = <0x0 0x20000>, <0x0 0x30000>;
-				android,pvmfw,phy-sid = <7>, <8>;
-			};
-		};
-	};
+    host {
+        #address-cells = <0x2>;
+        #size-cells = <0x1>;
+        rng {
+            reg = <0x0 0x12f00000 0x1000>;
+            iommus = <0x1 0x3>;
+            android,pvmfw,target = <0x2>;
+        };
+        light {
+            reg = <0x0 0x00f00000 0x1000>, <0x0 0x00f10000 0x1000>;
+            iommus = <0x3 0x4>, <0x4 0x5>;
+            android,pvmfw,target = <0x5>;
+        };
+        led {
+            reg = <0x0 0x12000000 0x1000>;
+            iommus = <0x1 0x3>;
+            android,pvmfw,target = <0x6>;
+        };
+        bus0 {
+            #address-cells = <0x1>;
+            #size-cells = <0x1>;
+            backlight {
+                reg = <0x300 0x100>;
+                android,pvmfw,target = <0x7>;
+            };
+        };
+        iommu0 {
+            #iommu-cells = <0x1>;
+            android,pvmfw,token = <0x0 0x12e40000>;
+            phandle = <0x1>;
+        };
+        iommu1 {
+            #iommu-cells = <0x1>;
+            android,pvmfw,token = <0x0 0x40000>;
+            phandle = <0x3>;
+        };
+        iommu2 {
+            #iommu-cells = <0x1>;
+            android,pvmfw,token = <0x0 0x50000>;
+            phandle = <0x4>;
+        };
+    };
+    fragment@rng {
+        target-path = "/";
+        __overlay__ {
+            rng {
+                compatible = "android,rng";
+                android,rng,ignore-gctrl-reset;
+                phandle = <0x2>;
+            };
+        };
+    };
+    fragment@sensor {
+        target-path = "/";
+        __overlay__ {
+            light {
+                compatible = "android,light";
+                version = <0x1 0x2>;
+                phandle = <0x5>;
+            };
+        };
+    };
+    fragment@led {
+        target-path = "/";
+        __overlay__ {
+            led {
+                compatible = "android,led";
+                prop = <0x555>;
+                phandle = <0x6>;
+            };
+        };
+    };
+    fragment@backlight {
+        target-path = "/";
+        __overlay__ {
+            bus0 {
+                backlight {
+                    compatible = "android,backlight";
+                    android,backlight,ignore-gctrl-reset;
+                    phandle = <0x7>;
+                };
+            };
+        };
+    };
+    __symbols__ {
+        iommu0 = "/host/iommu0";
+        iommu1 = "/host/iommu1";
+        iommu2 = "/host/iommu2";
+    };
+    __local_fixups__ {
+        host {
+            rng {
+                iommus = <0x0>;
+                android,pvmfw,target = <0x0>;
+            };
+            light {
+                iommus = <0x0 0x8>;
+                android,pvmfw,target = <0x0>;
+            };
+            led {
+                iommus = <0x0>;
+                android,pvmfw,target = <0x0>;
+            };
+            bus0 {
+                backlight {
+                    android,pvmfw,target = <0x0>;
+                };
+            };
+        };
+    };
 };
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_iommu_id_conflict.dts b/pvmfw/testdata/test_pvmfw_devices_with_iommu_id_conflict.dts
index 70b633c..a9e30be 100644
--- a/pvmfw/testdata/test_pvmfw_devices_with_iommu_id_conflict.dts
+++ b/pvmfw/testdata/test_pvmfw_devices_with_iommu_id_conflict.dts
@@ -27,7 +27,7 @@
 
     light@70000000 {
         compatible = "android,light";
-        reg = <0x100 0x9>;
+        reg = <0x0 0x100 0x0 0x100>, <0x0 0x200 0x0 0x100>;
         interrupts = <0x0 0xF 0x5>;
         iommus = <&pviommu_a 0xA>, <&pviommu_b 0xB>;
     };
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_iommu_sharing.dts b/pvmfw/testdata/test_pvmfw_devices_with_iommu_sharing.dts
index 7c6d2f2..78ff868 100644
--- a/pvmfw/testdata/test_pvmfw_devices_with_iommu_sharing.dts
+++ b/pvmfw/testdata/test_pvmfw_devices_with_iommu_sharing.dts
@@ -14,8 +14,8 @@
     };
 
     led@70000000 {
-        compatible = "android,light";
-        reg = <0x100 0x9>;
+        compatible = "android,led";
+        reg = <0x0 0x100 0x0 0x9>;
         interrupts = <0x0 0xF 0x5>;
         iommus = <&pviommu_0 0xFF0>;
     };
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_multiple_devices_iommus.dts b/pvmfw/testdata/test_pvmfw_devices_with_multiple_devices_iommus.dts
index 76c99c9..ca7e7f3 100644
--- a/pvmfw/testdata/test_pvmfw_devices_with_multiple_devices_iommus.dts
+++ b/pvmfw/testdata/test_pvmfw_devices_with_multiple_devices_iommus.dts
@@ -20,7 +20,7 @@
 
     light@70000000 {
         compatible = "android,light";
-        reg = <0x100 0x9>;
+        reg = <0x0 0x100 0x0 0x1000>, <0x0 0x200 0x0 0x1000>;
         interrupts = <0x0 0xF 0x5>;
         iommus = <&pviommu_a 0xFFA>, <&pviommu_b 0xFFB>;
     };
diff --git a/pvmfw/testdata/test_pvmfw_devices_without_device.dts b/pvmfw/testdata/test_pvmfw_devices_without_device.dts
new file mode 100644
index 0000000..ee0be3a
--- /dev/null
+++ b/pvmfw/testdata/test_pvmfw_devices_without_device.dts
@@ -0,0 +1,7 @@
+/dts-v1/;
+/plugin/;
+
+/include/ "test_crosvm_dt_base.dtsi"
+
+/ {
+};
diff --git a/pvmfw/testdata/test_pvmfw_devices_without_iommus.dts b/pvmfw/testdata/test_pvmfw_devices_without_iommus.dts
index 2036c9c..1a12c87 100644
--- a/pvmfw/testdata/test_pvmfw_devices_without_iommus.dts
+++ b/pvmfw/testdata/test_pvmfw_devices_without_iommus.dts
@@ -4,11 +4,16 @@
 /include/ "test_crosvm_dt_base.dtsi"
 
 / {
-    backlight@90000000 {
-        compatible = "android,backlight";
-        reg = <0x0 0x9 0x0 0xFF>;
-        interrupts = <0x0 0xF 0x4>;
-        google,eh,ignore-gctrl-reset;
-        status = "okay";
+    bus0 {
+        #address-cells = <0x2>;
+        #size-cells = <0x2>;
+
+        backlight@90000000 {
+            compatible = "android,backlight";
+            reg = <0x0 0x9 0x0 0xFF>;
+            interrupts = <0x0 0xF 0x4>;
+            google,eh,ignore-gctrl-reset;
+            status = "okay";
+        };
     };
 };
diff --git a/rialto/Android.bp b/rialto/Android.bp
index bbb5e54..5e7fe1f 100644
--- a/rialto/Android.bp
+++ b/rialto/Android.bp
@@ -63,6 +63,28 @@
     srcs: [":avb_testkey_rsa4096"],
 }
 
+// Both SERVICE_VM_VERSION and SERVICE_VM_VERSION_STRING should represent the
+// same version number for the service VM.
+SERVICE_VM_VERSION = 1
+SERVICE_VM_VERSION_STRING = "1"
+
+genrule {
+    name: "service_vm_version_rs",
+    out: ["lib.rs"],
+    cmd: "(" +
+        "    echo '#![no_std]';" +
+        "    echo '#![allow(missing_docs)]';" +
+        "    echo 'pub const VERSION: u64 = " + SERVICE_VM_VERSION_STRING + ";'" +
+        ") > $(out)",
+}
+
+rust_library_rlib {
+    name: "libservice_vm_version",
+    crate_name: "service_vm_version",
+    defaults: ["vmbase_rlib_defaults"],
+    srcs: [":service_vm_version_rs"],
+}
+
 avb_add_hash_footer {
     name: "rialto_signed",
     src: ":empty_file",
@@ -70,6 +92,7 @@
     partition_name: "boot",
     private_key: ":rialto_sign_key",
     salt: rialto_salt,
+    rollback_index: SERVICE_VM_VERSION,
     props: [
         {
             name: "com.android.virt.cap",
diff --git a/service_vm/fake_chain/Android.bp b/service_vm/fake_chain/Android.bp
index ebc185d..2bc7b4e 100644
--- a/service_vm/fake_chain/Android.bp
+++ b/service_vm/fake_chain/Android.bp
@@ -24,6 +24,7 @@
     visibility: [
         "//packages/modules/Virtualization/rialto:__subpackages__",
     ],
+    prefer_rlib: true,
     rustlibs: [
         "libcstr",
     ],
@@ -40,6 +41,7 @@
         "libcoset",
         "libdiced_open_dice",
         "liblog_rust",
+        "libmicrodroid_kernel_hashes",
     ],
 }
 
diff --git a/service_vm/fake_chain/src/client_vm.rs b/service_vm/fake_chain/src/client_vm.rs
index eb8654b..44ea898 100644
--- a/service_vm/fake_chain/src/client_vm.rs
+++ b/service_vm/fake_chain/src/client_vm.rs
@@ -24,21 +24,16 @@
 use coset::CborSerializable;
 use cstr::cstr;
 use diced_open_dice::{
-    retry_bcc_format_config_descriptor, retry_bcc_main_flow, Config, DiceArtifacts,
+    hash, retry_bcc_format_config_descriptor, retry_bcc_main_flow, Config, DiceArtifacts,
     DiceConfigValues, DiceError, DiceMode, InputValues, OwnedDiceArtifacts, Result, HASH_SIZE,
     HIDDEN_SIZE,
 };
 use log::error;
+use microdroid_kernel_hashes::{INITRD_DEBUG_HASH, KERNEL_HASH};
 
 type CborResult<T> = result::Result<T, ciborium::value::Error>;
 
 /// All the following data are generated with urandom.
-const CODE_HASH_KERNEL: [u8; HASH_SIZE] = [
-    0xc8, 0x54, 0x6c, 0xad, 0x9d, 0xe7, 0x25, 0xc7, 0x2b, 0xed, 0x07, 0xe1, 0xe9, 0x1a, 0xb0, 0xd0,
-    0xa7, 0x7f, 0x43, 0xb9, 0xe4, 0x56, 0x79, 0x0d, 0x7d, 0xd8, 0xc5, 0xdd, 0xad, 0x0d, 0x31, 0x85,
-    0xaf, 0x94, 0x02, 0xd8, 0x9d, 0x70, 0xab, 0xba, 0xac, 0xc7, 0x12, 0x80, 0xec, 0x7b, 0x9b, 0x65,
-    0xec, 0x6b, 0xdd, 0x64, 0x94, 0xd0, 0x9a, 0x3a, 0x09, 0xf2, 0x49, 0xdb, 0x60, 0x3c, 0x50, 0x30,
-];
 const CODE_HASH_PAYLOAD: [u8; HASH_SIZE] = [
     0x08, 0x78, 0xc2, 0x5b, 0xe7, 0xea, 0x3d, 0x62, 0x70, 0x22, 0xd9, 0x1c, 0x4f, 0x3c, 0x2e, 0x2f,
     0x0f, 0x97, 0xa4, 0x6f, 0x6d, 0xd5, 0xe6, 0x4a, 0x6d, 0xbe, 0x34, 0x2e, 0x56, 0x04, 0xaf, 0xef,
@@ -111,7 +106,7 @@
     // so the authority hash is the same.
     let authority_hash = service_vm::AUTHORITY_HASH_SERVICE_VM;
     let input_values = InputValues::new(
-        CODE_HASH_KERNEL,
+        kernel_code_hash()?,
         Config::Descriptor(config_descriptor.as_slice()),
         authority_hash,
         DiceMode::kDiceModeDebug,
@@ -179,3 +174,8 @@
         },
     ]
 }
+
+fn kernel_code_hash() -> Result<[u8; HASH_SIZE]> {
+    let code_hash = [KERNEL_HASH, INITRD_DEBUG_HASH].concat();
+    hash(&code_hash)
+}
diff --git a/service_vm/kernel/Android.bp b/service_vm/kernel/Android.bp
new file mode 100644
index 0000000..79158e6
--- /dev/null
+++ b/service_vm/kernel/Android.bp
@@ -0,0 +1,31 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+python_binary_host {
+    name: "extract_microdroid_kernel_hashes",
+    srcs: ["extract_microdroid_kernel_hashes.py"],
+}
+
+genrule {
+    name: "microdroid_kernel_hashes_rs",
+    srcs: [":microdroid_kernel"],
+    out: ["lib.rs"],
+    tools: [
+        "extract_microdroid_kernel_hashes",
+        "avbtool",
+    ],
+    cmd: "$(location extract_microdroid_kernel_hashes) $(location avbtool) $(in) > $(out)",
+}
+
+rust_library_rlib {
+    name: "libmicrodroid_kernel_hashes",
+    srcs: [":microdroid_kernel_hashes_rs"],
+    crate_name: "microdroid_kernel_hashes",
+    prefer_rlib: true,
+    no_stdlibs: true,
+    stdlibs: [
+        "libcompiler_builtins.rust_sysroot",
+        "libcore.rust_sysroot",
+    ],
+}
diff --git a/service_vm/kernel/extract_microdroid_kernel_hashes.py b/service_vm/kernel/extract_microdroid_kernel_hashes.py
new file mode 100644
index 0000000..148e8be
--- /dev/null
+++ b/service_vm/kernel/extract_microdroid_kernel_hashes.py
@@ -0,0 +1,73 @@
+"""Extracts the following hashes from the AVB footer of Microdroid's kernel:
+
+- kernel hash
+- initrd_normal hash
+- initrd_debug hash
+
+The hashes are written to stdout as a Rust file.
+
+In unsupportive environments such as x86, when the kernel is just an empty file,
+the output Rust file has the same hash constant fields for compatibility
+reasons, but all of them are empty.
+"""
+#!/usr/bin/env python3
+
+import sys
+import subprocess
+from typing import Dict
+
+PARTITION_NAME_BOOT = 'boot'
+PARTITION_NAME_INITRD_NORMAL = 'initrd_normal'
+PARTITION_NAME_INITRD_DEBUG = 'initrd_debug'
+
+def main(args):
+    """Main function."""
+    avbtool = args[0]
+    kernel_image_path = args[1]
+    hashes = collect_hashes(avbtool, kernel_image_path)
+
+    print("//! This file is generated by extract_microdroid_kernel_hashes.py.")
+    print("//! It contains the hashes of the kernel and initrds.\n")
+    print("#![no_std]\n#![allow(missing_docs)]\n")
+
+    # Microdroid's kernel is just an empty file in unsupportive environments
+    # such as x86, in this case the hashes should be empty.
+    if hashes.keys() != {PARTITION_NAME_BOOT,
+                         PARTITION_NAME_INITRD_NORMAL,
+                         PARTITION_NAME_INITRD_DEBUG}:
+        print("/// The kernel is empty, no hashes are available.")
+        hashes[PARTITION_NAME_BOOT] = ""
+        hashes[PARTITION_NAME_INITRD_NORMAL] = ""
+        hashes[PARTITION_NAME_INITRD_DEBUG] = ""
+
+    print("pub const KERNEL_HASH: &[u8] = &["
+          f"{format_hex_string(hashes[PARTITION_NAME_BOOT])}];\n")
+    print("pub const INITRD_NORMAL_HASH: &[u8] = &["
+          f"{format_hex_string(hashes[PARTITION_NAME_INITRD_NORMAL])}];\n")
+    print("pub const INITRD_DEBUG_HASH: &[u8] = &["
+          f"{format_hex_string(hashes[PARTITION_NAME_INITRD_DEBUG])}];")
+
+def collect_hashes(avbtool: str, kernel_image_path: str) -> Dict[str, str]:
+    """Collects the hashes from the AVB footer of the kernel image."""
+    hashes = {}
+    with subprocess.Popen(
+        [avbtool, 'print_partition_digests', '--image', kernel_image_path],
+        stdout=subprocess.PIPE, stderr=subprocess.STDOUT) as proc:
+        stdout, _ = proc.communicate()
+        for line in stdout.decode("utf-8").split("\n"):
+            line = line.replace(" ", "").split(":")
+            if len(line) == 2:
+                partition_name, hash_ = line
+                hashes[partition_name] = hash_
+    return hashes
+
+def format_hex_string(hex_string: str) -> str:
+    """Formats a hex string into a Rust array."""
+    assert len(hex_string) % 2 == 0, \
+          "Hex string must have even length: " + hex_string
+    return ", ".join(["\n0x" + hex_string[i:i+2] if i % 32 == 0
+                       else "0x" + hex_string[i:i+2]
+                       for i in range(0, len(hex_string), 2)])
+
+if __name__ == '__main__':
+    main(sys.argv[1:])
diff --git a/service_vm/requests/Android.bp b/service_vm/requests/Android.bp
index 52b54b4..57da012 100644
--- a/service_vm/requests/Android.bp
+++ b/service_vm/requests/Android.bp
@@ -24,6 +24,7 @@
         "libder_nostd",
         "libdiced_open_dice_nostd",
         "liblog_rust_nostd",
+        "libmicrodroid_kernel_hashes",
         "libserde_nostd",
         "libservice_vm_comm_nostd",
         "libspki_nostd",
diff --git a/service_vm/requests/src/cert.rs b/service_vm/requests/src/cert.rs
index 73828a7..91281e7 100644
--- a/service_vm/requests/src/cert.rs
+++ b/service_vm/requests/src/cert.rs
@@ -18,7 +18,7 @@
 use alloc::vec;
 use alloc::vec::Vec;
 use der::{
-    asn1::{BitStringRef, ObjectIdentifier, UIntRef, Utf8StringRef},
+    asn1::{BitString, ObjectIdentifier, OctetString, Utf8StringRef},
     oid::AssociatedOid,
     Decode, Sequence,
 };
@@ -27,6 +27,7 @@
     certificate::{Certificate, TbsCertificate, Version},
     ext::Extension,
     name::Name,
+    serial_number::SerialNumber,
     time::Validity,
 };
 
@@ -111,14 +112,14 @@
 ///   signature            BIT STRING
 /// }
 /// ```
-pub(crate) fn build_certificate<'a>(
-    tbs_cert: TbsCertificate<'a>,
-    signature: &'a [u8],
-) -> der::Result<Certificate<'a>> {
+pub(crate) fn build_certificate(
+    tbs_cert: TbsCertificate,
+    signature: &[u8],
+) -> der::Result<Certificate> {
     Ok(Certificate {
-        signature_algorithm: tbs_cert.signature,
+        signature_algorithm: tbs_cert.signature.clone(),
         tbs_certificate: tbs_cert,
-        signature: BitStringRef::new(0, signature)?,
+        signature: BitString::new(0, signature)?,
     })
 }
 
@@ -141,24 +142,24 @@
 ///                        -- If present, version MUST be v3 --
 /// }
 /// ```
-pub(crate) fn build_tbs_certificate<'a>(
-    serial_number: &'a [u8],
-    issuer: Name<'a>,
-    subject: Name<'a>,
+pub(crate) fn build_tbs_certificate(
+    serial_number: &[u8],
+    issuer: Name,
+    subject: Name,
     validity: Validity,
-    subject_public_key_info: &'a [u8],
-    attestation_ext: &'a [u8],
-) -> der::Result<TbsCertificate<'a>> {
+    subject_public_key_info: &[u8],
+    attestation_ext: &[u8],
+) -> der::Result<TbsCertificate> {
     let signature = AlgorithmIdentifier { oid: ECDSA_WITH_SHA_256, parameters: None };
     let subject_public_key_info = SubjectPublicKeyInfo::from_der(subject_public_key_info)?;
     let extensions = vec![Extension {
         extn_id: AttestationExtension::OID,
         critical: false,
-        extn_value: attestation_ext,
+        extn_value: OctetString::new(attestation_ext)?,
     }];
     Ok(TbsCertificate {
         version: Version::V3,
-        serial_number: UIntRef::new(serial_number)?,
+        serial_number: SerialNumber::new(serial_number)?,
         signature,
         issuer,
         validity,
diff --git a/service_vm/requests/src/client_vm.rs b/service_vm/requests/src/client_vm.rs
index cfdac2d..5b1bf6c 100644
--- a/service_vm/requests/src/client_vm.rs
+++ b/service_vm/requests/src/client_vm.rs
@@ -16,20 +16,26 @@
 //! client VM.
 
 use crate::cert;
-use crate::dice::{validate_client_vm_dice_chain_prefix_match, ClientVmDiceChain};
+use crate::dice::{
+    validate_client_vm_dice_chain_prefix_match, ClientVmDiceChain, DiceChainEntryPayload,
+};
 use crate::keyblob::decrypt_private_key;
 use alloc::vec::Vec;
-use bssl_avf::{rand_bytes, sha256, EcKey, PKey};
+use bssl_avf::{rand_bytes, sha256, Digester, EcKey, PKey};
+use cbor_util::value_to_array;
+use ciborium::value::Value;
 use core::result;
-use coset::{CborSerializable, CoseSign};
+use coset::{AsCborValue, CborSerializable, CoseSign, CoseSign1};
 use der::{Decode, Encode};
-use diced_open_dice::DiceArtifacts;
+use diced_open_dice::{DiceArtifacts, HASH_SIZE};
 use log::error;
+use microdroid_kernel_hashes::{INITRD_DEBUG_HASH, INITRD_NORMAL_HASH, KERNEL_HASH};
 use service_vm_comm::{ClientVmAttestationParams, Csr, CsrPayload, RequestProcessingError};
 use x509_cert::{certificate::Certificate, name::Name};
 
 type Result<T> = result::Result<T, RequestProcessingError>;
 
+const DICE_CDI_LEAF_SIGNATURE_INDEX: usize = 0;
 const ATTESTATION_KEY_SIGNATURE_INDEX: usize = 1;
 
 pub(super) fn request_attestation(
@@ -47,19 +53,29 @@
     // Validates the prefix of the Client VM DICE chain in the CSR.
     let service_vm_dice_chain =
         dice_artifacts.bcc().ok_or(RequestProcessingError::MissingDiceChain)?;
+    let service_vm_dice_chain =
+        value_to_array(Value::from_slice(service_vm_dice_chain)?, "service_vm_dice_chain")?;
     let client_vm_dice_chain =
-        validate_client_vm_dice_chain_prefix_match(&csr.dice_cert_chain, service_vm_dice_chain)?;
+        value_to_array(Value::from_slice(&csr.dice_cert_chain)?, "client_vm_dice_chain")?;
+    validate_client_vm_dice_chain_prefix_match(&client_vm_dice_chain, &service_vm_dice_chain)?;
     // Validates the signatures in the Client VM DICE chain and extracts the partially decoded
     // DiceChainEntryPayloads.
     let client_vm_dice_chain =
         ClientVmDiceChain::validate_signatures_and_parse_dice_chain(client_vm_dice_chain)?;
 
+    // The last entry in the service VM DICE chain describes the service VM, which should
+    // be signed with the same key as the kernel image.
+    let service_vm_entry = service_vm_dice_chain.last().unwrap();
+    validate_kernel_authority_hash(client_vm_dice_chain.microdroid_kernel(), service_vm_entry)?;
+    validate_kernel_code_hash(&client_vm_dice_chain)?;
+
     // AAD is empty as defined in service_vm/comm/client_vm_csr.cddl.
     let aad = &[];
 
     // Verifies the first signature with the leaf private key in the DICE chain.
-    // TODO(b/310931749): Verify the first signature with CDI_Leaf_Pub of
-    // the DICE chain in `cose_sign`.
+    cose_sign.verify_signature(DICE_CDI_LEAF_SIGNATURE_INDEX, aad, |signature, message| {
+        client_vm_dice_chain.microdroid_payload().subject_public_key.verify(signature, message)
+    })?;
 
     // Verifies the second signature with the public key in the CSR payload.
     let ec_public_key = EcKey::from_cose_public_key_slice(&csr_payload.public_key)?;
@@ -87,7 +103,7 @@
         client_vm_dice_chain.all_entries_are_secure(),
         vm_components,
     )
-    .to_vec()?;
+    .to_der()?;
     let tbs_cert = cert::build_tbs_certificate(
         &serial_number,
         rkp_cert.tbs_certificate.subject,
@@ -106,9 +122,9 @@
                 RequestProcessingError::FailedToDecryptKeyBlob
             })?;
     let ec_private_key = EcKey::from_ec_private_key(private_key.as_slice())?;
-    let signature = ecdsa_sign(&ec_private_key, &tbs_cert.to_vec()?)?;
+    let signature = ecdsa_sign(&ec_private_key, &tbs_cert.to_der()?)?;
     let certificate = cert::build_certificate(tbs_cert, &signature)?;
-    Ok(certificate.to_vec()?)
+    Ok(certificate.to_der()?)
 }
 
 fn ecdsa_verify(key: &EcKey, signature: &[u8], message: &[u8]) -> bssl_avf::Result<()> {
@@ -121,3 +137,60 @@
     let digest = sha256(message)?;
     key.ecdsa_sign(&digest)
 }
+
+/// Validates that the authority hash of the Microdroid kernel in the Client VM DICE chain
+/// matches the authority hash of the service VM entry in the service VM DICE chain, because
+/// the Microdroid kernel is signed with the same key as the one used for the service VM.
+fn validate_kernel_authority_hash(
+    kernel: &DiceChainEntryPayload,
+    service_vm_entry: &Value,
+) -> Result<()> {
+    if expected_kernel_authority_hash(service_vm_entry)? == kernel.authority_hash {
+        Ok(())
+    } else {
+        error!("The authority hash of the Microdroid kernel does not match the expected value");
+        Err(RequestProcessingError::InvalidDiceChain)
+    }
+}
+
+/// Validates that the kernel code hash in the Client VM DICE chain matches the code hashes
+/// embedded during the build time.
+fn validate_kernel_code_hash(dice_chain: &ClientVmDiceChain) -> Result<()> {
+    let kernel = dice_chain.microdroid_kernel();
+    if expected_kernel_code_hash_normal()? == kernel.code_hash {
+        return Ok(());
+    }
+    if expected_kernel_code_hash_debug()? == kernel.code_hash {
+        if dice_chain.all_entries_are_secure() {
+            error!("The Microdroid kernel has debug initrd but the DICE chain is secure");
+            return Err(RequestProcessingError::InvalidDiceChain);
+        }
+        return Ok(());
+    }
+    error!("The kernel code hash in the Client VM DICE chain does not match any expected values");
+    Err(RequestProcessingError::InvalidDiceChain)
+}
+
+fn expected_kernel_code_hash_normal() -> bssl_avf::Result<Vec<u8>> {
+    let mut code_hash = [0u8; 64];
+    code_hash[0..32].copy_from_slice(KERNEL_HASH);
+    code_hash[32..].copy_from_slice(INITRD_NORMAL_HASH);
+    Digester::sha512().digest(&code_hash)
+}
+
+fn expected_kernel_code_hash_debug() -> bssl_avf::Result<Vec<u8>> {
+    let mut code_hash = [0u8; 64];
+    code_hash[0..32].copy_from_slice(KERNEL_HASH);
+    code_hash[32..].copy_from_slice(INITRD_DEBUG_HASH);
+    Digester::sha512().digest(&code_hash)
+}
+
+fn expected_kernel_authority_hash(service_vm_entry: &Value) -> Result<[u8; HASH_SIZE]> {
+    let cose_sign1 = CoseSign1::from_cbor_value(service_vm_entry.clone())?;
+    let payload = cose_sign1.payload.ok_or_else(|| {
+        error!("No payload found in the service VM DICE chain entry");
+        RequestProcessingError::InternalError
+    })?;
+    let service_vm = DiceChainEntryPayload::from_slice(&payload)?;
+    Ok(service_vm.authority_hash)
+}
diff --git a/service_vm/requests/src/dice.rs b/service_vm/requests/src/dice.rs
index 557b678..657e482 100644
--- a/service_vm/requests/src/dice.rs
+++ b/service_vm/requests/src/dice.rs
@@ -16,15 +16,19 @@
 
 use alloc::string::String;
 use alloc::vec::Vec;
+use bssl_avf::{ed25519_verify, Digester, EcKey};
 use cbor_util::{
-    cbor_value_type, value_to_array, value_to_byte_array, value_to_bytes, value_to_map,
-    value_to_num, value_to_text,
+    cbor_value_type, get_label_value, get_label_value_as_bytes, value_to_array,
+    value_to_byte_array, value_to_bytes, value_to_map, value_to_num, value_to_text,
 };
 use ciborium::value::Value;
 use core::cell::OnceCell;
 use core::result;
 use coset::{
-    self, iana, AsCborValue, CborSerializable, CoseError, CoseKey, CoseSign1, KeyOperation,
+    self,
+    iana::{self, EnumI64},
+    Algorithm, AsCborValue, CborSerializable, CoseError, CoseKey, CoseSign1, KeyOperation, KeyType,
+    Label,
 };
 use diced_open_dice::{DiceMode, HASH_SIZE};
 use log::error;
@@ -58,7 +62,7 @@
 /// ]
 #[derive(Debug, Clone)]
 pub(crate) struct ClientVmDiceChain {
-    pub(crate) payloads: Vec<DiceChainEntryPayload>,
+    payloads: Vec<DiceChainEntryPayload>,
 }
 
 impl ClientVmDiceChain {
@@ -126,11 +130,11 @@
         Ok(())
     }
 
-    fn microdroid_kernel(&self) -> &DiceChainEntryPayload {
+    pub(crate) fn microdroid_kernel(&self) -> &DiceChainEntryPayload {
         &self.payloads[self.payloads.len() - 2]
     }
 
-    fn microdroid_payload(&self) -> &DiceChainEntryPayload {
+    pub(crate) fn microdroid_payload(&self) -> &DiceChainEntryPayload {
         &self.payloads[self.payloads.len() - 1]
     }
 
@@ -147,15 +151,11 @@
 /// Validates that the `client_vm_dice_chain` matches the `service_vm_dice_chain` up to the pvmfw
 /// entry.
 ///
-/// Returns a CBOR value array of the client VM's DICE chain if the verification succeeds.
+/// Returns `Ok(())` if the verification succeeds.
 pub(crate) fn validate_client_vm_dice_chain_prefix_match(
-    client_vm_dice_chain: &[u8],
-    service_vm_dice_chain: &[u8],
-) -> Result<Vec<Value>> {
-    let client_vm_dice_chain =
-        value_to_array(Value::from_slice(client_vm_dice_chain)?, "client_vm_dice_chain")?;
-    let service_vm_dice_chain =
-        value_to_array(Value::from_slice(service_vm_dice_chain)?, "service_vm_dice_chain")?;
+    client_vm_dice_chain: &[Value],
+    service_vm_dice_chain: &[Value],
+) -> Result<()> {
     if service_vm_dice_chain.len() < 3 {
         // The service VM's DICE chain must contain the root key and at least two other entries
         // that describe:
@@ -180,7 +180,7 @@
         );
         return Err(RequestProcessingError::InvalidDiceChain);
     }
-    Ok(client_vm_dice_chain)
+    Ok(())
 }
 
 #[derive(Debug, Clone)]
@@ -198,21 +198,72 @@
     }
 }
 
+impl PublicKey {
+    /// Verifies the signature of the provided message with the public key.
+    ///
+    /// This function supports the following key/algorithm types as specified in
+    /// hardware/interfaces/security/rkp/aidl/android/hardware/security/keymint/
+    /// generateCertificateRequestV2.cddl:
+    ///
+    /// PubKeyEd25519 / PubKeyECDSA256 / PubKeyECDSA384
+    pub(crate) fn verify(&self, signature: &[u8], message: &[u8]) -> Result<()> {
+        match &self.0.kty {
+            KeyType::Assigned(iana::KeyType::EC2) => {
+                let public_key = EcKey::from_cose_public_key(&self.0)?;
+                let Some(Algorithm::Assigned(alg)) = self.0.alg else {
+                    error!("Invalid algorithm in COSE key {:?}", self.0.alg);
+                    return Err(RequestProcessingError::InvalidDiceChain);
+                };
+                let digester = match alg {
+                    iana::Algorithm::ES256 => Digester::sha256(),
+                    iana::Algorithm::ES384 => Digester::sha384(),
+                    _ => {
+                        error!("Unsupported algorithm in EC2 key: {:?}", alg);
+                        return Err(RequestProcessingError::InvalidDiceChain);
+                    }
+                };
+                let digest = digester.digest(message)?;
+                Ok(public_key.ecdsa_verify(signature, &digest)?)
+            }
+            KeyType::Assigned(iana::KeyType::OKP) => {
+                let curve_type =
+                    get_label_value(&self.0, Label::Int(iana::OkpKeyParameter::Crv.to_i64()))?;
+                if curve_type != &Value::from(iana::EllipticCurve::Ed25519.to_i64()) {
+                    error!("Unsupported curve type in OKP COSE key: {:?}", curve_type);
+                    return Err(RequestProcessingError::OperationUnimplemented);
+                }
+                let x = get_label_value_as_bytes(
+                    &self.0,
+                    Label::Int(iana::OkpKeyParameter::X.to_i64()),
+                )?;
+                let public_key = x.try_into().map_err(|_| {
+                    error!("Invalid ED25519 public key size: {}", x.len());
+                    RequestProcessingError::InvalidDiceChain
+                })?;
+                let signature = signature.try_into().map_err(|_| {
+                    error!("Invalid ED25519 signature size: {}", signature.len());
+                    RequestProcessingError::InvalidDiceChain
+                })?;
+                Ok(ed25519_verify(message, signature, public_key)?)
+            }
+            kty => {
+                error!("Unsupported key type in COSE key: {:?}", kty);
+                Err(RequestProcessingError::OperationUnimplemented)
+            }
+        }
+    }
+}
+
 /// Represents a partially decoded `DiceChainEntryPayload`. The whole payload is defined in:
 ///
 /// hardware/interfaces/security/rkp/aidl/android/hardware/security/keymint/
 /// generateCertificateRequestV2.cddl
 #[derive(Debug, Clone)]
 pub(crate) struct DiceChainEntryPayload {
-    /// TODO(b/310931749): Verify the DICE chain entry using the subject public key.
-    #[allow(dead_code)]
-    subject_public_key: PublicKey,
+    pub(crate) subject_public_key: PublicKey,
     mode: DiceMode,
-    /// TODO(b/271275206): Verify Microdroid kernel authority and code hashes.
-    #[allow(dead_code)]
-    code_hash: [u8; HASH_SIZE],
-    #[allow(dead_code)]
-    authority_hash: [u8; HASH_SIZE],
+    pub(crate) code_hash: [u8; HASH_SIZE],
+    pub(crate) authority_hash: [u8; HASH_SIZE],
     config_descriptor: ConfigDescriptor,
 }
 
@@ -221,51 +272,54 @@
     /// extracts payload from the value.
     fn validate_cose_signature_and_extract_payload(
         value: Value,
-        _authority_public_key: &PublicKey,
+        authority_public_key: &PublicKey,
     ) -> Result<Self> {
         let cose_sign1 = CoseSign1::from_cbor_value(value)?;
-        // TODO(b/310931749): Verify the DICE chain entry using `authority_public_key`.
+        let aad = &[]; // AAD is not used in DICE chain entry.
+        cose_sign1.verify_signature(aad, |signature, message| {
+            authority_public_key.verify(signature, message)
+        })?;
 
         let payload = cose_sign1.payload.ok_or_else(|| {
             error!("No payload found in the DICE chain entry");
             RequestProcessingError::InvalidDiceChain
         })?;
-        let entries = value_to_map(Value::from_slice(&payload)?, "DiceChainEntryPayload")?;
-        build_payload(entries)
+        Self::from_slice(&payload)
     }
-}
 
-fn build_payload(entries: Vec<(Value, Value)>) -> Result<DiceChainEntryPayload> {
-    let mut builder = PayloadBuilder::default();
-    for (key, value) in entries.into_iter() {
-        let key: i64 = value_to_num(key, "DiceChainEntryPayload key")?;
-        match key {
-            SUBJECT_PUBLIC_KEY => {
-                let subject_public_key = value_to_bytes(value, "subject_public_key")?;
-                let subject_public_key = CoseKey::from_slice(&subject_public_key)?.try_into()?;
-                builder.subject_public_key(subject_public_key)?;
+    pub(crate) fn from_slice(data: &[u8]) -> Result<Self> {
+        let entries = value_to_map(Value::from_slice(data)?, "DiceChainEntryPayload")?;
+        let mut builder = PayloadBuilder::default();
+        for (key, value) in entries.into_iter() {
+            let key: i64 = value_to_num(key, "DiceChainEntryPayload key")?;
+            match key {
+                SUBJECT_PUBLIC_KEY => {
+                    let subject_public_key = value_to_bytes(value, "subject_public_key")?;
+                    let subject_public_key =
+                        CoseKey::from_slice(&subject_public_key)?.try_into()?;
+                    builder.subject_public_key(subject_public_key)?;
+                }
+                MODE => builder.mode(to_mode(value)?)?,
+                CODE_HASH => {
+                    let code_hash = value_to_byte_array(value, "DiceChainEntryPayload code_hash")?;
+                    builder.code_hash(code_hash)?;
+                }
+                AUTHORITY_HASH => {
+                    let authority_hash =
+                        value_to_byte_array(value, "DiceChainEntryPayload authority_hash")?;
+                    builder.authority_hash(authority_hash)?;
+                }
+                CONFIG_DESC => {
+                    let config_descriptor = value_to_bytes(value, "config_descriptor")?;
+                    let config_descriptor = ConfigDescriptor::from_slice(&config_descriptor)?;
+                    builder.config_descriptor(config_descriptor)?;
+                }
+                _ => {}
             }
-            MODE => builder.mode(to_mode(value)?)?,
-            CODE_HASH => {
-                let code_hash = value_to_byte_array(value, "DiceChainEntryPayload code_hash")?;
-                builder.code_hash(code_hash)?;
-            }
-            AUTHORITY_HASH => {
-                let authority_hash =
-                    value_to_byte_array(value, "DiceChainEntryPayload authority_hash")?;
-                builder.authority_hash(authority_hash)?;
-            }
-            CONFIG_DESC => {
-                let config_descriptor = value_to_bytes(value, "config_descriptor")?;
-                let config_descriptor = ConfigDescriptor::from_slice(&config_descriptor)?;
-                builder.config_descriptor(config_descriptor)?;
-            }
-            _ => {}
         }
+        builder.build()
     }
-    builder.build()
 }
-
 /// Represents a partially decoded `ConfigurationDescriptor`.
 ///
 /// The whole `ConfigurationDescriptor` is defined in:
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index 60f3e52..6f1286f 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -164,6 +164,12 @@
         if (!updateBootconfigs) {
             command.add("--do_not_update_bootconfigs");
         }
+        // In some cases we run a CTS binary that is built from a different branch that the /system
+        // image under test. In such cases we might end up in a situation when avb_version used in
+        // CTS binary and avb_version used to sign the com.android.virt APEX do not match.
+        // This is a weird configuration, but unfortunately it can happen, hence we pass here
+        // --do_not_validate_avb_version flag to make sure that CTS doesn't fail on it.
+        command.add("--do_not_validate_avb_version");
         keyOverrides.forEach(
                 (filename, keyFile) ->
                         command.add("--key_override " + filename + "=" + keyFile.getPath()));
diff --git a/virtualizationmanager/Android.bp b/virtualizationmanager/Android.bp
index 33897b2..60c94fc 100644
--- a/virtualizationmanager/Android.bp
+++ b/virtualizationmanager/Android.bp
@@ -5,7 +5,11 @@
 rust_defaults {
     name: "virtualizationmanager_defaults",
     crate_name: "virtualizationmanager",
-    defaults: ["avf_build_flags_rust"],
+    defaults: [
+        "avf_build_flags_rust",
+        "secretkeeper_use_latest_hal_aidl_rust",
+        "authgraph_use_latest_hal_aidl_rust",
+    ],
     edition: "2021",
     // Only build on targets which crosvm builds on.
     enabled: false,
@@ -34,6 +38,7 @@
         "libclap",
         "libcommand_fds",
         "libdisk",
+        "libhex",
         "libhypervisor_props",
         "liblazy_static",
         "liblibc",
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index c63ed4c..8c2099f 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -52,6 +52,14 @@
 use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::{
         BnVirtualMachineService, IVirtualMachineService,
 };
+use android_hardware_security_secretkeeper::aidl::android::hardware::security::secretkeeper::ISecretkeeper::{BnSecretkeeper, ISecretkeeper};
+use android_hardware_security_secretkeeper::aidl::android::hardware::security::secretkeeper::SecretId::SecretId;
+use android_hardware_security_authgraph::aidl::android::hardware::security::authgraph::{
+    Arc::Arc as AuthgraphArc, IAuthGraphKeyExchange::IAuthGraphKeyExchange,
+    IAuthGraphKeyExchange::BnAuthGraphKeyExchange, Identity::Identity, KeInitResult::KeInitResult,
+    Key::Key, PubKey::PubKey, SessionIdSignature::SessionIdSignature, SessionInfo::SessionInfo,
+    SessionInitiationInfo::SessionInitiationInfo,
+};
 use anyhow::{anyhow, bail, Context, Result};
 use apkverify::{HashAlgorithm, V4Signature};
 use avflog::LogResult;
@@ -102,9 +110,13 @@
 
 const MICRODROID_OS_NAME: &str = "microdroid";
 
+// TODO(b/291213394): Use 'default' instance for secretkeeper instead of 'nonsecure'
+const SECRETKEEPER_IDENTIFIER: &str =
+    "android.hardware.security.secretkeeper.ISecretkeeper/nonsecure";
+
 const UNFORMATTED_STORAGE_MAGIC: &str = "UNFORMATTED-STORAGE";
 
-/// Roughly estimated sufficient size for storing vendor public key into DTBO.
+/// Rough size for storing root digest of vendor hash descriptor into DTBO.
 const EMPTY_VENDOR_DT_OVERLAY_BUF_SIZE: usize = 10000;
 
 /// crosvm requires all partitions to be a multiple of 4KiB.
@@ -115,7 +127,7 @@
         wait_for_interface(BINDER_SERVICE_IDENTIFIER)
             .expect("Could not connect to VirtualizationServiceInternal");
     static ref MICRODROID_GKI_OS_NAME_PATTERN: Regex =
-        Regex::new(r"^microdroid_gki-\d+\.\d+$").expect("Failed to construct Regex");
+        Regex::new(r"^microdroid_gki-android\d+-\d+\.\d+$").expect("Failed to construct Regex");
 }
 
 fn create_or_update_idsig_file(
@@ -369,13 +381,17 @@
             check_gdb_allowed(config)?;
         }
 
-        let vendor_public_key = extract_vendor_public_key(config)
-            .context("Failed to extract vendor public key")
-            .or_service_specific_exception(-1)?;
-        let dtbo_vendor = if let Some(vendor_public_key) = vendor_public_key {
+        let vendor_hashtree_descriptor_root_digest =
+            extract_vendor_hashtree_descriptor_root_digest(config)
+                .context("Failed to extract root digest of vendor")
+                .or_service_specific_exception(-1)?;
+        let dtbo_vendor = if let Some(vendor_hashtree_descriptor_root_digest) =
+            vendor_hashtree_descriptor_root_digest
+        {
+            let root_digest_hex = hex::encode(vendor_hashtree_descriptor_root_digest);
             let dtbo_for_vendor_image = temporary_directory.join("dtbo_vendor");
-            create_dtbo_for_vendor_image(&vendor_public_key, &dtbo_for_vendor_image)
-                .context("Failed to write vendor_public_key")
+            create_dtbo_for_vendor_image(root_digest_hex.as_bytes(), &dtbo_for_vendor_image)
+                .context("Failed to write root digest of vendor")
                 .or_service_specific_exception(-1)?;
             let file = File::open(dtbo_for_vendor_image)
                 .context("Failed to open dtbo_vendor")
@@ -559,7 +575,9 @@
     }
 }
 
-fn extract_vendor_public_key(config: &VirtualMachineConfig) -> Result<Option<Vec<u8>>> {
+fn extract_vendor_hashtree_descriptor_root_digest(
+    config: &VirtualMachineConfig,
+) -> Result<Option<Vec<u8>>> {
     let VirtualMachineConfig::AppConfig(config) = config else {
         return Ok(None);
     };
@@ -574,15 +592,19 @@
     let size = file.metadata().context("Failed to get metadata from microdroid-vendor.img")?.len();
     let vbmeta = VbMetaImage::verify_reader_region(&file, 0, size)
         .context("Failed to get vbmeta from microdroid-vendor.img")?;
-    let vendor_public_key = vbmeta
-        .public_key()
-        .ok_or(anyhow!("No public key is extracted from microdroid-vendor.img"))?
-        .to_vec();
 
-    Ok(Some(vendor_public_key))
+    for descriptor in vbmeta.descriptors()?.iter() {
+        if let vbmeta::Descriptor::Hashtree(_) = descriptor {
+            return Ok(Some(descriptor.to_hashtree()?.root_digest().to_vec()));
+        }
+    }
+    Err(anyhow!("No root digest is extracted from microdroid-vendor.img"))
 }
 
-fn create_dtbo_for_vendor_image(vendor_public_key: &[u8], dtbo: &PathBuf) -> Result<()> {
+fn create_dtbo_for_vendor_image(
+    vendor_hashtree_descriptor_root_digest: &[u8],
+    dtbo: &PathBuf,
+) -> Result<()> {
     if dtbo.exists() {
         return Err(anyhow!("DTBO file already exists"));
     }
@@ -610,10 +632,16 @@
     let mut avf_node = overlay_node
         .add_subnode(avf_node_name.as_c_str())
         .map_err(|e| anyhow!("Failed to create avf node: {:?}", e))?;
-    let vendor_public_key_name = CString::new("vendor_public_key")?;
+    let vendor_hashtree_descriptor_root_digest_name =
+        CString::new("vendor_hashtree_descriptor_root_digest")?;
     avf_node
-        .setprop(vendor_public_key_name.as_c_str(), vendor_public_key)
-        .map_err(|e| anyhow!("Failed to set avf/vendor_public_key: {:?}", e))?;
+        .setprop(
+            vendor_hashtree_descriptor_root_digest_name.as_c_str(),
+            vendor_hashtree_descriptor_root_digest,
+        )
+        .map_err(|e| {
+            anyhow!("Failed to set avf/vendor_hashtree_descriptor_root_digest: {:?}", e)
+        })?;
 
     fdt.pack().map_err(|e| anyhow!("Failed to pack fdt: {:?}", e))?;
     let mut file = File::create(dtbo)?;
@@ -1370,6 +1398,20 @@
         }
     }
 
+    fn getSecretkeeper(&self) -> binder::Result<Option<Strong<dyn ISecretkeeper>>> {
+        let sk = match binder::get_interface(SECRETKEEPER_IDENTIFIER) {
+            Ok(sk) => {
+                Some(BnSecretkeeper::new_binder(SecretkeeperProxy(sk), BinderFeatures::default()))
+            }
+            Err(StatusCode::NAME_NOT_FOUND) => None,
+            Err(e) => {
+                error!("unexpected error while fetching connection to Secretkeeper {:?}", e);
+                return Err(e.into());
+            }
+        };
+        Ok(sk)
+    }
+
     fn requestAttestation(&self, csr: &[u8]) -> binder::Result<Vec<Certificate>> {
         GLOBAL_SERVICE.requestAttestation(csr, get_calling_uid() as i32)
     }
@@ -1502,13 +1544,14 @@
 
     #[test]
     fn test_create_dtbo_for_vendor_image() -> Result<()> {
-        let vendor_public_key = String::from("foo");
-        let vendor_public_key = vendor_public_key.as_bytes();
+        let vendor_hashtree_descriptor_root_digest = String::from("foo");
+        let vendor_hashtree_descriptor_root_digest =
+            vendor_hashtree_descriptor_root_digest.as_bytes();
 
         let tmp_dir = tempfile::TempDir::new()?;
         let dtbo_path = tmp_dir.path().to_path_buf().join("bar");
 
-        create_dtbo_for_vendor_image(vendor_public_key, &dtbo_path)?;
+        create_dtbo_for_vendor_image(vendor_hashtree_descriptor_root_digest, &dtbo_path)?;
 
         let data = std::fs::read(dtbo_path)?;
         let fdt = Fdt::from_slice(&data).unwrap();
@@ -1529,9 +1572,11 @@
         let Some(avf_node) = avf_node else {
             bail!("avf_node shouldn't be None.");
         };
-        let vendor_public_key_name = CString::new("vendor_public_key")?;
-        let key_from_dtbo = avf_node.getprop(vendor_public_key_name.as_c_str()).unwrap();
-        assert_eq!(key_from_dtbo, Some(vendor_public_key));
+        let vendor_hashtree_descriptor_root_digest_name =
+            CString::new("vendor_hashtree_descriptor_root_digest")?;
+        let digest_from_dtbo =
+            avf_node.getprop(vendor_hashtree_descriptor_root_digest_name.as_c_str()).unwrap();
+        assert_eq!(digest_from_dtbo, Some(vendor_hashtree_descriptor_root_digest));
 
         tmp_dir.close()?;
         Ok(())
@@ -1539,18 +1584,84 @@
 
     #[test]
     fn test_create_dtbo_for_vendor_image_throws_error_if_already_exists() -> Result<()> {
-        let vendor_public_key = String::from("foo");
-        let vendor_public_key = vendor_public_key.as_bytes();
+        let vendor_hashtree_descriptor_root_digest = String::from("foo");
+        let vendor_hashtree_descriptor_root_digest =
+            vendor_hashtree_descriptor_root_digest.as_bytes();
 
         let tmp_dir = tempfile::TempDir::new()?;
         let dtbo_path = tmp_dir.path().to_path_buf().join("bar");
 
-        create_dtbo_for_vendor_image(vendor_public_key, &dtbo_path)?;
+        create_dtbo_for_vendor_image(vendor_hashtree_descriptor_root_digest, &dtbo_path)?;
 
-        let ret_second_trial = create_dtbo_for_vendor_image(vendor_public_key, &dtbo_path);
+        let ret_second_trial =
+            create_dtbo_for_vendor_image(vendor_hashtree_descriptor_root_digest, &dtbo_path);
         assert!(ret_second_trial.is_err(), "should fail");
 
         tmp_dir.close()?;
         Ok(())
     }
 }
+
+struct SecretkeeperProxy(Strong<dyn ISecretkeeper>);
+
+impl Interface for SecretkeeperProxy {}
+
+impl ISecretkeeper for SecretkeeperProxy {
+    fn processSecretManagementRequest(&self, req: &[u8]) -> binder::Result<Vec<u8>> {
+        // Pass the request to the channel, and read the response.
+        self.0.processSecretManagementRequest(req)
+    }
+
+    fn getAuthGraphKe(&self) -> binder::Result<Strong<dyn IAuthGraphKeyExchange>> {
+        let ag = AuthGraphKeyExchangeProxy(self.0.getAuthGraphKe()?);
+        Ok(BnAuthGraphKeyExchange::new_binder(ag, BinderFeatures::default()))
+    }
+
+    fn deleteIds(&self, ids: &[SecretId]) -> binder::Result<()> {
+        self.0.deleteIds(ids)
+    }
+
+    fn deleteAll(&self) -> binder::Result<()> {
+        self.0.deleteAll()
+    }
+}
+
+struct AuthGraphKeyExchangeProxy(Strong<dyn IAuthGraphKeyExchange>);
+
+impl Interface for AuthGraphKeyExchangeProxy {}
+
+impl IAuthGraphKeyExchange for AuthGraphKeyExchangeProxy {
+    fn create(&self) -> binder::Result<SessionInitiationInfo> {
+        self.0.create()
+    }
+
+    fn init(
+        &self,
+        peer_pub_key: &PubKey,
+        peer_id: &Identity,
+        peer_nonce: &[u8],
+        peer_version: i32,
+    ) -> binder::Result<KeInitResult> {
+        self.0.init(peer_pub_key, peer_id, peer_nonce, peer_version)
+    }
+
+    fn finish(
+        &self,
+        peer_pub_key: &PubKey,
+        peer_id: &Identity,
+        peer_signature: &SessionIdSignature,
+        peer_nonce: &[u8],
+        peer_version: i32,
+        own_key: &Key,
+    ) -> binder::Result<SessionInfo> {
+        self.0.finish(peer_pub_key, peer_id, peer_signature, peer_nonce, peer_version, own_key)
+    }
+
+    fn authenticationComplete(
+        &self,
+        peer_signature: &SessionIdSignature,
+        shared_keys: &[AuthgraphArc; 2],
+    ) -> binder::Result<[AuthgraphArc; 2]> {
+        self.0.authenticationComplete(peer_signature, shared_keys)
+    }
+}
diff --git a/virtualizationservice/aidl/Android.bp b/virtualizationservice/aidl/Android.bp
index 91d91aa..8ca375a 100644
--- a/virtualizationservice/aidl/Android.bp
+++ b/virtualizationservice/aidl/Android.bp
@@ -57,11 +57,14 @@
 aidl_interface {
     name: "android.system.virtualmachineservice",
     srcs: ["android/system/virtualmachineservice/**/*.aidl"],
-    imports: ["android.system.virtualizationcommon"],
+    imports: [
+        "android.hardware.security.secretkeeper-V1",
+        "android.system.virtualizationcommon",
+    ],
     unstable: true,
     backend: {
         java: {
-            sdk_version: "module_current",
+            enabled: false,
         },
         rust: {
             enabled: true,
diff --git a/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl b/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
index 3c60478..cf91302 100644
--- a/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
+++ b/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
@@ -15,6 +15,7 @@
  */
 package android.system.virtualmachineservice;
 
+import android.hardware.security.secretkeeper.ISecretkeeper;
 import android.system.virtualizationcommon.Certificate;
 import android.system.virtualizationcommon.ErrorCode;
 
@@ -54,4 +55,11 @@
      *         key's certificate chain. The attestation key is provided in the CSR.
      */
     Certificate[] requestAttestation(in byte[] csr);
+
+    /**
+     * Request connection to Secretkeeper. This is used by pVM to store Anti-Rollback protected
+     * secrets. Note that the return value is nullable to reflect that Secretkeeper HAL may not be
+     * present.
+     */
+    @nullable ISecretkeeper getSecretkeeper();
 }
diff --git a/vmclient/src/lib.rs b/vmclient/src/lib.rs
index 9f1d7d1..a2a88d8 100644
--- a/vmclient/src/lib.rs
+++ b/vmclient/src/lib.rs
@@ -74,11 +74,7 @@
     // Create new POSIX socketpair, suitable for use with RpcBinder UDS bootstrap
     // transport. Make it O_CLOEXEC to align with how Rust creates file
     // descriptors (expected by SharedChild).
-    let (raw1, raw2) =
-        socketpair(AddressFamily::Unix, SockType::Stream, None, SockFlag::SOCK_CLOEXEC)?;
-
-    // SAFETY: Taking ownership of brand new FDs.
-    unsafe { Ok((OwnedFd::from_raw_fd(raw1), OwnedFd::from_raw_fd(raw2))) }
+    Ok(socketpair(AddressFamily::Unix, SockType::Stream, None, SockFlag::SOCK_CLOEXEC)?)
 }
 
 /// A running instance of virtmgr which is hosting a VirtualizationService