Merge "[apkverify] Move HashAlgorithm to algorithms"
diff --git a/.prebuilt_info/prebuilt_info_pvmfw_pvmfw_img.asciipb b/.prebuilt_info/prebuilt_info_pvmfw_pvmfw_img.asciipb
index ba1e6b9..984625e 100644
--- a/.prebuilt_info/prebuilt_info_pvmfw_pvmfw_img.asciipb
+++ b/.prebuilt_info/prebuilt_info_pvmfw_pvmfw_img.asciipb
@@ -1,6 +1,6 @@
 drops {
   android_build_drop {
-    build_id: "9107967"
+    build_id: "9110780"
     target: "u-boot_pvmfw"
     source_file: "pvmfw.img"
   }
diff --git a/apex/Android.bp b/apex/Android.bp
index fade6c5..52f4384 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -69,6 +69,7 @@
         "microdroid.json",
         "microdroid_bootloader",
         "microdroid_bootloader.avbpubkey",
+        "microdroid_kernel",
     ],
     file_contexts: ":com.android.virt-file_contexts",
     canned_fs_config: "canned_fs_config",
diff --git a/apex/sign_virt_apex.py b/apex/sign_virt_apex.py
index 6863b0c..c8cb07d 100644
--- a/apex/sign_virt_apex.py
+++ b/apex/sign_virt_apex.py
@@ -378,9 +378,9 @@
 
     # re-sign bootloader, boot.img, vendor_boot.img, and init_boot.img
     Async(AddHashFooter, args, key, files['bootloader'], wait=[replace_f])
-    boot_img_f = Async(AddHashFooter, args, key, files['boot.img'])
-    vendor_boot_img_f = Async(AddHashFooter, args, key, files['vendor_boot.img'])
-    init_boot_img_f = Async(AddHashFooter, args, key, files['init_boot.img'])
+    Async(AddHashFooter, args, key, files['boot.img'])
+    Async(AddHashFooter, args, key, files['vendor_boot.img'])
+    Async(AddHashFooter, args, key, files['init_boot.img'])
 
     # re-sign super.img
     # 1. unpack super.img
@@ -392,11 +392,10 @@
     partitions = {"system_a": system_a_img, "vendor_a": vendor_a_img}
     Async(MakeSuperImage, args, partitions, files['super.img'], wait=[system_a_f, vendor_a_f])
 
-    # re-generate vbmeta from re-signed {boot, vendor_boot, init_boot, system_a, vendor_a}.img
+    # re-generate vbmeta from re-signed {system_a, vendor_a}.img
     Async(MakeVbmetaImage, args, key, files['vbmeta.img'],
-          images=[files['boot.img'], files['vendor_boot.img'],
-                  files['init_boot.img'], system_a_img, vendor_a_img],
-          wait=[boot_img_f, vendor_boot_img_f, init_boot_img_f, system_a_f, vendor_a_f])
+          images=[system_a_img, vendor_a_img],
+          wait=[system_a_f, vendor_a_f])
 
     # Re-sign bootconfigs and the uboot_env with the same key
     bootconfig_sign_key = key
diff --git a/compos/common/compos_client.rs b/compos/common/compos_client.rs
index 49396d7..7327df7 100644
--- a/compos/common/compos_client.rs
+++ b/compos/common/compos_client.rs
@@ -23,7 +23,7 @@
 };
 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
     IVirtualizationService::IVirtualizationService,
-    VirtualMachineAppConfig::{DebugLevel::DebugLevel, VirtualMachineAppConfig},
+    VirtualMachineAppConfig::{DebugLevel::DebugLevel, Payload::Payload, VirtualMachineAppConfig},
     VirtualMachineConfig::VirtualMachineConfig,
 };
 use anyhow::{bail, Context, Result};
@@ -122,7 +122,7 @@
             apk: Some(apk_fd),
             idsig: Some(idsig_fd),
             instanceImage: Some(instance_fd),
-            configPath: config_path,
+            payload: Payload::ConfigPath(config_path),
             debugLevel: debug_level,
             extraIdsigs: extra_idsigs,
             protectedVm: protected_vm,
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
index 4ecd942..63a3f43 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -222,7 +222,7 @@
     /* package */ VirtualMachineAppConfig toParcel() throws FileNotFoundException {
         VirtualMachineAppConfig parcel = new VirtualMachineAppConfig();
         parcel.apk = ParcelFileDescriptor.open(new File(mApkPath), MODE_READ_ONLY);
-        parcel.configPath = mPayloadConfigPath;
+        parcel.payload = VirtualMachineAppConfig.Payload.configPath(mPayloadConfigPath);
         switch (mDebugLevel) {
             case NONE:
                 parcel.debugLevel = VirtualMachineAppConfig.DebugLevel.NONE;
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index 7b28ad2..10e5858 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -70,7 +70,7 @@
         "apexd",
         "atrace",
         "debuggerd",
-        "diced.microdroid",
+        "dice-service.microdroid",
         "linker",
         "linkerconfig",
         "servicemanager.microdroid",
@@ -195,7 +195,6 @@
     partition_name: "vendor",
     use_avb: true,
     deps: [
-        "android.hardware.security.dice-service.microdroid",
         "microdroid_fstab",
         "microdroid_precompiled_sepolicy.plat_sepolicy_and_mapping.sha256",
         "microdroid_vendor_manifest",
@@ -508,10 +507,7 @@
     private_key: ":microdroid_sign_key",
     partitions: [
         "microdroid_vendor",
-        "microdroid_vendor_boot",
         "microdroid",
-        "microdroid_boot",
-        "microdroid_init_boot",
     ],
 }
 
@@ -573,3 +569,37 @@
     name: "microdroid_bootconfig_normal_src",
     srcs: ["bootconfig.normal"],
 }
+
+avb_add_hash_footer {
+    name: "microdroid_kernel_signed",
+    src: "empty_kernel",
+    filename: "microdroid_kernel",
+    partition_name: "bootloader",
+    private_key: ":microdroid_sign_key",
+    salt: bootloader_salt,
+    enabled: false,
+    arch: {
+        arm64: {
+            src: ":microdroid_kernel_prebuilts-5.15-arm64",
+            enabled: true,
+        },
+        x86_64: {
+            src: ":microdroid_kernel_prebuilts-5.15-x86_64",
+            enabled: true,
+        },
+    },
+}
+
+prebuilt_etc {
+    name: "microdroid_kernel",
+    src: "empty_kernel",
+    relative_install_path: "fs",
+    arch: {
+        arm64: {
+            src: ":microdroid_kernel_signed",
+        },
+        x86_64: {
+            src: ":microdroid_kernel_signed",
+        },
+    },
+}
diff --git a/microdroid/dice/Android.bp b/microdroid/dice/Android.bp
index 8026581..859533e 100644
--- a/microdroid/dice/Android.bp
+++ b/microdroid/dice/Android.bp
@@ -3,27 +3,24 @@
 }
 
 rust_binary {
-    name: "android.hardware.security.dice-service.microdroid",
+    name: "dice-service.microdroid",
     srcs: ["service.rs"],
-    relative_install_path: "hw",
-    vendor: true,
     prefer_rlib: true,
     rustlibs: [
         "android.hardware.security.dice-V1-rust",
+        "android.security.dice-rust",
         "libandroid_logger",
         "libanyhow",
         "libbinder_rs",
         "libbyteorder",
+        "libdiced",
         "libdiced_open_dice_cbor",
         "libdiced_sample_inputs",
-        "libdiced_vendor",
+        "libdiced_utils",
         "liblibc",
         "liblog_rust",
         "libserde",
     ],
-    init_rc: ["android.hardware.security.dice-service.microdroid.rc"],
-    vintf_fragments: [
-        "android.hardware.security.dice-service.microdroid.xml",
-    ],
+    init_rc: ["dice-service.microdroid.rc"],
     bootstrap: true,
 }
