Empty merge of sc-v2-dev-plus-aosp-without-vendor@8433047

Bug: 226662282
Merged-In: I82ad727d6af6413c2d1aa22a02416b9f38306fb5
Change-Id: Id3b49efe17f4ae2b1a453ddc8c2fc35ffbfd8c47
diff --git a/apex/virtualizationservice.rc b/apex/virtualizationservice.rc
index 7e71105..02b2081 100644
--- a/apex/virtualizationservice.rc
+++ b/apex/virtualizationservice.rc
@@ -14,8 +14,8 @@
 
 service virtualizationservice /apex/com.android.virt/bin/virtualizationservice
     class main
-    user virtualizationservice
-    group virtualizationservice
+    user system
+    group system
     interface aidl android.system.virtualizationservice
     disabled
     oneshot
diff --git a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
index 5d36f16..64658a9 100644
--- a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
+++ b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
@@ -16,21 +16,28 @@
 
 package com.android.virt.fs;
 
+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.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeFalse;
 
 import android.platform.test.annotations.RootPermissionTest;
 import android.virt.test.CommandRunner;
 import android.virt.test.VirtualizationTestCaseBase;
 
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.compatibility.common.util.PollingCheck;
+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;
@@ -47,7 +54,8 @@
 import org.junit.rules.TestName;
 import org.junit.runner.RunWith;
 
-import java.util.Optional;
+import java.io.File;
+import java.io.FileNotFoundException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -62,6 +70,12 @@
     /** Output directory where the test can generate output on Android */
     private static final String TEST_OUTPUT_DIR = "/data/local/tmp/authfs/output_dir";
 
+    /** 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_extra_apk.json";
+
     /** Path to open_then_run on Android */
     private static final String OPEN_THEN_RUN_BIN = "/data/local/tmp/open_then_run";
 
@@ -75,9 +89,7 @@
     private static final String AUTHFS_BIN = "/system/bin/authfs";
 
     /** Idsig paths to be created for each APK in the "extra_apks" of vm_config_extra_apk.json. */
-    private static final String[] EXTRA_IDSIG_PATHS = new String[] {
-        TEST_DIR + "BuildManifest.apk.idsig",
-    };
+    private static final String EXTRA_IDSIG_PATH = TEST_DIR + "BuildManifest.apk.idsig";
 
     /** Build manifest path in the VM. 0 is the index of extra_apks in vm_config_extra_apk.json. */
     private static final String BUILD_MANIFEST_PATH = "/mnt/extra-apk/0/assets/build_manifest.pb";
@@ -99,7 +111,7 @@
     private static final int VMADDR_CID_HOST = 2;
 
     private static CommandRunner sAndroid;
-    private static String sCid;
+    private static CommandRunner sMicrodroid;
     private static boolean sAssumptionFailed;
 
     private ExecutorService mThreadPool = Executors.newCachedThreadPool();
@@ -127,30 +139,20 @@
             return;
         }
 
-        prepareVirtualizationTestSetup(androidDevice);
-
         // For each test case, boot and adb connect to a new Microdroid
         CLog.i("Starting the shared VM");
-        final String apkName = "MicrodroidTestApp.apk";
-        final String packageName = "com.android.microdroid.test";
-        final String configPath = "assets/vm_config_extra_apk.json"; // path inside the APK
-        sCid =
-                startMicrodroid(
-                        androidDevice,
-                        testInfo.getBuildInfo(),
-                        apkName,
-                        packageName,
-                        EXTRA_IDSIG_PATHS,
-                        configPath,
-                        /* debug */ true,
-                        /* use default memoryMib */ 0,
-                        Optional.empty(),
-                        Optional.empty());
-        adbConnectToMicrodroid(androidDevice, sCid);
+        ITestDevice microdroidDevice =
+                MicrodroidBuilder
+                        .fromFile(findTestApk(testInfo.getBuildInfo()), VM_CONFIG_PATH_IN_APK)
+                        .debugLevel("full")
+                        .addExtraIdsigPath(EXTRA_IDSIG_PATH)
+                        .build((TestDevice) androidDevice);
 
         // Root because authfs (started from shell in this test) currently require root to open
         // /dev/fuse and mount the FUSE.
-        rootMicrodroid();
+        assertThat(microdroidDevice.enableAdbRoot()).isTrue();
+
+        sMicrodroid = new CommandRunner(microdroidDevice);
     }
 
     @AfterClassWithInfo
@@ -158,13 +160,12 @@
             throws DeviceNotAvailableException {
         assertNotNull(sAndroid);
 
-        if (sCid != null) {
+        if (sMicrodroid != null) {
             CLog.i("Shutting down shared VM");
-            shutdownMicrodroid(sAndroid.getDevice(), sCid);
-            sCid = null;
+            ((TestDevice) testInfo.getDevice()).shutdownMicrodroid(sMicrodroid.getDevice());
+            sMicrodroid = null;
         }
 
-        cleanUpVirtualizationTestSetup(sAndroid.getDevice());
         sAndroid = null;
     }
 
@@ -176,9 +177,13 @@
 
     @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");
-        tryRunOnMicrodroid("killall authfs");
-        tryRunOnMicrodroid("umount " + MOUNT_DIR);
 
         // 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,
@@ -319,7 +324,7 @@
 
         // Verify
         // Force dropping the page cache, so that the next read can be validated.
-        runOnMicrodroid("echo 1 > /proc/sys/vm/drop_caches");
+        sMicrodroid.run("echo 1 > /proc/sys/vm/drop_caches");
         // A read will fail if the backing data has been tampered.
         assertFalse(checkReadAtFileOffsetOnMicrodroid(
                 destPath, /* offset */ 0, /* number */ 4096));
@@ -419,8 +424,8 @@
 
         // Action
         // Can create nested directories and can create a file in one.
-        runOnMicrodroid("mkdir " + authfsOutputDir + "/new_dir");
-        runOnMicrodroid("mkdir -p " + authfsOutputDir + "/we/need/to/go/deeper");
+        sMicrodroid.run("mkdir " + authfsOutputDir + "/new_dir");
+        sMicrodroid.run("mkdir -p " + authfsOutputDir + "/we/need/to/go/deeper");
         createFileWithOnesOnMicrodroid(authfsOutputDir + "/new_dir/file1", 10000);
         createFileWithOnesOnMicrodroid(authfsOutputDir + "/we/need/file2", 10000);
 
@@ -452,7 +457,7 @@
         runAuthFsOnMicrodroid("--remote-new-rw-dir 3 --cid " + VMADDR_CID_HOST);
 
         // Action & Verify
-        runOnMicrodroid("echo -n foo > " + authfsOutputDir + "/file");
+        sMicrodroid.run("echo -n foo > " + authfsOutputDir + "/file");
         assertEquals(getFileSizeInBytesOnMicrodroid(authfsOutputDir + "/file"), 3);
         // Can override a file and write normally.
         createFileWithOnesOnMicrodroid(authfsOutputDir + "/file", 10000);
@@ -472,13 +477,13 @@
         runFdServerOnAndroid("--open-dir 3:" + androidOutputDir, "--rw-dirs 3");
         runAuthFsOnMicrodroid("--remote-new-rw-dir 3 --cid " + VMADDR_CID_HOST);
 
-        runOnMicrodroid("echo -n foo > " + authfsOutputDir + "/file");
-        runOnMicrodroid("test -f " + authfsOutputDir + "/file");
+        sMicrodroid.run("echo -n foo > " + authfsOutputDir + "/file");
+        sMicrodroid.run("test -f " + authfsOutputDir + "/file");
         sAndroid.run("test -f " + androidOutputDir + "/file");
 
         // Action & Verify
-        runOnMicrodroid("rm " + authfsOutputDir + "/file");
-        runOnMicrodroid("test ! -f " + authfsOutputDir + "/file");
+        sMicrodroid.run("rm " + authfsOutputDir + "/file");
+        sMicrodroid.run("test ! -f " + authfsOutputDir + "/file");
         sAndroid.run("test ! -f " + androidOutputDir + "/file");
     }
 
