Merge "refactor: move signing out of compilation.rs"
diff --git a/.prebuilt_info/prebuilt_info_pvmfw_pvmfw_img.asciipb b/.prebuilt_info/prebuilt_info_pvmfw_pvmfw_img.asciipb
index a8abf97..0f60384 100644
--- a/.prebuilt_info/prebuilt_info_pvmfw_pvmfw_img.asciipb
+++ b/.prebuilt_info/prebuilt_info_pvmfw_pvmfw_img.asciipb
@@ -1,6 +1,6 @@
 drops {
   android_build_drop {
-    build_id: "8149267"
+    build_id: "8160972"
     target: "u-boot_pvmfw"
     source_file: "pvmfw.img"
   }
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 6338c82..7be81a8 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -7,7 +7,7 @@
       "name": "ComposHostTestCases"
     },
     {
-      "name": "VirtualizationTestCases"
+      "name": "VirtualizationTestCases.64"
     },
     {
       "name": "MicrodroidTestApp"
diff --git a/apex/sign_virt_apex.py b/apex/sign_virt_apex.py
index 8fe3403..207c938 100644
--- a/apex/sign_virt_apex.py
+++ b/apex/sign_virt_apex.py
@@ -15,7 +15,8 @@
 # limitations under the License.
 """sign_virt_apex is a command line tool for sign the Virt APEX file.
 
-Typical usage: sign_virt_apex [-v] [--avbtool path_to_avbtool] path_to_key payload_contents_dir
+Typical usage:
+  sign_virt_apex [-v] [--avbtool path_to_avbtool] [--signing_args args] payload_key payload_dir
 
 sign_virt_apex uses external tools which are assumed to be available via PATH.
 - avbtool (--avbtool can override the tool)
@@ -26,6 +27,7 @@
 import hashlib
 import os
 import re
+import shlex
 import shutil
 import subprocess
 import sys
@@ -45,6 +47,10 @@
         default='avbtool',
         help='Optional flag that specifies the AVB tool to use. Defaults to `avbtool`.')
     parser.add_argument(
+        '--signing_args',
+        help='the extra signing arguments passed to avbtool.'
+    )
+    parser.add_argument(
         'key',
         help='path to the private key file.')
     parser.add_argument(
@@ -163,6 +169,8 @@
                '--partition_name', partition_name,
                '--partition_size', partition_size,
                '--image', image_path]
+        if args.signing_args:
+            cmd.extend(shlex.split(args.signing_args))
         RunCommand(args, cmd)
 
 
@@ -182,6 +190,8 @@
                '--partition_size', partition_size,
                '--do_not_generate_fec',
                '--image', image_path]
+        if args.signing_args:
+            cmd.extend(shlex.split(args.signing_args))
         RunCommand(args, cmd)
 
 
@@ -216,6 +226,9 @@
                 cmd.extend(['--chain_partition', '%s:%s:%s' %
                            (part_name, ril, avbpubkey)])
 
+        if args.signing_args:
+            cmd.extend(shlex.split(args.signing_args))
+
         RunCommand(args, cmd)
         # libavb expects to be able to read the maximum vbmeta size, so we must provide a partition
         # which matches this or the read will fail.
diff --git a/authfs/tests/Android.bp b/authfs/tests/Android.bp
index 30606e5..1b5cf09 100644
--- a/authfs/tests/Android.bp
+++ b/authfs/tests/Android.bp
@@ -15,6 +15,7 @@
     ],
     test_suites: ["general-tests"],
     data_device_bins: ["open_then_run"],
+    per_testcase_directory: true,
     data: [
         ":authfs_test_files",
         ":MicrodroidTestApp",
diff --git a/compos/common/compos_client.rs b/compos/common/compos_client.rs
index 69f095a..b754ba7 100644
--- a/compos/common/compos_client.rs
+++ b/compos/common/compos_client.rs
@@ -206,8 +206,7 @@
         return Ok(true);
     }
 
-    let build_type = system_properties::read("ro.build.type")?.context("ro.build.type not set")?;
-    let is_debug_build = matches!(build_type.as_str(), "userdebug" | "eng");
+    let is_debug_build = system_properties::read("ro.debuggable")?.as_deref().unwrap_or("0") == "1";
     if !is_debug_build {
         bail!("Protected VM not supported, unable to start VM");
     }
@@ -215,7 +214,7 @@
     let have_unprotected_vm =
         system_properties::read_bool("ro.boot.hypervisor.vm.supported", false)?;
     if have_unprotected_vm {
-        warn!("Protected VM not supported, falling back to unprotected on {} build", build_type);
+        warn!("Protected VM not supported, falling back to unprotected on debuggable build");
         return Ok(false);
     }
 
diff --git a/compos/service/java/com/android/server/compos/IsolatedCompilationService.java b/compos/service/java/com/android/server/compos/IsolatedCompilationService.java
index bd272a0..11e3743 100644
--- a/compos/service/java/com/android/server/compos/IsolatedCompilationService.java
+++ b/compos/service/java/com/android/server/compos/IsolatedCompilationService.java
@@ -16,6 +16,8 @@
 
 package com.android.server.compos;
 
+import static android.os.Build.isDebuggable;
+
 import android.annotation.NonNull;
 import android.app.job.JobScheduler;
 import android.content.Context;
@@ -25,12 +27,11 @@
 import android.content.pm.StagedApexInfo;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.sysprop.HypervisorProperties;
 import android.util.Log;
 
 import com.android.server.SystemService;
 
-import java.io.File;
-
 /**
  * A system service responsible for performing Isolated Compilation (compiling boot & system server
  * classpath JARs in a protected VM) when appropriate.
@@ -71,12 +72,20 @@
     }
 
     private static boolean isIsolatedCompilationSupported() {
-        // Check that KVM is enabled on the device
-        if (!new File("/dev/kvm").exists()) {
-            return false;
+        // The CompOS APEX is present or we wouldn't be here. So just check that the device
+        // has a suitably capable hypervisor.
+
+        // We really want a protected VM
+        if (HypervisorProperties.hypervisor_protected_vm_supported().orElse(false)) {
+            return true;
         }
 
-        return true;
+        // But can use a non-protected VM on a debug build
+        if (isDebuggable()) {
+            return HypervisorProperties.hypervisor_vm_supported().orElse(false);
+        }
+
+        return false;
     }
 
     private static class StagedApexObserver extends IStagedApexObserver.Stub {
diff --git a/compos/tests/Android.bp b/compos/tests/Android.bp
index d380059..c178ddd 100644
--- a/compos/tests/Android.bp
+++ b/compos/tests/Android.bp
@@ -13,5 +13,7 @@
     static_libs: [
         "VirtualizationTestHelper",
     ],
-    test_suites: ["general-tests"],
+    test_suites: [
+        "general-tests",
+    ],
 }
diff --git a/docs/getting_started/index.md b/docs/getting_started/index.md
index 6d3b208..ac5d38b 100644
--- a/docs/getting_started/index.md
+++ b/docs/getting_started/index.md
@@ -36,7 +36,7 @@
 all can run via `atest`.
 
 ```shell
-atest VirtualizationTestCases
+atest VirtualizationTestCases.64
 atest MicrodroidHostTestCases
 atest MicrodroidTestApp
 ```
diff --git a/microdroid/payload/mk_payload.cc b/microdroid/payload/mk_payload.cc
index 33e91b9..fd1ce78 100644
--- a/microdroid/payload/mk_payload.cc
+++ b/microdroid/payload/mk_payload.cc
@@ -171,6 +171,7 @@
         auto* apex = metadata.add_apexes();
         apex->set_name(apex_config.name);
         apex->set_partition_name("microdroid-apex-" + std::to_string(apex_index++));
+        apex->set_is_factory(true);
     }
 
     if (config.apk.has_value()) {
diff --git a/pvmfw/pvmfw.img b/pvmfw/pvmfw.img
index c036c76..27c2d2b 100644
--- a/pvmfw/pvmfw.img
+++ b/pvmfw/pvmfw.img
Binary files differ
diff --git a/tests/Android.bp b/tests/Android.bp
index 35ff6a0..74d58f5 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -21,8 +21,13 @@
 kernel_stem = "kernel_prebuilts-" + kernel_version
 
 cc_test {
-    name: "VirtualizationTestCases",
-    test_suites: ["general-tests"],
+    // ".64" suffix is to work around cts-unit-test which is demanding that all
+    // executables in CTS should have both 32 and 64 ABIs.
+    name: "VirtualizationTestCases.64",
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
     srcs: [
         "common.cc",
         "vsock_test.cc",
diff --git a/tests/AndroidTest.xml b/tests/AndroidTest.xml
index 5e7faf9..68e9c1b 100644
--- a/tests/AndroidTest.xml
+++ b/tests/AndroidTest.xml
@@ -15,11 +15,16 @@
 -->
 
 <configuration description="Config for Virtualization tests">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="security" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <!-- Push test binaries to the device. -->
     <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
         <option name="cleanup" value="true" />
         <option name="abort-on-push-failure" value="true" />
-        <option name="push-file" key="VirtualizationTestCases" value="/data/local/tmp/virt-test/VirtualizationTestCases" />
+        <option name="push-file" key="VirtualizationTestCases.64" value="/data/local/tmp/virt-test/VirtualizationTestCases.64" />
         <option name="push-file" key="virt_test_kernel"        value="/data/local/tmp/virt-test/kernel" />
         <option name="push-file" key="virt_test_initramfs.img" value="/data/local/tmp/virt-test/initramfs" />
     </target_preparer>
@@ -30,7 +35,7 @@
 
     <test class="com.android.tradefed.testtype.GTest" >
         <option name="native-test-device-path" value="/data/local/tmp/virt-test" />
-        <option name="module-name" value="VirtualizationTestCases" />
+        <option name="module-name" value="VirtualizationTestCases.64" />
         <!-- test-timeout unit is ms, value = 2 minutes -->
         <option name="native-test-timeout" value="120000" />
     </test>
diff --git a/tests/hostside/Android.bp b/tests/hostside/Android.bp
index 1aef796..bc8a4a5 100644
--- a/tests/hostside/Android.bp
+++ b/tests/hostside/Android.bp
@@ -5,12 +5,16 @@
 java_test_host {
     name: "MicrodroidHostTestCases",
     srcs: ["java/**/*.java"],
-    test_suites: ["general-tests"],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
     libs: [
         "tradefed",
     ],
     static_libs: [
         "VirtualizationTestHelper",
     ],
+    per_testcase_directory: true,
     data: [":MicrodroidTestApp"],
 }
diff --git a/tests/hostside/AndroidTest.xml b/tests/hostside/AndroidTest.xml
index e8aced6..79428ce 100644
--- a/tests/hostside/AndroidTest.xml
+++ b/tests/hostside/AndroidTest.xml
@@ -14,6 +14,11 @@
      limitations under the License.
 -->
 <configuration description="Tests for microdroid">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="security" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="MicrodroidHostTestCases.jar" />
     </test>
diff --git a/tests/testapk/Android.bp b/tests/testapk/Android.bp
index a8b068c..0699e3d 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -4,7 +4,10 @@
 
 android_test {
     name: "MicrodroidTestApp",
-    test_suites: ["general-tests"],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
     srcs: ["src/java/**/*.java"],
     static_libs: [
         "androidx.test.runner",
@@ -16,6 +19,8 @@
     jni_libs: ["MicrodroidTestNativeLib"],
     platform_apis: true,
     use_embedded_native_libs: true,
+    // We only support 64-bit ABI, but CTS demands all APKs to be multi-ABI.
+    compile_multilib: "both",
 }
 
 // TODO(jiyong): make this a binary, not a shared library
diff --git a/tests/testapk/AndroidTest.xml b/tests/testapk/AndroidTest.xml
index c7097db..e8bb1aa 100644
--- a/tests/testapk/AndroidTest.xml
+++ b/tests/testapk/AndroidTest.xml
@@ -14,7 +14,12 @@
      limitations under the License.
 -->
 <configuration description="Runs sample instrumentation test.">
-    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="security" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="test-file-name" value="MicrodroidTestApp.apk" />
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
index 8cfb3a1..0587299 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -30,6 +30,7 @@
 import android.os.Build;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
+import android.os.SystemProperties;
 import android.system.virtualmachine.VirtualMachine;
 import android.system.virtualmachine.VirtualMachineCallback;
 import android.system.virtualmachine.VirtualMachineConfig;
@@ -51,6 +52,7 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.io.RandomAccessFile;
 import java.nio.file.Files;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutorService;
@@ -62,6 +64,8 @@
 public class MicrodroidTests {
     @Rule public Timeout globalTimeout = Timeout.seconds(300);
 
+    private static final String KERNEL_VERSION = SystemProperties.get("ro.kernel.version");
+
     private static class Inner {
         public Context mContext;
         public VirtualMachineManager mVmm;
@@ -140,6 +144,11 @@
 
     @Test
     public void connectToVmService() throws VirtualMachineException, InterruptedException {
+        assume()
+            .withMessage("SKip on 5.4 kernel. b/218303240")
+            .that(KERNEL_VERSION)
+            .isNotEqualTo("5.4");
+
         VirtualMachineConfig.Builder builder =
                 new VirtualMachineConfig.Builder(mInner.mContext,
                         "assets/vm_config_extra_apk.json");
@@ -218,6 +227,11 @@
             .that(android.os.Build.DEVICE)
             .isNotEqualTo("vsoc_x86_64");
 
+        assume()
+            .withMessage("SKip on 5.4 kernel. b/218303240")
+            .that(KERNEL_VERSION)
+            .isNotEqualTo("5.4");
+
         VirtualMachineConfig.Builder builder =
                 new VirtualMachineConfig.Builder(mInner.mContext, "assets/vm_config.json");
         VirtualMachineConfig normalConfig = builder.debugLevel(DebugLevel.NONE).build();
@@ -301,6 +315,11 @@
             .that(android.os.Build.DEVICE)
             .isNotEqualTo("vsoc_x86_64");
 
+        assume()
+            .withMessage("SKip on 5.4 kernel. b/218303240")
+            .that(KERNEL_VERSION)
+            .isNotEqualTo("5.4");
+
         byte[] vm_a_secret = launchVmAndGetSecret("test_vm_a");
         byte[] vm_b_secret = launchVmAndGetSecret("test_vm_b");
         assertThat(vm_a_secret).isNotNull();
@@ -316,10 +335,93 @@
             .that(android.os.Build.DEVICE)
             .isNotEqualTo("vsoc_x86_64");
 
+        assume()
+            .withMessage("SKip on 5.4 kernel. b/218303240")
+            .that(KERNEL_VERSION)
+            .isNotEqualTo("5.4");
+
         byte[] vm_secret_first_boot = launchVmAndGetSecret("test_vm");
         byte[] vm_secret_second_boot = launchVmAndGetSecret("test_vm");
         assertThat(vm_secret_first_boot).isNotNull();
         assertThat(vm_secret_second_boot).isNotNull();
         assertThat(vm_secret_first_boot).isEqualTo(vm_secret_second_boot);
     }
+
+    @Test
+    public void bootFailsWhenInstanceDiskIsCompromised()
+            throws VirtualMachineException, InterruptedException, IOException {
+        assume().withMessage("Skip on Cuttlefish. b/195765441")
+                .that(android.os.Build.DEVICE)
+                .isNotEqualTo("vsoc_x86_64");
+
+        VirtualMachineConfig config =
+                new VirtualMachineConfig.Builder(mInner.mContext, "assets/vm_config.json")
+                        .debugLevel(DebugLevel.NONE)
+                        .build();
+
+        // Remove any existing VM so we can start from scratch
+        VirtualMachine oldVm = mInner.mVmm.getOrCreate("test_vm_integrity", config);
+        oldVm.delete();
+
+        mInner.mVm = mInner.mVmm.getOrCreate("test_vm_integrity", config);
+
+        VmEventListener listener =
+                new VmEventListener() {
+                    private boolean mPayloadReadyCalled = false;
+
+                    @Override
+                    public void onPayloadReady(VirtualMachine vm) {
+                        mPayloadReadyCalled = true;
+                        forceStop(vm);
+                    }
+
+                    @Override
+                    public void onDied(VirtualMachine vm, @DeathReason int reason) {
+                        assertTrue(mPayloadReadyCalled);
+                    }
+                };
+        listener.runToFinish(mInner.mVm);
+
+        // Launch the same VM after flipping a bit of the instance image.
+        // Flip actual data, as flipping trivial bits like the magic string isn't interesting.
+        File vmRoot = new File(mInner.mContext.getFilesDir(), "vm");
+        File vmDir = new File(vmRoot, "test_vm_integrity");
+        File instanceImgPath = new File(vmDir, "instance.img");
+        RandomAccessFile instanceFile = new RandomAccessFile(instanceImgPath, "rw");
+
+        // microdroid data partition starts at 0x60200, actual data at 0x60400, based on experiment
+        // TODO: parse image file (QEMU qcow2) correctly?
+        long headerOffset = 0x60400;
+        instanceFile.seek(headerOffset);
+        int b = instanceFile.readByte();
+        instanceFile.seek(headerOffset);
+        instanceFile.writeByte(b ^ 1);
+        instanceFile.close();
+
+        mInner.mVm = mInner.mVmm.get("test_vm_integrity"); // re-load the vm with new instance disk
+        listener =
+                new VmEventListener() {
+                    private boolean mPayloadStarted = false;
+                    private boolean mErrorOccurred = false;
+
+                    @Override
+                    public void onPayloadStarted(VirtualMachine vm, ParcelFileDescriptor stream) {
+                        mPayloadStarted = true;
+                        forceStop(vm);
+                    }
+
+                    @Override
+                    public void onError(VirtualMachine vm, int errorCode, String message) {
+                        mErrorOccurred = true;
+                        forceStop(vm);
+                    }
+
+                    @Override
+                    public void onDied(VirtualMachine vm, @DeathReason int reason) {
+                        assertFalse(mPayloadStarted);
+                        assertTrue(mErrorOccurred);
+                    }
+                };
+        listener.runToFinish(mInner.mVm);
+    }
 }
diff --git a/virtualizationservice/src/crosvm.rs b/virtualizationservice/src/crosvm.rs
index 2c50fed..df7a7d2 100644
--- a/virtualizationservice/src/crosvm.rs
+++ b/virtualizationservice/src/crosvm.rs
@@ -272,6 +272,13 @@
 
     if config.protected {
         command.arg("--protected-vm");
+
+        // 3 virtio-console devices + vsock = 4.
+        let virtio_pci_device_count = 4 + config.disks.len();
+        // crosvm virtio queue has 256 entries, so 2 MiB per device (2 pages per entry) should be
+        // enough.
+        let swiotlb_size_mib = 2 * virtio_pci_device_count;
+        command.arg("--swiotlb").arg(swiotlb_size_mib.to_string());
     }
 
     if let Some(memory_mib) = config.memory_mib {