Merge "Document how to change the kernel config of microdroid kernels"
diff --git a/apex/Android.bp b/apex/Android.bp
index 52f4384..e0ca9bf 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -123,6 +123,8 @@
         // deapexer
         "deapexer",
         "debugfs_static",
+        "blkid",
+        "fsck.erofs",
 
         // sign_virt_apex
         "avbtool",
diff --git a/apex/canned_fs_config b/apex/canned_fs_config
index 1cf63b6..ce942d3 100644
--- a/apex/canned_fs_config
+++ b/apex/canned_fs_config
@@ -1 +1 @@
-/bin/crosvm 0 2000 0755 capabilities=0x4000
+/bin/virtualizationservice 0 2000 0755 capabilities=0x1000000  # CAP_SYS_RESOURCE
diff --git a/apex/sign_virt_apex_test.sh b/apex/sign_virt_apex_test.sh
index 640a3d4..03a56ca 100644
--- a/apex/sign_virt_apex_test.sh
+++ b/apex/sign_virt_apex_test.sh
@@ -23,8 +23,11 @@
 # To access host tools
 PATH=$TEST_DIR:$PATH
 DEBUGFS=$TEST_DIR/debugfs_static
+BLKID=$TEST_DIR/blkid
+FSCKEROFS=$TEST_DIR/fsck.erofs
 
-deapexer --debugfs_path $DEBUGFS extract $TEST_DIR/com.android.virt.apex $TMP_ROOT
+deapexer --debugfs_path $DEBUGFS --blkid_path $BLKID --fsckerofs_path $FSCKEROFS \
+  extract $TEST_DIR/com.android.virt.apex $TMP_ROOT
 
 if [ "$(ls -A $TMP_ROOT/etc/fs/)" ]; then
   sign_virt_apex $TEST_DIR/test.com.android.virt.pem $TMP_ROOT
diff --git a/javalib/src/android/system/virtualmachine/ParcelVirtualMachine.java b/javalib/src/android/system/virtualmachine/ParcelVirtualMachine.java
new file mode 100644
index 0000000..808f30a
--- /dev/null
+++ b/javalib/src/android/system/virtualmachine/ParcelVirtualMachine.java
@@ -0,0 +1,92 @@
+/*
+ * 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.virtualmachine;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * A parcelable that captures the state of a Virtual Machine.
+ *
+ * <p>You can capture the current state of VM by creating an instance of this class with {@link
+ * VirtualMachine#toParcelVirtualMachine()}, optionally pass it to another App, and then build an
+ * identical VM with the parcel received.
+ *
+ * @hide
+ */
+public final class ParcelVirtualMachine implements Parcelable {
+    private final @NonNull ParcelFileDescriptor mConfigFd;
+    private final @NonNull ParcelFileDescriptor mInstanceImgFd;
+    // TODO(b/243129654): Add trusted storage fd once it is available.
+
+    @Override
+    public int describeContents() {
+        return CONTENTS_FILE_DESCRIPTOR;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        mConfigFd.writeToParcel(out, flags);
+        mInstanceImgFd.writeToParcel(out, flags);
+    }
+
+    public static final Parcelable.Creator<ParcelVirtualMachine> CREATOR =
+            new Parcelable.Creator<ParcelVirtualMachine>() {
+                public ParcelVirtualMachine createFromParcel(Parcel in) {
+                    return new ParcelVirtualMachine(in);
+                }
+
+                public ParcelVirtualMachine[] newArray(int size) {
+                    return new ParcelVirtualMachine[size];
+                }
+            };
+
+    /**
+     * @return File descriptor of the VM configuration file config.xml.
+     * @hide
+     */
+    @VisibleForTesting
+    public @NonNull ParcelFileDescriptor getConfigFd() {
+        return mConfigFd;
+    }
+
+    /**
+     * @return File descriptor of the instance.img of the VM.
+     * @hide
+     */
+    @VisibleForTesting
+    public @NonNull ParcelFileDescriptor getInstanceImgFd() {
+        return mInstanceImgFd;
+    }
+
+    ParcelVirtualMachine(
+            @NonNull ParcelFileDescriptor configFd, @NonNull ParcelFileDescriptor instanceImgFd) {
+        mConfigFd = configFd;
+        mInstanceImgFd = instanceImgFd;
+    }
+
+    private ParcelVirtualMachine(Parcel in) {
+        mConfigFd = requireNonNull(in.readFileDescriptor());
+        mInstanceImgFd = requireNonNull(in.readFileDescriptor());
+    }
+}
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index b026fe3..e750ae9 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -901,6 +901,28 @@
         }
     }
 
