Merge "pvmfw.img: Generate an AVB-signed image"
diff --git a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
index 46b9e77..0eb1257 100644
--- a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
+++ b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
@@ -17,26 +17,15 @@
 package com.android.virt.fs;
 
 import static com.android.microdroid.test.host.CommandResultSubject.assertThat;
-import static com.android.microdroid.test.host.LogArchiver.archiveLogThenDelete;
-import static com.android.tradefed.device.TestDevice.MicrodroidBuilder;
-import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeTrue;
 
 import android.platform.test.annotations.RootPermissionTest;
 
-import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.compatibility.common.util.PollingCheck;
 import com.android.microdroid.test.host.CommandRunner;
-import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.device.TestDevice;
 import com.android.tradefed.invoker.TestInformation;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
@@ -45,19 +34,10 @@
 import com.android.tradefed.testtype.junit4.BeforeClassWithInfo;
 import com.android.tradefed.util.CommandResult;
 
-import org.junit.After;
-import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.TestName;
 import org.junit.runner.RunWith;
 
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.atomic.AtomicBoolean;
-
 @RootPermissionTest
 @RunWith(DeviceJUnit4ClassRunner.class)
 public final class AuthFsHostTest extends BaseHostJUnit4Test {
@@ -68,39 +48,15 @@
     /** Output directory where the test can generate output on Android */
     private static final String TEST_OUTPUT_DIR = "/data/local/tmp/authfs/output_dir";
 
-    /** VM's log file */
-    private static final String LOG_PATH = TEST_OUTPUT_DIR + "/log.txt";
-
-    /** File name of the test APK */
-    private static final String TEST_APK_NAME = "MicrodroidTestApp.apk";
-
-    /** VM config entry path in the test APK */
-    private static final String VM_CONFIG_PATH_IN_APK = "assets/vm_config.json";
-
-    /** Path to open_then_run on Android */
-    private static final String OPEN_THEN_RUN_BIN = "/data/local/tmp/open_then_run";
-
     /** Path to fsverity on Android */
     private static final String FSVERITY_BIN = "/data/local/tmp/fsverity";
 
     /** Mount point of authfs on Microdroid during the test */
     private static final String MOUNT_DIR = "/data/local/tmp";
 
-    /** Path to fd_server on Android */
-    private static final String FD_SERVER_BIN = "/apex/com.android.virt/bin/fd_server";
-
-    /** Path to authfs on Microdroid */
-    private static final String AUTHFS_BIN = "/system/bin/authfs";
-
     /** Input manifest path in the VM. */
     private static final String INPUT_MANIFEST_PATH = "/mnt/apk/assets/input_manifest.pb";
 
-    /** Plenty of time for authfs to get ready */
-    private static final int AUTHFS_INIT_TIMEOUT_MS = 3000;
-
-    /** FUSE's magic from statfs(2) */
-    private static final String FUSE_SUPER_MAGIC_HEX = "65735546";
-
     // fs-verity digest (sha256) of testdata/input.{4k, 4k1, 4m}
     private static final String DIGEST_4K =
             "sha256-9828cd65f4744d6adda216d3a63d8205375be485bfa261b3b8153d3358f5a576";
@@ -113,86 +69,20 @@
 
     private static CommandRunner sAndroid;
     private static CommandRunner sMicrodroid;
-    private static boolean sAssumptionFailed;
 
-    private ExecutorService mThreadPool = Executors.newCachedThreadPool();
-
-    @Rule public TestLogData mTestLogs = new TestLogData();
-    @Rule public TestName mTestName = new TestName();
+    @Rule public final AuthFsTestRule mAuthFsTestRule = new AuthFsTestRule();
 
     @BeforeClassWithInfo
     public static void beforeClassWithDevice(TestInformation testInfo) throws Exception {
-        assertNotNull(testInfo.getDevice());
-        if (!(testInfo.getDevice() instanceof TestDevice)) {
-            CLog.w("Unexpected type of ITestDevice. Skipping.");
-            return;
-        }
-        TestDevice androidDevice = (TestDevice) testInfo.getDevice();
-        sAndroid = new CommandRunner(androidDevice);
-
-        // NB: We can't use assumeTrue because the assumption exception is NOT handled by the test
-        // infra when it is thrown from a class method (see b/37502066). We need to skip both here
-        // and in setUp.
-        if (!androidDevice.supportsMicrodroid()) {
-            CLog.i("Microdroid not supported. Skipping.");
-            return;
-        }
-
-        // For each test case, boot and adb connect to a new Microdroid
-        CLog.i("Starting the shared VM");
-        ITestDevice microdroidDevice =
-                MicrodroidBuilder
-                        .fromFile(findTestApk(testInfo.getBuildInfo()), VM_CONFIG_PATH_IN_APK)
-                        .debugLevel("full")
-                        .build((TestDevice) androidDevice);
-
-        // From this point on, we need to tear down the Microdroid instance
-        sMicrodroid = new CommandRunner(microdroidDevice);
-
-        // Root because authfs (started from shell in this test) currently require root to open
-        // /dev/fuse and mount the FUSE.
-        assertThat(microdroidDevice.enableAdbRoot()).isTrue();
+        AuthFsTestRule.setUpClass(testInfo);
+        sAndroid = AuthFsTestRule.getAndroid();
+        sMicrodroid = AuthFsTestRule.getMicrodroid();
     }
 
     @AfterClassWithInfo
     public static void afterClassWithDevice(TestInformation testInfo)
             throws DeviceNotAvailableException {
-        assertNotNull(sAndroid);
-
-        if (sMicrodroid != null) {
-            CLog.i("Shutting down shared VM");
-            ((TestDevice) testInfo.getDevice()).shutdownMicrodroid(sMicrodroid.getDevice());
-            sMicrodroid = null;
-        }
-
-        sAndroid = null;
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        assumeTrue(((TestDevice) getDevice()).supportsMicrodroid());
-        sAndroid.run("mkdir " + TEST_OUTPUT_DIR);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        if (sMicrodroid != null) {
-            sMicrodroid.tryRun("killall authfs");
-            sMicrodroid.tryRun("umount " + MOUNT_DIR);
-        }
-
-        assertNotNull(sAndroid);
-        sAndroid.tryRun("killall fd_server");
-
-        // Even though we only run one VM for the whole class, and could have collect the VM log
-        // after all tests are done, TestLogData doesn't seem to work at class level. Hence,
-        // collect recent logs manually for each test method.
-        String vmRecentLog = TEST_OUTPUT_DIR + "/vm_recent.log";
-        sAndroid.tryRun("tail -n 50 " + LOG_PATH + " > " + vmRecentLog);
-        archiveLogThenDelete(mTestLogs, getDevice(), vmRecentLog,
-                "vm_recent.log-" + mTestName.getMethodName());
-
-        sAndroid.run("rm -rf " + TEST_OUTPUT_DIR);
+        AuthFsTestRule.tearDownClass(testInfo);
     }
 
     @Test
@@ -534,7 +424,7 @@
         sAndroid.run("test ! -d " + androidOutputDir + "/dir/dir2");
         // Can only delete a directory if empty
         assertThat(sMicrodroid.runForResult("rmdir " + authfsOutputDir + "/dir")).isFailed();
-        sMicrodroid.run("test -d " + authfsOutputDir + "/dir");  // still there
+        sMicrodroid.run("test -d " + authfsOutputDir + "/dir"); // still there
         sMicrodroid.run("rm " + authfsOutputDir + "/dir/file");
         sMicrodroid.run("rmdir " + authfsOutputDir + "/dir");
         sMicrodroid.run("test ! -d " + authfsOutputDir + "/dir");
@@ -560,9 +450,9 @@
         assertThat(sMicrodroid.runForResult("mkdir " + authfsOutputDir + "/some_file")).isFailed();
         assertThat(sMicrodroid.runForResult("mkdir " + authfsOutputDir + "/some_dir")).isFailed();
         assertThat(sMicrodroid.runForResult("mkdir " + authfsOutputDir + "/some_dir/file"))
-            .isFailed();
+                .isFailed();
         assertThat(sMicrodroid.runForResult("mkdir " + authfsOutputDir + "/some_dir/dir"))
-            .isFailed();
+                .isFailed();
     }
 
     @Test
@@ -741,16 +631,8 @@
         // Verify
         // Magic matches. Has only 2 inodes (root and "/3").
         assertEquals(
-                FUSE_SUPER_MAGIC_HEX + " 2", sMicrodroid.run("stat -f -c '%t %c' " + MOUNT_DIR));
-    }
-
-    private static File findTestApk(IBuildInfo buildInfo) {
-        try {
-            return (new CompatibilityBuildHelper(buildInfo)).getTestFile(TEST_APK_NAME);
-        } catch (FileNotFoundException e) {
-            fail("Missing test file: " + TEST_APK_NAME);
-            return null;
-        }
+                mAuthFsTestRule.FUSE_SUPER_MAGIC_HEX + " 2",
+                sMicrodroid.run("stat -f -c '%t %c' " + MOUNT_DIR));
     }
 
     private void expectBackingFileConsistency(
@@ -832,66 +714,11 @@
     }
 
     private void runAuthFsOnMicrodroid(String flags) {
-        String cmd = AUTHFS_BIN + " " + MOUNT_DIR + " " + flags;
-
-        AtomicBoolean starting = new AtomicBoolean(true);
-        mThreadPool.submit(
-                () -> {
-                    // authfs may fail to start if fd_server is not yet listening on the vsock
-                    // ("Error: Invalid raw AIBinder"). Just restart if that happens.
-                    while (starting.get()) {
-                        try {
-                            CLog.i("Starting authfs");
-                            CommandResult result = sMicrodroid.runForResult(cmd);
-                            CLog.w("authfs has stopped: " + result);
-                        } catch (DeviceNotAvailableException e) {
-                            CLog.e("Error running authfs", e);
-                            throw new RuntimeException(e);
-                        }
-                    }
-                });
-        try {
-            PollingCheck.waitFor(
-                    AUTHFS_INIT_TIMEOUT_MS, () -> isMicrodroidDirectoryOnFuse(MOUNT_DIR));
-        } catch (Exception e) {
-            // Convert the broad Exception into an unchecked exception to avoid polluting all other
-            // methods. waitFor throws Exception because the callback, Callable#call(), has a
-            // signature to throw an Exception.
-            throw new RuntimeException(e);
-        } finally {
-            starting.set(false);
-        }
+        mAuthFsTestRule.runAuthFsOnMicrodroid(flags);
     }
 
     private void runFdServerOnAndroid(String helperFlags, String fdServerFlags)
             throws DeviceNotAvailableException {
-        String cmd =
-                "cd "
-                        + TEST_DIR
-                        + " && "
-                        + OPEN_THEN_RUN_BIN
-                        + " "
-                        + helperFlags
-                        + " -- "
-                        + FD_SERVER_BIN
-                        + " "
-                        + fdServerFlags;
-
-        mThreadPool.submit(
-                () -> {
-                    try {
-                        CLog.i("Starting fd_server");
-                        CommandResult result = sAndroid.runForResult(cmd);
-                        CLog.w("fd_server has stopped: " + result);
-                    } catch (DeviceNotAvailableException e) {
-                        CLog.e("Error running fd_server", e);
-                        throw new RuntimeException(e);
-                    }
-                });
-    }
-
-    private boolean isMicrodroidDirectoryOnFuse(String path) throws DeviceNotAvailableException {
-        String fs_type = sMicrodroid.tryRun("stat -f -c '%t' " + path);
-        return FUSE_SUPER_MAGIC_HEX.equals(fs_type);
+        mAuthFsTestRule.runFdServerOnAndroid(helperFlags, fdServerFlags);
     }
 }
