Merge "Move HVC constants and wrappers to new module."
diff --git a/authfs/fd_server/src/main.rs b/authfs/fd_server/src/main.rs
index 21d0e64..9d97423 100644
--- a/authfs/fd_server/src/main.rs
+++ b/authfs/fd_server/src/main.rs
@@ -37,7 +37,8 @@
 use aidl::{FdConfig, FdService};
 use authfs_fsverity_metadata::parse_fsverity_metadata;
 
-const RPC_SERVICE_PORT: u32 = 3264; // TODO: support dynamic port for multiple fd_server instances
+// TODO(b/259920193): support dynamic port for multiple fd_server instances
+const RPC_SERVICE_PORT: u32 = 3264;
 
 fn is_fd_valid(fd: i32) -> bool {
     // SAFETY: a query-only syscall
@@ -137,7 +138,8 @@
 
     debug!("fd_server is starting as a rpc service.");
     let service = FdService::new_binder(fd_pool).as_binder();
-    let server = RpcServer::new_vsock(service, RPC_SERVICE_PORT)?;
+    // TODO(b/259920193): Only accept connections from the intended guest VM.
+    let server = RpcServer::new_vsock(service, libc::VMADDR_CID_ANY, RPC_SERVICE_PORT)?;
     debug!("fd_server is ready");
 
     // Close the ready-fd if we were given one to signal our readiness.
diff --git a/compos/common/lib.rs b/compos/common/lib.rs
index c9555d5..8d49ff0 100644
--- a/compos/common/lib.rs
+++ b/compos/common/lib.rs
@@ -21,9 +21,6 @@
 pub mod odrefresh;
 pub mod timeouts;
 
-/// Special CID indicating "any".
-pub const VMADDR_CID_ANY: u32 = -1i32 as u32;
-
 /// VSock port that the CompOS server listens on for RPC binder connections. This should be out of
 /// future port range (if happens) that microdroid may reserve for system components.
 pub const COMPOS_VSOCK_PORT: u32 = 6432;
diff --git a/demo/java/com/android/microdroid/demo/MainActivity.java b/demo/java/com/android/microdroid/demo/MainActivity.java
index 77f2ee7..54d7420 100644
--- a/demo/java/com/android/microdroid/demo/MainActivity.java
+++ b/demo/java/com/android/microdroid/demo/MainActivity.java
@@ -238,13 +238,6 @@
                             mService.shutdownNow();
                             mStatus.postValue(VirtualMachine.STATUS_STOPPED);
                         }