+    /**
+     * Captures the current state of the VM in a {@link ParcelVirtualMachine} instance.
+     * The VM needs to be stopped to avoid inconsistency in its state representation.
+     *
+     * @return a {@link ParcelVirtualMachine} instance that represents the VM's state.
+     * @throws VirtualMachineException if the virtual machine is not stopped, or the state could not
+     *     be captured.
+     */
+    @NonNull
+    public ParcelVirtualMachine toParcelVirtualMachine() throws VirtualMachineException {
+        synchronized (mLock) {
+            checkStopped();
+        }
+        try {
+            return new ParcelVirtualMachine(
+                ParcelFileDescriptor.open(mConfigFilePath, MODE_READ_ONLY),
+                ParcelFileDescriptor.open(mInstanceFilePath, MODE_READ_ONLY));
+        } catch (IOException e) {
+            throw new VirtualMachineException(e);
+        }
+    }
+
     @VirtualMachineCallback.ErrorCode
     private int getTranslatedError(int reason) {
         switch (reason) {
diff --git a/microdroid/vm_payload/src/vm_payload_service.rs b/microdroid/vm_payload/src/vm_payload_service.rs
index b0dd891..098d246 100644
--- a/microdroid/vm_payload/src/vm_payload_service.rs
+++ b/microdroid/vm_payload/src/vm_payload_service.rs
@@ -15,12 +15,12 @@
 //! This module handles the interaction with virtual machine payload service.
 
 use android_system_virtualization_payload::aidl::android::system::virtualization::payload::IVmPayloadService::{
-    IVmPayloadService, VM_PAYLOAD_SERVICE_NAME, VM_APK_CONTENTS_PATH};
+    IVmPayloadService, VM_PAYLOAD_SERVICE_SOCKET_NAME, VM_APK_CONTENTS_PATH};
 use anyhow::{Context, Result};
-use binder::{wait_for_interface, Strong, unstable_api::{AIBinder, new_spibinder}};
+use binder::{Strong, unstable_api::{AIBinder, new_spibinder}};
 use lazy_static::lazy_static;
 use log::{error, info, Level};
-use rpcbinder::run_vsock_rpc_server;
+use rpcbinder::{get_unix_domain_rpc_interface, run_vsock_rpc_server};
 use std::ffi::CString;
 use std::os::raw::{c_char, c_void};
 
@@ -203,6 +203,6 @@
 }
 
 fn get_vm_payload_service() -> Result<Strong<dyn IVmPayloadService>> {
-    wait_for_interface(VM_PAYLOAD_SERVICE_NAME)
-        .context(format!("Failed to connect to service: {}", VM_PAYLOAD_SERVICE_NAME))
+    get_unix_domain_rpc_interface(VM_PAYLOAD_SERVICE_SOCKET_NAME)
+        .context(format!("Failed to connect to service: {}", VM_PAYLOAD_SERVICE_SOCKET_NAME))
 }
