Merge "Set avb_hash_algorithm=sha256 for system & vendor img"
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/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/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/javalib/api/system-current.txt b/javalib/api/system-current.txt
index 821c47e..592a751 100644
--- a/javalib/api/system-current.txt
+++ b/javalib/api/system-current.txt
@@ -4,8 +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 @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;
@@ -17,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
@@ -86,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 49465aa..db5b4d1 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
@@ -993,9 +1013,13 @@
      */
     @SystemApi
     @NonNull
-    public IBinder connectToVsockServer(int port) throws VirtualMachineException {
+    public IBinder connectToVsockServer(
+            @IntRange(from = MIN_VSOCK_PORT, to = MAX_VSOCK_PORT) long port)
+            throws VirtualMachineException {
+
         synchronized (mLock) {
-            IBinder iBinder = nativeConnectToVsockServer(getRunningVm().asBinder(), port);
+            IBinder iBinder =
+                    nativeConnectToVsockServer(getRunningVm().asBinder(), validatePort(port));
             if (iBinder == null) {
                 throw new VirtualMachineException("Failed to connect to vsock server");
             }
@@ -1011,10 +1035,12 @@
      */
     @SystemApi
     @NonNull
-    public ParcelFileDescriptor connectVsock(int port) throws VirtualMachineException {
+    public ParcelFileDescriptor connectVsock(
+            @IntRange(from = MIN_VSOCK_PORT, to = MAX_VSOCK_PORT) long port)
+            throws VirtualMachineException {
         synchronized (mLock) {
             try {
-                return getRunningVm().connectVsock(port);
+                return getRunningVm().connectVsock(validatePort(port));
             } catch (RemoteException e) {
                 throw e.rethrowAsRuntimeException();
             } catch (ServiceSpecificException e) {
@@ -1023,6 +1049,16 @@
         }
     }
 
+    private int validatePort(long port) {
+        // Ports below 1024 are "privileged" (payload code can't bind to these), and port numbers
+        // are 32-bit unsigned numbers at the OS level, even though we pass them as 32-bit signed
+        // numbers internally.
+        if (port < MIN_VSOCK_PORT || port > MAX_VSOCK_PORT) {
+            throw new IllegalArgumentException("Bad port " + port);
+        }
+        return (int) port;
+    }
+
     /**
      * Returns the root directory where all files related to this {@link VirtualMachine} (e.g.
      * {@code instance.img}, {@code apk.idsig}, etc) are stored.
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
index 602f8b8..75e5414 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -123,7 +123,7 @@
      */
     @Nullable private final String mPayloadBinaryPath;
 
-    /** The size of storage in KB. 0 indicates that encryptedStorage is not required */
+    /** The size of storage in KiB. 0 indicates that encryptedStorage is not required */
     private final long mEncryptedStorageKib;
 
     private VirtualMachineConfig(
@@ -336,7 +336,7 @@
     }
 
     /**
-     * Returns the size of encrypted storage (in KB) available in the VM, or 0 if encrypted storage
+     * Returns the size of encrypted storage (in KiB) available in the VM, or 0 if encrypted storage
      * is not enabled
      *
      * @hide
@@ -608,7 +608,7 @@
         }
 
         /**
-         * Sets the size (in KB) of encrypted storage available to the VM. If not set, no encrypted
+         * 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
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/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 c273973..1a40e45 100644
--- a/libs/vbmeta/src/lib.rs
+++ b/libs/vbmeta/src/lib.rs
@@ -18,18 +18,12 @@
 
 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, transmute, MaybeUninit};
-use std::os::raw::c_uint;
 use std::path::Path;
 use std::ptr::null_mut;
 use thiserror::Error;
@@ -68,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.
@@ -130,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>()
@@ -144,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;
@@ -168,23 +159,21 @@
     // 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)),
     }
 }
 
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index 3141741..99f91ac 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -90,6 +90,8 @@
         "liblzma", // used by init_second_stage
 
         "libvm_payload", // used by payload to interact with microdroid manager
+
+        "prng_seeder",
     ] + microdroid_shell_and_utilities,
     multilib: {
         common: {
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 6a37b88..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};
@@ -160,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")
 }
 
@@ -795,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 b633745..f209784 100644
--- a/pvmfw/src/config.rs
+++ b/pvmfw/src/config.rs
@@ -20,13 +20,19 @@
 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],
 }
 
@@ -90,6 +96,7 @@
 
 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>());
 
@@ -186,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));
         }
diff --git a/pvmfw/src/entry.rs b/pvmfw/src/entry.rs
index 2763e80..45a8459 100644
--- a/pvmfw/src/entry.rs
+++ b/pvmfw/src/entry.rs
@@ -249,7 +249,7 @@
     // This wrapper allows main() to be blissfully ignorant of platform details.
     crate::main(slices.fdt, slices.kernel, slices.ramdisk, &bcc, &mut memory)?;
 
-    // TODO: Overwrite BCC before jumping to payload to avoid leaking our sealing key.
+    helpers::flushed_zeroize(bcc_slice);
 
     info!("Expecting a bug making MMIO_GUARD_UNMAP return NOT_SUPPORTED on success");
     memory.mmio_unmap_all().map_err(|e| {
diff --git a/pvmfw/src/helpers.rs b/pvmfw/src/helpers.rs
index f1ff36d..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;
@@ -44,6 +45,17 @@
     }
 }
 
+/// Aligns the given address to the given alignment, if it is a power of two.
+///
+/// Returns `None` if the alignment isn't a power of two.
+pub const fn align_down(addr: usize, alignment: usize) -> Option<usize> {
+    if !alignment.is_power_of_two() {
+        None
+    } else {
+        Some(unchecked_align_down(addr, alignment))
+    }
+}
+
 /// Computes the address of the 4KiB page containing a given address.
 pub const fn page_4kb_of(addr: usize) -> usize {
     unchecked_align_down(addr, SIZE_4KB)
@@ -75,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/hvc.rs b/pvmfw/src/hvc.rs
new file mode 100644
index 0000000..dc99303
--- /dev/null
+++ b/pvmfw/src/hvc.rs
@@ -0,0 +1,96 @@
+// 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.
+
+//! Wrappers around calls to the hypervisor.
+
+use crate::smccc::{self, checked_hvc64, checked_hvc64_expect_zero};
+use log::info;
+
+const ARM_SMCCC_KVM_FUNC_HYP_MEMINFO: u32 = 0xc6000002;
+const ARM_SMCCC_KVM_FUNC_MEM_SHARE: u32 = 0xc6000003;
+const ARM_SMCCC_KVM_FUNC_MEM_UNSHARE: u32 = 0xc6000004;
+const VENDOR_HYP_KVM_MMIO_GUARD_INFO_FUNC_ID: u32 = 0xc6000005;
+const VENDOR_HYP_KVM_MMIO_GUARD_ENROLL_FUNC_ID: u32 = 0xc6000006;
+const VENDOR_HYP_KVM_MMIO_GUARD_MAP_FUNC_ID: u32 = 0xc6000007;
+const VENDOR_HYP_KVM_MMIO_GUARD_UNMAP_FUNC_ID: u32 = 0xc6000008;
+
+/// Queries the memory protection parameters for a protected virtual machine.
+///
+/// Returns the memory protection granule size in bytes.
+pub fn hyp_meminfo() -> smccc::Result<u64> {
+    let args = [0u64; 17];
+    checked_hvc64(ARM_SMCCC_KVM_FUNC_HYP_MEMINFO, args)
+}
+
+/// Shares a region of memory with the KVM host, granting it read, write and execute permissions.
+/// The size of the region is equal to the memory protection granule returned by [`hyp_meminfo`].
+pub fn mem_share(base_ipa: u64) -> smccc::Result<()> {
+    let mut args = [0u64; 17];
+    args[0] = base_ipa;
+
+    checked_hvc64_expect_zero(ARM_SMCCC_KVM_FUNC_MEM_SHARE, args)
+}
+
+/// Revokes access permission from the KVM host to a memory region previously shared with
+/// [`mem_share`]. The size of the region is equal to the memory protection granule returned by
+/// [`hyp_meminfo`].
+pub fn mem_unshare(base_ipa: u64) -> smccc::Result<()> {
+    let mut args = [0u64; 17];
+    args[0] = base_ipa;
+
+    checked_hvc64_expect_zero(ARM_SMCCC_KVM_FUNC_MEM_UNSHARE, args)
+}
+
+pub fn mmio_guard_info() -> smccc::Result<u64> {
+    let args = [0u64; 17];
+
+    checked_hvc64(VENDOR_HYP_KVM_MMIO_GUARD_INFO_FUNC_ID, args)
+}
+
+pub fn mmio_guard_enroll() -> smccc::Result<()> {
+    let args = [0u64; 17];
+
+    checked_hvc64_expect_zero(VENDOR_HYP_KVM_MMIO_GUARD_ENROLL_FUNC_ID, args)
+}
+
+pub fn mmio_guard_map(ipa: u64) -> smccc::Result<()> {
+    let mut args = [0u64; 17];
+    args[0] = ipa;
+
+    // TODO(b/253586500): pKVM currently returns a i32 instead of a i64.
+    let is_i32_error_code = |n| u32::try_from(n).ok().filter(|v| (*v as i32) < 0).is_some();
+    match checked_hvc64_expect_zero(VENDOR_HYP_KVM_MMIO_GUARD_MAP_FUNC_ID, args) {
+        Err(smccc::Error::Unexpected(e)) if is_i32_error_code(e) => {
+            info!("Handled a pKVM bug by interpreting the MMIO_GUARD_MAP return value as i32");
+            match e as u32 as i32 {
+                -1 => Err(smccc::Error::NotSupported),
+                -2 => Err(smccc::Error::NotRequired),
+                -3 => Err(smccc::Error::InvalidParameter),
+                ret => Err(smccc::Error::Unknown(ret as i64)),
+            }
+        }
+        res => res,
+    }
+}
+
+pub fn mmio_guard_unmap(ipa: u64) -> smccc::Result<()> {
+    let mut args = [0u64; 17];
+    args[0] = ipa;
+
+    // TODO(b/251426790): pKVM currently returns NOT_SUPPORTED for SUCCESS.
+    match checked_hvc64_expect_zero(VENDOR_HYP_KVM_MMIO_GUARD_UNMAP_FUNC_ID, args) {
+        Err(smccc::Error::NotSupported) | Ok(_) => Ok(()),
+        x => x,
+    }
+}
diff --git a/pvmfw/src/main.rs b/pvmfw/src/main.rs
index 79b6f57..59efca0 100644
--- a/pvmfw/src/main.rs
+++ b/pvmfw/src/main.rs
@@ -26,6 +26,7 @@
 mod fdt;
 mod heap;
 mod helpers;
+mod hvc;
 mod memory;
 mod mmio_guard;
 mod mmu;
@@ -38,10 +39,10 @@
     memory::MemoryTracker,
     pci::{allocate_all_virtio_bars, PciError, PciInfo, PciMemory32Allocator},
 };
-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,
@@ -71,7 +72,7 @@
     let mut pci_root = unsafe { pci_info.make_pci_root() };
     allocate_all_virtio_bars(&mut pci_root, &mut bar_allocator).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/pvmfw/src/memory.rs b/pvmfw/src/memory.rs
index 892089e..5e4874f 100644
--- a/pvmfw/src/memory.rs
+++ b/pvmfw/src/memory.rs
@@ -14,9 +14,11 @@
 
 //! Low-level allocation and tracking of main memory.
 
-use crate::helpers::{self, page_4kb_of, SIZE_4KB};
+use crate::helpers::{self, align_down, page_4kb_of, SIZE_4KB};
+use crate::hvc::{hyp_meminfo, mem_share, mem_unshare};
 use crate::mmio_guard;
 use crate::mmu;
+use crate::smccc;
 use core::cmp::max;
 use core::cmp::min;
 use core::fmt;
@@ -267,6 +269,36 @@
     }
 }
 
+/// Gives the KVM host read, write and execute permissions on the given memory range. If the range
+/// is not aligned with the memory protection granule then it will be extended on either end to
+/// align.
+#[allow(unused)]
+pub fn share_range(range: &MemoryRange) -> smccc::Result<()> {
+    let granule = hyp_meminfo()? as usize;
+    for base in (align_down(range.start, granule)
+        .expect("Memory protection granule was not a power of two")..range.end)
+        .step_by(granule)
+    {
+        mem_share(base as u64)?;
+    }
+    Ok(())
+}
+
+/// Removes permission from the KVM host to access the given memory range which was previously
+/// shared. If the range is not aligned with the memory protection granule then it will be extended
+/// on either end to align.
+#[allow(unused)]
+pub fn unshare_range(range: &MemoryRange) -> smccc::Result<()> {
+    let granule = hyp_meminfo()? as usize;
+    for base in (align_down(range.start, granule)
+        .expect("Memory protection granule was not a power of two")..range.end)
+        .step_by(granule)
+    {
+        mem_unshare(base as u64)?;
+    }
+    Ok(())
+}
+
 /// Returns an iterator which yields the base address of each 4 KiB page within the given range.
 fn page_iterator(range: &MemoryRange) -> impl Iterator<Item = usize> {
     (page_4kb_of(range.start)..range.end).step_by(SIZE_4KB)
diff --git a/pvmfw/src/mmio_guard.rs b/pvmfw/src/mmio_guard.rs
index 28f928f..e5f376e 100644
--- a/pvmfw/src/mmio_guard.rs
+++ b/pvmfw/src/mmio_guard.rs
@@ -15,9 +15,9 @@
 //! Safe MMIO_GUARD support.
 
 use crate::helpers;
+use crate::hvc::{mmio_guard_enroll, mmio_guard_info, mmio_guard_map, mmio_guard_unmap};
 use crate::smccc;
 use core::{fmt, result};
-use log::info;
 
 #[derive(Debug, Clone)]
 pub enum Error {
@@ -63,50 +63,3 @@
 pub fn unmap(addr: usize) -> Result<()> {
     mmio_guard_unmap(helpers::page_4kb_of(addr) as u64).map_err(Error::UnmapFailed)
 }
-
-fn mmio_guard_info() -> smccc::Result<u64> {
-    const VENDOR_HYP_KVM_MMIO_GUARD_INFO_FUNC_ID: u32 = 0xc6000005;
-    let args = [0u64; 17];
-
-    smccc::checked_hvc64(VENDOR_HYP_KVM_MMIO_GUARD_INFO_FUNC_ID, args)
-}
-
-fn mmio_guard_enroll() -> smccc::Result<()> {
-    const VENDOR_HYP_KVM_MMIO_GUARD_ENROLL_FUNC_ID: u32 = 0xc6000006;
-    let args = [0u64; 17];
-
-    smccc::checked_hvc64_expect_zero(VENDOR_HYP_KVM_MMIO_GUARD_ENROLL_FUNC_ID, args)
-}
-
-fn mmio_guard_map(ipa: u64) -> smccc::Result<()> {
-    const VENDOR_HYP_KVM_MMIO_GUARD_MAP_FUNC_ID: u32 = 0xc6000007;
-    let mut args = [0u64; 17];
-    args[0] = ipa;
-
-    // TODO(b/253586500): pKVM currently returns a i32 instead of a i64.
-    let is_i32_error_code = |n| u32::try_from(n).ok().filter(|v| (*v as i32) < 0).is_some();
-    match smccc::checked_hvc64_expect_zero(VENDOR_HYP_KVM_MMIO_GUARD_MAP_FUNC_ID, args) {
-        Err(smccc::Error::Unexpected(e)) if is_i32_error_code(e) => {
-            info!("Handled a pKVM bug by interpreting the MMIO_GUARD_MAP return value as i32");
-            match e as u32 as i32 {
-                -1 => Err(smccc::Error::NotSupported),
-                -2 => Err(smccc::Error::NotRequired),
-                -3 => Err(smccc::Error::InvalidParameter),
-                ret => Err(smccc::Error::Unknown(ret as i64)),
-            }
-        }
-        res => res,
-    }
-}
-
-fn mmio_guard_unmap(ipa: u64) -> smccc::Result<()> {
-    const VENDOR_HYP_KVM_MMIO_GUARD_UNMAP_FUNC_ID: u32 = 0xc6000008;
-    let mut args = [0u64; 17];
-    args[0] = ipa;
-
-    // TODO(b/251426790): pKVM currently returns NOT_SUPPORTED for SUCCESS.
-    match smccc::checked_hvc64_expect_zero(VENDOR_HYP_KVM_MMIO_GUARD_UNMAP_FUNC_ID, args) {
-        Err(smccc::Error::NotSupported) | Ok(_) => Ok(()),
-        x => x,
-    }
-}
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/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 25f2310..f3bbbf1 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();
@@ -313,6 +363,37 @@
 
     @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
+    @CddTest(requirements = {"9.17/C-1-1"})
     public void vmUnitTests() throws Exception {
         VirtualMachineConfig.Builder builder =
                 newVmConfigBuilder().setPayloadBinaryPath("binary/path");
@@ -335,6 +416,48 @@
 
     @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";
+
+        // 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);
+
+        // Subsequent gets should return this new one.
+        assertThat(vmm.get(vmName)).isSameInstanceAs(vm2);
+        assertThat(vmm.getOrCreate(vmName, config)).isSameInstanceAs(vm2);
+
+        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();
@@ -423,10 +546,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
@@ -451,20 +574,6 @@
     }
 
     @Test
-    @CddTest(requirements = {
-            "9.17/C-1-1",
-    })
-    public void invalidApkPathIsRejected() {
-        VirtualMachineConfig.Builder builder =
-                newVmConfigBuilder()
-                        .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
-                        .setDebugLevel(DEBUG_LEVEL_FULL)
-                        .setMemoryMib(minMemoryRequired());
-        assertThrows(
-                IllegalArgumentException.class, () -> builder.setApkPath("relative/path/to.apk"));
-    }
-
-    @Test
     @CddTest(requirements = {"9.17/C-1-1"})
     public void invalidVmNameIsRejected() {
         VirtualMachineManager vmm = getVirtualMachineManager();
@@ -1008,6 +1117,25 @@
         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)
             throws IOException {
         File file1 = getVmFile(vmName1, fileName);
@@ -1024,6 +1152,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];
@@ -1052,6 +1190,7 @@
         String mExtraApkTestProp;
         String mApkContentsPath;
         String mEncryptedStoragePath;
+        String[] mEffectiveCapabilities;
     }
 
     private TestResults runVmTestService(VirtualMachine vm) throws Exception {
@@ -1075,6 +1214,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 34b6fa5..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
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 4c6b961..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)
 }
@@ -164,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
@@ -257,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 {
@@ -461,18 +489,14 @@
 
 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();
 
@@ -525,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;
@@ -852,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(());
@@ -987,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))
     }
 }
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 5125f19..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
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_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..e0c2613 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,12 +53,13 @@
  *
  * \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
@@ -69,8 +71,8 @@
  * \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 +83,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 +96,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 66c8ef7..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)
     }
@@ -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/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 7c05545..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