-
-                        @Override
-                        public void onRamdump(VirtualMachine vm, ParcelFileDescriptor ramdump) {
-                            if (!mService.isShutdown()) {
-                                mPayloadOutput.postValue("(Kernel panic. Ramdump created)");
-                            }
-                        }
                     };
 
             try {
diff --git a/docs/debug/ramdump.md b/docs/debug/ramdump.md
index a0d9bf2..771c608 100644
--- a/docs/debug/ramdump.md
+++ b/docs/debug/ramdump.md
@@ -1,6 +1,6 @@
 # Doing RAM dump of a Microdroid VM and analyzing it
 
-A Microdroid VM creates a RAM dump of itself when the kernel panics. This
+A debuggable Microdroid VM creates a RAM dump of itself when the kernel panics. This
 document explains how the dump can be obtained and analyzed.
 
 ## Force triggering a RAM dump
@@ -49,7 +49,7 @@
 
 ## Obtaining the RAM dump
 
-By default, RAM dumps are sent to tombstone. To see which tombstone file is for
+RAM dumps are sent to tombstone. To see which tombstone file is for
 the RAM dump, look into the log.
 
 ```shell
@@ -64,15 +64,6 @@
 $ adb root && adb pull /data/tombstones/tombstone_47 ramdump && adb unroot
 ```
 
-Alternatively, you can specify the path to where RAM dump is stored when
-launching the VM using the `--ramdump` option of the `vm` tool.
-
-```shell
-$ adb shelll /apex/com.android.virt/bin/vm run-app --ramdump /data/local/tmp/virt/ramdump ...
-```
-
-In the above example, the RAM dump is saved to `/data/local/tmp/virt/ramdump`.
-
 ## Analyzing the RAM dump
 
 ### Building the crash(8) tool
@@ -151,9 +142,3 @@
 actually triggered a crash in the kernel.
 
 For more commands of crash(8), refer to the man page, or embedded `help` command.
-
-
-
-
-
-
diff --git a/encryptedstore/src/main.rs b/encryptedstore/src/main.rs
index 9c8311d..7140ae2 100644
--- a/encryptedstore/src/main.rs
+++ b/encryptedstore/src/main.rs
@@ -137,7 +137,10 @@
 
 fn mount(source: &Path, mountpoint: &Path) -> Result<()> {
     create_dir_all(mountpoint).context(format!("Failed to create {:?}", &mountpoint))?;
-    let mount_options = CString::new("").unwrap();
+    let mount_options = CString::new(
+        "fscontext=u:object_r:encryptedstore_fs:s0,context=u:object_r:encryptedstore_file:s0",
+    )
+    .unwrap();
     let source = CString::new(source.as_os_str().as_bytes())?;
     let mountpoint = CString::new(mountpoint.as_os_str().as_bytes())?;
     let fstype = CString::new("ext4").unwrap();
diff --git a/javalib/api/system-current.txt b/javalib/api/system-current.txt
index fb7c98c..d14d83c 100644
--- a/javalib/api/system-current.txt
+++ b/javalib/api/system-current.txt
@@ -4,9 +4,8 @@
   public class VirtualMachine implements java.lang.AutoCloseable {
     method public void clearCallback();
     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;
+    method @NonNull public android.os.IBinder connectToVsockServer(@IntRange(from=android.system.virtualmachine.VirtualMachine.MIN_VSOCK_PORT, to=android.system.virtualmachine.VirtualMachine.MAX_VSOCK_PORT) long) throws android.system.virtualmachine.VirtualMachineException;
+    method @NonNull public android.os.ParcelFileDescriptor connectVsock(@IntRange(from=android.system.virtualmachine.VirtualMachine.MIN_VSOCK_PORT, to=android.system.virtualmachine.VirtualMachine.MAX_VSOCK_PORT) long) throws android.system.virtualmachine.VirtualMachineException;
     method @NonNull public android.system.virtualmachine.VirtualMachineConfig getConfig();
     method @NonNull public java.io.InputStream getConsoleOutput() throws android.system.virtualmachine.VirtualMachineException;
     method @NonNull public java.io.InputStream getLogOutput() throws android.system.virtualmachine.VirtualMachineException;
@@ -18,6 +17,8 @@
     method public void stop() throws android.system.virtualmachine.VirtualMachineException;
     method @NonNull public android.system.virtualmachine.VirtualMachineDescriptor toDescriptor() throws android.system.virtualmachine.VirtualMachineException;
     field public static final String MANAGE_VIRTUAL_MACHINE_PERMISSION = "android.permission.MANAGE_VIRTUAL_MACHINE";
+    field public static final long MAX_VSOCK_PORT = 4294967295L; // 0xffffffffL
+    field public static final long MIN_VSOCK_PORT = 1024L; // 0x400L
     field public static final int STATUS_DELETED = 2; // 0x2
     field public static final int STATUS_RUNNING = 1; // 0x1
     field public static final int STATUS_STOPPED = 0; // 0x0
@@ -29,7 +30,6 @@
     method public void onPayloadFinished(@NonNull android.system.virtualmachine.VirtualMachine, int);
     method public void onPayloadReady(@NonNull android.system.virtualmachine.VirtualMachine);
     method public void onPayloadStarted(@NonNull android.system.virtualmachine.VirtualMachine);
-    method public void onRamdump(@NonNull android.system.virtualmachine.VirtualMachine, @NonNull android.os.ParcelFileDescriptor);
     method public void onStopped(@NonNull android.system.virtualmachine.VirtualMachine, int);
     field public static final int ERROR_PAYLOAD_CHANGED = 2; // 0x2
     field public static final int ERROR_PAYLOAD_INVALID_CONFIG = 3; // 0x3
@@ -58,10 +58,12 @@
   public final class VirtualMachineConfig {
     method @NonNull public String getApkPath();
     method @NonNull public int getDebugLevel();
+    method @IntRange(from=0) public long getEncryptedStorageKib();
     method @IntRange(from=0) public int getMemoryMib();
     method @IntRange(from=1) public int getNumCpus();
     method @Nullable public String getPayloadBinaryPath();
     method public boolean isCompatibleWith(@NonNull android.system.virtualmachine.VirtualMachineConfig);
+    method public boolean isEncryptedStorageEnabled();
     method public boolean isProtectedVm();
     field public static final int DEBUG_LEVEL_FULL = 1; // 0x1
     field public static final int DEBUG_LEVEL_NONE = 0; // 0x0
@@ -72,7 +74,8 @@
     method @NonNull public android.system.virtualmachine.VirtualMachineConfig build();
     method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setApkPath(@NonNull String);
     method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setDebugLevel(int);
-    method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setMemoryMib(@IntRange(from=0) int);
+    method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setEncryptedStorageKib(@IntRange(from=1) long);
+    method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setMemoryMib(@IntRange(from=1) int);
     method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setNumCpus(@IntRange(from=1) int);
     method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setPayloadBinaryPath(@NonNull String);
     method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setProtectedVm(boolean);
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index b8be703..1ea6714 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -45,9 +45,11 @@
 
 import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.content.ComponentCallbacks2;
@@ -121,6 +123,24 @@
             "android.permission.USE_CUSTOM_VIRTUAL_MACHINE";
 
     /**
+     * The lowest port number that can be used to communicate with the virtual machine payload.
+     *
+     * @see #connectToVsockServer
+     * @see #connectVsock
+     */
+    @SuppressLint("MinMaxConstant") // Won't change: see man 7 vsock.
+    public static final long MIN_VSOCK_PORT = 1024;
+
+    /**
+     * The highest port number that can be used to communicate with the virtual machine payload.
+     *
+     * @see #connectToVsockServer
+     * @see #connectVsock
+     */
+    @SuppressLint("MinMaxConstant") // Won't change: see man 7 vsock.
+    public static final long MAX_VSOCK_PORT = (1L << 32) - 1;
+
+    /**
      * Status of a virtual machine
      *
      * @hide
@@ -169,6 +189,9 @@
     /** Size of the instance image. 10 MB. */
     private static final long INSTANCE_FILE_SIZE = 10 * 1024 * 1024;
 
+    /** Name of the file backing the encrypted storage */
+    private static final String ENCRYPTED_STORE_FILE = "storage.img";
+
     /** The package which owns this VM. */
     @NonNull private final String mPackageName;
 
@@ -191,6 +214,9 @@
     /** Path to the idsig file for this VM. */
     @NonNull private final File mIdsigFilePath;
 
+    /** File that backs the encrypted storage - Will be null if not enabled. */
+    @Nullable private final File mEncryptedStoreFilePath;
+
     /**
      * Unmodifiable list of extra apks. Apks are specified by the vm config, and corresponding
      * idsigs are to be generated.
@@ -324,6 +350,10 @@
         mExtraApks = setupExtraApks(context, config, thisVmDir);
         mMemoryManagementCallbacks = new MemoryManagementCallbacks();
         mContext = context;
+        mEncryptedStoreFilePath =
+                (config.isEncryptedStorageEnabled())
+                        ? new File(thisVmDir, ENCRYPTED_STORE_FILE)
+                        : null;
     }
 
     /**
@@ -354,6 +384,16 @@
                 throw new VirtualMachineException("failed to create instance image", e);
             }
             vm.importInstanceFrom(vmDescriptor.getInstanceImgFd());
+
+            if (vmDescriptor.getEncryptedStoreFd() != null) {
+                try {
+                    vm.mEncryptedStoreFilePath.createNewFile();
+                } catch (IOException e) {
+                    throw new VirtualMachineException(
+                            "failed to create encrypted storage image", e);
+                }
+                vm.importEncryptedStoreFrom(vmDescriptor.getEncryptedStoreFd());
+            }
             return vm;
         } catch (VirtualMachineException | RuntimeException e) {
             // If anything goes wrong, delete any files created so far and the VM's directory
@@ -386,6 +426,14 @@
             } catch (IOException e) {
                 throw new VirtualMachineException("failed to create instance image", e);
             }
+            if (config.isEncryptedStorageEnabled()) {
+                try {
+                    vm.mEncryptedStoreFilePath.createNewFile();
+                } catch (IOException e) {
+                    throw new VirtualMachineException(
+                            "failed to create encrypted storage image", e);
+                }
+            }
 
             IVirtualizationService service =
                     IVirtualizationService.Stub.asInterface(
@@ -403,6 +451,22 @@
             } catch (ServiceSpecificException | IllegalArgumentException e) {
                 throw new VirtualMachineException("failed to create instance partition", e);
             }
+
+            if (config.isEncryptedStorageEnabled()) {
+                try {
+                    service.initializeWritablePartition(
+                            ParcelFileDescriptor.open(vm.mEncryptedStoreFilePath, MODE_READ_WRITE),
+                            config.getEncryptedStorageKib() * 1024L,
+                            PartitionType.ENCRYPTEDSTORE);
+                } catch (FileNotFoundException e) {
+                    throw new VirtualMachineException("encrypted storage image missing", e);
+                } catch (RemoteException e) {
+                    throw e.rethrowAsRuntimeException();
+                } catch (ServiceSpecificException | IllegalArgumentException e) {
+                    throw new VirtualMachineException(
+                            "failed to create encrypted storage partition", e);
+                }
+            }
             return vm;
         } catch (VirtualMachineException | RuntimeException e) {
             // If anything goes wrong, delete any files created so far and the VM's directory
@@ -432,7 +496,9 @@
         if (!vm.mInstanceFilePath.exists()) {
             throw new VirtualMachineException("instance image missing");
         }
-
+        if (config.isEncryptedStorageEnabled() && !vm.mEncryptedStoreFilePath.exists()) {
+            throw new VirtualMachineException("Storage image missing");
+        }
         return vm;
     }
 
@@ -564,9 +630,22 @@
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
+        // It's stopped, but we still have a reference to it - we can fix that.
+        dropVm();
     }
 
-    // If we have an IVirtualMachine in the running state return it, otherwise throw.
+    /**
+     * This should only be called when we know our VM has stopped; we no longer need to hold a
+     * reference to it (this allows resources to be GC'd) and we no longer need to be informed of
+     * memory pressure.
+     */
+    @GuardedBy("mLock")
+    private void dropVm() {
+        mContext.unregisterComponentCallbacks(mMemoryManagementCallbacks);
+        mVirtualMachine = null;
+    }
+
+    /** If we have an IVirtualMachine in the running state return it, otherwise throw. */
     @GuardedBy("mLock")
     private IVirtualMachine getRunningVm() throws VirtualMachineException {
         try {
@@ -681,8 +760,12 @@
 
                 // Re-open idsig file in read-only mode
                 appConfig.idsig = ParcelFileDescriptor.open(mIdsigFilePath, MODE_READ_ONLY);
-                appConfig.instanceImage = ParcelFileDescriptor.open(mInstanceFilePath,
-                        MODE_READ_WRITE);
+                appConfig.instanceImage =
+                        ParcelFileDescriptor.open(mInstanceFilePath, MODE_READ_WRITE);
+                if (mEncryptedStoreFilePath != null) {
+                    appConfig.encryptedStorageImage =
+                            ParcelFileDescriptor.open(mEncryptedStoreFilePath, MODE_READ_WRITE);
+                }
                 List<ParcelFileDescriptor> extraIdsigs = new ArrayList<>();
                 for (ExtraApkSpec extraApk : mExtraApks) {
                     extraIdsigs.add(ParcelFileDescriptor.open(extraApk.idsig, MODE_READ_ONLY));
@@ -746,11 +829,6 @@
                                                             VirtualMachine.this, translatedReason));
                                 }
                             }
-
-                            @Override
-                            public void onRamdump(int cid, ParcelFileDescriptor ramdump) {
-                                executeCallback((cb) -> cb.onRamdump(VirtualMachine.this, ramdump));
-                            }
                         });
                 mContext.registerComponentCallbacks(mMemoryManagementCallbacks);
                 service.asBinder().linkToDeath(deathRecipient, 0);
@@ -829,8 +907,7 @@
             }
             try {
                 mVirtualMachine.stop();
-                mContext.unregisterComponentCallbacks(mMemoryManagementCallbacks);
-                mVirtualMachine = null;
+                dropVm();
             } catch (RemoteException e) {
                 throw e.rethrowAsRuntimeException();
             } catch (ServiceSpecificException e) {
@@ -855,8 +932,7 @@
             try {
                 if (stateToStatus(mVirtualMachine.getState()) == STATUS_RUNNING) {
                     mVirtualMachine.stop();
-                    mContext.unregisterComponentCallbacks(mMemoryManagementCallbacks);
-                    mVirtualMachine = null;
+                    dropVm();
                 }
             } catch (RemoteException e) {
                 throw e.rethrowAsRuntimeException();
@@ -890,23 +966,6 @@
     }
 
     /**
-     * Returns the CID of this virtual machine, if it is running.
-     *
-     * @throws VirtualMachineException if the virtual machine is not running.
-     * @hide
-     */
-    @SystemApi
-    public int getCid() throws VirtualMachineException {
-        synchronized (mLock) {
-            try {
-                return getRunningVm().getCid();
-            } catch (RemoteException e) {
-                throw e.rethrowAsRuntimeException();
-            }
-        }
-    }
-
-    /**
      * Changes the config of this virtual machine to a new one. This can be used to adjust things
      * 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.)
@@ -954,9 +1013,13 @@
      */
     @SystemApi
     @NonNull
-    public IBinder connectToVsockServer(int port) throws VirtualMachineException {
+    public IBinder connectToVsockServer(
+            @IntRange(from = MIN_VSOCK_PORT, to = MAX_VSOCK_PORT) long port)
+            throws VirtualMachineException {
+
         synchronized (mLock) {
-            IBinder iBinder = nativeConnectToVsockServer(getRunningVm().asBinder(), port);
+            IBinder iBinder =
+                    nativeConnectToVsockServer(getRunningVm().asBinder(), validatePort(port));
             if (iBinder == null) {
                 throw new VirtualMachineException("Failed to connect to vsock server");
             }
@@ -972,10 +1035,12 @@
      */
     @SystemApi
     @NonNull
-    public ParcelFileDescriptor connectVsock(int port) throws VirtualMachineException {
+    public ParcelFileDescriptor connectVsock(
+            @IntRange(from = MIN_VSOCK_PORT, to = MAX_VSOCK_PORT) long port)
+            throws VirtualMachineException {
         synchronized (mLock) {
             try {
-                return getRunningVm().connectVsock(port);
+                return getRunningVm().connectVsock(validatePort(port));
             } catch (RemoteException e) {
                 throw e.rethrowAsRuntimeException();
             } catch (ServiceSpecificException e) {
@@ -984,6 +1049,16 @@
         }
     }
 
+    private int validatePort(long port) {
+        // Ports below 1024 are "privileged" (payload code can't bind to these), and port numbers
+        // are 32-bit unsigned numbers at the OS level, even though we pass them as 32-bit signed
+        // numbers internally.
+        if (port < MIN_VSOCK_PORT || port > MAX_VSOCK_PORT) {
+            throw new IllegalArgumentException("Bad port " + port);
+        }
+        return (int) port;
+    }
+
     /**
      * Returns the root directory where all files related to this {@link VirtualMachine} (e.g.
      * {@code instance.img}, {@code apk.idsig}, etc) are stored.
@@ -1017,7 +1092,10 @@
             try {
                 return new VirtualMachineDescriptor(
                         ParcelFileDescriptor.open(mConfigFilePath, MODE_READ_ONLY),
-                        ParcelFileDescriptor.open(mInstanceFilePath, MODE_READ_ONLY));
+                        ParcelFileDescriptor.open(mInstanceFilePath, MODE_READ_ONLY),
+                        mEncryptedStoreFilePath != null
+                                ? ParcelFileDescriptor.open(mEncryptedStoreFilePath, MODE_READ_ONLY)
+                                : null);
             } catch (IOException e) {
                 throw new VirtualMachineException(e);
             }
@@ -1183,4 +1261,14 @@
             throw new VirtualMachineException("failed to transfer instance image", e);
         }
     }
+
+    private void importEncryptedStoreFrom(@NonNull ParcelFileDescriptor encryptedStoreFd)
+            throws VirtualMachineException {
+        try (FileChannel storeOutput = new FileOutputStream(mEncryptedStoreFilePath).getChannel();
+                FileChannel storeInput = new AutoCloseInputStream(encryptedStoreFd).getChannel()) {
+            storeOutput.transferFrom(storeInput, /*position=*/ 0, storeInput.size());
+        } catch (IOException e) {
+            throw new VirtualMachineException("failed to transfer encryptedstore image", e);
+        }
+    }
 }
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java b/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java
index fad2fa9..9aaecf0 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java
@@ -20,7 +20,6 @@
 import android.annotation.NonNull;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
-import android.os.ParcelFileDescriptor;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -155,7 +154,4 @@
 
     /** Called when the VM has stopped. */
     void onStopped(@NonNull VirtualMachine vm, @StopReason int reason);
-
-    /** Called when kernel panic occurs and as a result ramdump is generated from the VM. */
-    void onRamdump(@NonNull VirtualMachine vm, @NonNull ParcelFileDescriptor ramdump);
 }
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
index a9e062a..75e5414 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -55,6 +55,8 @@
  */
 @SystemApi
 public final class VirtualMachineConfig {
+    private static final String[] EMPTY_STRING_ARRAY = {};
+
     // These define the schema of the config file persisted on disk.
     private static final int VERSION = 2;
     private static final String KEY_VERSION = "version";
@@ -65,6 +67,7 @@
     private static final String KEY_PROTECTED_VM = "protectedVm";
     private static final String KEY_MEMORY_MIB = "memoryMib";
     private static final String KEY_NUM_CPUS = "numCpus";
+    private static final String KEY_ENCRYPTED_STORAGE_KIB = "encryptedStorageKib";
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
@@ -120,6 +123,9 @@
      */
     @Nullable private final String mPayloadBinaryPath;
 
+    /** The size of storage in KiB. 0 indicates that encryptedStorage is not required */
+    private final long mEncryptedStorageKib;
+
     private VirtualMachineConfig(
             @NonNull String apkPath,
             @Nullable String payloadConfigPath,
@@ -127,47 +133,9 @@
             @DebugLevel int debugLevel,
             boolean protectedVm,
             int memoryMib,
-            int numCpus) {
-        requireNonNull(apkPath);
-        if (!apkPath.startsWith("/")) {
-            throw new IllegalArgumentException("APK path must be an absolute path");
-        }
-
-        if (memoryMib < 0) {
-            throw new IllegalArgumentException("Memory size cannot be negative");
-        }
-
-        int availableCpus = Runtime.getRuntime().availableProcessors();
-        if (numCpus < 1 || numCpus > availableCpus) {
-            throw new IllegalArgumentException("Number of vCPUs (" + numCpus + ") is out of "
-                    + "range [1, " + availableCpus + "]");
-        }
-
-        if (debugLevel != DEBUG_LEVEL_NONE && debugLevel != DEBUG_LEVEL_FULL) {
-            throw new IllegalArgumentException("Invalid debugLevel: " + debugLevel);
-        }
-
-        if (payloadBinaryPath == null) {
-            if (payloadConfigPath == null) {
-                throw new IllegalStateException("setPayloadBinaryPath must be called");
-            }
-        } else {
-            if (payloadConfigPath != null) {
-                throw new IllegalStateException(
-                        "setPayloadBinaryPath and setPayloadConfigPath may not both be called");
-            }
-        }
-
-        if (protectedVm
-                && !HypervisorProperties.hypervisor_protected_vm_supported().orElse(false)) {
-            throw new UnsupportedOperationException(
-                    "Protected VMs are not supported on this device.");
-        }
-        if (!protectedVm && !HypervisorProperties.hypervisor_vm_supported().orElse(false)) {
-            throw new UnsupportedOperationException(
-                    "Unprotected VMs are not supported on this device.");
-        }
-
+            int numCpus,
+            long encryptedStorageKib) {
+        // This is only called from Builder.build(); the builder handles parameter validation.
         mApkPath = apkPath;
         mPayloadConfigPath = payloadConfigPath;
         mPayloadBinaryPath = payloadBinaryPath;
@@ -175,6 +143,7 @@
         mProtectedVm = protectedVm;
         mMemoryMib = memoryMib;
         mNumCpus = numCpus;
+        mEncryptedStorageKib = encryptedStorageKib;
     }
 
     /** Loads a config from a file. */
@@ -203,32 +172,48 @@
     private static VirtualMachineConfig fromInputStream(@NonNull InputStream input)
             throws IOException, VirtualMachineException {
         PersistableBundle b = PersistableBundle.readFromStream(input);
+        try {
+            return fromPersistableBundle(b);
+        } catch (NullPointerException | IllegalArgumentException | IllegalStateException e) {
+            throw new VirtualMachineException("Persisted VM config is invalid", e);
+        }
+    }
+
+    @NonNull
+    private static VirtualMachineConfig fromPersistableBundle(PersistableBundle b) {
         int version = b.getInt(KEY_VERSION);
         if (version > VERSION) {
-            throw new VirtualMachineException("Version too high");
+            throw new IllegalArgumentException(
+                    "Version " + version + " too high; current is " + VERSION);
         }
-        String apkPath = b.getString(KEY_APKPATH);
-        if (apkPath == null) {
-            throw new VirtualMachineException("No apkPath");
+
+        Builder builder = new Builder();
+        builder.setApkPath(b.getString(KEY_APKPATH));
+
+        String payloadConfigPath = b.getString(KEY_PAYLOADCONFIGPATH);
+        if (payloadConfigPath == null) {
+            builder.setPayloadBinaryPath(b.getString(KEY_PAYLOADBINARYPATH));
+        } else {
+            builder.setPayloadConfigPath(payloadConfigPath);
         }
-        String payloadBinaryPath = b.getString(KEY_PAYLOADBINARYPATH);
-        String payloadConfigPath = null;
-        if (payloadBinaryPath == null) {
-            payloadConfigPath = b.getString(KEY_PAYLOADCONFIGPATH);
-            if (payloadConfigPath == null) {
-                throw new VirtualMachineException("No payloadBinaryPath");
-            }
-        }
+
         @DebugLevel int debugLevel = b.getInt(KEY_DEBUGLEVEL);
         if (debugLevel != DEBUG_LEVEL_NONE && debugLevel != DEBUG_LEVEL_FULL) {
-            throw new VirtualMachineException("Invalid debugLevel: " + debugLevel);
+            throw new IllegalArgumentException("Invalid debugLevel: " + debugLevel);
         }
-        boolean protectedVm = b.getBoolean(KEY_PROTECTED_VM);
+        builder.setDebugLevel(debugLevel);
+        builder.setProtectedVm(b.getBoolean(KEY_PROTECTED_VM));
         int memoryMib = b.getInt(KEY_MEMORY_MIB);
-        int numCpus = b.getInt(KEY_NUM_CPUS);
+        if (memoryMib != 0) {
+            builder.setMemoryMib(memoryMib);
+        }
+        builder.setNumCpus(b.getInt(KEY_NUM_CPUS));
+        long encryptedStorageKib = b.getLong(KEY_ENCRYPTED_STORAGE_KIB);
+        if (encryptedStorageKib != 0) {
+            builder.setEncryptedStorageKib(encryptedStorageKib);
+        }
 
-        return new VirtualMachineConfig(apkPath, payloadConfigPath, payloadBinaryPath, debugLevel,
-                protectedVm, memoryMib, numCpus);
+        return builder.build();
     }
 
     /** Persists this config to a file. */
@@ -253,6 +238,9 @@
         if (mMemoryMib > 0) {
             b.putInt(KEY_MEMORY_MIB, mMemoryMib);
         }
+        if (mEncryptedStorageKib > 0) {
+            b.putLong(KEY_ENCRYPTED_STORAGE_KIB, mEncryptedStorageKib);
+        }
         b.writeToStream(output);
     }
 
@@ -315,7 +303,8 @@
     }
 
     /**
-     * Returns the amount of RAM that will be made available to the VM.
+     * Returns the amount of RAM that will be made available to the VM, or 0 if the default size
+     * will be used.
      *
      * @hide
      */
@@ -337,6 +326,28 @@
     }
 
     /**
+     * Returns whether encrypted storage is enabled or not.
+     *
+     * @hide
+     */
+    @SystemApi
+    public boolean isEncryptedStorageEnabled() {
+        return mEncryptedStorageKib > 0;
+    }
+
+    /**
+     * Returns the size of encrypted storage (in KiB) available in the VM, or 0 if encrypted storage
+     * is not enabled
+     *
+     * @hide
+     */
+    @SystemApi
+    @IntRange(from = 0)
+    public long getEncryptedStorageKib() {
+        return mEncryptedStorageKib;
+    }
+
+    /**
      * Tests if this config is compatible with other config. Being compatible means that the configs
      * can be interchangeably used for the same virtual machine. Compatible changes includes the
      * number of CPUs and the size of the RAM. All other changes (e.g. using a different payload,
@@ -348,6 +359,7 @@
     public boolean isCompatibleWith(@NonNull VirtualMachineConfig other) {
         return this.mDebugLevel == other.mDebugLevel
                 && this.mProtectedVm == other.mProtectedVm
+                && this.mEncryptedStorageKib == other.mEncryptedStorageKib
                 && Objects.equals(this.mPayloadConfigPath, other.mPayloadConfigPath)
                 && Objects.equals(this.mPayloadBinaryPath, other.mPayloadBinaryPath)
                 && this.mApkPath.equals(other.mApkPath);
@@ -383,9 +395,8 @@
         vsConfig.protectedVm = mProtectedVm;
         vsConfig.memoryMib = mMemoryMib;
         vsConfig.numCpus = mNumCpus;
-        // Don't allow apps to set task profiles ... at last for now. Also, don't forget to
-        // validate the string because these are appended to the cmdline argument.
-        vsConfig.taskProfiles = new String[0];
+        // Don't allow apps to set task profiles ... at least for now.
+        vsConfig.taskProfiles = EMPTY_STRING_ARRAY;
         return vsConfig;
     }
 
@@ -396,15 +407,16 @@
      */
     @SystemApi
     public static final class Builder {
-        private final Context mContext;
+        @Nullable private final Context mContext;
         @Nullable private String mApkPath;
         @Nullable private String mPayloadConfigPath;
         @Nullable private String mPayloadBinaryPath;
-        @DebugLevel private int mDebugLevel;
+        @DebugLevel private int mDebugLevel = DEBUG_LEVEL_NONE;
         private boolean mProtectedVm;
         private boolean mProtectedVmSet;
         private int mMemoryMib;
-        private int mNumCpus;
+        private int mNumCpus = 1;
+        private long mEncryptedStorageKib;
 
         /**
          * Creates a builder for the given context.
@@ -414,8 +426,14 @@
         @SystemApi
         public Builder(@NonNull Context context) {
             mContext = requireNonNull(context, "context must not be null");
-            mDebugLevel = DEBUG_LEVEL_NONE;
-            mNumCpus = 1;
+        }
+
+        /**
+         * Creates a builder with no associated context; {@link #setApkPath} must be called to
+         * specify which APK contains the payload.
+         */
+        private Builder() {
+            mContext = null;
         }
 
         /**
@@ -426,15 +444,40 @@
         @SystemApi
         @NonNull
         public VirtualMachineConfig build() {
-            String apkPath = (mApkPath == null) ? mContext.getPackageCodePath() : mApkPath;
+            String apkPath;
+            if (mApkPath == null) {
+                if (mContext == null) {
+                    throw new IllegalStateException("apkPath must be specified");
+                }
+                apkPath = mContext.getPackageCodePath();
+            } else {
+                apkPath = mApkPath;
+            }
+
+            if (mPayloadBinaryPath == null) {
+                if (mPayloadConfigPath == null) {
+                    throw new IllegalStateException("setPayloadBinaryPath must be called");
+                }
+            } else {
+                if (mPayloadConfigPath != null) {
+                    throw new IllegalStateException(
+                            "setPayloadBinaryPath and setPayloadConfigPath may not both be called");
+                }
+            }
 
             if (!mProtectedVmSet) {
                 throw new IllegalStateException("setProtectedVm must be called explicitly");
             }
 
             return new VirtualMachineConfig(
-                    apkPath, mPayloadConfigPath, mPayloadBinaryPath, mDebugLevel, mProtectedVm,
-                    mMemoryMib, mNumCpus);
+                    apkPath,
+                    mPayloadConfigPath,
+                    mPayloadBinaryPath,
+                    mDebugLevel,
+                    mProtectedVm,
+                    mMemoryMib,
+                    mNumCpus,
+                    mEncryptedStorageKib);
         }
 
         /**
@@ -446,7 +489,11 @@
         @SystemApi
         @NonNull
         public Builder setApkPath(@NonNull String apkPath) {
-            mApkPath = requireNonNull(apkPath);
+            requireNonNull(apkPath, "apkPath must not be null");
+            if (!apkPath.startsWith("/")) {
+                throw new IllegalArgumentException("APK path must be an absolute path");
+            }
+            mApkPath = apkPath;
             return this;
         }
 
@@ -461,7 +508,8 @@
         @TestApi
         @NonNull
         public Builder setPayloadConfigPath(@NonNull String payloadConfigPath) {
-            mPayloadConfigPath = requireNonNull(payloadConfigPath);
+            mPayloadConfigPath =
+                    requireNonNull(payloadConfigPath, "payloadConfigPath must not be null");
             return this;
         }
 
@@ -474,7 +522,8 @@
         @SystemApi
         @NonNull
         public Builder setPayloadBinaryPath(@NonNull String payloadBinaryPath) {
-            mPayloadBinaryPath = requireNonNull(payloadBinaryPath);
+            mPayloadBinaryPath =
+                    requireNonNull(payloadBinaryPath, "payloadBinaryPath must not be null");
             return this;
         }
 
@@ -486,6 +535,9 @@
         @SystemApi
         @NonNull
         public Builder setDebugLevel(@DebugLevel int debugLevel) {
+            if (debugLevel != DEBUG_LEVEL_NONE && debugLevel != DEBUG_LEVEL_FULL) {
+                throw new IllegalArgumentException("Invalid debugLevel: " + debugLevel);
+            }
             mDebugLevel = debugLevel;
             return this;
         }
@@ -500,20 +552,34 @@
         @SystemApi
         @NonNull
         public Builder setProtectedVm(boolean protectedVm) {
+            if (protectedVm) {
+                if (!HypervisorProperties.hypervisor_protected_vm_supported().orElse(false)) {
+                    throw new UnsupportedOperationException(
+                            "Protected VMs are not supported on this device.");
+                }
+            } else {
+                if (!HypervisorProperties.hypervisor_vm_supported().orElse(false)) {
+                    throw new UnsupportedOperationException(
+                            "Unprotected VMs are not supported on this device.");
+                }
+            }
             mProtectedVm = protectedVm;
             mProtectedVmSet = true;
             return this;
         }
 
         /**
-         * Sets the amount of RAM to give the VM, in mebibytes. If zero or not explicitly set then a
-         * default size will be used.
+         * Sets the amount of RAM to give the VM, in mebibytes. If not explicitly set then a default
+         * size will be used.
          *
          * @hide
          */
         @SystemApi
         @NonNull
-        public Builder setMemoryMib(@IntRange(from = 0) int memoryMib) {
+        public Builder setMemoryMib(@IntRange(from = 1) int memoryMib) {
+            if (memoryMib <= 0) {
+                throw new IllegalArgumentException("Memory size must be positive");
+            }
             mMemoryMib = memoryMib;
             return this;
         }
@@ -526,8 +592,44 @@
          */
         @SystemApi
         @NonNull
-        public Builder setNumCpus(@IntRange(from = 1) int num) {
-            mNumCpus = num;
+        public Builder setNumCpus(@IntRange(from = 1) int numCpus) {
+            int availableCpus = Runtime.getRuntime().availableProcessors();
+            if (numCpus < 1 || numCpus > availableCpus) {
+                throw new IllegalArgumentException(
+                        "Number of vCPUs ("
+                                + numCpus
+                                + ") is out of "
+                                + "range [1, "
+                                + availableCpus
+                                + "]");
+            }
+            mNumCpus = numCpus;
+            return this;
+        }
+
+        /**
+         * Sets the size (in KiB) of encrypted storage available to the VM. If not set, no encrypted
+         * storage is provided.
+         *
+         * <p>The storage is encrypted with a key deterministically derived from the VM identity
+         *
+         * <p>The encrypted storage is persistent across VM reboots as well as device reboots. The
+         * backing file (containing encrypted data) is stored in the app's private data directory.
+         *
+         * <p>Note - There is no integrity guarantee or rollback protection on the storage in case
+         * the encrypted data is modified.
+         *
+         * <p>Deleting the VM will delete the encrypted data - there is no way to recover that data.
+         *
+         * @hide
+         */
+        @SystemApi
+        @NonNull
+        public Builder setEncryptedStorageKib(@IntRange(from = 1) long encryptedStorageKib) {
+            if (encryptedStorageKib <= 0) {
+                throw new IllegalArgumentException("Encrypted Storage size must be positive");
+            }
+            mEncryptedStorageKib = encryptedStorageKib;
             return this;
         }
     }
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java b/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java
index edaf5b4..c9718aa 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java
@@ -19,6 +19,7 @@
 import static java.util.Objects.requireNonNull;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
@@ -37,7 +38,9 @@
 public final class VirtualMachineDescriptor implements Parcelable {
     @NonNull private final ParcelFileDescriptor mConfigFd;
     @NonNull private final ParcelFileDescriptor mInstanceImgFd;
-    // TODO(b/243129654): Add trusted storage fd once it is available.
+    // File descriptor of the image backing the encrypted storage - Will be null if encrypted
+    // storage is not enabled. */
+    @Nullable private final ParcelFileDescriptor mEncryptedStoreFd;
 
     @Override
     public int describeContents() {
@@ -48,6 +51,7 @@
     public void writeToParcel(@NonNull Parcel out, int flags) {
         mConfigFd.writeToParcel(out, flags);
         mInstanceImgFd.writeToParcel(out, flags);
+        if (mEncryptedStoreFd != null) mEncryptedStoreFd.writeToParcel(out, flags);
     }
 
     @NonNull
@@ -78,14 +82,27 @@
         return mInstanceImgFd;
     }
 
+    /**
+     * @return File descriptor of image backing the encrypted storage.
+     *     <p>This method will return null if encrypted storage is not enabled.
+     */
+    @Nullable
+    ParcelFileDescriptor getEncryptedStoreFd() {
+        return mEncryptedStoreFd;
+    }
+
     VirtualMachineDescriptor(
-            @NonNull ParcelFileDescriptor configFd, @NonNull ParcelFileDescriptor instanceImgFd) {
+            @NonNull ParcelFileDescriptor configFd,
+            @NonNull ParcelFileDescriptor instanceImgFd,
+            @Nullable ParcelFileDescriptor encryptedStoreFd) {
         mConfigFd = configFd;
         mInstanceImgFd = instanceImgFd;
+        mEncryptedStoreFd = encryptedStoreFd;
     }
 
     private VirtualMachineDescriptor(Parcel in) {
         mConfigFd = requireNonNull(in.readFileDescriptor());
         mInstanceImgFd = requireNonNull(in.readFileDescriptor());
+        mEncryptedStoreFd = in.readFileDescriptor();
     }
 }
diff --git a/libs/apkverify/src/v4.rs b/libs/apkverify/src/v4.rs
index 6c085f6..94abf99 100644
--- a/libs/apkverify/src/v4.rs
+++ b/libs/apkverify/src/v4.rs
@@ -146,6 +146,11 @@
 
     /// Read a stream for an APK file and creates a corresponding `V4Signature` struct that digests
     /// the APK file. Note that the signing is not done.
+    /// Important: callers of this function are expected to verify the validity of the passed |apk|.
+    /// To be more specific, they should check that |apk| corresponds to a regular file, as calling
+    /// lseek on directory fds is not defined in the standard, and on ext4 it will return (off_t)-1
+    /// (see: https://bugzilla.kernel.org/show_bug.cgi?id=200043), which will result in this
+    /// function OOMing.
     pub fn create(
         mut apk: &mut R,
         block_size: usize,
diff --git a/libs/avb/Android.bp b/libs/avb/Android.bp
index a19a538..8bac942 100644
--- a/libs/avb/Android.bp
+++ b/libs/avb/Android.bp
@@ -12,6 +12,7 @@
     source_stem: "bindings",
     bindgen_flags: [
         "--size_t-is-usize",
+        "--default-enum-style rust",
         "--allowlist-function=.*",
         "--use-core",
         "--raw-line=#![no_std]",
diff --git a/libs/avb/src/ops.rs b/libs/avb/src/ops.rs
index 429c980..8eb67f4 100644
--- a/libs/avb/src/ops.rs
+++ b/libs/avb/src/ops.rs
@@ -20,70 +20,54 @@
 #![allow(unused_imports)]
 
 use alloc::ffi::CString;
-use avb_bindgen::{
-    avb_slot_verify, AvbHashtreeErrorMode_AVB_HASHTREE_ERROR_MODE_EIO,
-    AvbSlotVerifyFlags_AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION,
-    AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_ARGUMENT,
-    AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA,
-    AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_IO,
-    AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_OOM,
-    AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED,
-    AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX,
-    AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_UNSUPPORTED_VERSION,
-    AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION,
-    AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_OK,
-};
+use avb_bindgen::{avb_slot_verify, AvbHashtreeErrorMode, AvbSlotVerifyFlags, AvbSlotVerifyResult};
 use core::fmt;
 use log::debug;
 
 /// Error code from AVB image verification.
 #[derive(Clone, Copy, Debug)]
 pub enum AvbImageVerifyError {
-    /// AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_ARGUMENT
+    /// AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_ARGUMENT
     InvalidArgument,
-    /// AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA
+    /// AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA
     InvalidMetadata,
-    /// AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_IO
+    /// AVB_SLOT_VERIFY_RESULT_ERROR_IO
     Io,
-    /// AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_OOM
+    /// AVB_SLOT_VERIFY_RESULT_ERROR_OOM
     Oom,
-    /// AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED
+    /// AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED
     PublicKeyRejected,
-    /// AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX
+    /// AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX
     RollbackIndex,
-    /// AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_UNSUPPORTED_VERSION
+    /// AVB_SLOT_VERIFY_RESULT_ERROR_UNSUPPORTED_VERSION
     UnsupportedVersion,
-    /// AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION
+    /// AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION
     Verification,
-    /// Unknown error.
-    Unknown(u32),
 }
 
-fn to_avb_verify_result(result: u32) -> Result<(), AvbImageVerifyError> {
-    #[allow(non_upper_case_globals)]
+fn to_avb_verify_result(result: AvbSlotVerifyResult) -> Result<(), AvbImageVerifyError> {
     match result {
-        AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_OK => Ok(()),
-        AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_ARGUMENT => {
+        AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_OK => Ok(()),
+        AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_ARGUMENT => {
             Err(AvbImageVerifyError::InvalidArgument)
         }
-        AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA => {
+        AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA => {
             Err(AvbImageVerifyError::InvalidMetadata)
         }
-        AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_IO => Err(AvbImageVerifyError::Io),
-        AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_OOM => Err(AvbImageVerifyError::Oom),
-        AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED => {
+        AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_ERROR_IO => Err(AvbImageVerifyError::Io),
+        AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_ERROR_OOM => Err(AvbImageVerifyError::Oom),
+        AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED => {
             Err(AvbImageVerifyError::PublicKeyRejected)
         }
-        AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX => {
+        AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX => {
             Err(AvbImageVerifyError::RollbackIndex)
         }
-        AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_UNSUPPORTED_VERSION => {
+        AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_ERROR_UNSUPPORTED_VERSION => {
             Err(AvbImageVerifyError::UnsupportedVersion)
         }
-        AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION => {
+        AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION => {
             Err(AvbImageVerifyError::Verification)
         }
-        _ => Err(AvbImageVerifyError::Unknown(result)),
     }
 }
 
@@ -105,7 +89,6 @@
                 "Some of the metadata requires a newer version of libavb than what is in use."
             ),
             Self::Verification => write!(f, "Data does not verify."),
-            Self::Unknown(e) => write!(f, "Unknown avb_slot_verify error '{e}'"),
         }
     }
 }
@@ -115,8 +98,9 @@
 ///  - The VBMeta struct is valid.
 ///  - The partitions of the image match the descriptors of the verified VBMeta struct.
 /// Returns Ok if everything is verified correctly and the public key is accepted.
-pub fn verify_image(image: &[u8], public_key: &[u8]) -> Result<(), AvbImageVerifyError> {
-    AvbOps::new().verify_image(image, public_key)
+pub fn verify_image(_image: &[u8], _public_key: &[u8]) -> Result<(), AvbImageVerifyError> {
+    // TODO(b/256148034): Call verify_slot() from pvmfw.
+    AvbOps::new().verify_slot()
 }
 
 /// TODO(b/256148034): Make AvbOps a rust wrapper of avb_bindgen::AvbOps using foreign_types.
@@ -127,25 +111,23 @@
         AvbOps {}
     }
 
-    fn verify_image(&self, image: &[u8], public_key: &[u8]) -> Result<(), AvbImageVerifyError> {
-        debug!("AVB image: addr={:?}, size={:#x} ({1})", image.as_ptr(), image.len());
-        debug!(
-            "AVB public key: addr={:?}, size={:#x} ({1})",
-            public_key.as_ptr(),
-            public_key.len()
-        );
+    fn verify_slot(&mut self) -> Result<(), AvbImageVerifyError> {
+        let flags = AvbSlotVerifyFlags::AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION;
+        let hashtree_error_mode = AvbHashtreeErrorMode::AVB_HASHTREE_ERROR_MODE_EIO;
+        debug!("flags: {:?}", flags);
+        debug!("hashtree_error_mode: {:?}", hashtree_error_mode);
         // TODO(b/256148034): Verify the kernel image with avb_slot_verify()
         // let result = unsafe {
         //     avb_slot_verify(
         //         self.as_ptr(),
         //         requested_partitions.as_ptr(),
         //         ab_suffix.as_ptr(),
-        //         AvbSlotVerifyFlags_AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION,
-        //         AvbHashtreeErrorMode_AVB_HASHTREE_ERROR_MODE_EIO,
+        //         flags,
+        //         hashtree_error_mode,
         //         &image.as_ptr(),
         //     )
         // };
-        let result = AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_OK;
+        let result = AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_OK;
         to_avb_verify_result(result)
     }
 }
diff --git a/libs/vbmeta/src/lib.rs b/libs/vbmeta/src/lib.rs
index 65d51d2..1a40e45 100644
--- a/libs/vbmeta/src/lib.rs
+++ b/libs/vbmeta/src/lib.rs
@@ -18,21 +18,14 @@
 
 use avb_bindgen::{
     avb_footer_validate_and_byteswap, avb_vbmeta_image_header_to_host_byte_order,
-    avb_vbmeta_image_verify, AvbAlgorithmType_AVB_ALGORITHM_TYPE_NONE, AvbFooter,
-    AvbVBMetaImageHeader, AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH,
-    AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER,
-    AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_OK,
-    AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_OK_NOT_SIGNED,
-    AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_SIGNATURE_MISMATCH,
-    AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_UNSUPPORTED_VERSION,
+    avb_vbmeta_image_verify, AvbAlgorithmType, AvbFooter, AvbVBMetaImageHeader,
+    AvbVBMetaVerifyResult,
 };
 use std::fs::File;
 use std::io::{self, Read, Seek, SeekFrom};
-use std::mem::{size_of, MaybeUninit};
-use std::os::raw::c_uint;
+use std::mem::{size_of, transmute, MaybeUninit};
 use std::path::Path;
 use std::ptr::null_mut;
-use std::slice;
 use thiserror::Error;
 
 pub use crate::descriptor::{Descriptor, Descriptors};
@@ -69,9 +62,6 @@
     /// The VBMeta image signature did not validate.
     #[error("Signature mismatch")]
     SignatureMismatch,
-    /// An unexpected libavb error code was returned.
-    #[error("Unknown libavb error: {0}")]
-    UnknownLibavbError(c_uint),
 }
 
 /// A VBMeta Image.
@@ -96,14 +86,17 @@
     ) -> Result<Self, VbMetaImageVerificationError> {
         // Check for a footer in the image or assume it's an entire VBMeta image.
         image.seek(SeekFrom::Start(offset + size)).map_err(VbMetaImageParseError::Io)?;
-        let footer = read_avb_footer(&mut image).map_err(VbMetaImageParseError::Io)?;
-        let (vbmeta_offset, vbmeta_size) = if let Some(footer) = footer {
-            if footer.vbmeta_offset > size || footer.vbmeta_size > size - footer.vbmeta_offset {
-                return Err(VbMetaImageParseError::InvalidFooter.into());
+        let (vbmeta_offset, vbmeta_size) = match read_avb_footer(&mut image) {
+            Ok(footer) => {
+                if footer.vbmeta_offset > size || footer.vbmeta_size > size - footer.vbmeta_offset {
+                    return Err(VbMetaImageParseError::InvalidFooter.into());
+                }
+                (footer.vbmeta_offset, footer.vbmeta_size)
             }
-            (footer.vbmeta_offset, footer.vbmeta_size)
-        } else {
-            (0, size)
+            Err(VbMetaImageParseError::InvalidFooter) => (0, size),
+            Err(e) => {
+                return Err(e.into());
+            }
         };
         image.seek(SeekFrom::Start(offset + vbmeta_offset)).map_err(VbMetaImageParseError::Io)?;
         // Verify the image before examining it to check the size.
@@ -128,7 +121,7 @@
     /// Get the public key that verified the VBMeta image. If the image was not signed, there
     /// is no such public key.
     pub fn public_key(&self) -> Option<&[u8]> {
-        if self.header.algorithm_type == AvbAlgorithmType_AVB_ALGORITHM_TYPE_NONE {
+        if self.header.algorithm_type == AvbAlgorithmType::AVB_ALGORITHM_TYPE_NONE as u32 {
             return None;
         }
         let begin = size_of::<AvbVBMetaImageHeader>()
@@ -142,7 +135,7 @@
     /// image was not signed, there might not be a hash and, if there is, it's not known to be
     /// correct.
     pub fn hash(&self) -> Option<&[u8]> {
-        if self.header.algorithm_type == AvbAlgorithmType_AVB_ALGORITHM_TYPE_NONE {
+        if self.header.algorithm_type == AvbAlgorithmType::AVB_ALGORITHM_TYPE_NONE as u32 {
             return None;
         }
         let begin = size_of::<AvbVBMetaImageHeader>() + self.header.hash_offset as usize;
@@ -166,42 +159,36 @@
     // SAFETY: the function only reads from the provided data and the NULL pointers disable the
     // output arguments.
     let res = unsafe { avb_vbmeta_image_verify(data.as_ptr(), data.len(), null_mut(), null_mut()) };
-    #[allow(non_upper_case_globals)]
     match res {
-        AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_OK
-        | AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_OK_NOT_SIGNED => Ok(()),
-        AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER => {
+        AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_OK
+        | AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_OK_NOT_SIGNED => Ok(()),
+        AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER => {
             Err(VbMetaImageParseError::InvalidHeader.into())
         }
-        AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_UNSUPPORTED_VERSION => {
+        AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_UNSUPPORTED_VERSION => {
             Err(VbMetaImageParseError::UnsupportedVersion.into())
         }
-        AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH => {
+        AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH => {
             Err(VbMetaImageVerificationError::HashMismatch)
         }
-        AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_SIGNATURE_MISMATCH => {
+        AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_SIGNATURE_MISMATCH => {
             Err(VbMetaImageVerificationError::SignatureMismatch)
         }
-        err => Err(VbMetaImageVerificationError::UnknownLibavbError(err)),
     }
 }
 
 /// Read the AVB footer, if present, given a reader that's positioned at the end of the image.
-fn read_avb_footer<R: Read + Seek>(image: &mut R) -> io::Result<Option<AvbFooter>> {
+fn read_avb_footer<R: Read + Seek>(image: &mut R) -> Result<AvbFooter, VbMetaImageParseError> {
     image.seek(SeekFrom::Current(-(size_of::<AvbFooter>() as i64)))?;
+    let mut raw_footer = [0u8; size_of::<AvbFooter>()];
+    image.read_exact(&mut raw_footer)?;
     // SAFETY: the slice is the same size as the struct which only contains simple data types.
-    let mut footer = unsafe {
-        let mut footer = MaybeUninit::<AvbFooter>::uninit();
-        let footer_slice =
-            slice::from_raw_parts_mut(&mut footer as *mut _ as *mut u8, size_of::<AvbFooter>());
-        image.read_exact(footer_slice)?;
-        footer.assume_init()
-    };
+    let mut footer = unsafe { transmute::<[u8; size_of::<AvbFooter>()], AvbFooter>(raw_footer) };
     // SAFETY: the function updates the struct in-place.
     if unsafe { avb_footer_validate_and_byteswap(&footer, &mut footer) } {
-        Ok(Some(footer))
+        Ok(footer)
     } else {
-        Ok(None)
+        Err(VbMetaImageParseError::InvalidFooter)
     }
 }
 
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index 028ac1f..2b8e03f 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -71,7 +71,6 @@
         "atrace",
         "debuggerd",
         "linker",
-        "linkerconfig",
         "tombstoned.microdroid",
         "tombstone_transmit.microdroid",
         "cgroups.json",
@@ -83,7 +82,7 @@
         "microdroid_manifest",
         "microdroid_plat_sepolicy_and_mapping.sha256",
         "microdroid_property_contexts",
-        "mke2fs",
+        "mke2fs.microdroid",
 
         // TODO(b/195425111) these should be added automatically
         "libcrypto", // used by many (init_second_stage, microdroid_manager, toybox, etc)
diff --git a/microdroid/init.rc b/microdroid/init.rc
index a48ba4b..7402481 100644
--- a/microdroid/init.rc
+++ b/microdroid/init.rc
@@ -17,6 +17,10 @@
 
     start ueventd
 
+    # Generate empty linker config to suppress warnings
+    write /linkerconfig/ld.config.txt \#
+    chmod 644 /linkerconfig/ld.config.txt
+
 # If VM is debuggable, send logs to outside ot the VM via the serial console.
 # If non-debuggable, logs are internally consumed at /dev/null
 on early-init && property:ro.boot.microdroid.debuggable=1
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 3c490f4..6a37b88 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -77,7 +77,6 @@
 
 const APEX_CONFIG_DONE_PROP: &str = "apex_config.done";
 const DEBUGGABLE_PROP: &str = "ro.boot.microdroid.debuggable";
-const APK_MOUNT_DONE_PROP: &str = "microdroid_manager.apk.mounted";
 
 // SYNC WITH virtualizationservice/src/crosvm.rs
 const FAILURE_SERIAL_DEVICE: &str = "/dev/ttyS1";
@@ -383,15 +382,16 @@
         None
     };
 
+    let mut zipfuse = Zipfuse::default();
+
     // Before reading a file from the APK, start zipfuse
-    run_zipfuse(
+    zipfuse.mount(
         MountForExec::Allowed,
         "fscontext=u:object_r:zipfusefs:s0,context=u:object_r:system_file:s0",
         Path::new("/dev/block/mapper/microdroid-apk"),
         Path::new(VM_APK_CONTENTS_PATH),
-        Some(APK_MOUNT_DONE_PROP),
-    )
-    .context("Failed to run zipfuse")?;
+        "microdroid_manager.apk.mounted".to_owned(),
+    )?;
 
     // Restricted APIs are only allowed to be used by platform or test components. Infer this from
     // the use of a VM config file since those can only be used by platform and test components.
@@ -414,7 +414,7 @@
             verified_data.extra_apks_data.len()
         ));
     }
-    mount_extra_apks(&config)?;
+    mount_extra_apks(&config, &mut zipfuse)?;
 
     // Wait until apex config is done. (e.g. linker configuration for apexes)
     wait_for_apex_config_done()?;
@@ -428,8 +428,8 @@
         control_service("stop", "tombstoned")?;
     }
 
-    // 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")?;
+    // Wait until zipfuse has mounted the APKs so we can access the payload
+    zipfuse.wait_until_done()?;
 
     register_vm_payload_service(allow_restricted_apis, service.clone(), dice_context)?;
 
@@ -480,21 +480,40 @@
     Disallowed,
 }
 
-fn run_zipfuse(
-    noexec: MountForExec,
-    option: &str,
-    zip_path: &Path,
-    mount_dir: &Path,
-    ready_prop: Option<&str>,
-) -> Result<Child> {
-    let mut cmd = Command::new(ZIPFUSE_BIN);
-    if let MountForExec::Disallowed = noexec {
-        cmd.arg("--noexec");
+#[derive(Default)]
+struct Zipfuse {
+    ready_properties: Vec<String>,
+}
+
+impl Zipfuse {
+    fn mount(
+        &mut self,
+        noexec: MountForExec,
+        option: &str,
+        zip_path: &Path,
+        mount_dir: &Path,
+        ready_prop: String,
+    ) -> Result<Child> {
+        let mut cmd = Command::new(ZIPFUSE_BIN);
+        if let MountForExec::Disallowed = noexec {
+            cmd.arg("--noexec");
+        }
+        cmd.args(["-p", &ready_prop, "-o", option]);
+        cmd.arg(zip_path).arg(mount_dir);
+        self.ready_properties.push(ready_prop);
+        cmd.spawn().with_context(|| format!("Failed to run zipfuse for {mount_dir:?}"))
     }
-    if let Some(property_name) = ready_prop {
-        cmd.args(["-p", property_name]);
+
+    fn wait_until_done(self) -> Result<()> {
+        // We check the last-started check first in the hope that by the time it is done
+        // all or most of the others will also be done, minimising the number of times we
+        // block on a property.
+        for property in self.ready_properties.into_iter().rev() {
+            wait_for_property_true(&property)
+                .with_context(|| format!("Failed waiting for {property}"))?;
+        }
+        Ok(())
     }
-    cmd.arg("-o").arg(option).arg(zip_path).arg(mount_dir).spawn().context("Spawn zipfuse")
 }
 
 fn write_apex_payload_data(
@@ -664,21 +683,20 @@
     })
 }
 
-fn mount_extra_apks(config: &VmPayloadConfig) -> Result<()> {
+fn mount_extra_apks(config: &VmPayloadConfig, zipfuse: &mut Zipfuse) -> Result<()> {
     // For now, only the number of apks is important, as the mount point and dm-verity name is fixed
     for i in 0..config.extra_apks.len() {
-        let mount_dir = format!("/mnt/extra-apk/{}", i);
+        let mount_dir = format!("/mnt/extra-apk/{i}");
         create_dir(Path::new(&mount_dir)).context("Failed to create mount dir for extra apks")?;
 
         // don't wait, just detach
-        run_zipfuse(
+        zipfuse.mount(
             MountForExec::Disallowed,
             "fscontext=u:object_r:zipfusefs:s0,context=u:object_r:extra_apk_file:s0",
-            Path::new(&format!("/dev/block/mapper/extra-apk-{}", i)),
+            Path::new(&format!("/dev/block/mapper/extra-apk-{i}")),
             Path::new(&mount_dir),
-            None,
-        )
-        .context("Failed to zipfuse extra apks")?;
+            format!("microdroid_manager.extra_apk.mounted.{i}"),
+        )?;
     }
 
     Ok(())
diff --git a/pvmfw/Android.bp b/pvmfw/Android.bp
index 4218fae..318c7fe 100644
--- a/pvmfw/Android.bp
+++ b/pvmfw/Android.bp
@@ -22,6 +22,7 @@
         "libtinyvec_nostd",
         "libvirtio_drivers",
         "libvmbase",
+        "libzeroize_nostd",
     ],
     apex_available: ["com.android.virt"],
 }
diff --git a/pvmfw/src/config.rs b/pvmfw/src/config.rs
index 0f2a39c..b633745 100644
--- a/pvmfw/src/config.rs
+++ b/pvmfw/src/config.rs
@@ -17,8 +17,7 @@
 use crate::helpers;
 use core::fmt;
 use core::mem;
-use core::num::NonZeroUsize;
-use core::ops;
+use core::ops::Range;
 use core::result;
 
 #[repr(C, packed)]
@@ -43,8 +42,10 @@
     InvalidFlags(u32),
     /// Header describes configuration data that doesn't fit in the expected buffer.
     InvalidSize(usize),
+    /// Header entry is missing.
+    MissingEntry(Entry),
     /// Header entry is invalid.
-    InvalidEntry(Entry),
+    InvalidEntry(Entry, EntryError),
 }
 
 impl fmt::Display for Error {
@@ -55,13 +56,38 @@
             Self::UnsupportedVersion(x, y) => write!(f, "Version {x}.{y} not supported"),
             Self::InvalidFlags(v) => write!(f, "Flags value {v:#x} is incorrect or reserved"),
             Self::InvalidSize(sz) => write!(f, "Total size ({sz:#x}) overflows reserved region"),
-            Self::InvalidEntry(e) => write!(f, "Entry {e:?} is invalid"),
+            Self::MissingEntry(entry) => write!(f, "Mandatory {entry:?} entry is missing"),
+            Self::InvalidEntry(entry, e) => write!(f, "Invalid {entry:?} entry: {e}"),
         }
     }
 }
 
 pub type Result<T> = result::Result<T, Error>;
 
+#[derive(Debug)]
+pub enum EntryError {
+    /// Offset isn't between the fixed minimum value and size of configuration data.
+    InvalidOffset(usize),
+    /// Size must be zero when offset is and not be when it isn't.
+    InvalidSize(usize),
+    /// Entry isn't fully within the configuration data structure.
+    OutOfBounds { offset: usize, size: usize, limit: usize },
+}
+
+impl fmt::Display for EntryError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            Self::InvalidOffset(offset) => write!(f, "Invalid offset: {offset:#x?}"),
+            Self::InvalidSize(sz) => write!(f, "Invalid size: {sz:#x?}"),
+            Self::OutOfBounds { offset, size, limit } => {
+                let range = Header::PADDED_SIZE..*limit;
+                let entry = *offset..(*offset + *size);
+                write!(f, "Out of bounds: {entry:#x?} must be within range {range:#x?}")
+            }
+        }
+    }
+}
+
 impl Header {
     const MAGIC: u32 = u32::from_ne_bytes(*b"pvmf");
     const PADDED_SIZE: usize =
@@ -83,8 +109,43 @@
         self.total_size() - Self::PADDED_SIZE
     }
 
-    fn get(&self, entry: Entry) -> HeaderEntry {
-        self.entries[entry as usize]
+    fn get_body_range(&self, entry: Entry) -> Result<Option<Range<usize>>> {
+        let e = self.entries[entry as usize];
+        let offset = e.offset as usize;
+        let size = e.size as usize;
+
+        match self._get_body_range(offset, size) {
+            Ok(r) => Ok(r),
+            Err(EntryError::InvalidSize(0)) => {
+                // As our bootloader currently uses this (non-compliant) case, permit it for now.
+                log::warn!("Config entry {entry:?} uses non-zero offset with zero size");
+                // TODO(b/262181812): Either make this case valid or fix the bootloader.
+                Ok(None)
+            }
+            Err(e) => Err(Error::InvalidEntry(entry, e)),
+        }
+    }
+
+    fn _get_body_range(
+        &self,
+        offset: usize,
+        size: usize,
+    ) -> result::Result<Option<Range<usize>>, EntryError> {
+        match (offset, size) {
+            (0, 0) => Ok(None),
+            (0, size) | (_, size @ 0) => Err(EntryError::InvalidSize(size)),
+            _ => {
+                let start = offset
+                    .checked_sub(Header::PADDED_SIZE)
+                    .ok_or(EntryError::InvalidOffset(offset))?;
+                let end = start
+                    .checked_add(size)
+                    .filter(|x| *x <= self.body_size())
+                    .ok_or(EntryError::OutOfBounds { offset, size, limit: self.total_size() })?;
+
+                Ok(Some(start..end))
+            }
+        }
     }
 }
 
@@ -105,38 +166,11 @@
     size: u32,
 }
 
-impl HeaderEntry {
-    pub fn is_empty(&self) -> bool {
-        self.offset() == 0 && self.size() == 0
-    }
-
-    pub fn fits_in(&self, max_size: usize) -> bool {
-        (Header::PADDED_SIZE..max_size).contains(&self.offset())
-            && NonZeroUsize::new(self.size())
-                .and_then(|s| s.checked_add(self.offset()))
-                .filter(|&x| x.get() <= max_size)
-                .is_some()
-    }
-
-    pub fn as_body_range(&self) -> ops::Range<usize> {
-        let start = self.offset() - Header::PADDED_SIZE;
-
-        start..(start + self.size())
-    }
-
-    pub fn offset(&self) -> usize {
-        self.offset as usize
-    }
-
-    pub fn size(&self) -> usize {
-        self.size as usize
-    }
-}
-
 #[derive(Debug)]
 pub struct Config<'a> {
-    header: &'a Header,
     body: &'a mut [u8],
+    bcc_range: Range<usize>,
+    dp_range: Option<Range<usize>>,
 }
 
 impl<'a> Config<'a> {
@@ -161,40 +195,26 @@
             return Err(Error::InvalidFlags(header.flags));
         }
 
-        let total_size = header.total_size();
-
-        // BCC is a mandatory entry of the configuration data.
-        if !header.get(Entry::Bcc).fits_in(total_size) {
-            return Err(Error::InvalidEntry(Entry::Bcc));
-        }
-
-        // Debug policy is optional.
-        let dp = header.get(Entry::DebugPolicy);
-        if !dp.is_empty() && !dp.fits_in(total_size) {
-            return Err(Error::InvalidEntry(Entry::DebugPolicy));
-        }
+        let bcc_range =
+            header.get_body_range(Entry::Bcc)?.ok_or(Error::MissingEntry(Entry::Bcc))?;
+        let dp_range = header.get_body_range(Entry::DebugPolicy)?;
 
         let body = data
             .get_mut(Header::PADDED_SIZE..)
             .ok_or(Error::BufferTooSmall)?
             .get_mut(..header.body_size())
-            .ok_or(Error::InvalidSize(total_size))?;
+            .ok_or_else(|| Error::InvalidSize(header.total_size()))?;
 
-        Ok(Self { header, body })
+        Ok(Self { body, bcc_range, dp_range })
     }
 
     /// Get slice containing the platform BCC.
     pub fn get_bcc_mut(&mut self) -> &mut [u8] {
-        &mut self.body[self.header.get(Entry::Bcc).as_body_range()]
+        &mut self.body[self.bcc_range.clone()]
     }
 
     /// Get slice containing the platform debug policy.
     pub fn get_debug_policy(&mut self) -> Option<&mut [u8]> {
-        let entry = self.header.get(Entry::DebugPolicy);
-        if entry.is_empty() {
-            None
-        } else {
-            Some(&mut self.body[entry.as_body_range()])
-        }
+        self.dp_range.as_ref().map(|r| &mut self.body[r.clone()])
     }
 }
diff --git a/pvmfw/src/entry.rs b/pvmfw/src/entry.rs
index 2763e80..45a8459 100644
--- a/pvmfw/src/entry.rs
+++ b/pvmfw/src/entry.rs
@@ -249,7 +249,7 @@
     // This wrapper allows main() to be blissfully ignorant of platform details.
     crate::main(slices.fdt, slices.kernel, slices.ramdisk, &bcc, &mut memory)?;
 
-    // TODO: Overwrite BCC before jumping to payload to avoid leaking our sealing key.
+    helpers::flushed_zeroize(bcc_slice);
 
     info!("Expecting a bug making MMIO_GUARD_UNMAP return NOT_SUPPORTED on success");
     memory.mmio_unmap_all().map_err(|e| {
diff --git a/pvmfw/src/helpers.rs b/pvmfw/src/helpers.rs
index f1ff36d..d1b828a 100644
--- a/pvmfw/src/helpers.rs
+++ b/pvmfw/src/helpers.rs
@@ -15,6 +15,7 @@
 //! Miscellaneous helper functions.
 
 use core::arch::asm;
+use zeroize::Zeroize;
 
 pub const SIZE_4KB: usize = 4 << 10;
 pub const SIZE_2MB: usize = 2 << 20;
@@ -75,3 +76,10 @@
         unsafe { asm!("dc cvau, {x}", x = in(reg) line) }
     }
 }
+
+#[inline]
+/// Overwrites the slice with zeroes, to the point of unification.
+pub fn flushed_zeroize(reg: &mut [u8]) {
+    reg.zeroize();
+    flush_region(reg.as_ptr() as usize, reg.len())
+}
diff --git a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
index 077c74f..a4ecc45 100644
--- a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
+++ b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
@@ -17,9 +17,9 @@
 
 /** {@hide} */
 interface ITestService {
-    const int SERVICE_PORT = 5678;
+    const long SERVICE_PORT = 5678;
 
-    const int ECHO_REVERSE_PORT = 6789;
+    const long ECHO_REVERSE_PORT = 0x80000001L; // Deliberately chosen to be > 2^31, < 2^32
 
     /* add two integers. */
     int addInteger(int a, int b);
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 72a0090..9aed34d 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
@@ -248,9 +248,6 @@
             vm.clearCallback();
             mExecutorService.shutdown();
         }
-
-        @Override
-        public void onRamdump(VirtualMachine vm, ParcelFileDescriptor ramdump) {}
     }
 
     public static class BootResult {
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 8b0d6d2..5f24c4b 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -53,6 +53,7 @@
 import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.function.ThrowingRunnable;
 import org.junit.rules.Timeout;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -212,6 +213,62 @@
     }
 
     @Test
