Merge changes I2614e5df,I25f8db10,I97947c38,Ic4eebf81,I1404cab0, ...

* changes:
  pvmfw: Add support for appended configuration data
  pvmfw: Add MemoryTracker & MemorySlices
  pvmfw: Support managing page tables dynamically
  libfdt: Rename Fdt::bytes to Fdt::buffer
  libfdt: Map FDT_ERR_NOTFOUND to Option::None
  libfdt: Extend Rust wrapper with write functions
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 8376c1f..dd81738 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -30,6 +30,11 @@
       "name": "AVFHostTestCases"
     }
   ],
+  "postsubmit": [
+    {
+      "name": "CtsMicrodroidDisabledTestCases"
+    }
+  ],
   "imports": [
     {
       "path": "packages/modules/Virtualization/apkdmverity"
diff --git a/compos/compos_key_helper/Android.bp b/compos/compos_key_helper/Android.bp
index cffa1e3..f8dc783 100644
--- a/compos/compos_key_helper/Android.bp
+++ b/compos/compos_key_helper/Android.bp
@@ -29,7 +29,7 @@
         "libcompos_key",
     ],
     shared_libs: [
-        "libvm_payload",
+        "libvm_payload#current",
         "libbinder_ndk",
     ],
 }
diff --git a/compos/src/compsvc_main.rs b/compos/src/compsvc_main.rs
index 206dd4b..77e2daa 100644
--- a/compos/src/compsvc_main.rs
+++ b/compos/src/compsvc_main.rs
@@ -23,11 +23,13 @@
 mod fsverity;
 
 use anyhow::Result;
+use binder::unstable_api::AsNative;
 use compos_common::COMPOS_VSOCK_PORT;
 use log::{debug, error};
-use rpcbinder::RpcServer;
+use std::os::raw::c_void;
 use std::panic;
-use vm_payload_bindgen::AVmPayload_notifyPayloadReady;
+use std::ptr;
+use vm_payload_bindgen::{AIBinder, AVmPayload_notifyPayloadReady, AVmPayload_runVsockRpcServer};
 
 fn main() {
     if let Err(e) = try_main() {
@@ -46,10 +48,20 @@
     }));
 
     debug!("compsvc is starting as a rpc service.");
-    let service = compsvc::new_binder()?.as_binder();
-    let server = RpcServer::new_vsock(service, COMPOS_VSOCK_PORT)?;
-    // SAFETY: Invokes a method from the bindgen library `vm_payload_bindgen`.
-    unsafe { AVmPayload_notifyPayloadReady() };
-    server.join();
+    let param = ptr::null_mut();
+    let mut service = compsvc::new_binder()?.as_binder();
+    unsafe {
+        // SAFETY: We hold a strong pointer, so the raw pointer remains valid. The bindgen AIBinder
+        // is the same type as sys::AIBinder.
+        let service = service.as_native_mut() as *mut AIBinder;
+        // SAFETY: It is safe for on_ready to be invoked at any time, with any parameter.
+        AVmPayload_runVsockRpcServer(service, COMPOS_VSOCK_PORT, Some(on_ready), param);
+    }
     Ok(())
 }
+
+extern "C" fn on_ready(_param: *mut c_void) {
+    // SAFETY: Invokes a method from the bindgen library `vm_payload_bindgen` which is safe to
+    // call at any time.
+    unsafe { AVmPayload_notifyPayloadReady() };
+}
diff --git a/demo/java/com/android/microdroid/demo/MainActivity.java b/demo/java/com/android/microdroid/demo/MainActivity.java
index 8e870ea..77f2ee7 100644
--- a/demo/java/com/android/microdroid/demo/MainActivity.java
+++ b/demo/java/com/android/microdroid/demo/MainActivity.java
@@ -256,7 +256,8 @@
                     builder.setDebugLevel(VirtualMachineConfig.DEBUG_LEVEL_FULL);
                 }
                 VirtualMachineConfig config = builder.build();