diff --git a/microdroid/dice/android.hardware.security.dice-service.microdroid.rc b/microdroid/dice/android.hardware.security.dice-service.microdroid.rc
deleted file mode 100644
index 7d9d441..0000000
--- a/microdroid/dice/android.hardware.security.dice-service.microdroid.rc
+++ /dev/null
@@ -1,3 +0,0 @@
-service vendor.dice-microdroid /vendor/bin/hw/android.hardware.security.dice-service.microdroid
-    user diced
-    group diced
diff --git a/microdroid/dice/android.hardware.security.dice-service.microdroid.xml b/microdroid/dice/android.hardware.security.dice-service.microdroid.xml
deleted file mode 100644
index cf6c482..0000000
--- a/microdroid/dice/android.hardware.security.dice-service.microdroid.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<manifest version="1.0" type="device">
-    <hal format="aidl">
-        <name>android.hardware.security.dice</name>
-        <fqname>IDiceDevice/default</fqname>
-    </hal>
-</manifest>
diff --git a/microdroid/dice/dice-service.microdroid.rc b/microdroid/dice/dice-service.microdroid.rc
new file mode 100644
index 0000000..5bcb049
--- /dev/null
+++ b/microdroid/dice/dice-service.microdroid.rc
@@ -0,0 +1,3 @@
+service dice-microdroid /system/bin/dice-service.microdroid
+    user diced
+    group diced
diff --git a/microdroid/dice/service.rs b/microdroid/dice/service.rs
index 8199c7c..2c19481 100644
--- a/microdroid/dice/service.rs
+++ b/microdroid/dice/service.rs
@@ -12,14 +12,17 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-//! Main entry point for the microdroid IDiceDevice HAL implementation.
+//! Main entry point for the microdroid DICE service implementation.
 
-use anyhow::{bail, Error, Result};
-use byteorder::{NativeEndian, ReadBytesExt};
-use diced::{
-    dice,
-    hal_node::{DiceArtifacts, DiceDevice, ResidentHal, UpdatableDiceArtifacts},
+use android_hardware_security_dice::aidl::android::hardware::security::dice::{
+    Bcc::Bcc, BccHandover::BccHandover, InputValues::InputValues as BinderInputValues,
+    Signature::Signature,
 };
+use anyhow::{bail, ensure, Context, Error, Result};
+use byteorder::{NativeEndian, ReadBytesExt};
+use dice::{ContextImpl, OpenDiceCborContext};
+use diced::{dice, DiceMaintenance, DiceNode, DiceNodeImpl};
+use diced_utils::make_bcc_handover;
 use libc::{c_void, mmap, munmap, MAP_FAILED, MAP_PRIVATE, PROT_READ};
 use serde::{Deserialize, Serialize};
 use std::fs;
@@ -28,10 +31,11 @@
 use std::path::{Path, PathBuf};
 use std::ptr::null_mut;
 use std::slice;
-use std::sync::Arc;
+use std::sync::{Arc, RwLock};
 
 const AVF_STRICT_BOOT: &str = "/sys/firmware/devicetree/base/chosen/avf,strict-boot";
-const DICE_HAL_SERVICE_NAME: &str = "android.hardware.security.dice.IDiceDevice/default";
+const DICE_NODE_SERVICE_NAME: &str = "android.security.dice.IDiceNode";
+const DICE_MAINTENANCE_SERVICE_NAME: &str = "android.security.dice.IDiceMaintenance";
 
 /// Artifacts that are mapped into the process address space from the driver.
 struct MappedDriverArtifacts<'a> {
@@ -99,19 +103,6 @@
     }
 }
 
-impl DiceArtifacts for MappedDriverArtifacts<'_> {
-    fn cdi_attest(&self) -> &[u8; dice::CDI_SIZE] {
-        self.cdi_attest
-    }
-    fn cdi_seal(&self) -> &[u8; dice::CDI_SIZE] {
-        self.cdi_seal
-    }
-    fn bcc(&self) -> Vec<u8> {
-        // The BCC only contains public information so it's fine to copy.
-        self.bcc.to_vec()
-    }
-}
-
 /// Artifacts that are kept in the process address space after the artifacts
 /// from the driver have been consumed.
 #[derive(Clone, Serialize, Deserialize)]
@@ -121,81 +112,169 @@
     bcc: Vec<u8>,
 }
 
-impl DiceArtifacts for RawArtifacts {
-    fn cdi_attest(&self) -> &[u8; dice::CDI_SIZE] {
-        &self.cdi_attest
-    }
-    fn cdi_seal(&self) -> &[u8; dice::CDI_SIZE] {
-        &self.cdi_seal
-    }
-    fn bcc(&self) -> Vec<u8> {
-        // The BCC only contains public information so it's fine to copy.
-        self.bcc.clone()
-    }
-}
-
 #[derive(Clone, Serialize, Deserialize)]