+    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+    public void vmLifecycleChecks() throws Exception {
+        assumeSupportedKernel();
+
+        VirtualMachineConfig config =
+                newVmConfigBuilder()
+                        .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+                        .setMemoryMib(minMemoryRequired())
+                        .setDebugLevel(DEBUG_LEVEL_FULL)
+                        .build();
+
+        VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
+        assertThat(vm.getStatus()).isEqualTo(STATUS_STOPPED);
+
+        // These methods require a running VM
+        assertThrowsVmExceptionContaining(
+                () -> vm.connectVsock(VirtualMachine.MIN_VSOCK_PORT), "not in running state");
+        assertThrowsVmExceptionContaining(
+                () -> vm.connectToVsockServer(VirtualMachine.MIN_VSOCK_PORT),
+                "not in running state");
+
+        vm.run();
+        assertThat(vm.getStatus()).isEqualTo(STATUS_RUNNING);
+
+        // These methods require a stopped VM
+        assertThrowsVmExceptionContaining(() -> vm.run(), "not in stopped state");
+        assertThrowsVmExceptionContaining(() -> vm.setConfig(config), "not in stopped state");
+        assertThrowsVmExceptionContaining(() -> vm.toDescriptor(), "not in stopped state");
+        assertThrowsVmExceptionContaining(
+                () -> getVirtualMachineManager().delete("test_vm"), "not in stopped state");
+
+        vm.stop();
+        getVirtualMachineManager().delete("test_vm");
+        assertThat(vm.getStatus()).isEqualTo(STATUS_DELETED);
+
+        // None of these should work for a deleted VM
+        assertThrowsVmExceptionContaining(
+                () -> vm.connectVsock(VirtualMachine.MIN_VSOCK_PORT), "deleted");
+        assertThrowsVmExceptionContaining(
+                () -> vm.connectToVsockServer(VirtualMachine.MIN_VSOCK_PORT), "deleted");
+        assertThrowsVmExceptionContaining(() -> vm.run(), "deleted");
+        assertThrowsVmExceptionContaining(() -> vm.setConfig(config), "deleted");
+        assertThrowsVmExceptionContaining(() -> vm.toDescriptor(), "deleted");
+        // This is indistinguishable from the VM having never existed, so the message
+        // is non-specific.
+        assertThrows(
+                VirtualMachineException.class, () -> getVirtualMachineManager().delete("test_vm"));
+    }
+
+    private void assertThrowsVmExceptionContaining(
+            ThrowingRunnable runnable, String expectedContents) {
+        Exception e = assertThrows(VirtualMachineException.class, runnable);
+        assertThat(e).hasMessageThat().contains(expectedContents);
+    }
+
+    @Test
     @CddTest(requirements = {"9.17/C-1-1"})
     public void connectVsock() throws Exception {
         assumeSupportedKernel();
@@ -264,8 +321,9 @@
     @Test
     @CddTest(requirements = {"9.17/C-1-1"})
     public void vmConfigUnitTests() {
-        VirtualMachineConfig minimal =
-                newVmConfigBuilder().setPayloadBinaryPath("binary/path").build();
+
+        VirtualMachineConfig.Builder minimalBuilder = newVmConfigBuilder();
+        VirtualMachineConfig minimal = minimalBuilder.setPayloadBinaryPath("binary/path").build();
 
         assertThat(minimal.getApkPath()).isEqualTo(getContext().getPackageCodePath());
         assertThat(minimal.getDebugLevel()).isEqualTo(DEBUG_LEVEL_NONE);
@@ -274,6 +332,8 @@
         assertThat(minimal.getPayloadBinaryPath()).isEqualTo("binary/path");
         assertThat(minimal.getPayloadConfigPath()).isNull();
         assertThat(minimal.isProtectedVm()).isEqualTo(isProtectedVm());
+        assertThat(minimal.isEncryptedStorageEnabled()).isFalse();
+        assertThat(minimal.getEncryptedStorageKib()).isEqualTo(0);
 
         int maxCpus = Runtime.getRuntime().availableProcessors();
         VirtualMachineConfig.Builder maximalBuilder =
@@ -282,7 +342,8 @@
                         .setApkPath("/apk/path")
                         .setNumCpus(maxCpus)
                         .setDebugLevel(DEBUG_LEVEL_FULL)
-                        .setMemoryMib(42);
+                        .setMemoryMib(42)
+                        .setEncryptedStorageKib(1024);
         VirtualMachineConfig maximal = maximalBuilder.build();
 
         assertThat(maximal.getApkPath()).isEqualTo("/apk/path");
@@ -292,6 +353,8 @@
         assertThat(maximal.getPayloadBinaryPath()).isNull();
         assertThat(maximal.getPayloadConfigPath()).isEqualTo("config/path");
         assertThat(maximal.isProtectedVm()).isEqualTo(isProtectedVm());
+        assertThat(maximal.isEncryptedStorageEnabled()).isTrue();
+        assertThat(maximal.getEncryptedStorageKib()).isEqualTo(1024);
 
         assertThat(minimal.isCompatibleWith(maximal)).isFalse();
         assertThat(minimal.isCompatibleWith(minimal)).isTrue();
@@ -299,6 +362,41 @@
 
         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() {
+        VirtualMachineConfig.Builder builder = newVmConfigBuilder();
+
+        // All your null are belong to me.
+        assertThrows(NullPointerException.class, () -> new VirtualMachineConfig.Builder(null));
+        assertThrows(NullPointerException.class, () -> builder.setApkPath(null));
+        assertThrows(NullPointerException.class, () -> builder.setPayloadConfigPath(null));
+        assertThrows(NullPointerException.class, () -> builder.setPayloadBinaryPath(null));
+        assertThrows(NullPointerException.class, () -> builder.setPayloadConfigPath(null));
+
+        // Individual property checks.
+        assertThrows(
+                IllegalArgumentException.class, () -> builder.setApkPath("relative/path/to.apk"));
+        assertThrows(IllegalArgumentException.class, () -> builder.setDebugLevel(-1));
+        assertThrows(IllegalArgumentException.class, () -> builder.setMemoryMib(0));
+        assertThrows(IllegalArgumentException.class, () -> builder.setNumCpus(0));
+        assertThrows(IllegalArgumentException.class, () -> builder.setEncryptedStorageKib(0));
+
+        // Consistency checks enforced at build time.
+        Exception e;
+        e = assertThrows(IllegalStateException.class, () -> builder.build());
+        assertThat(e).hasMessageThat().contains("setPayloadBinaryPath must be called");
+
+        VirtualMachineConfig.Builder protectedNotSet =
+                new VirtualMachineConfig.Builder(getContext()).setPayloadBinaryPath("binary/path");
+        e = assertThrows(IllegalStateException.class, () -> protectedNotSet.build());
+        assertThat(e).hasMessageThat().contains("setProtectedVm must be called");
     }
 
     @Test
@@ -441,20 +539,6 @@
     }
 
     @Test
-    @CddTest(requirements = {
-            "9.17/C-1-1",
-    })
-    public void invalidApkPathIsRejected() {
-        VirtualMachineConfig.Builder builder =
-                newVmConfigBuilder()
-                        .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
-                        .setApkPath("relative/path/to.apk")
-                        .setDebugLevel(DEBUG_LEVEL_FULL)
-                        .setMemoryMib(minMemoryRequired());
-        assertThrows(IllegalArgumentException.class, () -> builder.build());
-    }
-
-    @Test
     @CddTest(requirements = {"9.17/C-1-1"})
     public void invalidVmNameIsRejected() {
         VirtualMachineManager vmm = getVirtualMachineManager();
@@ -927,13 +1011,28 @@
     }
 
     @Test
-    public void importedVmIsEqualToTheOriginalVm() throws Exception {
+    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+    public void importedVmIsEqualToTheOriginalVm_WithoutStorage() throws Exception {
+        TestResults testResults = importedVmIsEqualToTheOriginalVm(false);
+        assertThat(testResults.mEncryptedStoragePath).isEqualTo("");
+    }
+
+    @Test
+    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+    public void importedVmIsEqualToTheOriginalVm_WithStorage() throws Exception {
+        TestResults testResults = importedVmIsEqualToTheOriginalVm(true);
+        assertThat(testResults.mEncryptedStoragePath).isEqualTo("/mnt/encryptedstore");
+    }
+
+    private TestResults importedVmIsEqualToTheOriginalVm(boolean encryptedStoreEnabled)
+            throws Exception {
         // Arrange
-        VirtualMachineConfig config =
+        VirtualMachineConfig.Builder builder =
                 newVmConfigBuilder()
                         .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
-                        .setDebugLevel(DEBUG_LEVEL_FULL)
-                        .build();
+                        .setDebugLevel(DEBUG_LEVEL_FULL);
+        if (encryptedStoreEnabled) builder = builder.setEncryptedStorageKib(4096);
+        VirtualMachineConfig config = builder.build();
         String vmNameOrig = "test_vm_orig";
         String vmNameImport = "test_vm_import";
         VirtualMachine vmOrig = forceCreateNewVirtualMachine(vmNameOrig, config);
@@ -953,12 +1052,34 @@
         // Asserts
         assertFileContentsAreEqualInTwoVms("config.xml", vmNameOrig, vmNameImport);
         assertFileContentsAreEqualInTwoVms("instance.img", vmNameOrig, vmNameImport);
+        if (encryptedStoreEnabled) {
+            assertFileContentsAreEqualInTwoVms("storage.img", vmNameOrig, vmNameImport);
+        }
         assertThat(vmImport).isNotEqualTo(vmOrig);
         vmm.delete(vmNameOrig);
         assertThat(vmImport).isEqualTo(vmm.get(vmNameImport));
         TestResults testResults = runVmTestService(vmImport);
         assertThat(testResults.mException).isNull();
         assertThat(testResults.mAddInteger).isEqualTo(123 + 456);
+        return testResults;
+    }
+
+    @Test
+    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+    public void encryptedStorageAvailable() throws Exception {
+        assumeSupportedKernel();
+
+        VirtualMachineConfig config =
+                newVmConfigBuilder()
+                        .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+                        .setMemoryMib(minMemoryRequired())
+                        .setEncryptedStorageKib(4096)
+                        .setDebugLevel(DEBUG_LEVEL_FULL)
+                        .build();
+        VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
+
+        TestResults testResults = runVmTestService(vm);
+        assertThat(testResults.mEncryptedStoragePath).isEqualTo("/mnt/encryptedstore");
     }
 
     private void assertFileContentsAreEqualInTwoVms(String fileName, String vmName1, String vmName2)
diff --git a/tests/testapk/src/native/testbinary.cpp b/tests/testapk/src/native/testbinary.cpp
index 8a0019d..b6a7aa2 100644
--- a/tests/testapk/src/native/testbinary.cpp
+++ b/tests/testapk/src/native/testbinary.cpp
@@ -112,7 +112,7 @@
     }
     struct sockaddr_vm server_sa = (struct sockaddr_vm){
             .svm_family = AF_VSOCK,
-            .svm_port = BnTestService::ECHO_REVERSE_PORT,
+            .svm_port = static_cast<uint32_t>(BnTestService::ECHO_REVERSE_PORT),
             .svm_cid = VMADDR_CID_ANY,
     };
     int ret = TEMP_FAILURE_RETRY(bind(server_fd, (struct sockaddr*)&server_sa, sizeof(server_sa)));
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl
index a329fa6..34b6fa5 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl
@@ -50,9 +50,4 @@
      * also use `link_to_death` to handle that.
      */
     void onDied(int cid, in DeathReason reason);
-
-    /**
-     * Called when kernel panic occurs and as a result ramdump is generated from the VM.
-     */
-    void onRamdump(int cid, in ParcelFileDescriptor ramdump);
 }
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 7d24a32..a35c2ac 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -115,6 +115,24 @@
     }
 }
 