-                VirtualMachineManager vmm = VirtualMachineManager.getInstance(getApplication());
+                VirtualMachineManager vmm =
+                        getApplication().getSystemService(VirtualMachineManager.class);
                 mVirtualMachine = vmm.getOrCreate(VM_NAME, config);
                 try {
                     mVirtualMachine.setConfig(config);
diff --git a/javalib/api/module-lib-current.txt b/javalib/api/module-lib-current.txt
index d802177..4d59764 100644
--- a/javalib/api/module-lib-current.txt
+++ b/javalib/api/module-lib-current.txt
@@ -1 +1,9 @@
 // Signature format: 2.0
+package android.system.virtualmachine {
+
+  public class VirtualizationFrameworkInitializer {
+    method public static void registerServiceWrappers();
+  }
+
+}
+
diff --git a/javalib/api/system-current.txt b/javalib/api/system-current.txt
index ea2d23e..16995c5 100644
--- a/javalib/api/system-current.txt
+++ b/javalib/api/system-current.txt
@@ -3,7 +3,7 @@
 
   public class VirtualMachine implements java.lang.AutoCloseable {
     method public void clearCallback();
-    method public void close() throws android.system.virtualmachine.VirtualMachineException;
+    method public void close();
     method @NonNull public android.os.IBinder connectToVsockServer(int) throws android.system.virtualmachine.VirtualMachineException;
     method @NonNull public android.os.ParcelFileDescriptor connectVsock(int) throws android.system.virtualmachine.VirtualMachineException;
     method public int getCid() throws android.system.virtualmachine.VirtualMachineException;
@@ -98,7 +98,6 @@
     method public void delete(@NonNull String) throws android.system.virtualmachine.VirtualMachineException;
     method @Nullable public android.system.virtualmachine.VirtualMachine get(@NonNull String) throws android.system.virtualmachine.VirtualMachineException;
     method public int getCapabilities();
-    method @NonNull public static android.system.virtualmachine.VirtualMachineManager getInstance(@NonNull android.content.Context);
     method @NonNull public android.system.virtualmachine.VirtualMachine getOrCreate(@NonNull String, @NonNull android.system.virtualmachine.VirtualMachineConfig) throws android.system.virtualmachine.VirtualMachineException;
     method @NonNull public android.system.virtualmachine.VirtualMachine importFromDescriptor(@NonNull String, @NonNull android.system.virtualmachine.VirtualMachineDescriptor) throws android.system.virtualmachine.VirtualMachineException;
     field public static final int CAPABILITY_NON_PROTECTED_VM = 2; // 0x2
diff --git a/javalib/jarjar-rules.txt b/javalib/jarjar-rules.txt
index dd8ad2d..726f9aa 100644
--- a/javalib/jarjar-rules.txt
+++ b/javalib/jarjar-rules.txt
@@ -1,9 +1,10 @@
 # Rules for the android.system.virtualmachine java_sdk_library.
 
-# This is the root of the API, everything we care about should be
-# reachable from here.
-# (This gets rid of all the android.sysprop classes we don't use.)
+# Keep the API surface, most of it is accessible from VirtualMachineManager
 keep android.system.virtualmachine.VirtualMachineManager
+# VirtualizationModuleFrameworkInitializer is not accessible from
+# VirtualMachineManager, we need to explicitly keep it.
+keep android.system.virtualmachine.VirtualizationFrameworkInitializer
 
 # We statically link PlatformProperties, rename to avoid clashes.
 rule android.sysprop.** com.android.system.virtualmachine.sysprop.@1
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index 193d213..dec873f 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -65,6 +65,7 @@
 import android.system.virtualizationservice.VirtualMachineAppConfig;
 import android.system.virtualizationservice.VirtualMachineState;
 import android.util.JsonReader;
+import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
 
@@ -105,6 +106,8 @@
  */
 @SystemApi
 public class VirtualMachine implements AutoCloseable {
+    private static final String TAG = "VirtualMachine";
+
     /** Name of the directory under the files directory where all VMs created for the app exist. */
     private static final String VM_DIR = "vm";
 
@@ -754,7 +757,8 @@
      * 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()}.
      *
-     * @throws VirtualMachineException if the virtual machine could not be stopped.
+     * @throws VirtualMachineException if the virtual machine is not running or could not be
+     *     stopped.
      * @hide
      */
     @SystemApi
@@ -775,15 +779,31 @@
     }
 
     /**
-     * Stops this virtual machine. See {@link #stop()}.
+     * Stops this virtual machine, if it is running.
      *
-     * @throws VirtualMachineException if the virtual machine could not be stopped.
+     * @see #stop()
      * @hide
      */
     @SystemApi
     @Override
-    public void close() throws VirtualMachineException {
-        stop();
+    public void close() {
+        synchronized (mLock) {
+            if (mVirtualMachine == null) {
+                return;
+            }
+            try {
+                if (stateToStatus(mVirtualMachine.getState()) == STATUS_RUNNING) {
+                    mVirtualMachine.stop();
+                    mVirtualMachine = null;
+                }
+            } catch (RemoteException e) {
+                throw e.rethrowAsRuntimeException();
+            } catch (ServiceSpecificException e) {
+                // Deliberately ignored; this almost certainly means the VM exited just as
+                // we tried to stop it.
+                Log.i(TAG, "Ignoring error on close()", e);
+            }
+        }
     }
 
     private static void deleteRecursively(File dir) throws IOException {
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
index a520ab4..c179498 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
@@ -21,10 +21,11 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresFeature;
 import android.annotation.RequiresPermission;
-import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.sysprop.HypervisorProperties;
 import android.util.ArrayMap;
 
@@ -34,7 +35,6 @@
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
 import java.util.Map;
-import java.util.WeakHashMap;
 
 /**
  * Manages {@link VirtualMachine virtual machine} instances created by an app. Each instance is
@@ -46,9 +46,13 @@
  *
  * <p>The app can then start, stop and otherwise interact with the VM.
  *
+ * <p>An instance of {@link VirtualMachineManager} can be obtained by calling {@link
+ * Context#getSystemService(Class)}.
+ *
  * @hide
  */
 @SystemApi
+@RequiresFeature(PackageManager.FEATURE_VIRTUALIZATION_FRAMEWORK)
 public class VirtualMachineManager {
     /**
      * A lock used to synchronize the creation of virtual machines. It protects {@link #mVmsByName},
@@ -59,14 +63,11 @@
 
     @NonNull private final Context mContext;
 
-    private VirtualMachineManager(@NonNull Context context) {
-        mContext = context;
+    /** @hide */
+    public VirtualMachineManager(@NonNull Context context) {
+        mContext = requireNonNull(context);
     }
 
-    @GuardedBy("sInstances")
-    private static final Map<Context, WeakReference<VirtualMachineManager>> sInstances =
-            new WeakHashMap<>();
-
     @GuardedBy("sCreateLock")
     private final Map<String, WeakReference<VirtualMachine>> mVmsByName = new ArrayMap<>();
 
@@ -93,27 +94,6 @@
     public static final int CAPABILITY_NON_PROTECTED_VM = 2;
 
     /**
-     * Returns the per-context instance.
-     *
-     * @hide
-     */
-    @SystemApi
-    @NonNull
-    @SuppressLint("ManagerLookup") // TODO(b/249093790): remove
-    public static VirtualMachineManager getInstance(@NonNull Context context) {
-        requireNonNull(context, "context must not be null");
-        synchronized (sInstances) {
-            VirtualMachineManager vmm =
-                    sInstances.containsKey(context) ? sInstances.get(context).get() : null;
-            if (vmm == null) {
-                vmm = new VirtualMachineManager(context);
-                sInstances.put(context, new WeakReference<>(vmm));
-            }
-            return vmm;
-        }
-    }
-
-    /**
      * Returns a set of flags indicating what this implementation of virtualization is capable of.
      *
      * @see #CAPABILITY_PROTECTED_VM
diff --git a/javalib/src/android/system/virtualmachine/VirtualizationFrameworkInitializer.java b/javalib/src/android/system/virtualmachine/VirtualizationFrameworkInitializer.java
new file mode 100644
index 0000000..30ac425
--- /dev/null
+++ b/javalib/src/android/system/virtualmachine/VirtualizationFrameworkInitializer.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.system.virtualmachine;
+
+import static android.content.pm.PackageManager.FEATURE_VIRTUALIZATION_FRAMEWORK;
+
+import android.annotation.SystemApi;
+import android.app.SystemServiceRegistry;
+import android.content.Context;
+
+/**
+ * Holds initialization code for virtualization module
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public class VirtualizationFrameworkInitializer {
+
+    private VirtualizationFrameworkInitializer() {}
+
+    /**
+     * Called by the static initializer in the {@link SystemServiceRegistry}, and registers {@link
+     * VirtualMachineManager} to the {@link Context}. so that it's accessible from {@link
+     * Context#getSystemService(String)}.
+     */
+    public static void registerServiceWrappers() {
+        // Note: it's important that the getPackageManager().hasSystemFeature() check is executed
+        // in the lambda, and not directly in the registerServiceWrappers method. The
+        // registerServiceWrappers is called during Zygote static initialization, and at that
+        // point the PackageManager is not available yet.
+        //
+        // On the other hand, the lambda is executed after the app calls Context.getSystemService
+        // (VirtualMachineManager.class), at which point the PackageManager is available. The
+        // result of the lambda is cached on per-context basis.
+        SystemServiceRegistry.registerContextAwareService(
+                Context.VIRTUALIZATION_SERVICE,
+                VirtualMachineManager.class,
+                ctx ->
+                        ctx.getPackageManager().hasSystemFeature(FEATURE_VIRTUALIZATION_FRAMEWORK)
+                                ? new VirtualMachineManager(ctx)
+                                : null);
+    }
+}
diff --git a/microdroid/init.rc b/microdroid/init.rc
index 94ef940..7d04557 100644
--- a/microdroid/init.rc
+++ b/microdroid/init.rc
@@ -74,6 +74,12 @@
     # some services can be started.
     trigger late-fs
 
+    # Wait for microdroid_manager to finish setting up sysprops from the payload config.
+    # Some further actions in the boot sequence might depend on the sysprops from the payloag,
+    # e.g. microdroid.config.enable_authfs configures whether to run authfs_service after
+    # /data is mounted.
+    wait_for_prop microdroid_manager.config_done 1
+
     trigger post-fs-data
 
     # Load persist properties and override properties (if enabled) from /data.
@@ -132,6 +138,13 @@
     mkdir /data/local 0751 root root
     mkdir /data/local/tmp 0771 shell shell
 
+on post-fs-data && property:microdroid_manager.authfs.enabled=1
+    start authfs_service
+
+on boot
+    # Mark boot completed. This will notify microdroid_manager to run payload.
+    setprop dev.bootcomplete 1
+
 service tombstone_transmit /system/bin/tombstone_transmit.microdroid -cid 2 -port 2000 -remove_tombstones_after_transmitting
     user system
     group system
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index a706dbe..17532d7 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -417,9 +417,10 @@
     mount_extra_apks(&config)?;
 
     // Wait until apex config is done. (e.g. linker configuration for apexes)
-    // TODO(jooyung): wait until sys.boot_completed?
     wait_for_apex_config_done()?;
 
+    setup_config_sysprops(&config)?;
+
     // Start tombstone_transmit if enabled
     if config.export_tombstones {
         control_service("start", "tombstone_transmit")?;
@@ -427,11 +428,6 @@
         control_service("stop", "tombstoned")?;
     }
 
-    // Start authfs if enabled
-    if config.enable_authfs {
-        control_service("start", "authfs_service")?;
-    }
-
     // Wait until zipfuse has mounted the APK so we can access the payload
     wait_for_property_true(APK_MOUNT_DONE_PROP).context("Failed waiting for APK mount done")?;
 
@@ -442,7 +438,8 @@
         ensure!(exitcode.success(), "Unable to prepare encrypted storage. Exitcode={}", exitcode);
     }
 
-    system_properties::write("dev.bootcomplete", "1").context("set dev.bootcomplete")?;
+    wait_for_property_true("dev.bootcomplete").context("failed waiting for dev.bootcomplete")?;
+    info!("boot completed, time to run payload");
     exec_task(task, service).context("Failed to run payload")
 }
 
@@ -682,6 +679,16 @@
     Ok(())
 }
 
