Merge "No need to allocate PCI BARs after all."
diff --git a/TEST_MAPPING b/TEST_MAPPING
index dd81738..321dbf4 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -58,6 +58,9 @@
       "path": "packages/modules/Virtualization/authfs"
     },
     {
+      "path": "packages/modules/Virtualization/pvmfw"
+    },
+    {
       "path": "packages/modules/Virtualization/rialto"
     },
     {
diff --git a/apex/Android.bp b/apex/Android.bp
index 579d7c7..dce8edd 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -57,6 +57,7 @@
         "com.android.virt-bootclasspath-fragment",
     ],
     jni_libs: [
+        "libvirtualizationservice_jni",
         "libvirtualmachine_jni",
     ],
 }
@@ -73,6 +74,7 @@
         arm64: {
             binaries: [
                 "crosvm",
+                "virtmgr",
                 "virtualizationservice",
             ],
             filesystems: microdroid_filesystem_images,
@@ -80,6 +82,7 @@
         x86_64: {
             binaries: [
                 "crosvm",
+                "virtmgr",
                 "virtualizationservice",
             ],
             filesystems: microdroid_filesystem_images,
diff --git a/apex/sign_virt_apex.py b/apex/sign_virt_apex.py
index 4d83c5f..557c8aa 100644
--- a/apex/sign_virt_apex.py
+++ b/apex/sign_virt_apex.py
@@ -239,14 +239,15 @@
         image_size = ReadBytesSize(info['Image size'])
         algorithm = info['Algorithm']
         partition_name = descriptor['Partition Name']
+        hash_algorithm = descriptor['Hash Algorithm']
         partition_size = str(image_size)
-
         cmd = ['avbtool', 'add_hashtree_footer',
                '--key', key,
                '--algorithm', algorithm,
                '--partition_name', partition_name,
                '--partition_size', partition_size,
                '--do_not_generate_fec',
+               '--hash_algorithm', hash_algorithm,
                '--image', image_path]
         if args.signing_args:
             cmd.extend(shlex.split(args.signing_args))
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/authfs/service/authfs_service.rc b/authfs/service/authfs_service.rc
index 7edb1ca..409e91c 100644
--- a/authfs/service/authfs_service.rc
+++ b/authfs/service/authfs_service.rc
@@ -1,3 +1,5 @@
 service authfs_service /system/bin/authfs_service
     disabled
     socket authfs_service stream 0666 root system
+    # SYS_ADMIN capability allows to mount FUSE filesystem
+    capabilities SYS_ADMIN
diff --git a/authfs/src/file.rs b/authfs/src/file.rs
index aff47c5..55c783b 100644
--- a/authfs/src/file.rs
+++ b/authfs/src/file.rs
@@ -9,7 +9,7 @@
 use crate::common::{divide_roundup, CHUNK_SIZE};
 use authfs_aidl_interface::aidl::com::android::virt::fs::IVirtFdService::IVirtFdService;
 use binder::{Status, StatusCode, Strong};
-use rpcbinder::get_vsock_rpc_interface;
+use rpcbinder::RpcSession;
 use std::convert::TryFrom;
 use std::io;
 use std::path::{Path, MAIN_SEPARATOR};
@@ -22,7 +22,7 @@
 pub const RPC_SERVICE_PORT: u32 = 3264;
 
 pub fn get_rpc_binder_service(cid: u32) -> io::Result<VirtFdService> {
-    get_vsock_rpc_interface(cid, RPC_SERVICE_PORT).map_err(|e| match e {
+    RpcSession::new().setup_vsock_client(cid, RPC_SERVICE_PORT).map_err(|e| match e {
         StatusCode::BAD_VALUE => {
             io::Error::new(io::ErrorKind::InvalidInput, "Invalid raw AIBinder")
         }
diff --git a/authfs/tests/benchmarks/src/measure_io.cpp b/authfs/tests/benchmarks/src/measure_io.cpp
index e1f2fb8..e766664 100644
--- a/authfs/tests/benchmarks/src/measure_io.cpp
+++ b/authfs/tests/benchmarks/src/measure_io.cpp
@@ -55,7 +55,10 @@
     }
 
     char buf[kBlockSizeBytes];
-    clock_t start = clock();
+    struct timespec start;
+    if (clock_gettime(CLOCK_MONOTONIC, &start) == -1) {
+        err(EXIT_FAILURE, "failed to clock_gettime");
+    }
     for (auto i = 0; i < block_count; ++i) {
         auto bytes = is_read ? pread(fd, buf, kBlockSizeBytes, offsets[i])
                              : pwrite(fd, buf, kBlockSizeBytes, offsets[i]);
@@ -69,7 +72,11 @@
         // Writes all the buffered modifications to the open file.
         assert(syncfs(fd) == 0);
     }
-    double elapsed_seconds = ((double)clock() - start) / CLOCKS_PER_SEC;
+    struct timespec finish;
+    if (clock_gettime(CLOCK_MONOTONIC, &finish) == -1) {
+        err(EXIT_FAILURE, "failed to clock_gettime");
+    }
+    double elapsed_seconds = finish.tv_sec - start.tv_sec + (finish.tv_nsec - start.tv_nsec) / 1e9;
     double rate = (double)file_size_mb / elapsed_seconds;
     std::cout << std::setprecision(12) << rate << std::endl;
 
diff --git a/compos/apex/composd.rc b/compos/apex/composd.rc
index 3e2efb1..df04642 100644
--- a/compos/apex/composd.rc
+++ b/compos/apex/composd.rc
@@ -19,3 +19,10 @@
     interface aidl android.system.composd
     disabled
     oneshot
+    # Explicitly specify empty capabilities, otherwise composd will inherit all
+    # the capabilities from init.
+    # Note: whether a process can use capabilities is controlled by SELinux, so
+    # inheriting all the capabilities from init is not a security issue.
+    # However, for defense-in-depth and just for the sake of bookkeeping it's
+    # better to explicitly state that composd doesn't need any capabilities.
+    capabilities
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/compos/src/compsvc.rs b/compos/src/compsvc.rs
index 40d14d8..8febd52 100644
--- a/compos/src/compsvc.rs
+++ b/compos/src/compsvc.rs
@@ -39,7 +39,7 @@
 };
 use compos_common::binder::to_binder_result;
 use compos_common::odrefresh::{is_system_property_interesting, ODREFRESH_PATH};
-use rpcbinder::get_unix_domain_rpc_interface;
+use rpcbinder::RpcSession;
 
 /// Constructs a binder object that implements ICompOsService.
 pub fn new_binder() -> Result<Strong<dyn ICompOsService>> {
@@ -130,9 +130,9 @@
 impl CompOsService {
     fn do_odrefresh(&self, args: &OdrefreshArgs) -> Result<i8> {
         log::debug!("Prepare to connect to {}", AUTHFS_SERVICE_SOCKET_NAME);
-        let authfs_service: Strong<dyn IAuthFsService> =
-            get_unix_domain_rpc_interface(AUTHFS_SERVICE_SOCKET_NAME)
-                .with_context(|| format!("Failed to connect to {}", AUTHFS_SERVICE_SOCKET_NAME))?;
+        let authfs_service: Strong<dyn IAuthFsService> = RpcSession::new()
+            .setup_unix_domain_client(AUTHFS_SERVICE_SOCKET_NAME)
+            .with_context(|| format!("Failed to connect to {}", AUTHFS_SERVICE_SOCKET_NAME))?;
         let exit_code = odrefresh(&self.odrefresh_path, args, authfs_service, |output_dir| {
             // authfs only shows us the files we created, so it's ok to just sign everything
             // under the output directory.
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..592a751 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);
@@ -85,9 +88,6 @@
   }
 
   public class VirtualMachineException extends java.lang.Exception {
-    ctor public VirtualMachineException(@Nullable String);
-    ctor public VirtualMachineException(@Nullable String, @Nullable Throwable);
-    ctor public VirtualMachineException(@Nullable Throwable);
   }
 
   public class VirtualMachineManager {
diff --git a/javalib/jni/Android.bp b/javalib/jni/Android.bp
index 2939db5..e82b2ce 100644
--- a/javalib/jni/Android.bp
+++ b/javalib/jni/Android.bp
@@ -3,11 +3,29 @@
 }
 
 cc_library_shared {
+    name: "libvirtualizationservice_jni",
+    srcs: [
+        "android_system_virtualmachine_VirtualizationService.cpp",
+    ],
+    apex_available: ["com.android.virt"],
+    shared_libs: [
+        "libbase",
+        "libbinder_ndk",
+        "libbinder_rpc_unstable",
+        "liblog",
+        "libnativehelper",
+    ],
+}
+
+cc_library_shared {
     name: "libvirtualmachine_jni",
-    srcs: ["android_system_virtualmachine_VirtualMachine.cpp"],
+    srcs: [
+        "android_system_virtualmachine_VirtualMachine.cpp",
+    ],
     apex_available: ["com.android.virt"],
     shared_libs: [
         "android.system.virtualizationservice-ndk",
+        "libbase",
         "libbinder_ndk",
         "libbinder_rpc_unstable",
         "liblog",
diff --git a/javalib/jni/android_system_virtualmachine_VirtualMachine.cpp b/javalib/jni/android_system_virtualmachine_VirtualMachine.cpp
index 7234dad..3230af4 100644
--- a/javalib/jni/android_system_virtualmachine_VirtualMachine.cpp
+++ b/javalib/jni/android_system_virtualmachine_VirtualMachine.cpp
@@ -16,16 +16,16 @@
 
 #define LOG_TAG "VirtualMachine"
 
-#include <tuple>
-
-#include <log/log.h>
-
 #include <aidl/android/system/virtualizationservice/IVirtualMachine.h>
 #include <android/binder_auto_utils.h>
 #include <android/binder_ibinder_jni.h>
-#include <binder_rpc_unstable.hpp>
-
 #include <jni.h>
+#include <log/log.h>
+
+#include <binder_rpc_unstable.hpp>
+#include <tuple>
+
+#include "common.h"
 
 JNIEXPORT jobject JNICALL android_system_virtualmachine_VirtualMachine_connectToVsockServer(
         JNIEnv* env, [[maybe_unused]] jclass clazz, jobject vmBinder, jint port) {
@@ -55,7 +55,9 @@
         return ret;
     };
 
-    return AIBinder_toJavaBinder(env, RpcPreconnectedClient(requestFunc, &args));
+    RpcSessionHandle session;
+    auto client = ARpcSession_setupPreconnectedClient(session.get(), requestFunc, &args);
+    return AIBinder_toJavaBinder(env, client);
 }
 
 JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/) {
diff --git a/javalib/jni/android_system_virtualmachine_VirtualizationService.cpp b/javalib/jni/android_system_virtualmachine_VirtualizationService.cpp
new file mode 100644
index 0000000..b9d2ca4
--- /dev/null
+++ b/javalib/jni/android_system_virtualmachine_VirtualizationService.cpp
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "VirtualizationService"
+
+#include <android-base/unique_fd.h>
+#include <android/binder_ibinder_jni.h>
+#include <jni.h>
+#include <log/log.h>
+
+#include <string>
+
+#include "common.h"
+
+using namespace android::base;
+
+static constexpr const char VIRTMGR_PATH[] = "/apex/com.android.virt/bin/virtmgr";
+static constexpr size_t VIRTMGR_THREADS = 16;
+
+JNIEXPORT jint JNICALL android_system_virtualmachine_VirtualizationService_spawn(
+        JNIEnv* env, [[maybe_unused]] jclass clazz) {
+    unique_fd serverFd, clientFd;
+    if (!Socketpair(SOCK_STREAM, &serverFd, &clientFd)) {
+        env->ThrowNew(env->FindClass("android/system/virtualmachine/VirtualMachineException"),
+                      ("Failed to create socketpair: " + std::string(strerror(errno))).c_str());
+        return -1;
+    }
+
+    unique_fd waitFd, readyFd;
+    if (!Pipe(&waitFd, &readyFd, 0)) {
+        env->ThrowNew(env->FindClass("android/system/virtualmachine/VirtualMachineException"),
+                      ("Failed to create pipe: " + std::string(strerror(errno))).c_str());
+        return -1;
+    }
+
+    if (fork() == 0) {
+        // Close client's FDs.
+        clientFd.reset();
+        waitFd.reset();
+
+        auto strServerFd = std::to_string(serverFd.get());
+        auto strReadyFd = std::to_string(readyFd.get());
+
+        execl(VIRTMGR_PATH, VIRTMGR_PATH, "--rpc-server-fd", strServerFd.c_str(), "--ready-fd",
+              strReadyFd.c_str(), NULL);
+    }
+
+    // Close virtmgr's FDs.
+    serverFd.reset();
+    readyFd.reset();
+
+    // Wait for the server to signal its readiness by closing its end of the pipe.
+    char buf;
+    if (read(waitFd.get(), &buf, sizeof(buf)) < 0) {
+        env->ThrowNew(env->FindClass("android/system/virtualmachine/VirtualMachineException"),
+                      "Failed to wait for VirtualizationService to be ready");
+        return -1;
+    }
+
+    return clientFd.release();
+}
+
+JNIEXPORT jobject JNICALL android_system_virtualmachine_VirtualizationService_connect(
+        JNIEnv* env, [[maybe_unused]] jobject obj, int clientFd) {
+    RpcSessionHandle session;
+    ARpcSession_setFileDescriptorTransportMode(session.get(),
+                                               ARpcSession_FileDescriptorTransportMode::Unix);
+    ARpcSession_setMaxIncomingThreads(session.get(), VIRTMGR_THREADS);
+    ARpcSession_setMaxOutgoingThreads(session.get(), VIRTMGR_THREADS);
+    // SAFETY - ARpcSession_setupUnixDomainBootstrapClient does not take ownership of clientFd.
+    auto client = ARpcSession_setupUnixDomainBootstrapClient(session.get(), clientFd);
+    return AIBinder_toJavaBinder(env, client);
+}
+
+JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/) {
+    JNIEnv* env;
+    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+        ALOGE("%s: Failed to get the environment", __FUNCTION__);
+        return JNI_ERR;
+    }
+
+    jclass c = env->FindClass("android/system/virtualmachine/VirtualizationService");
+    if (c == nullptr) {
+        ALOGE("%s: Failed to find class android.system.virtualmachine.VirtualizationService",
+              __FUNCTION__);
+        return JNI_ERR;
+    }
+
+    // Register your class' native methods.
+    static const JNINativeMethod methods[] = {
+            {"nativeSpawn", "()I",
+             reinterpret_cast<void*>(android_system_virtualmachine_VirtualizationService_spawn)},
+            {"nativeConnect", "(I)Landroid/os/IBinder;",
+             reinterpret_cast<void*>(android_system_virtualmachine_VirtualizationService_connect)},
+    };
+    int rc = env->RegisterNatives(c, methods, sizeof(methods) / sizeof(JNINativeMethod));
+    if (rc != JNI_OK) {
+        ALOGE("%s: Failed to register natives", __FUNCTION__);
+        return rc;
+    }
+
+    return JNI_VERSION_1_6;
+}
diff --git a/javalib/jni/common.h b/javalib/jni/common.h
new file mode 100644
index 0000000..c70ba76
--- /dev/null
+++ b/javalib/jni/common.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <binder_rpc_unstable.hpp>
+
+// Wrapper around ARpcSession handle that automatically frees the handle when
+// it goes out of scope.
+class RpcSessionHandle {
+public:
+    RpcSessionHandle() : mHandle(ARpcSession_new()) {}
+    ~RpcSessionHandle() { ARpcSession_free(mHandle); }
+
+    ARpcSession* get() { return mHandle; }
+
+private:
+    ARpcSession* mHandle;
+};
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index b8be703..b040cc0 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;
@@ -59,8 +61,8 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.ServiceSpecificException;
+import android.system.virtualizationcommon.DeathReason;
 import android.system.virtualizationcommon.ErrorCode;
-import android.system.virtualizationservice.DeathReason;
 import android.system.virtualizationservice.IVirtualMachine;
 import android.system.virtualizationservice.IVirtualMachineCallback;
 import android.system.virtualizationservice.IVirtualizationService;
@@ -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.
@@ -285,6 +311,9 @@
     @Nullable
     private ParcelFileDescriptor mLogWriter;
 
+    @GuardedBy("mLock")
+    private boolean mWasDeleted = false;
+
     /** The registered callback */
     @GuardedBy("mCallbackLock")
     @Nullable
@@ -324,6 +353,10 @@
         mExtraApks = setupExtraApks(context, config, thisVmDir);
         mMemoryManagementCallbacks = new MemoryManagementCallbacks();
         mContext = context;
+        mEncryptedStoreFilePath =
+                (config.isEncryptedStorageEnabled())
+                        ? new File(thisVmDir, ENCRYPTED_STORE_FILE)
+                        : null;
     }
 
     /**
@@ -354,6 +387,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 +429,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 +454,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 +499,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;
     }
 
@@ -440,6 +509,9 @@
     void delete(Context context, String name) throws VirtualMachineException {
         synchronized (mLock) {
             checkStopped();
+            // Once we explicitly delete a VM it must remain permanently in the deleted state;
+            // if a new VM is created with the same name (and files) that's unrelated.
+            mWasDeleted = true;
             deleteVmDirectory(context, name);
         }
     }
@@ -521,6 +593,9 @@
     public int getStatus() {
         IVirtualMachine virtualMachine;
         synchronized (mLock) {
+            if (mWasDeleted) {
+                return STATUS_DELETED;
+            }
             virtualMachine = mVirtualMachine;
         }
         if (virtualMachine == null) {
@@ -551,7 +626,7 @@
     // Throw an appropriate exception if we have a running VM, or the VM has been deleted.
     @GuardedBy("mLock")
     private void checkStopped() throws VirtualMachineException {
-        if (!mVmRootPath.exists()) {
+        if (mWasDeleted || !mVmRootPath.exists()) {
             throw new VirtualMachineException("VM has been deleted");
         }
         if (mVirtualMachine == null) {
@@ -564,9 +639,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 {
@@ -574,7 +662,7 @@
                     && stateToStatus(mVirtualMachine.getState()) == STATUS_RUNNING) {
                 return mVirtualMachine;
             } else {
-                if (!mVmRootPath.exists()) {
+                if (mWasDeleted || !mVmRootPath.exists()) {
                     throw new VirtualMachineException("VM has been deleted");
                 } else {
                     throw new VirtualMachineException("VM is not in running state");
@@ -681,8 +769,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 +838,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 +916,7 @@
             }
             try {
                 mVirtualMachine.stop();
-                mContext.unregisterComponentCallbacks(mMemoryManagementCallbacks);
-                mVirtualMachine = null;
+                dropVm();
             } catch (RemoteException e) {
                 throw e.rethrowAsRuntimeException();
             } catch (ServiceSpecificException e) {
@@ -855,8 +941,7 @@
             try {
                 if (stateToStatus(mVirtualMachine.getState()) == STATUS_RUNNING) {
                     mVirtualMachine.stop();
-                    mContext.unregisterComponentCallbacks(mMemoryManagementCallbacks);
-                    mVirtualMachine = null;
+                    dropVm();
                 }
             } catch (RemoteException e) {
                 throw e.rethrowAsRuntimeException();
@@ -890,23 +975,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 +1022,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 +1044,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 +1058,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 +1101,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 +1270,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/javalib/src/android/system/virtualmachine/VirtualMachineException.java b/javalib/src/android/system/virtualmachine/VirtualMachineException.java
index 985eb70..9948fda 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineException.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineException.java
@@ -26,15 +26,15 @@
  */
 @SystemApi
 public class VirtualMachineException extends Exception {
-    public VirtualMachineException(@Nullable String message) {
+    VirtualMachineException(@Nullable String message) {
         super(message);
     }
 
-    public VirtualMachineException(@Nullable String message, @Nullable Throwable cause) {
+    VirtualMachineException(@Nullable String message, @Nullable Throwable cause) {
         super(message, cause);
     }
 
-    public VirtualMachineException(@Nullable Throwable cause) {
+    VirtualMachineException(@Nullable Throwable cause) {
         super(cause);
     }
 }
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
index ea0a305..5b30617 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
@@ -150,6 +150,11 @@
      * Returns an existing {@link VirtualMachine} with the given name. Returns null if there is no
      * such virtual machine.
      *
+     * <p>There is at most one {@code VirtualMachine} object corresponding to a given virtual
+     * machine instance. Multiple calls to get() passing the same name will get the same object
+     * returned, until the virtual machine is deleted (via {@link #delete}) and then recreated.
+     *
+     * @see #getOrCreate
      * @throws VirtualMachineException if the virtual machine exists but could not be successfully
      *     retrieved.
      * @hide
diff --git a/javalib/src/android/system/virtualmachine/VirtualizationService.java b/javalib/src/android/system/virtualmachine/VirtualizationService.java
new file mode 100644
index 0000000..78d0c9c
--- /dev/null
+++ b/javalib/src/android/system/virtualmachine/VirtualizationService.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.system.virtualmachine;
+
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.system.virtualizationservice.IVirtualizationService;
+
+/** A running instance of virtmgr that is hosting a VirtualizationService AIDL service. */
+class VirtualizationService {
+    static {
+        System.loadLibrary("virtualizationservice_jni");
+    }
+
+    /*
+     * Client FD for UDS connection to virtmgr's RpcBinder server. Closing it
+     * will make virtmgr shut down.
+     */
+    private ParcelFileDescriptor mClientFd;
+
+    private static native int nativeSpawn();
+
+    private native IBinder nativeConnect(int clientFd);
+
+    /*
+     * Spawns a new virtmgr subprocess that will host a VirtualizationService
+     * AIDL service.
+     */
+    public VirtualizationService() throws VirtualMachineException {
+        int clientFd = nativeSpawn();
+        if (clientFd < 0) {
+            throw new VirtualMachineException("Could not spawn VirtualizationService");
+        }
+        mClientFd = ParcelFileDescriptor.adoptFd(clientFd);
+    }
+
+    /* Connects to the VirtualizationService AIDL service. */
+    public IVirtualizationService connect() throws VirtualMachineException {
+        IBinder binder = nativeConnect(mClientFd.getFd());
+        if (binder == null) {
+            throw new VirtualMachineException("Could not connect to VirtualizationService");
+        }
+        return IVirtualizationService.Stub.asInterface(binder);
+    }
+}
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..436f672 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]",
@@ -36,18 +37,3 @@
     clippy_lints: "none",
     lints: "none",
 }
-
-rust_library_rlib {
-    name: "libavb_nostd",
-    crate_name: "avb",
-    srcs: ["src/lib.rs"],
-    no_stdlibs: true,
-    prefer_rlib: true,
-    stdlibs: [
-        "libcore.rust_sysroot",
-    ],
-    rustlibs: [
-        "libavb_bindgen",
-        "liblog_rust_nostd",
-    ],
-}
diff --git a/libs/avb/src/ops.rs b/libs/avb/src/ops.rs
deleted file mode 100644
index 429c980..0000000
--- a/libs/avb/src/ops.rs
+++ /dev/null
@@ -1,151 +0,0 @@
-// Copyright 2022, The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-//! This module regroups methods related to AvbOps.
-
-#![warn(unsafe_op_in_unsafe_fn)]
-// TODO(b/256148034): Remove this when the feature is code complete.
-#![allow(dead_code)]
-#![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 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
-    InvalidArgument,
-    /// AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA
-    InvalidMetadata,
-    /// AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_IO
-    Io,
-    /// AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_OOM
-    Oom,
-    /// AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED
-    PublicKeyRejected,
-    /// AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX
-    RollbackIndex,
-    /// AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_UNSUPPORTED_VERSION
-    UnsupportedVersion,
-    /// AvbSlotVerifyResult_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)]
-    match result {
-        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 => {
-            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 => {
-            Err(AvbImageVerifyError::PublicKeyRejected)
-        }
-        AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX => {
-            Err(AvbImageVerifyError::RollbackIndex)
-        }
-        AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_UNSUPPORTED_VERSION => {
-            Err(AvbImageVerifyError::UnsupportedVersion)
-        }
-        AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION => {
-            Err(AvbImageVerifyError::Verification)
-        }
-        _ => Err(AvbImageVerifyError::Unknown(result)),
-    }
-}
-
-impl fmt::Display for AvbImageVerifyError {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            Self::InvalidArgument => write!(f, "Invalid parameters."),
-            Self::InvalidMetadata => write!(f, "Invalid metadata."),
-            Self::Io => write!(f, "I/O error while trying to load data or get a rollback index."),
-            Self::Oom => write!(f, "Unable to allocate memory."),
-            Self::PublicKeyRejected => write!(
-                f,
-                "Everything is verified correctly out but the public key is not accepted. \
-                This includes the case where integrity data is not signed."
-            ),
-            Self::RollbackIndex => write!(f, "Rollback index is less than its stored value."),
-            Self::UnsupportedVersion => write!(
-                f,
-                "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}'"),
-        }
-    }
-}
-
-/// Verifies that for the given image:
-///  - The given public key is acceptable.
-///  - 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)
-}
-
-/// TODO(b/256148034): Make AvbOps a rust wrapper of avb_bindgen::AvbOps using foreign_types.
-struct AvbOps {}
-
-impl AvbOps {
-    fn new() -> Self {
-        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()
-        );
-        // 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,
-        //         &image.as_ptr(),
-        //     )
-        // };
-        let result = AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_OK;
-        to_avb_verify_result(result)
-    }
-}
diff --git a/libs/capabilities/Android.bp b/libs/capabilities/Android.bp
new file mode 100644
index 0000000..db3f4d4
--- /dev/null
+++ b/libs/capabilities/Android.bp
@@ -0,0 +1,62 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_bindgen {
+    name: "libcap_bindgen",
+    edition: "2021",
+    wrapper_src: "bindgen/libcap.h",
+    crate_name: "cap_bindgen",
+    source_stem: "bindings",
+    shared_libs: ["libcap"],
+    bindgen_flags: [
+        "--default-enum-style rust",
+    ],
+    visibility: [
+        "//packages/modules/Virtualization:__subpackages__",
+    ],
+}
+
+rust_test {
+    name: "libcap_bindgen_test",
+    srcs: [":libcap_bindgen"],
+    crate_name: "cap_bindgen_test",
+    test_suites: ["general-tests"],
+    auto_gen_config: true,
+    clippy_lints: "none",
+    lints: "none",
+}
+
+rust_defaults {
+    name: "libcap_rust.defaults",
+    crate_name: "cap",
+    srcs: ["src/caps.rs"],
+    rustlibs: [
+        "libanyhow",
+        "libcap_bindgen",
+        "liblibc",
+        "libnix",
+        "libscopeguard",
+    ],
+    edition: "2021",
+    prefer_rlib: true,
+    multilib: {
+        lib32: {
+            enabled: false,
+        },
+    },
+    shared_libs: [
+        "libcap",
+    ],
+}
+
+rust_library {
+    name: "libcap_rust",
+    defaults: ["libcap_rust.defaults"],
+}
+
+rust_test {
+    name: "libcap_rust.test",
+    defaults: ["libcap_rust.defaults"],
+    test_suites: ["general-tests"],
+}
diff --git a/libs/capabilities/bindgen/libcap.h b/libs/capabilities/bindgen/libcap.h
new file mode 100644
index 0000000..cbb90dc
--- /dev/null
+++ b/libs/capabilities/bindgen/libcap.h
@@ -0,0 +1,3 @@
+#pragma once
+
+#include <sys/capability.h>
diff --git a/libs/capabilities/src/caps.rs b/libs/capabilities/src/caps.rs
new file mode 100644
index 0000000..1f44a34
--- /dev/null
+++ b/libs/capabilities/src/caps.rs
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! A rust library wrapping the libcap functionality.
+
+use anyhow::{bail, Result};
+use cap_bindgen::{
+    cap_clear_flag, cap_drop_bound, cap_flag_t, cap_free, cap_get_proc, cap_set_proc, cap_value_t,
+    CAP_LAST_CAP,
+};
+use nix::errno::Errno;
+
+/// Removes inheritable capabilities set for this process.
+/// See: https://man7.org/linux/man-pages/man7/capabilities.7.html
+pub fn drop_inheritable_caps() -> Result<()> {
+    unsafe {
+        // SAFETY: we do not manipulate memory handled by libcap.
+        let caps = cap_get_proc();
+        scopeguard::defer! {
+            cap_free(caps as *mut std::os::raw::c_void);
+        }
+        if cap_clear_flag(caps, cap_flag_t::CAP_INHERITABLE) < 0 {
+            let e = Errno::last();
+            bail!("cap_clear_flag failed: {:?}", e)
+        }
+        if cap_set_proc(caps) < 0 {
+            let e = Errno::last();
+            bail!("cap_set_proc failed: {:?}", e)
+        }
+    }
+    Ok(())
+}
+
+/// Drop bounding set capabitilies for this process.
+/// See: https://man7.org/linux/man-pages/man7/capabilities.7.html
+pub fn drop_bounding_set() -> Result<()> {
+    let mut cap_id: cap_value_t = 0;
+    while cap_id <= CAP_LAST_CAP.try_into().unwrap() {
+        unsafe {
+            // SAFETY: we do not manipulate memory handled by libcap.
+            if cap_drop_bound(cap_id) == -1 {
+                let e = Errno::last();
+                bail!("cap_drop_bound failed for {}: {:?}", cap_id, e);
+            }
+        }
+        cap_id += 1;
+    }
+    Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    // Basic test to verify that calling drop_inheritable_caps doesn't fail
+    #[test]
+    fn test_drop_inheritable_caps() {
+        let result = drop_inheritable_caps();
+        assert!(result.is_ok(), "failed with: {:?}", 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..ecaadf8 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -46,6 +46,7 @@
     use_avb: true,
     avb_private_key: ":microdroid_sign_key",
     avb_algorithm: "SHA256_RSA4096",
+    avb_hash_algorithm: "sha256",
     partition_name: "system",
     deps: [
         "init_second_stage",
@@ -71,7 +72,6 @@
         "atrace",
         "debuggerd",
         "linker",
-        "linkerconfig",
         "tombstoned.microdroid",
         "tombstone_transmit.microdroid",
         "cgroups.json",
@@ -83,13 +83,15 @@
         "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)
         "liblzma", // used by init_second_stage
 
         "libvm_payload", // used by payload to interact with microdroid manager
+
+        "prng_seeder_microdroid",
     ] + microdroid_shell_and_utilities,
     multilib: {
         common: {
@@ -214,6 +216,7 @@
     },
     avb_private_key: ":microdroid_sign_key",
     avb_algorithm: "SHA256_RSA4096",
+    avb_hash_algorithm: "sha256",
     file_contexts: ":microdroid_vendor_file_contexts.gen",
     // For deterministic output, use fake_timestamp, hard-coded uuid
     fake_timestamp: "1611569676",
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/kernel/README.md b/microdroid/kernel/README.md
index 12d8770..e2ac617 100644
--- a/microdroid/kernel/README.md
+++ b/microdroid/kernel/README.md
@@ -60,7 +60,7 @@
 
 For x86\_64,
 ```bash
-cp out/dist/bzImage <android_checkout>/packages/modules/Virtualization/microdroid/kernel/arm64/kernel-5.15
+cp out/dist/bzImage <android_checkout>/packages/modules/Virtualization/microdroid/kernel/x86_64/kernel-5.15
 ```
 
 ### For official updates
diff --git a/microdroid/ueventd.rc b/microdroid/ueventd.rc
index 268d3a2..0c5fbfc 100644
--- a/microdroid/ueventd.rc
+++ b/microdroid/ueventd.rc
@@ -29,3 +29,6 @@
 /dev/hvc2                 0666   system     system
 
 /dev/open-dice0           0660   root       root
+
+# Aside from kernel threads, only prng_seeder needs access to HW RNG
+/dev/hw_random            0400   prng_seeder prng_seeder
diff --git a/microdroid_manager/Android.bp b/microdroid_manager/Android.bp
index 44b4c01..383f371 100644
--- a/microdroid_manager/Android.bp
+++ b/microdroid_manager/Android.bp
@@ -18,6 +18,7 @@
         "libapkverify",
         "libbinder_rs",
         "libbyteorder",
+        "libcap_rust",
         "libdiced",
         "libdiced_open_dice_cbor",
         "libdiced_sample_inputs",
diff --git a/microdroid_manager/microdroid_manager.rc b/microdroid_manager/microdroid_manager.rc
index c41ee38..97d14b5 100644
--- a/microdroid_manager/microdroid_manager.rc
+++ b/microdroid_manager/microdroid_manager.rc
@@ -7,6 +7,8 @@
     setenv RUST_LOG info
     # TODO(jooyung) remove this when microdroid_manager becomes a daemon
     oneshot
-    # SYS_BOOT is required to exec kexecload from microdroid_manager
-    capabilities AUDIT_CONTROL SYS_ADMIN SYS_BOOT
+    # CAP_SYS_BOOT is required to exec kexecload from microdroid_manager
+    # CAP_SETCAP is required to allow microdroid_manager to drop capabilities
+    #   before executing the payload
+    capabilities AUDIT_CONTROL SYS_ADMIN SYS_BOOT SETPCAP
     socket vm_payload_service stream 0666 system system
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 3c490f4..4018d00 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -46,7 +46,7 @@
 use openssl::sha::Sha512;
 use payload::{get_apex_data_from_payload, load_metadata, to_metadata};
 use rand::Fill;
-use rpcbinder::get_vsock_rpc_interface;
+use rpcbinder::RpcSession;
 use rustutils::sockets::android_get_control_socket;
 use rustutils::system_properties;
 use rustutils::system_properties::PropertyWatcher;
@@ -55,6 +55,7 @@
 use std::env;
 use std::fs::{self, create_dir, OpenOptions};
 use std::io::Write;
+use std::os::unix::process::CommandExt;
 use std::os::unix::process::ExitStatusExt;
 use std::path::Path;
 use std::process::{Child, Command, Stdio};
@@ -77,7 +78,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";
@@ -161,7 +161,8 @@
     // The host is running a VirtualMachineService for this VM on a port equal
     // to the CID of this VM.
     let port = vsock::get_local_cid().context("Could not determine local CID")?;
-    get_vsock_rpc_interface(VMADDR_CID_HOST, port)
+    RpcSession::new()
+        .setup_vsock_client(VMADDR_CID_HOST, port)
         .context("Could not connect to IVirtualMachineService")
 }
 
@@ -383,15 +384,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 +416,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 +430,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 +482,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 +685,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(())
@@ -777,6 +797,24 @@
             command
         }
     };
+
+    unsafe {
+        // SAFETY: we are not accessing any resource of the parent process.
+        command.pre_exec(|| {
+            info!("dropping capabilities before executing payload");
+            // It is OK to continue with payload execution even if the calls below fail, since
+            // whether process can use a capability is controlled by the SELinux. Dropping the
+            // capabilities here is just another defense-in-depth layer.
+            if let Err(e) = cap::drop_inheritable_caps() {
+                error!("failed to drop inheritable capabilities: {:?}", e);
+            }
+            if let Err(e) = cap::drop_bounding_set() {
+                error!("failed to drop bounding set: {:?}", e);
+            }
+            Ok(())
+        });
+    }
+
     command.stdin(Stdio::null()).stdout(Stdio::null()).stderr(Stdio::null());
 
     info!("notifying payload started");
diff --git a/pvmfw/Android.bp b/pvmfw/Android.bp
index 4218fae..2912c91 100644
--- a/pvmfw/Android.bp
+++ b/pvmfw/Android.bp
@@ -13,17 +13,17 @@
     ],
     rustlibs: [
         "libaarch64_paging",
-        "libavb_nostd",
         "libbuddy_system_allocator",
         "libdice_nostd",
         "liblibfdt",
         "liblog_rust_nostd",
+        "libpvmfw_avb_nostd",
         "libpvmfw_embedded_key",
         "libtinyvec_nostd",
         "libvirtio_drivers",
         "libvmbase",
+        "libzeroize_nostd",
     ],
-    apex_available: ["com.android.virt"],
 }
 
 cc_binary {
@@ -39,7 +39,15 @@
         "image.ld",
         ":vmbase_sections",
     ],
-    apex_available: ["com.android.virt"],
+    // `installable: false` is inherited from vmbase_elf_defaults, and that
+    // hides this module from Make, which makes it impossible for the Make world
+    // to place the unstripped binary to the symbols directory. Marking back as
+    // installable exposes this module to the Make world again. Note that this
+    // module (pvmfw) still is NOT installed to any of the filesystem images. It
+    // is fed into pvmfw_bin and then into pvmfw_img to become a standalone
+    // partition image. This is just to package the unstripped file into the
+    // symbols zip file for debugging purpose.
+    installable: true,
 }
 
 raw_binary {
diff --git a/pvmfw/README.md b/pvmfw/README.md
new file mode 100644
index 0000000..e5ba88b
--- /dev/null
+++ b/pvmfw/README.md
@@ -0,0 +1,72 @@
+# Protected Virtual Machine Firmware
+
+## Configuration Data Format
+
+pvmfw will expect a [header] to have been appended to its loaded binary image
+at the next 4KiB boundary. It describes the configuration data entries that
+pvmfw will use and, being loaded by the pvmfw loader, is necessarily trusted.
+
+The layout of the configuration data is as follows:
+
+```
++===============================+
+|          pvmfw.bin            |
++~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+
+|  (Padding to 4KiB alignment)  |
++===============================+ <-- HEAD
+|      Magic (= 0x666d7670)     |
++-------------------------------+
+|           Version             |
++-------------------------------+
+|   Total Size = (TAIL - HEAD)  |
++-------------------------------+
+|            Flags              |
++-------------------------------+
+|           [Entry 0]           |
+|  offset = (FIRST - HEAD)      |
+|  size = (FIRST_END - FIRST)   |
++-------------------------------+
+|           [Entry 1]           |
+|  offset = (SECOND - HEAD)     |
+|  size = (SECOND_END - SECOND) |
++-------------------------------+
+|              ...              |
++-------------------------------+
+|           [Entry n]           |
++~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+
+| (Padding to 8-byte alignment) |
++===============================+ <-- FIRST
+|        {First blob: BCC}      |
++~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ <-- FIRST_END
+| (Padding to 8-byte alignment) |
++===============================+ <-- SECOND
+|        {Second blob: DP}      |
++~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ <-- SECOND_END
+| (Padding to 8-byte alignment) |
++===============================+
+|              ...              |
++===============================+ <-- TAIL
+```
+
+Where the version number is encoded using a "`major.minor`" as follows
+
+```
+((major << 16) | (minor & 0xffff))
+```
+
+and defines the format of the header (which may change between major versions),
+its size and, in particular, the expected number of appended blobs. Each blob is
+referred to by its offset in the entry array and may be mandatory or optional
+(as defined by this specification), where missing entries are denoted by a zero
+size. It is therefore not allowed to trim missing optional entries from the end
+of the array. The header uses the endianness of the virtual machine.
+
+The header format itself is agnostic of the internal format of the individual
+blos it refers to. In version 1.0, it describes two blobs:
+
+- entry 0 must point to a valid [BCC Handover]
+- entry 1 may point to a [DTBO] to be applied to the pVM device tree
+
+[header]: src/config.rs
+[BCC Handover]: https://pigweed.googlesource.com/open-dice/+/825e3beb6c6efcd8c35506d818c18d1e73b9834a/src/android/bcc.c#260
+[DTBO]: https://android.googlesource.com/platform/external/dtc/+/refs/heads/master/Documentation/dt-object-internal.txt
diff --git a/pvmfw/TEST_MAPPING b/pvmfw/TEST_MAPPING
new file mode 100644
index 0000000..8a2d352
--- /dev/null
+++ b/pvmfw/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "avf-presubmit" : [
+    {
+      "name" : "libpvmfw_avb.test"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/pvmfw/avb/Android.bp b/pvmfw/avb/Android.bp
new file mode 100644
index 0000000..65259a5
--- /dev/null
+++ b/pvmfw/avb/Android.bp
@@ -0,0 +1,28 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+    name: "libpvmfw_avb_nostd_defaults",
+    crate_name: "pvmfw_avb",
+    srcs: ["src/lib.rs"],
+    prefer_rlib: true,
+    rustlibs: [
+        "libavb_bindgen",
+    ],
+}
+
+rust_library_rlib {
+    name: "libpvmfw_avb_nostd",
+    defaults: ["libpvmfw_avb_nostd_defaults"],
+    no_stdlibs: true,
+    stdlibs: [
+        "libcore.rust_sysroot",
+    ],
+}
+
+rust_test {
+    name: "libpvmfw_avb.test",
+    defaults: ["libpvmfw_avb_nostd_defaults"],
+    test_suites: ["general-tests"],
+}
diff --git a/libs/avb/src/lib.rs b/pvmfw/avb/src/lib.rs
similarity index 77%
rename from libs/avb/src/lib.rs
rename to pvmfw/avb/src/lib.rs
index 21b7d2a..eb1f918 100644
--- a/libs/avb/src/lib.rs
+++ b/pvmfw/avb/src/lib.rs
@@ -12,12 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-//! This module regroups the rust API for libavb.
+//! A library implementing the payload verification for pvmfw with libavb
 
-#![no_std]
+#![cfg_attr(not(test), no_std)]
 
-extern crate alloc;
+mod verify;
 
-mod ops;
-
-pub use ops::{verify_image, AvbImageVerifyError};
+pub use verify::{verify_payload, AvbImageVerifyError};
diff --git a/pvmfw/avb/src/verify.rs b/pvmfw/avb/src/verify.rs
new file mode 100644
index 0000000..7f3ba3d
--- /dev/null
+++ b/pvmfw/avb/src/verify.rs
@@ -0,0 +1,112 @@
+// Copyright 2022, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! This module handles the pvmfw payload verification.
+
+use avb_bindgen::AvbSlotVerifyResult;
+use core::fmt;
+
+/// Error code from AVB image verification.
+#[derive(Clone, Debug)]
+pub enum AvbImageVerifyError {
+    /// AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_ARGUMENT
+    InvalidArgument,
+    /// AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA
+    InvalidMetadata,
+    /// AVB_SLOT_VERIFY_RESULT_ERROR_IO
+    Io,
+    /// AVB_SLOT_VERIFY_RESULT_ERROR_OOM
+    Oom,
+    /// AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED
+    PublicKeyRejected,
+    /// AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX
+    RollbackIndex,
+    /// AVB_SLOT_VERIFY_RESULT_ERROR_UNSUPPORTED_VERSION
+    UnsupportedVersion,
+    /// AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION
+    Verification,
+}
+
+impl fmt::Display for AvbImageVerifyError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            Self::InvalidArgument => write!(f, "Invalid parameters."),
+            Self::InvalidMetadata => write!(f, "Invalid metadata."),
+            Self::Io => write!(f, "I/O error while trying to load data or get a rollback index."),
+            Self::Oom => write!(f, "Unable to allocate memory."),
+            Self::PublicKeyRejected => write!(f, "Public key rejected or data not signed."),
+            Self::RollbackIndex => write!(f, "Rollback index is less than its stored value."),
+            Self::UnsupportedVersion => write!(
+                f,
+                "Some of the metadata requires a newer version of libavb than what is in use."
+            ),
+            Self::Verification => write!(f, "Data does not verify."),
+        }
+    }
+}
+
+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 => {
+            Err(AvbImageVerifyError::InvalidArgument)
+        }
+        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 => {
+            Err(AvbImageVerifyError::PublicKeyRejected)
+        }
+        AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX => {
+            Err(AvbImageVerifyError::RollbackIndex)
+        }
+        AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_ERROR_UNSUPPORTED_VERSION => {
+            Err(AvbImageVerifyError::UnsupportedVersion)
+        }
+        AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION => {
+            Err(AvbImageVerifyError::Verification)
+        }
+    }
+}
+
+/// Verifies the payload (signed kernel + initrd) against the trusted public key.
+pub fn verify_payload(_public_key: &[u8]) -> Result<(), AvbImageVerifyError> {
+    // TODO(b/256148034): Verify the kernel image with avb_slot_verify()
+    // let result = unsafe {
+    //     avb_slot_verify(
+    //         &mut avb_ops,
+    //         requested_partitions.as_ptr(),
+    //         ab_suffix.as_ptr(),
+    //         flags,
+    //         hashtree_error_mode,
+    //         null_mut(),
+    //     )
+    // };
+    let result = AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_OK;
+    to_avb_verify_result(result)
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    // TODO(b/256148034): Test verification succeeds with valid payload later.
+    #[test]
+    fn verification_succeeds_with_placeholder_input() {
+        let fake_public_key = [0u8; 2];
+        assert!(verify_payload(&fake_public_key).is_ok());
+    }
+}
diff --git a/pvmfw/src/config.rs b/pvmfw/src/config.rs
index 0f2a39c..f209784 100644
--- a/pvmfw/src/config.rs
+++ b/pvmfw/src/config.rs
@@ -17,17 +17,22 @@
 use crate::helpers;
 use core::fmt;
 use core::mem;