+fn create_or_update_idsig_file(
+    input_fd: &ParcelFileDescriptor,
+    idsig_fd: &ParcelFileDescriptor,
+) -> Result<()> {
+    let mut input = clone_file(input_fd)?;
+    let metadata = input.metadata().context("failed to get input metadata")?;
+    if !metadata.is_file() {
+        bail!("input is not a regular file");
+    }
+    let mut sig = V4Signature::create(&mut input, 4096, &[], HashAlgorithm::SHA256)
+        .context("failed to create idsig")?;
+
+    let mut output = clone_file(idsig_fd)?;
+    output.set_len(0).context("failed to set_len on the idsig output")?;
+    sig.write_into(&mut output).context("failed to write idsig")?;
+    Ok(())
+}
+
 /// Singleton service for allocating globally-unique VM resources, such as the CID, and running
 /// singleton servers, like tombstone receiver.
 #[derive(Debug, Default)]
@@ -345,12 +363,8 @@
 
         check_manage_access()?;
 
-        let mut input = clone_file(input_fd)?;
-        let mut sig = V4Signature::create(&mut input, 4096, &[], HashAlgorithm::SHA256).unwrap();
-
-        let mut output = clone_file(idsig_fd)?;
-        output.set_len(0).unwrap();
-        sig.write_into(&mut output).unwrap();
+        create_or_update_idsig_file(input_fd, idsig_fd)
+            .map_err(|e| Status::new_service_specific_error_str(-1, Some(format!("{:?}", e))))?;
         Ok(())
     }
 
