Start using virtmgr for running VMs

Make the final changes to start running VMs using virtmgr:

  * Have virtualizationservice host the VirtualizationServiceInternal
    AIDL service.

  * Remove memlock rlimit of virtmgr (instead of virtualizationservice)
    via a method on VirtualizationServiceInternal.

  * Have VirtualizationServiceInternal create the VM's temporary folder
    and change its owner to the client's UID. The files keep the same
    virtualizationservice_data_file SELinux label, but are now owned by
    the client's virtmgr instance. To this end, virtualizationservice
    requires CAP_CHOWN.

  * Switch all users to the new vmclient/javalib API for spawning
    virtmgr.

Bug: 245727626
Test: atest -p packages/modules/Virtualization:avf-presubmit
Change-Id: I93b2cadb67a8c125e1a86f9c1ba9cb98336f0cd4
diff --git a/apex/canned_fs_config b/apex/canned_fs_config
index ce942d3..5afd9d6 100644
--- a/apex/canned_fs_config
+++ b/apex/canned_fs_config
@@ -1 +1 @@
-/bin/virtualizationservice 0 2000 0755 capabilities=0x1000000  # CAP_SYS_RESOURCE
+/bin/virtualizationservice 0 2000 0755 capabilities=0x1000001  # CAP_CHOWN, CAP_SYS_RESOURCE
diff --git a/compos/composd/src/composd_main.rs b/compos/composd/src/composd_main.rs
index 5315828..b558f06 100644
--- a/compos/composd/src/composd_main.rs
+++ b/compos/composd/src/composd_main.rs
@@ -45,8 +45,11 @@
 
     ProcessState::start_thread_pool();
 
+    let virtmgr =
+        vmclient::VirtualizationService::new().context("Failed to spawn VirtualizationService")?;
     let virtualization_service =
-        vmclient::connect().context("Failed to find VirtualizationService")?;
+        virtmgr.connect().context("Failed to connect to VirtualizationService")?;
+
     let instance_manager = Arc::new(InstanceManager::new(virtualization_service));
     let composd_service = service::new_binder(instance_manager);
     register_lazy_service("android.system.composd", composd_service.as_binder())
diff --git a/compos/verify/verify.rs b/compos/verify/verify.rs
index 5b7a8ad..745d5e9 100644
--- a/compos/verify/verify.rs
+++ b/compos/verify/verify.rs
@@ -106,7 +106,8 @@
     // We need to start the thread pool to be able to receive Binder callbacks
     ProcessState::start_thread_pool();
 
-    let virtualization_service = vmclient::connect()?;
+    let virtmgr = vmclient::VirtualizationService::new()?;
+    let virtualization_service = virtmgr.connect()?;
     let vm_instance = ComposClient::start(
         &*virtualization_service,
         instance_image,
diff --git a/javalib/jni/android_system_virtualmachine_VirtualizationService.cpp b/javalib/jni/android_system_virtualmachine_VirtualizationService.cpp
index b9d2ca4..b0e2cd2 100644
--- a/javalib/jni/android_system_virtualmachine_VirtualizationService.cpp
+++ b/javalib/jni/android_system_virtualmachine_VirtualizationService.cpp
@@ -20,6 +20,7 @@
 #include <android/binder_ibinder_jni.h>
 #include <jni.h>
 #include <log/log.h>
+#include <poll.h>
 
 #include <string>
 
@@ -85,6 +86,18 @@
     return AIBinder_toJavaBinder(env, client);
 }
 