-use core::num::NonZeroUsize;
-use core::ops;
+use core::ops::Range;
 use core::result;
 
+/// Configuration data header.
 #[repr(C, packed)]
 #[derive(Clone, Copy, Debug)]
 struct Header {
+    /// Magic number; must be `Header::MAGIC`.
     magic: u32,
+    /// Version of the header format.
     version: u32,
+    /// Total size of the configuration data.
     total_size: u32,
+    /// Feature flags; currently reserved and must be zero.
     flags: u32,
+    /// (offset, size) pairs used to locate individual entries appended to the header.
     entries: [HeaderEntry; Entry::COUNT],
 }
 
@@ -43,8 +48,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,15 +62,41 @@
             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 VERSION_1_0: u32 = Self::version(1, 0);
     const PADDED_SIZE: usize =
         helpers::unchecked_align_up(mem::size_of::<Self>(), mem::size_of::<u64>());
 
@@ -83,8 +116,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 +173,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> {
@@ -152,7 +193,7 @@
             return Err(Error::InvalidMagic);
         }
 
-        if header.version != Header::version(1, 0) {
+        if header.version != Header::VERSION_1_0 {
             let (major, minor) = header.version_tuple();
             return Err(Error::UnsupportedVersion(major, minor));
         }
@@ -161,40 +202,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 d307759..1b35c79 100644
--- a/pvmfw/src/entry.rs
+++ b/pvmfw/src/entry.rs
@@ -247,7 +247,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 6c35d91..e8a20a8 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;
@@ -86,3 +87,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/pvmfw/src/main.rs b/pvmfw/src/main.rs
index 9c5fb60..7d64bf0 100644
--- a/pvmfw/src/main.rs
+++ b/pvmfw/src/main.rs
@@ -39,10 +39,10 @@
     memory::MemoryTracker,
     pci::{find_virtio_devices, PciError, PciInfo},
 };