@@ -463,9 +477,8 @@
             let service = VirtualMachineService::new_binder(self.state.clone(), cid).as_binder();
 
             // Start VM service listening for connections from the new CID on port=CID.
-            // TODO(b/245727626): Only accept connections from the new VM.
             let port = cid;
-            match RpcServer::new_vsock(service, port) {
+            match RpcServer::new_vsock(service, cid, port) {
                 Ok(vm_server) => {
                     vm_server.start();
                     return Ok((VmContext::new(global_context, vm_server), cid));
@@ -881,8 +894,9 @@
 // Return whether a partition is exempt from selinux label checks, because we know that it does
 // not contain code and is likely to be generated in an app-writable directory.
 fn is_safe_app_partition(label: &str) -> bool {
-    // See make_payload_disk in payload.rs.
+    // See add_microdroid_system_images & add_microdroid_payload_images in payload.rs.
     label == "vm-instance"
+        || label == "encryptedstore"
         || label == "microdroid-apk-idsig"
         || label == "payload-metadata"
         || label.starts_with("extra-idsig-")
@@ -898,7 +912,7 @@
     match ctx.selinux_type()? {
         | "system_file" // immutable dm-verity protected partition
         | "apk_data_file" // APKs of an installed app
-        | "staging_data_file" // updated/staged APEX imagess
+        | "staging_data_file" // updated/staged APEX images
         | "shell_data_file" // test files created via adb shell
          => Ok(()),
         _ => bail!("Label {} is not allowed", ctx),
@@ -973,13 +987,16 @@
         if !matches!(&*self.instance.vm_state.lock().unwrap(), VmState::Running { .. }) {
             return Err(Status::new_service_specific_error_str(-1, Some("VM is not running")));
         }
-        let stream =
-            VsockStream::connect_with_cid_port(self.instance.cid, port as u32).map_err(|e| {
-                Status::new_service_specific_error_str(
-                    -1,
-                    Some(format!("Failed to connect: {:?}", e)),
-                )
-            })?;
+        let port = port as u32;
+        if port < 1024 {
+            return Err(Status::new_service_specific_error_str(
+                -1,
+                Some(format!("Can't connect to privileged port {port}")),
+            ));
+        }
+        let stream = VsockStream::connect_with_cid_port(self.instance.cid, port).map_err(|e| {
+            Status::new_service_specific_error_str(-1, Some(format!("Failed to connect: {:?}", e)))
+        })?;
         Ok(vsock_stream_to_pfd(stream))
     }
 }
@@ -1049,17 +1066,6 @@
         }
     }
 
-    /// Call all registered callbacks to say that there was a ramdump to download.
-    pub fn callback_on_ramdump(&self, cid: Cid, ramdump: File) {
-        let callbacks = &*self.0.lock().unwrap();
-        let pfd = ParcelFileDescriptor::new(ramdump);
-        for callback in callbacks {
-            if let Err(e) = callback.onRamdump(cid as i32, &pfd) {
-                error!("Error notifying ramdump of VM CID {}: {:?}", cid, e);
-            }
-        }
-    }
-
     /// Add a new callback to the set.
     fn add(&self, callback: Strong<dyn IVirtualMachineCallback>) {
         self.0.lock().unwrap().push(callback);
@@ -1302,4 +1308,50 @@
         }
         Ok(())
     }
