Merge "Migrate MicrodroidHostTestCases to MicrodroidBuilder"
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/compos/compos_key_helper/Android.bp b/compos/compos_key_helper/Android.bp
index c9480fc..cffa1e3 100644
--- a/compos/compos_key_helper/Android.bp
+++ b/compos/compos_key_helper/Android.bp
@@ -24,6 +24,7 @@
defaults: ["compos_key_defaults"],
srcs: ["compos_key_main.cpp"],
+ header_libs: ["vm_payload_restricted_headers"],
static_libs: [
"libcompos_key",
],
diff --git a/compos/compos_key_helper/compos_key_main.cpp b/compos/compos_key_helper/compos_key_main.cpp
index 4fb0762..9417584 100644
--- a/compos/compos_key_helper/compos_key_main.cpp
+++ b/compos/compos_key_helper/compos_key_main.cpp
@@ -17,7 +17,7 @@
#include <android-base/file.h>
#include <android-base/logging.h>
#include <unistd.h>
-#include <vm_payload.h>
+#include <vm_payload_restricted.h>
#include <string_view>
#include <vector>
diff --git a/compos/tests/AndroidTest.xml b/compos/tests/AndroidTest.xml
index 2a84291..f9e6837 100644
--- a/compos/tests/AndroidTest.xml
+++ b/compos/tests/AndroidTest.xml
@@ -14,6 +14,8 @@
limitations under the License.
-->
<configuration description="Tests for CompOS">
+ <option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.art.apex" />
+
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
<option name="force-root" value="true" />
</target_preparer>
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..214e7e6 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -282,14 +282,7 @@
@GuardedBy("sCreateLock")
@NonNull
private static Map<String, WeakReference<VirtualMachine>> getInstancesMap(Context context) {
- Map<String, WeakReference<VirtualMachine>> instancesMap;
- if (sInstances.containsKey(context)) {
- instancesMap = sInstances.get(context);
- } else {
- instancesMap = new HashMap<>();
- sInstances.put(context, instancesMap);
- }
- return instancesMap;
+ return sInstances.computeIfAbsent(context, unused -> new HashMap<>());
}
@NonNull
@@ -901,6 +894,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/kernel/README.md b/microdroid/kernel/README.md
index 1f6b2ee..38eca71 100644
--- a/microdroid/kernel/README.md
+++ b/microdroid/kernel/README.md
@@ -24,6 +24,7 @@
For x86\_64,
```bash
+tools/bazel clean
tools/bazel run --config=fast --lto=thin //common-modules/virtual-device:microdroid_x86_64_dist -- --dist_dir=out/dist
```
@@ -36,7 +37,15 @@
### Change the kernel configs
+For ARM64
+```bash
+tools/bazel run //common-modules/virtual-device:microdroid_aarch64_config menuconfig
+```
+For x86\_64
+```bash
+tools/bazel run //common-modules/virtual-device:microdroid_x86_64_config menuconfig
+```
## How to update Microdroid kernel prebuilts
diff --git a/microdroid/vm_payload/Android.bp b/microdroid/vm_payload/Android.bp
index 8d78444..e153f92 100644
--- a/microdroid/vm_payload/Android.bp
+++ b/microdroid/vm_payload/Android.bp
@@ -29,7 +29,7 @@
rust_bindgen {
name: "libvm_payload_bindgen",
- wrapper_src: "include/vm_payload.h",
+ wrapper_src: "include-restricted/vm_payload_restricted.h",
crate_name: "vm_payload_bindgen",
source_stem: "bindings",
apex_available: ["com.android.compos"],
@@ -41,5 +41,15 @@
cc_library_headers {
name: "vm_payload_headers",
+ apex_available: ["com.android.compos"],
export_include_dirs: ["include"],
}
+
+cc_library_headers {
+ name: "vm_payload_restricted_headers",
+ header_libs: ["vm_payload_headers"],
+ export_header_lib_headers: ["vm_payload_headers"],
+ export_include_dirs: ["include-restricted"],
+ apex_available: ["com.android.compos"],
+ visibility: ["//packages/modules/Virtualization:__subpackages__"],
+}
diff --git a/microdroid/vm_payload/include-restricted/vm_payload_restricted.h b/microdroid/vm_payload/include-restricted/vm_payload_restricted.h
new file mode 100644
index 0000000..8170a64
--- /dev/null
+++ b/microdroid/vm_payload/include-restricted/vm_payload_restricted.h
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <sys/cdefs.h>
+
+#include "vm_payload.h"
+
+// The functions declared here are restricted to VMs created with a config file;
+// they will fail if called in other VMs. The ability to create such VMs
+// requires the android.permission.USE_CUSTOM_VIRTUAL_MACHINE permission, and is
+// therefore not available to privileged or third party apps.
+
+// These functions can be used by tests, if the permission is granted via shell.
+
+__BEGIN_DECLS
+
+/**
+ * Get the VM's DICE attestation chain.
+ *
+ * \param data pointer to size bytes where the chain is written.
+ * \param size number of bytes that can be written to data.
+ * \param total outputs the total size of the chain if the function succeeds
+ *
+ * \return true on success and false on failure.
+ */
+bool AVmPayload_getDiceAttestationChain(void *data, size_t size, size_t *total);
+
+/**
+ * Get the VM's DICE attestation CDI.
+ *
+ * \param data pointer to size bytes where the CDI is written.
+ * \param size number of bytes that can be written to data.
+ * \param total outputs the total size of the CDI if the function succeeds
+ *
+ * \return true on success and false on failure.
+ */
+bool AVmPayload_getDiceAttestationCdi(void *data, size_t size, size_t *total);
+
+__END_DECLS
diff --git a/microdroid/vm_payload/include/vm_payload.h b/microdroid/vm_payload/include/vm_payload.h
index 2aeb44e..82dbd6d 100644
--- a/microdroid/vm_payload/include/vm_payload.h
+++ b/microdroid/vm_payload/include/vm_payload.h
@@ -18,12 +18,11 @@
#include <stdbool.h>
#include <stddef.h>
+#include <sys/cdefs.h>
#include "vm_main.h"
-#ifdef __cplusplus
-extern "C" {
-#endif
+__BEGIN_DECLS
struct AIBinder;
typedef struct AIBinder AIBinder;
@@ -71,32 +70,6 @@
size_t size);
/**
- * Get the VM's DICE attestation chain.
- *
- * This function will fail if the use of restricted APIs is not permitted.
- *
- * \param data pointer to size bytes where the chain is written.
- * \param size number of bytes that can be written to data.
- * \param total outputs the total size of the chain if the function succeeds
- *
- * \return true on success and false on failure.
- */
-bool AVmPayload_getDiceAttestationChain(void *data, size_t size, size_t *total);
-
-/**
- * Get the VM's DICE attestation CDI.
- *
- * This function will fail if the use of restricted APIs is not permitted.
- *
- * \param data pointer to size bytes where the CDI is written.
- * \param size number of bytes that can be written to data.
- * \param total outputs the total size of the CDI if the function succeeds
- *
- * \return true on success and false on failure.
- */
-bool AVmPayload_getDiceAttestationCdi(void *data, size_t size, size_t *total);
-
-/**
* Gets the path to the APK contents. It is a directory, under which are
* the unzipped contents of the APK containing the payload, all read-only
* but accessible to the payload.
@@ -107,6 +80,4 @@
*/
const char *AVmPayload_getApkContentsPath(void);
-#ifdef __cplusplus
-} // extern "C"
-#endif
+__END_DECLS
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/Android.bp b/tests/testapk/Android.bp
index d1d5459..8972046 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -35,6 +35,7 @@
name: "MicrodroidTestNativeLib",
srcs: ["src/native/testbinary.cpp"],
stl: "libc++_static",
+ header_libs: ["vm_payload_restricted_headers"],
shared_libs: [
"libbinder_ndk",
"MicrodroidTestNativeLibSub",
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..cc623a8 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,12 @@
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
+import android.content.Context;
import android.os.Build;
import android.os.ParcelFileDescriptor;
+import android.os.ServiceSpecificException;
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 +38,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 +59,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 +296,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);
@@ -332,7 +338,10 @@
}
};
listener.runToFinish(TAG, vm);
- assertThat(exception.getNow(null)).isNull();
+ Exception e = exception.getNow(null);
+ if (e != null) {
+ throw e;
+ }
return vmCdis;
}
@@ -433,6 +442,24 @@
}
}
+ @Test
+ @CddTest(requirements = {
+ "9.17/C-1-1",
+ "9.17/C-1-2"
+ })
+ public void accessToCdisIsRestricted() throws Exception {
+ assumeSupportedKernel();
+
+ VirtualMachineConfig config = mInner.newVmConfigBuilder()
+ .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .build();
+ mInner.forceCreateNewVirtualMachine("test_vm", config);
+
+ assertThrows(ServiceSpecificException.class, () -> launchVmAndGetCdis("test_vm"));
+ }
+
+
private static final UUID MICRODROID_PARTITION_UUID =
UUID.fromString("cf9afe9a-0662-11ec-a329-c32663a09d75");
private static final UUID U_BOOT_AVB_PARTITION_UUID =
@@ -476,10 +503,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 +599,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/tests/testapk/src/native/testbinary.cpp b/tests/testapk/src/native/testbinary.cpp
index 1a3e940..48942dc 100644
--- a/tests/testapk/src/native/testbinary.cpp
+++ b/tests/testapk/src/native/testbinary.cpp
@@ -28,7 +28,7 @@
#include <sys/system_properties.h>
#include <unistd.h>
#include <vm_main.h>
-#include <vm_payload.h>
+#include <vm_payload_restricted.h>
#include <string>
@@ -79,7 +79,7 @@
if (!AVmPayload_getVmInstanceSecret(identifier, sizeof(identifier), out->data(),
out->size())) {
return ndk::ScopedAStatus::
- fromServiceSpecificErrorWithMessage(0, "Failed to VM instance secret");
+ fromServiceSpecificErrorWithMessage(0, "Failed to get VM instance secret");
}
return ndk::ScopedAStatus::ok();
}
diff --git a/virtualizationservice/src/atom.rs b/virtualizationservice/src/atom.rs
index eabb4cc..20f88e7 100644
--- a/virtualizationservice/src/atom.rs
+++ b/virtualizationservice/src/atom.rs
@@ -26,7 +26,9 @@
use binder::{ParcelFileDescriptor, ThreadState};
use log::{trace, warn};
use microdroid_payload_config::VmPayloadConfig;
+use rustutils::system_properties;
use statslog_virtualization_rust::{vm_booted, vm_creation_requested, vm_exited};
+use std::thread;
use std::time::{Duration, SystemTime};
use zip::ZipArchive;
@@ -86,17 +88,16 @@
binder_exception_code = e.exception_code() as i32;
}
}
-
let (vm_identifier, config_type, num_cpus, memory_mib, apexes) = match config {
VirtualMachineConfig::AppConfig(config) => (
- &config.name,
+ config.name.clone(),
vm_creation_requested::ConfigType::VirtualMachineAppConfig,
config.numCpus,
config.memoryMib,
get_apex_list(config),
),
VirtualMachineConfig::RawConfig(config) => (
- &config.name,
+ config.name.clone(),
vm_creation_requested::ConfigType::VirtualMachineRawConfig,
config.numCpus,
config.memoryMib,
@@ -104,105 +105,139 @@
),
};
- let vm_creation_requested = vm_creation_requested::VmCreationRequested {
- uid: ThreadState::get_calling_uid() as i32,
- 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 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
+ };
- match vm_creation_requested.stats_write() {
- Err(e) => {
- warn!("statslog_rust failed with error: {}", e);
+ 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"),
}
- 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(
uid: i32,
- vm_identifier: &String,
+ vm_identifier: &str,
vm_start_timestamp: Option<SystemTime>,
) {
+ let vm_identifier = vm_identifier.to_owned();
let duration = get_duration(vm_start_timestamp);
- let vm_booted = vm_booted::VmBooted {
- uid,
- vm_identifier,
- elapsed_time_millis: duration.as_millis() as i64,
- };
- match vm_booted.stats_write() {
- Err(e) => {
- warn!("statslog_rust failed with error: {}", e);
+ 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"),
}
- 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(
uid: i32,
- vm_identifier: &String,
+ vm_identifier: &str,
reason: DeathReason,
vm_start_timestamp: Option<SystemTime>,
) {
+ let vm_identifier = vm_identifier.to_owned();
let duration = get_duration(vm_start_timestamp);
- let vm_exited = vm_exited::VmExited {
- uid,
- vm_identifier,
- elapsed_time_millis: duration.as_millis() as i64,
- 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::ERROR => 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
+ thread::spawn(move || {
+ let vm_exited = vm_exited::VmExited {
+ uid,
+ vm_identifier: &vm_identifier,
+ elapsed_time_millis: duration.as_millis() as i64,
+ 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::ERROR => 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,
+ },
+ };
+ 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);
}
- 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,
- },
- };
- match vm_exited.stats_write() {
- Err(e) => {
- warn!("statslog_rust failed with error: {}", e);
+ Ok(_) => trace!("statslog_rust succeeded for virtualization service"),
}
- Ok(_) => trace!("statslog_rust succeeded for virtualization service"),
+ });
+}
+
+fn wait_for_statsd() -> Result<()> {
+ let mut prop = system_properties::PropertyWatcher::new("init.svc.statsd")?;
+ loop {
+ prop.wait()?;
+ match system_properties::read("init.svc.statsd")? {
+ Some(s) => {
+ if s == "running" {
+ break;
+ }
+ }
+ None => {
+ // This case never really happens because
+ // prop.wait() waits for property to be non-null.
+ break;
+ }
+ }
}
+ Ok(())
}
diff --git a/virtualizationservice/src/crosvm.rs b/virtualizationservice/src/crosvm.rs
index 6f646b7..1b8061e 100644
--- a/virtualizationservice/src/crosvm.rs
+++ b/virtualizationservice/src/crosvm.rs
@@ -546,6 +546,7 @@
debug!("Preserving FDs {:?}", preserved_fds);
command.preserved_fds(preserved_fds);
+ command.arg("--params").arg("crashkernel=17M");
print_crosvm_args(&command);
let result = SharedChild::spawn(&mut command)?;
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)? {