-use ::avb::verify_image;
 use dice::bcc;
 use libfdt::Fdt;
 use log::{debug, error, info, trace};
+use pvmfw_avb::verify_payload;
 
 fn main(
     fdt: &Fdt,
@@ -70,7 +70,7 @@
     let mut pci_root = unsafe { pci_info.make_pci_root() };
     find_virtio_devices(&mut pci_root).map_err(handle_pci_error)?;
 
-    verify_image(signed_kernel, PUBLIC_KEY).map_err(|e| {
+    verify_payload(PUBLIC_KEY).map_err(|e| {
         error!("Failed to verify the payload: {e}");
         RebootReason::PayloadVerificationError
     })?;
diff --git a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
index 077c74f..c936e1b 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);
@@ -46,4 +46,7 @@
      * each line reverse.
      */
     void runEchoReverseServer();
+
+    /** Returns a mask of effective capabilities that the process running the payload binary has. */
+    String[] getEffectiveCapabilities();
 }
diff --git a/tests/benchmark/src/jni/io_vsock_host_jni.cpp b/tests/benchmark/src/jni/io_vsock_host_jni.cpp
index dd32e29..47d5325 100644
--- a/tests/benchmark/src/jni/io_vsock_host_jni.cpp
+++ b/tests/benchmark/src/jni/io_vsock_host_jni.cpp
@@ -20,6 +20,7 @@
 #include <jni.h>
 #include <time.h>
 
+using android::base::ErrnoError;
 using android::base::Error;
 using android::base::Result;
 using android::base::WriteStringToFd;
@@ -29,12 +30,18 @@
 Result<double> measure_send_rate(int fd, int num_bytes_to_send) {
     std::string data;
     data.assign(num_bytes_to_send, 'a');
-    clock_t start = clock();
+    struct timespec start;
+    if (clock_gettime(CLOCK_MONOTONIC, &start) == -1) {
+        return ErrnoError() << "failed to clock_gettime";
+    }
     if (!WriteStringToFd(data, fd)) {
         return Error() << "Cannot send data to client";
     }
-    clock_t end = clock();
-    double elapsed_seconds = (double)(end - start) / CLOCKS_PER_SEC;
+    struct timespec finish;
+    if (clock_gettime(CLOCK_MONOTONIC, &finish) == -1) {
+        return ErrnoError() << "failed to clock_gettime";
+    }
+    double elapsed_seconds = finish.tv_sec - start.tv_sec + (finish.tv_nsec - start.tv_nsec) / 1e9;
     LOG(INFO) << "Host:Finished sending data in " << elapsed_seconds << " seconds.";
     double send_rate = (double)num_bytes_to_send / kNumBytesPerMB / elapsed_seconds;
     return {send_rate};
diff --git a/tests/benchmark/src/native/benchmarkbinary.cpp b/tests/benchmark/src/native/benchmarkbinary.cpp
index 56963e6..6cfc71d 100644
--- a/tests/benchmark/src/native/benchmarkbinary.cpp
+++ b/tests/benchmark/src/native/benchmarkbinary.cpp
@@ -118,7 +118,10 @@
         }
         char buf[kBlockSizeBytes];
 
-        clock_t start = clock();
+        struct timespec start;
+        if (clock_gettime(CLOCK_MONOTONIC, &start) == -1) {
+            return ErrnoError() << "failed to clock_gettime";
+        }
         unique_fd fd(open(filename.c_str(), O_RDONLY | O_CLOEXEC));
         if (fd.get() == -1) {
             return ErrnoError() << "Read: opening " << filename << " failed";
@@ -131,7 +134,12 @@
                 return ErrnoError() << "failed to read";
             }
         }