+
+    #[test]
+    fn test_create_or_update_idsig_file_empty_apk() -> Result<()> {
+        let apk = tempfile::tempfile().unwrap();
+        let idsig = tempfile::tempfile().unwrap();
+
+        let ret = create_or_update_idsig_file(
+            &ParcelFileDescriptor::new(apk),
+            &ParcelFileDescriptor::new(idsig),
+        );
+        assert!(ret.is_err(), "should fail");
+        Ok(())
+    }
+
+    #[test]
+    fn test_create_or_update_idsig_dir_instead_of_file_for_apk() -> Result<()> {
+        let tmp_dir = tempfile::TempDir::new().unwrap();
+        let apk = File::open(tmp_dir.path()).unwrap();
+        let idsig = tempfile::tempfile().unwrap();
+
+        let ret = create_or_update_idsig_file(
+            &ParcelFileDescriptor::new(apk),
+            &ParcelFileDescriptor::new(idsig),
+        );
+        assert!(ret.is_err(), "should fail");
+        Ok(())
+    }
+
+    /// Verifies that create_or_update_idsig_file won't oom if a fd that corresponds to a directory
+    /// on ext4 filesystem is passed.
+    /// On ext4 lseek on a directory fd will return (off_t)-1 (see:
+    /// https://bugzilla.kernel.org/show_bug.cgi?id=200043), which will result in
+    /// create_or_update_idsig_file ooming while attempting to allocate petabytes of memory.
+    #[test]
+    fn test_create_or_update_idsig_does_not_crash_dir_on_ext4() -> Result<()> {
+        // APEXes are backed by the ext4.
+        let apk = File::open("/apex/com.android.virt/").unwrap();
+        let idsig = tempfile::tempfile().unwrap();
+
+        let ret = create_or_update_idsig_file(
+            &ParcelFileDescriptor::new(apk),
+            &ParcelFileDescriptor::new(idsig),
+        );
+        assert!(ret.is_err(), "should fail");
+        Ok(())
+    }
 }
