Merge changes Ida323bae,I3097b152,Id63cc01f

* changes:
  javalib: Remove JNI boilerplate code
  javalib: More eagerly terminate virtmgr
  javalib: Mark mVirtualizationService field final
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 321dbf4..c37fd19 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -46,9 +46,18 @@
       "path": "packages/modules/Virtualization/virtualizationservice"
     },
     {
+      "path": "packages/modules/Virtualization/libs/apexutil"
+    },
+    {
       "path": "packages/modules/Virtualization/libs/apkverify"
     },
     {
+      "path": "packages/modules/Virtualization/libs/avb"
+    },
+    {
+      "path": "packages/modules/Virtualization/libs/capabilities"
+    },
+    {
       "path": "packages/modules/Virtualization/libs/devicemapper"
     },
     {
@@ -58,12 +67,18 @@
       "path": "packages/modules/Virtualization/authfs"
     },
     {
+      "path": "packages/modules/Virtualization/microdroid_manager"
+    },
+    {
       "path": "packages/modules/Virtualization/pvmfw"
     },
     {
       "path": "packages/modules/Virtualization/rialto"
     },
     {
+      "path": "packages/modules/Virtualization/vm"
+    },
+    {
       "path": "packages/modules/Virtualization/vmbase"
     },
     {
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index aa8d600..34851e4 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -162,7 +162,7 @@
     })
     public @interface Status {}
 
-     /** The virtual machine has just been created, or {@link #stop()} was called on it. */
+    /** The virtual machine has just been created, or {@link #stop} was called on it. */
     public static final int STATUS_STOPPED = 0;
 
     /** The virtual machine is running. */
@@ -588,8 +588,7 @@
      * Returns the currently selected config of this virtual machine. There can be multiple virtual
      * machines sharing the same config. Even in that case, the virtual machines are completely
      * isolated from each other; they have different secrets. It is also possible that a virtual