-        double elapsed_seconds = ((double)clock() - start) / CLOCKS_PER_SEC;
+        struct timespec finish;
+        if (clock_gettime(CLOCK_MONOTONIC, &finish) == -1) {
+            return ErrnoError() << "failed to clock_gettime";
+        }
+        double elapsed_seconds =
+                finish.tv_sec - start.tv_sec + (finish.tv_nsec - start.tv_nsec) / 1e9;
         double file_size_mb = (double)file_size_bytes / kNumBytesPerMB;
         return {file_size_mb / elapsed_seconds};
     }
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/Android.bp b/tests/testapk/Android.bp
index edb4759..e3c9961 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -40,6 +40,7 @@
     header_libs: ["vm_payload_restricted_headers"],
     shared_libs: [
         "libbinder_ndk",
+        "libcap",
         "MicrodroidTestNativeLibSub",
         "libvm_payload#current",
     ],
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..160b679 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,55 @@
     }
 
     @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.
+        assertThrowsVmException(() -> getVirtualMachineManager().delete("test_vm"));
+    }
+
+    @Test
     @CddTest(requirements = {"9.17/C-1-1"})
     public void connectVsock() throws Exception {
         assumeSupportedKernel();
@@ -264,8 +314,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 +325,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 +335,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 +346,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 +355,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
@@ -325,6 +416,54 @@
 
     @Test
     @CddTest(requirements = {"9.17/C-1-1"})
+    public void vmmGetAndCreate() throws Exception {
+        assumeSupportedKernel();
+
+        VirtualMachineConfig config =
+                newVmConfigBuilder()
+                        .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+                        .setMemoryMib(minMemoryRequired())
+                        .setDebugLevel(DEBUG_LEVEL_FULL)
+                        .build();
+
+        VirtualMachineManager vmm = getVirtualMachineManager();
+        String vmName = "vmName";
+
+        try {
+            // VM does not yet exist
+            assertThat(vmm.get(vmName)).isNull();
+
+            VirtualMachine vm1 = vmm.create(vmName, config);
+
+            // Now it does, and we should get the same instance back
+            assertThat(vmm.get(vmName)).isSameInstanceAs(vm1);
+            assertThat(vmm.getOrCreate(vmName, config)).isSameInstanceAs(vm1);
+
+            // Can't recreate it though
+            assertThrowsVmException(() -> vmm.create(vmName, config));
+
+            vmm.delete(vmName);
+            assertThat(vmm.get(vmName)).isNull();
+
+            // Now that we deleted the old one, this should create rather than get, and it should be
+            // a new instance.
+            VirtualMachine vm2 = vmm.getOrCreate(vmName, config);
+            assertThat(vm2).isNotSameInstanceAs(vm1);
+
+            // The old one must remain deleted, or we'd have two VirtualMachine instances referring
+            // to the same VM.
+            assertThat(vm1.getStatus()).isEqualTo(STATUS_DELETED);
+
+            // Subsequent gets should return this new one.
+            assertThat(vmm.get(vmName)).isSameInstanceAs(vm2);
+            assertThat(vmm.getOrCreate(vmName, config)).isSameInstanceAs(vm2);
+        } finally {
+            vmm.delete(vmName);
+        }
+    }
+
+    @Test
+    @CddTest(requirements = {"9.17/C-1-1"})
     public void vmFilesStoredInDeDirWhenCreatedFromDEContext() throws Exception {
         final Context ctx = getContext().createDeviceProtectedStorageContext();
         final int userId = ctx.getUserId();
@@ -413,10 +552,10 @@
         assertThat(vmm.get("test_vm_delete")).isNull();
 
         // Can't start the VM even with an existing reference
-        assertThrows(VirtualMachineException.class, vm::run);
+        assertThrowsVmException(vm::run);
 
         // Can't delete the VM since it no longer exists
-        assertThrows(VirtualMachineException.class, () -> vmm.delete("test_vm_delete"));
+        assertThrowsVmException(() -> vmm.delete("test_vm_delete"));
     }
 
     @Test
@@ -441,20 +580,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 +1052,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 +1093,53 @@
         // 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");
+    }
+
+    @Test
+    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+    public void microdroidLauncherHasEmptyCapabilities() throws Exception {
+        assumeSupportedKernel();
+
+        final VirtualMachineConfig vmConfig =
+                newVmConfigBuilder()
+                        .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+                        .setMemoryMib(minMemoryRequired())
+                        .setDebugLevel(DEBUG_LEVEL_FULL)
+                        .build();
+        final VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_caps", vmConfig);
+
+        final TestResults testResults = runVmTestService(vm);
+
+        assertThat(testResults.mException).isNull();
+        assertThat(testResults.mEffectiveCapabilities).isEmpty();
     }
 
     private void assertFileContentsAreEqualInTwoVms(String fileName, String vmName1, String vmName2)
@@ -977,6 +1158,16 @@
         return filePath.toFile();
     }
 
+    private void assertThrowsVmException(ThrowingRunnable runnable) {
+        assertThrows(VirtualMachineException.class, runnable);
+    }
+
+    private void assertThrowsVmExceptionContaining(
+            ThrowingRunnable runnable, String expectedContents) {
+        Exception e = assertThrows(VirtualMachineException.class, runnable);
+        assertThat(e).hasMessageThat().contains(expectedContents);
+    }
+
     private int minMemoryRequired() {
         if (Build.SUPPORTED_ABIS.length > 0) {
             String primaryAbi = Build.SUPPORTED_ABIS[0];
@@ -1005,6 +1196,7 @@
         String mExtraApkTestProp;
         String mApkContentsPath;
         String mEncryptedStoragePath;
+        String[] mEffectiveCapabilities;
     }
 
     private TestResults runVmTestService(VirtualMachine vm) throws Exception {
@@ -1028,6 +1220,8 @@
                             testResults.mApkContentsPath = testService.getApkContentsPath();
                             testResults.mEncryptedStoragePath =
                                     testService.getEncryptedStoragePath();
+                            testResults.mEffectiveCapabilities =
+                                    testService.getEffectiveCapabilities();
                         } catch (Exception e) {
                             testResults.mException = e;
                         }
diff --git a/tests/testapk/src/native/testbinary.cpp b/tests/testapk/src/native/testbinary.cpp
index 8a0019d..da408e4 100644
--- a/tests/testapk/src/native/testbinary.cpp
+++ b/tests/testapk/src/native/testbinary.cpp
@@ -18,12 +18,14 @@
 #include <android-base/file.h>
 #include <android-base/properties.h>
 #include <android-base/result.h>
+#include <android-base/scopeguard.h>
 #include <android/log.h>
 #include <fcntl.h>
 #include <fsverity_digests.pb.h>
 #include <linux/vm_sockets.h>
 #include <stdint.h>
 #include <stdio.h>
+#include <sys/capability.h>
 #include <sys/system_properties.h>
 #include <unistd.h>
 #include <vm_main.h>
@@ -35,6 +37,7 @@
 using android::base::borrowed_fd;
 using android::base::ErrnoError;
 using android::base::Error;
+using android::base::make_scope_guard;
 using android::base::Result;
 using android::base::unique_fd;
 
@@ -112,7 +115,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)));
@@ -197,6 +200,29 @@
             return ScopedAStatus::ok();
         }
 