diff --git a/virtualizationservice/src/crosvm.rs b/virtualizationservice/src/crosvm.rs
index fc85ca5..5125f19 100644
--- a/virtualizationservice/src/crosvm.rs
+++ b/virtualizationservice/src/crosvm.rs
@@ -520,15 +520,10 @@
         Ok(())
     }
 
-    /// Checks if ramdump has been created. If so, send a notification to the user with the handle
-    /// to read the ramdump.
+    /// Checks if ramdump has been created. If so, send it to tombstoned.
     fn handle_ramdump(&self) -> Result<(), Error> {
         let ramdump_path = self.temporary_directory.join("ramdump");
         if std::fs::metadata(&ramdump_path)?.len() > 0 {
-            let ramdump = File::open(&ramdump_path)
-                .context(format!("Failed to open ramdump {:?} for reading", &ramdump_path))?;
-            self.callbacks.callback_on_ramdump(self.cid, ramdump);
-
             Self::send_ramdump_to_tombstoned(&ramdump_path)?;
         }
         Ok(())
@@ -536,7 +531,7 @@
 
     fn send_ramdump_to_tombstoned(ramdump_path: &Path) -> Result<(), Error> {
         let mut input = File::open(ramdump_path)
-            .context(format!("Failed to open raudmp {:?} for reading", ramdump_path))?;
+            .context(format!("Failed to open ramdump {:?} for reading", ramdump_path))?;
 
         let pid = std::process::id() as i32;
         let conn = TombstonedConnection::connect(pid, DebuggerdDumpType::Tombstone)
diff --git a/vm/src/main.rs b/vm/src/main.rs
index 32b165b..3d2fc00 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -81,10 +81,6 @@
         #[clap(long)]
         log: Option<PathBuf>,
 
-        /// Path to file where ramdump is recorded on kernel panic
-        #[clap(long)]
-        ramdump: Option<PathBuf>,
-
         /// Debug level of the VM. Supported values: "none" (default), and "full".
         #[clap(long, default_value = "none", value_parser = parse_debug_level)]
         debug: DebugLevel,
@@ -144,10 +140,6 @@
         #[clap(long)]
         log: Option<PathBuf>,
 
-        /// Path to file where ramdump is recorded on kernel panic
-        #[clap(long)]
-        ramdump: Option<PathBuf>,
-
         /// Debug level of the VM. Supported values: "none" (default), and "full".
         #[clap(long, default_value = "full", value_parser = parse_debug_level)]
         debug: DebugLevel,
@@ -268,7 +260,6 @@
             daemonize,
             console,
             log,
-            ramdump,
             debug,
             protected,
             mem,
@@ -288,7 +279,6 @@
             daemonize,
             console.as_deref(),
             log.as_deref(),
-            ramdump.as_deref(),
             debug,
             protected,
             mem,
@@ -304,7 +294,6 @@
             daemonize,
             console,
             log,
-            ramdump,
             debug,
             protected,
             mem,
@@ -319,7 +308,6 @@
             daemonize,
             console.as_deref(),
             log.as_deref(),
-            ramdump.as_deref(),
             debug,
             protected,
             mem,
diff --git a/vm/src/run.rs b/vm/src/run.rs
index 3f25bba..6096913 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -52,7 +52,6 @@
     daemonize: bool,
     console_path: Option<&Path>,
     log_path: Option<&Path>,
-    ramdump_path: Option<&Path>,
     debug_level: DebugLevel,
     protected: bool,
     mem: Option<u32>,
@@ -144,7 +143,7 @@
         numCpus: cpus.unwrap_or(1) as i32,
         taskProfiles: task_profiles,
     });