@@ -491,20 +496,20 @@
         runFdServerOnAndroid("--open-dir 3:" + androidOutputDir, "--rw-dirs 3");
         runAuthFsOnMicrodroid("--remote-new-rw-dir 3 --cid " + VMADDR_CID_HOST);
 
-        runOnMicrodroid("mkdir -p " + authfsOutputDir + "/dir/dir2");
-        runOnMicrodroid("echo -n foo > " + authfsOutputDir + "/dir/file");
+        sMicrodroid.run("mkdir -p " + authfsOutputDir + "/dir/dir2");
+        sMicrodroid.run("echo -n foo > " + authfsOutputDir + "/dir/file");
         sAndroid.run("test -d " + androidOutputDir + "/dir/dir2");
 
         // Action & Verify
-        runOnMicrodroid("rmdir " + authfsOutputDir + "/dir/dir2");
-        runOnMicrodroid("test ! -d " + authfsOutputDir + "/dir/dir2");
+        sMicrodroid.run("rmdir " + authfsOutputDir + "/dir/dir2");
+        sMicrodroid.run("test ! -d " + authfsOutputDir + "/dir/dir2");
         sAndroid.run("test ! -d " + androidOutputDir + "/dir/dir2");
         // Can only delete a directory if empty
         assertFailedOnMicrodroid("rmdir " + authfsOutputDir + "/dir");
-        runOnMicrodroid("test -d " + authfsOutputDir + "/dir");  // still there
-        runOnMicrodroid("rm " + authfsOutputDir + "/dir/file");
-        runOnMicrodroid("rmdir " + authfsOutputDir + "/dir");
-        runOnMicrodroid("test ! -d " + authfsOutputDir + "/dir");
+        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");
         sAndroid.run("test ! -d " + androidOutputDir + "/dir");
     }
 
@@ -517,10 +522,10 @@
         runFdServerOnAndroid("--open-dir 3:" + androidOutputDir, "--rw-dirs 3");
         runAuthFsOnMicrodroid("--remote-new-rw-dir 3 --cid " + VMADDR_CID_HOST);
 
-        runOnMicrodroid("touch " + authfsOutputDir + "/some_file");
-        runOnMicrodroid("mkdir " + authfsOutputDir + "/some_dir");
-        runOnMicrodroid("touch " + authfsOutputDir + "/some_dir/file");
-        runOnMicrodroid("mkdir " + authfsOutputDir + "/some_dir/dir");
+        sMicrodroid.run("touch " + authfsOutputDir + "/some_file");
+        sMicrodroid.run("mkdir " + authfsOutputDir + "/some_dir");
+        sMicrodroid.run("touch " + authfsOutputDir + "/some_dir/file");
+        sMicrodroid.run("mkdir " + authfsOutputDir + "/some_dir/dir");
 
         // Action & Verify
         // Cannot create directory if an entry with the same name already exists.
@@ -542,12 +547,12 @@
         // Create a file with some data. Test the existence.
         String outputPath = authfsOutputDir + "/out";
         String androidOutputPath = androidOutputDir + "/out";
-        runOnMicrodroid("echo -n 123 > " + outputPath);
-        runOnMicrodroid("test -f " + outputPath);
+        sMicrodroid.run("echo -n 123 > " + outputPath);
+        sMicrodroid.run("test -f " + outputPath);
         sAndroid.run("test -f " + androidOutputPath);
 
         // Action
-        String output = runOnMicrodroid(
+        String output = sMicrodroid.run(
                 // Open the file for append and read
                 "exec 4>>" + outputPath + " 5<" + outputPath + "; "
                 // Delete the file from the directory
@@ -560,7 +565,7 @@
         // Verify
         // Output contains all written data, while the files are deleted.
         assertEquals("123456", output);
-        runOnMicrodroid("test ! -f " + outputPath);
+        sMicrodroid.run("test ! -f " + outputPath);
         sAndroid.run("test ! -f " + androidOutputDir + "/out");
     }
 
@@ -590,7 +595,7 @@
                 + VMADDR_CID_HOST);
 
         // Verify
-        runOnMicrodroid("test -f " + authfsInputDir + "/system/framework/services.jar");
+        sMicrodroid.run("test -f " + authfsInputDir + "/system/framework/services.jar");
         assertFailedOnMicrodroid("test -f " + authfsInputDir + "/system/bin/sh");
     }
 
@@ -602,14 +607,14 @@
 
         // Action
         String authfsOutputDir = MOUNT_DIR + "/3";
-        runOnMicrodroid("mkdir -p " + authfsOutputDir + "/dir/dir2/dir3");
-        runOnMicrodroid("touch " + authfsOutputDir + "/dir/dir2/dir3/file1");
-        runOnMicrodroid("touch " + authfsOutputDir + "/dir/dir2/dir3/file2");
-        runOnMicrodroid("touch " + authfsOutputDir + "/dir/dir2/dir3/file3");
-        runOnMicrodroid("touch " + authfsOutputDir + "/file");
+        sMicrodroid.run("mkdir -p " + authfsOutputDir + "/dir/dir2/dir3");
+        sMicrodroid.run("touch " + authfsOutputDir + "/dir/dir2/dir3/file1");
+        sMicrodroid.run("touch " + authfsOutputDir + "/dir/dir2/dir3/file2");
+        sMicrodroid.run("touch " + authfsOutputDir + "/dir/dir2/dir3/file3");
+        sMicrodroid.run("touch " + authfsOutputDir + "/file");
 
         // Verify