+JNIEXPORT jboolean JNICALL android_system_virtualmachine_VirtualizationService_isOk(
+        JNIEnv* env, [[maybe_unused]] jobject obj, int clientFd) {
+    /* Setting events=0 only returns POLLERR, POLLHUP or POLLNVAL. */
+    struct pollfd pfds[] = {{.fd = clientFd, .events = 0}};
+    if (poll(pfds, /*nfds*/ 1, /*timeout*/ 0) < 0) {
+        env->ThrowNew(env->FindClass("android/system/virtualmachine/VirtualMachineException"),
+                      ("Failed to poll client FD: " + std::string(strerror(errno))).c_str());
+        return false;
+    }
+    return pfds[0].revents == 0;
+}
+
 JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/) {
     JNIEnv* env;
     if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
@@ -105,6 +118,8 @@
              reinterpret_cast<void*>(android_system_virtualmachine_VirtualizationService_spawn)},
             {"nativeConnect", "(I)Landroid/os/IBinder;",
              reinterpret_cast<void*>(android_system_virtualmachine_VirtualizationService_connect)},
+            {"nativeIsOk", "(I)Z",
+             reinterpret_cast<void*>(android_system_virtualmachine_VirtualizationService_isOk)},
     };
     int rc = env->RegisterNatives(c, methods, sizeof(methods) / sizeof(JNINativeMethod));
     if (rc != JNI_OK) {
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index b040cc0..34fb379 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -59,7 +59,6 @@
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.ServiceSpecificException;
 import android.system.virtualizationcommon.DeathReason;
 import android.system.virtualizationcommon.ErrorCode;
@@ -324,6 +323,9 @@
     @Nullable
     private Executor mCallbackExecutor;
 
+    /* Running instance of virtmgr that hosts VirtualizationService for this VM. */
+    @NonNull private VirtualizationService mVirtualizationService;
+
     private static class ExtraApkSpec {
         public final File apk;
         public final File idsig;
@@ -339,11 +341,15 @@
     }
 
     private VirtualMachine(
-            @NonNull Context context, @NonNull String name, @NonNull VirtualMachineConfig config)
+            @NonNull Context context,
+            @NonNull String name,
+            @NonNull VirtualMachineConfig config,
+            @NonNull VirtualizationService service)
             throws VirtualMachineException {
         mPackageName = context.getPackageName();
         mName = requireNonNull(name, "Name must not be null");
         mConfig = requireNonNull(config, "Config must not be null");
+        mVirtualizationService = service;
 
         File thisVmDir = getVmDir(context, mName);
         mVmRootPath = thisVmDir;
@@ -357,6 +363,7 @@
                 (config.isEncryptedStorageEnabled())
                         ? new File(thisVmDir, ENCRYPTED_STORE_FILE)
                         : null;
+
     }
 
     /**
@@ -379,7 +386,8 @@
         VirtualMachineConfig config = VirtualMachineConfig.from(vmDescriptor.getConfigFd());
         File vmDir = createVmDir(context, name);
         try {
-            VirtualMachine vm = new VirtualMachine(context, name, config);
+            VirtualMachine vm =
+                    new VirtualMachine(context, name, config, VirtualizationService.getInstance());
             config.serialize(vm.mConfigFilePath);
             try {
                 vm.mInstanceFilePath.createNewFile();
@@ -422,7 +430,8 @@
         File vmDir = createVmDir(context, name);
 
         try {
-            VirtualMachine vm = new VirtualMachine(context, name, config);
+            VirtualMachine vm =
+                    new VirtualMachine(context, name, config, VirtualizationService.getInstance());
             config.serialize(vm.mConfigFilePath);
             try {
                 vm.mInstanceFilePath.createNewFile();
@@ -438,9 +447,7 @@
                 }
             }
 
-            IVirtualizationService service =
-                    IVirtualizationService.Stub.asInterface(
-                            ServiceManager.waitForService(SERVICE_NAME));
+            IVirtualizationService service = vm.mVirtualizationService.connect();
 
             try {
                 service.initializeWritablePartition(
@@ -494,7 +501,8 @@
         }
         File configFilePath = new File(thisVmDir, CONFIG_FILE);
         VirtualMachineConfig config = VirtualMachineConfig.from(configFilePath);
-        VirtualMachine vm = new VirtualMachine(context, name, config);
+        VirtualMachine vm =
+                new VirtualMachine(context, name, config, VirtualizationService.getInstance());
 
         if (!vm.mInstanceFilePath.exists()) {
             throw new VirtualMachineException("instance image missing");
@@ -747,9 +755,7 @@
                 throw new VirtualMachineException("failed to create idsig file", e);
             }
 
-            IVirtualizationService service =
-                    IVirtualizationService.Stub.asInterface(
-                            ServiceManager.waitForService(SERVICE_NAME));
+            IVirtualizationService service = mVirtualizationService.connect();
 
             try {
                 createVmPipes();
diff --git a/javalib/src/android/system/virtualmachine/VirtualizationService.java b/javalib/src/android/system/virtualmachine/VirtualizationService.java
index 78d0c9c..06ef494 100644
--- a/javalib/src/android/system/virtualmachine/VirtualizationService.java
+++ b/javalib/src/android/system/virtualmachine/VirtualizationService.java
@@ -16,16 +16,25 @@
 
 package android.system.virtualmachine;
 
+import android.annotation.NonNull;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
 import android.system.virtualizationservice.IVirtualizationService;
 
+import com.android.internal.annotations.GuardedBy;
+
+import java.lang.ref.SoftReference;
+
 /** A running instance of virtmgr that is hosting a VirtualizationService AIDL service. */
 class VirtualizationService {
     static {
         System.loadLibrary("virtualizationservice_jni");
     }
 
+    /* Soft reference caching the last created instance of this class. */
+    @GuardedBy("VirtualMachineManager.sCreateLock")
+    private static SoftReference<VirtualizationService> sInstance;
+
     /*
      * Client FD for UDS connection to virtmgr's RpcBinder server. Closing it
      * will make virtmgr shut down.
@@ -36,11 +45,13 @@
 
     private native IBinder nativeConnect(int clientFd);
 
+    private native boolean nativeIsOk(int clientFd);
+
     /*
      * Spawns a new virtmgr subprocess that will host a VirtualizationService
      * AIDL service.
      */
-    public VirtualizationService() throws VirtualMachineException {
+    private VirtualizationService() throws VirtualMachineException {
         int clientFd = nativeSpawn();
         if (clientFd < 0) {
             throw new VirtualMachineException("Could not spawn VirtualizationService");
@@ -56,4 +67,27 @@
         }
         return IVirtualizationService.Stub.asInterface(binder);
     }
+
+    /*
+     * Checks the state of the client FD. Returns false if the FD is in erroneous state
+     * or if the other endpoint had closed its FD.
+     */
+    private boolean isOk() {
+        return nativeIsOk(mClientFd.getFd());
+    }
+
+    /*
+     * Returns an instance of this class. Might spawn a new instance if one doesn't exist, or
+     * if the previous instance had crashed.
+     */
+    @GuardedBy("VirtualMachineManager.sCreateLock")
+    @NonNull
+    static VirtualizationService getInstance() throws VirtualMachineException {
+        VirtualizationService service = (sInstance == null) ? null : sInstance.get();
+        if (service == null || !service.isOk()) {
+            service = new VirtualizationService();
+            sInstance = new SoftReference(service);
+        }
+        return service;
+    }
 }
diff --git a/rialto/tests/test.rs b/rialto/tests/test.rs
index 687ce86..0447cd3 100644
--- a/rialto/tests/test.rs
+++ b/rialto/tests/test.rs
@@ -48,7 +48,10 @@
     // We need to start the thread pool for Binder to work properly, especially link_to_death.
     ProcessState::start_thread_pool();
 
-    let service = vmclient::connect().context("Failed to find VirtualizationService")?;
+    let virtmgr =
+        vmclient::VirtualizationService::new().context("Failed to spawn VirtualizationService")?;
+    let service = virtmgr.connect().context("Failed to connect to VirtualizationService")?;
+
     let rialto = File::open(RIALTO_PATH).context("Failed to open Rialto kernel binary")?;
     let console = android_log_fd()?;
     let log = android_log_fd()?;
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IGlobalVmContext.aidl b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IGlobalVmContext.aidl
index 1a7aa4a..a4d5d19 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IGlobalVmContext.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IGlobalVmContext.aidl
@@ -18,4 +18,7 @@
 interface IGlobalVmContext {
     /** Get the CID allocated to the VM. */
     int getCid();
+
+    /** Get the path to the temporary folder of the VM. */
+    String getTemporaryDirectory();
 }
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
index ff56b68..d6b3536 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
@@ -22,6 +22,13 @@
 
 interface IVirtualizationServiceInternal {
     /**
+     * Removes the memlock rlimit of the calling process.
+     *
+     * The SELinux policy only allows this to succeed for virtmgr callers.
+     */
+    void removeMemlockRlimit();
+
+    /**
      * Allocates global context for a new VM.
      *
      * This allocates VM's globally unique resources such as the CID.
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index d7c7125..733d9c5 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -47,7 +47,7 @@
     AtomVmCreationRequested::AtomVmCreationRequested,
     AtomVmExited::AtomVmExited,
     IGlobalVmContext::{BnGlobalVmContext, IGlobalVmContext},
-    IVirtualizationServiceInternal::{BnVirtualizationServiceInternal, IVirtualizationServiceInternal},
+    IVirtualizationServiceInternal::IVirtualizationServiceInternal,
 };
 use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::{
         BnVirtualMachineService, IVirtualMachineService, VM_TOMBSTONES_SERVICE_PORT,
@@ -55,8 +55,8 @@
 use anyhow::{anyhow, bail, Context, Result};
 use apkverify::{HashAlgorithm, V4Signature};
 use binder::{
-    self, BinderFeatures, ExceptionCode, Interface, LazyServiceGuard, ParcelFileDescriptor,
-    Status, StatusCode, Strong,
+    self, wait_for_interface, BinderFeatures, ExceptionCode, Interface, LazyServiceGuard,
+    ParcelFileDescriptor, Status, StatusCode, Strong,
 };
 use disk::QcowFile;
 use lazy_static::lazy_static;
@@ -69,9 +69,10 @@
 use std::collections::HashMap;
 use std::convert::TryInto;
 use std::ffi::CStr;
-use std::fs::{create_dir, File, OpenOptions};
+use std::fs::{create_dir, read_dir, remove_dir, remove_file, set_permissions, File, OpenOptions, Permissions};
 use std::io::{Error, ErrorKind, Read, Write};
 use std::num::NonZeroU32;
+use std::os::unix::fs::PermissionsExt;
 use std::os::unix::io::{FromRawFd, IntoRawFd};
 use std::path::{Path, PathBuf};
 use std::sync::{Arc, Mutex, Weak};
@@ -79,10 +80,13 @@
 use vmconfig::VmConfig;
 use vsock::{VsockListener, VsockStream};
 use zip::ZipArchive;
+use nix::unistd::{chown, Uid};
 
 /// 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";
 
@@ -110,10 +114,9 @@
 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())
-    };
+    pub static ref GLOBAL_SERVICE: Strong<dyn IVirtualizationServiceInternal> =
+        wait_for_interface(BINDER_SERVICE_IDENTIFIER)
+            .expect("Could not connect to VirtualizationServiceInternal");
 }
 
 fn is_valid_guest_cid(cid: Cid) -> bool {
@@ -155,6 +158,9 @@
 }
 
 impl VirtualizationServiceInternal {
+    // TODO(b/245727626): Remove after the source files for virtualizationservice
+    // and virtmgr binaries are split from each other.
+    #[allow(dead_code)]
     pub fn init() -> VirtualizationServiceInternal {
         let service = VirtualizationServiceInternal::default();
 
@@ -171,12 +177,32 @@
 impl Interface for VirtualizationServiceInternal {}
 
 impl IVirtualizationServiceInternal for VirtualizationServiceInternal {
+    fn removeMemlockRlimit(&self) -> binder::Result<()> {
+        let pid = get_calling_pid();
+        let lim = libc::rlimit { rlim_cur: libc::RLIM_INFINITY, rlim_max: libc::RLIM_INFINITY };
+
+        // SAFETY - borrowing the new limit struct only
+        let ret = unsafe { libc::prlimit(pid, libc::RLIMIT_MEMLOCK, &lim, std::ptr::null_mut()) };
+
+        match ret {
+            0 => Ok(()),
+            -1 => Err(Status::new_exception_str(
+                ExceptionCode::ILLEGAL_STATE,
+                Some(std::io::Error::last_os_error().to_string()),
+            )),
+            n => Err(Status::new_exception_str(
+                ExceptionCode::ILLEGAL_STATE,
+                Some(format!("Unexpected return value from prlimit(): {n}")),
+            )),
+        }
+    }
+
     fn allocateGlobalVmContext(&self) -> binder::Result<Strong<dyn IGlobalVmContext>> {
+        let client_uid = Uid::from_raw(get_calling_uid());
         let state = &mut *self.state.lock().unwrap();
-        let cid = state.allocate_cid().map_err(|e| {
+        state.allocate_vm_context(client_uid).map_err(|e| {
             Status::new_exception_str(ExceptionCode::ILLEGAL_STATE, Some(e.to_string()))
-        })?;
-        Ok(GlobalVmContext::create(cid))
+        })
     }
 
     fn atomVmBooted(&self, atom: &AtomVmBooted) -> Result<(), Status> {
@@ -258,6 +284,50 @@
     {
         range.find(|cid| !self.held_cids.contains_key(cid))
     }
+
+    fn allocate_vm_context(&mut self, client_uid: Uid) -> Result<Strong<dyn IGlobalVmContext>> {
+        let cid = self.allocate_cid()?;
+        let temp_dir = create_vm_directory(client_uid, *cid)?;
+        let binder = GlobalVmContext { cid, temp_dir, ..Default::default() };
+        Ok(BnGlobalVmContext::new_binder(binder, BinderFeatures::default()))
+    }
+}
+
+fn create_vm_directory(client_uid: Uid, cid: Cid) -> Result<PathBuf> {
+    let path: PathBuf = format!("{}/{}", TEMPORARY_DIRECTORY, cid).into();
+    if path.as_path().exists() {
+        remove_temporary_dir(&path).unwrap_or_else(|e| {
+            warn!("Could not delete temporary directory {:?}: {}", path, e);
+        });
+    }
+    // Create a directory that is owned by client's UID but system's GID, and permissions 0700.
+    // If the chown() fails, this will leave behind an empty directory that will get removed
+    // at the next attempt, or if virtualizationservice is restarted.
+    create_dir(&path)
+        .with_context(|| format!("Could not create temporary directory {:?}", path))?;
+    chown(&path, Some(client_uid), None)
+        .with_context(|| format!("Could not set ownership of temporary directory {:?}", path))?;
+    Ok(path)
+}
+
+/// Removes a directory owned by a different user by first changing its owner back
+/// to VirtualizationService.
+pub fn remove_temporary_dir(path: &PathBuf) -> Result<()> {
+    if !path.as_path().is_dir() {
+        bail!("Path {:?} is not a directory", path);
+    }
+    chown(path, Some(Uid::current()), None)?;
+    set_permissions(path, Permissions::from_mode(0o700))?;
+    remove_temporary_files(path)?;
+    remove_dir(path)?;
+    Ok(())
+}
+
+pub fn remove_temporary_files(path: &PathBuf) -> Result<()> {
+    for dir_entry in read_dir(path)? {
+        remove_file(dir_entry?.path())?;
+    }
+    Ok(())
 }
 
 /// Implementation of the AIDL `IGlobalVmContext` interface.
@@ -265,24 +335,23 @@
 struct GlobalVmContext {
     /// The unique CID assigned to the VM for vsock communication.
     cid: Arc<Cid>,
+    /// The temporary folder created for the VM and owned by the creator's UID.
+    temp_dir: PathBuf,
     /// Keeps our service process running as long as this VM context exists.
     #[allow(dead_code)]
     lazy_service_guard: LazyServiceGuard,
 }
 
-impl GlobalVmContext {
-    fn create(cid: Arc<Cid>) -> Strong<dyn IGlobalVmContext> {
-        let binder = GlobalVmContext { cid, ..Default::default() };
-        BnGlobalVmContext::new_binder(binder, BinderFeatures::default())
-    }
-}
-
 impl Interface for GlobalVmContext {}
 
 impl IGlobalVmContext for GlobalVmContext {
     fn getCid(&self) -> binder::Result<i32> {
         Ok(*self.cid as i32)
     }
+
+    fn getTemporaryDirectory(&self) -> binder::Result<String> {
+        Ok(self.temp_dir.to_string_lossy().to_string())
+    }
 }
 
 /// Implementation of `IVirtualizationService`, the entry point of the AIDL service.
@@ -488,16 +557,20 @@
 }
 
 impl VirtualizationService {
+    // TODO(b/245727626): Remove after the source files for virtualizationservice
+    // and virtmgr binaries are split from each other.
+    #[allow(dead_code)]
     pub fn init() -> VirtualizationService {
         VirtualizationService::default()
     }
 
-    fn create_vm_context(&self) -> Result<(VmContext, Cid)> {
+    fn create_vm_context(&self) -> Result<(VmContext, Cid, PathBuf)> {
         const NUM_ATTEMPTS: usize = 5;
 
         for _ in 0..NUM_ATTEMPTS {
             let global_context = GLOBAL_SERVICE.allocateGlobalVmContext()?;
             let cid = global_context.getCid()? as Cid;
+            let temp_dir: PathBuf = global_context.getTemporaryDirectory()?.into();
             let service = VirtualMachineService::new_binder(self.state.clone(), cid).as_binder();
 
             // Start VM service listening for connections from the new CID on port=CID.
@@ -505,7 +578,7 @@
             match RpcServer::new_vsock(service, cid, port) {
                 Ok(vm_server) => {
                     vm_server.start();
-                    return Ok((VmContext::new(global_context, vm_server), cid));
+                    return Ok((VmContext::new(global_context, vm_server), cid, temp_dir));
                 }
                 Err(err) => {
                     warn!("Could not start RpcServer on port {}: {}", port, err);
@@ -538,7 +611,7 @@
             check_use_custom_virtual_machine()?;
         }
 
-        let (vm_context, cid) = self.create_vm_context().map_err(|e| {
+        let (vm_context, cid, temporary_directory) = self.create_vm_context().map_err(|e| {
             error!("Failed to create VmContext: {:?}", e);
             Status::new_service_specific_error_str(
                 -1,
@@ -558,24 +631,6 @@
         // child process, and not closed before it is started.
         let mut indirect_files = vec![];
 
-        // Make directory for temporary files.
-        let temporary_directory: PathBuf = format!("{}/{}", TEMPORARY_DIRECTORY, cid).into();
-        create_dir(&temporary_directory).map_err(|e| {
-            // At this point, we do not know the protected status of Vm
-            // setting it to false, though this may not be correct.
-            error!(
-                "Failed to create temporary directory {:?} for VM files: {:?}",
-                temporary_directory, e
-            );
-            Status::new_service_specific_error_str(
-                -1,
-                Some(format!(
-                    "Failed to create temporary directory {:?} for VM files: {:?}",
-                    temporary_directory, e
-                )),
-            )
-        })?;
-
         let (is_app_config, config) = match config {
             VirtualMachineConfig::RawConfig(config) => (false, BorrowedOrOwned::Borrowed(config)),
             VirtualMachineConfig::AppConfig(config) => {
@@ -947,15 +1002,11 @@
 #[derive(Debug)]
 struct VirtualMachine {
     instance: Arc<VmInstance>,
-    /// Keeps our service process running as long as this VM instance exists.
-    #[allow(dead_code)]
-    lazy_service_guard: LazyServiceGuard,
 }
 
 impl VirtualMachine {
     fn create(instance: Arc<VmInstance>) -> Strong<dyn IVirtualMachine> {
-        let binder = VirtualMachine { instance, lazy_service_guard: Default::default() };
-        BnVirtualMachine::new_binder(binder, BinderFeatures::default())
+        BnVirtualMachine::new_binder(VirtualMachine { instance }, BinderFeatures::default())
     }
 }
 
diff --git a/virtualizationservice/src/crosvm.rs b/virtualizationservice/src/crosvm.rs
index 94248f8..98e7d99 100644
--- a/virtualizationservice/src/crosvm.rs
+++ b/virtualizationservice/src/crosvm.rs
@@ -14,7 +14,7 @@
 
 //! Functions for running instances of `crosvm`.
 
-use crate::aidl::{Cid, VirtualMachineCallbacks};
+use crate::aidl::{remove_temporary_files, Cid, VirtualMachineCallbacks};
 use crate::atom::write_vm_exited_stats;
 use anyhow::{anyhow, bail, Context, Error, Result};
 use command_fds::CommandFdExt;
@@ -29,7 +29,7 @@
 use std::borrow::Cow;
 use std::cmp::max;
 use std::fmt;
-use std::fs::{read_to_string, remove_dir_all, File};
+use std::fs::{read_to_string, File};
 use std::io::{self, Read};
 use std::mem;
 use std::num::NonZeroU32;
@@ -379,10 +379,10 @@
             &*vm_metric,
         );
 
-        // Delete temporary files.
-        if let Err(e) = remove_dir_all(&self.temporary_directory) {
-            error!("Error removing temporary directory {:?}: {}", self.temporary_directory, e);
-        }
+        // Delete temporary files. The folder itself is removed by VirtualizationServiceInternal.
+        remove_temporary_files(&self.temporary_directory).unwrap_or_else(|e| {
+            error!("Error removing temporary files from {:?}: {}", self.temporary_directory, e);
+        });
     }
 
     /// Waits until payload is started, or timeout expires. When timeout occurs, kill
diff --git a/virtualizationservice/src/main.rs b/virtualizationservice/src/main.rs
index 9272e65..35eeff3 100644
--- a/virtualizationservice/src/main.rs
+++ b/virtualizationservice/src/main.rs
@@ -21,19 +21,17 @@
 mod payload;
 mod selinux;
 
-use crate::aidl::{VirtualizationService, TEMPORARY_DIRECTORY};
+use crate::aidl::{remove_temporary_dir, BINDER_SERVICE_IDENTIFIER, TEMPORARY_DIRECTORY, VirtualizationServiceInternal};
 use android_logger::{Config, FilterBuilder};
-use android_system_virtualizationservice::aidl::android::system::virtualizationservice::IVirtualizationService::BnVirtualizationService;
-use anyhow::{bail, Context, Error};
+use android_system_virtualizationservice_internal::aidl::android::system::virtualizationservice_internal::IVirtualizationServiceInternal::BnVirtualizationServiceInternal;
+use anyhow::Error;
 use binder::{register_lazy_service, BinderFeatures, ProcessState, ThreadState};
 use log::{info, Level};
-use std::fs::{remove_dir_all, remove_file, read_dir};
+use std::fs::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()
 }
@@ -55,38 +53,19 @@
             ),
     );
 
-    remove_memlock_rlimit().expect("Failed to remove memlock rlimit");
     clear_temporary_files().expect("Failed to delete old temporary files");
 
-    let service = VirtualizationService::init();
-    let service = BnVirtualizationService::new_binder(service, BinderFeatures::default());
+    let service = VirtualizationServiceInternal::init();
+    let service = BnVirtualizationServiceInternal::new_binder(service, BinderFeatures::default());
     register_lazy_service(BINDER_SERVICE_IDENTIFIER, service.as_binder()).unwrap();
     info!("Registered Binder service, joining threadpool.");
     ProcessState::join_thread_pool();
 }
 
-/// Set this PID's RLIMIT_MEMLOCK to RLIM_INFINITY to allow crosvm (a child process) to mlock()
-/// arbitrary amounts of memory. This is necessary for spawning protected VMs.
-fn remove_memlock_rlimit() -> Result<(), Error> {
-    let lim = libc::rlimit { rlim_cur: libc::RLIM_INFINITY, rlim_max: libc::RLIM_INFINITY };
-    // SAFETY - borrowing the new limit struct only
-    match unsafe { libc::setrlimit(libc::RLIMIT_MEMLOCK, &lim) } {
-        0 => Ok(()),
-        -1 => Err(std::io::Error::last_os_error()).context("setrlimit failed"),
-        n => bail!("Unexpected return value from setrlimit(): {}", n),
-    }
-}
-
 /// Remove any files under `TEMPORARY_DIRECTORY`.
 fn clear_temporary_files() -> Result<(), Error> {
     for dir_entry in read_dir(TEMPORARY_DIRECTORY)? {
-        let dir_entry = dir_entry?;
-        let path = dir_entry.path();
-        if dir_entry.file_type()?.is_dir() {
-            remove_dir_all(path)?;
-        } else {
-            remove_file(path)?;
-        }
+        remove_temporary_dir(&dir_entry?.path())?
     }
     Ok(())
 }
diff --git a/virtualizationservice/src/virtmgr.rs b/virtualizationservice/src/virtmgr.rs
index 1aa3df9..90b4789 100644
--- a/virtualizationservice/src/virtmgr.rs
+++ b/virtualizationservice/src/virtmgr.rs
@@ -21,10 +21,10 @@
 mod payload;
 mod selinux;
 
-use crate::aidl::VirtualizationService;
+use crate::aidl::{GLOBAL_SERVICE, VirtualizationService};
 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::IVirtualizationService::BnVirtualizationService;
 use anyhow::{bail, Context};
-use binder::BinderFeatures;
+use binder::{BinderFeatures, ProcessState};
 use lazy_static::lazy_static;
 use log::{info, Level};
 use rpcbinder::{FileDescriptorTransportMode, RpcServer};
@@ -102,6 +102,11 @@
     let ready_fd = take_fd_ownership(args.ready_fd, &mut owned_fds)
         .expect("Failed to take ownership of ready_fd");
 
+    // Start thread pool for kernel Binder connection to VirtualizationServiceInternal.
+    ProcessState::start_thread_pool();
+
+    GLOBAL_SERVICE.removeMemlockRlimit().expect("Failed to remove memlock rlimit");
+
     let service = VirtualizationService::init();
     let service =
         BnVirtualizationService::new_binder(service, BinderFeatures::default()).as_binder();
diff --git a/vm/src/main.rs b/vm/src/main.rs
index 3d2fc00..002e505 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -245,7 +245,9 @@
     // We need to start the thread pool for Binder to work properly, especially link_to_death.
     ProcessState::start_thread_pool();
 
-    let service = vmclient::connect().context("Failed to find VirtualizationService")?;
+    let virtmgr =
+        vmclient::VirtualizationService::new().context("Failed to spawn VirtualizationService")?;
+    let service = virtmgr.connect().context("Failed to connect to VirtualizationService")?;
 
     match opt {
         Opt::RunApp {
diff --git a/vmbase/example/tests/test.rs b/vmbase/example/tests/test.rs
index d58c8e6..c6aea8c 100644
--- a/vmbase/example/tests/test.rs
+++ b/vmbase/example/tests/test.rs
@@ -50,7 +50,9 @@
     // We need to start the thread pool for Binder to work properly, especially link_to_death.
     ProcessState::start_thread_pool();
 
-    let service = vmclient::connect().context("Failed to find VirtualizationService")?;
+    let virtmgr =
+        vmclient::VirtualizationService::new().context("Failed to spawn VirtualizationService")?;
+    let service = virtmgr.connect().context("Failed to connect to VirtualizationService")?;
 
     // Start example VM.
     let bootloader = ParcelFileDescriptor::new(