+fn setup_config_sysprops(config: &VmPayloadConfig) -> Result<()> {
+    if config.enable_authfs {
+        system_properties::write("microdroid_manager.authfs.enabled", "1")
+            .context("failed to write microdroid_manager.authfs.enabled")?;
+    }
+    system_properties::write("microdroid_manager.config_done", "1")
+        .context("failed to write microdroid_manager.config_done")?;
+    Ok(())
+}
+
 // Waits until linker config is generated
 fn wait_for_apex_config_done() -> Result<()> {
     wait_for_property_true(APEX_CONFIG_DONE_PROP).context("Failed waiting for apex config done")
diff --git a/tests/benchmark/Android.bp b/tests/benchmark/Android.bp
index 10cdac5..9d2b6c7 100644
--- a/tests/benchmark/Android.bp
+++ b/tests/benchmark/Android.bp
@@ -38,6 +38,6 @@
         "libbase",
         "libbinder_ndk",
         "liblog",
-        "libvm_payload",
+        "libvm_payload#current",
     ],
 }
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 28852e8..14a0e39 100644
--- a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
+++ b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
@@ -88,16 +88,17 @@
 
     private boolean canBootMicrodroidWithMemory(int mem)
             throws VirtualMachineException, InterruptedException, IOException {
-        VirtualMachineConfig normalConfig = mInner.newVmConfigBuilder()
-                .setPayloadBinaryPath("MicrodroidIdleNativeLib.so")
-                .setDebugLevel(DEBUG_LEVEL_NONE)
-                .setMemoryMib(mem)
-                .build();
+        VirtualMachineConfig normalConfig =
+                newVmConfigBuilder()
+                        .setPayloadBinaryPath("MicrodroidIdleNativeLib.so")
+                        .setDebugLevel(DEBUG_LEVEL_NONE)
+                        .setMemoryMib(mem)
+                        .build();
 
         // returns true if succeeded at least once.
         final int trialCount = 5;
         for (int i = 0; i < trialCount; i++) {
-            mInner.forceCreateNewVirtualMachine("test_vm_minimum_memory", normalConfig);
+            forceCreateNewVirtualMachine("test_vm_minimum_memory", normalConfig);
 
             if (tryBootVm(TAG, "test_vm_minimum_memory").payloadStarted) return true;
         }
@@ -147,12 +148,13 @@
         for (int i = 0; i < trialCount; i++) {
 
             // To grab boot events from log, set debug mode to FULL
-            VirtualMachineConfig normalConfig = mInner.newVmConfigBuilder()
-                    .setPayloadBinaryPath("MicrodroidIdleNativeLib.so")
-                    .setDebugLevel(DEBUG_LEVEL_FULL)
-                    .setMemoryMib(256)
-                    .build();
-            mInner.forceCreateNewVirtualMachine("test_vm_boot_time", normalConfig);
+            VirtualMachineConfig normalConfig =
+                    newVmConfigBuilder()
+                            .setPayloadBinaryPath("MicrodroidIdleNativeLib.so")
+                            .setDebugLevel(DEBUG_LEVEL_FULL)
+                            .setMemoryMib(256)
+                            .build();
+            forceCreateNewVirtualMachine("test_vm_boot_time", normalConfig);
 
             BootResult result = tryBootVm(TAG, "test_vm_boot_time");
             assertThat(result.payloadStarted).isTrue();
@@ -195,17 +197,17 @@
 
     @Test
     public void testVsockTransferFromHostToVM() throws Exception {
-        VirtualMachineConfig config = mInner.newVmConfigBuilder()
-                .setPayloadConfigPath("assets/vm_config_io.json")
-                .setDebugLevel(DEBUG_LEVEL_FULL)
-                .build();
+        VirtualMachineConfig config =
+                newVmConfigBuilder()
+                        .setPayloadConfigPath("assets/vm_config_io.json")
+                        .setDebugLevel(DEBUG_LEVEL_FULL)
+                        .build();
         List<Double> transferRates = new ArrayList<>(IO_TEST_TRIAL_COUNT);
 
         for (int i = 0; i < IO_TEST_TRIAL_COUNT; ++i) {
             int port = (mProtectedVm ? 5666 : 6666) + i;
             String vmName = "test_vm_io_" + i;
-            mInner.forceCreateNewVirtualMachine(vmName, config);
-            VirtualMachine vm = mInner.getVirtualMachineManager().get(vmName);
+            VirtualMachine vm = forceCreateNewVirtualMachine(vmName, config);
             BenchmarkVmListener.create(new VsockListener(transferRates, port)).runToFinish(TAG, vm);
         }
         reportMetrics(transferRates, "vsock/transfer_host_to_vm", "mb_per_sec");
@@ -222,10 +224,11 @@
     }
 
     private void testVirtioBlkReadRate(boolean isRand) throws Exception {
-        VirtualMachineConfig config = mInner.newVmConfigBuilder()
-                .setPayloadConfigPath("assets/vm_config_io.json")
-                .setDebugLevel(DEBUG_LEVEL_FULL)
-                .build();
+        VirtualMachineConfig config =
+                newVmConfigBuilder()
+                        .setPayloadConfigPath("assets/vm_config_io.json")
+                        .setDebugLevel(DEBUG_LEVEL_FULL)
+                        .build();
         List<Double> readRates = new ArrayList<>(IO_TEST_TRIAL_COUNT);
 
         for (int i = 0; i < IO_TEST_TRIAL_COUNT + 1; ++i) {
@@ -236,8 +239,7 @@
                 readRates.clear();
             }
             String vmName = "test_vm_io_" + i;
-            mInner.forceCreateNewVirtualMachine(vmName, config);
-            VirtualMachine vm = mInner.getVirtualMachineManager().get(vmName);
+            VirtualMachine vm = forceCreateNewVirtualMachine(vmName, config);
             BenchmarkVmListener.create(new VirtioBlkListener(readRates, isRand))
                     .runToFinish(TAG, vm);
         }
@@ -280,13 +282,13 @@
     @Test
     public void testMemoryUsage() throws Exception {
         final String vmName = "test_vm_mem_usage";
-        VirtualMachineConfig config = mInner.newVmConfigBuilder()
-                .setPayloadConfigPath("assets/vm_config_io.json")
-                .setDebugLevel(DEBUG_LEVEL_NONE)
-                .setMemoryMib(256)
-                .build();
-        mInner.forceCreateNewVirtualMachine(vmName, config);
-        VirtualMachine vm = mInner.getVirtualMachineManager().get(vmName);
+        VirtualMachineConfig config =
+                newVmConfigBuilder()
+                        .setPayloadConfigPath("assets/vm_config_io.json")
+                        .setDebugLevel(DEBUG_LEVEL_NONE)
+                        .setMemoryMib(256)
+                        .build();
+        VirtualMachine vm = forceCreateNewVirtualMachine(vmName, config);
         MemoryUsageListener listener = new MemoryUsageListener(this::executeCommand);
         BenchmarkVmListener.create(listener).runToFinish(TAG, vm);
 
diff --git a/tests/benchmark/src/native/benchmarkbinary.cpp b/tests/benchmark/src/native/benchmarkbinary.cpp
index 66b41a1..70ec7db 100644
--- a/tests/benchmark/src/native/benchmarkbinary.cpp
+++ b/tests/benchmark/src/native/benchmarkbinary.cpp
@@ -161,10 +161,8 @@
 Result<void> run_io_benchmark_tests() {
     auto test_service = ndk::SharedRefBase::make<IOBenchmarkService>();
     auto callback = []([[maybe_unused]] void* param) { AVmPayload_notifyPayloadReady(); };
-    if (!AVmPayload_runVsockRpcServer(test_service->asBinder().get(), test_service->SERVICE_PORT,
-                                      callback, nullptr)) {
-        return Error() << "RPC Server failed to run";
-    }
+    AVmPayload_runVsockRpcServer(test_service->asBinder().get(), test_service->SERVICE_PORT,
+                                 callback, nullptr);
     return {};
 }
 } // Anonymous namespace
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 24e2049..c266b96 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
@@ -73,59 +73,44 @@
                 permission);
     }
 