diff --git a/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl b/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
index 4823bb8..f8e7d34 100644
--- a/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
+++ b/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
@@ -21,8 +21,8 @@
  * Microdroid Manager for execution.
  */
 interface IVmPayloadService {
-    /** Name of the service IVmPayloadService. */
-    const String VM_PAYLOAD_SERVICE_NAME = "virtual_machine_payload_service";
+    /** Socket name of the service IVmPayloadService. */
+    const String VM_PAYLOAD_SERVICE_SOCKET_NAME = "vm_payload_service";
 
     /** Path to the APK contents path. */
     const String VM_APK_CONTENTS_PATH = "/mnt/apk";
diff --git a/microdroid_manager/microdroid_manager.rc b/microdroid_manager/microdroid_manager.rc
index 74a219d..cfa70bd 100644
--- a/microdroid_manager/microdroid_manager.rc
+++ b/microdroid_manager/microdroid_manager.rc
@@ -6,3 +6,4 @@
     oneshot
     # SYS_BOOT is required to exec kexecload from microdroid_manager
     capabilities AUDIT_CONTROL SYS_ADMIN SYS_BOOT
+    socket vm_payload_service stream 0666 system system
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 73c36aa..4b4f996 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -31,7 +31,7 @@
 use android_system_virtualization_payload::aidl::android::system::virtualization::payload::IVmPayloadService::VM_APK_CONTENTS_PATH;
 use anyhow::{anyhow, bail, ensure, Context, Error, Result};
 use apkverify::{get_public_key_der, verify, V4Signature};
-use binder::{ProcessState, Strong};
+use binder::Strong;
 use diced_utils::cbor::{encode_header, encode_number};
 use glob::glob;
 use itertools::sorted;
@@ -397,7 +397,6 @@
     wait_for_property_true(APK_MOUNT_DONE_PROP).context("Failed waiting for APK mount done")?;
 
     register_vm_payload_service(allow_restricted_apis, service.clone(), dice)?;
-    ProcessState::start_thread_pool();
 
     system_properties::write("dev.bootcomplete", "1").context("set dev.bootcomplete")?;
     exec_task(task, service).context("Failed to run payload")
diff --git a/microdroid_manager/src/swap.rs b/microdroid_manager/src/swap.rs
index 0fd1468..d7916db 100644
--- a/microdroid_manager/src/swap.rs
+++ b/microdroid_manager/src/swap.rs
@@ -43,20 +43,21 @@
     let sysfs_size = format!("/sys/{}/size", dev);
     let len = read_to_string(&sysfs_size)?
         .trim()
-        .parse::<u32>()
-        .context(format!("No u32 in {}", &sysfs_size))?
-        * 512;
+        .parse::<u64>()
+        .context(format!("No u64 in {}", &sysfs_size))?
+        .checked_mul(512)
+        .ok_or_else(|| anyhow!("sysfs_size too large"))?;
 
-    let pagesize: libc::c_uint;
     // safe because we give a constant and known-valid sysconf parameter
-    unsafe {
-        pagesize = libc::sysconf(libc::_SC_PAGE_SIZE) as libc::c_uint;
-    }
+    let pagesize = unsafe { libc::sysconf(libc::_SC_PAGE_SIZE) as u64 };
 
     let mut f = OpenOptions::new().read(false).write(true).open(format!("/dev/{}", dev))?;
 
+    let last_page = len / pagesize - 1;
+
     // Write the info fields: [ version, last_page ]
-    let info: [u32; 2] = [1, (len / pagesize) - 1];
+    let info: [u32; 2] = [1, last_page.try_into().context("Number of pages out of range")?];
+
     f.seek(SeekFrom::Start(1024))?;
     f.write_all(&info.iter().flat_map(|v| v.to_ne_bytes()).collect::<Vec<u8>>())?;
 
diff --git a/microdroid_manager/src/vm_payload_service.rs b/microdroid_manager/src/vm_payload_service.rs
index 159bf67..fcfc79d 100644
--- a/microdroid_manager/src/vm_payload_service.rs
+++ b/microdroid_manager/src/vm_payload_service.rs
@@ -16,13 +16,17 @@
 
 use crate::dice::DiceContext;
 use android_system_virtualization_payload::aidl::android::system::virtualization::payload::IVmPayloadService::{
-    BnVmPayloadService, IVmPayloadService, VM_PAYLOAD_SERVICE_NAME};
+    BnVmPayloadService, IVmPayloadService, VM_PAYLOAD_SERVICE_SOCKET_NAME};
 use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::IVirtualMachineService;
-use anyhow::{Context, Result};
-use binder::{Interface, BinderFeatures, ExceptionCode, Status, Strong, add_service};
-use log::error;
+use anyhow::{bail, Result};
+use binder::{Interface, BinderFeatures, ExceptionCode, Status, Strong};
+use log::{error, info};
 use openssl::hkdf::hkdf;
 use openssl::md::Md;
+use rpcbinder::run_init_unix_domain_rpc_server;
+use std::sync::mpsc;
+use std::thread;
+use std::time::Duration;
 
 /// Implementation of `IVmPayloadService`.
 struct VmPayloadService {
@@ -97,8 +101,29 @@
         VmPayloadService::new(allow_restricted_apis, vm_service, dice),
         BinderFeatures::default(),
     );
-    add_service(VM_PAYLOAD_SERVICE_NAME, vm_payload_binder.as_binder())
-        .with_context(|| format!("Failed to register service {}", VM_PAYLOAD_SERVICE_NAME))?;
-    log::info!("{} is running", VM_PAYLOAD_SERVICE_NAME);
-    Ok(())
+    let (sender, receiver) = mpsc::channel();
+    thread::spawn(move || {
+        let retval = run_init_unix_domain_rpc_server(
+            vm_payload_binder.as_binder(),
+            VM_PAYLOAD_SERVICE_SOCKET_NAME,
+            || {
+                sender.send(()).unwrap();
+            },
+        );
+        if retval {
+            info!(
+                "The RPC server at '{}' has shut down gracefully.",
+                VM_PAYLOAD_SERVICE_SOCKET_NAME
+            );
+        } else {
+            error!("Premature termination of the RPC server '{}'.", VM_PAYLOAD_SERVICE_SOCKET_NAME);
+        }
+    });
+    match receiver.recv_timeout(Duration::from_millis(200)) {
+        Ok(()) => {
+            info!("The RPC server '{}' is running.", VM_PAYLOAD_SERVICE_SOCKET_NAME);
+            Ok(())
+        }
+        _ => bail!("Failed to register service '{}'", VM_PAYLOAD_SERVICE_SOCKET_NAME),
+    }
 }
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 42c0e64..dd01867 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -25,9 +25,11 @@
 
 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
 