-     * machine can change its config, which can be done by calling {@link
-     * #setConfig(VirtualMachineConfig)}.
+     * machine can change its config, which can be done by calling {@link #setConfig}.
      *
      * <p>NOTE: This method may block and should not be called on the main thread.
      *
@@ -748,8 +747,7 @@
     /**
      * Runs this virtual machine. The returning of this method however doesn't mean that the VM has
      * actually started running or the OS has booted there. Such events can be notified by
-     * registering a callback using {@link #setCallback(Executor, VirtualMachineCallback)} before
-     * calling {@code run()}.
+     * registering a callback using {@link #setCallback} before calling {@code run()}.
      *
      * <p>NOTE: This method may block and should not be called on the main thread.
      *
@@ -933,7 +931,15 @@
     /**
      * Stops this virtual machine. Stopping a virtual machine is like pulling the plug on a real
      * computer; the machine halts immediately. Software running on the virtual machine is not
-     * notified of the event. A stopped virtual machine can be re-started by calling {@link #run()}.
+     * notified of the event. Writes to {@linkplain
+     * VirtualMachineConfig.Builder#setEncryptedStorageKib encrypted storage} might not be
+     * persisted, and the instance might be left in an inconsistent state.
+     *
+     * <p>For a graceful shutdown, you could request the payload to call {@code exit()}, e.g. via a
+     * {@linkplain #connectToVsockServer binder request}, and wait for {@link
+     * VirtualMachineCallback#onPayloadFinished} to be called.
+     *
+     * <p>A stopped virtual machine can be re-started by calling {@link #run()}.
      *
      * <p>NOTE: This method may block and should not be called on the main thread.
      *
@@ -1016,8 +1022,8 @@
      * like the number of CPU and size of the RAM, depending on the situation (e.g. the size of the
      * application to run on the virtual machine, etc.)
      *
-     * <p>The new config must be {@link VirtualMachineConfig#isCompatibleWith compatible with} the
-     * existing config.
+     * <p>The new config must be {@linkplain VirtualMachineConfig#isCompatibleWith compatible with}
+     * the existing config.
      *
      * <p>NOTE: This method may block and should not be called on the main thread.
      *
@@ -1053,8 +1059,8 @@
     /**
      * Connect to a VM's binder service via vsock and return the root IBinder object. Guest VMs are
      * expected to set up vsock servers in their payload. After the host app receives the {@link
-     * VirtualMachineCallback#onPayloadReady(VirtualMachine)}, it can use this method to establish a
-     * connection to the guest VM.
+     * VirtualMachineCallback#onPayloadReady}, it can use this method to establish a connection to
+     * the guest VM.
      *
      * <p>NOTE: This method may block and should not be called on the main thread.
      *
@@ -1082,6 +1088,8 @@
     /**
      * Opens a vsock connection to the VM on the given port.
      *
+     * <p>The caller is responsible for closing the returned {@code ParcelFileDescriptor}.
+     *
      * <p>NOTE: This method may block and should not be called on the main thread.
      *
      * @throws VirtualMachineException if connecting fails.
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java b/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java
index 9aaecf0..d72ba14 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java
@@ -141,8 +141,8 @@
     void onPayloadStarted(@NonNull VirtualMachine vm);
 
     /**
-     * Called when the payload in the VM is ready to serve. See
-     * {@link VirtualMachine#connectToVsockServer(int)}.
+     * Called when the payload in the VM is ready to serve. See {@link
+     * VirtualMachine#connectToVsockServer}.
      */
     void onPayloadReady(@NonNull VirtualMachine vm);
 
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
index 7555dec..7c16798 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -587,7 +587,7 @@
 
         /**
          * Sets the number of vCPUs in the VM. Defaults to 1. Cannot be more than the number of real
-         * CPUs (as returned by {@link Runtime#availableProcessors()}).
+         * CPUs (as returned by {@link Runtime#availableProcessors}).
          *
          * @hide
          */
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java b/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java
index c9718aa..483779a 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java
@@ -29,7 +29,7 @@
  * A VM descriptor that captures the state of a Virtual Machine.
  *
  * <p>You can capture the current state of VM by creating an instance of this class with {@link
- * VirtualMachine#toDescriptor()}, optionally pass it to another App, and then build an identical VM
+ * VirtualMachine#toDescriptor}, optionally pass it to another App, and then build an identical VM
  * with the descriptor received.
  *
  * @hide
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
index 6aa8133..7773cb5 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
@@ -38,9 +38,9 @@
 import java.util.Map;
 
 /**
- * Manages {@link VirtualMachine virtual machine} instances created by an app. Each instance is
- * created from a {@link VirtualMachineConfig configuration} that defines the shape of the VM (RAM,
- * CPUs), the code to execute within it, etc.
+ * Manages {@linkplain VirtualMachine virtual machine} instances created by an app. Each instance is
+ * created from a {@linkplain VirtualMachineConfig configuration} that defines the shape of the VM
+ * (RAM, CPUs), the code to execute within it, etc.
  *
  * <p>Each virtual machine instance is named; the configuration and related state of each is
  * persisted in the app's private data directory and an instance can be retrieved given the name.
diff --git a/libs/apexutil/TEST_MAPPING b/libs/apexutil/TEST_MAPPING
new file mode 100644
index 0000000..f66a400
--- /dev/null
+++ b/libs/apexutil/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "avf-presubmit" : [
+    {
+      "name" : "libapexutil_rust.test"
+    }
+  ]
+}
diff --git a/libs/avb/TEST_MAPPING b/libs/avb/TEST_MAPPING
new file mode 100644
index 0000000..2f4bccc
--- /dev/null
+++ b/libs/avb/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "avf-presubmit" : [
+    {
+      "name" : "libavb_bindgen_test"
+    }
+  ]
+}
diff --git a/libs/capabilities/TEST_MAPPING b/libs/capabilities/TEST_MAPPING
new file mode 100644
index 0000000..568a771
--- /dev/null
+++ b/libs/capabilities/TEST_MAPPING
@@ -0,0 +1,10 @@
+{
+  "avf-presubmit" : [
+    {
+      "name" : "libcap_rust.test"
+    },
+    {
+      "name" : "libcap_bindgen_test"
+    }
+  ]
+}
diff --git a/microdroid_manager/TEST_MAPPING b/microdroid_manager/TEST_MAPPING
new file mode 100644
index 0000000..af0abe7
--- /dev/null
+++ b/microdroid_manager/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "avf-presubmit" : [
+    {
+      "name" : "microdroid_manager_test"
+    }
+  ]
+}
diff --git a/pvmfw/avb/src/verify.rs b/pvmfw/avb/src/verify.rs
index d5f7283..b6db601 100644
--- a/pvmfw/avb/src/verify.rs
+++ b/pvmfw/avb/src/verify.rs
@@ -393,10 +393,12 @@
 mod tests {
     use super::*;
     use anyhow::Result;
-    use std::fs;
+    use avb_bindgen::AvbFooter;
+    use std::{fs, mem::size_of};
 
     const PUBLIC_KEY_RSA2048_PATH: &str = "data/testkey_rsa2048_pub.bin";
     const PUBLIC_KEY_RSA4096_PATH: &str = "data/testkey_rsa4096_pub.bin";
+    const RANDOM_FOOTER_POS: usize = 30;
 
     /// This test uses the Microdroid payload compiled on the fly to check that
     /// the latest payload can be verified successfully.
@@ -457,6 +459,19 @@
         )
     }
 
+    #[test]
+    fn tampered_kernel_footer_fails_verification() -> Result<()> {
+        let mut kernel = load_latest_signed_kernel()?;
+        let avb_footer_index = kernel.len() - size_of::<AvbFooter>() + RANDOM_FOOTER_POS;
+        kernel[avb_footer_index] = !kernel[avb_footer_index];
+
+        assert_payload_verification_fails(
+            &kernel,
+            &fs::read(PUBLIC_KEY_RSA4096_PATH)?,
+            AvbImageVerifyError::InvalidMetadata,
+        )
+    }
+
     fn assert_payload_verification_fails(
         kernel: &[u8],
         trusted_public_key: &[u8],
diff --git a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
index 3c3faf2..9cfac4e 100644
--- a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
+++ b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
@@ -65,6 +65,7 @@
 
     private static final String APEX_ETC_FS = "/apex/com.android.virt/etc/fs/";
     private static final double SIZE_MB = 1024.0 * 1024.0;
+    private static final double NANO_TO_MILLI = 1000000.0;
     private static final String MICRODROID_IMG_PREFIX = "microdroid_";
     private static final String MICRODROID_IMG_SUFFIX = ".img";
 
@@ -80,10 +81,11 @@
     private Instrumentation mInstrumentation;
 
     @Before
-    public void setup() {
+    public void setup() throws IOException {
         grantPermission(VirtualMachine.MANAGE_VIRTUAL_MACHINE_PERMISSION);
         grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
         prepareTestSetup(mProtectedVm);
+        setMaxPerformanceTaskProfile();
         mInstrumentation = getInstrumentation();
     }
 
@@ -140,19 +142,12 @@
 
         final int trialCount = 10;
 
-        List<Double> vmStartingTimeMetrics = new ArrayList<>();
         List<Double> bootTimeMetrics = new ArrayList<>();
-        List<Double> bootloaderTimeMetrics = new ArrayList<>();
-        List<Double> kernelBootTimeMetrics = new ArrayList<>();
-        List<Double> userspaceBootTimeMetrics = new ArrayList<>();
-
         for (int i = 0; i < trialCount; i++) {
-
-            // To grab boot events from log, set debug mode to FULL
             VirtualMachineConfig normalConfig =
                     newVmConfigBuilder()
                             .setPayloadBinaryPath("MicrodroidIdleNativeLib.so")
-                            .setDebugLevel(DEBUG_LEVEL_FULL)
+                            .setDebugLevel(DEBUG_LEVEL_NONE)
                             .setMemoryMib(256)
                             .build();
             forceCreateNewVirtualMachine("test_vm_boot_time", normalConfig);
@@ -160,12 +155,74 @@
             BootResult result = tryBootVm(TAG, "test_vm_boot_time");
             assertThat(result.payloadStarted).isTrue();
 
-            final double nanoToMilli = 1000000.0;
-            vmStartingTimeMetrics.add(result.getVMStartingElapsedNanoTime() / nanoToMilli);
-            bootTimeMetrics.add(result.endToEndNanoTime / nanoToMilli);
-            bootloaderTimeMetrics.add(result.getBootloaderElapsedNanoTime() / nanoToMilli);
-            kernelBootTimeMetrics.add(result.getKernelElapsedNanoTime() / nanoToMilli);
-            userspaceBootTimeMetrics.add(result.getUserspaceElapsedNanoTime() / nanoToMilli);
+            bootTimeMetrics.add(result.endToEndNanoTime / NANO_TO_MILLI);
+        }
+
+        reportMetrics(bootTimeMetrics, "boot_time", "ms");
+    }
+
+    @Test
+    public void testMicrodroidMulticoreBootTime()
+            throws VirtualMachineException, InterruptedException, IOException {
+        assume().withMessage("Skip on CF; too slow").that(isCuttlefish()).isFalse();
+
+        final int trialCount = 10;
+        final int[] trialNumCpus = {2, 4, 8};
+
+        for (int numCpus : trialNumCpus) {
+            List<Double> bootTimeMetrics = new ArrayList<>();
+            for (int i = 0; i < trialCount; i++) {
+                VirtualMachineConfig normalConfig =
+                        newVmConfigBuilder()
+                                .setPayloadBinaryPath("MicrodroidIdleNativeLib.so")
+                                .setDebugLevel(DEBUG_LEVEL_NONE)
+                                .setMemoryMib(256)
+                                .setNumCpus(numCpus)
+                                .build();
+                forceCreateNewVirtualMachine("test_vm_boot_time_multicore", normalConfig);
+
+                BootResult result = tryBootVm(TAG, "test_vm_boot_time_multicore");
+                assertThat(result.payloadStarted).isTrue();
+
+                bootTimeMetrics.add(result.endToEndNanoTime / NANO_TO_MILLI);
+            }
+
+            String metricName = "boot_time_" + numCpus + "cpus";
+            reportMetrics(bootTimeMetrics, metricName, "ms");
+        }
+    }
+
+    @Test
+    public void testMicrodroidDebugBootTime()
+            throws VirtualMachineException, InterruptedException, IOException {
+        assume().withMessage("Skip on CF; too slow").that(isCuttlefish()).isFalse();
+
+        final int trialCount = 10;
+
+        List<Double> vmStartingTimeMetrics = new ArrayList<>();
+        List<Double> bootTimeMetrics = new ArrayList<>();
+        List<Double> bootloaderTimeMetrics = new ArrayList<>();
+        List<Double> kernelBootTimeMetrics = new ArrayList<>();
+        List<Double> userspaceBootTimeMetrics = new ArrayList<>();
+
+        for (int i = 0; i < trialCount; i++) {
+            // To grab boot events from log, set debug mode to FULL
+            VirtualMachineConfig normalConfig =
+                    newVmConfigBuilder()
+                            .setPayloadBinaryPath("MicrodroidIdleNativeLib.so")
+                            .setDebugLevel(DEBUG_LEVEL_FULL)
+                            .setMemoryMib(256)
+                            .build();
+            forceCreateNewVirtualMachine("test_vm_boot_time_debug", normalConfig);
+
+            BootResult result = tryBootVm(TAG, "test_vm_boot_time_debug");
+            assertThat(result.payloadStarted).isTrue();
+
+            vmStartingTimeMetrics.add(result.getVMStartingElapsedNanoTime() / NANO_TO_MILLI);
+            bootTimeMetrics.add(result.endToEndNanoTime / NANO_TO_MILLI);
+            bootloaderTimeMetrics.add(result.getBootloaderElapsedNanoTime() / NANO_TO_MILLI);
+            kernelBootTimeMetrics.add(result.getKernelElapsedNanoTime() / NANO_TO_MILLI);
+            userspaceBootTimeMetrics.add(result.getUserspaceElapsedNanoTime() / NANO_TO_MILLI);
         }
 
         reportMetrics(vmStartingTimeMetrics, "vm_starting_time", "ms");
diff --git a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
index 536f663..d762310 100644
--- a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
+++ b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
@@ -24,6 +24,7 @@
 import android.content.Context;
 import android.os.ParcelFileDescriptor;
 import android.os.SystemProperties;
+import android.system.Os;
 import android.system.virtualmachine.VirtualMachine;
 import android.system.virtualmachine.VirtualMachineCallback;
 import android.system.virtualmachine.VirtualMachineConfig;
@@ -50,6 +51,8 @@
 import java.util.concurrent.TimeUnit;
 
 public abstract class MicrodroidDeviceTestBase {
+    private final String MAX_PERFORMANCE_TASK_PROFILE = "CPUSET_SP_TOP_APP";
+
     public static boolean isCuttlefish() {
         return DeviceProperties.create(SystemProperties::get).isCuttlefish();
     }
@@ -73,6 +76,17 @@
                 permission);
     }
 
+    protected final void setMaxPerformanceTaskProfile() throws IOException {
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        UiAutomation uiAutomation = instrumentation.getUiAutomation();
+        String cmd = "settaskprofile " + Os.gettid() + " " + MAX_PERFORMANCE_TASK_PROFILE;
+        String out = runInShell("MicrodroidDeviceTestBase", uiAutomation, cmd).trim();
+        String expect = "Profile " + MAX_PERFORMANCE_TASK_PROFILE + " is applied successfully!";
+        if (!expect.equals(out)) {
+            throw new IOException("Could not apply max performance task profile: " + out);
+        }
+    }
+
     private Context mCtx;
     private boolean mProtectedVm;
 
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 897879b..2656c3b 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -20,6 +20,8 @@
 import static android.system.virtualmachine.VirtualMachine.STATUS_STOPPED;
 import static android.system.virtualmachine.VirtualMachineConfig.DEBUG_LEVEL_FULL;
 import static android.system.virtualmachine.VirtualMachineConfig.DEBUG_LEVEL_NONE;
+import static android.system.virtualmachine.VirtualMachineManager.CAPABILITY_NON_PROTECTED_VM;
+import static android.system.virtualmachine.VirtualMachineManager.CAPABILITY_PROTECTED_VM;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.TruthJUnit.assume;
@@ -48,6 +50,8 @@
 import com.android.microdroid.test.device.MicrodroidDeviceTestBase;
 import com.android.microdroid.testservice.ITestService;
 
+import com.google.common.truth.BooleanSubject;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Ignore;
@@ -314,8 +318,8 @@
 
     @Test
     @CddTest(requirements = {"9.17/C-1-1"})
-    public void vmConfigUnitTests() {
-
+    public void vmConfigGetAndSetTests() {
+        // Minimal has as little as specified as possible; everything that can be is defaulted.
         VirtualMachineConfig.Builder minimalBuilder = newVmConfigBuilder();
         VirtualMachineConfig minimal = minimalBuilder.setPayloadBinaryPath("binary/path").build();
 
@@ -329,6 +333,8 @@
         assertThat(minimal.isEncryptedStorageEnabled()).isFalse();
         assertThat(minimal.getEncryptedStorageKib()).isEqualTo(0);
 
+        // Maximal has everything that can be set to some non-default value. (And has different
+        // values than minimal for the required fields.)
         int maxCpus = Runtime.getRuntime().availableProcessors();
         VirtualMachineConfig.Builder maximalBuilder =
                 newVmConfigBuilder()
@@ -353,18 +359,11 @@
         assertThat(minimal.isCompatibleWith(maximal)).isFalse();
         assertThat(minimal.isCompatibleWith(minimal)).isTrue();
         assertThat(maximal.isCompatibleWith(maximal)).isTrue();
-
-        VirtualMachineConfig compatible = maximalBuilder.setNumCpus(1).setMemoryMib(99).build();
-        assertThat(compatible.isCompatibleWith(maximal)).isTrue();
-
-        // Assert that different encrypted storage size would imply the configs are incompatible
-        VirtualMachineConfig incompatible = minimalBuilder.setEncryptedStorageKib(1048).build();
-        assertThat(incompatible.isCompatibleWith(minimal)).isFalse();
     }
 
     @Test
     @CddTest(requirements = {"9.17/C-1-1"})
-    public void vmConfigBuilderUnitTests() {
+    public void vmConfigBuilderValidationTests() {
         VirtualMachineConfig.Builder builder = newVmConfigBuilder();
 
         // All your null are belong to me.
@@ -395,6 +394,44 @@
 
     @Test
     @CddTest(requirements = {"9.17/C-1-1"})
+    public void compatibleConfigTests() throws Exception {
+        int maxCpus = Runtime.getRuntime().availableProcessors();
+
+        VirtualMachineConfig.Builder builder =
+                newVmConfigBuilder().setPayloadBinaryPath("binary/path").setApkPath("/apk/path");
+        VirtualMachineConfig baseline = builder.build();
+
+        // A config must be compatible with itself
+        assertConfigCompatible(baseline, builder).isTrue();
+
+        // Changes that must always be compatible
+        assertConfigCompatible(baseline, builder.setMemoryMib(99)).isTrue();
+        if (maxCpus > 1) {
+            assertConfigCompatible(baseline, builder.setNumCpus(2)).isTrue();
+        }
+
+        // Changes that must be incompatible, since they must change the VM identity.
+        assertConfigCompatible(baseline, builder.setDebugLevel(DEBUG_LEVEL_FULL)).isFalse();
+        assertConfigCompatible(baseline, builder.setPayloadBinaryPath("different")).isFalse();
+        int capabilities = getVirtualMachineManager().getCapabilities();
+        if ((capabilities & CAPABILITY_PROTECTED_VM) != 0
+                && (capabilities & CAPABILITY_NON_PROTECTED_VM) != 0) {
+            assertConfigCompatible(baseline, builder.setProtectedVm(!isProtectedVm())).isFalse();
+        }
+
+        // Changes that are currently incompatible for ease of implementation, but this might change
+        // in the future.
+        assertConfigCompatible(baseline, builder.setApkPath("/different")).isFalse();
+        assertConfigCompatible(baseline, builder.setEncryptedStorageKib(100)).isFalse();
+    }
+
+    private BooleanSubject assertConfigCompatible(
+            VirtualMachineConfig baseline, VirtualMachineConfig.Builder builder) {
+        return assertThat(builder.build().isCompatibleWith(baseline));
+    }
+
+    @Test
+    @CddTest(requirements = {"9.17/C-1-1"})
     public void vmUnitTests() throws Exception {
         VirtualMachineConfig.Builder builder =
                 newVmConfigBuilder().setPayloadBinaryPath("binary/path");
diff --git a/virtualizationservice/src/virtmgr.rs b/virtualizationservice/src/virtmgr.rs
index 90b4789..5616097 100644
--- a/virtualizationservice/src/virtmgr.rs
+++ b/virtualizationservice/src/virtmgr.rs
@@ -30,6 +30,7 @@
 use rpcbinder::{FileDescriptorTransportMode, RpcServer};
 use std::os::unix::io::{FromRawFd, OwnedFd, RawFd};
 use clap::Parser;
+use nix::fcntl::{fcntl, F_GETFD, F_SETFD, FdFlag};
 use nix::unistd::{Pid, Uid};
 use std::os::unix::raw::{pid_t, uid_t};
 
@@ -66,8 +67,12 @@
 
 fn take_fd_ownership(raw_fd: RawFd, owned_fds: &mut Vec<RawFd>) -> Result<OwnedFd, anyhow::Error> {
     // Basic check that the integer value does correspond to a file descriptor.
-    nix::fcntl::fcntl(raw_fd, nix::fcntl::F_GETFD)
-        .with_context(|| format!("Invalid file descriptor {raw_fd}"))?;
+    fcntl(raw_fd, F_GETFD).with_context(|| format!("Invalid file descriptor {raw_fd}"))?;
+
+    // The file descriptor had CLOEXEC disabled to be inherited from the parent.
+    // Re-enable it to make sure it is not accidentally inherited further.
+    fcntl(raw_fd, F_SETFD(FdFlag::FD_CLOEXEC))
+        .with_context(|| format!("Could not set CLOEXEC on file descriptor {raw_fd}"))?;
 
     // Creating OwnedFd for stdio FDs is not safe.
     if [libc::STDIN_FILENO, libc::STDOUT_FILENO, libc::STDERR_FILENO].contains(&raw_fd) {
diff --git a/vm/TEST_MAPPING b/vm/TEST_MAPPING
new file mode 100644
index 0000000..a8d1fa6
--- /dev/null
+++ b/vm/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "avf-presubmit" : [
+    {
+      "name" : "vm.test"
+    }
+  ]
+}