diff --git a/authfs/tests/java/src/com/android/fs/AuthFsTestRule.java b/authfs/tests/java/src/com/android/fs/AuthFsTestRule.java
new file mode 100644
index 0000000..e6081f7
--- /dev/null
+++ b/authfs/tests/java/src/com/android/fs/AuthFsTestRule.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 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 com.android.virt.fs;
+
+import static com.android.microdroid.test.host.LogArchiver.archiveLogThenDelete;
+import static com.android.tradefed.device.TestDevice.MicrodroidBuilder;
+import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.compatibility.common.util.PollingCheck;
+import com.android.microdroid.test.host.CommandRunner;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.device.TestDevice;
+import com.android.tradefed.invoker.TestInformation;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.util.CommandResult;
+
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/** Custom TestRule for AuthFs tests. */
+public class AuthFsTestRule extends TestLogData {
+    /** FUSE's magic from statfs(2) */
+    static final String FUSE_SUPER_MAGIC_HEX = "65735546";
+
+    /** VM config entry path in the test APK */
+    private static final String VM_CONFIG_PATH_IN_APK = "assets/vm_config.json";
+
+    /** Test directory on Android where data are located */
+    private static final String TEST_DIR = "/data/local/tmp/authfs";
+
+    /** File name of the test APK */
+    private static final String TEST_APK_NAME = "MicrodroidTestApp.apk";
+
+    /** Output directory where the test can generate output on Android */
+    private static final String TEST_OUTPUT_DIR = "/data/local/tmp/authfs/output_dir";
+
+    /** Mount point of authfs on Microdroid during the test */
+    private static final String MOUNT_DIR = "/data/local/tmp";
+
+    /** VM's log file */
+    private static final String LOG_PATH = TEST_OUTPUT_DIR + "/log.txt";
+
+    /** Path to open_then_run on Android */
+    private static final String OPEN_THEN_RUN_BIN = "/data/local/tmp/open_then_run";
+
+    /** Path to fd_server on Android */
+    private static final String FD_SERVER_BIN = "/apex/com.android.virt/bin/fd_server";
+
+    /** Path to authfs on Microdroid */
+    private static final String AUTHFS_BIN = "/system/bin/authfs";
+
+    /** Plenty of time for authfs to get ready */
+    private static final int AUTHFS_INIT_TIMEOUT_MS = 3000;
+
+    private static TestInformation sTestInfo;
+    private static CommandRunner sAndroid;
+    private static CommandRunner sMicrodroid;
+
+    private final ExecutorService mThreadPool = Executors.newCachedThreadPool();
+
+    static void setUpClass(TestInformation testInfo) throws Exception {
+        assertNotNull(testInfo.getDevice());
+        if (!(testInfo.getDevice() instanceof TestDevice)) {
+            CLog.w("Unexpected type of ITestDevice. Skipping.");
+            return;
+        }
+        sTestInfo = testInfo;
+        TestDevice androidDevice = getDevice();
+        sAndroid = new CommandRunner(androidDevice);
+
+        // NB: We can't use assumeTrue because the assumption exception is NOT handled by the test
+        // infra when it is thrown from a class method (see b/37502066). We need to skip both here
+        // and in setUp.
+        if (!androidDevice.supportsMicrodroid()) {
+            CLog.i("Microdroid not supported. Skipping.");
+            return;
+        }
+
+        // For each test case, boot and adb connect to a new Microdroid
+        CLog.i("Starting the shared VM");
+        ITestDevice microdroidDevice =
+                MicrodroidBuilder.fromFile(
+                                findTestApk(testInfo.getBuildInfo()), VM_CONFIG_PATH_IN_APK)
+                        .debugLevel("full")
+                        .build((TestDevice) androidDevice);
+
+        // From this point on, we need to tear down the Microdroid instance
+        sMicrodroid = new CommandRunner(microdroidDevice);
+
+        // Root because authfs (started from shell in this test) currently require root to open
+        // /dev/fuse and mount the FUSE.
+        assertThat(microdroidDevice.enableAdbRoot()).isTrue();
+    }
+
+    static void tearDownClass(TestInformation testInfo) throws DeviceNotAvailableException {
+        assertNotNull(sAndroid);
+
+        if (sMicrodroid != null) {
+            CLog.i("Shutting down shared VM");
+            ((TestDevice) testInfo.getDevice()).shutdownMicrodroid(sMicrodroid.getDevice());
+            sMicrodroid = null;
+        }
+
+        sAndroid = null;
+    }
+
+    /** This method is supposed to be called after {@link #setUpTest()}. */
+    static CommandRunner getAndroid() {
+        assertThat(sAndroid).isNotNull();
+        return sAndroid;
+    }
+
+    /** This method is supposed to be called after {@link #setUpTest()}. */
+    static CommandRunner getMicrodroid() {
+        assertThat(sMicrodroid).isNotNull();
+        return sMicrodroid;
+    }
+
+    @Override
+    public Statement apply(final Statement base, Description description) {
+        return super.apply(
+                new Statement() {
+                    @Override
+                    public void evaluate() throws Throwable {
+                        setUpTest();
+                        base.evaluate();
+                        tearDownTest(description.getMethodName());
+                    }
+                },
+                description);
+    }
+
+    void runFdServerOnAndroid(String helperFlags, String fdServerFlags)
+            throws DeviceNotAvailableException {
+        String cmd =
+                "cd "
+                        + TEST_DIR
+                        + " && "
+                        + OPEN_THEN_RUN_BIN
+                        + " "
+                        + helperFlags
+                        + " -- "
+                        + FD_SERVER_BIN
+                        + " "
+                        + fdServerFlags;
+        Future<?> unusedFuture = mThreadPool.submit(() -> runForResult(sAndroid, cmd, "fd_server"));
+    }
+
+    void runAuthFsOnMicrodroid(String flags) {
+        String cmd = AUTHFS_BIN + " " + MOUNT_DIR + " " + flags;
+
+        AtomicBoolean starting = new AtomicBoolean(true);
+        Future<?> unusedFuture =
+                mThreadPool.submit(
+                        () -> {
+                            // authfs may fail to start if fd_server is not yet listening on the
+                            // vsock
+                            // ("Error: Invalid raw AIBinder"). Just restart if that happens.
+                            while (starting.get()) {
+                                runForResult(sMicrodroid, cmd, "authfs");
+                            }
+                        });
+        try {
+            PollingCheck.waitFor(
+                    AUTHFS_INIT_TIMEOUT_MS, () -> isMicrodroidDirectoryOnFuse(MOUNT_DIR));
+        } catch (Exception e) {
+            // Convert the broad Exception into an unchecked exception to avoid polluting all other
+            // methods. waitFor throws Exception because the callback, Callable#call(), has a
+            // signature to throw an Exception.
+            throw new RuntimeException(e);
+        } finally {
+            starting.set(false);
+        }
+    }
+
+    private static File findTestApk(IBuildInfo buildInfo) {
+        try {
+            return (new CompatibilityBuildHelper(buildInfo)).getTestFile(TEST_APK_NAME);
+        } catch (FileNotFoundException e) {
+            fail("Missing test file: " + TEST_APK_NAME);
+            return null;
+        }
+    }
+
+    private static TestDevice getDevice() {
+        return (TestDevice) sTestInfo.getDevice();
+    }
+
+    private void runForResult(CommandRunner cmdRunner, String cmd, String serviceName) {
+        try {
+            CLog.i("Starting " + serviceName);
+            CommandResult result = cmdRunner.runForResult(cmd);
+            CLog.w(serviceName + " has stopped: " + result);
+        } catch (DeviceNotAvailableException e) {
+            CLog.e("Error running " + serviceName, e);
+            throw new RuntimeException(e);
+        }
+    }
+
+    private boolean isMicrodroidDirectoryOnFuse(String path) throws DeviceNotAvailableException {
+        String fs_type = sMicrodroid.tryRun("stat -f -c '%t' " + path);
+        return FUSE_SUPER_MAGIC_HEX.equals(fs_type);
+    }
+
+    private void setUpTest() throws Exception {
+        assumeTrue(getDevice().supportsMicrodroid());
+        sAndroid.run("mkdir -p " + TEST_OUTPUT_DIR);
+    }
+
+    private void tearDownTest(String testName) throws Exception {
+        if (sMicrodroid != null) {
+            sMicrodroid.tryRun("killall authfs");
+            sMicrodroid.tryRun("umount " + MOUNT_DIR);
+        }
+
+        assertNotNull(sAndroid);
+        sAndroid.tryRun("killall fd_server");
+
+        // Even though we only run one VM for the whole class, and could have collect the VM log
+        // after all tests are done, TestLogData doesn't seem to work at class level. Hence,
+        // collect recent logs manually for each test method.
+        String vmRecentLog = TEST_OUTPUT_DIR + "/vm_recent.log";
+        sAndroid.tryRun("tail -n 50 " + LOG_PATH + " > " + vmRecentLog);
+        archiveLogThenDelete(this, getDevice(), vmRecentLog, "vm_recent.log-" + testName);
+
+        sAndroid.run("rm -rf " + TEST_OUTPUT_DIR);
+    }
+}
diff --git a/compos/compos_key_helper/Android.bp b/compos/compos_key_helper/Android.bp
index a932b40..fdfcfc1 100644
--- a/compos/compos_key_helper/Android.bp
+++ b/compos/compos_key_helper/Android.bp
@@ -24,10 +24,11 @@
     defaults: ["compos_key_defaults"],
     srcs: ["compos_key_main.cpp"],
 
-    static_libs: ["libcompos_key"],
+    static_libs: [
+        "libcompos_key",
+        "libvm_payload",
+    ],
     shared_libs: [
-        "android.hardware.security.dice-V1-ndk",
-        "android.security.dice-ndk",
         "libbinder_ndk",
     ],
 }
diff --git a/compos/compos_key_helper/compos_key_main.cpp b/compos/compos_key_helper/compos_key_main.cpp
index 9ba9f8d..77a9cf9 100644
--- a/compos/compos_key_helper/compos_key_main.cpp
+++ b/compos/compos_key_helper/compos_key_main.cpp
@@ -14,21 +14,15 @@
  * limitations under the License.
  */
 
-#include <aidl/android/security/dice/IDiceNode.h>
 #include <android-base/file.h>
 #include <android-base/logging.h>
-#include <android/binder_auto_utils.h>
-#include <android/binder_manager.h>
 #include <unistd.h>
+#include <vm_payload.h>
 
 #include <string_view>
 
 #include "compos_key.h"
 
-using aidl::android::hardware::security::dice::Bcc;
-using aidl::android::hardware::security::dice::BccHandover;
-using aidl::android::hardware::security::dice::InputValues;
-using aidl::android::security::dice::IDiceNode;
 using android::base::Error;
 using android::base::ReadFdToString;
 using android::base::Result;