-        String[] actual = runOnMicrodroid("cd " + authfsOutputDir + "; find |sort").split("\n");
+        String[] actual = sMicrodroid.run("cd " + authfsOutputDir + "; find |sort").split("\n");
         String[] expected = new String[] {
                 ".",
                 "./dir",
@@ -622,14 +627,14 @@
         assertEquals(expected, actual);
 
         // Add more entries.
-        runOnMicrodroid("mkdir -p " + authfsOutputDir + "/dir2");
-        runOnMicrodroid("touch " + authfsOutputDir + "/file2");
+        sMicrodroid.run("mkdir -p " + authfsOutputDir + "/dir2");
+        sMicrodroid.run("touch " + authfsOutputDir + "/file2");
         // Check new entries. Also check that the types are correct.
-        actual = runOnMicrodroid(
+        actual = sMicrodroid.run(
                 "cd " + authfsOutputDir + "; find -maxdepth 1 -type f |sort").split("\n");
         expected = new String[] {"./file", "./file2"};
         assertEquals(expected, actual);
-        actual = runOnMicrodroid(
+        actual = sMicrodroid.run(
                 "cd " + authfsOutputDir + "; find -maxdepth 1 -type d |sort").split("\n");
         expected = new String[] {".", "./dir", "./dir2"};
         assertEquals(expected, actual);
@@ -643,7 +648,7 @@
 
         // Action & Verify
         // Change mode
-        runOnMicrodroid("chmod 321 " + MOUNT_DIR + "/3");
+        sMicrodroid.run("chmod 321 " + MOUNT_DIR + "/3");
         expectFileMode("--wx-w---x", MOUNT_DIR + "/3", TEST_OUTPUT_DIR + "/file");
         // Can't set the disallowed bits
         assertFailedOnMicrodroid("chmod +s " + MOUNT_DIR + "/3");
@@ -659,14 +664,14 @@
         // Action & Verify
         String authfsOutputDir = MOUNT_DIR + "/3";
         // Create with umask
-        runOnMicrodroid("umask 000; mkdir " + authfsOutputDir + "/dir");
-        runOnMicrodroid("umask 022; mkdir " + authfsOutputDir + "/dir/dir2");
+        sMicrodroid.run("umask 000; mkdir " + authfsOutputDir + "/dir");
+        sMicrodroid.run("umask 022; mkdir " + authfsOutputDir + "/dir/dir2");
         expectFileMode("drwxrwxrwx", authfsOutputDir + "/dir", TEST_OUTPUT_DIR + "/dir");
         expectFileMode("drwxr-xr-x", authfsOutputDir + "/dir/dir2", TEST_OUTPUT_DIR + "/dir/dir2");
         // Change mode
-        runOnMicrodroid("chmod -w " + authfsOutputDir + "/dir/dir2");
+        sMicrodroid.run("chmod -w " + authfsOutputDir + "/dir/dir2");
         expectFileMode("dr-xr-xr-x", authfsOutputDir + "/dir/dir2", TEST_OUTPUT_DIR + "/dir/dir2");
-        runOnMicrodroid("chmod 321 " + authfsOutputDir + "/dir");
+        sMicrodroid.run("chmod 321 " + authfsOutputDir + "/dir");
         expectFileMode("d-wx-w---x", authfsOutputDir + "/dir", TEST_OUTPUT_DIR + "/dir");
         // Can't set the disallowed bits
         assertFailedOnMicrodroid("chmod +s " + authfsOutputDir + "/dir/dir2");
@@ -682,14 +687,14 @@
         // Action & Verify
         String authfsOutputDir = MOUNT_DIR + "/3";
         // Create with umask
-        runOnMicrodroid("umask 000; echo -n foo > " + authfsOutputDir + "/file");
-        runOnMicrodroid("umask 022; echo -n foo > " + authfsOutputDir + "/file2");
+        sMicrodroid.run("umask 000; echo -n foo > " + authfsOutputDir + "/file");
+        sMicrodroid.run("umask 022; echo -n foo > " + authfsOutputDir + "/file2");
         expectFileMode("-rw-rw-rw-", authfsOutputDir + "/file", TEST_OUTPUT_DIR + "/file");
         expectFileMode("-rw-r--r--", authfsOutputDir + "/file2", TEST_OUTPUT_DIR + "/file2");
         // Change mode
-        runOnMicrodroid("chmod -w " + authfsOutputDir + "/file");
+        sMicrodroid.run("chmod -w " + authfsOutputDir + "/file");
         expectFileMode("-r--r--r--", authfsOutputDir + "/file", TEST_OUTPUT_DIR + "/file");
-        runOnMicrodroid("chmod 321 " + authfsOutputDir + "/file2");
+        sMicrodroid.run("chmod 321 " + authfsOutputDir + "/file2");
         expectFileMode("--wx-w---x", authfsOutputDir + "/file2", TEST_OUTPUT_DIR + "/file2");
         // Can't set the disallowed bits
         assertFailedOnMicrodroid("chmod +s " + authfsOutputDir + "/file");
@@ -705,7 +710,16 @@
         // Verify
         // Magic matches. Has only 2 inodes (root and "/3").
         assertEquals(
-                FUSE_SUPER_MAGIC_HEX + " 2", runOnMicrodroid("stat -f -c '%t %c' " + MOUNT_DIR));
+                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;
+        }
     }
 
     private void expectBackingFileConsistency(
@@ -719,8 +733,8 @@
                 "Inconsistent file hash on the backend storage", hashOnAuthFs, hashOfBackingFile);
     }
 
