Merge "libs: Move cstr!() into its own crate" into main
diff --git a/Android.bp b/Android.bp
index 550a6be..9c17c7f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -22,6 +22,7 @@
     module_type: "rust_defaults",
     config_namespace: "ANDROID",
     bool_variables: [
+        "release_avf_enable_device_assignment",
         "release_avf_enable_dice_changes",
         "release_avf_enable_llpvm_changes",
         "release_avf_enable_multi_tenant_microdroid_vm",
@@ -36,6 +37,9 @@
 avf_flag_aware_rust_defaults {
     name: "avf_build_flags_rust",
     soong_config_variables: {
+        release_avf_enable_device_assignment: {
+            cfgs: ["device_assignment"],
+        },
         release_avf_enable_dice_changes: {
             cfgs: ["dice_changes"],
         },
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 77ccc1d..a9193d7 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -105,6 +105,9 @@
       "path": "packages/modules/Virtualization/rialto"
     },
     {
+      "path": "packages/modules/Virtualization/service_vm/client_vm_csr"
+    },
+    {
       "path": "packages/modules/Virtualization/service_vm/comm"
     },
     {
diff --git a/apex/Android.bp b/apex/Android.bp
index a4c8861..e04dbd2 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -67,7 +67,21 @@
     ],
 }
 
-apex_defaults {
+soong_config_module_type {
+    name: "avf_flag_aware_apex_defaults",
+    module_type: "apex_defaults",
+    config_namespace: "ANDROID",
+    bool_variables: [
+        "release_avf_enable_device_assignment",
+        "release_avf_enable_vendor_modules",
+    ],
+    properties: [
+        "arch",
+        "prebuilts",
+    ],
+}
+
+avf_flag_aware_apex_defaults {
     name: "com.android.virt_avf_enabled",
 
     defaults: ["com.android.virt_common"],
@@ -79,7 +93,6 @@
         arm64: {
             binaries: [
                 "crosvm",
-                "vfio_handler",
                 "virtmgr",
                 "virtualizationservice",
             ],
@@ -88,7 +101,6 @@
         x86_64: {
             binaries: [
                 "crosvm",
-                "vfio_handler",
                 "virtmgr",
                 "virtualizationservice",
             ],
@@ -115,6 +127,29 @@
     apps: [
         "EmptyPayloadApp",
     ],
+    soong_config_variables: {
+        release_avf_enable_device_assignment: {
+            prebuilts: [
+                "com.android.virt.vfio_handler.rc",
+            ],
+            arch: {
+                arm64: {
+                    binaries: ["vfio_handler"],
+                },
+                x86_64: {
+                    binaries: ["vfio_handler"],
+                },
+            },
+        },
+        release_avf_enable_vendor_modules: {
+            prebuilts: [
+                "microdroid_gki_initrd_debuggable",
+                "microdroid_gki_initrd_normal",
+                "microdroid_gki_kernel",
+                "microdroid_gki.json",
+            ],
+        },
+    },
 }
 
 apex_defaults {
@@ -141,6 +176,13 @@
     installable: false,
 }
 
+prebuilt_etc {
+    name: "com.android.virt.vfio_handler.rc",
+    src: "vfio_handler.rc",
+    filename: "vfio_handler.rc",
+    installable: false,
+}
+
 sh_binary_host {
     name: "prepare_device_vfio",
     src: "prepare_device_vfio.sh",
diff --git a/apex/sign_virt_apex.py b/apex/sign_virt_apex.py
index 8257aae..7393636 100644
--- a/apex/sign_virt_apex.py
+++ b/apex/sign_virt_apex.py
@@ -27,6 +27,7 @@
 - lpmake, lpunpack, simg2img, img2simg, initrd_bootconfig
 """
 import argparse
+import builtins
 import hashlib
 import os
 import re
@@ -282,7 +283,7 @@
         avb_version_bc = re.search(
             r"androidboot.vbmeta.avb_version = \"([^\"]*)\"", bootconfigs).group(1)
         if avb_version_curr != avb_version_bc:
-            raise Exception(f'AVB version mismatch between current & one & \
+            raise builtins.Exception(f'AVB version mismatch between current & one & \
                 used to build bootconfigs:{avb_version_curr}&{avb_version_bc}')
 
     def calc_vbmeta_digest():
@@ -412,10 +413,13 @@
 # dict of (key, file) for re-sign/verification. keys are un-versioned for readability.
 virt_apex_files = {
     'kernel': 'etc/fs/microdroid_kernel',
+    'gki_kernel': 'etc/fs/microdroid_gki_kernel',
     'vbmeta.img': 'etc/fs/microdroid_vbmeta.img',
     'super.img': 'etc/fs/microdroid_super.img',
     'initrd_normal.img': 'etc/microdroid_initrd_normal.img',
+    'gki_initrd_normal.img': 'etc/microdroid_gki_initrd_normal.img',
     'initrd_debuggable.img': 'etc/microdroid_initrd_debuggable.img',
+    'gki_initrd_debuggable.img': 'etc/microdroid_gki_initrd_debuggable.img',
 }
 
 
@@ -430,42 +434,67 @@
 
     # unpacked files (will be unpacked from super.img below)
     system_a_img = os.path.join(unpack_dir.name, 'system_a.img')
+    vendor_a_img = os.path.join(unpack_dir.name, 'vendor_a.img')
 
     # re-sign super.img
     # 1. unpack super.img
-    # 2. resign system
-    # 3. repack super.img out of resigned system
+    # 2. resign system and vendor (if exists)
+    # 3. repack super.img out of resigned system and vendor (if exists)
     UnpackSuperImg(args, files['super.img'], unpack_dir.name)
     system_a_f = Async(AddHashTreeFooter, args, key, system_a_img)
     partitions = {"system_a": system_a_img}
+    images = [system_a_img]
+    images_f = [system_a_f]
+
+    # if vendor_a.img exists, resign it
+    if os.path.exists(vendor_a_img):
+        partitions.update({'vendor_a': vendor_a_img})
+        images.append(vendor_a_img)
+        vendor_a_f = Async(AddHashTreeFooter, args, key, vendor_a_img)
+        images_f.append(vendor_a_f)
+
     Async(MakeSuperImage, args, partitions,
-          files['super.img'], wait=[system_a_f])
+          files['super.img'], wait=images_f)
 
     # re-generate vbmeta from re-signed system_a.img
     vbmeta_f = Async(MakeVbmetaImage, args, key, files['vbmeta.img'],
-                     images=[system_a_img],
-                     wait=[system_a_f])
+                     images=images,
+                     wait=images_f)
+
+    has_gki_kernel = os.path.isfile(files['gki_kernel'])
 
     vbmeta_bc_f = None
     if not args.do_not_update_bootconfigs:
-        vbmeta_bc_f = Async(UpdateVbmetaBootconfig, args,
-                            [files['initrd_normal.img'],
-                                files['initrd_debuggable.img']], files['vbmeta.img'],
+        initrd_files = [files['initrd_normal.img'], files['initrd_debuggable.img']]
+        if has_gki_kernel:
+            initrd_files += [files['gki_initrd_normal.img'], files['gki_initrd_debuggable.img']]
+        vbmeta_bc_f = Async(UpdateVbmetaBootconfig, args, initrd_files,
+                            files['vbmeta.img'],
                             wait=[vbmeta_f])
 
     # Re-sign kernel. Note kernel's vbmeta contain addition descriptor from ramdisk(s)
-    initrd_normal_hashdesc = tempfile.NamedTemporaryFile(delete=False).name
-    initrd_debug_hashdesc = tempfile.NamedTemporaryFile(delete=False).name
-    initrd_n_f = Async(GenVbmetaImage, args, files['initrd_normal.img'],
-                       initrd_normal_hashdesc, "initrd_normal",
-                       wait=[vbmeta_bc_f] if vbmeta_bc_f is not None else [])
-    initrd_d_f = Async(GenVbmetaImage, args, files['initrd_debuggable.img'],
-                       initrd_debug_hashdesc, "initrd_debug",
-                       wait=[vbmeta_bc_f] if vbmeta_bc_f is not None else [])
-    Async(AddHashFooter, args, key, files['kernel'], partition_name="boot",
-          additional_descriptors=[
-              initrd_normal_hashdesc, initrd_debug_hashdesc],
-          wait=[initrd_n_f, initrd_d_f])
+    def resign_kernel(kernel, initrd_normal, initrd_debug):
+        kernel_file = files[kernel]
+        initrd_normal_file = files[initrd_normal]
+        initrd_debug_file = files[initrd_debug]
+
+        initrd_normal_hashdesc = tempfile.NamedTemporaryFile(delete=False).name
+        initrd_debug_hashdesc = tempfile.NamedTemporaryFile(delete=False).name
+        initrd_n_f = Async(GenVbmetaImage, args, initrd_normal_file,
+                           initrd_normal_hashdesc, "initrd_normal",
+                           wait=[vbmeta_bc_f] if vbmeta_bc_f is not None else [])
+        initrd_d_f = Async(GenVbmetaImage, args, initrd_debug_file,
+                           initrd_debug_hashdesc, "initrd_debug",
+                           wait=[vbmeta_bc_f] if vbmeta_bc_f is not None else [])
+        Async(AddHashFooter, args, key, kernel_file, partition_name="boot",
+              additional_descriptors=[
+                  initrd_normal_hashdesc, initrd_debug_hashdesc],
+              wait=[initrd_n_f, initrd_d_f])
+
+    resign_kernel('kernel', 'initrd_normal.img', 'initrd_debuggable.img')
+
+    if has_gki_kernel:
+        resign_kernel('gki_kernel', 'gki_initrd_normal.img', 'gki_initrd_debuggable.img')
 
 
 def VerifyVirtApex(args):
@@ -490,7 +519,8 @@
         assert info['Public key (sha1)'] == pubkey_digest, f'pubkey mismatch: {file}'
 
     for f in files.values():
-        if f in (files['initrd_normal.img'], files['initrd_debuggable.img']):
+        if f in (files['initrd_normal.img'], files['initrd_debuggable.img'],
+                 files['gki_initrd_normal.img'], files['gki_initrd_debuggable.img']):
             # TODO(b/245277660): Verify that ramdisks contain the correct vbmeta digest
             continue
         if f == files['super.img']:
diff --git a/apex/vfio_handler.rc b/apex/vfio_handler.rc
new file mode 100644
index 0000000..419acef
--- /dev/null
+++ b/apex/vfio_handler.rc
@@ -0,0 +1,20 @@
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+service vfio_handler /apex/com.android.virt/bin/vfio_handler
+    user root
+    group system
+    interface aidl android.system.virtualizationservice_internal.IVfioHandler
+    disabled
+    oneshot
diff --git a/apex/virtualizationservice.rc b/apex/virtualizationservice.rc
index 8283594..02b2081 100644
--- a/apex/virtualizationservice.rc
+++ b/apex/virtualizationservice.rc
@@ -19,10 +19,3 @@
     interface aidl android.system.virtualizationservice
     disabled
     oneshot
-
-service vfio_handler /apex/com.android.virt/bin/vfio_handler
-    user root
-    group system
-    interface aidl android.system.virtualizationservice_internal.IVfioHandler
-    disabled
-    oneshot
diff --git a/compos/src/compsvc_main.rs b/compos/src/compsvc_main.rs
index b0fc323..128d581 100644
--- a/compos/src/compsvc_main.rs
+++ b/compos/src/compsvc_main.rs
@@ -54,10 +54,7 @@
     // SAFETY: We hold a strong pointer, so the raw pointer remains valid. The bindgen AIBinder
     // is the same type as sys::AIBinder. It is safe for on_ready to be invoked at any time, with
     // any parameter.
-    unsafe {
-        AVmPayload_runVsockRpcServer(service, COMPOS_VSOCK_PORT, Some(on_ready), param);
-    }
-    Ok(())
+    unsafe { AVmPayload_runVsockRpcServer(service, COMPOS_VSOCK_PORT, Some(on_ready), param) }
 }
 
 extern "C" fn on_ready(_param: *mut c_void) {
diff --git a/libs/bssl/error/src/lib.rs b/libs/bssl/error/src/lib.rs
index 3766c41..88929af 100644
--- a/libs/bssl/error/src/lib.rs
+++ b/libs/bssl/error/src/lib.rs
@@ -56,11 +56,13 @@
     BN_bn2bin_padded,
     CBB_flush,
     CBB_len,
+    EC_GROUP_new_by_curve_name,
     EC_KEY_check_key,
     EC_KEY_generate_key,
     EC_KEY_get0_group,
     EC_KEY_get0_public_key,
     EC_KEY_marshal_private_key,
+    EC_KEY_parse_private_key,
     EC_KEY_new_by_curve_name,
     EC_POINT_get_affine_coordinates,
     EVP_AEAD_CTX_new,
diff --git a/libs/bssl/src/cbs.rs b/libs/bssl/src/cbs.rs
new file mode 100644
index 0000000..9718903
--- /dev/null
+++ b/libs/bssl/src/cbs.rs
@@ -0,0 +1,55 @@
+// 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.
+
+//! Helpers for using BoringSSL CBS (crypto byte string) objects.
+
+use bssl_ffi::{CBS_init, CBS};
+use core::marker::PhantomData;
+use core::mem::MaybeUninit;
+
+/// CRYPTO ByteString.
+///
+/// Wraps a `CBS` that references an existing fixed-sized buffer; no memory is allocated, but the
+/// buffer cannot grow.
+pub struct Cbs<'a> {
+    cbs: CBS,
+    /// The CBS contains a mutable reference to the buffer, disguised as a pointer.
+    /// Make sure the borrow checker knows that.
+    _buffer: PhantomData<&'a [u8]>,
+}
+
+impl<'a> Cbs<'a> {
+    /// Creates a new CBS that points to the given buffer.
+    pub fn new(buffer: &'a [u8]) -> Self {
+        let mut cbs = MaybeUninit::uninit();
+        // SAFETY: `CBS_init()` only sets `cbs` to point to `buffer`. It doesn't take ownership
+        // of data.
+        unsafe { CBS_init(cbs.as_mut_ptr(), buffer.as_ptr(), buffer.len()) };
+        // SAFETY: `cbs` has just been initialized by `CBS_init()`.
+        let cbs = unsafe { cbs.assume_init() };
+        Self { cbs, _buffer: PhantomData }
+    }
+}
+
+impl<'a> AsRef<CBS> for Cbs<'a> {
+    fn as_ref(&self) -> &CBS {
+        &self.cbs
+    }
+}
+
+impl<'a> AsMut<CBS> for Cbs<'a> {
+    fn as_mut(&mut self) -> &mut CBS {
+        &mut self.cbs
+    }
+}
diff --git a/libs/bssl/src/ec_key.rs b/libs/bssl/src/ec_key.rs
index 7038e21..4c1ba5c 100644
--- a/libs/bssl/src/ec_key.rs
+++ b/libs/bssl/src/ec_key.rs
@@ -16,14 +16,15 @@
 //! BoringSSL.
 
 use crate::cbb::CbbFixed;
+use crate::cbs::Cbs;
 use crate::util::{check_int_result, to_call_failed_error};
 use alloc::vec::Vec;
 use bssl_avf_error::{ApiName, Error, Result};
 use bssl_ffi::{
-    BN_bn2bin_padded, BN_clear_free, BN_new, CBB_flush, CBB_len, EC_KEY_free, EC_KEY_generate_key,
-    EC_KEY_get0_group, EC_KEY_get0_public_key, EC_KEY_marshal_private_key,
-    EC_KEY_new_by_curve_name, EC_POINT_get_affine_coordinates, NID_X9_62_prime256v1, BIGNUM,
-    EC_GROUP, EC_KEY, EC_POINT,
+    BN_bn2bin_padded, BN_clear_free, BN_new, CBB_flush, CBB_len, EC_GROUP_new_by_curve_name,
+    EC_KEY_check_key, EC_KEY_free, EC_KEY_generate_key, EC_KEY_get0_group, EC_KEY_get0_public_key,
+    EC_KEY_marshal_private_key, EC_KEY_new_by_curve_name, EC_KEY_parse_private_key,
+    EC_POINT_get_affine_coordinates, NID_X9_62_prime256v1, BIGNUM, EC_GROUP, EC_KEY, EC_POINT,
 };
 use core::ptr::{self, NonNull};
 use core::result;
@@ -59,6 +60,16 @@
         Ok(ec_key)
     }
 
+    /// Performs several checks on the key. See BoringSSL doc for more details:
+    ///
+    /// https://commondatastorage.googleapis.com/chromium-boringssl-docs/ec_key.h.html#EC_KEY_check_key
+    pub fn check_key(&self) -> Result<()> {
+        // SAFETY: This function only reads the `EC_KEY` pointer, the non-null check is performed
+        // within the function.
+        let ret = unsafe { EC_KEY_check_key(self.0.as_ptr()) };
+        check_int_result(ret, ApiName::EC_KEY_check_key)
+    }
+
     /// Generates a random, private key, calculates the corresponding public key and stores both
     /// in the `EC_KEY`.
     fn generate_key(&mut self) -> Result<()> {
@@ -124,10 +135,34 @@
         }
     }
 
+    /// Constructs an `EcKey` instance from the provided DER-encoded ECPrivateKey slice.
+    ///
+    /// Currently, only the EC P-256 curve is supported.
+    pub fn from_ec_private_key(der_encoded_ec_private_key: &[u8]) -> Result<Self> {
+        // SAFETY: This function only returns a pointer to a static object, and the
+        // return is checked below.
+        let ec_group = unsafe {
+            EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1) // EC P-256 CURVE Nid
+        };
+        if ec_group.is_null() {
+            return Err(to_call_failed_error(ApiName::EC_GROUP_new_by_curve_name));
+        }
+        let mut cbs = Cbs::new(der_encoded_ec_private_key);
+        // SAFETY: The function only reads bytes from the buffer managed by the valid `CBS`
+        // object, and the returned EC_KEY is checked.
+        let ec_key = unsafe { EC_KEY_parse_private_key(cbs.as_mut(), ec_group) };
+
+        let ec_key = NonNull::new(ec_key)
+            .map(Self)
+            .ok_or(to_call_failed_error(ApiName::EC_KEY_parse_private_key))?;
+        ec_key.check_key()?;
+        Ok(ec_key)
+    }
+
     /// Returns the DER-encoded ECPrivateKey structure described in RFC 5915 Section 3:
     ///
     /// https://datatracker.ietf.org/doc/html/rfc5915#section-3
-    pub fn private_key(&self) -> Result<ZVec> {
+    pub fn ec_private_key(&self) -> Result<ZVec> {
         const CAPACITY: usize = 256;
         let mut buf = Zeroizing::new([0u8; CAPACITY]);
         let mut cbb = CbbFixed::new(buf.as_mut());
diff --git a/libs/bssl/src/lib.rs b/libs/bssl/src/lib.rs
index 709e8ad..de81368 100644
--- a/libs/bssl/src/lib.rs
+++ b/libs/bssl/src/lib.rs
@@ -20,6 +20,7 @@
 
 mod aead;
 mod cbb;
+mod cbs;
 mod digest;
 mod ec_key;
 mod err;
@@ -32,6 +33,7 @@
 
 pub use aead::{Aead, AeadContext, AES_GCM_NONCE_LENGTH};
 pub use cbb::CbbFixed;
+pub use cbs::Cbs;
 pub use digest::Digester;
 pub use ec_key::{EcKey, ZVec};
 pub use hkdf::hkdf;
diff --git a/libs/bssl/tests/eckey_test.rs b/libs/bssl/tests/eckey_test.rs
new file mode 100644
index 0000000..a013fba
--- /dev/null
+++ b/libs/bssl/tests/eckey_test.rs
@@ -0,0 +1,25 @@
+// 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.
+
+use bssl_avf::{EcKey, Result};
+
+#[test]
+fn ec_private_key_serialization() -> Result<()> {
+    let ec_key = EcKey::new_p256()?;
+    let der_encoded_ec_private_key = ec_key.ec_private_key()?;
+    let deserialized_ec_key = EcKey::from_ec_private_key(der_encoded_ec_private_key.as_slice())?;
+
+    assert_eq!(ec_key.cose_public_key()?, deserialized_ec_key.cose_public_key()?);
+    Ok(())
+}
diff --git a/libs/bssl/tests/tests.rs b/libs/bssl/tests/tests.rs
index 4c0b0b0..02666d8 100644
--- a/libs/bssl/tests/tests.rs
+++ b/libs/bssl/tests/tests.rs
@@ -15,5 +15,6 @@
 //! API tests of the crate `bssl_avf`.
 
 mod aead_test;
+mod eckey_test;
 mod hkdf_test;
 mod hmac_test;
diff --git a/libs/libfdt/src/lib.rs b/libs/libfdt/src/lib.rs
index 6cfe5f7..b513649 100644
--- a/libs/libfdt/src/lib.rs
+++ b/libs/libfdt/src/lib.rs
@@ -520,7 +520,7 @@
 
 /// Phandle of a FDT node
 #[repr(transparent)]
-#[derive(Debug, Copy, Clone, PartialEq)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
 pub struct Phandle(u32);
 
 impl Phandle {
diff --git a/libs/vbmeta/Android.bp b/libs/vbmeta/Android.bp
index ae83703..4fb6ae4 100644
--- a/libs/vbmeta/Android.bp
+++ b/libs/vbmeta/Android.bp
@@ -18,6 +18,9 @@
 rust_library {
     name: "libvbmeta_rust",
     defaults: ["libvbmeta_rust.defaults"],
+    apex_available: [
+        "com.android.virt",
+    ],
 }
 
 rust_test_host {
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index 42ff4b0..23eadcf 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -243,7 +243,35 @@
         "echo ro.product.cpu.abi=arm64-v8a) > $(out)",
 }
 
-logical_partition {
+// Need to keep microdroid_vendor for the release configurations that don't
+// have RELEASE_AVF_ENABLE_VENDOR_MODULES build flag enabled.
+android_filesystem {
+    name: "microdroid_vendor",
+    partition_name: "vendor",
+    use_avb: true,
+    avb_private_key: ":microdroid_sign_key",
+    avb_algorithm: "SHA256_RSA4096",
+    avb_hash_algorithm: "sha256",
+    file_contexts: ":microdroid_vendor_file_contexts.gen",
+    // For deterministic output, use fake_timestamp, hard-coded uuid
+    fake_timestamp: "1611569676",
+    // python -c "import uuid; print(uuid.uuid5(uuid.NAMESPACE_URL, 'www.android.com/avf/microdroid/vendor'))"
+    uuid: "156d40d7-8d8e-5c99-8913-ec82de549a70",
+}
+
+soong_config_module_type {
+    name: "flag_aware_microdroid_super_partition",
+    module_type: "logical_partition",
+    config_namespace: "ANDROID",
+    bool_variables: [
+        "release_avf_enable_vendor_modules",
+    ],
+    properties: [
+        "default_group",
+    ],
+}
+
+flag_aware_microdroid_super_partition {
     name: "microdroid_super",
     sparse: true,
     size: "auto",
@@ -253,6 +281,16 @@
             filesystem: ":microdroid",
         },
     ],
+    soong_config_variables: {
+        release_avf_enable_vendor_modules: {
+            default_group: [
+                {
+                    name: "vendor_a",
+                    filesystem: ":microdroid_vendor",
+                },
+            ],
+        },
+    },
 }
 
 android_filesystem {
@@ -292,6 +330,22 @@
     ],
 }
 
+android_filesystem {
+    name: "microdroid_gki_modules-6.1-arm64",
+    deps: [
+        "microdroid_gki_kernel_modules-6.1-arm64",
+    ],
+    type: "compressed_cpio",
+}
+
+android_filesystem {
+    name: "microdroid_gki_modules-6.1-x86_64",
+    deps: [
+        "microdroid_gki_kernel_modules-6.1-x86_64",
+    ],
+    type: "compressed_cpio",
+}
+
 genrule {
     name: "microdroid_bootconfig_arm64_gen",
     srcs: [
@@ -330,13 +384,30 @@
     srcs: [":avb_testkey_rsa4096"],
 }
 
-vbmeta {
+soong_config_module_type {
+    name: "flag_aware_microdroid_vbmeta",
+    module_type: "vbmeta",
+    config_namespace: "ANDROID",
+    bool_variables: [
+        "release_avf_enable_vendor_modules",
+    ],
+    properties: [
+        "partitions",
+    ],
+}
+
+flag_aware_microdroid_vbmeta {
     name: "microdroid_vbmeta",
     partition_name: "vbmeta",
     private_key: ":microdroid_sign_key",
     partitions: [
         "microdroid",
     ],
+    soong_config_variables: {
+        release_avf_enable_vendor_modules: {
+            partitions: ["microdroid_vendor"],
+        },
+    },
 }
 
 prebuilt_etc {
@@ -345,6 +416,11 @@
 }
 
 prebuilt_etc {
+    name: "microdroid_gki.json",
+    src: "microdroid_gki.json",
+}
+
+prebuilt_etc {
     name: "microdroid_manifest",
     src: "microdroid_manifest.xml",
     filename: "manifest.xml",
@@ -389,6 +465,23 @@
     },
 }
 
+avb_gen_vbmeta_image {
+    name: "microdroid_gki_initrd_normal_hashdesc",
+    src: ":microdroid_gki_initrd_normal",
+    partition_name: "initrd_normal",
+    salt: initrd_normal_salt,
+    enabled: false,
+    arch: {
+        // Microdroid kernel is only available in these architectures.
+        arm64: {
+            enabled: true,
+        },
+        x86_64: {
+            enabled: true,
+        },
+    },
+}
+
 // python -c "import hashlib; print(hashlib.sha256(b'initrd_debug').hexdigest())"
 initrd_debug_salt = "8ab9dc9cb7e6456700ff6ef18c6b4c3acc24c5fa5381b829563f8d7a415d869a"
 
@@ -409,6 +502,23 @@
     },
 }
 
+avb_gen_vbmeta_image {
+    name: "microdroid_gki_initrd_debug_hashdesc",
+    src: ":microdroid_gki_initrd_debuggable",
+    partition_name: "initrd_debug",
+    salt: initrd_debug_salt,
+    enabled: false,
+    arch: {
+        // Microdroid kernel is only available in these architectures.
+        arm64: {
+            enabled: true,
+        },
+        x86_64: {
+            enabled: true,
+        },
+    },
+}
+
 soong_config_module_type {
     name: "flag_aware_avb_add_hash_footer",
     module_type: "avb_add_hash_footer",
@@ -458,6 +568,42 @@
     },
 }
 
+flag_aware_avb_add_hash_footer {
+    name: "microdroid_gki_kernel_signed",
+    src: ":empty_file",
+    filename: "microdroid_gki_kernel",
+    partition_name: "boot",
+    private_key: ":microdroid_sign_key",
+    salt: bootloader_salt,
+    enabled: false,
+    arch: {
+        arm64: {
+            src: ":microdroid_gki_kernel_prebuilts-6.1-arm64",
+            enabled: true,
+        },
+        x86_64: {
+            src: ":microdroid_gki_kernel_prebuilts-6.1-x86_64",
+            enabled: true,
+        },
+    },
+    include_descriptors_from_images: [
+        ":microdroid_gki_initrd_normal_hashdesc",
+        ":microdroid_gki_initrd_debug_hashdesc",
+    ],
+    // Below are properties that are conditionally set depending on value of build flags.
+    soong_config_variables: {
+        release_avf_enable_llpvm_changes: {
+            rollback_index: 1,
+            props: [
+                {
+                    name: "com.android.virt.cap",
+                    value: "secretkeeper_protection",
+                },
+            ],
+        },
+    },
+}
+
 prebuilt_etc {
     name: "microdroid_kernel",
     src: ":empty_file",
@@ -471,3 +617,17 @@
         },
     },
 }
+
+prebuilt_etc {
+    name: "microdroid_gki_kernel",
+    src: ":empty_file",
+    relative_install_path: "fs",
+    arch: {
+        arm64: {
+            src: ":microdroid_gki_kernel_signed",
+        },
+        x86_64: {
+            src: ":microdroid_gki_kernel_signed",
+        },
+    },
+}
diff --git a/microdroid/initrd/Android.bp b/microdroid/initrd/Android.bp
index de28d8a..6cd84fa 100644
--- a/microdroid/initrd/Android.bp
+++ b/microdroid/initrd/Android.bp
@@ -40,6 +40,28 @@
     cmd: "cat $(in) > $(out)",
 }
 
+genrule {
+    name: "microdroid_gki_initrd_gen_arm64",
+    srcs: [
+        ":microdroid_ramdisk",
+        ":microdroid_fstab_ramdisk",
+        ":microdroid_gki_modules-6.1-arm64",
+    ],
+    out: ["microdroid_initrd.img"],
+    cmd: "cat $(in) > $(out)",
+}
+
+genrule {
+    name: "microdroid_gki_initrd_gen_x86_64",
+    srcs: [
+        ":microdroid_ramdisk",
+        ":microdroid_fstab_ramdisk",
+        ":microdroid_gki_modules-6.1-x86_64",
+    ],
+    out: ["microdroid_initrd.img"],
+    cmd: "cat $(in) > $(out)",
+}
+
 // This contains vbmeta hashes & related (boot)configs which are passed to kernel/init
 genrule {
     name: "microdroid_vbmeta_bootconfig_gen",
@@ -74,6 +96,17 @@
 }
 
 genrule {
+    name: "microdroid_gki_initrd_debuggable_arm64",
+    tools: ["initrd_bootconfig"],
+    srcs: [
+        ":microdroid_gki_initrd_gen_arm64",
+        ":microdroid_bootconfig_debuggable_src",
+    ] + bootconfigs_arm64,
+    out: ["microdroid_gki_initrd_debuggable_arm64"],
+    cmd: "$(location initrd_bootconfig) attach --output $(out) $(in)",
+}
+
+genrule {
     name: "microdroid_initrd_debuggable_x86_64",
     tools: ["initrd_bootconfig"],
     srcs: [
@@ -85,6 +118,17 @@
 }
 
 genrule {
+    name: "microdroid_gki_initrd_debuggable_x86_64",
+    tools: ["initrd_bootconfig"],
+    srcs: [
+        ":microdroid_gki_initrd_gen_x86_64",
+        ":microdroid_bootconfig_debuggable_src",
+    ] + bootconfigs_x86_64,
+    out: ["microdroid_gki_initrd_debuggable_x86_64"],
+    cmd: "$(location initrd_bootconfig) attach --output $(out) $(in)",
+}
+
+genrule {
     name: "microdroid_initrd_normal_arm64",
     tools: ["initrd_bootconfig"],
     srcs: [
@@ -96,6 +140,17 @@
 }
 
 genrule {
+    name: "microdroid_gki_initrd_normal_arm64",
+    tools: ["initrd_bootconfig"],
+    srcs: [
+        ":microdroid_gki_initrd_gen_arm64",
+        ":microdroid_bootconfig_normal_src",
+    ] + bootconfigs_arm64,
+    out: ["microdroid_gki_initrd_normal_arm64"],
+    cmd: "$(location initrd_bootconfig) attach --output $(out) $(in)",
+}
+
+genrule {
     name: "microdroid_initrd_normal_x86_64",
     tools: ["initrd_bootconfig"],
     srcs: [
@@ -106,6 +161,17 @@
     cmd: "$(location initrd_bootconfig) attach --output $(out) $(in)",
 }
 
+genrule {
+    name: "microdroid_gki_initrd_normal_x86_64",
+    tools: ["initrd_bootconfig"],
+    srcs: [
+        ":microdroid_gki_initrd_gen_x86_64",
+        ":microdroid_bootconfig_normal_src",
+    ] + bootconfigs_x86_64,
+    out: ["microdroid_gki_initrd_normal_x86_64"],
+    cmd: "$(location initrd_bootconfig) attach --output $(out) $(in)",
+}
+
 prebuilt_etc {
     name: "microdroid_initrd_debuggable",
     // We don't have ramdisk for architectures other than x86_64 & arm64
@@ -122,6 +188,21 @@
 }
 
 prebuilt_etc {
+    name: "microdroid_gki_initrd_debuggable",
+    // We don't have ramdisk for architectures other than x86_64 & arm64
+    src: ":empty_file",
+    arch: {
+        x86_64: {
+            src: ":microdroid_gki_initrd_debuggable_x86_64",
+        },
+        arm64: {
+            src: ":microdroid_gki_initrd_debuggable_arm64",
+        },
+    },
+    filename: "microdroid_gki_initrd_debuggable.img",
+}
+
+prebuilt_etc {
     name: "microdroid_initrd_normal",
     // We don't have ramdisk for architectures other than x86_64 & arm64
     src: ":empty_file",
@@ -135,3 +216,18 @@
     },
     filename: "microdroid_initrd_normal.img",
 }
+
+prebuilt_etc {
+    name: "microdroid_gki_initrd_normal",
+    // We don't have ramdisk for architectures other than x86_64 & arm64
+    src: ":empty_file",
+    arch: {
+        x86_64: {
+            src: ":microdroid_gki_initrd_normal_x86_64",
+        },
+        arm64: {
+            src: ":microdroid_gki_initrd_normal_arm64",
+        },
+    },
+    filename: "microdroid_gki_initrd_normal.img",
+}
diff --git a/microdroid/microdroid_gki.json b/microdroid/microdroid_gki.json
new file mode 100644
index 0000000..d7ba53e
--- /dev/null
+++ b/microdroid/microdroid_gki.json
@@ -0,0 +1,20 @@
+{
+  "kernel": "/apex/com.android.virt/etc/fs/microdroid_gki_kernel",
+  "disks": [
+    {
+      "partitions": [
+        {
+          "label": "vbmeta_a",
+          "path": "/apex/com.android.virt/etc/fs/microdroid_vbmeta.img"
+        },
+        {
+          "label": "super",
+          "path": "/apex/com.android.virt/etc/fs/microdroid_super.img"
+        }
+      ],
+      "writable": false
+    }
+  ],
+  "memory_mib": 256,
+  "platform_version": "~1.0"
+}
diff --git a/microdroid_manager/Android.bp b/microdroid_manager/Android.bp
index 93f49ef..8481edf 100644
--- a/microdroid_manager/Android.bp
+++ b/microdroid_manager/Android.bp
@@ -23,6 +23,7 @@
         "libbinder_rs",
         "libbyteorder",
         "libcap_rust",
+        "libclient_vm_csr",
         "libciborium",
         "libcoset",
         "libdiced_open_dice",
@@ -46,12 +47,10 @@
         "libserde",
         "libserde_cbor",
         "libserde_json",
-        "libservice_vm_comm",
         "libthiserror",
         "libuuid",
         "libvsock",
         "librand",
-        "libzeroize",
     ],
     init_rc: ["microdroid_manager.rc"],
     multilib: {
@@ -72,7 +71,6 @@
     defaults: ["microdroid_manager_defaults"],
     test_suites: ["general-tests"],
     rustlibs: [
-        "libhwtrust",
         "libtempfile",
     ],
     multilib: {
diff --git a/microdroid_manager/src/vm_payload_service.rs b/microdroid_manager/src/vm_payload_service.rs
index 0661314..d3346d8 100644
--- a/microdroid_manager/src/vm_payload_service.rs
+++ b/microdroid_manager/src/vm_payload_service.rs
@@ -22,31 +22,12 @@
 use anyhow::{anyhow, Context, Result};
 use avflog::LogResult;
 use binder::{Interface, BinderFeatures, ExceptionCode, Strong, IntoBinderResult, Status};
-use diced_open_dice::{DiceArtifacts, derive_cdi_leaf_priv, PrivateKey, sign};
+use client_vm_csr::{generate_attestation_key_and_csr, ClientVmAttestationData};
+use diced_open_dice::DiceArtifacts;
 use log::info;
 use rpcbinder::RpcServer;
-
 use crate::vm_secret::VmSecret;
-use coset::{
-    iana, CborSerializable, CoseKey, CoseKeyBuilder, CoseSign, CoseSignBuilder, CoseSignature,
-    CoseSignatureBuilder, HeaderBuilder,
-};
-use openssl::{
-    bn::{BigNum, BigNumContext},
-    ec::{EcGroup, EcKey, EcKeyRef},
-    ecdsa::EcdsaSig,
-    nid::Nid,
-    pkey::Private,
-    sha::sha256,
-};
-use service_vm_comm::{Csr, CsrPayload};
 use std::os::unix::io::OwnedFd;
-use zeroize::Zeroizing;
-
-const ATTESTATION_KEY_NID: Nid = Nid::X9_62_PRIME256V1; // NIST P-256 curve
-const ATTESTATION_KEY_ALGO: iana::Algorithm = iana::Algorithm::ES256;
-const ATTESTATION_KEY_CURVE: iana::EllipticCurve = iana::EllipticCurve::P_256;
-const ATTESTATION_KEY_AFFINE_COORDINATE_SIZE: i32 = 32;
 
 /// Implementation of `IVmPayloadService`.
 struct VmPayloadService {
@@ -90,11 +71,21 @@
 
     fn requestAttestation(&self, challenge: &[u8]) -> binder::Result<AttestationResult> {
         self.check_restricted_apis_allowed()?;
-        let (private_key, csr) = generate_attestation_key_and_csr(challenge, self.secret.dice())
+        let ClientVmAttestationData { private_key, csr } =
+            generate_attestation_key_and_csr(challenge, self.secret.dice())
+                .map_err(|e| {
+                    Status::new_service_specific_error_str(
+                        STATUS_FAILED_TO_PREPARE_CSR_AND_KEY,
+                        Some(format!("Failed to prepare the CSR and key pair: {e:?}")),
+                    )
+                })
+                .with_log()?;
+        let csr = csr
+            .into_cbor_vec()
             .map_err(|e| {
                 Status::new_service_specific_error_str(
                     STATUS_FAILED_TO_PREPARE_CSR_AND_KEY,
-                    Some(format!("Failed to prepare the CSR and key pair: {e:?}")),
+                    Some(format!("Failed to serialize CSR into CBOR: {e:?}")),
                 )
             })
             .with_log()?;
@@ -106,93 +97,6 @@
     }
 }
 
-fn generate_attestation_key_and_csr(
-    challenge: &[u8],
-    dice_artifacts: &dyn DiceArtifacts,
-) -> Result<(Zeroizing<Vec<u8>>, Vec<u8>)> {
-    let group = EcGroup::from_curve_name(ATTESTATION_KEY_NID)?;
-    let attestation_key = EcKey::generate(&group)?;
-    let csr = build_csr(challenge, attestation_key.as_ref(), dice_artifacts)?;
-
-    let csr = csr.into_cbor_vec().context("Failed to serialize CSR")?;
-    let private_key = attestation_key.private_key_to_der()?;
-    Ok((Zeroizing::new(private_key), csr))
-}
-
-fn build_csr(
-    challenge: &[u8],
-    attestation_key: &EcKeyRef<Private>,
-    dice_artifacts: &dyn DiceArtifacts,
-) -> Result<Csr> {
-    // Builds CSR Payload to be signed.
-    let public_key =
-        to_cose_public_key(attestation_key)?.to_vec().context("Failed to serialize public key")?;
-    let csr_payload = CsrPayload { public_key, challenge: challenge.to_vec() };
-    let csr_payload = csr_payload.into_cbor_vec()?;
-
-    // Builds signed CSR Payload.
-    let cdi_leaf_priv = derive_cdi_leaf_priv(dice_artifacts)?;
-    let signed_csr_payload = build_signed_data(csr_payload, &cdi_leaf_priv, attestation_key)?
-        .to_vec()
-        .context("Failed to serialize signed CSR payload")?;
-
-    // Builds CSR.
-    let dice_cert_chain = dice_artifacts.bcc().ok_or(anyhow!("bcc is none"))?.to_vec();
-    Ok(Csr { dice_cert_chain, signed_csr_payload })
-}
-
-fn build_signed_data(
-    payload: Vec<u8>,
-    cdi_leaf_priv: &PrivateKey,
-    attestation_key: &EcKeyRef<Private>,
-) -> Result<CoseSign> {
-    let cdi_leaf_sig_headers = build_signature_headers(iana::Algorithm::EdDSA);
-    let attestation_key_sig_headers = build_signature_headers(ATTESTATION_KEY_ALGO);
-    let aad = &[];
-    let signed_data = CoseSignBuilder::new()
-        .payload(payload)
-        .try_add_created_signature(cdi_leaf_sig_headers, aad, |message| {
-            sign(message, cdi_leaf_priv.as_array()).map(|v| v.to_vec())
-        })?
-        .try_add_created_signature(attestation_key_sig_headers, aad, |message| {
-            ecdsa_sign(message, attestation_key)
-        })?
-        .build();
-    Ok(signed_data)
-}
-
-/// Builds a signature with headers filled with the provided algorithm.
-/// The signature data will be filled later when building the signed data.
-fn build_signature_headers(alg: iana::Algorithm) -> CoseSignature {
-    let protected = HeaderBuilder::new().algorithm(alg).build();
-    CoseSignatureBuilder::new().protected(protected).build()
-}
-
-fn ecdsa_sign(message: &[u8], key: &EcKeyRef<Private>) -> Result<Vec<u8>> {
-    let digest = sha256(message);
-    // Passes the digest to `ECDSA_do_sign` as recommended in the spec:
-    // https://commondatastorage.googleapis.com/chromium-boringssl-docs/ecdsa.h.html#ECDSA_do_sign
-    let sig = EcdsaSig::sign::<Private>(&digest, key)?;
-    Ok(sig.to_der()?)
-}
-
-fn get_affine_coordinates(key: &EcKeyRef<Private>) -> Result<(Vec<u8>, Vec<u8>)> {
-    let mut ctx = BigNumContext::new()?;
-    let mut x = BigNum::new()?;
-    let mut y = BigNum::new()?;
-    key.public_key().affine_coordinates_gfp(key.group(), &mut x, &mut y, &mut ctx)?;
-    let x = x.to_vec_padded(ATTESTATION_KEY_AFFINE_COORDINATE_SIZE)?;
-    let y = y.to_vec_padded(ATTESTATION_KEY_AFFINE_COORDINATE_SIZE)?;
-    Ok((x, y))
-}
-
-fn to_cose_public_key(key: &EcKeyRef<Private>) -> Result<CoseKey> {
-    let (x, y) = get_affine_coordinates(key)?;
-    Ok(CoseKeyBuilder::new_ec2_pub_key(ATTESTATION_KEY_CURVE, x, y)
-        .algorithm(ATTESTATION_KEY_ALGO)
-        .build())
-}
-
 impl Interface for VmPayloadService {}
 
 impl VmPayloadService {
@@ -237,106 +141,3 @@
     });
     Ok(())
 }
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use anyhow::bail;
-    use ciborium::Value;
-    use coset::{iana::EnumI64, Label};
-    use hwtrust::{dice, session::Session};
-    use openssl::pkey::Public;
-
-    /// The following data is generated randomly with urandom.
-    const CHALLENGE: [u8; 16] = [
-        0xb3, 0x66, 0xfa, 0x72, 0x92, 0x32, 0x2c, 0xd4, 0x99, 0xcb, 0x00, 0x1f, 0x0e, 0xe0, 0xc7,
-        0x41,
-    ];
-
-    #[test]
-    fn csr_and_private_key_have_correct_format() -> Result<()> {
-        let dice_artifacts = diced_sample_inputs::make_sample_bcc_and_cdis()?;
-
-        let (private_key, csr) = generate_attestation_key_and_csr(&CHALLENGE, &dice_artifacts)?;
-        let ec_private_key = EcKey::private_key_from_der(&private_key)?;
-        let csr = Csr::from_cbor_slice(&csr).unwrap();
-        let cose_sign = CoseSign::from_slice(&csr.signed_csr_payload).unwrap();
-        let aad = &[];
-
-        // Checks CSR payload.
-        let csr_payload =
-            cose_sign.payload.as_ref().and_then(|v| CsrPayload::from_cbor_slice(v).ok()).unwrap();
-        let public_key = to_cose_public_key(&ec_private_key)?.to_vec().unwrap();
-        let expected_csr_payload = CsrPayload { challenge: CHALLENGE.to_vec(), public_key };
-        assert_eq!(expected_csr_payload, csr_payload);
-
-        // Checks the first signature is signed with CDI_Leaf_Priv.
-        let session = Session::default();
-        let chain = dice::Chain::from_cbor(&session, &csr.dice_cert_chain)?;
-        let public_key = chain.leaf().subject_public_key();
-        cose_sign
-            .verify_signature(0, aad, |signature, message| public_key.verify(signature, message))?;
-
-        // Checks the second signature is signed with attestation key.
-        let attestation_public_key = CoseKey::from_slice(&csr_payload.public_key).unwrap();
-        let ec_public_key = to_ec_public_key(&attestation_public_key)?;
-        cose_sign.verify_signature(1, aad, |signature, message| {
-            ecdsa_verify(signature, message, &ec_public_key)
-        })?;
-
-        // Verifies that private key and the public key form a valid key pair.
-        let message = b"test message";
-        let signature = ecdsa_sign(message, &ec_private_key)?;
-        ecdsa_verify(&signature, message, &ec_public_key)?;
-
-        Ok(())
-    }
-
-    fn ecdsa_verify(
-        signature: &[u8],
-        message: &[u8],
-        ec_public_key: &EcKeyRef<Public>,
-    ) -> Result<()> {
-        let sig = EcdsaSig::from_der(signature)?;
-        let digest = sha256(message);
-        if sig.verify(&digest, ec_public_key)? {
-            Ok(())
-        } else {
-            bail!("Signature does not match")
-        }
-    }
-
-    fn to_ec_public_key(cose_key: &CoseKey) -> Result<EcKey<Public>> {
-        check_ec_key_params(cose_key)?;
-        let group = EcGroup::from_curve_name(ATTESTATION_KEY_NID)?;
-        let x = get_label_value_as_bignum(cose_key, Label::Int(iana::Ec2KeyParameter::X.to_i64()))?;
-        let y = get_label_value_as_bignum(cose_key, Label::Int(iana::Ec2KeyParameter::Y.to_i64()))?;
-        let key = EcKey::from_public_key_affine_coordinates(&group, &x, &y)?;
-        key.check_key()?;
-        Ok(key)
-    }
-
-    fn check_ec_key_params(cose_key: &CoseKey) -> Result<()> {
-        assert_eq!(coset::KeyType::Assigned(iana::KeyType::EC2), cose_key.kty);
-        assert_eq!(Some(coset::Algorithm::Assigned(ATTESTATION_KEY_ALGO)), cose_key.alg);
-        let crv = get_label_value(cose_key, Label::Int(iana::Ec2KeyParameter::Crv.to_i64()))?;
-        assert_eq!(&Value::from(ATTESTATION_KEY_CURVE.to_i64()), crv);
-        Ok(())
-    }
-
-    fn get_label_value_as_bignum(key: &CoseKey, label: Label) -> Result<BigNum> {
-        get_label_value(key, label)?
-            .as_bytes()
-            .map(|v| BigNum::from_slice(&v[..]).unwrap())
-            .ok_or_else(|| anyhow!("Value not a bstr."))
-    }
-
-    fn get_label_value(key: &CoseKey, label: Label) -> Result<&Value> {
-        Ok(&key
-            .params
-            .iter()
-            .find(|(k, _)| k == &label)
-            .ok_or_else(|| anyhow!("Label {:?} not found", label))?
-            .1)
-    }
-}
diff --git a/rialto/Android.bp b/rialto/Android.bp
index 12d6e7b..728c1eb 100644
--- a/rialto/Android.bp
+++ b/rialto/Android.bp
@@ -110,6 +110,8 @@
         "libandroid_logger",
         "libanyhow",
         "libciborium",
+        "libclient_vm_csr",
+        "libdiced_sample_inputs",
         "liblibc",
         "liblog_rust",
         "libservice_vm_comm",
diff --git a/rialto/tests/test.rs b/rialto/tests/test.rs
index e13b7a1..0f59350 100644
--- a/rialto/tests/test.rs
+++ b/rialto/tests/test.rs
@@ -23,9 +23,11 @@
 };
 use anyhow::{bail, Context, Result};
 use ciborium::value::Value;
+use client_vm_csr::generate_attestation_key_and_csr;
 use log::info;
 use service_vm_comm::{
-    EcdsaP256KeyPair, GenerateCertificateRequestParams, Request, Response, VmType,
+    ClientVmAttestationParams, EcdsaP256KeyPair, GenerateCertificateRequestParams, Request,
+    RequestProcessingError, Response, VmType,
 };
 use service_vm_manager::ServiceVm;
 use std::fs::File;
@@ -51,8 +53,9 @@
     let mut vm = start_service_vm(vm_type)?;
 
     check_processing_reverse_request(&mut vm)?;
-    let maced_public_key = check_processing_generating_key_pair_request(&mut vm)?;
-    check_processing_generating_certificate_request(&mut vm, maced_public_key)?;
+    let key_pair = check_processing_generating_key_pair_request(&mut vm)?;
+    check_processing_generating_certificate_request(&mut vm, &key_pair.maced_public_key)?;
+    check_attestation_request(&mut vm, &key_pair.key_blob)?;
     Ok(())
 }
 
@@ -68,17 +71,17 @@
     Ok(())
 }
 
-fn check_processing_generating_key_pair_request(vm: &mut ServiceVm) -> Result<Vec<u8>> {
+fn check_processing_generating_key_pair_request(vm: &mut ServiceVm) -> Result<EcdsaP256KeyPair> {
     let request = Request::GenerateEcdsaP256KeyPair;
 
     let response = vm.process_request(request)?;
     info!("Received response: {response:?}.");
 
     match response {
-        Response::GenerateEcdsaP256KeyPair(EcdsaP256KeyPair { maced_public_key, key_blob }) => {
-            assert_array_has_nonzero(&maced_public_key);
-            assert_array_has_nonzero(&key_blob);
-            Ok(maced_public_key)
+        Response::GenerateEcdsaP256KeyPair(key_pair) => {
+            assert_array_has_nonzero(&key_pair.maced_public_key);
+            assert_array_has_nonzero(&key_pair.key_blob);
+            Ok(key_pair)
         }
         _ => bail!("Incorrect response type: {response:?}"),
     }
@@ -90,10 +93,10 @@
 
 fn check_processing_generating_certificate_request(
     vm: &mut ServiceVm,
-    maced_public_key: Vec<u8>,
+    maced_public_key: &[u8],
 ) -> Result<()> {
     let params = GenerateCertificateRequestParams {
-        keys_to_sign: vec![maced_public_key],
+        keys_to_sign: vec![maced_public_key.to_vec()],
         challenge: vec![],
     };
     let request = Request::GenerateCertificateRequest(params);
@@ -107,6 +110,31 @@
     }
 }
 
+fn check_attestation_request(vm: &mut ServiceVm, key_blob: &[u8]) -> Result<()> {
+    /// The following data was generated randomly with urandom.
+    const CHALLENGE: [u8; 16] = [
+        0x7d, 0x86, 0x58, 0x79, 0x3a, 0x09, 0xdf, 0x1c, 0xa5, 0x80, 0x80, 0x15, 0x2b, 0x13, 0x17,
+        0x5c,
+    ];
+    let dice_artifacts = diced_sample_inputs::make_sample_bcc_and_cdis()?;
+    let attestation_data = generate_attestation_key_and_csr(&CHALLENGE, &dice_artifacts)?;
+
+    let params = ClientVmAttestationParams {
+        csr: attestation_data.csr.into_cbor_vec()?,
+        remotely_provisioned_key_blob: key_blob.to_vec(),
+    };
+    let request = Request::RequestClientVmAttestation(params);
+
+    let response = vm.process_request(request)?;
+    info!("Received response: {response:?}.");
+
+    match response {
+        // TODO(b/309441500): Check the certificate once it is implemented.
+        Response::Err(RequestProcessingError::OperationUnimplemented) => Ok(()),
+        _ => bail!("Incorrect response type: {response:?}"),
+    }
+}
+
 /// TODO(b/300625792): Check the CSR with libhwtrust once the CSR is complete.
 fn check_csr(csr: Vec<u8>) -> Result<()> {
     let mut reader = io::Cursor::new(csr);
diff --git a/secretkeeper/dice_policy/Android.bp b/secretkeeper/dice_policy/Android.bp
index a7ac5b9..4f1e8b6 100644
--- a/secretkeeper/dice_policy/Android.bp
+++ b/secretkeeper/dice_policy/Android.bp
@@ -13,7 +13,9 @@
         "libanyhow",
         "libciborium",
         "libcoset",
+        "libnum_traits",
     ],
+    proc_macros: ["libnum_derive"],
 }
 
 rust_library {
diff --git a/secretkeeper/dice_policy/src/lib.rs b/secretkeeper/dice_policy/src/lib.rs
index 327b8a4..2e91305 100644
--- a/secretkeeper/dice_policy/src/lib.rs
+++ b/secretkeeper/dice_policy/src/lib.rs
@@ -57,16 +57,20 @@
 //!
 //! value = bool / int / tstr / bstr
 
-use anyhow::{anyhow, bail, Context, Result};
+use anyhow::{anyhow, bail, ensure, Context, Result};
 use ciborium::Value;
 use coset::{AsCborValue, CoseSign1};
+use num_derive::FromPrimitive;
+use num_traits::FromPrimitive;
 use std::borrow::Cow;
+use std::iter::zip;
 
 const DICE_POLICY_VERSION: u64 = 1;
 
 /// Constraint Types supported in Dice policy.
+#[repr(u16)]
 #[non_exhaustive]
-#[derive(Clone, Copy, Debug, PartialEq)]
+#[derive(Clone, Copy, Debug, FromPrimitive, PartialEq)]
 pub enum ConstraintType {
     /// Enforce exact match criteria, indicating the policy should match
     /// if the dice chain has exact same specified values.
@@ -133,6 +137,7 @@
     ///    ];
     ///
     /// 2. For hypothetical (and highly simplified) dice chain:
+    ///
     ///    [ROT_KEY, [{1 : 'a', 2 : {200 : 5, 201 : 'b'}}]]
     ///    The following can be used
     ///    constraint_spec =[
@@ -140,13 +145,7 @@
     ///     ConstraintSpec(ConstraintType::GreaterOrEqual, vec![2, 200]),// matches any value >= 5
     ///    ];
     pub fn from_dice_chain(dice_chain: &[u8], constraint_spec: &[ConstraintSpec]) -> Result<Self> {
-        // TODO(b/298217847): Check if the given dice chain adheres to Explicit-key DiceCertChain
-        // format and if not, convert it before policy construction.
-        let dice_chain = value_from_bytes(dice_chain).context("Unable to decode top-level CBOR")?;
-        let dice_chain = match dice_chain {
-            Value::Array(array) if array.len() >= 2 => array,
-            _ => bail!("Expected an array of at least length 2, found: {:?}", dice_chain),
-        };
+        let dice_chain = deserialize_dice_chain(dice_chain)?;
         let mut constraints_list: Vec<NodeConstraints> = Vec::with_capacity(dice_chain.len());
         let mut it = dice_chain.into_iter();
 
@@ -167,6 +166,61 @@
             node_constraints_list: constraints_list.into_boxed_slice(),
         })
     }
+
+    /// Dice chain policy verifier - Compare the input dice chain against this Dice policy.
+    /// The method returns Ok() if the dice chain meets the constraints set in Dice policy,
+    /// otherwise returns error in case of mismatch.
+    /// TODO(b/291238565) Create a separate error module for DicePolicy mismatches.
+    pub fn matches_dice_chain(&self, dice_chain: &[u8]) -> Result<()> {
+        let dice_chain = deserialize_dice_chain(dice_chain)?;
+        ensure!(
+            dice_chain.len() == self.node_constraints_list.len(),
+            format!(
+                "Dice chain size({}) does not match policy({})",
+                dice_chain.len(),
+                self.node_constraints_list.len()
+            )
+        );
+
+        for (n, (dice_node, node_constraints)) in
+            zip(dice_chain, self.node_constraints_list.iter()).enumerate()
+        {
+            let dice_node_payload = if n == 0 {
+                dice_node
+            } else {
+                cbor_value_from_cose_sign(dice_node)
+                    .with_context(|| format!("Unable to get Cose payload at: {}", n))?
+            };
+            check_constraints_on_node(node_constraints, &dice_node_payload)
+                .context(format!("Mismatch found at {}", n))?;
+        }
+        Ok(())
+    }
+}
+
+fn check_constraints_on_node(node_constraints: &NodeConstraints, dice_node: &Value) -> Result<()> {
+    for constraint in node_constraints.0.iter() {
+        check_constraint_on_node(constraint, dice_node)?;
+    }
+    Ok(())
+}
+
+fn check_constraint_on_node(constraint: &Constraint, dice_node: &Value) -> Result<()> {
+    let Constraint(cons_type, path, value_in_constraint) = constraint;
+    let value_in_node = lookup_value_in_nested_map(dice_node, path)?;
+    match ConstraintType::from_u16(*cons_type).ok_or(anyhow!("Unexpected Constraint type"))? {
+        ConstraintType::ExactMatch => ensure!(value_in_node == *value_in_constraint),
+        ConstraintType::GreaterOrEqual => {
+            let value_in_node = value_in_node
+                .as_integer()
+                .ok_or(anyhow!("Mismatch type: expected a cbor integer"))?;
+            let value_min = value_in_constraint
+                .as_integer()
+                .ok_or(anyhow!("Mismatch type: expected a cbor integer"))?;
+            ensure!(value_in_node >= value_min);
+        }
+    };
+    Ok(())
 }
 
 // Take the payload of a dice node & construct the constraints on it.
@@ -231,6 +285,17 @@
         Some(payload) => Ok(value_from_bytes(&payload)?),
     }
 }
+fn deserialize_dice_chain(dice_chain_bytes: &[u8]) -> Result<Vec<Value>> {
+    // TODO(b/298217847): Check if the given dice chain adheres to Explicit-key DiceCertChain
+    // format and if not, convert it.
+    let dice_chain =
+        value_from_bytes(dice_chain_bytes).context("Unable to decode top-level CBOR")?;
+    let dice_chain = match dice_chain {
+        Value::Array(array) if array.len() >= 2 => array,
+        _ => bail!("Expected an array of at least length 2, found: {:?}", dice_chain),
+    };
+    Ok(dice_chain)
+}
 
 /// Decodes the provided binary CBOR-encoded value and returns a
 /// ciborium::Value struct wrapped in Result.
@@ -266,38 +331,29 @@
         constraint_spec: Vec<ConstraintSpec>,
         // The expected dice policy if above constraint_spec is applied to input_dice.
         expected_dice_policy: DicePolicy,
+        // Another dice chain, which is almost same as the input_dice, but (roughly) imitates
+        // an 'updated' one, ie, some int entries are higher than corresponding entry
+        // in input_chain.
+        updated_input_dice: Vec<u8>,
     }
 
     impl TestArtifacts {
         // Get an example instance of TestArtifacts. This uses a hard coded, hypothetical
         // chain of certificates & a list of constraint_spec on this.
         fn get_example() -> Self {
-            const EXAMPLE_NUM: i64 = 59765;
+            const EXAMPLE_NUM_1: i64 = 59765;
+            const EXAMPLE_NUM_2: i64 = 59766;
             const EXAMPLE_STRING: &str = "testing_dice_policy";
+            const UNCONSTRAINED_STRING: &str = "unconstrained_string";
+            const ANOTHER_UNCONSTRAINED_STRING: &str = "another_unconstrained_string";
 
             let rot_key = CoseKey::default().to_cbor_value().unwrap();
-            let nested_payload = cbor!({
-                100 => EXAMPLE_NUM
-            })
-            .unwrap();
-            let payload = cbor!({
-                1 => EXAMPLE_STRING,
-                2 => "some_other_example_string",
-                3 => Value::Bytes(value_to_bytes(&nested_payload).unwrap()),
-            })
-            .unwrap();
-            let payload = value_to_bytes(&payload).unwrap();
-            let dice_node = CoseSign1 {
-                protected: ProtectedHeader::default(),
-                unprotected: Header::default(),
-                payload: Some(payload),
-                signature: b"ddef".to_vec(),
-            }
-            .to_cbor_value()
-            .unwrap();
-            let input_dice = Value::Array([rot_key.clone(), dice_node].to_vec());
-
-            let input_dice = value_to_bytes(&input_dice).unwrap();
+            let input_dice = Self::get_dice_chain_helper(
+                rot_key.clone(),
+                EXAMPLE_NUM_1,
+                EXAMPLE_STRING,
+                UNCONSTRAINED_STRING,
+            );
 
             // Now construct constraint_spec on the input dice, note this will use the keys
             // which are also hardcoded within the get_dice_chain_helper.
@@ -305,7 +361,7 @@
             let constraint_spec = vec![
                 ConstraintSpec::new(ConstraintType::ExactMatch, vec![1]).unwrap(),
                 // Notice how key "2" is (deliberately) absent in ConstraintSpec
-                // so policy should not constraint it.
+                // so policy should not constrain it.
                 ConstraintSpec::new(ConstraintType::GreaterOrEqual, vec![3, 100]).unwrap(),
             ];
             let expected_dice_policy = DicePolicy {
@@ -325,12 +381,53 @@
                         Constraint(
                             ConstraintType::GreaterOrEqual as u16,
                             vec![3, 100],
-                            Value::from(EXAMPLE_NUM),
+                            Value::from(EXAMPLE_NUM_1),
                         ),
                     ])),
                 ]),
             };
-            Self { input_dice, constraint_spec, expected_dice_policy }
+
+            let updated_input_dice = Self::get_dice_chain_helper(
+                rot_key.clone(),
+                EXAMPLE_NUM_2,
+                EXAMPLE_STRING,
+                ANOTHER_UNCONSTRAINED_STRING,
+            );
+            Self { input_dice, constraint_spec, expected_dice_policy, updated_input_dice }
+        }
+
+        // Helper method method to generate a dice chain with a given rot_key.
+        // Other arguments are ad-hoc values in the nested map. Callers use these to
+        // construct appropriate constrains in dice policies.
+        fn get_dice_chain_helper(
+            rot_key: Value,
+            version: i64,
+            constrained_string: &str,
+            unconstrained_string: &str,
+        ) -> Vec<u8> {
+            let nested_payload = cbor!({
+                100 => version
+            })
+            .unwrap();
+
+            let payload = cbor!({
+                1 => constrained_string,
+                2 => unconstrained_string,
+                3 => Value::Bytes(value_to_bytes(&nested_payload).unwrap()),
+            })
+            .unwrap();
+            let payload = value_to_bytes(&payload).unwrap();
+            let dice_node = CoseSign1 {
+                protected: ProtectedHeader::default(),
+                unprotected: Header::default(),
+                payload: Some(payload),
+                signature: b"ddef".to_vec(),
+            }
+            .to_cbor_value()
+            .unwrap();
+            let input_dice = Value::Array([rot_key.clone(), dice_node].to_vec());
+
+            value_to_bytes(&input_dice).unwrap()
         }
     }
 
@@ -344,6 +441,43 @@
         assert_eq!(policy, example.expected_dice_policy);
     }
 
+    test!(policy_matches_original_dice_chain);
+    fn policy_matches_original_dice_chain() {
+        let example = TestArtifacts::get_example();
+        assert!(
+            DicePolicy::from_dice_chain(&example.input_dice, &example.constraint_spec)
+                .unwrap()
+                .matches_dice_chain(&example.input_dice)
+                .is_ok(),
+            "The dice chain did not match the policy constructed out of it!"
+        );
+    }
+
+    test!(policy_matches_updated_dice_chain);
+    fn policy_matches_updated_dice_chain() {
+        let example = TestArtifacts::get_example();
+        assert!(
+            DicePolicy::from_dice_chain(&example.input_dice, &example.constraint_spec)
+                .unwrap()
+                .matches_dice_chain(&example.updated_input_dice)
+                .is_ok(),
+            "The updated dice chain did not match the original policy!"
+        );
+    }
+
+    test!(policy_mismatch_downgraded_dice_chain);
+    fn policy_mismatch_downgraded_dice_chain() {
+        let example = TestArtifacts::get_example();
+        assert!(
+            DicePolicy::from_dice_chain(&example.updated_input_dice, &example.constraint_spec)
+                .unwrap()
+                .matches_dice_chain(&example.input_dice)
+                .is_err(),
+            "The (downgraded) dice chain matched the policy constructed out of the 'updated'\
+            dice chain!!"
+        );
+    }
+
     test!(policy_dice_size_is_same);
     fn policy_dice_size_is_same() {
         // This is the number of certs in compos bcc (including the first ROT)
diff --git a/service_vm/client_vm_csr/Android.bp b/service_vm/client_vm_csr/Android.bp
new file mode 100644
index 0000000..8d738d8
--- /dev/null
+++ b/service_vm/client_vm_csr/Android.bp
@@ -0,0 +1,37 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+    name: "libclient_vm_csr_defaults",
+    crate_name: "client_vm_csr",
+    srcs: ["src/lib.rs"],
+    rustlibs: [
+        "libanyhow",
+        "libcoset",
+        "libdiced_open_dice",
+        "libopenssl",
+        "libservice_vm_comm",
+        "libzeroize",
+    ],
+}
+
+rust_library {
+    name: "libclient_vm_csr",
+    defaults: ["libclient_vm_csr_defaults"],
+    prefer_rlib: true,
+    apex_available: [
+        "com.android.virt",
+    ],
+}
+
+rust_test {
+    name: "libclient_vm_csr.test",
+    defaults: ["libclient_vm_csr_defaults"],
+    test_suites: ["general-tests"],
+    rustlibs: [
+        "libciborium",
+        "libdiced_sample_inputs",
+        "libhwtrust",
+    ],
+}
diff --git a/service_vm/client_vm_csr/TEST_MAPPING b/service_vm/client_vm_csr/TEST_MAPPING
new file mode 100644
index 0000000..5bc06c0
--- /dev/null
+++ b/service_vm/client_vm_csr/TEST_MAPPING
@@ -0,0 +1,9 @@
+// When adding or removing tests here, don't forget to amend _all_modules list in
+// wireless/android/busytown/ath_config/configs/prod/avf/tests.gcl
+{
+  "avf-presubmit" : [
+    {
+      "name" : "libclient_vm_csr.test"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/service_vm/client_vm_csr/src/lib.rs b/service_vm/client_vm_csr/src/lib.rs
new file mode 100644
index 0000000..512ecaf
--- /dev/null
+++ b/service_vm/client_vm_csr/src/lib.rs
@@ -0,0 +1,242 @@
+// 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.
+
+//! Generate the attestation key and CSR for client VM in the remote
+//! attestation.
+
+use anyhow::{anyhow, Context, Result};
+use coset::{
+    iana, CborSerializable, CoseKey, CoseKeyBuilder, CoseSign, CoseSignBuilder, CoseSignature,
+    CoseSignatureBuilder, HeaderBuilder,
+};
+use diced_open_dice::{derive_cdi_leaf_priv, sign, DiceArtifacts, PrivateKey};
+use openssl::{
+    bn::{BigNum, BigNumContext},
+    ec::{EcGroup, EcKey, EcKeyRef},
+    ecdsa::EcdsaSig,
+    nid::Nid,
+    pkey::Private,
+    sha::sha256,
+};
+use service_vm_comm::{Csr, CsrPayload};
+use zeroize::Zeroizing;
+
+/// Key parameters for the attestation key.
+///
+/// See service_vm/comm/client_vm_csr.cddl for more information about the attestation key.
+const ATTESTATION_KEY_NID: Nid = Nid::X9_62_PRIME256V1; // NIST P-256 curve
+const ATTESTATION_KEY_ALGO: iana::Algorithm = iana::Algorithm::ES256;
+const ATTESTATION_KEY_CURVE: iana::EllipticCurve = iana::EllipticCurve::P_256;
+const ATTESTATION_KEY_AFFINE_COORDINATE_SIZE: i32 = 32;
+
+/// Represents the output of generating the attestation key and CSR for the client VM.
+pub struct ClientVmAttestationData {
+    /// DER-encoded ECPrivateKey to be attested.
+    pub private_key: Zeroizing<Vec<u8>>,
+
+    /// CSR containing client VM information and the public key corresponding to the
+    /// private key to be attested.
+    pub csr: Csr,
+}
+
+/// Generates the attestation key and CSR including the public key to be attested for the
+/// client VM in remote attestation.
+pub fn generate_attestation_key_and_csr(
+    challenge: &[u8],
+    dice_artifacts: &dyn DiceArtifacts,
+) -> Result<ClientVmAttestationData> {
+    let group = EcGroup::from_curve_name(ATTESTATION_KEY_NID)?;
+    let attestation_key = EcKey::generate(&group)?;
+
+    let csr = build_csr(challenge, attestation_key.as_ref(), dice_artifacts)?;
+    let private_key = attestation_key.private_key_to_der()?;
+    Ok(ClientVmAttestationData { private_key: Zeroizing::new(private_key), csr })
+}
+
+fn build_csr(
+    challenge: &[u8],
+    attestation_key: &EcKeyRef<Private>,
+    dice_artifacts: &dyn DiceArtifacts,
+) -> Result<Csr> {
+    // Builds CSR Payload to be signed.
+    let public_key =
+        to_cose_public_key(attestation_key)?.to_vec().context("Failed to serialize public key")?;
+    let csr_payload = CsrPayload { public_key, challenge: challenge.to_vec() };
+    let csr_payload = csr_payload.into_cbor_vec()?;
+
+    // Builds signed CSR Payload.
+    let cdi_leaf_priv = derive_cdi_leaf_priv(dice_artifacts)?;
+    let signed_csr_payload = build_signed_data(csr_payload, &cdi_leaf_priv, attestation_key)?
+        .to_vec()
+        .context("Failed to serialize signed CSR payload")?;
+
+    // Builds CSR.
+    let dice_cert_chain = dice_artifacts.bcc().ok_or(anyhow!("bcc is none"))?.to_vec();
+    Ok(Csr { dice_cert_chain, signed_csr_payload })
+}
+
+fn build_signed_data(
+    payload: Vec<u8>,
+    cdi_leaf_priv: &PrivateKey,
+    attestation_key: &EcKeyRef<Private>,
+) -> Result<CoseSign> {
+    let cdi_leaf_sig_headers = build_signature_headers(iana::Algorithm::EdDSA);
+    let attestation_key_sig_headers = build_signature_headers(ATTESTATION_KEY_ALGO);
+    let aad = &[];
+    let signed_data = CoseSignBuilder::new()
+        .payload(payload)
+        .try_add_created_signature(cdi_leaf_sig_headers, aad, |message| {
+            sign(message, cdi_leaf_priv.as_array()).map(|v| v.to_vec())
+        })?
+        .try_add_created_signature(attestation_key_sig_headers, aad, |message| {
+            ecdsa_sign(message, attestation_key)
+        })?
+        .build();
+    Ok(signed_data)
+}
+
+/// Builds a signature with headers filled with the provided algorithm.
+/// The signature data will be filled later when building the signed data.
+fn build_signature_headers(alg: iana::Algorithm) -> CoseSignature {
+    let protected = HeaderBuilder::new().algorithm(alg).build();
+    CoseSignatureBuilder::new().protected(protected).build()
+}
+
+fn ecdsa_sign(message: &[u8], key: &EcKeyRef<Private>) -> Result<Vec<u8>> {
+    let digest = sha256(message);
+    // Passes the digest to `ECDSA_do_sign` as recommended in the spec:
+    // https://commondatastorage.googleapis.com/chromium-boringssl-docs/ecdsa.h.html#ECDSA_do_sign
+    let sig = EcdsaSig::sign::<Private>(&digest, key)?;
+    Ok(sig.to_der()?)
+}
+
+fn get_affine_coordinates(key: &EcKeyRef<Private>) -> Result<(Vec<u8>, Vec<u8>)> {
+    let mut ctx = BigNumContext::new()?;
+    let mut x = BigNum::new()?;
+    let mut y = BigNum::new()?;
+    key.public_key().affine_coordinates_gfp(key.group(), &mut x, &mut y, &mut ctx)?;
+    let x = x.to_vec_padded(ATTESTATION_KEY_AFFINE_COORDINATE_SIZE)?;
+    let y = y.to_vec_padded(ATTESTATION_KEY_AFFINE_COORDINATE_SIZE)?;
+    Ok((x, y))
+}
+
+fn to_cose_public_key(key: &EcKeyRef<Private>) -> Result<CoseKey> {
+    let (x, y) = get_affine_coordinates(key)?;
+    Ok(CoseKeyBuilder::new_ec2_pub_key(ATTESTATION_KEY_CURVE, x, y)
+        .algorithm(ATTESTATION_KEY_ALGO)
+        .build())
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use anyhow::bail;
+    use ciborium::Value;
+    use coset::{iana::EnumI64, Label};
+    use hwtrust::{dice, session::Session};
+    use openssl::pkey::Public;
+
+    /// The following data was generated randomly with urandom.
+    const CHALLENGE: [u8; 16] = [
+        0xb3, 0x66, 0xfa, 0x72, 0x92, 0x32, 0x2c, 0xd4, 0x99, 0xcb, 0x00, 0x1f, 0x0e, 0xe0, 0xc7,
+        0x41,
+    ];
+
+    #[test]
+    fn csr_and_private_key_have_correct_format() -> Result<()> {
+        let dice_artifacts = diced_sample_inputs::make_sample_bcc_and_cdis()?;
+
+        let ClientVmAttestationData { private_key, csr } =
+            generate_attestation_key_and_csr(&CHALLENGE, &dice_artifacts)?;
+        let ec_private_key = EcKey::private_key_from_der(&private_key)?;
+        let cose_sign = CoseSign::from_slice(&csr.signed_csr_payload).unwrap();
+        let aad = &[];
+
+        // Checks CSR payload.
+        let csr_payload =
+            cose_sign.payload.as_ref().and_then(|v| CsrPayload::from_cbor_slice(v).ok()).unwrap();
+        let public_key = to_cose_public_key(&ec_private_key)?.to_vec().unwrap();
+        let expected_csr_payload = CsrPayload { challenge: CHALLENGE.to_vec(), public_key };
+        assert_eq!(expected_csr_payload, csr_payload);
+
+        // Checks the first signature is signed with CDI_Leaf_Priv.
+        let session = Session::default();
+        let chain = dice::Chain::from_cbor(&session, &csr.dice_cert_chain)?;
+        let public_key = chain.leaf().subject_public_key();
+        cose_sign
+            .verify_signature(0, aad, |signature, message| public_key.verify(signature, message))?;
+
+        // Checks the second signature is signed with attestation key.
+        let attestation_public_key = CoseKey::from_slice(&csr_payload.public_key).unwrap();
+        let ec_public_key = to_ec_public_key(&attestation_public_key)?;
+        cose_sign.verify_signature(1, aad, |signature, message| {
+            ecdsa_verify(signature, message, &ec_public_key)
+        })?;
+
+        // Verifies that private key and the public key form a valid key pair.
+        let message = b"test message";
+        let signature = ecdsa_sign(message, &ec_private_key)?;
+        ecdsa_verify(&signature, message, &ec_public_key)?;
+
+        Ok(())
+    }
+
+    fn ecdsa_verify(
+        signature: &[u8],
+        message: &[u8],
+        ec_public_key: &EcKeyRef<Public>,
+    ) -> Result<()> {
+        let sig = EcdsaSig::from_der(signature)?;
+        let digest = sha256(message);
+        if sig.verify(&digest, ec_public_key)? {
+            Ok(())
+        } else {
+            bail!("Signature does not match")
+        }
+    }
+
+    fn to_ec_public_key(cose_key: &CoseKey) -> Result<EcKey<Public>> {
+        check_ec_key_params(cose_key)?;
+        let group = EcGroup::from_curve_name(ATTESTATION_KEY_NID)?;
+        let x = get_label_value_as_bignum(cose_key, Label::Int(iana::Ec2KeyParameter::X.to_i64()))?;
+        let y = get_label_value_as_bignum(cose_key, Label::Int(iana::Ec2KeyParameter::Y.to_i64()))?;
+        let key = EcKey::from_public_key_affine_coordinates(&group, &x, &y)?;
+        key.check_key()?;
+        Ok(key)
+    }
+
+    fn check_ec_key_params(cose_key: &CoseKey) -> Result<()> {
+        assert_eq!(coset::KeyType::Assigned(iana::KeyType::EC2), cose_key.kty);
+        assert_eq!(Some(coset::Algorithm::Assigned(ATTESTATION_KEY_ALGO)), cose_key.alg);
+        let crv = get_label_value(cose_key, Label::Int(iana::Ec2KeyParameter::Crv.to_i64()))?;
+        assert_eq!(&Value::from(ATTESTATION_KEY_CURVE.to_i64()), crv);
+        Ok(())
+    }
+
+    fn get_label_value_as_bignum(key: &CoseKey, label: Label) -> Result<BigNum> {
+        get_label_value(key, label)?
+            .as_bytes()
+            .map(|v| BigNum::from_slice(&v[..]).unwrap())
+            .ok_or_else(|| anyhow!("Value not a bstr."))
+    }
+
+    fn get_label_value(key: &CoseKey, label: Label) -> Result<&Value> {
+        Ok(&key
+            .params
+            .iter()
+            .find(|(k, _)| k == &label)
+            .ok_or_else(|| anyhow!("Label {:?} not found", label))?
+            .1)
+    }
+}
diff --git a/service_vm/requests/src/client_vm.rs b/service_vm/requests/src/client_vm.rs
index 1081f3a..74c26d3 100644
--- a/service_vm/requests/src/client_vm.rs
+++ b/service_vm/requests/src/client_vm.rs
@@ -17,10 +17,12 @@
 
 use crate::keyblob::decrypt_private_key;
 use alloc::vec::Vec;
+use bssl_avf::EcKey;
 use core::result;
+use coset::{CborSerializable, CoseSign};
 use diced_open_dice::DiceArtifacts;
 use log::error;
-use service_vm_comm::{ClientVmAttestationParams, RequestProcessingError};
+use service_vm_comm::{ClientVmAttestationParams, Csr, RequestProcessingError};
 
 type Result<T> = result::Result<T, RequestProcessingError>;
 
@@ -28,19 +30,22 @@
     params: ClientVmAttestationParams,
     dice_artifacts: &dyn DiceArtifacts,
 ) -> Result<Vec<u8>> {
-    // TODO(b/309440321): Verify the signatures in the csr.
+    let csr = Csr::from_cbor_slice(&params.csr)?;
+    let _cose_sign = CoseSign::from_slice(&csr.signed_csr_payload)?;
+    // TODO(b/309440321): Verify the signatures in the `_cose_sign`.
 
-    // TODO(b/278717513): Compare client VM's DICE chain up to pvmfw cert with
-    // RKP VM's DICE chain.
+    // TODO(b/278717513): Compare client VM's DICE chain in the `csr` up to pvmfw
+    // cert with RKP VM's DICE chain.
 
-    let _private_key =
+    let private_key =
         decrypt_private_key(&params.remotely_provisioned_key_blob, dice_artifacts.cdi_seal())
             .map_err(|e| {
                 error!("Failed to decrypt the remotely provisioned key blob: {e}");
                 RequestProcessingError::FailedToDecryptKeyBlob
             })?;
+    let _ec_private_key = EcKey::from_ec_private_key(private_key.as_slice())?;
 
     // TODO(b/309441500): Build a new certificate signed with the remotely provisioned
-    // private key.
+    // `_private_key`.
     Err(RequestProcessingError::OperationUnimplemented)
 }
diff --git a/service_vm/requests/src/rkp.rs b/service_vm/requests/src/rkp.rs
index 8d7d771..c2c13b3 100644
--- a/service_vm/requests/src/rkp.rs
+++ b/service_vm/requests/src/rkp.rs
@@ -48,7 +48,7 @@
 
     let maced_public_key = build_maced_public_key(ec_key.cose_public_key()?, hmac_key.as_ref())?;
     let key_blob =
-        EncryptedKeyBlob::new(ec_key.private_key()?.as_slice(), dice_artifacts.cdi_seal())?;
+        EncryptedKeyBlob::new(ec_key.ec_private_key()?.as_slice(), dice_artifacts.cdi_seal())?;
 
     let key_pair =
         EcdsaP256KeyPair { maced_public_key, key_blob: cbor_util::serialize(&key_blob)? };
diff --git a/tests/testapk/Android.bp b/tests/testapk/Android.bp
index 6e8216f..6a30b1c 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -58,6 +58,7 @@
     data: [
         ":MicrodroidVmShareApp",
         ":test_microdroid_vendor_image",
+        ":test_microdroid_vendor_image_unsigned",
     ],
 }
 
diff --git a/tests/testapk/AndroidTest.xml b/tests/testapk/AndroidTest.xml
index e72a2e3..8a4c367 100644
--- a/tests/testapk/AndroidTest.xml
+++ b/tests/testapk/AndroidTest.xml
@@ -30,6 +30,7 @@
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
         <option name="cleanup" value="true" />
         <option name="push" value="test_microdroid_vendor_image.img->/data/local/tmp/cts/microdroid/test_microdroid_vendor_image.img" />
+        <option name="push" value="test_microdroid_vendor_image_unsigned.img->/data/local/tmp/cts/microdroid/test_microdroid_vendor_image_unsigned.img" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="com.android.microdroid.test" />
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
index d9d9cb9..c6291e4 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -2160,6 +2160,28 @@
     }
 
     @Test
+    public void creationFailsWithUnsignedVendorPartition() throws Exception {
+        assumeSupportedDevice();
+        assumeFeatureEnabled(VirtualMachineManager.FEATURE_VENDOR_MODULES);
+
+        grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
+
+        File unsignedVendorDiskImage =
+                new File(
+                        "/data/local/tmp/cts/microdroid/test_microdroid_vendor_image_unsigned.img");
+        VirtualMachineConfig config =
+                newVmConfigBuilder()
+                        .setPayloadBinaryName("MicrodroidTestNativeLib.so")
+                        .setVendorDiskImage(unsignedVendorDiskImage)
+                        .setDebugLevel(DEBUG_LEVEL_FULL)
+                        .build();
+
+        VirtualMachine vm = forceCreateNewVirtualMachine("test_boot_with_unsigned_vendor", config);
+        assertThrowsVmExceptionContaining(
+                () -> vm.run(), "Failed to get vbmeta from microdroid-vendor.img");
+    }
+
+    @Test
     public void systemPartitionMountFlags() throws Exception {
         assumeSupportedDevice();
 
diff --git a/tests/vendor_images/Android.bp b/tests/vendor_images/Android.bp
index e5f863a..26dbc01 100644
--- a/tests/vendor_images/Android.bp
+++ b/tests/vendor_images/Android.bp
@@ -10,8 +10,16 @@
 
 android_filesystem {
     name: "test_microdroid_vendor_image",
+    partition_name: "microdroid-vendor",
     type: "ext4",
     file_contexts: ":microdroid_vendor_file_contexts.gen",
     use_avb: true,
     avb_private_key: ":vendor_sign_key",
 }
+
+android_filesystem {
+    name: "test_microdroid_vendor_image_unsigned",
+    partition_name: "microdroid-vendor",
+    type: "ext4",
+    file_contexts: ":microdroid_vendor_file_contexts.gen",
+}
diff --git a/virtualizationmanager/Android.bp b/virtualizationmanager/Android.bp
index 12d8724..33897b2 100644
--- a/virtualizationmanager/Android.bp
+++ b/virtualizationmanager/Android.bp
@@ -54,6 +54,7 @@
         "libshared_child",
         "libstatslog_virtualization_rust",
         "libtombstoned_client_rust",
+        "libvbmeta_rust",
         "libvm_control",
         "libvmconfig",
         "libzip",
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index bf00852..6ae3bbd 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -62,6 +62,7 @@
 };
 use disk::QcowFile;
 use lazy_static::lazy_static;
+use libfdt::Fdt;
 use log::{debug, error, info, warn};
 use microdroid_payload_config::{OsConfig, Task, TaskType, VmPayloadConfig};
 use nix::unistd::pipe;
@@ -70,7 +71,7 @@
 use semver::VersionReq;
 use std::collections::HashSet;
 use std::convert::TryInto;
-use std::ffi::CStr;
+use std::ffi::{CStr, CString};
 use std::fs::{canonicalize, read_dir, remove_file, File, OpenOptions};
 use std::io::{BufRead, BufReader, Error, ErrorKind, Write};
 use std::num::{NonZeroU16, NonZeroU32};
@@ -78,6 +79,7 @@
 use std::os::unix::raw::pid_t;
 use std::path::{Path, PathBuf};
 use std::sync::{Arc, Mutex, Weak};
+use vbmeta::VbMetaImage;
 use vmconfig::VmConfig;
 use vsock::VsockStream;
 use zip::ZipArchive;
@@ -99,8 +101,13 @@
 
 const MICRODROID_OS_NAME: &str = "microdroid";
 
+const MICRODROID_GKI_OS_NAME: &str = "microdroid_gki";
+
 const UNFORMATTED_STORAGE_MAGIC: &str = "UNFORMATTED-STORAGE";
 
+/// Roughly estimated sufficient size for storing vendor public key into DTBO.
+const EMPTY_VENDOR_DT_OVERLAY_BUF_SIZE: usize = 10000;
+
 /// crosvm requires all partitions to be a multiple of 4KiB.
 const PARTITION_GRANULARITY_BYTES: u64 = 4096;
 
@@ -163,23 +170,23 @@
 }
 
 impl Interface for VirtualizationService {
-    fn dump(&self, mut file: &File, _args: &[&CStr]) -> Result<(), StatusCode> {
+    fn dump(&self, writer: &mut dyn Write, _args: &[&CStr]) -> Result<(), StatusCode> {
         check_permission("android.permission.DUMP").or(Err(StatusCode::PERMISSION_DENIED))?;
         let state = &mut *self.state.lock().unwrap();
         let vms = state.vms();
-        writeln!(file, "Running {0} VMs:", vms.len()).or(Err(StatusCode::UNKNOWN_ERROR))?;
+        writeln!(writer, "Running {0} VMs:", vms.len()).or(Err(StatusCode::UNKNOWN_ERROR))?;
         for vm in vms {
-            writeln!(file, "VM CID: {}", vm.cid).or(Err(StatusCode::UNKNOWN_ERROR))?;
-            writeln!(file, "\tState: {:?}", vm.vm_state.lock().unwrap())
+            writeln!(writer, "VM CID: {}", vm.cid).or(Err(StatusCode::UNKNOWN_ERROR))?;
+            writeln!(writer, "\tState: {:?}", vm.vm_state.lock().unwrap())
                 .or(Err(StatusCode::UNKNOWN_ERROR))?;
-            writeln!(file, "\tPayload state {:?}", vm.payload_state())
+            writeln!(writer, "\tPayload state {:?}", vm.payload_state())
                 .or(Err(StatusCode::UNKNOWN_ERROR))?;
-            writeln!(file, "\tProtected: {}", vm.protected).or(Err(StatusCode::UNKNOWN_ERROR))?;
-            writeln!(file, "\ttemporary_directory: {}", vm.temporary_directory.to_string_lossy())
+            writeln!(writer, "\tProtected: {}", vm.protected).or(Err(StatusCode::UNKNOWN_ERROR))?;
+            writeln!(writer, "\ttemporary_directory: {}", vm.temporary_directory.to_string_lossy())
                 .or(Err(StatusCode::UNKNOWN_ERROR))?;
-            writeln!(file, "\trequester_uid: {}", vm.requester_uid)
+            writeln!(writer, "\trequester_uid: {}", vm.requester_uid)
                 .or(Err(StatusCode::UNKNOWN_ERROR))?;
-            writeln!(file, "\trequester_debug_pid: {}", vm.requester_debug_pid)
+            writeln!(writer, "\trequester_debug_pid: {}", vm.requester_debug_pid)
                 .or(Err(StatusCode::UNKNOWN_ERROR))?;
         }
         Ok(())
@@ -361,6 +368,22 @@
             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 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")
+                .or_service_specific_exception(-1)?;
+            let file = File::open(dtbo_for_vendor_image)
+                .context("Failed to open dtbo_vendor")
+                .or_service_specific_exception(-1)?;
+            Some(file)
+        } else {
+            None
+        };
+
         let debug_level = match config {
             VirtualMachineConfig::AppConfig(config) => config.debugLevel,
             _ => DebugLevel::NONE,
@@ -506,6 +529,7 @@
             detect_hangup: is_app_config,
             gdb_port,
             vfio_devices,
+            dtbo_vendor,
         };
         let instance = Arc::new(
             VmInstance::new(
@@ -524,6 +548,68 @@
     }
 }
 
+fn extract_vendor_public_key(config: &VirtualMachineConfig) -> Result<Option<Vec<u8>>> {
+    let VirtualMachineConfig::AppConfig(config) = config else {
+        return Ok(None);
+    };
+    let Some(custom_config) = &config.customConfig else {
+        return Ok(None);
+    };
+    let Some(file) = custom_config.vendorImage.as_ref() else {
+        return Ok(None);
+    };
+
+    let file = clone_file(file)?;
+    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))
+}
+
+fn create_dtbo_for_vendor_image(vendor_public_key: &[u8], dtbo: &PathBuf) -> Result<()> {
+    if dtbo.exists() {
+        return Err(anyhow!("DTBO file already exists"));
+    }
+
+    let mut buf = vec![0; EMPTY_VENDOR_DT_OVERLAY_BUF_SIZE];
+    let fdt = Fdt::create_empty_tree(buf.as_mut_slice())
+        .map_err(|e| anyhow!("Failed to create FDT: {:?}", e))?;
+    let mut root = fdt.root_mut().map_err(|e| anyhow!("Failed to get root node: {:?}", e))?;
+
+    let fragment_node_name = CString::new("fragment@0")?;
+    let mut fragment_node = root
+        .add_subnode(fragment_node_name.as_c_str())
+        .map_err(|e| anyhow!("Failed to create fragment node: {:?}", e))?;
+    let target_path_prop_name = CString::new("target-path")?;
+    let target_path = CString::new("/")?;
+    fragment_node
+        .setprop(target_path_prop_name.as_c_str(), target_path.to_bytes_with_nul())
+        .map_err(|e| anyhow!("Failed to set target-path: {:?}", e))?;
+    let overlay_node_name = CString::new("__overlay__")?;
+    let mut overlay_node = fragment_node
+        .add_subnode(overlay_node_name.as_c_str())
+        .map_err(|e| anyhow!("Failed to create overlay node: {:?}", e))?;
+
+    let avf_node_name = CString::new("avf")?;
+    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")?;
+    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))?;
+
+    fdt.pack().map_err(|e| anyhow!("Failed to pack fdt: {:?}", e))?;
+    let mut file = File::create(dtbo)?;
+    file.write_all(fdt.as_slice())?;
+    Ok(file.flush()?)
+}
+
 fn write_zero_filler(zero_filler_path: &Path) -> Result<()> {
     let file = OpenOptions::new()
         .create_new(true)
@@ -610,6 +696,16 @@
     }
 }
 
+fn is_valid_os(os_name: &str) -> bool {
+    if os_name == MICRODROID_OS_NAME {
+        return true;
+    }
+    if cfg!(vendor_modules) && os_name == MICRODROID_GKI_OS_NAME {
+        return true;
+    }
+    false
+}
+
 fn load_app_config(
     config: &VirtualMachineAppConfig,
     debug_config: &DebugConfig,
@@ -633,9 +729,9 @@
         Payload::PayloadConfig(payload_config) => create_vm_payload_config(payload_config)?,
     };
 
-    // For now, the only supported OS is Microdroid
+    // For now, the only supported OS is Microdroid and Microdroid GKI
     let os_name = vm_payload_config.os.name.as_str();
-    if os_name != MICRODROID_OS_NAME {
+    if !is_valid_os(os_name) {
         bail!("Unknown OS \"{}\"", os_name);
     }
 
@@ -669,7 +765,7 @@
     vm_config.cpuTopology = config.cpuTopology;
 
     // Microdroid takes additional init ramdisk & (optionally) storage image
-    add_microdroid_system_images(config, instance_file, storage_image, &mut vm_config)?;
+    add_microdroid_system_images(config, instance_file, storage_image, os_name, &mut vm_config)?;
 
     // Include Microdroid payload disk (contains apks, idsigs) in vm config
     add_microdroid_payload_images(
@@ -704,8 +800,9 @@
     }
 
     let task = Task { type_: TaskType::MicrodroidLauncher, command: payload_binary_name.clone() };
+    let name = payload_config.osName.clone();
     Ok(VmPayloadConfig {
-        os: OsConfig { name: MICRODROID_OS_NAME.to_owned() },
+        os: OsConfig { name },
         task: Some(task),
         apexes: vec![],
         extra_apks: vec![],
@@ -787,7 +884,7 @@
 /// Check that a file SELinux label is acceptable.
 ///
 /// We only want to allow code in a VM to be sourced from places that apps, and the
-/// system, do not have write access to.
+/// system or vendor, do not have write access to.
 ///
 /// Note that sepolicy must also grant read access for these types to both virtualization
 /// service and crosvm.
@@ -801,6 +898,7 @@
         | "staging_data_file" // updated/staged APEX images
         | "system_file" // immutable dm-verity protected partition
         | "virtualizationservice_data_file" // files created by VS / VirtMgr
+        | "vendor_microdroid_file" // immutable dm-verity protected partition (/vendor/etc/avf/microdroid/.*)
          => Ok(()),
         _ => bail!("Label {} is not allowed", context),
     }
@@ -1111,10 +1209,24 @@
     Ok(())
 }
 
+fn check_no_devices(config: &VirtualMachineConfig) -> binder::Result<()> {
+    let VirtualMachineConfig::AppConfig(config) = config else { return Ok(()) };
+    if let Some(custom_config) = &config.customConfig {
+        if !custom_config.devices.is_empty() {
+            return Err(anyhow!("device assignment feature is disabled"))
+                .or_binder_exception(ExceptionCode::UNSUPPORTED_OPERATION);
+        }
+    }
+    Ok(())
+}
+
 fn check_config_features(config: &VirtualMachineConfig) -> binder::Result<()> {
     if !cfg!(vendor_modules) {
         check_no_vendor_modules(config)?;
     }
+    if !cfg!(device_assignment) {
+        check_no_devices(config)?;
+    }
     Ok(())
 }
 
@@ -1248,7 +1360,7 @@
     }
 
     fn requestAttestation(&self, csr: &[u8]) -> binder::Result<Vec<Certificate>> {
-        GLOBAL_SERVICE.requestAttestation(csr)
+        GLOBAL_SERVICE.requestAttestation(csr, get_calling_uid() as i32)
     }
 }
 
@@ -1376,4 +1488,58 @@
         append_kernel_param("bar=42", &mut vm_config);
         assert_eq!(vm_config.params, Some("foo=5 bar=42".to_owned()))
     }
+
+    #[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 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)?;
+
+        let data = std::fs::read(dtbo_path)?;
+        let fdt = Fdt::from_slice(&data).unwrap();
+
+        let fragment_node_path = CString::new("/fragment@0")?;
+        let fragment_node = fdt.node(fragment_node_path.as_c_str()).unwrap();
+        let Some(fragment_node) = fragment_node else {
+            bail!("fragment_node shouldn't be None.");
+        };
+        let target_path_prop_name = CString::new("target-path")?;
+        let target_path_from_dtbo =
+            fragment_node.getprop(target_path_prop_name.as_c_str()).unwrap();
+        let target_path_expected = CString::new("/")?;
+        assert_eq!(target_path_from_dtbo, Some(target_path_expected.to_bytes_with_nul()));
+
+        let avf_node_path = CString::new("/fragment@0/__overlay__/avf")?;
+        let avf_node = fdt.node(avf_node_path.as_c_str()).unwrap();
+        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));
+
+        tmp_dir.close()?;
+        Ok(())
+    }
+
+    #[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 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)?;
+
+        let ret_second_trial = create_dtbo_for_vendor_image(vendor_public_key, &dtbo_path);
+        assert!(ret_second_trial.is_err(), "should fail");
+
+        tmp_dir.close()?;
+        Ok(())
+    }
 }
diff --git a/virtualizationmanager/src/crosvm.rs b/virtualizationmanager/src/crosvm.rs
index bb6066f..9a50776 100644
--- a/virtualizationmanager/src/crosvm.rs
+++ b/virtualizationmanager/src/crosvm.rs
@@ -116,6 +116,7 @@
     pub detect_hangup: bool,
     pub gdb_port: Option<NonZeroU16>,
     pub vfio_devices: Vec<VfioDevice>,
+    pub dtbo_vendor: Option<File>,
 }
 
 /// A disk image to pass to crosvm for a VM.
@@ -716,7 +717,7 @@
     }
 
     if let Some(p) = path.to_str() {
-        Ok(format!("--vfio={p},iommu=viommu,dt-symbol={0}", device.dtbo_label))
+        Ok(format!("--vfio={p},iommu=pkvm-iommu,dt-symbol={0}", device.dtbo_label))
     } else {
         bail!("invalid path {path:?}");
     }
@@ -886,6 +887,8 @@
         .arg("--socket")
         .arg(add_preserved_fd(&mut preserved_fds, &control_server_socket.as_raw_descriptor()));
 
+    // TODO(b/285855436): Pass dtbo_vendor after --device-tree-overlay crosvm option is supported.
+
     append_platform_devices(&mut command, &config)?;
 
     debug!("Preserving FDs {:?}", preserved_fds);
diff --git a/virtualizationmanager/src/payload.rs b/virtualizationmanager/src/payload.rs
index 3bfad33..c19c103 100644
--- a/virtualizationmanager/src/payload.rs
+++ b/virtualizationmanager/src/payload.rs
@@ -425,6 +425,7 @@
     config: &VirtualMachineAppConfig,
     instance_file: File,
     storage_image: Option<File>,
+    os_name: &str,
     vm_config: &mut VirtualMachineRawConfig,
 ) -> Result<()> {
     let debug_suffix = match config.debugLevel {
@@ -432,7 +433,7 @@
         DebugLevel::FULL => "debuggable",
         _ => return Err(anyhow!("unsupported debug level: {:?}", config.debugLevel)),
     };
-    let initrd = format!("/apex/com.android.virt/etc/microdroid_initrd_{}.img", debug_suffix);
+    let initrd = format!("/apex/com.android.virt/etc/{os_name}_initrd_{debug_suffix}.img");
     vm_config.initrd = Some(open_parcel_file(Path::new(&initrd), false)?);
 
     let mut writable_partitions = vec![Partition {
diff --git a/virtualizationservice/Android.bp b/virtualizationservice/Android.bp
index c00445d..bef7dd0 100644
--- a/virtualizationservice/Android.bp
+++ b/virtualizationservice/Android.bp
@@ -34,6 +34,7 @@
         "liblibc",
         "liblog_rust",
         "libnix",
+        "librkpd_client",
         "librustutils",
         "libvmclient",
         "libstatslog_virtualization_rust",
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachinePayloadConfig.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachinePayloadConfig.aidl
index 55c2f5d..f3f54f3 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachinePayloadConfig.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachinePayloadConfig.aidl
@@ -23,4 +23,9 @@
      * function invoked.
      */
     @utf8InCpp String payloadBinaryName;
+
+    /**
+     * Name of the OS to run the payload. Currently "microdroid" and "microdroid_gki" is supported.
+     */
+    @utf8InCpp String osName = "microdroid";
 }
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
index 2592135..c384a6f 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
@@ -60,10 +60,14 @@
      * Requests a certificate chain for the provided certificate signing request (CSR).
      *
      * @param csr The certificate signing request.
+     * @param requesterUid The UID of the app that requests remote attestation. The client VM to be
+     *                     attested is owned by this app.
+     *                     The uniqueness of the UID ensures that no two VMs owned by different apps
+     *                     are able to correlate keys.
      * @return A sequence of DER-encoded X.509 certificates that make up the attestation
      *         key's certificate chain. The attestation key is provided in the CSR.
      */
-    Certificate[] requestAttestation(in byte[] csr);
+    Certificate[] requestAttestation(in byte[] csr, int requesterUid);
 
     /**
      * Get a list of assignable devices.
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 2be2b19..938225e 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -14,7 +14,7 @@
 
 //! Implementation of the AIDL interface of the VirtualizationService.
 
-use crate::{get_calling_pid, get_calling_uid};
+use crate::{get_calling_pid, get_calling_uid, REMOTELY_PROVISIONED_COMPONENT_SERVICE_NAME};
 use crate::atom::{forward_vm_booted_atom, forward_vm_creation_atom, forward_vm_exited_atom};
 use crate::rkpvm::request_attestation;
 use android_os_permissions_aidl::aidl::android::os::IPermissionController;
@@ -39,6 +39,7 @@
 use binder::{self, wait_for_interface, BinderFeatures, ExceptionCode, Interface, LazyServiceGuard, Status, Strong, IntoBinderResult};
 use libc::VMADDR_CID_HOST;
 use log::{error, info, warn};
+use rkpd_client::get_rkpd_attestation_key;
 use rustutils::system_properties;
 use serde::Deserialize;
 use std::collections::{HashMap, HashSet};
@@ -159,14 +160,31 @@
         Ok(cids)
     }
 
-    fn requestAttestation(&self, csr: &[u8]) -> binder::Result<Vec<Certificate>> {
+    fn requestAttestation(
+        &self,
+        csr: &[u8],
+        requester_uid: i32,
+    ) -> binder::Result<Vec<Certificate>> {
         check_manage_access()?;
         info!("Received csr. Requestting attestation...");
         if cfg!(remote_attestation) {
-            request_attestation(csr)
+            let attestation_key = get_rkpd_attestation_key(
+                REMOTELY_PROVISIONED_COMPONENT_SERVICE_NAME,
+                requester_uid as u32,
+            )
+            .context("Failed to retrieve the remotely provisioned keys")
+            .with_log()
+            .or_service_specific_exception(-1)?;
+            let certificate = request_attestation(csr, &attestation_key.keyBlob)
                 .context("Failed to request attestation")
                 .with_log()
-                .or_service_specific_exception(-1)
+                .or_service_specific_exception(-1)?;
+            // TODO(b/309780089): Parse the remotely provisioned certificate chain into
+            // individual certificates.
+            let mut certificate_chain =
+                vec![Certificate { encodedCertificate: attestation_key.encodedCertChain }];
+            certificate_chain.push(Certificate { encodedCertificate: certificate });
+            Ok(certificate_chain)
         } else {
             Err(Status::new_exception_str(
                 ExceptionCode::UNSUPPORTED_OPERATION,
diff --git a/virtualizationservice/src/main.rs b/virtualizationservice/src/main.rs
index fd668bc..d80ddd4 100644
--- a/virtualizationservice/src/main.rs
+++ b/virtualizationservice/src/main.rs
@@ -33,7 +33,7 @@
 use std::path::Path;
 
 const LOG_TAG: &str = "VirtualizationService";
-const _REMOTELY_PROVISIONED_COMPONENT_SERVICE_NAME: &str =
+pub(crate) const REMOTELY_PROVISIONED_COMPONENT_SERVICE_NAME: &str =
     "android.system.virtualization.IRemotelyProvisionedComponent/avf";
 
 fn get_calling_pid() -> pid_t {
diff --git a/virtualizationservice/src/rkpvm.rs b/virtualizationservice/src/rkpvm.rs
index 8f1de6b..5087120 100644
--- a/virtualizationservice/src/rkpvm.rs
+++ b/virtualizationservice/src/rkpvm.rs
@@ -17,23 +17,25 @@
 //! serves as a trusted platform to attest a client VM.
 
 use android_hardware_security_rkp::aidl::android::hardware::security::keymint::MacedPublicKey::MacedPublicKey;
-use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::Certificate::Certificate;
 use anyhow::{bail, Context, Result};
-use service_vm_comm::{GenerateCertificateRequestParams, Request, Response};
+use service_vm_comm::{
+    ClientVmAttestationParams, GenerateCertificateRequestParams, Request, Response,
+};
 use service_vm_manager::ServiceVm;
 
-pub(crate) fn request_attestation(csr: &[u8]) -> Result<Vec<Certificate>> {
+pub(crate) fn request_attestation(
+    csr: &[u8],
+    remotely_provisioned_keyblob: &[u8],
+) -> Result<Vec<u8>> {
     let mut vm = ServiceVm::start()?;
 
-    // TODO(b/271275206): Send the correct request type with client VM's
-    // information to be attested.
-    let request = Request::Reverse(csr.to_vec());
+    let params = ClientVmAttestationParams {
+        csr: csr.to_vec(),
+        remotely_provisioned_key_blob: remotely_provisioned_keyblob.to_vec(),
+    };
+    let request = Request::RequestClientVmAttestation(params);
     match vm.process_request(request).context("Failed to process request")? {
-        // TODO(b/271275206): Adjust the response type.
-        Response::Reverse(cert) => {
-            let cert = Certificate { encodedCertificate: cert };
-            Ok(vec![cert])
-        }
+        Response::RequestClientVmAttestation(cert) => Ok(cert),
         _ => bail!("Incorrect response type"),
     }
 }
diff --git a/vm/src/main.rs b/vm/src/main.rs
index 0af9791..87278bc 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -97,33 +97,24 @@
     #[arg(long)]
     storage_size: Option<u64>,
 
-    /// Path to custom kernel image to use when booting Microdroid.
-    #[cfg(vendor_modules)]
-    #[arg(long)]
-    kernel: Option<PathBuf>,
-
     /// Path to disk image containing vendor-specific modules.
     #[cfg(vendor_modules)]
     #[arg(long)]
     vendor: Option<PathBuf>,
 
     /// SysFS nodes of devices to assign to VM
+    #[cfg(device_assignment)]
     #[arg(long)]
     devices: Vec<PathBuf>,
+
+    /// If set, use GKI instead of microdroid kernel
+    #[cfg(vendor_modules)]
+    #[arg(long)]
+    gki: bool,
 }
 
 impl MicrodroidConfig {
     #[cfg(vendor_modules)]
-    fn kernel(&self) -> &Option<PathBuf> {
-        &self.kernel
-    }
-
-    #[cfg(not(vendor_modules))]
-    fn kernel(&self) -> Option<PathBuf> {
-        None
-    }
-
-    #[cfg(vendor_modules)]
     fn vendor(&self) -> &Option<PathBuf> {
         &self.vendor
     }
@@ -132,6 +123,26 @@
     fn vendor(&self) -> Option<PathBuf> {
         None
     }
+
+    #[cfg(vendor_modules)]
+    fn gki(&self) -> bool {
+        self.gki
+    }
+
+    #[cfg(not(vendor_modules))]
+    fn gki(&self) -> bool {
+        false
+    }
+
+    #[cfg(device_assignment)]
+    fn devices(&self) -> &Vec<PathBuf> {
+        &self.devices
+    }
+
+    #[cfg(not(device_assignment))]
+    fn devices(&self) -> Vec<PathBuf> {
+        Vec::new()
+    }
 }
 
 #[derive(Args)]
diff --git a/vm/src/run.rs b/vm/src/run.rs
index 1ba9dec..44ba9af 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -98,9 +98,6 @@
         None
     };
 
-    let kernel =
-        config.microdroid.kernel().as_ref().map(|p| open_parcel_file(p, false)).transpose()?;
-
     let vendor =
         config.microdroid.vendor().as_ref().map(|p| open_parcel_file(p, false)).transpose()?;
 
@@ -114,8 +111,11 @@
         }
         Payload::ConfigPath(config_path)
     } else if let Some(payload_binary_name) = config.payload_binary_name {
+        let os_name =
+            if config.microdroid.gki() { "microdroid_gki" } else { "microdroid" }.to_owned();
         Payload::PayloadConfig(VirtualMachinePayloadConfig {
             payloadBinaryName: payload_binary_name,
+            osName: os_name,
         })
     } else {
         bail!("Either --config-path or --payload-binary-name must be defined")
@@ -124,13 +124,13 @@
     let payload_config_str = format!("{:?}!{:?}", config.apk, payload);
 
     let custom_config = CustomConfig {
-        customKernelImage: kernel,
+        customKernelImage: None,
         gdbPort: config.debug.gdb.map(u16::from).unwrap_or(0) as i32, // 0 means no gdb
         taskProfiles: config.common.task_profiles,
         vendorImage: vendor,
         devices: config
             .microdroid
-            .devices
+            .devices()
             .iter()
             .map(|x| {
                 x.to_str().map(String::from).ok_or(anyhow!("Failed to convert {x:?} to String"))
diff --git a/vm_payload/include/vm_payload.h b/vm_payload/include/vm_payload.h
index 951b57f..78cd80d 100644
--- a/vm_payload/include/vm_payload.h
+++ b/vm_payload/include/vm_payload.h
@@ -19,7 +19,6 @@
 #include <stdbool.h>
 #include <stddef.h>
 #include <stdint.h>
-#include <stdnoreturn.h>
 #include <sys/cdefs.h>
 
 #include "vm_main.h"
@@ -81,9 +80,9 @@
  * callback will be called at most once.
  * \param param parameter to be passed to the `on_ready` callback.
  */
-noreturn void AVmPayload_runVsockRpcServer(AIBinder* _Nonnull service, uint32_t port,
-                                           void (*_Nullable on_ready)(void* _Nullable param),
-                                           void* _Nullable param);
+__attribute__((noreturn)) void AVmPayload_runVsockRpcServer(
+        AIBinder* _Nonnull service, uint32_t port,
+        void (*_Nullable on_ready)(void* _Nullable param), void* _Nullable param);
 
 /**
  * Returns all or part of a 32-byte secret that is bound to this unique VM
diff --git a/vmbase/src/heap.rs b/vmbase/src/heap.rs
index ec03d38..99c06aa 100644
--- a/vmbase/src/heap.rs
+++ b/vmbase/src/heap.rs
@@ -86,6 +86,21 @@
 }
 
 #[no_mangle]
+unsafe extern "C" fn __memset_chk(
+    dest: *mut c_void,
+    val: u8,
+    len: usize,
+    destlen: usize,
+) -> *mut c_void {
+    assert!(len <= destlen, "memset buffer overflow detected");
+    // SAFETY: `dest` is valid for writes of `len` bytes.
+    unsafe {
+        ptr::write_bytes(dest, val, len);
+    }
+    dest
+}
+
+#[no_mangle]
 /// SAFETY: ptr must be null or point to a currently-allocated block returned by allocate (either
 /// directly or via malloc or calloc). Note that this function is called directly from C, so we have
 /// to trust that the C code is doing the right thing; there are checks below which will catch some