@@ -38,22 +32,15 @@
 
 namespace {
 Result<Ed25519KeyPair> deriveKeyFromDice() {
-    ndk::SpAIBinder binder{AServiceManager_getService("android.security.dice.IDiceNode")};
-    auto dice_node = IDiceNode::fromBinder(binder);
-    if (!dice_node) {
-        return Error() << "Unable to connect to IDiceNode";
-    }
-
-    const std::vector<InputValues> empty_input_values;
-    BccHandover bcc;
-    auto status = dice_node->derive(empty_input_values, &bcc);
-    if (!status.isOk()) {
-        return Error() << "Derive failed: " << status.getDescription();
+    uint8_t cdi_seal[64];
+    size_t cdi_size = get_dice_sealing_cdi(cdi_seal, sizeof(cdi_seal));
+    if (cdi_size == 0) {
+        return Error() << "Failed to get sealing CDI";
     }
 
     // We use the sealing CDI because we want stability - the key needs to be the same
     // for any instance of the "same" VM.
-    return compos_key::deriveKeyFromSecret(bcc.cdiSeal.data(), bcc.cdiSeal.size());
+    return compos_key::deriveKeyFromSecret(cdi_seal, cdi_size);
 }
 
 int write_public_key() {
@@ -70,22 +57,14 @@
 }
 
 int write_bcc() {
-    ndk::SpAIBinder binder{AServiceManager_getService("android.security.dice.IDiceNode")};
-    auto dice_node = IDiceNode::fromBinder(binder);
-    if (!dice_node) {
-        LOG(ERROR) << "Unable to connect to IDiceNode";
+    uint8_t bcc[2048];
+    size_t bcc_size = get_dice_attestation_chain(bcc, sizeof(bcc));
+    if (bcc_size == 0) {
+        LOG(ERROR) << "Failed to get attestation chain";
         return 1;
     }
 
-    const std::vector<InputValues> empty_input_values;
-    Bcc bcc;
-    auto status = dice_node->getAttestationChain(empty_input_values, &bcc);
-    if (!status.isOk()) {
-        LOG(ERROR) << "GetAttestationChain failed: " << status.getDescription();
-        return 1;
-    }
-
-    if (!WriteFully(STDOUT_FILENO, bcc.data.data(), bcc.data.size())) {
+    if (!WriteFully(STDOUT_FILENO, bcc, bcc_size)) {
         PLOG(ERROR) << "Write failed";
         return 1;
     }
diff --git a/docs/getting_started/index.md b/docs/getting_started/index.md
index 7fddc5a..34a990d 100644
--- a/docs/getting_started/index.md
+++ b/docs/getting_started/index.md
@@ -118,7 +118,7 @@
 on pVM. You can manually run the demo app on top of Microdroid as follows:
 
 ```shell
-TARGET_BUILD_APPS=MicrodroidDemoApp m apps_only dist
+UNBUNDLED_BUILD_SDKS_FROM_SOURCE=true TARGET_BUILD_APPS=MicrodroidDemoApp m apps_only dist
 adb shell mkdir -p /data/local/tmp/virt
 adb push out/dist/MicrodroidDemoApp.apk /data/local/tmp/virt/
 adb shell /apex/com.android.virt/bin/vm run-app \
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index 10e5858..81f94bc 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -70,7 +70,6 @@
         "apexd",
         "atrace",
         "debuggerd",
-        "dice-service.microdroid",
         "linker",
         "linkerconfig",
         "servicemanager.microdroid",
@@ -80,7 +79,6 @@
         "task_profiles.json",
         "public.libraries.android.txt",
 
-        "microdroid_compatibility_matrix",
         "microdroid_event-log-tags",
         "microdroid_file_contexts",
         "microdroid_manifest",
@@ -533,14 +531,6 @@
 }
 
 prebuilt_etc {
-    name: "microdroid_compatibility_matrix",
-    src: "microdroid_compatibility_matrix.xml",
-    filename: "compatibility_matrix.current.xml",
-    relative_install_path: "vintf",
-    installable: false,
-}
-
-prebuilt_etc {
     name: "microdroid_manifest",
     src: "microdroid_manifest.xml",
     filename: "manifest.xml",
diff --git a/microdroid/dice/Android.bp b/microdroid/dice/Android.bp
deleted file mode 100644
index 859533e..0000000
--- a/microdroid/dice/Android.bp
+++ /dev/null
@@ -1,26 +0,0 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-rust_binary {
-    name: "dice-service.microdroid",
-    srcs: ["service.rs"],
-    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_utils",
-        "liblibc",
-        "liblog_rust",
-        "libserde",
-    ],
-    init_rc: ["dice-service.microdroid.rc"],
-    bootstrap: true,
-}
diff --git a/microdroid/dice/dice-service.microdroid.rc b/microdroid/dice/dice-service.microdroid.rc
deleted file mode 100644
index 5bcb049..0000000
--- a/microdroid/dice/dice-service.microdroid.rc
+++ /dev/null
@@ -1,3 +0,0 @@
-service dice-microdroid /system/bin/dice-service.microdroid
-    user diced
-    group diced
diff --git a/microdroid/dice/service.rs b/microdroid/dice/service.rs
deleted file mode 100644
index 2c19481..0000000
--- a/microdroid/dice/service.rs
+++ /dev/null
@@ -1,303 +0,0 @@
-// Copyright 2022, The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-//! Main entry point for the microdroid DICE service implementation.
-
-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;
-use std::os::unix::io::AsRawFd;
-use std::panic;
-use std::path::{Path, PathBuf};
-use std::ptr::null_mut;
-use std::slice;
-use std::sync::{Arc, RwLock};
-
-const AVF_STRICT_BOOT: &str = "/sys/firmware/devicetree/base/chosen/avf,strict-boot";
-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> {
-    mmap_addr: *mut c_void,
-    mmap_size: usize,
-    cdi_attest: &'a [u8; dice::CDI_SIZE],
-    cdi_seal: &'a [u8; dice::CDI_SIZE],
-    bcc: &'a [u8],
-}
-
-impl MappedDriverArtifacts<'_> {
-    fn new(driver_path: &Path) -> Result<Self> {
-        let mut file = fs::File::open(driver_path)
-            .map_err(|error| Error::new(error).context("Opening driver"))?;
-        let mmap_size =
-            file.read_u64::<NativeEndian>()
-                .map_err(|error| Error::new(error).context("Reading driver"))? as usize;
-        // It's safe to map the driver as the service will only create a single
-        // mapping per process.
-        let mmap_addr = unsafe {
-            let fd = file.as_raw_fd();
-            mmap(null_mut(), mmap_size, PROT_READ, MAP_PRIVATE, fd, 0)
-        };
-        if mmap_addr == MAP_FAILED {
-            bail!("Failed to mmap {:?}", driver_path);
-        }
-        // The slice is created for the region of memory that was just
-        // successfully mapped into the process address space so it will be
-        // accessible and not referenced from anywhere else.
-        let mmap_buf =
-            unsafe { slice::from_raw_parts((mmap_addr as *const u8).as_ref().unwrap(), mmap_size) };
-        // Very inflexible parsing / validation of the BccHandover data. Assumes deterministically
-        // encoded CBOR.
-        //
-        // BccHandover = {
-        //   1 : bstr .size 32,     ; CDI_Attest
-        //   2 : bstr .size 32,     ; CDI_Seal
-        //   3 : Bcc,               ; Certificate chain
-        // }
-        if mmap_buf[0..4] != [0xa3, 0x01, 0x58, 0x20]
-            || mmap_buf[36..39] != [0x02, 0x58, 0x20]
-            || mmap_buf[71] != 0x03
-        {
-            bail!("BccHandover format mismatch");
-        }
-        Ok(Self {
-            mmap_addr,
-            mmap_size,
-            cdi_attest: mmap_buf[4..36].try_into().unwrap(),
-            cdi_seal: mmap_buf[39..71].try_into().unwrap(),
-            bcc: &mmap_buf[72..],
-        })
-    }
-}
-
-impl Drop for MappedDriverArtifacts<'_> {
-    fn drop(&mut self) {
-        // All references to the mapped region have the same lifetime as self.
-        // Since self is being dropped, so are all the references to the mapped
-        // region meaning its safe to unmap.
-        let ret = unsafe { munmap(self.mmap_addr, self.mmap_size) };
-        if ret != 0 {
-            log::warn!("Failed to munmap ({})", ret);
-        }
-    }
-}
-
-/// Artifacts that are kept in the process address space after the artifacts
-/// from the driver have been consumed.
-#[derive(Clone, Serialize, Deserialize)]
-struct RawArtifacts {
-    cdi_attest: [u8; dice::CDI_SIZE],
-    cdi_seal: [u8; dice::CDI_SIZE],
-    bcc: Vec<u8>,
-}
-
-#[derive(Clone, Serialize, Deserialize)]
-enum UpdatableArtifacts {
-    Invalid,
-    Driver(PathBuf),
-    Updated(RawArtifacts),
-}
-
-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) => {
-                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, inputs: &BinderInputValues) -> Result<Self> {
-        if let Self::Invalid = self {
-            bail!("Cannot update invalid DICE artifacts.");
-        }
-        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: 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("dice").with_min_level(log::Level::Debug),
-    );
-    // Redirect panic messages to logcat.
-    panic::set_hook(Box::new(|panic_info| {
-        log::error!("{}", panic_info);
-    }));
-
-    // Saying hi.
-    log::info!("DICE service is starting.");
-
-    let node_impl = Arc::new(ArtifactManager::new(Path::new("/dev/open-dice0")));
-
-    let node = DiceNode::new_as_binder(node_impl.clone())
-        .expect("Failed to create IDiceNode service instance.");
-
-    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 47002c9..2b4295f 100644
--- a/microdroid/init.rc
+++ b/microdroid/init.rc
@@ -67,8 +67,6 @@
 
     start servicemanager
 
-    start dice-microdroid
-
 on init
     mkdir /mnt/apk 0755 system system
     mkdir /mnt/extra-apk 0755 root root
@@ -177,7 +175,7 @@
     mkdir /data/local/tmp 0771 shell shell
 
 service tombstone_transmit /system/bin/tombstone_transmit.microdroid -cid 2 -port 2000 -remove_tombstones_after_transmitting
-    user root
+    user system
     group system
     shutdown critical
 
@@ -186,12 +184,14 @@
     group system
     oneshot
     disabled
+    capabilities CHOWN DAC_OVERRIDE DAC_READ_SEARCH FOWNER SYS_ADMIN
 
 service ueventd /system/bin/ueventd
     class core
     critical
     seclabel u:r:ueventd:s0
     shutdown critical
+    capabilities CHOWN DAC_OVERRIDE DAC_READ_SEARCH FOWNER FSETID MKNOD NET_ADMIN SETGID SETUID SYS_MODULE SYS_RAWIO
 
 service console /system/bin/sh
     class core
diff --git a/microdroid/microdroid_compatibility_matrix.xml b/microdroid/microdroid_compatibility_matrix.xml
deleted file mode 100644
index a345e30..0000000
--- a/microdroid/microdroid_compatibility_matrix.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<compatibility-matrix version="1.0" type="framework">
-    <hal format="aidl" optional="true">
-        <name>android.hardware.security.dice</name>
-        <version>1</version>
-        <interface>
-            <name>IDiceDevice</name>
-            <instance>default</instance>
-        </interface>
-    </hal>
-</compatibility-matrix>
diff --git a/microdroid/payload/metadata.proto b/microdroid/payload/metadata.proto
index 229d03f..06cbbf4 100644
--- a/microdroid/payload/metadata.proto
+++ b/microdroid/payload/metadata.proto
@@ -68,12 +68,8 @@
   // 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;
+  repeated string args = 2;
 }
diff --git a/microdroid/ueventd.rc b/microdroid/ueventd.rc
index fc165c8..268d3a2 100644
--- a/microdroid/ueventd.rc
+++ b/microdroid/ueventd.rc
@@ -28,4 +28,4 @@
 # Virtual console for logcat
 /dev/hvc2                 0666   system     system
 
-/dev/open-dice0           0660   diced      diced
+/dev/open-dice0           0660   root       root
diff --git a/microdroid/vm_payload/Android.bp b/microdroid/vm_payload/Android.bp
new file mode 100644
index 0000000..f7223ab
--- /dev/null
+++ b/microdroid/vm_payload/Android.bp
@@ -0,0 +1,21 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_ffi_static {
+    name: "libvm_payload",
+    crate_name: "vm_payload",
+    srcs: ["src/*.rs"],
+    include_dirs: ["include"],
+    prefer_rlib: true,
+    rustlibs: [
+        "android.system.virtualization.payload-rust",
+        "libandroid_logger",
+        "libanyhow",
+        "libbinder_rs",
+        "liblog_rust",
+    ],
+    apex_available: [
+        "com.android.compos",
+    ],
+}
diff --git a/microdroid/vm_payload/include/vm_payload.h b/microdroid/vm_payload/include/vm_payload.h
new file mode 100644
index 0000000..6dba760
--- /dev/null
+++ b/microdroid/vm_payload/include/vm_payload.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 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.
+ */
+
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Notifies the host that the payload is ready.
+ * Returns true if the notification succeeds else false.
+ */
+bool notify_payload_ready();
+
+/**
+ * Get the VM's attestation chain.
+ * Returns the size of data or 0 on failure.
+ * TODO: don't expose the contained privacy breaking identifiers to the payload
+ * TODO: keep the DICE chain as an internal detail for as long as possible
+ */
+size_t get_dice_attestation_chain(void *data, size_t size);
+
+/**
+ * Get the VM's attestation CDI.
+ * Returns the size of data or 0 on failure.
+ * TODO: don't expose the raw CDI, only derived values
+ */
+size_t get_dice_attestation_cdi(void *data, size_t size);
+
+/**
+ * Get the VM's sealing CDI.
+ * Returns the size of data or 0 on failure.
+ * TODO: don't expose the raw CDI, only derived values
+ */
+size_t get_dice_sealing_cdi(void *data, size_t size);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
diff --git a/microdroid/vm_payload/src/lib.rs b/microdroid/vm_payload/src/lib.rs
new file mode 100644
index 0000000..e3da227
--- /dev/null
+++ b/microdroid/vm_payload/src/lib.rs
@@ -0,0 +1,22 @@
+// 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.
+
+//! Library for payload to communicate with the VM service.
+
+mod vm_service;
+
+pub use vm_service::{
+    get_dice_attestation_cdi, get_dice_attestation_chain, get_dice_sealing_cdi,
+    notify_payload_ready,
+};
diff --git a/microdroid/vm_payload/src/vm_service.rs b/microdroid/vm_payload/src/vm_service.rs
new file mode 100644
index 0000000..18d8222
--- /dev/null
+++ b/microdroid/vm_payload/src/vm_service.rs
@@ -0,0 +1,132 @@
+// 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.
+
+//! This module handles the interaction with virtual machine payload service.
+
+use android_system_virtualization_payload::aidl::android::system::virtualization::payload::IVmPayloadService::{
+    IVmPayloadService, VM_PAYLOAD_SERVICE_NAME};
+use anyhow::{Context, Result};
+use binder::{wait_for_interface, Strong};
+use log::{error, info, Level};
+
+/// Notifies the host that the payload is ready.
+/// Returns true if the notification succeeds else false.
+#[no_mangle]
+pub extern "C" fn notify_payload_ready() -> bool {
+    android_logger::init_once(
+        android_logger::Config::default().with_tag("vm_payload").with_min_level(Level::Debug),
+    );
+    if let Err(e) = try_notify_payload_ready() {
+        error!("{:?}", e);
+        false
+    } else {
+        info!("Notified host payload ready successfully");
+        true
+    }
+}
+
+/// Notifies the host that the payload is ready.
+/// Returns a `Result` containing error information if failed.
+fn try_notify_payload_ready() -> Result<()> {
+    get_vm_payload_service()?.notifyPayloadReady().context("Cannot notify payload ready")
+}
+
+/// Get the VM's attestation chain.
+/// Returns the size of data or 0 on failure.
+///
+/// # Safety
+///
+/// The data must be size bytes big.
+#[no_mangle]
+pub unsafe extern "C" fn get_dice_attestation_chain(data: *mut u8, size: usize) -> usize {
+    match try_get_dice_attestation_chain() {
+        Err(e) => {
+            error!("{:?}", e);
+            0
+        }
+        Ok(chain) => {
+            if size < chain.len() {
+                0
+            } else {
+                std::ptr::copy_nonoverlapping(chain.as_ptr(), data, chain.len());
+                chain.len()
+            }
+        }
+    }
+}
+
+fn try_get_dice_attestation_chain() -> Result<Vec<u8>> {
+    get_vm_payload_service()?.getDiceAttestationChain().context("Cannot get attestation chain")
+}
+
+/// Get the VM's attestation CDI.
+/// Returns the size of data or 0 on failure.
+///
+/// # Safety
+///
+/// The data must be size bytes big.
+#[no_mangle]
+pub unsafe extern "C" fn get_dice_attestation_cdi(data: *mut u8, size: usize) -> usize {
+    match try_get_dice_attestation_cdi() {
+        Err(e) => {
+            error!("{:?}", e);
+            0
+        }
+        Ok(cdi) => {
+            if size < cdi.len() {
+                0
+            } else {
+                std::ptr::copy_nonoverlapping(cdi.as_ptr(), data, cdi.len());
+                cdi.len()
+            }
+        }
+    }
+}
+
+fn try_get_dice_attestation_cdi() -> Result<Vec<u8>> {
+    get_vm_payload_service()?.getDiceAttestationCdi().context("Cannot get attestation CDI")
+}
+
+/// Get the VM's sealing CDI.
+/// Returns the size of data or 0 on failure.
+///
+/// # Safety
+///
+/// The data must be size bytes big.
+#[no_mangle]
+pub unsafe extern "C" fn get_dice_sealing_cdi(data: *mut u8, size: usize) -> usize {
+    match try_get_dice_sealing_cdi() {
+        Err(e) => {
+            error!("{:?}", e);
+            0
+        }
+        Ok(cdi) => {
+            if size < cdi.len() {
+                0
+            } else {
+                std::ptr::copy_nonoverlapping(cdi.as_ptr(), data, cdi.len());
+                cdi.len()
+            }
+        }
+    }
+}
+
+fn try_get_dice_sealing_cdi() -> Result<Vec<u8>> {
+    get_vm_payload_service()?.getDiceSealingCdi().context("Cannot get sealing CDI")
+}
+
+fn get_vm_payload_service() -> Result<Strong<dyn IVmPayloadService>> {
+    wait_for_interface(VM_PAYLOAD_SERVICE_NAME)
+        .context(format!("Failed to connect to service: {}", VM_PAYLOAD_SERVICE_NAME))
+}
diff --git a/microdroid_manager/Android.bp b/microdroid_manager/Android.bp
index 67130b4..4b06b3e 100644
--- a/microdroid_manager/Android.bp
+++ b/microdroid_manager/Android.bp
@@ -9,16 +9,18 @@
     edition: "2021",
     prefer_rlib: true,
     rustlibs: [
-        "android.hardware.security.dice-V1-rust",
-        "android.security.dice-rust",
         "android.system.virtualizationcommon-rust",
         "android.system.virtualizationservice-rust",
         "android.system.virtualmachineservice-rust",
+        "android.system.virtualization.payload-rust",
         "libanyhow",
         "libapexutil_rust",
         "libapkverify",
         "libbinder_rs",
         "libbyteorder",
+        "libdiced",
+        "libdiced_open_dice_cbor",
+        "libdiced_sample_inputs",
         "libdiced_utils",
         "libglob",
         "libitertools",
diff --git a/microdroid_manager/aidl/Android.bp b/microdroid_manager/aidl/Android.bp
new file mode 100644
index 0000000..0aa8662
--- /dev/null
+++ b/microdroid_manager/aidl/Android.bp
@@ -0,0 +1,18 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+aidl_interface {
+    name: "android.system.virtualization.payload",
+    srcs: ["android/system/virtualization/payload/*.aidl"],
+    unstable: true,
+    backend: {
+        rust: {
+            enabled: true,
+            apex_available: [
+                "com.android.virt",
+                "com.android.compos",
+            ],
+        },
+    },
+}
diff --git a/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl b/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
new file mode 100644
index 0000000..9f56957
--- /dev/null
+++ b/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
@@ -0,0 +1,56 @@
+/*
+ * 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.virtualization.payload;
+
+/**
+ * This interface regroups the tasks that payloads delegate to
+ * Microdroid Manager for execution.
+ */
+interface IVmPayloadService {
+    /** Name of the service IVmPayloadService. */
+    const String VM_PAYLOAD_SERVICE_NAME = "virtual_machine_payload_service";
+
+    /** Notifies that the payload is ready to serve. */
+    void notifyPayloadReady();
+
+    /**
+     * Gets the DICE attestation chain for the VM.
+     *
+     * STOPSHIP:
+     * TODO: don't expose this to untrusted payloads as it contains privacy breaking identifiers.
+     */
+    byte[] getDiceAttestationChain();
+
+    /**
+     * Gets the DICE attestation CDI for the VM.
+     *
+     * STOPSHIP:
+     * TODO: A better API would handle key derivation on behalf of the payload so they can't forget
+     * to do it themselves. It also means the payload doesn't get the raw CDI so there's less chance
+     * of it leaking.
+     */
+    byte[] getDiceAttestationCdi();
+
+    /**
+     * Gets the DICE sealing CDI for the VM.
+     *
+     * TODO: A better API would handle key derivation on behalf of the payload so they can't forget
+     * to do it themselves. It also means the payload doesn't get the raw CDI so there's less chance
+     * of it leaking.
+     */
+    byte[] getDiceSealingCdi();
+}
diff --git a/microdroid_manager/microdroid_manager.rc b/microdroid_manager/microdroid_manager.rc
index 60d8ab7..74a219d 100644
--- a/microdroid_manager/microdroid_manager.rc
+++ b/microdroid_manager/microdroid_manager.rc
@@ -4,3 +4,5 @@
     setenv RUST_LOG info
     # TODO(jooyung) remove this when microdroid_manager becomes a daemon
     oneshot
+    # SYS_BOOT is required to exec kexecload from microdroid_manager
+    capabilities AUDIT_CONTROL SYS_ADMIN SYS_BOOT
diff --git a/microdroid_manager/src/dice.rs b/microdroid_manager/src/dice.rs
new file mode 100644
index 0000000..3881db3
--- /dev/null
+++ b/microdroid_manager/src/dice.rs
@@ -0,0 +1,177 @@
+// 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.
+
+//! Logic for handling the DICE values and boot operations.
+
+use anyhow::{bail, Context, Error, Result};
+use byteorder::{NativeEndian, ReadBytesExt};
+use diced_open_dice_cbor::{
+    Config, ContextImpl, InputValuesOwned, Mode, OpenDiceCborContext, CDI_SIZE, HASH_SIZE,
+    HIDDEN_SIZE,
+};
+use keystore2_crypto::ZVec;
+use libc::{c_void, mmap, munmap, MAP_FAILED, MAP_PRIVATE, PROT_READ};
+use openssl::hkdf::hkdf;
+use openssl::md::Md;
+use std::fs;
+use std::os::unix::io::AsRawFd;
+use std::path::{Path, PathBuf};
+use std::ptr::null_mut;
+use std::slice;
+
+/// Artifacts that are kept in the process address space after the artifacts from the driver have
+/// been consumed.
+pub struct DiceContext {
+    pub cdi_attest: [u8; CDI_SIZE],
+    pub cdi_seal: [u8; CDI_SIZE],
+    pub bcc: Vec<u8>,
+}
+
+/// Artifacts that are mapped into the process address space from the driver.
+pub enum DiceDriver<'a> {
+    Real {
+        driver_path: PathBuf,
+        mmap_addr: *mut c_void,
+        mmap_size: usize,
+        cdi_attest: &'a [u8; CDI_SIZE],
+        cdi_seal: &'a [u8; CDI_SIZE],
+        bcc: &'a [u8],
+    },
+    Fake(DiceContext),
+}
+
+impl DiceDriver<'_> {
+    pub fn new(driver_path: &Path) -> Result<Self> {
+        if driver_path.exists() {
+            log::info!("Using DICE values from driver");
+        } else if super::is_strict_boot() {
+            bail!("Strict boot requires DICE value from driver but none were found");
+        } 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.");
+            return Ok(Self::Fake(DiceContext {
+                cdi_attest: cdi_attest[..].try_into().unwrap(),
+                cdi_seal: cdi_seal[..].try_into().unwrap(),
+                bcc,
+            }));
+        };
+
+        let mut file = fs::File::open(driver_path)
+            .map_err(|error| Error::new(error).context("Opening driver"))?;
+        let mmap_size =
+            file.read_u64::<NativeEndian>()
+                .map_err(|error| Error::new(error).context("Reading driver"))? as usize;
+        // It's safe to map the driver as the service will only create a single
+        // mapping per process.
+        let mmap_addr = unsafe {
+            let fd = file.as_raw_fd();
+            mmap(null_mut(), mmap_size, PROT_READ, MAP_PRIVATE, fd, 0)
+        };
+        if mmap_addr == MAP_FAILED {
+            bail!("Failed to mmap {:?}", driver_path);
+        }
+        // The slice is created for the region of memory that was just
+        // successfully mapped into the process address space so it will be
+        // accessible and not referenced from anywhere else.
+        let mmap_buf =
+            unsafe { slice::from_raw_parts((mmap_addr as *const u8).as_ref().unwrap(), mmap_size) };
+        // Very inflexible parsing / validation of the BccHandover data. Assumes deterministically
+        // encoded CBOR.
+        //
+        // BccHandover = {
+        //   1 : bstr .size 32,     ; CDI_Attest
+        //   2 : bstr .size 32,     ; CDI_Seal
+        //   3 : Bcc,               ; Certificate chain
+        // }
+        if mmap_buf[0..4] != [0xa3, 0x01, 0x58, 0x20]
+            || mmap_buf[36..39] != [0x02, 0x58, 0x20]
+            || mmap_buf[71] != 0x03
+        {
+            bail!("BccHandover format mismatch");
+        }
+        Ok(Self::Real {
+            driver_path: driver_path.to_path_buf(),
+            mmap_addr,
+            mmap_size,
+            cdi_attest: mmap_buf[4..36].try_into().unwrap(),
+            cdi_seal: mmap_buf[39..71].try_into().unwrap(),
+            bcc: &mmap_buf[72..],
+        })
+    }
+
+    pub fn get_sealing_key(&self, identifier: &[u8]) -> Result<ZVec> {
+        // Deterministically derive a key to use for sealing data, rather than using the CDI
+        // directly, so we have the chance to rotate the key if needed. A salt isn't needed as the
+        // input key material is already cryptographically strong.
+        let cdi_seal = match self {
+            Self::Real { cdi_seal, .. } => cdi_seal,
+            Self::Fake(fake) => &fake.cdi_seal,
+        };
+        let salt = &[];
+        let mut key = ZVec::new(32)?;
+        hkdf(&mut key, Md::sha256(), cdi_seal, salt, identifier)?;
+        Ok(key)
+    }
+
+    pub fn derive(
+        self,
+        code_hash: [u8; HASH_SIZE],
+        config_desc: &[u8],
+        authority_hash: [u8; HASH_SIZE],
+        debug: bool,
+        hidden: [u8; HIDDEN_SIZE],
+    ) -> Result<DiceContext> {
+        let input_values = InputValuesOwned::new(
+            code_hash,
+            Config::Descriptor(config_desc),
+            authority_hash,
+            None,
+            if debug { Mode::Debug } else { Mode::Normal },
+            hidden,
+        );
+        let (cdi_attest, cdi_seal, bcc) = match &self {
+            Self::Real { cdi_attest, cdi_seal, bcc, .. } => (*cdi_attest, *cdi_seal, *bcc),
+            Self::Fake(fake) => (&fake.cdi_attest, &fake.cdi_seal, fake.bcc.as_slice()),
+        };
+        let (cdi_attest, cdi_seal, bcc) = OpenDiceCborContext::new()
+            .bcc_main_flow(cdi_attest, cdi_seal, bcc, &input_values)
+            .context("DICE derive from driver")?;
+        if let Self::Real { 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(|err| Error::new(err).context("Wiping driver"))?;
+        }
+        Ok(DiceContext {
+            cdi_attest: cdi_attest[..].try_into().unwrap(),
+            cdi_seal: cdi_seal[..].try_into().unwrap(),
+            bcc,
+        })
+    }
+}
+
+impl Drop for DiceDriver<'_> {
+    fn drop(&mut self) {
+        if let &mut Self::Real { mmap_addr, mmap_size, .. } = self {
+            // All references to the mapped region have the same lifetime as self. Since self is
+            // being dropped, so are all the references to the mapped region meaning its safe to
+            // unmap.
+            let ret = unsafe { munmap(mmap_addr, mmap_size) };
+            if ret != 0 {
+                log::warn!("Failed to munmap ({})", ret);
+            }
+        }
+    }
+}
diff --git a/microdroid_manager/src/instance.rs b/microdroid_manager/src/instance.rs
index 358d88b..96e9360 100644
--- a/microdroid_manager/src/instance.rs
+++ b/microdroid_manager/src/instance.rs
@@ -33,15 +33,11 @@
 //! The payload of a partition is encrypted/signed by a key that is unique to the loader and to the
 //! VM as well. Failing to decrypt/authenticate a partition by a loader stops the boot process.
 
+use crate::dice::DiceDriver;
 use crate::ioutil;
 
-use android_security_dice::aidl::android::security::dice::IDiceNode::IDiceNode;
 use anyhow::{anyhow, bail, Context, Result};
-use binder::wait_for_interface;
 use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
-use keystore2_crypto::ZVec;
-use openssl::hkdf::hkdf;
-use openssl::md::Md;
 use openssl::symm::{decrypt_aead, encrypt_aead, Cipher};
 use serde::{Deserialize, Serialize};
 use std::fs::{File, OpenOptions};
@@ -51,6 +47,9 @@
 /// Path to the instance disk inside the VM
 const INSTANCE_IMAGE_PATH: &str = "/dev/block/by-name/vm-instance";
 
+/// Identifier for the key used to seal the instance data.
+const INSTANCE_KEY_IDENTIFIER: &[u8] = b"microdroid_manager_key";
+
 /// Magic string in the instance disk header
 const DISK_HEADER_MAGIC: &str = "Android-VM-instance";
 
@@ -114,7 +113,7 @@
     /// Reads the identity data that was written by microdroid manager. The returned data is
     /// plaintext, although it is stored encrypted. In case when the partition for microdroid
     /// manager doesn't exist, which can happen if it's the first boot, `Ok(None)` is returned.
-    pub fn read_microdroid_data(&mut self) -> Result<Option<MicrodroidData>> {
+    pub fn read_microdroid_data(&mut self, dice: &DiceDriver) -> Result<Option<MicrodroidData>> {
         let (header, offset) = self.locate_microdroid_header()?;
         if header.is_none() {
             return Ok(None);
@@ -143,7 +142,7 @@
         self.file.read_exact(&mut header)?;
 
         // Decrypt and authenticate the data (along with the header).
-        let key = get_key()?;
+        let key = dice.get_sealing_key(INSTANCE_KEY_IDENTIFIER)?;
         let plaintext =
             decrypt_aead(Cipher::aes_256_gcm(), &key, Some(&nonce), &header, &data, &tag)?;
 
@@ -153,7 +152,11 @@
 
     /// Writes identity data to the partition for microdroid manager. The partition is appended
     /// if it doesn't exist. The data is stored encrypted.
-    pub fn write_microdroid_data(&mut self, microdroid_data: &MicrodroidData) -> Result<()> {
+    pub fn write_microdroid_data(
+        &mut self,
+        microdroid_data: &MicrodroidData,
+        dice: &DiceDriver,
+    ) -> Result<()> {
         let (header, offset) = self.locate_microdroid_header()?;
 
         let data = serde_cbor::to_vec(microdroid_data)?;
@@ -185,7 +188,7 @@
         self.file.write_all(nonce.as_ref())?;
 
         // Then encrypt and sign the data.
-        let key = get_key()?;
+        let key = dice.get_sealing_key(INSTANCE_KEY_IDENTIFIER)?;
         let mut tag = [0; AES_256_GCM_TAG_LENGTH];
         let ciphertext =
             encrypt_aead(Cipher::aes_256_gcm(), &key, Some(&nonce), &header, &data, &mut tag)?;
@@ -268,23 +271,6 @@
     Ok(ret)
 }
 
-/// Returns the key that is used to encrypt the microdroid manager partition. It is derived from
-/// the sealing CDI of the previous stage, which is Android Boot Loader (ABL).
-fn get_key() -> Result<ZVec> {
-    // Sealing CDI from the previous stage.
-    let diced = wait_for_interface::<dyn IDiceNode>("android.security.dice.IDiceNode")
-        .context("IDiceNode service not found")?;
-    let bcc_handover = diced.derive(&[]).context("Failed to get BccHandover")?;
-    // Deterministically derive another key to use for encrypting the data, rather than using the
-    // CDI directly, so we have the chance to rotate the key if needed. A salt isn't needed as the
-    // input key material is already cryptographically strong.
-    let salt = &[];
-    let info = b"microdroid_manager_key".as_ref();
-    let mut key = ZVec::new(32)?;
-    hkdf(&mut key, Md::sha256(), &bcc_handover.cdiSeal, salt, info)?;
-    Ok(key)
-}
-
 #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
 pub struct MicrodroidData {
     pub salt: Vec<u8>, // Should be [u8; 64] but that isn't serializable.
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 90cabb6..49dcacb 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -14,28 +14,28 @@
 
 //! Microdroid Manager
 
+mod dice;
 mod instance;
 mod ioutil;
 mod payload;
+mod vm_payload_service;
 
+use crate::dice::{DiceContext, DiceDriver};
 use crate::instance::{ApexData, ApkData, InstanceDisk, MicrodroidData, RootHash};
-use android_hardware_security_dice::aidl::android::hardware::security::dice::{
-    Config::Config, InputValues::InputValues, Mode::Mode,
-};
-use android_security_dice::aidl::android::security::dice::IDiceMaintenance::IDiceMaintenance;
+use crate::vm_payload_service::register_vm_payload_service;
 use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::ErrorCode::ErrorCode;
 use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::{
     VM_BINDER_SERVICE_PORT, VM_STREAM_SERVICE_PORT, IVirtualMachineService,
 };
 use anyhow::{anyhow, bail, ensure, Context, Error, Result};
 use apkverify::{get_public_key_der, verify, V4Signature};
-use binder::{wait_for_interface, Strong};
-use diced_utils::cbor::encode_header;
+use binder::{ProcessState, Strong};
+use diced_utils::cbor::{encode_header, encode_number};
 use glob::glob;
 use itertools::sorted;
 use log::{error, info};
 use microdroid_metadata::{write_metadata, Metadata, PayloadMetadata};
-use microdroid_payload_config::{Task, TaskType, VmPayloadConfig};
+use microdroid_payload_config::{Task, TaskType, VmPayloadConfig, OsConfig};
 use openssl::sha::Sha512;
 use payload::{get_apex_data_from_payload, load_metadata, to_metadata};
 use rand::Fill;
@@ -191,7 +191,11 @@
     }
 }
 
-fn dice_derivation(verified_data: &MicrodroidData, payload_config_path: &str) -> Result<()> {
+fn dice_derivation(
+    dice: DiceDriver,
+    verified_data: &MicrodroidData,
+    payload_metadata: &PayloadMetadata,
+) -> Result<DiceContext> {
     // Calculate compound digests of code and authorities
     let mut code_hash_ctx = Sha512::new();
     let mut authority_hash_ctx = Sha512::new();
@@ -210,36 +214,55 @@
 
     // {
     //   -70002: "Microdroid payload",
-    //   -71000: payload_config_path
+    //   ? -71000: tstr // payload_config_path
+    //   ? -71001: PayloadConfig
     // }
+    // PayloadConfig = {
+    //   1: tstr // payload_binary_path
+    //   // TODO(b/249064104 Either include args, or deprecate them
+    // }
+
     let mut config_desc = vec![
-        0xa2, 0x3a, 0x00, 0x01, 0x11, 0x71, 0x72, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x64, 0x72, 0x6f,
-        0x69, 0x64, 0x20, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x3a, 0x00, 0x01, 0x15, 0x57,
+        0xa2, // map(2)
+        0x3a, 0x00, 0x01, 0x11, 0x71, // -70002
+        0x72, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x20, 0x70, 0x61, 0x79,
+        0x6c, 0x6f, 0x61, 0x64, // "Microdroid payload"
     ];
-    let config_path_bytes = payload_config_path.as_bytes();
-    encode_header(3, config_path_bytes.len().try_into().unwrap(), &mut config_desc)?;
-    config_desc.extend_from_slice(config_path_bytes);
+
+    match payload_metadata {
+        PayloadMetadata::config_path(payload_config_path) => {
+            encode_negative_number(-71000, &mut config_desc)?;
+            encode_tstr(payload_config_path, &mut config_desc)?;
+        }
+        PayloadMetadata::config(payload_config) => {
+            encode_negative_number(-71001, &mut config_desc)?;
+            encode_header(5, 1, &mut config_desc)?; // map(1)
+            encode_number(1, &mut config_desc)?;
+            encode_tstr(&payload_config.payload_binary_path, &mut config_desc)?;
+        }
+    }
 
     // Check app debuggability, conervatively assuming it is debuggable
     let app_debuggable = system_properties::read_bool(APP_DEBUGGABLE_PROP, true)?;
 
     // Send the details to diced
-    let diced =
-        wait_for_interface::<dyn IDiceMaintenance>("android.security.dice.IDiceMaintenance")
-            .context("IDiceMaintenance service not found")?;
-    diced
-        .demoteSelf(&[InputValues {
-            codeHash: code_hash,
-            config: Config { desc: config_desc },
-            authorityHash: authority_hash,
-            authorityDescriptor: None,
-            mode: if app_debuggable { Mode::DEBUG } else { Mode::NORMAL },
-            hidden: verified_data.salt.clone().try_into().unwrap(),
-        }])
-        .context("IDiceMaintenance::demoteSelf failed")?;
+    let hidden = verified_data.salt.clone().try_into().unwrap();
+    dice.derive(code_hash, &config_desc, authority_hash, app_debuggable, hidden)
+}
+
+fn encode_tstr(tstr: &str, buffer: &mut Vec<u8>) -> Result<()> {
+    let bytes = tstr.as_bytes();
+    encode_header(3, bytes.len().try_into().unwrap(), buffer)?;
+    buffer.extend_from_slice(bytes);
     Ok(())
 }
 
+fn encode_negative_number(n: i64, buffer: &mut dyn Write) -> Result<()> {
+    ensure!(n < 0);
+    let n = -1 - n;
+    encode_header(1, n.try_into().unwrap(), buffer)
+}
+
 fn is_strict_boot() -> bool {
     Path::new(AVF_STRICT_BOOT).exists()
 }
@@ -254,9 +277,11 @@
 
 fn try_run_payload(service: &Strong<dyn IVirtualMachineService>) -> Result<i32> {
     let metadata = load_metadata().context("Failed to load payload metadata")?;
+    let dice = DiceDriver::new(Path::new("/dev/open-dice0")).context("Failed to load DICE")?;
 
     let mut instance = InstanceDisk::new().context("Failed to load instance.img")?;
-    let saved_data = instance.read_microdroid_data().context("Failed to read identity data")?;
+    let saved_data =
+        instance.read_microdroid_data(&dice).context("Failed to read identity data")?;
 
     if is_strict_boot() {
         // Provisioning must happen on the first boot and never again.
@@ -297,18 +322,19 @@
         saved_data
     } else {
         info!("Saving verified data.");
-        instance.write_microdroid_data(&verified_data).context("Failed to write identity data")?;
+        instance
+            .write_microdroid_data(&verified_data, &dice)
+            .context("Failed to write identity data")?;
         verified_data
     };
 
-    let payload_config_path = match metadata.payload {
-        Some(PayloadMetadata::config_path(p)) => p,
-        _ => bail!("Unsupported payload config"),
-    };
+    let payload_metadata = metadata.payload.ok_or_else(|| {
+        MicrodroidError::InvalidConfig("No payload config in metadata".to_string())
+    })?;
 
     // To minimize the exposure to untrusted data, derive dice profile as soon as possible.
     info!("DICE derivation for payload");
-    dice_derivation(&verified_data, &payload_config_path)?;
+    let dice = dice_derivation(dice, &verified_data, &payload_metadata)?;
 
     // Before reading a file from the APK, start zipfuse
     let noexec = false;
@@ -320,12 +346,7 @@
     )
     .context("Failed to run zipfuse")?;
 
-    ensure!(
-        !payload_config_path.is_empty(),
-        MicrodroidError::InvalidConfig("No payload_config_path in metadata".to_string())
-    );
-
-    let config = load_config(Path::new(&payload_config_path))?;
+    let config = load_config(payload_metadata)?;
 
     let task = config
         .task
@@ -334,7 +355,7 @@
 
     if config.extra_apks.len() != verified_data.extra_apks_data.len() {
         return Err(anyhow!(
-            "config expects {} extra apks, but found only {}",
+            "config expects {} extra apks, but found {}",
             config.extra_apks.len(),
             verified_data.extra_apks_data.len()
         ));
@@ -358,6 +379,8 @@
     }
 
     system_properties::write("dev.bootcomplete", "1").context("set dev.bootcomplete")?;
+    register_vm_payload_service(service.clone(), dice)?;
+    ProcessState::start_thread_pool();
     exec_task(task, service)
 }
 
@@ -620,10 +643,31 @@
     }
 }
 
-fn load_config(path: &Path) -> Result<VmPayloadConfig> {
-    info!("loading config from {:?}...", path);
-    let file = ioutil::wait_for_file(path, WAIT_TIMEOUT)?;
-    Ok(serde_json::from_reader(file)?)
+fn load_config(payload_metadata: PayloadMetadata) -> Result<VmPayloadConfig> {
+    match payload_metadata {
+        PayloadMetadata::config_path(path) => {
+            let path = Path::new(&path);
+            info!("loading config from {:?}...", path);
+            let file = ioutil::wait_for_file(path, WAIT_TIMEOUT)?;
+            Ok(serde_json::from_reader(file)?)
+        }
+        PayloadMetadata::config(payload_config) => {
+            let task = Task {
+                type_: TaskType::MicrodroidLauncher,
+                command: payload_config.payload_binary_path,
+                args: payload_config.args.into_vec(),
+            };
+            Ok(VmPayloadConfig {
+                os: OsConfig { name: "microdroid".to_owned() },
+                task: Some(task),
+                apexes: vec![],
+                extra_apks: vec![],
+                prefer_staged: false,
+                export_tombstones: false,
+                enable_authfs: false,
+            })
+        }
+    }
 }
 
 /// Loads the crashkernel into memory using kexec if the VM is loaded with `crashkernel=' parameter
diff --git a/microdroid_manager/src/vm_payload_service.rs b/microdroid_manager/src/vm_payload_service.rs
new file mode 100644
index 0000000..70117c0
--- /dev/null
+++ b/microdroid_manager/src/vm_payload_service.rs
@@ -0,0 +1,70 @@
+// 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.
+
+//! Implementation of the AIDL interface `IVmPayloadService`.
+
+use crate::dice::DiceContext;
+use android_system_virtualization_payload::aidl::android::system::virtualization::payload::IVmPayloadService::{
+    BnVmPayloadService, IVmPayloadService, VM_PAYLOAD_SERVICE_NAME};
+use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::IVirtualMachineService;
+use anyhow::{Context, Result};
+use binder::{Interface, BinderFeatures, Strong, add_service};
+
+/// Implementation of `IVmPayloadService`.
+struct VmPayloadService {
+    virtual_machine_service: Strong<dyn IVirtualMachineService>,
+    dice: DiceContext,
+}
+
+impl IVmPayloadService for VmPayloadService {
+    fn notifyPayloadReady(&self) -> binder::Result<()> {
+        self.virtual_machine_service.notifyPayloadReady()
+    }
+
+    fn getDiceAttestationChain(&self) -> binder::Result<Vec<u8>> {
+        Ok(self.dice.bcc.clone())
+    }
+
+    fn getDiceAttestationCdi(&self) -> binder::Result<Vec<u8>> {
+        Ok(self.dice.cdi_attest.to_vec())
+    }
+
+    fn getDiceSealingCdi(&self) -> binder::Result<Vec<u8>> {
+        Ok(self.dice.cdi_seal.to_vec())
+    }
+}
+
+impl Interface for VmPayloadService {}
+
+impl VmPayloadService {
+    /// Creates a new `VmPayloadService` instance from the `IVirtualMachineService` reference.
+    fn new(vm_service: Strong<dyn IVirtualMachineService>, dice: DiceContext) -> Self {
+        Self { virtual_machine_service: vm_service, dice }
+    }
+}
+
+/// Registers the `IVmPayloadService` service.
+pub(crate) fn register_vm_payload_service(
+    vm_service: Strong<dyn IVirtualMachineService>,
+    dice: DiceContext,
+) -> Result<()> {
+    let vm_payload_binder = BnVmPayloadService::new_binder(
+        VmPayloadService::new(vm_service, dice),
+        BinderFeatures::default(),
+    );
+    add_service(VM_PAYLOAD_SERVICE_NAME, vm_payload_binder.as_binder())
+        .with_context(|| format!("Failed to register service {}", VM_PAYLOAD_SERVICE_NAME))?;
+    log::info!("{} is running", VM_PAYLOAD_SERVICE_NAME);
+    Ok(())
+}
diff --git a/pvmfw/src/exceptions.rs b/pvmfw/src/exceptions.rs
index 596ecc7..0fb2911 100644
--- a/pvmfw/src/exceptions.rs
+++ b/pvmfw/src/exceptions.rs
@@ -14,14 +14,23 @@
 
 //! Exception handlers.
 
+use crate::helpers::page_4kb_of;
 use core::arch::asm;
+use vmbase::console;
 use vmbase::{console::emergency_write_str, eprintln, power::reboot};
 
+const ESR_32BIT_EXT_DABT: u64 = 0x96000010;
+const UART_PAGE: u64 = page_4kb_of(console::BASE_ADDRESS as u64);
+
 #[no_mangle]
 extern "C" fn sync_exception_current(_elr: u64, _spsr: u64) {
     let esr = read_esr();
-    emergency_write_str("sync_exception_current\n");
-    print_esr(esr);
+    let far = read_far();
+    // Don't print to the UART if we're handling the exception it could raise.
+    if esr != ESR_32BIT_EXT_DABT || page_4kb_of(far) != UART_PAGE {
+        emergency_write_str("sync_exception_current\n");
+        print_esr(esr);
+    }
     reboot();
 }
 
@@ -86,3 +95,12 @@
 fn print_esr(esr: u64) {
     eprintln!("esr={:#08x}", esr);
 }
+
+#[inline]
+fn read_far() -> u64 {
+    let mut far: u64;
+    unsafe {
+        asm!("mrs {far}, far_el1", far = out(reg) far);
+    }
+    far
+}
diff --git a/pvmfw/src/helpers.rs b/pvmfw/src/helpers.rs
new file mode 100644
index 0000000..781c1ac
--- /dev/null
+++ b/pvmfw/src/helpers.rs
@@ -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.
+
+//! Miscellaneous helper functions.
+
+/// Computes the address of the page containing a given address.
+pub const fn page_of(addr: u64, page_size: u64) -> u64 {
+    addr & !(page_size - 1)
+}
+
+/// Validates a page size and computes the address of the page containing a given address.
+pub const fn checked_page_of(addr: u64, page_size: u64) -> Option<u64> {
+    if page_size.is_power_of_two() {
+        Some(page_of(addr, page_size))
+    } else {
+        None
+    }
+}
+
+/// Computes the address of the 4KiB page containing a given address.
+pub const fn page_4kb_of(addr: u64) -> u64 {
+    const PAGE_SIZE: u64 = 4 << 10;
+
+    page_of(addr, PAGE_SIZE)
+}
diff --git a/pvmfw/src/main.rs b/pvmfw/src/main.rs
index 8526a92..eb97961 100644
--- a/pvmfw/src/main.rs
+++ b/pvmfw/src/main.rs
@@ -18,13 +18,36 @@
 #![no_std]
 
 mod exceptions;
+mod helpers;
+mod smccc;
 
-use vmbase::{main, println};
+use core::fmt;
+use helpers::checked_page_of;
 
-main!(main);
+use vmbase::{console, main, power::reboot, println};
 
-/// Entry point for pVM firmware.
-pub fn main(fdt_address: u64, payload_start: u64, payload_size: u64, arg3: u64) {
+#[derive(Debug, Clone)]
+enum Error {
+    /// Failed to configure the UART; no logs available.
+    FailedUartSetup,
+}
+
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        let msg = match self {
+            Self::FailedUartSetup => "Failed to configure the UART",
+        };
+        write!(f, "{}", msg)
+    }
+}
+
+fn main(fdt_address: u64, payload_start: u64, payload_size: u64, arg3: u64) -> Result<(), Error> {
+    // We need to inform the hypervisor that the MMIO page containing the UART may be shared back.
+    let uart = console::BASE_ADDRESS as u64;
+    let mmio_granule = smccc::mmio_guard_info().map_err(|_| Error::FailedUartSetup)?;
+    let uart_page = checked_page_of(uart, mmio_granule).ok_or(Error::FailedUartSetup)?;
+    smccc::mmio_guard_map(uart_page).map_err(|_| Error::FailedUartSetup)?;
+
     println!("pVM firmware");
     println!(
         "fdt_address={:#018x}, payload_start={:#018x}, payload_size={:#018x}, x3={:#018x}",
@@ -32,6 +55,24 @@
     );
 
     println!("Starting payload...");
+
+    Ok(())
+}
+
+main!(main_wrapper);
+
+/// Entry point for pVM firmware.
+pub fn main_wrapper(fdt_address: u64, payload_start: u64, payload_size: u64, arg3: u64) {
+    match main(fdt_address, payload_start, payload_size, arg3) {
+        Ok(()) => jump_to_payload(fdt_address, payload_start),
+        Err(e) => {
+            println!("Boot rejected: {}", e);
+        }
+    }
+    reboot()
+}
+
+fn jump_to_payload(fdt_address: u64, payload_start: u64) {
     // Safe because this is a function we have implemented in assembly that matches its signature
     // here.
     unsafe {
diff --git a/pvmfw/src/smccc.rs b/pvmfw/src/smccc.rs
new file mode 100644
index 0000000..e3a2b05
--- /dev/null
+++ b/pvmfw/src/smccc.rs
@@ -0,0 +1,115 @@
+// 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.
+
+use core::fmt;
+
+// TODO(b/245889995): use psci-0.1.1 crate
+#[inline(always)]
+fn hvc64(function: u32, args: [u64; 17]) -> [u64; 18] {
+    #[cfg(target_arch = "aarch64")]
+    unsafe {
+        let mut ret = [0; 18];
+
+        core::arch::asm!(
+            "hvc #0",
+            inout("x0") function as u64 => ret[0],
+            inout("x1") args[0] => ret[1],
+            inout("x2") args[1] => ret[2],
+            inout("x3") args[2] => ret[3],
+            inout("x4") args[3] => ret[4],
+            inout("x5") args[4] => ret[5],
+            inout("x6") args[5] => ret[6],
+            inout("x7") args[6] => ret[7],
+            inout("x8") args[7] => ret[8],
+            inout("x9") args[8] => ret[9],
+            inout("x10") args[9] => ret[10],
+            inout("x11") args[10] => ret[11],
+            inout("x12") args[11] => ret[12],
+            inout("x13") args[12] => ret[13],
+            inout("x14") args[13] => ret[14],
+            inout("x15") args[14] => ret[15],
+            inout("x16") args[15] => ret[16],
+            inout("x17") args[16] => ret[17],
+            options(nomem, nostack)
+        );
+
+        ret
+    }
+}
+
+/// Standard SMCCC error values as described in DEN 0028E.
+#[derive(Debug, Clone)]
+pub enum Error {
+    /// The call is not supported by the implementation.
+    NotSupported,
+    /// The call is deemed not required by the implementation.
+    NotRequired,
+    /// One of the call parameters has a non-supported value.
+    InvalidParameter,
+    /// Negative values indicate error.
+    Unknown(i64),
+    /// The call returned a positive value when 0 was expected.
+    Unexpected(u64),
+}
+
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            Self::NotSupported => write!(f, "SMCCC call not supported"),
+            Self::NotRequired => write!(f, "SMCCC call not required"),
+            Self::InvalidParameter => write!(f, "SMCCC call received non-supported value"),
+            Self::Unexpected(v) => write!(f, "Unexpected SMCCC return value '{}'", v),
+            Self::Unknown(e) => write!(f, "Unknown SMCCC return value '{}'", e),
+        }
+    }
+}
+
+fn check_smccc_err(ret: i64) -> Result<(), Error> {
+    match check_smccc_value(ret)? {
+        0 => Ok(()),
+        v => Err(Error::Unexpected(v)),
+    }
+}
+
+fn check_smccc_value(ret: i64) -> Result<u64, Error> {
+    match ret {
+        x if x >= 0 => Ok(ret as u64),
+        -1 => Err(Error::NotSupported),
+        -2 => Err(Error::NotRequired),
+        -3 => Err(Error::InvalidParameter),
+        _ => Err(Error::Unknown(ret)),
+    }
+}
+
+const VENDOR_HYP_KVM_MMIO_GUARD_INFO_FUNC_ID: u32 = 0xc6000005;
+const VENDOR_HYP_KVM_MMIO_GUARD_MAP_FUNC_ID: u32 = 0xc6000007;
+
+/// Issue pKVM-specific MMIO_GUARD_INFO HVC64.
+pub fn mmio_guard_info() -> Result<u64, Error> {
+    let args = [0u64; 17];
+
+    let res = hvc64(VENDOR_HYP_KVM_MMIO_GUARD_INFO_FUNC_ID, args);
+
+    check_smccc_value(res[0] as i64)
+}
+
+/// Issue pKVM-specific MMIO_GUARD_MAP HVC64.
+pub fn mmio_guard_map(ipa: u64) -> Result<(), Error> {
+    let mut args = [0u64; 17];
+    args[0] = ipa;
+
+    let res = hvc64(VENDOR_HYP_KVM_MMIO_GUARD_MAP_FUNC_ID, args);
+
+    check_smccc_err(res[0] as i64)
+}
diff --git a/tests/helper/src/java/com/android/microdroid/test/common/ProcessUtil.java b/tests/helper/src/java/com/android/microdroid/test/common/ProcessUtil.java
index 611a572..d85929d 100644
--- a/tests/helper/src/java/com/android/microdroid/test/common/ProcessUtil.java
+++ b/tests/helper/src/java/com/android/microdroid/test/common/ProcessUtil.java
@@ -36,14 +36,14 @@
     public static List<SMapEntry> getProcessSmaps(int pid, Function<String, String> shellExecutor)
             throws IOException {
         String path = "/proc/" + pid + "/smaps";
-        return parseSmaps(shellExecutor.apply("cat " + path + " || true"));
+        return parseMemoryInfo(shellExecutor.apply("cat " + path + " || true"));
     }
 
     /** Gets metrics key and values mapping of specified process id */
     public static Map<String, Long> getProcessSmapsRollup(
             int pid, Function<String, String> shellExecutor) throws IOException {
         String path = "/proc/" + pid + "/smaps_rollup";
-        List<SMapEntry> entries = parseSmaps(shellExecutor.apply("cat " + path + " || true"));
+        List<SMapEntry> entries = parseMemoryInfo(shellExecutor.apply("cat " + path + " || true"));
         if (entries.size() > 1) {
             throw new RuntimeException(
                     "expected at most one entry in smaps_rollup, got " + entries.size());
@@ -54,6 +54,21 @@
         return new HashMap<String, Long>();
     }
 
+    /** Gets global memory metrics key and values mapping */
+    public static Map<String, Long> getProcessMemoryMap(
+            Function<String, String> shellExecutor) throws IOException {
+        // The input file of parseMemoryInfo need a header string as the key of output entries.
+        // /proc/meminfo doesn't have this line so add one as the key.
+        String header = "device memory info\n";
+        List<SMapEntry> entries = parseMemoryInfo(header
+                + shellExecutor.apply("cat /proc/meminfo"));
+        if (entries.size() != 1) {
+            throw new RuntimeException(
+                    "expected one entry in /proc/meminfo, got " + entries.size());
+        }
+        return entries.get(0).metrics;
+    }
+
     /** Gets process id and process name mapping of the device */
     public static Map<Integer, String> getProcessMap(Function<String, String> shellExecutor)
             throws IOException {
@@ -77,7 +92,7 @@
     // To ensures that only one object is created at a time.
     private ProcessUtil() {}
 
-    private static List<SMapEntry> parseSmaps(String file) {
+    private static List<SMapEntry> parseMemoryInfo(String file) {
         List<SMapEntry> entries = new ArrayList<SMapEntry>();
         for (String line : file.split("\n")) {
             line = line.trim();
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidTestCase.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidTestCase.java
index 7a71254..9dd2a15 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidTestCase.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidTestCase.java
@@ -71,7 +71,6 @@
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -684,34 +683,6 @@
         shutdownMicrodroid(getDevice(), cid);
     }
 
-    /**
-     * TODO(b/249409434): to be replaced by ProcessUtil
-     *
-     * @deprecated use ProcessUtil instead.
-     */
-    @Deprecated
-    private Map<String, Long> parseMemInfo(String file) {
-        Map<String, Long> stats = new HashMap<>();
-        file.lines().forEach(line -> {
-            if (line.endsWith(" kB")) line = line.substring(0, line.length() - 3);
-
-            String[] elems = line.split(":");
-            assertThat(elems.length).isEqualTo(2);
-            stats.put(elems[0].trim(), Long.parseLong(elems[1].trim()));
-        });
-        return stats;
-    }
-
-    /**
-     * TODO(b/249409434): to be replaced by ProcessUtil
-     *
-     * @deprecated use ProcessUtil instead.
-     */
-    @Deprecated
-    private Map<String, Long> getProcMemInfo() {
-        return parseMemInfo(runOnMicrodroid("cat", "/proc/meminfo"));
-    }
-
     @Test
     public void testMicrodroidRamUsage() throws Exception {
         final String configPath = "assets/vm_config.json";
@@ -729,7 +700,8 @@
         waitForBootComplete();
         rootMicrodroid();
 
-        for (Map.Entry<String, Long> stat : getProcMemInfo().entrySet()) {
+        for (Map.Entry<String, Long> stat :
+                ProcessUtil.getProcessMemoryMap(cmd -> runOnMicrodroid(cmd)).entrySet()) {
             mMetrics.addTestMetric(
                     mMetricPrefix + "meminfo/" + stat.getKey().toLowerCase(),
                     stat.getValue().toString());
diff --git a/tests/testapk/Android.bp b/tests/testapk/Android.bp
index 47116eb..e1c8124 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -32,8 +32,6 @@
     name: "MicrodroidTestNativeLib",
     srcs: ["src/native/testbinary.cpp"],
     shared_libs: [
-        "android.security.dice-ndk",
-        "android.system.virtualmachineservice-ndk",
         "com.android.microdroid.testservice-ndk",
         "libbase",
         "libbinder_ndk",
@@ -44,6 +42,7 @@
         "libfsverity_digests_proto_cc",
         "liblog",
         "libprotobuf-cpp-lite-ndk",
+        "libvm_payload",
     ],
 }
 
diff --git a/tests/testapk/src/native/testbinary.cpp b/tests/testapk/src/native/testbinary.cpp
index b4fee86..d1cfc56 100644
--- a/tests/testapk/src/native/testbinary.cpp
+++ b/tests/testapk/src/native/testbinary.cpp
@@ -13,8 +13,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-#include <aidl/android/security/dice/IDiceNode.h>
-#include <aidl/android/system/virtualmachineservice/IVirtualMachineService.h>
 #include <aidl/com/android/microdroid/testservice/BnTestService.h>
 #include <android-base/file.h>
 #include <android-base/properties.h>
@@ -29,15 +27,11 @@
 #include <sys/ioctl.h>
 #include <sys/system_properties.h>
 #include <unistd.h>
+#include <vm_payload.h>
 
 #include <binder_rpc_unstable.hpp>
 #include <string>
 
-using aidl::android::hardware::security::dice::BccHandover;
-using aidl::android::security::dice::IDiceNode;
-
-using aidl::android::system::virtualmachineservice::IVirtualMachineService;
-
 using android::base::ErrnoError;
 using android::base::Error;
 using android::base::Result;
@@ -80,76 +74,46 @@
         }
 
         ndk::ScopedAStatus insecurelyExposeSealingCdi(std::vector<uint8_t>* out) override {
-            ndk::SpAIBinder binder(AServiceManager_getService("android.security.dice.IDiceNode"));
-            auto service = IDiceNode::fromBinder(binder);
-            if (service == nullptr) {
+            uint8_t cdi[64];
+            size_t cdi_size = get_dice_sealing_cdi(cdi, sizeof(cdi));
+            if (cdi_size == 0) {
                 return ndk::ScopedAStatus::
-                        fromServiceSpecificErrorWithMessage(0, "Failed to find diced");
+                        fromServiceSpecificErrorWithMessage(0, "Failed to get sealing cdi");
             }
-            BccHandover handover;
-            auto deriveStatus = service->derive({}, &handover);
-            if (!deriveStatus.isOk()) {
-                return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(0,
-                                                                               "Failed call diced");
-            }
-            *out = {handover.cdiSeal.begin(), handover.cdiSeal.end()};
+            *out = {cdi, cdi + cdi_size};
             return ndk::ScopedAStatus::ok();
         }
 
         ndk::ScopedAStatus insecurelyExposeAttestationCdi(std::vector<uint8_t>* out) override {
-            ndk::SpAIBinder binder(AServiceManager_getService("android.security.dice.IDiceNode"));
-            auto service = IDiceNode::fromBinder(binder);
-            if (service == nullptr) {
+            uint8_t cdi[64];
+            size_t cdi_size = get_dice_attestation_cdi(cdi, sizeof(cdi));
+            if (cdi_size == 0) {
                 return ndk::ScopedAStatus::
-                        fromServiceSpecificErrorWithMessage(0, "Failed to find diced");
+                        fromServiceSpecificErrorWithMessage(0, "Failed to get attestation cdi");
             }
-            BccHandover handover;
-            auto deriveStatus = service->derive({}, &handover);
-            if (!deriveStatus.isOk()) {
-                return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(0,
-                                                                               "Failed call diced");
-            }
-            *out = {handover.cdiAttest.begin(), handover.cdiAttest.end()};
+            *out = {cdi, cdi + cdi_size};
             return ndk::ScopedAStatus::ok();
         }
 
         ndk::ScopedAStatus getBcc(std::vector<uint8_t>* out) override {
-            ndk::SpAIBinder binder(AServiceManager_getService("android.security.dice.IDiceNode"));
-            auto service = IDiceNode::fromBinder(binder);
-            if (service == nullptr) {
+            uint8_t bcc[2048];
+            size_t bcc_size = get_dice_attestation_chain(bcc, sizeof(bcc));
+            if (bcc_size == 0) {
                 return ndk::ScopedAStatus::
-                        fromServiceSpecificErrorWithMessage(0, "Failed to find diced");
+                        fromServiceSpecificErrorWithMessage(0, "Failed to get attestation chain");
             }
-            BccHandover handover;
-            auto deriveStatus = service->derive({}, &handover);
-            if (!deriveStatus.isOk()) {
-                return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(0,
-                                                                               "Failed call diced");
-            }
-            *out = {handover.bcc.data.begin(), handover.bcc.data.end()};
+            *out = {bcc, bcc + bcc_size};
             return ndk::ScopedAStatus::ok();
         }
     };
     auto testService = ndk::SharedRefBase::make<TestService>();
 
     auto callback = []([[maybe_unused]] void* param) {
-        // Tell microdroid_manager that we're ready.
-        // If we can't, abort in order to fail fast - the host won't proceed without
-        // receiving the onReady signal.
-        ndk::SpAIBinder binder(
-                RpcClient(VMADDR_CID_HOST, IVirtualMachineService::VM_BINDER_SERVICE_PORT));
-        auto virtualMachineService = IVirtualMachineService::fromBinder(binder);
-        if (virtualMachineService == nullptr) {
-            std::cerr << "failed to connect VirtualMachineService\n";
-            abort();
-        }
-        if (auto status = virtualMachineService->notifyPayloadReady(); !status.isOk()) {
-            std::cerr << "failed to notify payload ready to virtualizationservice: "
-                      << status.getDescription() << std::endl;
+        if (!notify_payload_ready()) {
+            std::cerr << "failed to notify payload ready to virtualizationservice" << std::endl;
             abort();
         }
     };
-
     if (!RunRpcServerCallback(testService->asBinder().get(), testService->SERVICE_PORT, callback,
                               nullptr)) {
         return Error() << "RPC Server failed to run";
diff --git a/virtualizationservice/Android.bp b/virtualizationservice/Android.bp
index 2f4f731..0551229 100644
--- a/virtualizationservice/Android.bp
+++ b/virtualizationservice/Android.bp
@@ -27,6 +27,7 @@
         "libandroid_logger",
         "libanyhow",
         "libapkverify",
+        "libbase_rust",
         "libbinder_rs",
         "libcommand_fds",
         "libdisk",
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachinePayloadConfig.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachinePayloadConfig.aidl
index 0c83349..4d37848 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachinePayloadConfig.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachinePayloadConfig.aidl
@@ -24,11 +24,6 @@
     @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
      */
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 22418b9..563fab0 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -667,7 +667,7 @@
         apexes: vec![],
         extra_apks: vec![],
         prefer_staged: false,
-        export_tombstones: payload_config.exportTombstones,
+        export_tombstones: false,
         enable_authfs: false,
     }
 }
diff --git a/virtualizationservice/src/crosvm.rs b/virtualizationservice/src/crosvm.rs
index 82a9e78..ff1116a 100644
--- a/virtualizationservice/src/crosvm.rs
+++ b/virtualizationservice/src/crosvm.rs
@@ -41,6 +41,9 @@
 use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::IVirtualMachineService;
 use tombstoned_client::{TombstonedConnection, DebuggerdDumpType};
 
+/// external/crosvm
+use base::UnixSeqpacketListener;
+
 const CROSVM_PATH: &str = "/apex/com.android.virt/bin/crosvm";
 
 /// Version of the platform that crosvm currently implements. The format follows SemVer. This
@@ -139,7 +142,8 @@
             let (failure_pipe_read, failure_pipe_write) = create_pipe()?;
 
             // If this fails and returns an error, `self` will be left in the `Failed` state.
-            let child = Arc::new(run_vm(config, failure_pipe_write)?);
+            let child =
+                Arc::new(run_vm(config, &instance.temporary_directory, failure_pipe_write)?);
 
             let child_clone = child.clone();
             let instance_clone = instance.clone();
@@ -425,7 +429,11 @@
 }
 
 /// Starts an instance of `crosvm` to manage a new VM.
-fn run_vm(config: CrosvmConfig, failure_pipe_write: File) -> Result<SharedChild, Error> {
+fn run_vm(
+    config: CrosvmConfig,
+    temporary_directory: &Path,
+    failure_pipe_write: File,
+) -> Result<SharedChild, Error> {
     validate_config(&config)?;
 
     let mut command = Command::new(CROSVM_PATH);
@@ -434,6 +442,7 @@
         .arg("--extended-status")
         .arg("run")
         .arg("--disable-sandbox")
+        .arg("--no-balloon")
         .arg("--cid")
         .arg(config.cid.to_string());
 
@@ -514,6 +523,11 @@
         command.arg(add_preserved_fd(&mut preserved_fds, kernel));
     }
 
+    let control_server_socket =
+        UnixSeqpacketListener::bind(temporary_directory.join("crosvm.sock"))
+            .context("failed to create control server")?;
+    command.arg("--socket").arg(add_preserved_fd(&mut preserved_fds, &control_server_socket));
+
     debug!("Preserving FDs {:?}", preserved_fds);
     command.preserved_fds(preserved_fds);
 
@@ -546,7 +560,7 @@
 
 /// Adds the file descriptor for `file` to `preserved_fds`, and returns a string of the form
 /// "/proc/self/fd/N" where N is the file descriptor.
-fn add_preserved_fd(preserved_fds: &mut Vec<RawFd>, file: &File) -> String {
+fn add_preserved_fd(preserved_fds: &mut Vec<RawFd>, file: &dyn AsRawFd) -> String {
     let fd = file.as_raw_fd();
     preserved_fds.push(fd);
     format!("/proc/self/fd/{}", fd)
diff --git a/virtualizationservice/src/payload.rs b/virtualizationservice/src/payload.rs
index 3efd7ac..82aa760 100644
--- a/virtualizationservice/src/payload.rs
+++ b/virtualizationservice/src/payload.rs
@@ -165,7 +165,6 @@
     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()
         }),
diff --git a/vmbase/src/console.rs b/vmbase/src/console.rs
index b52d924..fabea91 100644
--- a/vmbase/src/console.rs
+++ b/vmbase/src/console.rs
@@ -18,7 +18,8 @@
 use core::fmt::{write, Arguments, Write};
 use spin::mutex::SpinMutex;
 
-const BASE_ADDRESS: usize = 0x3f8;
+/// Base memory-mapped address of the primary UART device.
+pub const BASE_ADDRESS: usize = 0x3f8;
 
 static CONSOLE: SpinMutex<Option<Uart>> = SpinMutex::new(None);