-    private String computeFileHashOnMicrodroid(String path) {
-        String result = runOnMicrodroid("sha256sum " + path);
+    private String computeFileHashOnMicrodroid(String path) throws DeviceNotAvailableException {
+        String result = sMicrodroid.run("sha256sum " + path);
         String[] tokens = result.split("\\s");
         if (tokens.length > 0) {
             return tokens[0];
@@ -735,7 +749,7 @@
         // TODO(b/182576497): cp returns error because close(2) returns ENOSYS in the current authfs
         // implementation. We should probably fix that since programs can expect close(2) return 0.
         String cmd = "cat " + src + " > " + dest;
-        return tryRunOnMicrodroid(cmd) != null;
+        return sMicrodroid.tryRun(cmd) != null;
     }
 
     private String computeFileHashOnAndroid(String path) throws DeviceNotAvailableException {
@@ -751,38 +765,42 @@
 
     private void expectFileMode(String expected, String microdroidPath, String androidPath)
             throws DeviceNotAvailableException {
-        String actual = runOnMicrodroid("stat -c '%A' " + microdroidPath);
+        String actual = sMicrodroid.run("stat -c '%A' " + microdroidPath);
         assertEquals("Inconsistent mode for " + microdroidPath, expected, actual);
 
         actual = sAndroid.run("stat -c '%A' " + androidPath);
         assertEquals("Inconsistent mode for " + androidPath + " (android)", expected, actual);
     }
 
-    private boolean resizeFileOnMicrodroid(String path, long size) {
-        CommandResult result = runOnMicrodroidForResult("truncate -c -s " + size + " " + path);
+    private boolean resizeFileOnMicrodroid(String path, long size)
+            throws DeviceNotAvailableException {
+        CommandResult result = sMicrodroid.runForResult("truncate -c -s " + size + " " + path);
         return result.getStatus() == CommandStatus.SUCCESS;
     }
 
-    private long getFileSizeInBytesOnMicrodroid(String path) {
-        return Long.parseLong(runOnMicrodroid("stat -c '%s' " + path));
+    private long getFileSizeInBytesOnMicrodroid(String path) throws DeviceNotAvailableException {
+        return Long.parseLong(sMicrodroid.run("stat -c '%s' " + path));
     }
 
-    private void createFileWithOnesOnMicrodroid(String filePath, long numberOfOnes) {
-        runOnMicrodroid(
+    private void createFileWithOnesOnMicrodroid(String filePath, long numberOfOnes)
+            throws DeviceNotAvailableException {
+        sMicrodroid.run(
                 "yes $'\\x01' | tr -d '\\n' | dd bs=1 count=" + numberOfOnes + " of=" + filePath);
     }
 
-    private boolean checkReadAtFileOffsetOnMicrodroid(String filePath, long offset, long size) {
+    private boolean checkReadAtFileOffsetOnMicrodroid(String filePath, long offset, long size)
+            throws DeviceNotAvailableException {
         String cmd = "dd if=" + filePath + " of=/dev/null bs=1 count=" + size;
         if (offset > 0) {
             cmd += " skip=" + offset;
         }
-        CommandResult result = runOnMicrodroidForResult(cmd);
+        CommandResult result = sMicrodroid.runForResult(cmd);
         return result.getStatus() == CommandStatus.SUCCESS;
     }
 
     private boolean writeZerosAtFileOffsetOnMicrodroid(
-            String filePath, long offset, long numberOfZeros, boolean writeThrough) {
+            String filePath, long offset, long numberOfZeros, boolean writeThrough)
+            throws DeviceNotAvailableException {
         String cmd = "dd if=/dev/zero of=" + filePath + " bs=1 count=" + numberOfZeros
                 + " conv=notrunc";
         if (offset > 0) {
@@ -791,7 +809,7 @@
         if (writeThrough) {
             cmd += " direct";
         }
-        CommandResult result = runOnMicrodroidForResult(cmd);
+        CommandResult result = sMicrodroid.runForResult(cmd);
         return result.getStatus() == CommandStatus.SUCCESS;
     }
 
@@ -810,9 +828,14 @@
                     // 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()) {
-                        CLog.i("Starting authfs");
-                        CommandResult result = runOnMicrodroidForResult(cmd);
-                        CLog.w("authfs has stopped: " + result);
+                        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 {
@@ -855,8 +878,8 @@
                 });
     }
 
-    private boolean isMicrodroidDirectoryOnFuse(String path) {
-        String fs_type = tryRunOnMicrodroid("stat -f -c '%t' " + path);
+    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);
     }
 }
diff --git a/libs/avb_bindgen/Android.bp b/libs/avb_bindgen/Android.bp
index 1035498..1e62864 100644
--- a/libs/avb_bindgen/Android.bp
+++ b/libs/avb_bindgen/Android.bp
@@ -4,6 +4,7 @@
 
 rust_bindgen {
     name: "libavb_bindgen",
+    host_supported: true,
     wrapper_src: "bindgen/avb.h",
     crate_name: "avb_bindgen",
     source_stem: "bindings",
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index 60f8fd4..b7d844f 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -79,6 +79,7 @@
         "tombstoned",
         "tombstone_transmit.microdroid",
         "cgroups.json",
+        "task_profiles.json",
         "public.libraries.android.txt",
 
         "microdroid_compatibility_matrix",
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 9e159d2..63a7fb3 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -146,17 +146,17 @@
     }
 }
 
-fn dice_derivation(verified_data: MicrodroidData, payload_config_path: &str) -> Result<()> {
+fn dice_derivation(verified_data: &MicrodroidData, payload_config_path: &str) -> Result<()> {
     // Calculate compound digests of code and authorities
     let mut code_hash_ctx = digest::Context::new(&digest::SHA512);
     let mut authority_hash_ctx = digest::Context::new(&digest::SHA512);
     code_hash_ctx.update(verified_data.apk_data.root_hash.as_ref());
     authority_hash_ctx.update(verified_data.apk_data.pubkey.as_ref());
-    for extra_apk in verified_data.extra_apks_data {
+    for extra_apk in &verified_data.extra_apks_data {
         code_hash_ctx.update(extra_apk.root_hash.as_ref());
         authority_hash_ctx.update(extra_apk.pubkey.as_ref());
     }
-    for apex in verified_data.apex_data {
+    for apex in &verified_data.apex_data {
         code_hash_ctx.update(apex.root_digest.as_ref());
         authority_hash_ctx.update(apex.public_key.as_ref());
     }
@@ -189,7 +189,7 @@
             authorityHash: authority_hash,
             authorityDescriptor: None,
             mode: if app_debuggable { Mode::DEBUG } else { Mode::NORMAL },
-            hidden: verified_data.salt.try_into().unwrap(),
+            hidden: verified_data.salt.clone().try_into().unwrap(),
         }])
         .context("IDiceMaintenance::demoteSelf failed")?;
     Ok(())
@@ -240,6 +240,10 @@
         instance.write_microdroid_data(&verified_data).context("Failed to write identity data")?;
     }
 
+    // To minimize the exposure to untrusted data, derive dice profile as soon as possible.
+    info!("DICE derivation for payload");
+    dice_derivation(&verified_data, &metadata.payload_config_path)?;
+
     // Before reading a file from the APK, start zipfuse
     run_zipfuse(
         "fscontext=u:object_r:zipfusefs:s0,context=u:object_r:system_file:s0",
@@ -263,9 +267,6 @@
     }
     mount_extra_apks(&config)?;
 
-    info!("DICE derivation for payload");
-    dice_derivation(verified_data, &metadata.payload_config_path)?;
-
     // Wait until apex config is done. (e.g. linker configuration for apexes)
     // TODO(jooyung): wait until sys.boot_completed?
     wait_for_apex_config_done()?;
diff --git a/pvmfw/Android.bp b/pvmfw/Android.bp
index 66b4c9e..8af40e2 100644
--- a/pvmfw/Android.bp
+++ b/pvmfw/Android.bp
@@ -20,3 +20,28 @@
     },
     apex_available: ["com.android.virt"],
 }
+
+cc_binary {
+    name: "pvmfw",
+    srcs: [
+        "entry.S",
+        "idmap.S",
+    ],
+    static_libs: [
+        "libpvmfw",
+    ],
+    static_executable: true,
+    no_libcrt: true,
+    nocrt: true,
+    system_shared_libs: [],
+    ldflags: [
+        "-Tpackages/modules/Virtualization/pvmfw/image.ld",
+    ],
+    enabled: false,
+    target: {
+        android_arm64: {
+            enabled: true,
+        },
+    },
+    apex_available: ["com.android.virt"],
+}
diff --git a/pvmfw/entry.S b/pvmfw/entry.S
new file mode 100644
index 0000000..787b4ff
--- /dev/null
+++ b/pvmfw/entry.S
@@ -0,0 +1,140 @@
+/*
+ * 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
+ *
+ *     https://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.
+ */
+
+.macro adr_l, reg:req, sym:req
+	adrp \reg, \sym
+	add \reg, \reg, :lo12:\sym
+.endm
+
+.macro mov_i, reg:req, imm:req
+	movz \reg, :abs_g3:\imm
+	movk \reg, :abs_g2_nc:\imm
+	movk \reg, :abs_g1_nc:\imm
+	movk \reg, :abs_g0_nc:\imm
+.endm
+
+.set .L_MAIR_DEV_nGnRE,	0x04
+.set .L_MAIR_MEM_WBWA,	0xff
+.set .Lmairval, .L_MAIR_DEV_nGnRE | (.L_MAIR_MEM_WBWA << 8)
+
+/* 4 KiB granule size for TTBR0_EL1. */
+.set .L_TCR_TG0_4KB, 0x0 << 14
+/* 4 KiB granule size for TTBR1_EL1. */
+.set .L_TCR_TG1_4KB, 0x2 << 30
+/* Disable translation table walk for TTBR1_EL1, generating a translation fault instead. */
+.set .L_TCR_EPD1, 0x1 << 23
+/* Translation table walks for TTBR0_EL1 are inner sharable. */
+.set .L_TCR_SH_INNER, 0x3 << 12
+/*
+ * Translation table walks for TTBR0_EL1 are outer write-back read-allocate write-allocate
+ * cacheable.
+ */
+.set .L_TCR_RGN_OWB, 0x1 << 10
+/*
+ * Translation table walks for TTBR0_EL1 are inner write-back read-allocate write-allocate
+ * cacheable.
+ */
+.set .L_TCR_RGN_IWB, 0x1 << 8
+/* Size offset for TTBR0_EL1 is 2**39 bytes (512 GiB). */
+.set .L_TCR_T0SZ_512, 64 - 39
+.set .Ltcrval, .L_TCR_TG0_4KB | .L_TCR_TG1_4KB | .L_TCR_EPD1 | .L_TCR_RGN_OWB
+.set .Ltcrval, .Ltcrval | .L_TCR_RGN_IWB | .L_TCR_SH_INNER | .L_TCR_T0SZ_512
+
+/* Stage 1 instruction access cacheability is unaffected. */
+.set .L_SCTLR_ELx_I, 0x1 << 12
+/* SP alignment fault if SP is not aligned to a 16 byte boundary. */
+.set .L_SCTLR_ELx_SA, 0x1 << 3
+/* Stage 1 data access cacheability is unaffected. */
+.set .L_SCTLR_ELx_C, 0x1 << 2
+/* EL0 and EL1 stage 1 MMU enabled. */
+.set .L_SCTLR_ELx_M, 0x1 << 0
+/* Privileged Access Never is unchanged on taking an exception to EL1. */
+.set .L_SCTLR_EL1_SPAN, 0x1 << 23
+/* SETEND instruction disabled at EL0 in aarch32 mode. */
+.set .L_SCTLR_EL1_SED, 0x1 << 8
+/* Various IT instructions are disabled at EL0 in aarch32 mode. */
+.set .L_SCTLR_EL1_ITD, 0x1 << 7
+.set .L_SCTLR_EL1_RES1, (0x1 << 11) | (0x1 << 20) | (0x1 << 22) | (0x1 << 28) | (0x1 << 29)
+.set .Lsctlrval, .L_SCTLR_ELx_M | .L_SCTLR_ELx_C | .L_SCTLR_ELx_SA | .L_SCTLR_EL1_ITD | .L_SCTLR_EL1_SED
+.set .Lsctlrval, .Lsctlrval | .L_SCTLR_ELx_I | .L_SCTLR_EL1_SPAN | .L_SCTLR_EL1_RES1
+/**
+ * This is a generic entry point for an image. It carries out the operations
+ * required to prepare the loaded image to be run. Specifically, it zeroes the
+ * bss section using registers x25 and above, prepares the stack, enables
+ * floating point, and sets up the exception vector.
+ */
+.section .init.entry, "ax"
+.global entry
+entry:
+	/* Enable MMU and caches. */
+
+	/*
+	 * Load and apply the memory management configuration.
+	 */
+	adrp x1, idmap
+	mov_i x2, .Lmairval
+	mov_i x3, .Ltcrval
+	mov_i x4, .Lsctlrval
+
+	/* Copy the supported PA range into TCR_EL1.IPS. */
+	mrs x6, id_aa64mmfr0_el1
+	bfi x3, x6, #32, #4
+
+	msr ttbr0_el1, x1
+	msr mair_el1, x2
+	msr tcr_el1, x3
+
+	/*
+	 * Ensure everything before this point has completed, then invalidate any potentially stale
+	 * local TLB entries before they start being used.
+	 */
+	isb
+	tlbi vmalle1
+	ic iallu
+	dsb nsh
+	isb
+
+	/*
+	 * Configure sctlr_el1 to enable MMU and cache and don't proceed until
+	 * this has completed.
+	 */
+	msr sctlr_el1, x4
+	isb
+
+	/* Disable trapping floating point access in EL1. */
+	mrs x30, cpacr_el1
+	orr x30, x30, #(0x3 << 20)
+	msr cpacr_el1, x30
+	isb
+
+	/* Zero out the bss section. */
+	adr_l x29, bss_begin
+	adr_l x30, bss_end
+0:	cmp x29, x30
+	b.hs 1f
+	stp xzr, xzr, [x29], #16
+	b 0b
+
+1:	/* Prepare the stack. */
+	adr x30, boot_stack_end
+	mov sp, x30
+
+	/* Call into Rust code. */
+	bl main
+
+	/* Loop forever waiting for interrupts. */
+2:	wfi
+	b 2b
diff --git a/pvmfw/idmap.S b/pvmfw/idmap.S
new file mode 100644
index 0000000..f5050af
--- /dev/null
+++ b/pvmfw/idmap.S
@@ -0,0 +1,48 @@
+/*
+ * 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
+ *
+ *     https://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.
+ */
+
+.set .L_TT_TYPE_BLOCK, 0x1
+.set .L_TT_TYPE_PAGE,  0x3
+.set .L_TT_TYPE_TABLE, 0x3
+
+/* Access flag. */
+.set .L_TT_AF, 0x1 << 10
+/* Not global. */
+.set .L_TT_NG, 0x1 << 11
+.set .L_TT_RO, 0x2 << 6
+.set .L_TT_XN, 0x3 << 53
+
+.set .L_TT_MT_DEV, 0x0 << 2			// MAIR #0 (DEV_nGnRE)
+.set .L_TT_MT_MEM, (0x1 << 2) | (0x3 << 8)	// MAIR #1 (MEM_WBWA), inner shareable
+
+.set .L_BLOCK_RO,  .L_TT_TYPE_BLOCK | .L_TT_MT_MEM | .L_TT_AF | .L_TT_RO | .L_TT_XN
+.set .L_BLOCK_DEV, .L_TT_TYPE_BLOCK | .L_TT_MT_DEV | .L_TT_AF | .L_TT_XN
+.set .L_BLOCK_MEM_XIP, .L_TT_TYPE_BLOCK | .L_TT_MT_MEM | .L_TT_AF | .L_TT_NG
+
+.section ".rodata.idmap", "a", %progbits
+.global idmap
+.align 12
+idmap:
+	/* level 1 */
+	.quad		.L_BLOCK_DEV | 0x0		// 1 GB of device mappings
+	.quad		.L_BLOCK_DEV | 0x40000000	// Another 1 GB of device mapppings
+	.quad		.L_TT_TYPE_TABLE + 0f		// up to 1 GB of DRAM
+	.fill		509, 8, 0x0			// 509 GB of remaining VA space
+
+	/* level 2 */
+0:	.quad		.L_BLOCK_RO  | 0x80000000	// DT provided by VMM
+	.quad		.L_BLOCK_MEM_XIP | 0x80200000	// 2 MB of DRAM containing image
+	.fill		510, 8, 0x0
diff --git a/pvmfw/image.ld b/pvmfw/image.ld
new file mode 100644
index 0000000..e08fbe2
--- /dev/null
+++ b/pvmfw/image.ld
@@ -0,0 +1,117 @@
+/*
+ * 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
+ *
+ *     https://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.
+ */
+
+MEMORY
+{
+	dtb_region	: ORIGIN = 0x80000000, LENGTH = 2M
+	image		: ORIGIN = 0x80200000, LENGTH = 2M
+}
+
+/*
+ * Code will start running at this symbol which is placed at the start of the
+ * image.
+ */
+ENTRY(entry)
+
+/*
+ * The following would be useful to check that .init code is not called back
+ * into once it has completed but it isn't supported by ld.lld.
+ *
+ * NOCROSSREFS_TO(.init .text)
+ */
+
+SECTIONS
+{
+	.dtb (NOLOAD) : {
+		dtb_begin = .;
+		. += LENGTH(dtb_region);
+		dtb_end = .;
+	} >dtb_region
+
+	/*
+	 * Collect together the code. This is page aligned so it can be mapped
+	 * as executable-only.
+	 */
+	.init : ALIGN(4096) {
+		text_begin = .;
+		*(.init.entry)
+		*(.init.*)
+	} >image
+	.text : {
+		*(.text.*)
+	} >image
+	text_end = .;
+
+	/*
+	 * Collect together read-only data. This is page aligned so it can be
+	 * mapped as read-only and non-executable.
+	 */
+	.rodata : ALIGN(4096) {
+		rodata_begin = .;
+		*(.rodata.*)
+	} >image
+	.got : {
+		*(.got)
+	} >image
+	rodata_end = .;
+
+	/*
+	 * Collect together the read-write data including .bss at the end which
+	 * will be zero'd by the entry code. This is page aligned so it can be
+	 * mapped as non-executable.
+	 */
+	.data : ALIGN(4096) {
+		data_begin = .;
+		*(.data.*)
+		/*
+		 * The entry point code assumes that .data is a multiple of 32
+		 * bytes long.
+		 */
+		. = ALIGN(32);
+		data_end = .;
+	} >image
+	/* Everything beyond this point will not be included in the binary. */
+	bin_end = .;
+
+	/* The entry point code assumes that .bss is 16-byte aligned. */
+	.bss : ALIGN(16)  {
+		bss_begin = .;
+		*(.bss.*)
+		*(COMMON)
+		. = ALIGN(16);
+		bss_end = .;
+	} >image
+
+	.stack (NOLOAD) : ALIGN(4096) {
+		boot_stack_begin = .;
+		. += 40 * 4096;
+		. = ALIGN(4096);
+		boot_stack_end = .;
+	} >image
+
+	/*
+	 * Remove unused sections from the image.
+	 */
+	/DISCARD/ : {
+		/* The image loads itself so doesn't need these sections. */
+		*(.gnu.hash)
+		*(.hash)
+		*(.interp)
+		*(.eh_frame_hdr)
+		*(.eh_frame)
+		*(.note.gnu.build-id)
+	}
+}
diff --git a/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java b/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
index df3e247..440ae18 100644
--- a/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
+++ b/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
@@ -133,6 +133,19 @@
         return result.getStdout().trim();
     }
 
+    // Same as runOnMicrodroid, but keeps retrying on error till timeout
+    private static String runOnMicrodroidRetryingOnFailure(String... cmd) {
+        final long timeoutMs = 30000; // 30 sec. Microdroid is extremely slow on GCE-on-CF.
+        int attempts = (int) MICRODROID_ADB_CONNECT_TIMEOUT_MINUTES * 60 * 1000 / 500;
+        CommandResult result = RunUtil.getDefault()
+                .runTimedCmdRetry(timeoutMs, 500, attempts,
+                        "adb", "-s", MICRODROID_SERIAL, "shell", join(cmd));
+        if (result.getStatus() != CommandStatus.SUCCESS) {
+            fail(join(cmd) + " has failed: " + result);
+        }
+        return result.getStdout().trim();
+    }
+
     // Same as runOnMicrodroid, but returns null on error.
     public static String tryRunOnMicrodroid(String... cmd) {
         CommandResult result = runOnMicrodroidForResult(cmd);
@@ -332,13 +345,16 @@
 
     public static void rootMicrodroid() {
         runOnHost("adb", "-s", MICRODROID_SERIAL, "root");
-
         runOnHostWithTimeout(
                 MICRODROID_ADB_CONNECT_TIMEOUT_MINUTES * 60 * 1000,
                 "adb",
                 "-s",
                 MICRODROID_SERIAL,
                 "wait-for-device");
+        // There have been tests when adb wait-for-device succeeded but the following command
+        // fails with error: closed. Hence, we run adb shell true in microdroid with retries
+        // before returning.
+        runOnMicrodroidRetryingOnFailure("true");
     }
 
     // Establish an adb connection to microdroid by letting Android forward the connection to
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 73cbcfa..a2e856c 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -132,139 +132,26 @@
         console_fd: Option<&ParcelFileDescriptor>,
         log_fd: Option<&ParcelFileDescriptor>,
     ) -> binder::Result<Strong<dyn IVirtualMachine>> {
-        check_manage_access()?;
-        let state = &mut *self.state.lock().unwrap();
-        let console_fd = console_fd.map(clone_file).transpose()?;
-        let log_fd = log_fd.map(clone_file).transpose()?;
-        let requester_uid = ThreadState::get_calling_uid();
-        let requester_sid = get_calling_sid()?;
-        let requester_debug_pid = ThreadState::get_calling_pid();
-        let cid = next_cid().or(Err(ExceptionCode::ILLEGAL_STATE))?;
-
-        // Counter to generate unique IDs for temporary image files.
-        let mut next_temporary_image_id = 0;
-        // Files which are referred to from composite images. These must be mapped to the crosvm
-        // child process, and not closed before it is started.
-        let mut indirect_files = vec![];
-
-        // Make directory for temporary files.
-        let temporary_directory: PathBuf = format!("{}/{}", TEMPORARY_DIRECTORY, cid).into();
-        create_dir(&temporary_directory).map_err(|e| {
-            // At this point, we do not know the protected status of Vm
-            // setting it to false, though this may not be correct.
-            write_vm_creation_stats(false, false);
-            error!(
-                "Failed to create temporary directory {:?} for VM files: {}",
-                temporary_directory, e
-            );
-            new_binder_exception(
-                ExceptionCode::SERVICE_SPECIFIC,
-                format!(
-                    "Failed to create temporary directory {:?} for VM files: {}",
-                    temporary_directory, e
-                ),
-            )
-        })?;
-
-        let is_app_config = matches!(config, VirtualMachineConfig::AppConfig(_));
-
-        let config = match config {
-            VirtualMachineConfig::AppConfig(config) => BorrowedOrOwned::Owned(
-                load_app_config(config, &temporary_directory).map_err(|e| {
-                    error!("Failed to load app config from {}: {}", &config.configPath, e);
-                    write_vm_creation_stats(config.protectedVm, false);
-                    new_binder_exception(
-                        ExceptionCode::SERVICE_SPECIFIC,
-                        format!("Failed to load app config from {}: {}", &config.configPath, e),
-                    )
-                })?,
-            ),
-            VirtualMachineConfig::RawConfig(config) => BorrowedOrOwned::Borrowed(config),
-        };
-        let config = config.as_ref();
-        let protected = config.protectedVm;
-
-        // Check if partition images are labeled incorrectly. This is to prevent random images
-        // which are not protected by the Android Verified Boot (e.g. bits downloaded by apps) from
-        // being loaded in a pVM.  Specifically, for images in the raw config, nothing is allowed
-        // to be labeled as app_data_file. For images in the app config, nothing but the instance
-        // partition is allowed to be labeled as such.
-        config
-            .disks
-            .iter()
-            .flat_map(|disk| disk.partitions.iter())
-            .filter(|partition| {
-                if is_app_config {
-                    partition.label != "vm-instance"
-                } else {
-                    true // all partitions are checked
-                }
-            })
-            .try_for_each(check_label_for_partition)
-            .map_err(|e| new_binder_exception(ExceptionCode::SERVICE_SPECIFIC, e.to_string()))?;
-
-        let zero_filler_path = temporary_directory.join("zero.img");
-        write_zero_filler(&zero_filler_path).map_err(|e| {
-            error!("Failed to make composite image: {}", e);
-            write_vm_creation_stats(protected, false);
-            new_binder_exception(
-                ExceptionCode::SERVICE_SPECIFIC,
-                format!("Failed to make composite image: {}", e),
-            )
-        })?;
-
-        // Assemble disk images if needed.
-        let disks = config
-            .disks
-            .iter()
-            .map(|disk| {
-                assemble_disk_image(
-                    disk,
-                    &zero_filler_path,
-                    &temporary_directory,
-                    &mut next_temporary_image_id,
-                    &mut indirect_files,
-                )
-            })
-            .collect::<Result<Vec<DiskFile>, _>>()?;
-
-        // Actually start the VM.
-        let crosvm_config = CrosvmConfig {
-            cid,
-            bootloader: maybe_clone_file(&config.bootloader)?,
-            kernel: maybe_clone_file(&config.kernel)?,
-            initrd: maybe_clone_file(&config.initrd)?,
-            disks,
-            params: config.params.to_owned(),
-            protected,
-            memory_mib: config.memoryMib.try_into().ok().and_then(NonZeroU32::new),
-            cpus: config.numCpus.try_into().ok().and_then(NonZeroU32::new),
-            cpu_affinity: config.cpuAffinity.clone(),
-            console_fd,
-            log_fd,
-            indirect_files,
-            platform_version: parse_platform_version_req(&config.platformVersion)?,
-        };
-        let instance = Arc::new(
-            VmInstance::new(
-                crosvm_config,
-                temporary_directory,
-                requester_uid,
-                requester_sid,
-                requester_debug_pid,
-            )
-            .map_err(|e| {
-                error!("Failed to create VM with config {:?}: {}", config, e);
-                write_vm_creation_stats(protected, false);
-                new_binder_exception(
-                    ExceptionCode::SERVICE_SPECIFIC,
-                    format!("Failed to create VM: {}", e),
-                )
-            })?,
-        );
-        state.add_vm(Arc::downgrade(&instance));
-        write_vm_creation_stats(protected, true);
-        Ok(VirtualMachine::create(instance))
+        let mut is_protected = false;
+        let ret = self.create_vm_internal(config, console_fd, log_fd, &mut is_protected);
+        match ret {
+            Ok(_) => {
+                let ok_status = Status::ok();
+                write_vm_creation_stats(
+                    is_protected,
+                    /*creation_succeeded*/ true,
+                    ok_status.exception_code() as i32,
+                );
+            }
+            Err(ref e) => {
+                write_vm_creation_stats(
+                    is_protected,
+                    /*creation_succeeded*/ false,
+                    e.exception_code() as i32,
+                );
+            }
+        }
+        ret
     }
 
     /// Initialise an empty partition image of the given size to be used as a writable partition.
@@ -466,11 +353,149 @@
         });
         service
     }