+import android.content.Context;
 import android.os.Build;
 import android.os.ParcelFileDescriptor;
 import android.os.SystemProperties;
+import android.system.virtualmachine.ParcelVirtualMachine;
 import android.system.virtualmachine.VirtualMachine;
 import android.system.virtualmachine.VirtualMachineCallback;
 import android.system.virtualmachine.VirtualMachineConfig;
@@ -35,6 +37,8 @@
 import android.system.virtualmachine.VirtualMachineManager;
 import android.util.Log;
 
+import androidx.test.core.app.ApplicationProvider;
+
 import com.android.compatibility.common.util.CddTest;
 import com.android.microdroid.test.device.MicrodroidDeviceTestBase;
 import com.android.microdroid.testservice.ITestService;
@@ -54,6 +58,8 @@
 import java.io.IOException;
 import java.io.RandomAccessFile;
 import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.List;
 import java.util.OptionalLong;
 import java.util.UUID;
@@ -289,8 +295,7 @@
         // Try to run the VM again with the previous instance.img
         // We need to make sure that no changes on config don't invalidate the identity, to compare
         // the result with the below "different debug level" test.
-        File vmRoot = new File(getContext().getDataDir(), "vm");
-        File vmInstance = new File(new File(vmRoot, "test_vm"), "instance.img");
+        File vmInstance = getVmFile("test_vm", "instance.img");
         File vmInstanceBackup = File.createTempFile("instance", ".img");
         Files.copy(vmInstance.toPath(), vmInstanceBackup.toPath(), REPLACE_EXISTING);
         mInner.forceCreateNewVirtualMachine("test_vm", normalConfig);
@@ -476,10 +481,7 @@
 
         mInner.forceCreateNewVirtualMachine(vmName, config);
         assertThat(tryBootVm(TAG, vmName).payloadStarted).isTrue();
-
-        File vmRoot = new File(getContext().getDataDir(), "vm");
-        File vmDir = new File(vmRoot, vmName);
-        File instanceImgPath = new File(vmDir, "instance.img");
+        File instanceImgPath = getVmFile(vmName, "instance.img");
         return new RandomAccessFile(instanceImgPath, "rw");
     }
 
@@ -575,6 +577,41 @@
         assertThat(vm).isNotEqualTo(newVm);
     }
 
+    @Test
+    public void vmConvertsToValidParcelVm() throws Exception {
+        // Arrange
+        VirtualMachineConfig config =
+                mInner.newVmConfigBuilder()
+                        .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+                        .setDebugLevel(DEBUG_LEVEL_NONE)
+                        .build();
+        String vmName = "test_vm";
+        VirtualMachine vm = mInner.forceCreateNewVirtualMachine(vmName, config);
+
+        // Action
+        ParcelVirtualMachine parcelVm = vm.toParcelVirtualMachine();
+
+        // Asserts
+        assertFileContentsAreEqual(parcelVm.getConfigFd(), vmName, "config.xml");
+        assertFileContentsAreEqual(parcelVm.getInstanceImgFd(), vmName, "instance.img");
+    }
+
+    private void assertFileContentsAreEqual(
+            ParcelFileDescriptor parcelFd, String vmName, String fileName) throws IOException {
+        File file = getVmFile(vmName, fileName);
+        // Use try-with-resources to close the files automatically after assert.
+        try (FileInputStream input1 = new FileInputStream(parcelFd.getFileDescriptor());
+                FileInputStream input2 = new FileInputStream(file)) {
+            assertThat(input1.readAllBytes()).isEqualTo(input2.readAllBytes());
+        }
+    }
+
+    private File getVmFile(String vmName, String fileName) {
+        Context context = ApplicationProvider.getApplicationContext();
+        Path filePath = Paths.get(context.getDataDir().getPath(), "vm", vmName, fileName);
+        return filePath.toFile();
+    }
+
     private int minMemoryRequired() {
         if (Build.SUPPORTED_ABIS.length > 0) {
             String primaryAbi = Build.SUPPORTED_ABIS[0];
diff --git a/virtualizationservice/src/main.rs b/virtualizationservice/src/main.rs
index cea2747..714bcfd 100644
--- a/virtualizationservice/src/main.rs
+++ b/virtualizationservice/src/main.rs
@@ -24,8 +24,8 @@
 use crate::aidl::{VirtualizationService, BINDER_SERVICE_IDENTIFIER, 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 anyhow::Error;
 use log::{info, Level};
 use std::fs::{remove_dir_all, remove_file, read_dir};
 
@@ -44,6 +44,7 @@
             ),
     );
 
+    remove_memlock_rlimit().expect("Failed to remove memlock rlimit");
     clear_temporary_files().expect("Failed to delete old temporary files");
 
     let service = VirtualizationService::init();
@@ -53,6 +54,18 @@
     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)? {