+        ScopedAStatus getEffectiveCapabilities(std::vector<std::string>* out) override {
+            if (out == nullptr) {
+                return ScopedAStatus::ok();
+            }
+            cap_t cap = cap_get_proc();
+            auto guard = make_scope_guard([&cap]() { cap_free(cap); });
+            for (cap_value_t cap_id = 0; cap_id < CAP_LAST_CAP + 1; cap_id++) {
+                cap_flag_value_t value;
+                if (cap_get_flag(cap, cap_id, CAP_EFFECTIVE, &value) != 0) {
+                    return ScopedAStatus::
+                            fromServiceSpecificErrorWithMessage(0, "cap_get_flag failed");
+                }
+                if (value == CAP_SET) {
+                    // Ideally we would just send back the cap_ids, but I wasn't able to find java
+                    // APIs for linux capabilities, hence we transform to the human readable name
+                    // here.
+                    char* name = cap_to_name(cap_id);
+                    out->push_back(std::string(name) + "(" + std::to_string(cap_id) + ")");
+                }
+            }
+            return ScopedAStatus::ok();
+        }
+
         virtual ::ScopedAStatus runEchoReverseServer() override {
             auto result = start_echo_reverse_server();
             if (result.ok()) {
diff --git a/virtualizationservice/Android.bp b/virtualizationservice/Android.bp
index da56f76..d0dde42 100644
--- a/virtualizationservice/Android.bp
+++ b/virtualizationservice/Android.bp
@@ -5,7 +5,6 @@
 rust_defaults {
     name: "virtualizationservice_defaults",
     crate_name: "virtualizationservice",
-    srcs: ["src/main.rs"],
     edition: "2021",
     // Only build on targets which crosvm builds on.
     enabled: false,
@@ -67,11 +66,23 @@
 rust_binary {
     name: "virtualizationservice",
     defaults: ["virtualizationservice_defaults"],
+    srcs: ["src/main.rs"],
+    apex_available: ["com.android.virt"],
+}
+
+rust_binary {
+    name: "virtmgr",
+    defaults: ["virtualizationservice_defaults"],
+    srcs: ["src/virtmgr.rs"],
+    rustlibs: [
+        "libclap",
+    ],
     apex_available: ["com.android.virt"],
 }
 
 rust_test {
     name: "virtualizationservice_device_test",
+    srcs: ["src/main.rs"],
     defaults: ["virtualizationservice_defaults"],
     rustlibs: [
         "libtempfile",
diff --git a/virtualizationservice/aidl/Android.bp b/virtualizationservice/aidl/Android.bp
index a0bbc00..f028c0f 100644
--- a/virtualizationservice/aidl/Android.bp
+++ b/virtualizationservice/aidl/Android.bp
@@ -36,6 +36,7 @@
 aidl_interface {
     name: "android.system.virtualizationservice_internal",
     srcs: ["android/system/virtualizationservice_internal/**/*.aidl"],
+    imports: ["android.system.virtualizationcommon"],
     unstable: true,
     backend: {
         java: {
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/DeathReason.aidl b/virtualizationservice/aidl/android/system/virtualizationcommon/DeathReason.aidl
similarity index 97%
rename from virtualizationservice/aidl/android/system/virtualizationservice/DeathReason.aidl
rename to virtualizationservice/aidl/android/system/virtualizationcommon/DeathReason.aidl
index 0980630..3f47002 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/DeathReason.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationcommon/DeathReason.aidl
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.system.virtualizationservice;
+package android.system.virtualizationcommon;
 
 /**
  * The reason why a VM died.
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl
index a329fa6..40e4c58 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl
@@ -15,8 +15,8 @@
  */
 package android.system.virtualizationservice;
 
+import android.system.virtualizationcommon.DeathReason;
 import android.system.virtualizationcommon.ErrorCode;
-import android.system.virtualizationservice.DeathReason;
 
 /**
  * An object which a client may register with the VirtualizationService to get callbacks about the
@@ -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/aidl/android/system/virtualizationservice_internal/AtomVmBooted.aidl b/virtualizationservice/aidl/android/system/virtualizationservice_internal/AtomVmBooted.aidl
new file mode 100644
index 0000000..2d9e9bf
--- /dev/null
+++ b/virtualizationservice/aidl/android/system/virtualizationservice_internal/AtomVmBooted.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.system.virtualizationservice_internal;
+
+parcelable AtomVmBooted {
+    int uid;
+    @utf8InCpp String vmIdentifier;
+    long elapsedTimeMillis;
+}
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice_internal/AtomVmCreationRequested.aidl b/virtualizationservice/aidl/android/system/virtualizationservice_internal/AtomVmCreationRequested.aidl
new file mode 100644
index 0000000..ec2d206
--- /dev/null
+++ b/virtualizationservice/aidl/android/system/virtualizationservice_internal/AtomVmCreationRequested.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.system.virtualizationservice_internal;
+
+parcelable AtomVmCreationRequested {
+    int uid;
+    @utf8InCpp String vmIdentifier;
+    boolean isProtected;
+    boolean creationSucceeded;
+    int binderExceptionCode;
+    int configType;
+    int numCpus;
+    int memoryMib;
+    @utf8InCpp String apexes;
+}
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice_internal/AtomVmExited.aidl b/virtualizationservice/aidl/android/system/virtualizationservice_internal/AtomVmExited.aidl
new file mode 100644
index 0000000..60b902d
--- /dev/null
+++ b/virtualizationservice/aidl/android/system/virtualizationservice_internal/AtomVmExited.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.system.virtualizationservice_internal;
+
+import android.system.virtualizationcommon.DeathReason;
+
+parcelable AtomVmExited {
+    int uid;
+    String vmIdentifier;
+    DeathReason deathReason;
+    int exitSignal;
+    long elapsedTimeMillis;
+    long guestTimeMillis;
+    long rssVmKb;
+    long rssCrosvmKb;
+}
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
index 851ddf4..ff56b68 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
@@ -15,6 +15,9 @@
  */
 package android.system.virtualizationservice_internal;
 
+import android.system.virtualizationservice_internal.AtomVmBooted;
+import android.system.virtualizationservice_internal.AtomVmCreationRequested;
+import android.system.virtualizationservice_internal.AtomVmExited;
 import android.system.virtualizationservice_internal.IGlobalVmContext;
 
 interface IVirtualizationServiceInternal {
@@ -26,4 +29,13 @@
      * to the returned object.
      */
     IGlobalVmContext allocateGlobalVmContext();
+
+    /** Forwards a VmBooted atom to statsd. */
+    void atomVmBooted(in AtomVmBooted atom);
+
+    /** Forwards a VmCreationRequested atom to statsd. */
+    void atomVmCreationRequested(in AtomVmCreationRequested atom);
+
+    /** Forwards a VmExited atom to statsd. */
+    void atomVmExited(in AtomVmExited atom);
 }
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 7d24a32..d7c7125 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -14,15 +14,20 @@
 
 //! Implementation of the AIDL interface of the VirtualizationService.
 
-use crate::atom::{write_vm_booted_stats, write_vm_creation_stats};
+use crate::{get_calling_pid, get_calling_uid};
+use crate::atom::{
+    forward_vm_booted_atom, forward_vm_creation_atom, forward_vm_exited_atom,
+    write_vm_booted_stats, write_vm_creation_stats};
 use crate::composite::make_composite_image;
 use crate::crosvm::{CrosvmConfig, DiskFile, PayloadState, VmContext, VmInstance, VmState};
 use crate::payload::{add_microdroid_payload_images, add_microdroid_system_images};
 use crate::selinux::{getfilecon, SeContext};
 use android_os_permissions_aidl::aidl::android::os::IPermissionController;
-use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::ErrorCode::ErrorCode;
-use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
+use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::{
     DeathReason::DeathReason,
+    ErrorCode::ErrorCode,
+};
+use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
     DiskImage::DiskImage,
     IVirtualMachine::{BnVirtualMachine, IVirtualMachine},
     IVirtualMachineCallback::IVirtualMachineCallback,
@@ -38,6 +43,9 @@
     VirtualMachineState::VirtualMachineState,
 };
 use android_system_virtualizationservice_internal::aidl::android::system::virtualizationservice_internal::{
+    AtomVmBooted::AtomVmBooted,
+    AtomVmCreationRequested::AtomVmCreationRequested,
+    AtomVmExited::AtomVmExited,
     IGlobalVmContext::{BnGlobalVmContext, IGlobalVmContext},
     IVirtualizationServiceInternal::{BnVirtualizationServiceInternal, IVirtualizationServiceInternal},
 };
@@ -48,9 +56,10 @@
 use apkverify::{HashAlgorithm, V4Signature};
 use binder::{
     self, BinderFeatures, ExceptionCode, Interface, LazyServiceGuard, ParcelFileDescriptor,
-    Status, StatusCode, Strong, ThreadState,
+    Status, StatusCode, Strong,
 };
 use disk::QcowFile;
+use lazy_static::lazy_static;
 use libc::VMADDR_CID_HOST;
 use log::{debug, error, info, warn};
 use microdroid_payload_config::{OsConfig, Task, TaskType, VmPayloadConfig};
@@ -74,8 +83,6 @@
 /// The unique ID of a VM used (together with a port number) for vsock communication.
 pub type Cid = u32;
 
-pub const BINDER_SERVICE_IDENTIFIER: &str = "android.system.virtualizationservice";
-
 /// Directory in which to write disk image files used while running VMs.
 pub const TEMPORARY_DIRECTORY: &str = "/data/misc/virtualizationservice";
 
@@ -102,6 +109,13 @@
 
 const UNFORMATTED_STORAGE_MAGIC: &str = "UNFORMATTED-STORAGE";
 
+lazy_static! {
+    pub static ref GLOBAL_SERVICE: Strong<dyn IVirtualizationServiceInternal> = {
+        let service = VirtualizationServiceInternal::init();
+        BnVirtualizationServiceInternal::new_binder(service, BinderFeatures::default())
+    };
+}
+
 fn is_valid_guest_cid(cid: Cid) -> bool {
     (GUEST_CID_MIN..=GUEST_CID_MAX).contains(&cid)
 }
@@ -115,6 +129,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)]
@@ -146,6 +178,21 @@
         })?;
         Ok(GlobalVmContext::create(cid))
     }
+
+    fn atomVmBooted(&self, atom: &AtomVmBooted) -> Result<(), Status> {
+        forward_vm_booted_atom(atom);
+        Ok(())
+    }
+
+    fn atomVmCreationRequested(&self, atom: &AtomVmCreationRequested) -> Result<(), Status> {
+        forward_vm_creation_atom(atom);
+        Ok(())
+    }
+
+    fn atomVmExited(&self, atom: &AtomVmExited) -> Result<(), Status> {
+        forward_vm_exited_atom(atom);
+        Ok(())
+    }
 }
 
 /// The mutable state of the VirtualizationServiceInternal. There should only be one instance
@@ -239,10 +286,9 @@
 }
 
 /// Implementation of `IVirtualizationService`, the entry point of the AIDL service.
-#[derive(Debug)]
+#[derive(Debug, Default)]
 pub struct VirtualizationService {
     state: Arc<Mutex<State>>,
-    global_service: Strong<dyn IVirtualizationServiceInternal>,
 }
 
 impl Interface for VirtualizationService {
@@ -345,12 +391,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(())
     }
 
@@ -447,25 +489,20 @@
 
 impl VirtualizationService {
     pub fn init() -> VirtualizationService {
-        let global_service = VirtualizationServiceInternal::init();
-        let global_service =
-            BnVirtualizationServiceInternal::new_binder(global_service, BinderFeatures::default());
-
-        VirtualizationService { global_service, state: Default::default() }
+        VirtualizationService::default()
     }
 
     fn create_vm_context(&self) -> Result<(VmContext, Cid)> {
         const NUM_ATTEMPTS: usize = 5;
 
         for _ in 0..NUM_ATTEMPTS {
-            let global_context = self.global_service.allocateGlobalVmContext()?;
+            let global_context = GLOBAL_SERVICE.allocateGlobalVmContext()?;
             let cid = global_context.getCid()? as Cid;
             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));
@@ -512,8 +549,8 @@
         let state = &mut *self.state.lock().unwrap();
         let console_fd = console_fd.map(clone_file).transpose()?;
         let log_fd = log_fd.map(clone_file).transpose()?;
-        let requester_uid = ThreadState::get_calling_uid();
-        let requester_debug_pid = ThreadState::get_calling_pid();
+        let requester_uid = get_calling_uid();
+        let requester_debug_pid = get_calling_pid();
 
         // Counter to generate unique IDs for temporary image files.
         let mut next_temporary_image_id = 0;
@@ -839,8 +876,8 @@
 
 /// Checks whether the caller has a specific permission
 fn check_permission(perm: &str) -> binder::Result<()> {
-    let calling_pid = ThreadState::get_calling_pid();
-    let calling_uid = ThreadState::get_calling_uid();
+    let calling_pid = get_calling_pid();
+    let calling_uid = get_calling_uid();
     // Root can do anything
     if calling_uid == 0 {
         return Ok(());
@@ -881,8 +918,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 +936,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 +1011,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 +1090,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 +1332,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/atom.rs b/virtualizationservice/src/atom.rs
index a880d60..e430c74 100644
--- a/virtualizationservice/src/atom.rs
+++ b/virtualizationservice/src/atom.rs
@@ -14,17 +14,23 @@
 
 //! Functions for creating and collecting atoms.
 
-use crate::aidl::clone_file;
+use crate::aidl::{clone_file, GLOBAL_SERVICE};
 use crate::crosvm::VmMetric;
+use crate::get_calling_uid;
+use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::DeathReason::DeathReason;
 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
-    DeathReason::DeathReason,
     IVirtualMachine::IVirtualMachine,
     VirtualMachineAppConfig::{Payload::Payload, VirtualMachineAppConfig},
     VirtualMachineConfig::VirtualMachineConfig,
 };
 use android_system_virtualizationservice::binder::{Status, Strong};
+use android_system_virtualizationservice_internal::aidl::android::system::virtualizationservice_internal::{
+    AtomVmBooted::AtomVmBooted,
+    AtomVmCreationRequested::AtomVmCreationRequested,
+    AtomVmExited::AtomVmExited,
+};
 use anyhow::{anyhow, Result};
-use binder::{ParcelFileDescriptor, ThreadState};
+use binder::ParcelFileDescriptor;
 use log::{trace, warn};
 use microdroid_payload_config::VmPayloadConfig;
 use rustutils::system_properties;
@@ -106,34 +112,58 @@
         ),
     };
 