-    run(service, &config, &payload_config_str, daemonize, console_path, log_path, ramdump_path)
+    run(service, &config, &payload_config_str, daemonize, console_path, log_path)
 }
 
 const EMPTY_PAYLOAD_APK: &str = "com.android.microdroid.empty_payload";
@@ -182,7 +181,6 @@
     daemonize: bool,
     console_path: Option<&Path>,
     log_path: Option<&Path>,
-    ramdump_path: Option<&Path>,
     debug_level: DebugLevel,
     protected: bool,
     mem: Option<u32>,
@@ -214,7 +212,6 @@
         daemonize,
         console_path,
         log_path,
-        ramdump_path,
         debug_level,
         protected,
         mem,
@@ -259,7 +256,6 @@
         daemonize,
         console_path,
         log_path,
-        /* ramdump_path */ None,
     )
 }
 
@@ -282,7 +278,6 @@
     daemonize: bool,
     console_path: Option<&Path>,
     log_path: Option<&Path>,
-    ramdump_path: Option<&Path>,
 ) -> Result<(), Error> {
     let console = if let Some(console_path) = console_path {
         Some(
@@ -325,27 +320,12 @@
         // Wait until the VM or VirtualizationService dies. If we just returned immediately then the
         // IVirtualMachine Binder object would be dropped and the VM would be killed.
         let death_reason = vm.wait_for_death();
-
-        if let Some(path) = ramdump_path {
-            save_ramdump_if_available(path, &vm)?;
-        }
         println!("VM ended: {:?}", death_reason);
     }
 
     Ok(())
 }
 
-fn save_ramdump_if_available(path: &Path, vm: &VmInstance) -> Result<(), Error> {
-    if let Some(mut ramdump) = vm.get_ramdump() {
-        let mut file =
-            File::create(path).context(format!("Failed to create ramdump file {:?}", path))?;
-        let size = std::io::copy(&mut ramdump, &mut file)
-            .context(format!("Failed to save ramdump to file {:?}", path))?;
-        eprintln!("Ramdump ({} bytes) saved to {:?}", size, path);
-    }
-    Ok(())
-}
-
 fn parse_extra_apk_list(apk: &Path, config_path: &str) -> Result<Vec<String>, Error> {
     let mut archive = ZipArchive::new(File::open(apk)?)?;
     let config_file = archive.by_name(config_path)?;
diff --git a/vm_payload/src/api.rs b/vm_payload/src/api.rs
index 28b440e..66c8ef7 100644
--- a/vm_payload/src/api.rs
+++ b/vm_payload/src/api.rs
@@ -136,7 +136,7 @@
     // safely be taken by new_spibinder.
     let service = unsafe { new_spibinder(service) };
     if let Some(service) = service {
-        match RpcServer::new_vsock(service, port) {
+        match RpcServer::new_vsock(service, libc::VMADDR_CID_HOST, port) {
             Ok(server) => {
                 if let Some(on_ready) = on_ready {
                     // SAFETY: We're calling the callback with the parameter specified within the
diff --git a/vmbase/Android.bp b/vmbase/Android.bp
index 7a36a0a..5ed436c 100644
--- a/vmbase/Android.bp
+++ b/vmbase/Android.bp
@@ -68,7 +68,7 @@
         "libspin_nostd",
     ],
     whole_static_libs: [
-        "libarm-optimized-routines-mem",
+        "librust_baremetal",
     ],
     apex_available: ["com.android.virt"],
 }
diff --git a/vmclient/src/lib.rs b/vmclient/src/lib.rs
index 20b7f02..7c05545 100644
--- a/vmclient/src/lib.rs
+++ b/vmclient/src/lib.rs
@@ -190,11 +190,6 @@
             }
         })
     }
-
-    /// Get ramdump
-    pub fn get_ramdump(&self) -> Option<File> {
-        self.state.get_ramdump()
-    }
 }
 
 impl Debug for VmInstance {
@@ -222,7 +217,6 @@
 struct VmState {
     death_reason: Option<DeathReason>,
     reported_state: VirtualMachineState,
-    ramdump: Option<File>,
 }
 
 impl Monitor<VmState> {
@@ -239,14 +233,6 @@
         self.state.lock().unwrap().reported_state = state;
         self.cv.notify_all();
     }
-
-    fn set_ramdump(&self, ramdump: File) {
-        self.state.lock().unwrap().ramdump = Some(ramdump);
-    }
-
-    fn get_ramdump(&self) -> Option<File> {
-        self.state.lock().unwrap().ramdump.as_ref().and_then(|f| f.try_clone().ok())
-    }
 }
 
 struct VirtualMachineCallback {
@@ -302,12 +288,6 @@
         Ok(())
     }
 
-    fn onRamdump(&self, _cid: i32, ramdump: &ParcelFileDescriptor) -> BinderResult<()> {
-        let ramdump: File = ramdump.as_ref().try_clone().unwrap();
-        self.state.set_ramdump(ramdump);
-        Ok(())
-    }
-
     fn onDied(&self, cid: i32, reason: AidlDeathReason) -> BinderResult<()> {
         let reason = reason.into();
         self.state.notify_death(reason);