-enum DriverArtifactManager {
+enum UpdatableArtifacts {
     Invalid,
     Driver(PathBuf),
     Updated(RawArtifacts),
 }
 
-impl DriverArtifactManager {
-    fn new(driver_path: &Path) -> Self {
-        if driver_path.exists() {
-            log::info!("Using DICE values from driver");
-            Self::Driver(driver_path.to_path_buf())
-        } else if Path::new(AVF_STRICT_BOOT).exists() {
-            log::error!("Strict boot requires DICE value from driver but none were found");
-            Self::Invalid
-        } else {
-            log::warn!("Using sample DICE values");
-            let (cdi_attest, cdi_seal, bcc) = diced_sample_inputs::make_sample_bcc_and_cdis()
-                .expect("Failed to create sample dice artifacts.");
-            Self::Updated(RawArtifacts {
-                cdi_attest: cdi_attest[..].try_into().unwrap(),
-                cdi_seal: cdi_seal[..].try_into().unwrap(),
-                bcc,
-            })
-        }
-    }
-}
-
-impl UpdatableDiceArtifacts for DriverArtifactManager {
-    fn with_artifacts<F, T>(&self, f: F) -> Result<T>
-    where
-        F: FnOnce(&dyn DiceArtifacts) -> Result<T>,
-    {
+impl UpdatableArtifacts {
+    fn get(
+        &self,
+        input_values: &BinderInputValues,
+    ) -> Result<(dice::CdiAttest, dice::CdiSeal, Vec<u8>)> {
+        let input_values: diced_utils::InputValues = input_values.into();
         match self {
             Self::Invalid => bail!("No DICE artifacts available."),
-            Self::Driver(driver_path) => f(&MappedDriverArtifacts::new(driver_path.as_path())?),
-            Self::Updated(raw_artifacts) => f(raw_artifacts),
+            Self::Driver(driver_path) => {
+                let artifacts = MappedDriverArtifacts::new(driver_path.as_path())?;
+                dice::OpenDiceCborContext::new().bcc_main_flow(
+                    artifacts.cdi_attest,
+                    artifacts.cdi_seal,
+                    artifacts.bcc,
+                    &input_values,
+                )
+            }
+            Self::Updated(artifacts) => dice::OpenDiceCborContext::new().bcc_main_flow(
+                &artifacts.cdi_attest,
+                &artifacts.cdi_seal,
+                &artifacts.bcc,
+                &input_values,
+            ),
         }
+        .context("Deriving artifacts")
     }
-    fn update(self, new_artifacts: &impl DiceArtifacts) -> Result<Self> {
+
+    fn update(self, inputs: &BinderInputValues) -> Result<Self> {
         if let Self::Invalid = self {
             bail!("Cannot update invalid DICE artifacts.");
         }
-        if let Self::Driver(driver_path) = self {
-            // Writing to the device wipes the artifcates. The string is ignored
+        let (cdi_attest, cdi_seal, bcc) =
+            self.get(inputs).context("Failed to get update artifacts.")?;
+        if let Self::Driver(ref driver_path) = self {
+            // Writing to the device wipes the artifacts. The string is ignored
             // by the driver but included for documentation.
             fs::write(driver_path, "wipe")
                 .map_err(|error| Error::new(error).context("Wiping driver"))?;
         }
         Ok(Self::Updated(RawArtifacts {
-            cdi_attest: *new_artifacts.cdi_attest(),
-            cdi_seal: *new_artifacts.cdi_seal(),
-            bcc: new_artifacts.bcc(),
+            cdi_attest: cdi_attest[..].try_into().unwrap(),
+            cdi_seal: cdi_seal[..].try_into().unwrap(),
+            bcc,
         }))
     }
 }
 
+struct ArtifactManager {
+    artifacts: RwLock<UpdatableArtifacts>,
+}
+
+impl ArtifactManager {
+    fn new(driver_path: &Path) -> Self {
+        Self {
+            artifacts: RwLock::new(if driver_path.exists() {
+                log::info!("Using DICE values from driver");
+                UpdatableArtifacts::Driver(driver_path.to_path_buf())
+            } else if Path::new(AVF_STRICT_BOOT).exists() {
+                log::error!("Strict boot requires DICE value from driver but none were found");
+                UpdatableArtifacts::Invalid
+            } else {
+                log::warn!("Using sample DICE values");
+                let (cdi_attest, cdi_seal, bcc) = diced_sample_inputs::make_sample_bcc_and_cdis()
+                    .expect("Failed to create sample dice artifacts.");
+                UpdatableArtifacts::Updated(RawArtifacts {
+                    cdi_attest: cdi_attest[..].try_into().unwrap(),
+                    cdi_seal: cdi_seal[..].try_into().unwrap(),
+                    bcc,
+                })
+            }),
+        }
+    }
+}
+
+impl DiceNodeImpl for ArtifactManager {
+    fn sign(
+        &self,
+        client: BinderInputValues,
+        input_values: &[BinderInputValues],
+        message: &[u8],
+    ) -> Result<Signature> {
+        ensure!(input_values.is_empty(), "Extra input values not supported");
+        let artifacts = self.artifacts.read().unwrap().clone();
+        let (cdi_attest, _, _) =
+            artifacts.get(&client).context("Failed to get signing artifacts.")?;
+        let mut dice = OpenDiceCborContext::new();
+        let seed = dice
+            .derive_cdi_private_key_seed(
+                cdi_attest[..].try_into().context("Failed to convert cdi_attest.")?,
+            )
+            .context("Failed to derive seed from cdi_attest.")?;
+        let (_public_key, private_key) = dice
+            .keypair_from_seed(seed[..].try_into().context("Failed to convert seed.")?)
+            .context("Failed to derive keypair from seed.")?;
+        let signature = dice
+            .sign(message, private_key[..].try_into().context("Failed to convert private_key.")?)
+            .context("Failed to sign.")?;
+        Ok(Signature { data: signature })
+    }
+
+    fn get_attestation_chain(
+        &self,
+        client: BinderInputValues,
+        input_values: &[BinderInputValues],
+    ) -> Result<Bcc> {
+        ensure!(input_values.is_empty(), "Extra input values not supported");
+        let artifacts = self.artifacts.read().unwrap().clone();
+        let (_, _, bcc) =
+            artifacts.get(&client).context("Failed to get attestation chain artifacts.")?;
+        Ok(Bcc { data: bcc })
+    }
+
+    fn derive(
+        &self,
+        client: BinderInputValues,
+        input_values: &[BinderInputValues],
+    ) -> Result<BccHandover> {
+        ensure!(input_values.is_empty(), "Extra input values not supported");
+        let artifacts = self.artifacts.read().unwrap().clone();
+        let (cdi_attest, cdi_seal, bcc) =
+            artifacts.get(&client).context("Failed to get attestation chain artifacts.")?;
+        make_bcc_handover(
+            &cdi_attest
+                .to_vec()
+                .as_slice()
+                .try_into()
+                .context("Trying to convert cdi_attest to sized array.")?,
+            &cdi_seal
+                .to_vec()
+                .as_slice()
+                .try_into()
+                .context("Trying to convert cdi_seal to sized array.")?,
+            &bcc,
+        )
+        .context("Trying to construct BccHandover.")
+    }
+
+    fn demote(
+        &self,
+        _client: BinderInputValues,
+        _input_values: &[BinderInputValues],
+    ) -> Result<()> {
+        bail!("Demote not supported.");
+    }
+
+    fn demote_self(&self, input_values: &[BinderInputValues]) -> Result<()> {
+        ensure!(input_values.len() == 1, "Can only demote_self one level.");
+        let mut artifacts = self.artifacts.write().unwrap();
+        *artifacts = (*artifacts).clone().update(&input_values[0])?;
+        Ok(())
+    }
+}
+
 fn main() {
     android_logger::init_once(
-        android_logger::Config::default()
-            .with_tag("android.hardware.security.dice")
-            .with_min_level(log::Level::Debug),
+        android_logger::Config::default().with_tag("dice").with_min_level(log::Level::Debug),
     );
     // Redirect panic messages to logcat.
     panic::set_hook(Box::new(|panic_info| {
@@ -203,22 +282,21 @@
     }));
 
     // Saying hi.
-    log::info!("android.hardware.security.dice is starting.");
+    log::info!("DICE service is starting.");
 
-    let hal_impl = Arc::new(
-        unsafe {
-            // Safety: ResidentHal cannot be used in multi threaded processes.
-            // This service does not start a thread pool. The main thread is the only thread
-            // joining the thread pool, thereby keeping the process single threaded.
-            ResidentHal::new(DriverArtifactManager::new(Path::new("/dev/open-dice0")))
-        }
-        .expect("Failed to create ResidentHal implementation."),
-    );
+    let node_impl = Arc::new(ArtifactManager::new(Path::new("/dev/open-dice0")));
 
-    let hal = DiceDevice::new_as_binder(hal_impl).expect("Failed to construct hal service.");
+    let node = DiceNode::new_as_binder(node_impl.clone())
+        .expect("Failed to create IDiceNode service instance.");
 
-    binder::add_service(DICE_HAL_SERVICE_NAME, hal.as_binder())
-        .expect("Failed to register IDiceDevice Service");
+    let maintenance = DiceMaintenance::new_as_binder(node_impl)
+        .expect("Failed to create IDiceMaintenance service instance.");
+
+    binder::add_service(DICE_NODE_SERVICE_NAME, node.as_binder())
+        .expect("Failed to register IDiceNode Service");
+
+    binder::add_service(DICE_MAINTENANCE_SERVICE_NAME, maintenance.as_binder())
+        .expect("Failed to register IDiceMaintenance Service");
 
     log::info!("Joining thread pool now.");
     binder::ProcessState::join_thread_pool();
diff --git a/microdroid/init.rc b/microdroid/init.rc
index 4e36726..47002c9 100644
--- a/microdroid/init.rc
+++ b/microdroid/init.rc
@@ -67,8 +67,7 @@
 
     start servicemanager
 
-    start vendor.dice-microdroid
-    start diced
+    start dice-microdroid
 
 on init
     mkdir /mnt/apk 0755 system system
diff --git a/microdroid/microdroid.json b/microdroid/microdroid.json
index f02dcbf..00cedc8 100644
--- a/microdroid/microdroid.json
+++ b/microdroid/microdroid.json
@@ -1,21 +1,9 @@
 {
-  "bootloader": "/apex/com.android.virt/etc/fs/microdroid_bootloader",
+  "kernel": "/apex/com.android.virt/etc/fs/microdroid_kernel",
   "disks": [
     {
       "partitions": [
         {
-          "label": "boot_a",
-          "path": "/apex/com.android.virt/etc/fs/microdroid_boot.img"
-        },
-        {
-          "label": "init_boot_a",
-          "path": "/apex/com.android.virt/etc/fs/microdroid_init_boot.img"
-        },
-        {
-          "label": "vendor_boot_a",
-          "path": "/apex/com.android.virt/etc/fs/microdroid_vendor_boot.img"
-        },
-        {
           "label": "vbmeta_a",
           "path": "/apex/com.android.virt/etc/fs/microdroid_vbmeta.img"
         },
@@ -25,16 +13,6 @@
         }
       ],
       "writable": false
-    },
-    {
-      "partitions": [
-        {
-          "label": "uboot_env",
-          "path": "/apex/com.android.virt/etc/fs/uboot_env.img",
-          "writable": false
-        }
-      ],
-      "writable": true
     }
   ],
   "memory_mib": 256,
diff --git a/microdroid/payload/metadata.cc b/microdroid/payload/metadata.cc
index 07083e9..fd07bdd 100644
--- a/microdroid/payload/metadata.cc
+++ b/microdroid/payload/metadata.cc
@@ -40,7 +40,7 @@
     }
     size = be32toh(*reinterpret_cast<uint32_t*>(content.data()));
     if (content.size() < length_prefix_bytes + size) {
-        return Error() << "Invalid metadata: size(" << size << ") mimatches to the content size("
+        return Error() << "Invalid metadata: size(" << size << ") doesn't match content size("
                        << content.size() - length_prefix_bytes << ")";
     }
     content = content.substr(length_prefix_bytes, size);
diff --git a/microdroid/payload/metadata.proto b/microdroid/payload/metadata.proto
index 2e92f55..229d03f 100644
--- a/microdroid/payload/metadata.proto
+++ b/microdroid/payload/metadata.proto
@@ -26,7 +26,11 @@
 
   ApkPayload apk = 3;
 
-  string payload_config_path = 4;
+  oneof payload {
+    // Path to JSON config file inside the APK.
+    string config_path = 4;
+    PayloadConfig config = 5;
+  }
 }
 
 message ApexPayload {
@@ -58,3 +62,18 @@
 
   string idsig_partition_name = 3;
 }
+
+message PayloadConfig {
+  // Required.
+  // Path to the payload binary file inside the APK.
+  string payload_binary_path = 1;
+
+  // Required.
+  // Whether tombstones from crashes inside the VM should be exported to the host.
+  bool export_tombstones = 2;
+
+  // Optional.
+  // Arguments to be passed to the payload.
+  // TODO(b/249064104): Remove this
+  repeated string args = 3;
+}
diff --git a/microdroid/payload/metadata/src/lib.rs b/microdroid/payload/metadata/src/lib.rs
index eb9d90d..bfbec60 100644
--- a/microdroid/payload/metadata/src/lib.rs
+++ b/microdroid/payload/metadata/src/lib.rs
@@ -23,7 +23,9 @@
 use std::io::Read;
 use std::io::Write;
 
-pub use microdroid_metadata::metadata::{ApexPayload, ApkPayload, Metadata};
+pub use microdroid_metadata::metadata::{
+    ApexPayload, ApkPayload, Metadata, Metadata_oneof_payload as PayloadMetadata, PayloadConfig,
+};
 
 /// Reads a metadata from a reader
 pub fn read_metadata<T: Read>(mut r: T) -> Result<Metadata> {
diff --git a/microdroid/payload/mk_payload.cc b/microdroid/payload/mk_payload.cc
index 4dbcabf..d31333f 100644
--- a/microdroid/payload/mk_payload.cc
+++ b/microdroid/payload/mk_payload.cc
@@ -181,7 +181,7 @@
     }
 
     if (config.payload_config_path.has_value()) {
-        *metadata.mutable_payload_config_path() = config.payload_config_path.value();
+        *metadata.mutable_config_path() = config.payload_config_path.value();
     }
 
     std::ofstream out(filename);
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 30521f6..90cabb6 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -34,7 +34,7 @@
 use glob::glob;
 use itertools::sorted;
 use log::{error, info};
-use microdroid_metadata::{write_metadata, Metadata};
+use microdroid_metadata::{write_metadata, Metadata, PayloadMetadata};
 use microdroid_payload_config::{Task, TaskType, VmPayloadConfig};
 use openssl::sha::Sha512;
 use payload::{get_apex_data_from_payload, load_metadata, to_metadata};
@@ -301,9 +301,14 @@
         verified_data
     };
 
+    let payload_config_path = match metadata.payload {
+        Some(PayloadMetadata::config_path(p)) => p,
+        _ => bail!("Unsupported payload config"),
+    };
+
     // To minimize the exposure to untrusted data, derive dice profile as soon as possible.
     info!("DICE derivation for payload");
-    dice_derivation(&verified_data, &metadata.payload_config_path)?;
+    dice_derivation(&verified_data, &payload_config_path)?;
 
     // Before reading a file from the APK, start zipfuse
     let noexec = false;
@@ -316,11 +321,11 @@
     .context("Failed to run zipfuse")?;
 
     ensure!(
-        !metadata.payload_config_path.is_empty(),
+        !payload_config_path.is_empty(),
         MicrodroidError::InvalidConfig("No payload_config_path in metadata".to_string())
     );
 
-    let config = load_config(Path::new(&metadata.payload_config_path))?;
+    let config = load_config(Path::new(&payload_config_path))?;
 
     let task = config
         .task
diff --git a/pvmfw/Android.bp b/pvmfw/Android.bp
index 5bcafd5..57a3df0 100644
--- a/pvmfw/Android.bp
+++ b/pvmfw/Android.bp
@@ -42,3 +42,17 @@
         },
     },
 }