-    let uid = ThreadState::get_calling_uid() as i32;
-    thread::spawn(move || {
-        let vm_creation_requested = vm_creation_requested::VmCreationRequested {
-            uid,
-            vm_identifier: &vm_identifier,
-            hypervisor: vm_creation_requested::Hypervisor::Pkvm,
-            is_protected,
-            creation_succeeded,
-            binder_exception_code,
-            config_type,
-            num_cpus,
-            cpu_affinity: "", // deprecated
-            memory_mib,
-            apexes: &apexes,
-            // TODO(seungjaeyoo) Fill information about task_profile
-            // TODO(seungjaeyoo) Fill information about disk_image for raw config
-        };
+    let atom = AtomVmCreationRequested {
+        uid: get_calling_uid() as i32,
+        vmIdentifier: vm_identifier,
+        isProtected: is_protected,
+        creationSucceeded: creation_succeeded,
+        binderExceptionCode: binder_exception_code,
+        configType: config_type as i32,
+        numCpus: num_cpus,
+        memoryMib: memory_mib,
+        apexes,
+    };
 
-        wait_for_statsd().unwrap_or_else(|e| warn!("failed to wait for statsd with error: {}", e));
-        match vm_creation_requested.stats_write() {
-            Err(e) => {
-                warn!("statslog_rust failed with error: {}", e);
-            }
-            Ok(_) => trace!("statslog_rust succeeded for virtualization service"),
-        }
+    thread::spawn(move || {
+        GLOBAL_SERVICE.atomVmCreationRequested(&atom).unwrap_or_else(|e| {
+            warn!("Failed to write VmCreationRequested atom: {e}");
+        });
     });
 }
 
+pub fn forward_vm_creation_atom(atom: &AtomVmCreationRequested) {
+    let config_type = match atom.configType {
+        x if x == vm_creation_requested::ConfigType::VirtualMachineAppConfig as i32 => {
+            vm_creation_requested::ConfigType::VirtualMachineAppConfig
+        }
+        x if x == vm_creation_requested::ConfigType::VirtualMachineRawConfig as i32 => {
+            vm_creation_requested::ConfigType::VirtualMachineRawConfig
+        }
+        _ => vm_creation_requested::ConfigType::UnknownConfig,
+    };
+    let vm_creation_requested = vm_creation_requested::VmCreationRequested {
+        uid: atom.uid,
+        vm_identifier: &atom.vmIdentifier,
+        hypervisor: vm_creation_requested::Hypervisor::Pkvm,
+        is_protected: atom.isProtected,
+        creation_succeeded: atom.creationSucceeded,
+        binder_exception_code: atom.binderExceptionCode,
+        config_type,
+        num_cpus: atom.numCpus,
+        cpu_affinity: "", // deprecated
+        memory_mib: atom.memoryMib,
+        apexes: &atom.apexes,
+        // TODO(seungjaeyoo) Fill information about task_profile
+        // TODO(seungjaeyoo) Fill information about disk_image for raw config
+    };
+
+    wait_for_statsd().unwrap_or_else(|e| warn!("failed to wait for statsd with error: {}", e));
+    match vm_creation_requested.stats_write() {
+        Err(e) => warn!("statslog_rust failed with error: {}", e),
+        Ok(_) => trace!("statslog_rust succeeded for virtualization service"),
+    }
+}
+
 /// Write the stats of VM boot to statsd
 /// The function creates a separate thread which waits fro statsd to start to push atom
 pub fn write_vm_booted_stats(
@@ -143,22 +173,34 @@
 ) {
     let vm_identifier = vm_identifier.to_owned();
     let duration = get_duration(vm_start_timestamp);
+
+    let atom = AtomVmBooted {
+        uid,
+        vmIdentifier: vm_identifier,
+        elapsedTimeMillis: duration.as_millis() as i64,
+    };
+
     thread::spawn(move || {
-        let vm_booted = vm_booted::VmBooted {
-            uid,
-            vm_identifier: &vm_identifier,
-            elapsed_time_millis: duration.as_millis() as i64,
-        };
-        wait_for_statsd().unwrap_or_else(|e| warn!("failed to wait for statsd with error: {}", e));
-        match vm_booted.stats_write() {
-            Err(e) => {
-                warn!("statslog_rust failed with error: {}", e);
-            }
-            Ok(_) => trace!("statslog_rust succeeded for virtualization service"),
-        }
+        GLOBAL_SERVICE.atomVmBooted(&atom).unwrap_or_else(|e| {
+            warn!("Failed to write VmCreationRequested atom: {e}");
+        });
     });
 }
 
+pub fn forward_vm_booted_atom(atom: &AtomVmBooted) {
+    let vm_booted = vm_booted::VmBooted {
+        uid: atom.uid,
+        vm_identifier: &atom.vmIdentifier,
+        elapsed_time_millis: atom.elapsedTimeMillis,
+    };
+
+    wait_for_statsd().unwrap_or_else(|e| warn!("failed to wait for statsd with error: {}", e));
+    match vm_booted.stats_write() {
+        Err(e) => warn!("statslog_rust failed with error: {}", e),
+        Ok(_) => trace!("statslog_rust succeeded for virtualization service"),
+    }
+}
+
 /// Write the stats of VM exit to statsd
 /// The function creates a separate thread which waits fro statsd to start to push atom
 pub fn write_vm_exited_stats(
@@ -173,64 +215,82 @@
     let guest_time_millis = vm_metric.cpu_guest_time.unwrap_or_default();
     let rss = vm_metric.rss.unwrap_or_default();
 
+    let atom = AtomVmExited {
+        uid,
+        vmIdentifier: vm_identifier,
+        elapsedTimeMillis: elapsed_time_millis,
+        deathReason: reason,
+        guestTimeMillis: guest_time_millis,
+        rssVmKb: rss.vm,
+        rssCrosvmKb: rss.crosvm,
+        exitSignal: exit_signal.unwrap_or_default(),
+    };
+
     thread::spawn(move || {
-        let vm_exited = vm_exited::VmExited {
-            uid,
-            vm_identifier: &vm_identifier,
-            elapsed_time_millis,
-            death_reason: match reason {
-                DeathReason::INFRASTRUCTURE_ERROR => vm_exited::DeathReason::InfrastructureError,
-                DeathReason::KILLED => vm_exited::DeathReason::Killed,
-                DeathReason::UNKNOWN => vm_exited::DeathReason::Unknown,
-                DeathReason::SHUTDOWN => vm_exited::DeathReason::Shutdown,
-                DeathReason::START_FAILED => vm_exited::DeathReason::Error,
-                DeathReason::REBOOT => vm_exited::DeathReason::Reboot,
-                DeathReason::CRASH => vm_exited::DeathReason::Crash,
-                DeathReason::PVM_FIRMWARE_PUBLIC_KEY_MISMATCH => {
-                    vm_exited::DeathReason::PvmFirmwarePublicKeyMismatch
-                }
-                DeathReason::PVM_FIRMWARE_INSTANCE_IMAGE_CHANGED => {
-                    vm_exited::DeathReason::PvmFirmwareInstanceImageChanged
-                }
-                DeathReason::BOOTLOADER_PUBLIC_KEY_MISMATCH => {
-                    vm_exited::DeathReason::BootloaderPublicKeyMismatch
-                }
-                DeathReason::BOOTLOADER_INSTANCE_IMAGE_CHANGED => {
-                    vm_exited::DeathReason::BootloaderInstanceImageChanged
-                }
-                DeathReason::MICRODROID_FAILED_TO_CONNECT_TO_VIRTUALIZATION_SERVICE => {
-                    vm_exited::DeathReason::MicrodroidFailedToConnectToVirtualizationService
-                }
-                DeathReason::MICRODROID_PAYLOAD_HAS_CHANGED => {
-                    vm_exited::DeathReason::MicrodroidPayloadHasChanged
-                }
-                DeathReason::MICRODROID_PAYLOAD_VERIFICATION_FAILED => {
-                    vm_exited::DeathReason::MicrodroidPayloadVerificationFailed
-                }
-                DeathReason::MICRODROID_INVALID_PAYLOAD_CONFIG => {
-                    vm_exited::DeathReason::MicrodroidInvalidPayloadConfig
-                }
-                DeathReason::MICRODROID_UNKNOWN_RUNTIME_ERROR => {
-                    vm_exited::DeathReason::MicrodroidUnknownRuntimeError
-                }
-                DeathReason::HANGUP => vm_exited::DeathReason::Hangup,
-                _ => vm_exited::DeathReason::Unknown,
-            },
-            guest_time_millis,
-            rss_vm_kb: rss.vm,
-            rss_crosvm_kb: rss.crosvm,
-            exit_signal: exit_signal.unwrap_or_default(),
-        };
-        wait_for_statsd().unwrap_or_else(|e| warn!("failed to wait for statsd with error: {}", e));
-        match vm_exited.stats_write() {
-            Err(e) => {
-                warn!("statslog_rust failed with error: {}", e);
-            }
-            Ok(_) => trace!("statslog_rust succeeded for virtualization service"),
-        }
+        GLOBAL_SERVICE.atomVmExited(&atom).unwrap_or_else(|e| {
+            warn!("Failed to write VmExited atom: {e}");
+        });
     });
 }
 
+pub fn forward_vm_exited_atom(atom: &AtomVmExited) {
+    let death_reason = match atom.deathReason {
+        DeathReason::INFRASTRUCTURE_ERROR => vm_exited::DeathReason::InfrastructureError,
+        DeathReason::KILLED => vm_exited::DeathReason::Killed,
+        DeathReason::UNKNOWN => vm_exited::DeathReason::Unknown,
+        DeathReason::SHUTDOWN => vm_exited::DeathReason::Shutdown,
+        DeathReason::START_FAILED => vm_exited::DeathReason::Error,
+        DeathReason::REBOOT => vm_exited::DeathReason::Reboot,
+        DeathReason::CRASH => vm_exited::DeathReason::Crash,
+        DeathReason::PVM_FIRMWARE_PUBLIC_KEY_MISMATCH => {
+            vm_exited::DeathReason::PvmFirmwarePublicKeyMismatch
+        }
+        DeathReason::PVM_FIRMWARE_INSTANCE_IMAGE_CHANGED => {
+            vm_exited::DeathReason::PvmFirmwareInstanceImageChanged
+        }
+        DeathReason::BOOTLOADER_PUBLIC_KEY_MISMATCH => {
+            vm_exited::DeathReason::BootloaderPublicKeyMismatch
+        }
+        DeathReason::BOOTLOADER_INSTANCE_IMAGE_CHANGED => {
+            vm_exited::DeathReason::BootloaderInstanceImageChanged
+        }
+        DeathReason::MICRODROID_FAILED_TO_CONNECT_TO_VIRTUALIZATION_SERVICE => {
+            vm_exited::DeathReason::MicrodroidFailedToConnectToVirtualizationService
+        }
+        DeathReason::MICRODROID_PAYLOAD_HAS_CHANGED => {
+            vm_exited::DeathReason::MicrodroidPayloadHasChanged
+        }
+        DeathReason::MICRODROID_PAYLOAD_VERIFICATION_FAILED => {
+            vm_exited::DeathReason::MicrodroidPayloadVerificationFailed
+        }
+        DeathReason::MICRODROID_INVALID_PAYLOAD_CONFIG => {
+            vm_exited::DeathReason::MicrodroidInvalidPayloadConfig
+        }
+        DeathReason::MICRODROID_UNKNOWN_RUNTIME_ERROR => {
+            vm_exited::DeathReason::MicrodroidUnknownRuntimeError
+        }
+        DeathReason::HANGUP => vm_exited::DeathReason::Hangup,
+        _ => vm_exited::DeathReason::Unknown,
+    };
+
+    let vm_exited = vm_exited::VmExited {
+        uid: atom.uid,
+        vm_identifier: &atom.vmIdentifier,
+        elapsed_time_millis: atom.elapsedTimeMillis,
+        death_reason,
+        guest_time_millis: atom.guestTimeMillis,
+        rss_vm_kb: atom.rssVmKb,
+        rss_crosvm_kb: atom.rssCrosvmKb,
+        exit_signal: atom.exitSignal,
+    };
+
+    wait_for_statsd().unwrap_or_else(|e| warn!("failed to wait for statsd with error: {}", e));
+    match vm_exited.stats_write() {
+        Err(e) => warn!("statslog_rust failed with error: {}", e),
+        Ok(_) => trace!("statslog_rust succeeded for virtualization service"),
+    }
+}
+
 fn wait_for_statsd() -> Result<()> {
     let mut prop = system_properties::PropertyWatcher::new("init.svc.statsd")?;
     loop {
diff --git a/virtualizationservice/src/crosvm.rs b/virtualizationservice/src/crosvm.rs
index fc85ca5..94248f8 100644
--- a/virtualizationservice/src/crosvm.rs
+++ b/virtualizationservice/src/crosvm.rs
@@ -39,11 +39,9 @@
 use std::process::{Command, ExitStatus};
 use std::sync::{Arc, Condvar, Mutex};
 use std::time::{Duration, SystemTime};
-use std::thread;
-use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
-    DeathReason::DeathReason,
-    MemoryTrimLevel::MemoryTrimLevel,
-};
+use std::thread::{self, JoinHandle};
+use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::DeathReason::DeathReason;
+use android_system_virtualizationservice::aidl::android::system::virtualizationservice::MemoryTrimLevel::MemoryTrimLevel;
 use android_system_virtualizationservice_internal::aidl::android::system::virtualizationservice_internal::IGlobalVmContext::IGlobalVmContext;
 use binder::Strong;
 use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::IVirtualMachineService;
@@ -140,6 +138,8 @@
     Running {
         /// The crosvm child process.
         child: Arc<SharedChild>,
+        /// The thread waiting for crosvm to finish.
+        monitor_vm_exit_thread: Option<JoinHandle<()>>,
     },
     /// The VM died or was killed.
     Dead,
@@ -187,9 +187,9 @@
 
             let child_clone = child.clone();
             let instance_clone = instance.clone();
-            thread::spawn(move || {
+            let monitor_vm_exit_thread = Some(thread::spawn(move || {
                 instance_clone.monitor_vm_exit(child_clone, failure_pipe_read);
-            });
+            }));
 
             if detect_hangup {
                 let child_clone = child.clone();
@@ -199,7 +199,7 @@
             }
 
             // If it started correctly, update the state.
-            *self = VmState::Running { child };
+            *self = VmState::Running { child, monitor_vm_exit_thread };
             Ok(())
         } else {
             *self = state;
@@ -465,19 +465,24 @@
 
     /// Kills the crosvm instance, if it is running.
     pub fn kill(&self) -> Result<(), Error> {
-        let vm_state = &*self.vm_state.lock().unwrap();
-        if let VmState::Running { child } = vm_state {
-            let id = child.id();
-            debug!("Killing crosvm({})", id);
-            // TODO: Talk to crosvm to shutdown cleanly.
-            if let Err(e) = child.kill() {
-                bail!("Error killing crosvm({}) instance: {}", id, e);
+        let monitor_vm_exit_thread = {
+            let vm_state = &mut *self.vm_state.lock().unwrap();
+            if let VmState::Running { child, monitor_vm_exit_thread } = vm_state {
+                let id = child.id();
+                debug!("Killing crosvm({})", id);
+                // TODO: Talk to crosvm to shutdown cleanly.
+                child.kill().with_context(|| format!("Error killing crosvm({id}) instance"))?;
+                monitor_vm_exit_thread.take()
             } else {
-                Ok(())
+                bail!("VM is not running")
             }
-        } else {
-            bail!("VM is not running")
-        }
+        };
+
+        // Wait for monitor_vm_exit() to finish. Must release vm_state lock
+        // first, as monitor_vm_exit() takes it as well.
+        monitor_vm_exit_thread.map(JoinHandle::join);
+
+        Ok(())
     }
 
     /// Responds to memory-trimming notifications by inflating the virtio
@@ -520,15 +525,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 +536,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/virtualizationservice/src/main.rs b/virtualizationservice/src/main.rs
index 714bcfd..9272e65 100644
--- a/virtualizationservice/src/main.rs
+++ b/virtualizationservice/src/main.rs
@@ -21,16 +21,27 @@
 mod payload;
 mod selinux;
 
-use crate::aidl::{VirtualizationService, BINDER_SERVICE_IDENTIFIER, TEMPORARY_DIRECTORY};
+use crate::aidl::{VirtualizationService, TEMPORARY_DIRECTORY};
 use android_logger::{Config, FilterBuilder};
 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::IVirtualizationService::BnVirtualizationService;
 use anyhow::{bail, Context, Error};
-use binder::{register_lazy_service, BinderFeatures, ProcessState};
+use binder::{register_lazy_service, BinderFeatures, ProcessState, ThreadState};
 use log::{info, Level};
 use std::fs::{remove_dir_all, remove_file, read_dir};
+use std::os::unix::raw::{pid_t, uid_t};
 
 const LOG_TAG: &str = "VirtualizationService";
 
+const BINDER_SERVICE_IDENTIFIER: &str = "android.system.virtualizationservice";
+
+fn get_calling_pid() -> pid_t {
+    ThreadState::get_calling_pid()
+}
+
+fn get_calling_uid() -> uid_t {
+    ThreadState::get_calling_uid()
+}
+
 fn main() {
     android_logger::init_once(
         Config::default()
diff --git a/virtualizationservice/src/virtmgr.rs b/virtualizationservice/src/virtmgr.rs
new file mode 100644
index 0000000..1aa3df9
--- /dev/null
+++ b/virtualizationservice/src/virtmgr.rs
@@ -0,0 +1,120 @@
+// Copyright 2022, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Android Virtualization Manager
+
+mod aidl;
+mod atom;
+mod composite;
+mod crosvm;
+mod payload;
+mod selinux;
+
+use crate::aidl::VirtualizationService;
+use android_system_virtualizationservice::aidl::android::system::virtualizationservice::IVirtualizationService::BnVirtualizationService;
+use anyhow::{bail, Context};
+use binder::BinderFeatures;
+use lazy_static::lazy_static;
+use log::{info, Level};
+use rpcbinder::{FileDescriptorTransportMode, RpcServer};
+use std::os::unix::io::{FromRawFd, OwnedFd, RawFd};
+use clap::Parser;
+use nix::unistd::{Pid, Uid};
+use std::os::unix::raw::{pid_t, uid_t};
+
+const LOG_TAG: &str = "virtmgr";
+
+lazy_static! {
+    static ref PID_PARENT: Pid = Pid::parent();
+    static ref UID_CURRENT: Uid = Uid::current();
+}
+
+fn get_calling_pid() -> pid_t {
+    // The caller is the parent of this process.
+    PID_PARENT.as_raw()
+}
+
+fn get_calling_uid() -> uid_t {
+    // The caller and this process share the same UID.
+    UID_CURRENT.as_raw()
+}
+
+#[derive(Parser)]
+struct Args {
+    /// File descriptor inherited from the caller to run RpcBinder server on.
+    /// This should be one end of a socketpair() compatible with RpcBinder's
+    /// UDS bootstrap transport.
+    #[clap(long)]
+    rpc_server_fd: RawFd,
+    /// File descriptor inherited from the caller to signal RpcBinder server
+    /// readiness. This should be one end of pipe() and the caller should be
+    /// waiting for HUP on the other end.
+    #[clap(long)]
+    ready_fd: RawFd,
+}
+
+fn take_fd_ownership(raw_fd: RawFd, owned_fds: &mut Vec<RawFd>) -> Result<OwnedFd, anyhow::Error> {
+    // Basic check that the integer value does correspond to a file descriptor.
+    nix::fcntl::fcntl(raw_fd, nix::fcntl::F_GETFD)
+        .with_context(|| format!("Invalid file descriptor {raw_fd}"))?;
+
+    // Creating OwnedFd for stdio FDs is not safe.
+    if [libc::STDIN_FILENO, libc::STDOUT_FILENO, libc::STDERR_FILENO].contains(&raw_fd) {
+        bail!("File descriptor {raw_fd} is standard I/O descriptor");
+    }
+
+    // Reject RawFds that already have a corresponding OwnedFd.
+    if owned_fds.contains(&raw_fd) {
+        bail!("File descriptor {raw_fd} already owned");
+    }
+    owned_fds.push(raw_fd);
+
+    // SAFETY - Initializing OwnedFd for a RawFd provided in cmdline arguments.
+    // We checked that the integer value corresponds to a valid FD and that this
+    // is the first argument to claim its ownership.
+    Ok(unsafe { OwnedFd::from_raw_fd(raw_fd) })
+}
+
+fn main() {
+    android_logger::init_once(
+        android_logger::Config::default()
+            .with_tag(LOG_TAG)
+            .with_min_level(Level::Info)
+            .with_log_id(android_logger::LogId::System),
+    );
+
+    let args = Args::parse();
+
+    let mut owned_fds = vec![];
+    let rpc_server_fd = take_fd_ownership(args.rpc_server_fd, &mut owned_fds)
+        .expect("Failed to take ownership of rpc_server_fd");
+    let ready_fd = take_fd_ownership(args.ready_fd, &mut owned_fds)
+        .expect("Failed to take ownership of ready_fd");
+
+    let service = VirtualizationService::init();
+    let service =
+        BnVirtualizationService::new_binder(service, BinderFeatures::default()).as_binder();
+
+    let server = RpcServer::new_unix_domain_bootstrap(service, rpc_server_fd)
+        .expect("Failed to start RpcServer");
+    server.set_supported_file_descriptor_transport_modes(&[FileDescriptorTransportMode::Unix]);
+
+    info!("Started VirtualizationService RpcServer. Ready to accept connections");
+
+    // Signal readiness to the caller by closing our end of the pipe.
+    drop(ready_fd);
+
+    server.join();
+    info!("Shutting down VirtualizationService RpcServer");
+}
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/vm_shell.sh b/vm/vm_shell.sh
index c0dd38f..29cc7da 100755
--- a/vm/vm_shell.sh
+++ b/vm/vm_shell.sh
@@ -14,16 +14,33 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# vm_shell.sh shows the VMs running in the Android device and connects to it
-# Usage:
-# vm_shell [cid]
-#
-#   cid: CID of the VM to connect to. If omitted, the list of CIDs available are shown
+# vm_shell.sh: utilities to interact with Microdroid VMs
+
+function print_help() {
+    echo "vm_shell.sh provides utilities to interact with Microdroid VMs"
+    echo ""
+    echo "Available commands:"
+    echo "    connect [cid] - establishes adb connection with the VM"
+    echo "      cid - cid of the VM to connect to. If not specified user will "
+    echo "            be promted to select one from the list of available cids"
+    echo ""
+    echo "    start-microdroid [--auto-connect] [-- extra_args]"
+    echo "        Starts a Microdroid VM. Args after the -- will be"
+    echo "        passed through to the invocation of the "
+    echo "        /apex/com.android.virt/bin/vm run-microdroid binary."
+    echo ""
+    echo "        E.g.:"
+    echo "            vm_shell start-microdroid -- --cpu 5"
+    echo ""
+    echo "        --auto-connect - automatically connects to the started VMs"
+    echo ""
+    echo "    help - prints this help message"
+}
 
 function connect_vm() {
     cid=$1
     echo Connecting to CID ${cid}
-    adb disconnect localhost:8000
+    adb disconnect localhost:8000 2>/dev/null
     adb forward tcp:8000 vsock:${cid}:5555
     adb connect localhost:8000
     adb -s localhost:8000 root
@@ -32,26 +49,63 @@
     exit 0
 }
 
-selected_cid=$1
-available_cids=$(adb shell /apex/com.android.virt/bin/vm list | awk 'BEGIN { FS="[:,]" } /cid/ { print $2; }')
+function list_cids() {
+    local selected_cid=$1
+    local available_cids=$(adb shell /apex/com.android.virt/bin/vm list | awk 'BEGIN { FS="[:,]" } /cid/ { print $2; }')
+    echo "${available_cids}"
+}
 
-if [ -z "${available_cids}" ]; then
-    echo No VM is available
-    exit 1
-fi
+function handle_connect_cmd() {
+    selected_cid=$1
 
-if [ ! -n "${selected_cid}" ]; then
-    PS3="Select CID of VM to adb-shell into: "
-    select cid in ${available_cids}
-    do
-        selected_cid=${cid}
-        break
+    available_cids=$(list_cids)
+
+    if [ -z "${available_cids}" ]; then
+        echo No VM is available
+        exit 1
+    fi
+
+    if [ ! -n "${selected_cid}" ]; then
+        PS3="Select CID of VM to adb-shell into: "
+        select cid in ${available_cids}
+        do
+            selected_cid=${cid}
+            break
+        done
+    fi
+
+    if [[ ! " ${available_cids[*]} " =~ " ${selected_cid} " ]]; then
+        echo VM of CID $selected_cid does not exist. Available CIDs: ${available_cids}
+        exit 1
+    fi
+
+    connect_vm ${selected_cid}
+}
+
+function handle_start_microdroid_cmd() {
+    while [[ "$#" -gt 0 ]]; do
+        case $1 in
+          --auto-connect) auto_connect=true; ;;
+          --) shift; passthrough_args="$@"; break ;;
+          *) echo "Unknown argument: $1"; exit 1 ;;
+        esac
+        shift
     done
-fi
+    if [[ "${auto_connect}" == true ]]; then
+        adb shell /apex/com.android.virt/bin/vm run-microdroid -d "${passthrough_args}"
+        sleep 2
+        handle_connect_cmd
+    else
+        adb shell /apex/com.android.virt/bin/vm run-microdroid "${passthrough_args}"
+    fi
+}
 
-if [[ ! " ${available_cids[*]} " =~ " ${selected_cid} " ]]; then
-    echo VM of CID $selected_cid does not exist. Available CIDs: ${available_cids}
-    exit 1
-fi
+cmd=$1
+shift
 
-connect_vm ${selected_cid}
+case $cmd in
+  connect) handle_connect_cmd "$@" ;;
+  start-microdroid) handle_start_microdroid_cmd "$@" ;;
+  help) print_help ;;
+  *) print_help; exit 1 ;;
+esac
diff --git a/vm_payload/README.md b/vm_payload/README.md
index 6ce6770..bcba9be 100644
--- a/vm_payload/README.md
+++ b/vm_payload/README.md
@@ -60,7 +60,7 @@
 
 C++ can be used, but you will need to include the C++ runtime in your APK along
 with your payload, either statically linked (if
-[appropriate](https://developer.android.com/ndk/guides/cpp-support#sr) or as a
+[appropriate](https://developer.android.com/ndk/guides/cpp-support#sr)) or as a
 separate .so.
 
 The same is true for other languages such as Rust.
diff --git a/vm_payload/include-restricted/vm_payload_restricted.h b/vm_payload/include-restricted/vm_payload_restricted.h
index 0b78541..7f17cde 100644
--- a/vm_payload/include-restricted/vm_payload_restricted.h
+++ b/vm_payload/include-restricted/vm_payload_restricted.h
@@ -34,21 +34,21 @@
 /**
  * Get the VM's DICE attestation chain.
  *
- * \param data pointer to size bytes where the chain is written.
+ * \param data pointer to size bytes where the chain is written (may be null if size is 0).
  * \param size number of bytes that can be written to data.
  *
  * \return the total size of the chain
  */
-size_t AVmPayload_getDiceAttestationChain(void *data, size_t size);
+size_t AVmPayload_getDiceAttestationChain(void* _Nullable data, size_t size);
 
 /**
  * Get the VM's DICE attestation CDI.
  *
- * \param data pointer to size bytes where the CDI is written.
+ * \param data pointer to size bytes where the CDI is written (may be null if size is 0).
  * \param size number of bytes that can be written to data.
  *
  * \return the total size of the CDI
  */
-size_t AVmPayload_getDiceAttestationCdi(void *data, size_t size);
+size_t AVmPayload_getDiceAttestationCdi(void* _Nullable data, size_t size);
 
 __END_DECLS
diff --git a/vm_payload/include/vm_payload.h b/vm_payload/include/vm_payload.h
index 7c224f6..5cc2d1e 100644
--- a/vm_payload/include/vm_payload.h
+++ b/vm_payload/include/vm_payload.h
@@ -18,6 +18,7 @@
 
 #include <stdbool.h>
 #include <stddef.h>
+#include <stdint.h>
 #include <stdnoreturn.h>
 #include <sys/cdefs.h>
 
@@ -52,25 +53,40 @@
  *
  * \param service the service to bind to the given port.
  * \param port vsock port.
- * \param on_ready the callback to execute once the server is ready for connections. The callback
- *                 will be called at most once.
- * \param param param for the `on_ready` callback.
+ * \param on_ready the callback to execute once the server is ready for connections. If not null the
+ * callback will be called at most once.
+ * \param param parameter to be passed to the `on_ready` callback.
  */
-noreturn void AVmPayload_runVsockRpcServer(AIBinder *service, unsigned int port,
-                                           void (*on_ready)(void *param), void *param);
+noreturn void AVmPayload_runVsockRpcServer(AIBinder* _Nonnull service, uint32_t port,
+                                           void (*_Nullable on_ready)(void* _Nullable param),
+                                           void* _Nullable param);
 
 /**
- * Get a secret that is uniquely bound to this VM instance. The secrets are
- * 32-byte values and the value associated with an identifier will not change
- * over the lifetime of the VM instance.
+ * Returns all or part of a 32-byte secret that is bound to this unique VM
+ * instance and the supplied identifier. The secret can be used e.g. as an
+ * encryption key.
+ *
+ * Every VM has a secret that is derived from a device-specific value known to
+ * the hypervisor, the code that runs in the VM and its non-modifiable
+ * configuration; it is not made available to the host OS.
+ *
+ * This function performs a further derivation from the VM secret and the
+ * supplied identifier. As long as the VM identity doesn't change the same value
+ * will be returned for the same identifier, even if the VM is stopped &
+ * restarted or the device rebooted.
+ *
+ * If multiple secrets are required for different purposes, a different
+ * identifier should be used for each. The identifiers otherwise are arbitrary
+ * byte sequences and do not need to be kept secret; typically they are
+ * hardcoded in the calling code.
  *
  * \param identifier identifier of the secret to return.
  * \param identifier_size size of the secret identifier.
  * \param secret pointer to size bytes where the secret is written.
  * \param size number of bytes of the secret to get, <= 32.
  */
-void AVmPayload_getVmInstanceSecret(const void *identifier, size_t identifier_size, void *secret,
-                                    size_t size);
+void AVmPayload_getVmInstanceSecret(const void* _Nonnull identifier, size_t identifier_size,
+                                    void* _Nonnull secret, size_t size);
 
 /**
  * Gets the path to the APK contents. It is a directory, under which are
@@ -81,7 +97,7 @@
  * deleted or freed by the application. The string remains valid for the
  * lifetime of the VM.
  */
-const char *AVmPayload_getApkContentsPath(void);
+const char* _Nonnull AVmPayload_getApkContentsPath(void);
 
 /**
  * Gets the path to the encrypted persistent storage for the VM, if any. This is
@@ -94,6 +110,6 @@
  * be deleted or freed by the application and remains valid for the lifetime of
  * the VM.
  */
-const char *AVmPayload_getEncryptedStoragePath(void);
+const char* _Nullable AVmPayload_getEncryptedStoragePath(void);
 
 __END_DECLS
diff --git a/vm_payload/src/api.rs b/vm_payload/src/api.rs
index 28b440e..4b565e0 100644
--- a/vm_payload/src/api.rs
+++ b/vm_payload/src/api.rs
@@ -23,7 +23,7 @@
 use binder::{Strong, unstable_api::{AIBinder, new_spibinder}};
 use lazy_static::lazy_static;
 use log::{error, info, Level};
-use rpcbinder::{get_unix_domain_rpc_interface, RpcServer};
+use rpcbinder::{RpcSession, RpcServer};
 use std::convert::Infallible;
 use std::ffi::CString;
 use std::fmt::Debug;
@@ -49,10 +49,9 @@
     if let Some(strong) = &*connection {
         Ok(strong.clone())
     } else {
-        let new_connection: Strong<dyn IVmPayloadService> = get_unix_domain_rpc_interface(
-            VM_PAYLOAD_SERVICE_SOCKET_NAME,
-        )
-        .context(format!("Failed to connect to service: {}", VM_PAYLOAD_SERVICE_SOCKET_NAME))?;
+        let new_connection: Strong<dyn IVmPayloadService> = RpcSession::new()
+            .setup_unix_domain_client(VM_PAYLOAD_SERVICE_SOCKET_NAME)
+            .context(format!("Failed to connect to service: {}", VM_PAYLOAD_SERVICE_SOCKET_NAME))?;
         *connection = Some(new_connection.clone());
         Ok(new_connection)
     }
@@ -136,7 +135,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
@@ -206,7 +205,7 @@
 ///
 /// Behavior is undefined if any of the following conditions are violated:
 ///
-/// * `data` must be [valid] for writes of `size` bytes.
+/// * `data` must be [valid] for writes of `size` bytes, if size > 0.
 ///
 /// [valid]: ptr#safety
 #[no_mangle]
@@ -214,9 +213,13 @@
     initialize_logging();
 
     let chain = unwrap_or_abort(try_get_dice_attestation_chain());
-    // SAFETY: See the requirements on `data` above. The number of bytes copied doesn't exceed
-    // the length of either buffer, and `chain` cannot overlap `data` because we just allocated it.
-    unsafe { ptr::copy_nonoverlapping(chain.as_ptr(), data, std::cmp::min(chain.len(), size)) };
+    if size != 0 {
+        // SAFETY: See the requirements on `data` above. The number of bytes copied doesn't exceed
+        // the length of either buffer, and `chain` cannot overlap `data` because we just allocated
+        // it. We allow data to be null, which is never valid, but only if size == 0 which is
+        // checked above.
+        unsafe { ptr::copy_nonoverlapping(chain.as_ptr(), data, std::cmp::min(chain.len(), size)) };
+    }
     chain.len()
 }
 
@@ -231,7 +234,7 @@
 ///
 /// Behavior is undefined if any of the following conditions are violated:
 ///
-/// * `data` must be [valid] for writes of `size` bytes.
+/// * `data` must be [valid] for writes of `size` bytes, if size > 0.
 ///
 /// [valid]: ptr#safety
 #[no_mangle]
@@ -239,9 +242,13 @@
     initialize_logging();
 
     let cdi = unwrap_or_abort(try_get_dice_attestation_cdi());
-    // SAFETY: See the requirements on `data` above. The number of bytes copied doesn't exceed
-    // the length of either buffer, and `cdi` cannot overlap `data` because we just allocated it.
-    unsafe { ptr::copy_nonoverlapping(cdi.as_ptr(), data, std::cmp::min(cdi.len(), size)) };
+    if size != 0 {
+        // SAFETY: See the requirements on `data` above. The number of bytes copied doesn't exceed
+        // the length of either buffer, and `cdi` cannot overlap `data` because we just allocated
+        // it. We allow data to be null, which is never valid, but only if size == 0 which is
+        // checked above.
+        unsafe { ptr::copy_nonoverlapping(cdi.as_ptr(), data, std::cmp::min(cdi.len(), size)) };
+    }
     cdi.len()
 }
 
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/Android.bp b/vmclient/Android.bp
index 88b0c9a..0a2e692 100644
--- a/vmclient/Android.bp
+++ b/vmclient/Android.bp
@@ -11,8 +11,11 @@
         "android.system.virtualizationcommon-rust",
         "android.system.virtualizationservice-rust",
         "libbinder_rs",
+        "libcommand_fds",
         "liblog_rust",
+        "libnix",
         "librpcbinder_rs",
+        "libshared_child",
         "libthiserror",
     ],
     shared_libs: [
diff --git a/vmclient/src/death_reason.rs b/vmclient/src/death_reason.rs
index 34d89fc..c417a7c 100644
--- a/vmclient/src/death_reason.rs
+++ b/vmclient/src/death_reason.rs
@@ -12,9 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use android_system_virtualizationservice::{
-        aidl::android::system::virtualizationservice::{
-            DeathReason::DeathReason as AidlDeathReason}};
+use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::DeathReason::DeathReason as AidlDeathReason;
 
 /// The reason why a VM died.
 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
diff --git a/vmclient/src/lib.rs b/vmclient/src/lib.rs
index 20b7f02..0e3d140 100644
--- a/vmclient/src/lib.rs
+++ b/vmclient/src/lib.rs
@@ -23,10 +23,11 @@
 pub use crate::error_code::ErrorCode;
 pub use crate::errors::VmWaitError;
 use crate::sync::Monitor;
-use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::ErrorCode::ErrorCode as AidlErrorCode;
+use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::{
+    DeathReason::DeathReason as AidlDeathReason, ErrorCode::ErrorCode as AidlErrorCode,
+};
 use android_system_virtualizationservice::{
     aidl::android::system::virtualizationservice::{
-        DeathReason::DeathReason as AidlDeathReason,
         IVirtualMachine::IVirtualMachine,
         IVirtualMachineCallback::{BnVirtualMachineCallback, IVirtualMachineCallback},
         IVirtualizationService::IVirtualizationService,
@@ -38,12 +39,16 @@
         ParcelFileDescriptor, Result as BinderResult, StatusCode, Strong,
     },
 };
+use command_fds::CommandFdExt;
 use log::warn;
-use rpcbinder::get_preconnected_rpc_interface;
+use rpcbinder::{FileDescriptorTransportMode, RpcSession};
+use shared_child::SharedChild;
+use std::io::{self, Read};
+use std::process::Command;
 use std::{
     fmt::{self, Debug, Formatter},
     fs::File,
-    os::unix::io::IntoRawFd,
+    os::unix::io::{AsFd, AsRawFd, FromRawFd, IntoRawFd, OwnedFd},
     sync::Arc,
     time::Duration,
 };
@@ -51,6 +56,79 @@
 const VIRTUALIZATION_SERVICE_BINDER_SERVICE_IDENTIFIER: &str =
     "android.system.virtualizationservice";
 
+const VIRTMGR_PATH: &str = "/apex/com.android.virt/bin/virtmgr";
+const VIRTMGR_THREADS: usize = 16;
+
+fn posix_pipe() -> Result<(OwnedFd, OwnedFd), io::Error> {
+    use nix::fcntl::OFlag;
+    use nix::unistd::pipe2;
+
+    // Create new POSIX pipe. Make it O_CLOEXEC to align with how Rust creates
+    // file descriptors (expected by SharedChild).
+    let (raw1, raw2) = pipe2(OFlag::O_CLOEXEC)?;
+
+    // SAFETY - Taking ownership of brand new FDs.
+    unsafe { Ok((OwnedFd::from_raw_fd(raw1), OwnedFd::from_raw_fd(raw2))) }
+}
+
+fn posix_socketpair() -> Result<(OwnedFd, OwnedFd), io::Error> {
+    use nix::sys::socket::{socketpair, AddressFamily, SockFlag, SockType};
+
+    // Create new POSIX socketpair, suitable for use with RpcBinder UDS bootstrap
+    // transport. Make it O_CLOEXEC to align with how Rust creates file
+    // descriptors (expected by SharedChild).
+    let (raw1, raw2) =
+        socketpair(AddressFamily::Unix, SockType::Stream, None, SockFlag::SOCK_CLOEXEC)?;
+
+    // SAFETY - Taking ownership of brand new FDs.
+    unsafe { Ok((OwnedFd::from_raw_fd(raw1), OwnedFd::from_raw_fd(raw2))) }
+}
+
+/// A running instance of virtmgr which is hosting a VirtualizationService
+/// RpcBinder server.
+pub struct VirtualizationService {
+    /// Client FD for UDS connection to virtmgr's RpcBinder server. Closing it
+    /// will make virtmgr shut down.
+    client_fd: OwnedFd,
+}
+
+impl VirtualizationService {
+    /// Spawns a new instance of virtmgr, a child process that will host
+    /// the VirtualizationService AIDL service.
+    pub fn new() -> Result<VirtualizationService, io::Error> {
+        let (wait_fd, ready_fd) = posix_pipe()?;
+        let (client_fd, server_fd) = posix_socketpair()?;
+
+        let mut command = Command::new(VIRTMGR_PATH);
+        command.arg("--rpc-server-fd").arg(format!("{}", server_fd.as_raw_fd()));
+        command.arg("--ready-fd").arg(format!("{}", ready_fd.as_raw_fd()));
+        command.preserved_fds(vec![server_fd.as_raw_fd(), ready_fd.as_raw_fd()]);
+
+        SharedChild::spawn(&mut command)?;
+
+        // Drop FDs that belong to virtmgr.
+        drop(server_fd);
+        drop(ready_fd);
+
+        // Wait for the child to signal that the RpcBinder server is ready
+        // by closing its end of the pipe.
+        let _ = File::from(wait_fd).read(&mut [0]);
+
+        Ok(VirtualizationService { client_fd })
+    }
+
+    /// Connects to the VirtualizationService AIDL service.
+    pub fn connect(&self) -> Result<Strong<dyn IVirtualizationService>, io::Error> {
+        let session = RpcSession::new();
+        session.set_file_descriptor_transport_mode(FileDescriptorTransportMode::Unix);
+        session.set_max_incoming_threads(VIRTMGR_THREADS);
+        session.set_max_outgoing_threads(VIRTMGR_THREADS);
+        session
+            .setup_unix_domain_bootstrap_client(self.client_fd.as_fd())
+            .map_err(|_| io::Error::from(io::ErrorKind::ConnectionRefused))
+    }
+}
+
 /// Connects to the VirtualizationService AIDL service.
 pub fn connect() -> Result<Strong<dyn IVirtualizationService>, StatusCode> {
     wait_for_interface(VIRTUALIZATION_SERVICE_BINDER_SERVICE_IDENTIFIER)
@@ -177,7 +255,7 @@
         &self,
         port: u32,
     ) -> Result<Strong<T>, StatusCode> {
-        get_preconnected_rpc_interface(|| {
+        RpcSession::new().setup_preconnected_client(|| {
             match self.vm.connectVsock(port as i32) {
                 Ok(vsock) => {
                     // Ownership of the fd is transferred to binder
@@ -190,11 +268,6 @@
             }
         })
     }
-
-    /// Get ramdump
-    pub fn get_ramdump(&self) -> Option<File> {
-        self.state.get_ramdump()
-    }
 }
 
 impl Debug for VmInstance {
@@ -222,7 +295,6 @@
 struct VmState {
     death_reason: Option<DeathReason>,
     reported_state: VirtualMachineState,
-    ramdump: Option<File>,
 }
 
 impl Monitor<VmState> {
@@ -239,14 +311,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 +366,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);