+
+    fn create_vm_internal(
+        &self,
+        config: &VirtualMachineConfig,
+        console_fd: Option<&ParcelFileDescriptor>,
+        log_fd: Option<&ParcelFileDescriptor>,
+        is_protected: &mut bool,
+    ) -> binder::Result<Strong<dyn IVirtualMachine>> {
+        check_manage_access()?;
+        let state = &mut *self.state.lock().unwrap();
+        let console_fd = console_fd.map(clone_file).transpose()?;
+        let log_fd = log_fd.map(clone_file).transpose()?;
+        let requester_uid = ThreadState::get_calling_uid();
+        let requester_sid = get_calling_sid()?;
+        let requester_debug_pid = ThreadState::get_calling_pid();
+        let cid = next_cid().or(Err(ExceptionCode::ILLEGAL_STATE))?;
+
+        // Counter to generate unique IDs for temporary image files.
+        let mut next_temporary_image_id = 0;
+        // Files which are referred to from composite images. These must be mapped to the crosvm
+        // child process, and not closed before it is started.
+        let mut indirect_files = vec![];
+
+        // Make directory for temporary files.
+        let temporary_directory: PathBuf = format!("{}/{}", TEMPORARY_DIRECTORY, cid).into();
+        create_dir(&temporary_directory).map_err(|e| {
+            // At this point, we do not know the protected status of Vm
+            // setting it to false, though this may not be correct.
+            error!(
+                "Failed to create temporary directory {:?} for VM files: {}",
+                temporary_directory, e
+            );
+            new_binder_exception(
+                ExceptionCode::SERVICE_SPECIFIC,
+                format!(
+                    "Failed to create temporary directory {:?} for VM files: {}",
+                    temporary_directory, e
+                ),
+            )
+        })?;
+
+        let is_app_config = matches!(config, VirtualMachineConfig::AppConfig(_));
+
+        let config = match config {
+            VirtualMachineConfig::AppConfig(config) => BorrowedOrOwned::Owned(
+                load_app_config(config, &temporary_directory).map_err(|e| {
+                    error!("Failed to load app config from {}: {}", &config.configPath, e);
+                    *is_protected = config.protectedVm;
+                    new_binder_exception(
+                        ExceptionCode::SERVICE_SPECIFIC,
+                        format!("Failed to load app config from {}: {}", &config.configPath, e),
+                    )
+                })?,
+            ),
+            VirtualMachineConfig::RawConfig(config) => BorrowedOrOwned::Borrowed(config),
+        };
+        let config = config.as_ref();
+        *is_protected = config.protectedVm;
+
+        // Check if partition images are labeled incorrectly. This is to prevent random images
+        // which are not protected by the Android Verified Boot (e.g. bits downloaded by apps) from
+        // being loaded in a pVM.  Specifically, for images in the raw config, nothing is allowed
+        // to be labeled as app_data_file. For images in the app config, nothing but the instance
+        // partition is allowed to be labeled as such.
+        config
+            .disks
+            .iter()
+            .flat_map(|disk| disk.partitions.iter())
+            .filter(|partition| {
+                if is_app_config {
+                    partition.label != "vm-instance"
+                } else {
+                    true // all partitions are checked
+                }
+            })
+            .try_for_each(check_label_for_partition)
+            .map_err(|e| new_binder_exception(ExceptionCode::SERVICE_SPECIFIC, e.to_string()))?;
+
+        let zero_filler_path = temporary_directory.join("zero.img");
+        write_zero_filler(&zero_filler_path).map_err(|e| {
+            error!("Failed to make composite image: {}", e);
+            new_binder_exception(
+                ExceptionCode::SERVICE_SPECIFIC,
+                format!("Failed to make composite image: {}", e),
+            )
+        })?;
+
+        // Assemble disk images if needed.
+        let disks = config
+            .disks
+            .iter()
+            .map(|disk| {
+                assemble_disk_image(
+                    disk,
+                    &zero_filler_path,
+                    &temporary_directory,
+                    &mut next_temporary_image_id,
+                    &mut indirect_files,
+                )
+            })
+            .collect::<Result<Vec<DiskFile>, _>>()?;
+
+        // Actually start the VM.
+        let crosvm_config = CrosvmConfig {
+            cid,
+            bootloader: maybe_clone_file(&config.bootloader)?,
+            kernel: maybe_clone_file(&config.kernel)?,
+            initrd: maybe_clone_file(&config.initrd)?,
+            disks,
+            params: config.params.to_owned(),
+            protected: *is_protected,
+            memory_mib: config.memoryMib.try_into().ok().and_then(NonZeroU32::new),
+            cpus: config.numCpus.try_into().ok().and_then(NonZeroU32::new),
+            cpu_affinity: config.cpuAffinity.clone(),
+            console_fd,
+            log_fd,
+            indirect_files,
+            platform_version: parse_platform_version_req(&config.platformVersion)?,
+        };
+        let instance = Arc::new(
+            VmInstance::new(
+                crosvm_config,
+                temporary_directory,
+                requester_uid,
+                requester_sid,
+                requester_debug_pid,
+            )
+            .map_err(|e| {
+                error!("Failed to create VM with config {:?}: {}", config, e);
+                new_binder_exception(
+                    ExceptionCode::SERVICE_SPECIFIC,
+                    format!("Failed to create VM: {}", e),
+                )
+            })?,
+        );
+        state.add_vm(Arc::downgrade(&instance));
+        Ok(VirtualMachine::create(instance))
+    }
 }
 
 /// Write the stats of VMCreation to statsd
-fn write_vm_creation_stats(protected: bool, success: bool) {
-    match stats_write(Hypervisor::Pkvm, protected, success) {
+fn write_vm_creation_stats(is_protected: bool, creation_succeeded: bool, exception_code: i32) {
+    match stats_write(Hypervisor::Pkvm, is_protected, creation_succeeded, exception_code) {
         Err(e) => {
             warn!("statslog_rust failed with error: {}", e);
         }