+
+bootimg {
+    name: "pvmfw_img",
+    stem: "pvmfw.img",
+    kernel_prebuilt: ":pvmfw_bin",
+    header_version: "3",
+    partition_name: "pvmfw",
+    enabled: false,
+    target: {
+        android_arm64: {
+            enabled: true,
+        },
+    },
+}
diff --git a/pvmfw/pvmfw.img b/pvmfw/pvmfw.img
index 7f32a9a..bacf213 100644
--- a/pvmfw/pvmfw.img
+++ b/pvmfw/pvmfw.img
Binary files differ
diff --git a/tests/benchmark/AndroidTest.xml b/tests/benchmark/AndroidTest.xml
index e908077..4949d22 100644
--- a/tests/benchmark/AndroidTest.xml
+++ b/tests/benchmark/AndroidTest.xml
@@ -21,6 +21,10 @@
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="test-file-name" value="MicrodroidBenchmarkApp.apk" />
     </target_preparer>
+    <!-- Need root to access /proc/$pid/smaps when measuring memory usage. -->
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+        <option name="force-root" value="true" />
+    </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
       <option
         name="run-command"
diff --git a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
index 5dcd77b..a1dee6d 100644
--- a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
+++ b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
@@ -282,7 +282,9 @@
         }
 
         private long getKernelStartedNanoTime() {
-            return kernelStartedNanoTime.getAsLong();
+            // pvmfw emits log at the end which is used to estimate the kernelStart time.
+            // In case of no pvmfw run(non-protected mode), use vCPU started time instead.
+            return kernelStartedNanoTime.orElse(vcpuStartedNanoTime.getAsLong());
         }
 
         private long getInitStartedNanoTime() {
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidTestCase.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidTestCase.java
index c9cfd72..7a71254 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidTestCase.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidTestCase.java
@@ -59,6 +59,7 @@
 import org.json.JSONObject;
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TestName;
@@ -359,7 +360,7 @@
         JSONObject config = new JSONObject(FileUtil.readStringFromFile(microdroidConfigFile));
 
         // Replace paths so that the config uses re-signed images from TEST_ROOT
-        config.put("bootloader", config.getString("bootloader").replace(VIRT_APEX, TEST_ROOT));
+        config.put("kernel", config.getString("kernel").replace(VIRT_APEX, TEST_ROOT));
         JSONArray disks = config.getJSONArray("disks");
         for (int diskIndex = 0; diskIndex < disks.length(); diskIndex++) {
             JSONObject disk = disks.getJSONObject(diskIndex);
@@ -371,14 +372,16 @@
         }
 
         // Add partitions to the second disk
-        final String vbmetaPath = TEST_ROOT + "etc/fs/microdroid_vbmeta_bootconfig.img";
-        final String bootconfigPath = TEST_ROOT + "etc/fs/microdroid_bootconfig.full_debuggable";
-        disks.getJSONObject(1)
-                .getJSONArray("partitions")
-                .put(newPartition("vbmeta", vbmetaPath))
-                .put(newPartition("bootconfig", bootconfigPath))
-                .put(newPartition("vm-instance", instanceImgPath));
-
+        final String initrdPath = TEST_ROOT + "etc/microdroid_initrd_full_debuggable.img";
+        config.put("initrd", initrdPath);
+        // Add instance image as a partition in disks[1]
+        disks.put(
+            new JSONObject()
+                .put("writable", true)
+                .put(
+                    "partitions",
+                    new JSONArray()
+                        .put(newPartition("vm-instance", instanceImgPath))));
         // Add payload image disk with partitions:
         // - payload-metadata
         // - apexes: com.android.os.statsd, com.android.adbd, [sharedlib apex](optional)
@@ -438,7 +441,10 @@
         assertThat(getDevice().pullFileContents(consolePath), containsString("pvmfw boot failed"));
     }
 