-    // TODO(b/220920264): remove Inner class; this is a hack to hide virt APEX types
-    protected static class Inner {
-        private final boolean mProtectedVm;
-        private final Context mContext;
-        private final VirtualMachineManager mVmm;
-
-        public Inner(Context context, boolean protectedVm, VirtualMachineManager vmm) {
-            mProtectedVm = protectedVm;
-            mVmm = vmm;
-            mContext = context;
-        }
-
-        public VirtualMachineManager getVirtualMachineManager() {
-            return mVmm;
-        }
-
-        public Context getContext() {
-            return mContext;
-        }
-
-        public VirtualMachineConfig.Builder newVmConfigBuilder() {
-            return new VirtualMachineConfig.Builder(mContext).setProtectedVm(mProtectedVm);
-        }
-
-        /**
-         * Creates a new virtual machine, potentially removing an existing virtual machine with
-         * given name.
-         */
-        public VirtualMachine forceCreateNewVirtualMachine(String name, VirtualMachineConfig config)
-                throws VirtualMachineException {
-            VirtualMachine existingVm = mVmm.get(name);
-            if (existingVm != null) {
-                mVmm.delete(name);
-            }
-            return mVmm.create(name, config);
-        }
-    }
-
-    protected Inner mInner;
+    private Context mCtx;
+    private boolean mProtectedVm;
 
     protected Context getContext() {
-        return mInner.getContext();
+        return mCtx;
+    }
+
+    public VirtualMachineManager getVirtualMachineManager() {
+        return mCtx.getSystemService(VirtualMachineManager.class);
+    }
+
+    public VirtualMachineConfig.Builder newVmConfigBuilder() {
+        return new VirtualMachineConfig.Builder(mCtx).setProtectedVm(mProtectedVm);
+    }
+
+    /**
+     * Creates a new virtual machine, potentially removing an existing virtual machine with given
+     * name.
+     */
+    public VirtualMachine forceCreateNewVirtualMachine(String name, VirtualMachineConfig config)
+            throws VirtualMachineException {
+        final VirtualMachineManager vmm = getVirtualMachineManager();
+        VirtualMachine existingVm = vmm.get(name);
+        if (existingVm != null) {
+            vmm.delete(name);
+        }
+        return vmm.create(name, config);
     }
 
     public void prepareTestSetup(boolean protectedVm) {
-        Context ctx = ApplicationProvider.getApplicationContext();
+        mCtx = ApplicationProvider.getApplicationContext();
         assume().withMessage("Device doesn't support AVF")
-                .that(ctx.getPackageManager().hasSystemFeature(FEATURE_VIRTUALIZATION_FRAMEWORK))
+                .that(mCtx.getPackageManager().hasSystemFeature(FEATURE_VIRTUALIZATION_FRAMEWORK))
                 .isTrue();
 
-        mInner = new Inner(ctx, protectedVm, VirtualMachineManager.getInstance(ctx));
+        mProtectedVm = protectedVm;
 
-        int capabilities = mInner.getVirtualMachineManager().getCapabilities();
+        int capabilities = getVirtualMachineManager().getCapabilities();
         if (protectedVm) {
             assume().withMessage("Skip where protected VMs aren't supported")
                     .that(capabilities & VirtualMachineManager.CAPABILITY_PROTECTED_VM)
@@ -314,7 +299,7 @@
 
     public BootResult tryBootVm(String logTag, String vmName)
             throws VirtualMachineException, InterruptedException {
-        VirtualMachine vm = mInner.getVirtualMachineManager().get(vmName);
+        VirtualMachine vm = getVirtualMachineManager().get(vmName);
         final CompletableFuture<Boolean> payloadStarted = new CompletableFuture<>();
         final CompletableFuture<Integer> deathReason = new CompletableFuture<>();
         final CompletableFuture<Long> endTime = new CompletableFuture<>();
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index 795d7b3..11b3e84 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -146,23 +146,16 @@
                                 .collect(toList())));
         FileUtil.writeToFile(config.toString(), configFile);
 
-        File mkPayload = findTestFile("mk_payload");
         RunUtil runUtil = new RunUtil();
-        // Set the parent dir on the PATH (e.g. <workdir>/bin)
-        String separator = System.getProperty("path.separator");
-        String path = mkPayload.getParentFile().getPath() + separator + System.getenv("PATH");
-        runUtil.setEnvVariable("PATH", path);
-
-        List<String> command = new ArrayList<>();
-        command.add("mk_payload");
-        command.add("--metadata-only");
-        command.add(configFile.toString());
-        command.add(payloadMetadata.toString());
-
-        CommandResult result =
-                runUtil.runTimedCmd(
-                        // mk_payload should run fast enough
-                        5 * 1000, "/bin/bash", "-c", String.join(" ", command));
+        String command =
+                String.join(
+                        " ",
+                        findTestFile("mk_payload").getAbsolutePath(),
+                        "--metadata-only",
+                        configFile.getAbsolutePath(),
+                        payloadMetadata.getAbsolutePath());
+        // mk_payload should run fast enough
+        CommandResult result = runUtil.runTimedCmd(5000, "/bin/bash", "-c", command);
         String out = result.getStdout();
         String err = result.getStderr();
         assertWithMessage(
@@ -186,7 +179,7 @@
         runUtil.setEnvVariable("PATH", path);
 
         List<String> command = new ArrayList<>();
-        command.add("sign_virt_apex");
+        command.add(signVirtApex.getAbsolutePath());
         keyOverrides.forEach(
                 (filename, keyFile) ->
                         command.add("--key_override " + filename + "=" + keyFile.getPath()));
@@ -211,18 +204,11 @@
             long timeoutMillis, Callable<T> callable, org.hamcrest.Matcher<T> matcher)
             throws Exception {
         long start = System.currentTimeMillis();
-        while (true) {
-            try {
-                assertThat(callable.call(), matcher);
-                return;
-            } catch (Throwable e) {
-                if (System.currentTimeMillis() - start < timeoutMillis) {
-                    Thread.sleep(500);
-                } else {
-                    throw e;
-                }
-            }
+        while ((System.currentTimeMillis() - start < timeoutMillis)
+                && !matcher.matches(callable.call())) {
+            Thread.sleep(500);
         }
+        assertThat(callable.call(), matcher);
     }
 
     static class ActiveApexInfo {
diff --git a/tests/no_avf/Android.bp b/tests/no_avf/Android.bp
new file mode 100644
index 0000000..fd0d5e2
--- /dev/null
+++ b/tests/no_avf/Android.bp
@@ -0,0 +1,21 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "CtsMicrodroidDisabledTestCases",
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    srcs: ["src/**/*.java"],
+    static_libs: [
+        "androidx.test.runner",
+        "androidx.test.ext.junit",
+        "compatibility-common-util-devicesidelib",
+        "truth-prebuilt",
+    ],
+    sdk_version: "test_current",
+    compile_multilib: "both",
+    min_sdk_version: "UpsideDownCake",
+}
diff --git a/tests/no_avf/AndroidManifest.xml b/tests/no_avf/AndroidManifest.xml
new file mode 100644
index 0000000..4a1304e
--- /dev/null
+++ b/tests/no_avf/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+      package="com.android.nomicrodroid.test">
+    <application />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.nomicrodroid.test"
+        android:label="CTS test for devices without Microdroid support" />
+</manifest>
diff --git a/tests/no_avf/AndroidTest.xml b/tests/no_avf/AndroidTest.xml
new file mode 100644
index 0000000..1e93887
--- /dev/null
+++ b/tests/no_avf/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Runs Microdroid device-side 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" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="test-file-name" value="CtsMicrodroidDisabledTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.nomicrodroid.test" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+    </test>
+</configuration>
diff --git a/tests/no_avf/README.md b/tests/no_avf/README.md
new file mode 100644
index 0000000..b96dc97
--- /dev/null
+++ b/tests/no_avf/README.md
@@ -0,0 +1 @@
+CTS tests for devices that don't support AVF.
\ No newline at end of file
diff --git a/tests/no_avf/src/com/android/nomicrodroid/test/NoMicrodroidTest.java b/tests/no_avf/src/com/android/nomicrodroid/test/NoMicrodroidTest.java
new file mode 100644
index 0000000..0982e35
--- /dev/null
+++ b/tests/no_avf/src/com/android/nomicrodroid/test/NoMicrodroidTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.nomicrodroid.test;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.TruthJUnit.assume;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.system.virtualmachine.VirtualMachineManager;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.CddTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests to validate that devices without support for AVF (Android Virtualization Framework) are set
+ * up correctly.
+ */
+@RunWith(JUnit4.class)
+public class NoMicrodroidTest {
+
+    @Before
+    public void setUp() {
+        final PackageManager pm =
+                InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
+        assume().withMessage("Device supports AVF")
+                .that(pm.hasSystemFeature(PackageManager.FEATURE_VIRTUALIZATION_FRAMEWORK))
+                .isFalse();
+    }
+
+    @CddTest(requirements = {"9.17/C-1-1"})
+    @Test
+    public void testVirtualMachineManagerLookupReturnsNull() {
+        final Context ctx = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        assertThat(ctx.getSystemService(VirtualMachineManager.class)).isNull();
+    }
+}
diff --git a/tests/testapk/Android.bp b/tests/testapk/Android.bp
index df7c6c0..4dc9489 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -39,7 +39,7 @@
     shared_libs: [
         "libbinder_ndk",
         "MicrodroidTestNativeLibSub",
-        "libvm_payload",
+        "libvm_payload#current",
     ],
     static_libs: [
         "com.android.microdroid.testservice-ndk",
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 71a9e3b..1f5bae9 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -15,6 +15,9 @@
  */
 package com.android.microdroid.test;
 
+import static android.system.virtualmachine.VirtualMachine.STATUS_DELETED;
+import static android.system.virtualmachine.VirtualMachine.STATUS_RUNNING;
+import static android.system.virtualmachine.VirtualMachine.STATUS_STOPPED;
 import static android.system.virtualmachine.VirtualMachineConfig.DEBUG_LEVEL_APP_ONLY;
 import static android.system.virtualmachine.VirtualMachineConfig.DEBUG_LEVEL_FULL;
 import static android.system.virtualmachine.VirtualMachineConfig.DEBUG_LEVEL_NONE;
@@ -102,18 +105,16 @@
     private static final int MIN_MEM_X86_64 = 196;
 
     @Test
-    @CddTest(requirements = {
-            "9.17/C-1-1",
-            "9.17/C-2-1"
-    })
-    public void connectToVmService() throws Exception {
+    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+    public void createAndConnectToVm() throws Exception {
         assumeSupportedKernel();
 
-        VirtualMachineConfig config = mInner.newVmConfigBuilder()
-                .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
-                .setMemoryMib(minMemoryRequired())
-                .build();
-        VirtualMachine vm = mInner.forceCreateNewVirtualMachine("test_vm", config);
+        VirtualMachineConfig config =
+                newVmConfigBuilder()
+                        .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+                        .setMemoryMib(minMemoryRequired())
+                        .build();
+        VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
 
         TestResults testResults = runVmTestService(vm);
         assertThat(testResults.mException).isNull();
@@ -135,18 +136,51 @@
 
         revokePermission(VirtualMachine.MANAGE_VIRTUAL_MACHINE_PERMISSION);
 
-        VirtualMachineConfig config = mInner.newVmConfigBuilder()
-                .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
-                .setMemoryMib(minMemoryRequired())
-                .build();
+        VirtualMachineConfig config =
+                newVmConfigBuilder()
+                        .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+                        .setMemoryMib(minMemoryRequired())
+                        .build();
 
-        SecurityException e = assertThrows(SecurityException.class,
-                () -> mInner.forceCreateNewVirtualMachine("test_vm_requires_permission", config));
+        SecurityException e =
+                assertThrows(
+                        SecurityException.class,
+                        () -> forceCreateNewVirtualMachine("test_vm_requires_permission", config));
         assertThat(e).hasMessageThat()
                 .contains("android.permission.MANAGE_VIRTUAL_MACHINE permission");
     }
 
     @Test
+    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+    public void autoCloseVm() throws Exception {
+        assumeSupportedKernel();
+
+        VirtualMachineConfig config =
+                newVmConfigBuilder()
+                        .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+                        .setMemoryMib(minMemoryRequired())
+                        .build();
+
+        try (VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config)) {
+            assertThat(vm.getStatus()).isEqualTo(STATUS_STOPPED);
+            // close() implicitly called on stopped VM.
+        }
+
+        try (VirtualMachine vm = getVirtualMachineManager().get("test_vm")) {
+            vm.run();
+            assertThat(vm.getStatus()).isEqualTo(STATUS_RUNNING);
+            // close() implicitly called on running VM.
+        }
+
+        try (VirtualMachine vm = getVirtualMachineManager().get("test_vm")) {
+            assertThat(vm.getStatus()).isEqualTo(STATUS_STOPPED);
+            getVirtualMachineManager().delete("test_vm");
+            assertThat(vm.getStatus()).isEqualTo(STATUS_DELETED);
+            // close() implicitly called on deleted VM.
+        }
+    }
+
+    @Test
     @CddTest(requirements = {
             "9.17/C-1-1",
             "9.17/C-1-2",
@@ -155,13 +189,14 @@
     public void createVmWithConfigRequiresPermission() throws Exception {
         assumeSupportedKernel();
 
-        VirtualMachineConfig config = mInner.newVmConfigBuilder()
-                .setPayloadConfigPath("assets/vm_config.json")
-                .setMemoryMib(minMemoryRequired())
-                .build();
+        VirtualMachineConfig config =
+                newVmConfigBuilder()
+                        .setPayloadConfigPath("assets/vm_config.json")
+                        .setMemoryMib(minMemoryRequired())
+                        .build();
 
-        VirtualMachine vm = mInner.forceCreateNewVirtualMachine(
-                "test_vm_config_requires_permission", config);
+        VirtualMachine vm =
+                forceCreateNewVirtualMachine("test_vm_config_requires_permission", config);
 
         SecurityException e = assertThrows(SecurityException.class, () -> runVmTestService(vm));
         assertThat(e).hasMessageThat()
@@ -175,14 +210,14 @@
     public void deleteVm() throws Exception {
         assumeSupportedKernel();
 
-        VirtualMachineConfig config = mInner.newVmConfigBuilder()
-                .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
-                .setMemoryMib(minMemoryRequired())
-                .build();
+        VirtualMachineConfig config =
+                newVmConfigBuilder()
+                        .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+                        .setMemoryMib(minMemoryRequired())
+                        .build();
 
-        VirtualMachine vm = mInner.forceCreateNewVirtualMachine("test_vm_delete",
-                config);
-        VirtualMachineManager vmm = mInner.getVirtualMachineManager();
+        VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_delete", config);
+        VirtualMachineManager vmm = getVirtualMachineManager();
         vmm.delete("test_vm_delete");
 
         // VM should no longer exist
@@ -202,14 +237,14 @@
     public void validApkPathIsAccepted() throws Exception {
         assumeSupportedKernel();
 
-        VirtualMachineConfig config = mInner.newVmConfigBuilder()
-                .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
-                .setApkPath(getContext().getPackageCodePath())
-                .setMemoryMib(minMemoryRequired())
-                .build();
+        VirtualMachineConfig config =
+                newVmConfigBuilder()
+                        .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+                        .setApkPath(getContext().getPackageCodePath())
+                        .setMemoryMib(minMemoryRequired())
+                        .build();
 
-        VirtualMachine vm = mInner.forceCreateNewVirtualMachine(
-                "test_vm_explicit_apk_path", config);
+        VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_explicit_apk_path", config);
 
         TestResults testResults = runVmTestService(vm);
         assertThat(testResults.mException).isNull();
@@ -222,10 +257,11 @@
     public void invalidApkPathIsRejected() {
         assumeSupportedKernel();
 
-        VirtualMachineConfig.Builder builder = mInner.newVmConfigBuilder()
-                .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
-                .setApkPath("relative/path/to.apk")
-                .setMemoryMib(minMemoryRequired());
+        VirtualMachineConfig.Builder builder =
+                newVmConfigBuilder()
+                        .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+                        .setApkPath("relative/path/to.apk")
+                        .setMemoryMib(minMemoryRequired());
         assertThrows(IllegalArgumentException.class, () -> builder.build());
     }
 
@@ -238,11 +274,12 @@
         assumeSupportedKernel();
 
         grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
-        VirtualMachineConfig config = mInner.newVmConfigBuilder()
-                .setPayloadConfigPath("assets/vm_config_extra_apk.json")
-                .setMemoryMib(minMemoryRequired())
-                .build();
-        VirtualMachine vm = mInner.forceCreateNewVirtualMachine("test_vm_extra_apk", config);
+        VirtualMachineConfig config =
+                newVmConfigBuilder()
+                        .setPayloadConfigPath("assets/vm_config_extra_apk.json")
+                        .setMemoryMib(minMemoryRequired())
+                        .build();
+        VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_extra_apk", config);
 
         TestResults testResults = runVmTestService(vm);
         assertThat(testResults.mExtraApkTestProp).isEqualTo("PASS");
@@ -251,12 +288,13 @@
     @Test
     public void bootFailsWhenLowMem() throws Exception {
         for (int memMib : new int[]{ 10, 20, 40 }) {
-            VirtualMachineConfig lowMemConfig = mInner.newVmConfigBuilder()
-                    .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
-                    .setMemoryMib(memMib)
-                    .setDebugLevel(DEBUG_LEVEL_NONE)
-                    .build();
-            VirtualMachine vm = mInner.forceCreateNewVirtualMachine("low_mem", lowMemConfig);
+            VirtualMachineConfig lowMemConfig =
+                    newVmConfigBuilder()
+                            .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+                            .setMemoryMib(memMib)
+                            .setDebugLevel(DEBUG_LEVEL_NONE)
+                            .build();
+            VirtualMachine vm = forceCreateNewVirtualMachine("low_mem", lowMemConfig);
             final CompletableFuture<Boolean> onPayloadReadyExecuted = new CompletableFuture<>();
             final CompletableFuture<Boolean> onStoppedExecuted = new CompletableFuture<>();
             VmEventListener listener =
@@ -298,11 +336,11 @@
         assumeSupportedKernel();
 
         VirtualMachineConfig.Builder builder =
-                mInner.newVmConfigBuilder()
+                newVmConfigBuilder()
                         .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
                         .setDebugLevel(fromLevel);
         VirtualMachineConfig normalConfig = builder.build();
-        mInner.forceCreateNewVirtualMachine("test_vm", normalConfig);
+        forceCreateNewVirtualMachine("test_vm", normalConfig);
         assertThat(tryBootVm(TAG, "test_vm").payloadStarted).isTrue();
 
         // Try to run the VM again with the previous instance.img
@@ -311,7 +349,7 @@
         File vmInstance = getVmFile("test_vm", "instance.img");
         File vmInstanceBackup = File.createTempFile("instance", ".img");
         Files.copy(vmInstance.toPath(), vmInstanceBackup.toPath(), REPLACE_EXISTING);
-        mInner.forceCreateNewVirtualMachine("test_vm", normalConfig);
+        forceCreateNewVirtualMachine("test_vm", normalConfig);
         Files.copy(vmInstanceBackup.toPath(), vmInstance.toPath(), REPLACE_EXISTING);
         assertThat(tryBootVm(TAG, "test_vm").payloadStarted).isTrue();
 
@@ -320,7 +358,7 @@
         // For testing, we do that by creating a new VM with debug level, and copy the old instance
         // image to the new VM instance image.
         VirtualMachineConfig debugConfig = builder.setDebugLevel(toLevel).build();
-        mInner.forceCreateNewVirtualMachine("test_vm", debugConfig);
+        forceCreateNewVirtualMachine("test_vm", debugConfig);
         Files.copy(vmInstanceBackup.toPath(), vmInstance.toPath(), REPLACE_EXISTING);
         assertThat(tryBootVm(TAG, "test_vm").payloadStarted).isFalse();
     }
@@ -331,7 +369,7 @@
     }
 
     private VmCdis launchVmAndGetCdis(String instanceName) throws Exception {
-        VirtualMachine vm = mInner.getVirtualMachineManager().get(instanceName);
+        VirtualMachine vm = getVirtualMachineManager().get(instanceName);
         final VmCdis vmCdis = new VmCdis();
         final CompletableFuture<Exception> exception = new CompletableFuture<>();
         VmEventListener listener =
@@ -367,12 +405,13 @@
         assumeSupportedKernel();
 
         grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
-        VirtualMachineConfig normalConfig = mInner.newVmConfigBuilder()
-                .setPayloadConfigPath("assets/vm_config.json")
-                .setDebugLevel(DEBUG_LEVEL_FULL)
-                .build();
-        mInner.forceCreateNewVirtualMachine("test_vm_a", normalConfig);
-        mInner.forceCreateNewVirtualMachine("test_vm_b", normalConfig);
+        VirtualMachineConfig normalConfig =
+                newVmConfigBuilder()
+                        .setPayloadConfigPath("assets/vm_config.json")
+                        .setDebugLevel(DEBUG_LEVEL_FULL)
+                        .build();
+        forceCreateNewVirtualMachine("test_vm_a", normalConfig);
+        forceCreateNewVirtualMachine("test_vm_b", normalConfig);
         VmCdis vm_a_cdis = launchVmAndGetCdis("test_vm_a");
         VmCdis vm_b_cdis = launchVmAndGetCdis("test_vm_b");
         assertThat(vm_a_cdis.cdiAttest).isNotNull();
@@ -390,13 +429,15 @@
     })
     public void sameInstanceKeepsSameCdis() throws Exception {
         assumeSupportedKernel();
+        assume().withMessage("Skip on CF. Too Slow. b/257270529").that(isCuttlefish()).isFalse();
 
         grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
-        VirtualMachineConfig normalConfig = mInner.newVmConfigBuilder()
-                .setPayloadConfigPath("assets/vm_config.json")
-                .setDebugLevel(DEBUG_LEVEL_FULL)
-                .build();
-        mInner.forceCreateNewVirtualMachine("test_vm", normalConfig);
+        VirtualMachineConfig normalConfig =
+                newVmConfigBuilder()
+                        .setPayloadConfigPath("assets/vm_config.json")
+                        .setDebugLevel(DEBUG_LEVEL_FULL)
+                        .build();
+        forceCreateNewVirtualMachine("test_vm", normalConfig);
 
         VmCdis first_boot_cdis = launchVmAndGetCdis("test_vm");
         VmCdis second_boot_cdis = launchVmAndGetCdis("test_vm");
@@ -415,11 +456,12 @@
         assumeSupportedKernel();
 
         grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
-        VirtualMachineConfig normalConfig = mInner.newVmConfigBuilder()
-                .setPayloadConfigPath("assets/vm_config.json")
-                .setDebugLevel(DEBUG_LEVEL_FULL)
-                .build();
-        VirtualMachine vm = mInner.forceCreateNewVirtualMachine("bcc_vm", normalConfig);
+        VirtualMachineConfig normalConfig =
+                newVmConfigBuilder()
+                        .setPayloadConfigPath("assets/vm_config.json")
+                        .setDebugLevel(DEBUG_LEVEL_FULL)
+                        .build();
+        VirtualMachine vm = forceCreateNewVirtualMachine("bcc_vm", normalConfig);
         final CompletableFuture<byte[]> bcc = new CompletableFuture<>();
         final CompletableFuture<Exception> exception = new CompletableFuture<>();
         VmEventListener listener =
@@ -463,11 +505,12 @@
     public void accessToCdisIsRestricted() throws Exception {
         assumeSupportedKernel();
 
-        VirtualMachineConfig config = mInner.newVmConfigBuilder()
-                .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
-                .setDebugLevel(DEBUG_LEVEL_FULL)
-                .build();
-        mInner.forceCreateNewVirtualMachine("test_vm", config);
+        VirtualMachineConfig config =
+                newVmConfigBuilder()
+                        .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+                        .setDebugLevel(DEBUG_LEVEL_FULL)
+                        .build();
+        forceCreateNewVirtualMachine("test_vm", config);
 
         assertThrows(Exception.class, () -> launchVmAndGetCdis("test_vm"));
     }
@@ -509,12 +552,13 @@
     }
 
     private RandomAccessFile prepareInstanceImage(String vmName) throws Exception {
-        VirtualMachineConfig config = mInner.newVmConfigBuilder()
-                .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
-                .setDebugLevel(DEBUG_LEVEL_FULL)
-                .build();
+        VirtualMachineConfig config =
+                newVmConfigBuilder()
+                        .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+                        .setDebugLevel(DEBUG_LEVEL_FULL)
+                        .build();
 
-        mInner.forceCreateNewVirtualMachine(vmName, config);
+        forceCreateNewVirtualMachine(vmName, config);
         assertThat(tryBootVm(TAG, vmName).payloadStarted).isTrue();
         File instanceImgPath = getVmFile(vmName, "instance.img");
         return new RandomAccessFile(instanceImgPath, "rw");
@@ -569,11 +613,12 @@
     @Test
     public void bootFailsWhenConfigIsInvalid() throws Exception {
         grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
-        VirtualMachineConfig normalConfig = mInner.newVmConfigBuilder()
-                .setPayloadConfigPath("assets/vm_config_no_task.json")
-                .setDebugLevel(DEBUG_LEVEL_FULL)
-                .build();
-        mInner.forceCreateNewVirtualMachine("test_vm_invalid_config", normalConfig);
+        VirtualMachineConfig normalConfig =
+                newVmConfigBuilder()
+                        .setPayloadConfigPath("assets/vm_config_no_task.json")
+                        .setDebugLevel(DEBUG_LEVEL_FULL)
+                        .build();
+        forceCreateNewVirtualMachine("test_vm_invalid_config", normalConfig);
 
         BootResult bootResult = tryBootVm(TAG, "test_vm_invalid_config");
         assertThat(bootResult.payloadStarted).isFalse();
@@ -583,10 +628,10 @@
 
     @Test
     public void bootFailsWhenBinaryPathIsInvalid() throws Exception {
-        VirtualMachineConfig.Builder builder = mInner.newVmConfigBuilder()
-                .setPayloadBinaryPath("DoesNotExist.so");
+        VirtualMachineConfig.Builder builder =
+                newVmConfigBuilder().setPayloadBinaryPath("DoesNotExist.so");
         VirtualMachineConfig normalConfig = builder.setDebugLevel(DEBUG_LEVEL_FULL).build();
-        mInner.forceCreateNewVirtualMachine("test_vm_invalid_binary_path", normalConfig);
+        forceCreateNewVirtualMachine("test_vm_invalid_binary_path", normalConfig);
 
         BootResult bootResult = tryBootVm(TAG, "test_vm_invalid_binary_path");
         assertThat(bootResult.payloadStarted).isFalse();
@@ -596,17 +641,18 @@
 
     @Test
     public void sameInstancesShareTheSameVmObject() throws Exception {
-        VirtualMachineConfig config = mInner.newVmConfigBuilder()
-                .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
-                .setDebugLevel(DEBUG_LEVEL_NONE)
-                .build();
+        VirtualMachineConfig config =
+                newVmConfigBuilder()
+                        .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+                        .setDebugLevel(DEBUG_LEVEL_NONE)
+                        .build();
 
-        VirtualMachine vm = mInner.forceCreateNewVirtualMachine("test_vm", config);
-        VirtualMachine vm2 = mInner.getVirtualMachineManager().get("test_vm");
+        VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
+        VirtualMachine vm2 = getVirtualMachineManager().get("test_vm");
         assertThat(vm).isEqualTo(vm2);
 
-        VirtualMachine newVm = mInner.forceCreateNewVirtualMachine("test_vm", config);
-        VirtualMachine newVm2 = mInner.getVirtualMachineManager().get("test_vm");
+        VirtualMachine newVm = forceCreateNewVirtualMachine("test_vm", config);
+        VirtualMachine newVm2 = getVirtualMachineManager().get("test_vm");
         assertThat(newVm).isEqualTo(newVm2);
 
         assertThat(vm).isNotEqualTo(newVm);
@@ -618,17 +664,17 @@
         // Arrange
         grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
         VirtualMachineConfig config =
-                mInner.newVmConfigBuilder()
+                newVmConfigBuilder()
                         .setPayloadConfigPath("assets/vm_config.json")
                         .setDebugLevel(DEBUG_LEVEL_FULL)
                         .build();
         String vmNameOrig = "test_vm_orig";
         String vmNameImport = "test_vm_import";
-        VirtualMachine vmOrig = mInner.forceCreateNewVirtualMachine(vmNameOrig, config);
+        VirtualMachine vmOrig = forceCreateNewVirtualMachine(vmNameOrig, config);
         VmCdis origCdis = launchVmAndGetCdis(vmNameOrig);
         assertThat(origCdis.instanceSecret).isNotNull();
         VirtualMachineDescriptor descriptor = vmOrig.toDescriptor();
-        VirtualMachineManager vmm = mInner.getVirtualMachineManager();
+        VirtualMachineManager vmm = getVirtualMachineManager();
         if (vmm.get(vmNameImport) != null) {
             vmm.delete(vmNameImport);
         }
@@ -646,19 +692,19 @@
     public void importedVmIsEqualToTheOriginalVm() throws Exception {
         // Arrange
         VirtualMachineConfig config =
-                mInner.newVmConfigBuilder()
+                newVmConfigBuilder()
                         .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
                         .setDebugLevel(DEBUG_LEVEL_NONE)
                         .build();
         String vmNameOrig = "test_vm_orig";
         String vmNameImport = "test_vm_import";
-        VirtualMachine vmOrig = mInner.forceCreateNewVirtualMachine(vmNameOrig, config);
+        VirtualMachine vmOrig = forceCreateNewVirtualMachine(vmNameOrig, config);
         // Run something to make the instance.img different with the initialized one.
         TestResults origTestResults = runVmTestService(vmOrig);
         assertThat(origTestResults.mException).isNull();
         assertThat(origTestResults.mAddInteger).isEqualTo(123 + 456);
         VirtualMachineDescriptor descriptor = vmOrig.toDescriptor();
-        VirtualMachineManager vmm = mInner.getVirtualMachineManager();
+        VirtualMachineManager vmm = getVirtualMachineManager();
         if (vmm.get(vmNameImport) != null) {
             vmm.delete(vmNameImport);
         }
diff --git a/tests/testapk/src/native/testbinary.cpp b/tests/testapk/src/native/testbinary.cpp
index 5c217ff..c0a8c0e 100644
--- a/tests/testapk/src/native/testbinary.cpp
+++ b/tests/testapk/src/native/testbinary.cpp
@@ -119,10 +119,8 @@
     auto testService = ndk::SharedRefBase::make<TestService>();
 
     auto callback = []([[maybe_unused]] void* param) { AVmPayload_notifyPayloadReady(); };
-    if (!AVmPayload_runVsockRpcServer(testService->asBinder().get(), testService->SERVICE_PORT,
-                                      callback, nullptr)) {
-        return Error() << "RPC Server failed to run";
-    }
+    AVmPayload_runVsockRpcServer(testService->asBinder().get(), testService->SERVICE_PORT, callback,
+                                 nullptr);
 
     return {};
 }
diff --git a/vm_payload/Android.bp b/vm_payload/Android.bp
index 6be6f22..967d1cf 100644
--- a/vm_payload/Android.bp
+++ b/vm_payload/Android.bp
@@ -2,9 +2,11 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-rust_ffi_shared {
-    name: "libvm_payload",
+// The Rust implementation of the C API.
+rust_ffi_static {
+    name: "libvm_payload_impl",
     crate_name: "vm_payload",
+    visibility: ["//visibility:private"],
     srcs: ["src/*.rs"],
     include_dirs: ["include"],
     prefer_rlib: true,
@@ -19,9 +21,6 @@
         "librpcbinder_rs",
         "libvsock",
     ],
-    apex_available: [
-        "com.android.compos",
-    ],
     // The sanitize section below fixes the fuzzer build in b/256166339.
     // TODO(b/250854486): Remove the sanitize section once the bug is fixed.
     sanitize: {
@@ -29,6 +28,8 @@
     },
 }
 
+// Rust wrappers round the C API for Rust clients.
+// (Yes, this involves going Rust -> C -> Rust.)
 rust_bindgen {
     name: "libvm_payload_bindgen",
     wrapper_src: "include-restricted/vm_payload_restricted.h",
@@ -37,16 +38,38 @@
     apex_available: ["com.android.compos"],
     visibility: ["//packages/modules/Virtualization/compos"],
     shared_libs: [
-        "libvm_payload",
+        "libvm_payload#current",
     ],
 }
 
+// Shared library for clients to link against.
+cc_library_shared {
+    name: "libvm_payload",
+    shared_libs: [
+        "libbinder_ndk",
+        "libbinder_rpc_unstable",
+        "liblog",
+    ],
+    whole_static_libs: ["libvm_payload_impl"],
+    export_static_lib_headers: ["libvm_payload_impl"],
+    installable: false,
+    version_script: "libvm_payload.map.txt",
+    stubs: {
+        symbol_file: "libvm_payload.map.txt",
+        // Implementation is available inside a Microdroid VM.
+        implementation_installable: false,
+    },
+}
+
+// Just the headers. Mostly useful for clients that only want the
+// declaration of AVmPayload_main().
 cc_library_headers {
     name: "vm_payload_headers",
     apex_available: ["com.android.compos"],
     export_include_dirs: ["include"],
 }
 
+// Restricted headers for use by internal clients & associated tests.
 cc_library_headers {
     name: "vm_payload_restricted_headers",
     header_libs: ["vm_payload_headers"],
diff --git a/vm_payload/include/vm_payload.h b/vm_payload/include/vm_payload.h
index 0ad4c64..7c224f6 100644
--- a/vm_payload/include/vm_payload.h
+++ b/vm_payload/include/vm_payload.h
@@ -18,6 +18,7 @@
 
 #include <stdbool.h>
 #include <stddef.h>
+#include <stdnoreturn.h>
 #include <sys/cdefs.h>
 
 #include "vm_main.h"
@@ -46,18 +47,17 @@
  * called to allow appropriate action to be taken - e.g. to notify clients that they may now
  * attempt to connect with `AVmPayload_notifyPayloadReady`.
  *
- * The current thread is joined to the binder thread pool to handle incoming messages.
+ * Note that this function does not return. The calling thread joins the binder
+ * thread pool to handle incoming messages.
  *
  * \param service the service to bind to the given port.
  * \param port vsock port.
  * \param on_ready the callback to execute once the server is ready for connections. The callback
  *                 will be called at most once.
  * \param param param for the `on_ready` callback.
- *
- * \return true if the server has shutdown normally, false if it failed in some way.
  */
-bool AVmPayload_runVsockRpcServer(AIBinder *service, unsigned int port,
-                                  void (*on_ready)(void *param), void *param);
+noreturn void AVmPayload_runVsockRpcServer(AIBinder *service, unsigned int port,
+                                           void (*on_ready)(void *param), void *param);
 
 /**
  * Get a secret that is uniquely bound to this VM instance. The secrets are
diff --git a/vm_payload/libvm_payload.map.txt b/vm_payload/libvm_payload.map.txt
new file mode 100644
index 0000000..a2402d1
--- /dev/null
+++ b/vm_payload/libvm_payload.map.txt
@@ -0,0 +1,12 @@
+LIBVM_PAYLOAD {
+  global:
+    AVmPayload_notifyPayloadReady;       # systemapi
+    AVmPayload_runVsockRpcServer;        # systemapi
+    AVmPayload_getVmInstanceSecret;      # systemapi
+    AVmPayload_getDiceAttestationChain;  # systemapi
+    AVmPayload_getDiceAttestationCdi;    # systemapi
+    AVmPayload_getApkContentsPath;       # systemapi
+    AVmPayload_getEncryptedStoragePath;  # systemapi
+  local:
+    *;
+};
diff --git a/vm_payload/src/api.rs b/vm_payload/src/api.rs
index febc2be..a79c0bb 100644
--- a/vm_payload/src/api.rs
+++ b/vm_payload/src/api.rs
@@ -14,13 +14,17 @@
 
 //! This module handles the interaction with virtual machine payload service.
 
+// We're implementing unsafe functions, but we still want warnings on unsafe usage within them.
+#![warn(unsafe_op_in_unsafe_fn)]
+
 use android_system_virtualization_payload::aidl::android::system::virtualization::payload::IVmPayloadService::{
     IVmPayloadService, VM_PAYLOAD_SERVICE_SOCKET_NAME, VM_APK_CONTENTS_PATH};
-use anyhow::{ensure, Context, Result};
+use anyhow::{ensure, bail, Context, Result};
 use binder::{Strong, unstable_api::{AIBinder, new_spibinder}};
 use lazy_static::lazy_static;
 use log::{error, info, Level};
 use rpcbinder::{get_unix_domain_rpc_interface, RpcServer};
+use std::convert::Infallible;
 use std::ffi::CString;
 use std::fmt::Debug;
 use std::os::raw::{c_char, c_void};
@@ -96,44 +100,55 @@
 /// called to allow appropriate action to be taken - e.g. to notify clients that they may now
 /// attempt to connect.
 ///
-/// The current thread is joined to the binder thread pool to handle incoming messages.
+/// The current thread joins the binder thread pool to handle incoming messages.
+/// This function never returns.
 ///
-/// Returns true if the server has shutdown normally, false if it failed in some way.
+/// Panics on error (including unexpected server exit).
 ///
 /// # Safety
 ///
-/// The `on_ready` callback is only called inside `run_vsock_rpc_server`, within the lifetime of
-/// `ReadyNotifier` (the last parameter of `run_vsock_rpc_server`). If `on_ready` is called with
-/// wrong param, the callback execution could go wrong.
+/// If present, the `on_ready` callback must be a valid function pointer, which will be called at
+/// most once, while this function is executing, with the `param` parameter.
 #[no_mangle]
 pub unsafe extern "C" fn AVmPayload_runVsockRpcServer(
     service: *mut AIBinder,
     port: u32,
     on_ready: Option<unsafe extern "C" fn(param: *mut c_void)>,
     param: *mut c_void,
-) -> bool {
+) -> Infallible {
     initialize_logging();
 
+    // SAFETY: try_run_vsock_server has the same requirements as this function
+    unwrap_or_abort(unsafe { try_run_vsock_server(service, port, on_ready, param) })
+}
+
+/// # Safety: Same as `AVmPayload_runVsockRpcServer`.
+unsafe fn try_run_vsock_server(
+    service: *mut AIBinder,
+    port: u32,
+    on_ready: Option<unsafe extern "C" fn(param: *mut c_void)>,
+    param: *mut c_void,
+) -> Result<Infallible> {
     // SAFETY: AIBinder returned has correct reference count, and the ownership can
     // safely be taken by new_spibinder.
-    let service = new_spibinder(service);
+    let service = unsafe { new_spibinder(service) };
     if let Some(service) = service {
         match RpcServer::new_vsock(service, port) {
             Ok(server) => {
                 if let Some(on_ready) = on_ready {
-                    on_ready(param);
+                    // SAFETY: We're calling the callback with the parameter specified within the
+                    // allowed lifetime.
+                    unsafe { on_ready(param) };
                 }
                 server.join();
-                true
+                bail!("RpcServer unexpectedly terminated");
             }
             Err(err) => {
-                error!("Failed to start RpcServer: {:?}", err);
-                false
+                bail!("Failed to start RpcServer: {:?}", err);
             }
         }
     } else {
-        error!("Failed to convert the given service from AIBinder to SpIBinder.");
-        false
+        bail!("Failed to convert the given service from AIBinder to SpIBinder.");
     }
 }
 
@@ -157,9 +172,15 @@
 ) {
     initialize_logging();
 
-    let identifier = std::slice::from_raw_parts(identifier, identifier_size);
+    // SAFETY: See the requirements on `identifier` above.
+    let identifier = unsafe { std::slice::from_raw_parts(identifier, identifier_size) };
     let vm_secret = unwrap_or_abort(try_get_vm_instance_secret(identifier, size));
-    ptr::copy_nonoverlapping(vm_secret.as_ptr(), secret, size);
+
+    // SAFETY: See the requirements on `secret` above; `vm_secret` is known to have length `size`,
+    // and cannot overlap `secret` because we just allocated it.
+    unsafe {
+        ptr::copy_nonoverlapping(vm_secret.as_ptr(), secret, size);
+    }
 }
 
 fn try_get_vm_instance_secret(identifier: &[u8], size: usize) -> Result<Vec<u8>> {
@@ -190,7 +211,9 @@
     initialize_logging();
 
     let chain = unwrap_or_abort(try_get_dice_attestation_chain());
-    ptr::copy_nonoverlapping(chain.as_ptr(), data, std::cmp::min(chain.len(), size));
+    // SAFETY: See the requirements on `data` above. The number of bytes copied doesn't exceed
+    // the length of either buffer, and `chain` cannot overlap `data` because we just allocated it.
+    unsafe { ptr::copy_nonoverlapping(chain.as_ptr(), data, std::cmp::min(chain.len(), size)) };
     chain.len()
 }
 
@@ -213,7 +236,9 @@
     initialize_logging();
 
     let cdi = unwrap_or_abort(try_get_dice_attestation_cdi());
-    ptr::copy_nonoverlapping(cdi.as_ptr(), data, std::cmp::min(cdi.len(), size));
+    // SAFETY: See the requirements on `data` above. The number of bytes copied doesn't exceed
+    // the length of either buffer, and `cdi` cannot overlap `data` because we just allocated it.
+    unsafe { ptr::copy_nonoverlapping(cdi.as_ptr(), data, std::cmp::min(cdi.len(), size)) };
     cdi.len()
 }