+    // TODO(b/245277660): Resigning the system/vendor image changes the vbmeta hash.
+    // So, unless vbmeta related bootconfigs are updated the following test will fail
     @Test
+    @Ignore("b/245277660")
     @CddTest(requirements = {"9.17/C-2-2", "9.17/C-2-6"})
     public void testBootSucceedsWhenNonProtectedVmStartsWithImagesSignedWithDifferentKey()
             throws Exception {
@@ -457,7 +463,7 @@
 
     @Test
     @CddTest(requirements = {"9.17/C-2-2", "9.17/C-2-6"})
-    public void testBootFailsWhenBootloaderAndVbMetaAreSignedWithDifferentKeys() throws Exception {
+    public void testBootFailsWhenVbMetaDigestDoesNotMatchBootconfig() throws Exception {
         // Sign everything with key1 except vbmeta
         File key = findTestFile("test.com.android.virt.pem");
         File key2 = findTestFile("test2.com.android.virt.pem");
@@ -469,34 +475,11 @@
         String cid =
                 runMicrodroidWithResignedImages(
                         key, keyOverrides, isProtected, daemonize, consolePath);
-        // Wail for a while so that bootloader prints errors to console
+        // Wait so that init can print errors to console (time in cuttlefish >> in real device)
         assertThatEventually(
-                10000,
+                100000,
                 () -> getDevice().pullFileContents(consolePath),
-                containsString("Public key was rejected"));
-        shutdownMicrodroid(getDevice(), cid);
-    }
-
-    @Test
-    @CddTest(requirements = {"9.17/C-2-2", "9.17/C-2-6"})
-    public void testBootSucceedsWhenBootloaderAndVbmetaHaveSameSigningKeys() throws Exception {
-        // Sign everything with key1 except bootloader and vbmeta
-        File key = findTestFile("test.com.android.virt.pem");
-        File key2 = findTestFile("test2.com.android.virt.pem");
-        Map<String, File> keyOverrides =
-                Map.of(
-                        "microdroid_bootloader", key2,
-                        "microdroid_vbmeta.img", key2,
-                        "microdroid_vbmeta_bootconfig.img", key2);
-        boolean isProtected = false; // Not interested in pvwfw
-        boolean daemonize = true; // Bootloader should succeed.
-        // To be able to stop it, it should be a daemon.
-        String consolePath = TEST_ROOT + "console";
-        String cid =
-                runMicrodroidWithResignedImages(
-                        key, keyOverrides, isProtected, daemonize, consolePath);
-        // Adb connection to the microdroid means that boot succeeded.
-        adbConnectToMicrodroid(getDevice(), cid);
+                containsString("init: [libfs_avb]Failed to verify vbmeta digest"));
         shutdownMicrodroid(getDevice(), cid);
     }
 
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 c2060cb..4b40293 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -451,36 +451,6 @@
             "9.17/C-1-1",
             "9.17/C-2-7"
     })
-    public void bootFailsWhenUBootAvbDataIsCompromised()
-            throws VirtualMachineException, InterruptedException, IOException {
-        if (mProtectedVm) {
-            assertThatBootFailsAfterCompromisingPartition(U_BOOT_AVB_PARTITION_UUID);
-        } else {
-            // non-protected VM shouldn't have u-boot avb data
-            assertThatPartitionIsMissing(U_BOOT_AVB_PARTITION_UUID);
-        }
-    }
-
-    @Test
-    @CddTest(requirements = {
-            "9.17/C-1-1",
-            "9.17/C-2-7"
-    })
-    public void bootFailsWhenUBootEnvDataIsCompromised()
-            throws VirtualMachineException, InterruptedException, IOException {
-        if (mProtectedVm) {
-            assertThatBootFailsAfterCompromisingPartition(U_BOOT_ENV_PARTITION_UUID);
-        } else {
-            // non-protected VM shouldn't have u-boot env data
-            assertThatPartitionIsMissing(U_BOOT_ENV_PARTITION_UUID);
-        }
-    }
-
-    @Test
-    @CddTest(requirements = {
-            "9.17/C-1-1",
-            "9.17/C-2-7"
-    })
     public void bootFailsWhenPvmFwDataIsCompromised()
             throws VirtualMachineException, InterruptedException, IOException {
         if (mProtectedVm) {
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl
index cf5398d..9b0a658 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl
@@ -15,6 +15,8 @@
  */
 package android.system.virtualizationservice;
 
+import android.system.virtualizationservice.VirtualMachinePayloadConfig;
+
 /** Configuration for running an App in a VM */
 parcelable VirtualMachineAppConfig {
     /** Name of VM */
@@ -32,8 +34,20 @@
     /** instance.img that has per-instance data */
     ParcelFileDescriptor instanceImage;
 
-    /** Path to a configuration in an APK. This is the actual configuration for a VM. */
-    @utf8InCpp String configPath;
+    union Payload {
+        /**
+         * Path to a JSON file in an APK containing the configuration.
+         */
+        @utf8InCpp String configPath;
+
+        /**
+         * Configuration provided explicitly.
+         */
+        VirtualMachinePayloadConfig payloadConfig;
+    }
+
+    /** Detailed configuration for the VM, specifying how the payload will be run. */
+    Payload payload;
 
     enum DebugLevel {
         /** Not debuggable at all */
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachinePayloadConfig.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachinePayloadConfig.aidl
new file mode 100644
index 0000000..0c83349
--- /dev/null
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachinePayloadConfig.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.system.virtualizationservice;
+
+parcelable VirtualMachinePayloadConfig {
+    /**
+     * Path to the payload executable code in an APK. The code is in the form of a .so with a
+     * defined entry point; inside the VM this file is loaded and the entry function invoked.
+     */
+    @utf8InCpp String payloadPath;
+
+    /**
+     * Whether to export tombstones (VM crash details) from the VM to the host.
+     */
+    boolean exportTombstones;
+
+    /**
+     * Command-line style arguments to be passed to the payload when it is executed.
+     * TODO(b/249064104): Remove this
+     */
+    @utf8InCpp String[] args;
+}
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 3b75df9..22418b9 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -29,9 +29,10 @@
     IVirtualizationService::IVirtualizationService,
     Partition::Partition,
     PartitionType::PartitionType,
-    VirtualMachineAppConfig::VirtualMachineAppConfig,
+    VirtualMachineAppConfig::{VirtualMachineAppConfig, Payload::Payload},
     VirtualMachineConfig::VirtualMachineConfig,
     VirtualMachineDebugInfo::VirtualMachineDebugInfo,
+    VirtualMachinePayloadConfig::VirtualMachinePayloadConfig,
     VirtualMachineRawConfig::VirtualMachineRawConfig,
     VirtualMachineState::VirtualMachineState,
 };
@@ -49,7 +50,7 @@
 use disk::QcowFile;
 use apkverify::{HashAlgorithm, V4Signature};
 use log::{debug, error, info, warn};
-use microdroid_payload_config::VmPayloadConfig;
+use microdroid_payload_config::{VmPayloadConfig, OsConfig, Task, TaskType};
 use rustutils::system_properties;
 use semver::VersionReq;
 use std::convert::TryInto;
@@ -85,6 +86,8 @@
 
 const CHUNK_RECV_MAX_LEN: usize = 1024;
 
+const MICRODROID_OS_NAME: &str = "microdroid";
+
 /// Implementation of `IVirtualizationService`, the entry point of the AIDL service.
 #[derive(Debug, Default)]
 pub struct VirtualizationService {
@@ -376,15 +379,10 @@
         let config = match config {
             VirtualMachineConfig::AppConfig(config) => BorrowedOrOwned::Owned(
                 load_app_config(config, &temporary_directory).map_err(|e| {
-                    error!("Failed to load app config from {}: {:?}", &config.configPath, e);
                     *is_protected = config.protectedVm;
-                    Status::new_service_specific_error_str(
-                        -1,
-                        Some(format!(
-                            "Failed to load app config from {}: {:?}",
-                            &config.configPath, e
-                        )),
-                    )
+                    let message = format!("Failed to load app config: {:?}", e);
+                    error!("{}", message);
+                    Status::new_service_specific_error_str(-1, Some(message))
                 })?,
             ),
             VirtualMachineConfig::RawConfig(config) => BorrowedOrOwned::Borrowed(config),
@@ -604,16 +602,18 @@
     let apk_file = clone_file(config.apk.as_ref().unwrap())?;
     let idsig_file = clone_file(config.idsig.as_ref().unwrap())?;
     let instance_file = clone_file(config.instanceImage.as_ref().unwrap())?;
-    let config_path = &config.configPath;
 
-    let mut apk_zip = ZipArchive::new(&apk_file)?;
-    let config_file = apk_zip.by_name(config_path)?;
-    let vm_payload_config: VmPayloadConfig = serde_json::from_reader(config_file)?;
+    let vm_payload_config = match &config.payload {
+        Payload::ConfigPath(config_path) => {
+            load_vm_payload_config_from_file(&apk_file, config_path.as_str())
+                .with_context(|| format!("Couldn't read config from {}", config_path))?
+        }
+        Payload::PayloadConfig(payload_config) => create_vm_payload_config(payload_config),
+    };
 
-    let os_name = &vm_payload_config.os.name;
-
-    // For now, the only supported "os" value is "microdroid"
-    if os_name != "microdroid" {
+    // For now, the only supported OS is Microdroid
+    let os_name = vm_payload_config.os.name.as_str();
+    if os_name != MICRODROID_OS_NAME {
         bail!("Unknown OS \"{}\"", os_name);
     }
 
@@ -632,22 +632,46 @@
     vm_config.numCpus = config.numCpus;
     vm_config.taskProfiles = config.taskProfiles.clone();
 
-    // Microdroid requires an additional payload disk image and the bootconfig partition.
-    if os_name == "microdroid" {
-        add_microdroid_images(
-            config,
-            temporary_directory,
-            apk_file,
-            idsig_file,
-            instance_file,
-            &vm_payload_config,
-            &mut vm_config,
-        )?;
-    }
+    // Microdroid requires an additional init ramdisk & payload disk image
+    add_microdroid_images(
+        config,
+        temporary_directory,
+        apk_file,
+        idsig_file,
+        instance_file,
+        &vm_payload_config,
+        &mut vm_config,
+    )?;
 
     Ok(vm_config)
 }
 
+fn load_vm_payload_config_from_file(apk_file: &File, config_path: &str) -> Result<VmPayloadConfig> {
+    let mut apk_zip = ZipArchive::new(apk_file)?;
+    let config_file = apk_zip.by_name(config_path)?;
+    Ok(serde_json::from_reader(config_file)?)
+}
+
+fn create_vm_payload_config(payload_config: &VirtualMachinePayloadConfig) -> VmPayloadConfig {
+    // There isn't an actual config file. Construct a synthetic VmPayloadConfig from the explicit
+    // parameters we've been given. Microdroid will do something equivalent inside the VM using the
+    // payload config that we send it via the metadata file.
+    let task = Task {
+        type_: TaskType::MicrodroidLauncher,
+        command: payload_config.payloadPath.clone(),
+        args: payload_config.args.clone(),
+    };
+    VmPayloadConfig {
+        os: OsConfig { name: MICRODROID_OS_NAME.to_owned() },
+        task: Some(task),
+        apexes: vec![],
+        extra_apks: vec![],
+        prefer_staged: false,
+        export_tombstones: payload_config.exportTombstones,
+        enable_authfs: false,
+    }
+}
+
 /// Generates a unique filename to use for a composite disk image.
 fn make_composite_image_filenames(
     temporary_directory: &Path,
diff --git a/virtualizationservice/src/atom.rs b/virtualizationservice/src/atom.rs
index 3b29d19..eabb4cc 100644
--- a/virtualizationservice/src/atom.rs
+++ b/virtualizationservice/src/atom.rs
@@ -16,23 +16,47 @@
 
 use crate::aidl::clone_file;
 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
-    DeathReason::DeathReason, IVirtualMachine::IVirtualMachine,
-    VirtualMachineAppConfig::VirtualMachineAppConfig, VirtualMachineConfig::VirtualMachineConfig,
+    DeathReason::DeathReason,
+    IVirtualMachine::IVirtualMachine,
+    VirtualMachineAppConfig::{Payload::Payload, VirtualMachineAppConfig},
+    VirtualMachineConfig::VirtualMachineConfig,
 };
 use android_system_virtualizationservice::binder::{Status, Strong};
 use anyhow::{anyhow, Result};
-use binder::ThreadState;
+use binder::{ParcelFileDescriptor, ThreadState};
 use log::{trace, warn};
 use microdroid_payload_config::VmPayloadConfig;
 use statslog_virtualization_rust::{vm_booted, vm_creation_requested, vm_exited};
 use std::time::{Duration, SystemTime};
 use zip::ZipArchive;
 
-fn get_vm_payload_config(config: &VirtualMachineAppConfig) -> Result<VmPayloadConfig> {
-    let apk = config.apk.as_ref().ok_or_else(|| anyhow!("APK is none"))?;
+fn get_apex_list(config: &VirtualMachineAppConfig) -> String {
+    match &config.payload {
+        Payload::PayloadConfig(_) => String::new(),
+        Payload::ConfigPath(config_path) => {
+            let vm_payload_config = get_vm_payload_config(&config.apk, config_path);
+            if let Ok(vm_payload_config) = vm_payload_config {
+                vm_payload_config
+                    .apexes
+                    .iter()
+                    .map(|x| x.name.clone())
+                    .collect::<Vec<String>>()
+                    .join(":")
+            } else {
+                "INFO: Can't get VmPayloadConfig".to_owned()
+            }
+        }
+    }
+}
+
+fn get_vm_payload_config(
+    apk_fd: &Option<ParcelFileDescriptor>,
+    config_path: &str,
+) -> Result<VmPayloadConfig> {
+    let apk = apk_fd.as_ref().ok_or_else(|| anyhow!("APK is none"))?;
     let apk_file = clone_file(apk)?;
     let mut apk_zip = ZipArchive::new(&apk_file)?;
-    let config_file = apk_zip.by_name(&config.configPath)?;
+    let config_file = apk_zip.by_name(config_path)?;
     let vm_payload_config: VmPayloadConfig = serde_json::from_reader(config_file)?;
     Ok(vm_payload_config)
 }
@@ -63,38 +87,22 @@
         }
     }
 
-    let vm_identifier;
-    let config_type;
-    let num_cpus;
-    let memory_mib;
-    let apexes;
-    match config {
-        VirtualMachineConfig::AppConfig(config) => {
-            vm_identifier = &config.name;
-            config_type = vm_creation_requested::ConfigType::VirtualMachineAppConfig;
-            num_cpus = config.numCpus;
-            memory_mib = config.memoryMib;
-
-            let vm_payload_config = get_vm_payload_config(config);
-            if let Ok(vm_payload_config) = vm_payload_config {
-                apexes = vm_payload_config
-                    .apexes
-                    .iter()
-                    .map(|x| x.name.clone())
-                    .collect::<Vec<String>>()
-                    .join(":");
-            } else {
-                apexes = "INFO: Can't get VmPayloadConfig".into();
-            }
-        }
-        VirtualMachineConfig::RawConfig(config) => {
-            vm_identifier = &config.name;
-            config_type = vm_creation_requested::ConfigType::VirtualMachineRawConfig;
-            num_cpus = config.numCpus;
-            memory_mib = config.memoryMib;
-            apexes = String::new();
-        }
-    }
+    let (vm_identifier, config_type, num_cpus, memory_mib, apexes) = match config {
+        VirtualMachineConfig::AppConfig(config) => (
+            &config.name,
+            vm_creation_requested::ConfigType::VirtualMachineAppConfig,
+            config.numCpus,
+            config.memoryMib,
+            get_apex_list(config),
+        ),
+        VirtualMachineConfig::RawConfig(config) => (
+            &config.name,
+            vm_creation_requested::ConfigType::VirtualMachineRawConfig,
+            config.numCpus,
+            config.memoryMib,
+            String::new(),
+        ),
+    };
 
     let vm_creation_requested = vm_creation_requested::VmCreationRequested {
         uid: ThreadState::get_calling_uid() as i32,
diff --git a/virtualizationservice/src/payload.rs b/virtualizationservice/src/payload.rs
index 675ca50..3efd7ac 100644
--- a/virtualizationservice/src/payload.rs
+++ b/virtualizationservice/src/payload.rs
@@ -15,14 +15,16 @@
 //! Payload disk image
 
 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
-    DiskImage::DiskImage, Partition::Partition, VirtualMachineAppConfig::DebugLevel::DebugLevel,
-    VirtualMachineAppConfig::VirtualMachineAppConfig,
+    DiskImage::DiskImage,
+    Partition::Partition,
+    VirtualMachineAppConfig::DebugLevel::DebugLevel,
+    VirtualMachineAppConfig::{Payload::Payload, VirtualMachineAppConfig},
     VirtualMachineRawConfig::VirtualMachineRawConfig,
 };
 use anyhow::{anyhow, bail, Context, Result};
 use binder::{wait_for_interface, ParcelFileDescriptor};
 use log::{info, warn};
-use microdroid_metadata::{ApexPayload, ApkPayload, Metadata};
+use microdroid_metadata::{ApexPayload, ApkPayload, Metadata, PayloadConfig, PayloadMetadata};
 use microdroid_payload_config::{ApexConfig, VmPayloadConfig};
 use once_cell::sync::OnceCell;
 use packagemanager_aidl::aidl::android::content::pm::IPackageManagerNative::IPackageManagerNative;
@@ -156,11 +158,22 @@
 }
 
 fn make_metadata_file(
-    config_path: &str,
+    app_config: &VirtualMachineAppConfig,
     apex_infos: &[&ApexInfo],
     temporary_directory: &Path,
 ) -> Result<ParcelFileDescriptor> {
-    let metadata_path = temporary_directory.join("metadata");
+    let payload_metadata = match &app_config.payload {
+        Payload::PayloadConfig(payload_config) => PayloadMetadata::config(PayloadConfig {
+            payload_binary_path: payload_config.payloadPath.clone(),
+            export_tombstones: payload_config.exportTombstones,
+            args: payload_config.args.clone().into(),
+            ..Default::default()
+        }),
+        Payload::ConfigPath(config_path) => {
+            PayloadMetadata::config_path(format!("/mnt/apk/{}", config_path))
+        }
+    };
+
     let metadata = Metadata {
         version: 1,
         apexes: apex_infos
@@ -183,11 +196,12 @@
             ..Default::default()
         })
         .into(),
-        payload_config_path: format!("/mnt/apk/{}", config_path),
+        payload: Some(payload_metadata),
         ..Default::default()
     };
 
     // Write metadata to file.
+    let metadata_path = temporary_directory.join("metadata");
     let mut metadata_file = OpenOptions::new()
         .create_new(true)
         .read(true)
@@ -235,8 +249,7 @@
         collect_apex_infos(&apex_list, &vm_payload_config.apexes, app_config.debugLevel);
     info!("Microdroid payload APEXes: {:?}", apex_infos.iter().map(|ai| &ai.name));
 
-    let metadata_file =
-        make_metadata_file(&app_config.configPath, &apex_infos, temporary_directory)?;
+    let metadata_file = make_metadata_file(app_config, &apex_infos, temporary_directory)?;
     // put metadata at the first partition
     let mut partitions = vec![Partition {
         label: "payload-metadata".to_owned(),
@@ -360,6 +373,21 @@
     vm_payload_config: &VmPayloadConfig,
     vm_config: &mut VirtualMachineRawConfig,
 ) -> Result<()> {
+    let debug_suffix = match config.debugLevel {
+        DebugLevel::NONE => "normal",
+        DebugLevel::APP_ONLY => "app_debuggable",
+        DebugLevel::FULL => "full_debuggable",
+        _ => return Err(anyhow!("unsupported debug level: {:?}", config.debugLevel)),
+    };
+    let initrd = format!("/apex/com.android.virt/etc/microdroid_initrd_{}.img", debug_suffix);
+    vm_config.initrd = Some(open_parcel_file(Path::new(&initrd), false)?);
+
+    let instance_img = Partition {
+        label: "vm-instance".to_owned(),
+        image: Some(ParcelFileDescriptor::new(instance_file)),
+        writable: true,
+    };
+    vm_config.disks.push(DiskImage { image: None, partitions: vec![instance_img], writable: true });
     vm_config.disks.push(make_payload_disk(
         config,
         apk_file,
@@ -368,34 +396,6 @@
         temporary_directory,
     )?);
 
-    vm_config.disks[1].partitions.push(Partition {
-        label: "vbmeta".to_owned(),
-        image: Some(open_parcel_file(
-            Path::new("/apex/com.android.virt/etc/fs/microdroid_vbmeta_bootconfig.img"),
-            false,
-        )?),
-        writable: false,
-    });
-    let bootconfig_image = "/apex/com.android.virt/etc/fs/microdroid_bootconfig.".to_owned()
-        + match config.debugLevel {
-            DebugLevel::NONE => "normal",
-            DebugLevel::APP_ONLY => "app_debuggable",
-            DebugLevel::FULL => "full_debuggable",
-            _ => return Err(anyhow!("unsupported debug level: {:?}", config.debugLevel)),
-        };
-    vm_config.disks[1].partitions.push(Partition {
-        label: "bootconfig".to_owned(),
-        image: Some(open_parcel_file(Path::new(&bootconfig_image), false)?),
-        writable: false,
-    });
-
-    // instance image is at the second partition in the second disk.
-    vm_config.disks[1].partitions.push(Partition {
-        label: "vm-instance".to_owned(),
-        image: Some(ParcelFileDescriptor::new(instance_file)),
-        writable: true,
-    });
-
     Ok(())
 }
 
diff --git a/vm/src/main.rs b/vm/src/main.rs
index 0845897..21cc74b 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -48,7 +48,7 @@
         instance: PathBuf,
 
         /// Path to VM config JSON within APK (e.g. assets/vm_config.json)
-        config_path: String,
+        config_path: Option<String>,
 
         /// Name of VM
         #[clap(long)]
@@ -205,7 +205,7 @@
             &apk,
             &idsig,
             &instance,
-            &config_path,
+            config_path.as_deref().unwrap_or(""),
             daemonize,
             console.as_deref(),
             log.as_deref(),
diff --git a/vm/src/run.rs b/vm/src/run.rs
index 44e15f9..c524c59 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -16,9 +16,10 @@
 
 use crate::create_partition::command_create_partition;
 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
-    IVirtualizationService::IVirtualizationService, PartitionType::PartitionType,
-    VirtualMachineAppConfig::DebugLevel::DebugLevel,
-    VirtualMachineAppConfig::VirtualMachineAppConfig, VirtualMachineConfig::VirtualMachineConfig,
+    IVirtualizationService::IVirtualizationService,
+    PartitionType::PartitionType,
+    VirtualMachineAppConfig::{DebugLevel::DebugLevel, Payload::Payload, VirtualMachineAppConfig},
+    VirtualMachineConfig::VirtualMachineConfig,
     VirtualMachineState::VirtualMachineState,
 };
 use anyhow::{bail, Context, Error};
@@ -96,7 +97,7 @@
         idsig: idsig_fd.into(),
         extraIdsigs: extra_idsig_fds,
         instanceImage: open_parcel_file(instance, true /* writable */)?.into(),
-        configPath: config_path.to_owned(),
+        payload: Payload::ConfigPath(config_path.to_owned()),
         debugLevel: debug_level,
         protectedVm: protected,
         memoryMib: mem.unwrap_or(0) as i32, // 0 means use the VM default