Merge "Update TEST_MAPPING files"
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 b38e0e4..3295681 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -60,7 +60,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;
@@ -334,6 +333,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;
@@ -349,11 +351,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;
@@ -367,6 +373,7 @@
(config.isEncryptedStorageEnabled())
? new File(thisVmDir, ENCRYPTED_STORE_FILE)
: null;
+
}
/**
@@ -389,7 +396,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();
@@ -432,7 +440,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();
@@ -448,9 +457,7 @@
}
}
- IVirtualizationService service =
- IVirtualizationService.Stub.asInterface(
- ServiceManager.waitForService(SERVICE_NAME));
+ IVirtualizationService service = vm.mVirtualizationService.connect();
try {
service.initializeWritablePartition(
@@ -504,7 +511,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");
@@ -766,9 +774,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/pvmfw/avb/src/verify.rs b/pvmfw/avb/src/verify.rs
index d5f7283..b6db601 100644
--- a/pvmfw/avb/src/verify.rs
+++ b/pvmfw/avb/src/verify.rs
@@ -393,10 +393,12 @@
mod tests {
use super::*;
use anyhow::Result;
- use std::fs;
+ use avb_bindgen::AvbFooter;
+ use std::{fs, mem::size_of};
const PUBLIC_KEY_RSA2048_PATH: &str = "data/testkey_rsa2048_pub.bin";
const PUBLIC_KEY_RSA4096_PATH: &str = "data/testkey_rsa4096_pub.bin";
+ const RANDOM_FOOTER_POS: usize = 30;
/// This test uses the Microdroid payload compiled on the fly to check that
/// the latest payload can be verified successfully.
@@ -457,6 +459,19 @@
)
}
+ #[test]
+ fn tampered_kernel_footer_fails_verification() -> Result<()> {
+ let mut kernel = load_latest_signed_kernel()?;
+ let avb_footer_index = kernel.len() - size_of::<AvbFooter>() + RANDOM_FOOTER_POS;
+ kernel[avb_footer_index] = !kernel[avb_footer_index];
+
+ assert_payload_verification_fails(
+ &kernel,
+ &fs::read(PUBLIC_KEY_RSA4096_PATH)?,
+ AvbImageVerifyError::InvalidMetadata,
+ )
+ }
+
fn assert_payload_verification_fails(
kernel: &[u8],
trusted_public_key: &[u8],
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/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
index 3c3faf2..9cfac4e 100644
--- a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
+++ b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
@@ -65,6 +65,7 @@
private static final String APEX_ETC_FS = "/apex/com.android.virt/etc/fs/";
private static final double SIZE_MB = 1024.0 * 1024.0;
+ private static final double NANO_TO_MILLI = 1000000.0;
private static final String MICRODROID_IMG_PREFIX = "microdroid_";
private static final String MICRODROID_IMG_SUFFIX = ".img";
@@ -80,10 +81,11 @@
private Instrumentation mInstrumentation;
@Before
- public void setup() {
+ public void setup() throws IOException {
grantPermission(VirtualMachine.MANAGE_VIRTUAL_MACHINE_PERMISSION);
grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
prepareTestSetup(mProtectedVm);
+ setMaxPerformanceTaskProfile();
mInstrumentation = getInstrumentation();
}
@@ -140,19 +142,12 @@
final int trialCount = 10;
- List<Double> vmStartingTimeMetrics = new ArrayList<>();
List<Double> bootTimeMetrics = new ArrayList<>();
- List<Double> bootloaderTimeMetrics = new ArrayList<>();
- List<Double> kernelBootTimeMetrics = new ArrayList<>();
- List<Double> userspaceBootTimeMetrics = new ArrayList<>();
-
for (int i = 0; i < trialCount; i++) {
-
- // To grab boot events from log, set debug mode to FULL
VirtualMachineConfig normalConfig =
newVmConfigBuilder()
.setPayloadBinaryPath("MicrodroidIdleNativeLib.so")
- .setDebugLevel(DEBUG_LEVEL_FULL)
+ .setDebugLevel(DEBUG_LEVEL_NONE)
.setMemoryMib(256)
.build();
forceCreateNewVirtualMachine("test_vm_boot_time", normalConfig);
@@ -160,12 +155,74 @@
BootResult result = tryBootVm(TAG, "test_vm_boot_time");
assertThat(result.payloadStarted).isTrue();
- final double nanoToMilli = 1000000.0;
- vmStartingTimeMetrics.add(result.getVMStartingElapsedNanoTime() / nanoToMilli);
- bootTimeMetrics.add(result.endToEndNanoTime / nanoToMilli);
- bootloaderTimeMetrics.add(result.getBootloaderElapsedNanoTime() / nanoToMilli);
- kernelBootTimeMetrics.add(result.getKernelElapsedNanoTime() / nanoToMilli);
- userspaceBootTimeMetrics.add(result.getUserspaceElapsedNanoTime() / nanoToMilli);
+ bootTimeMetrics.add(result.endToEndNanoTime / NANO_TO_MILLI);
+ }
+
+ reportMetrics(bootTimeMetrics, "boot_time", "ms");
+ }
+
+ @Test
+ public void testMicrodroidMulticoreBootTime()
+ throws VirtualMachineException, InterruptedException, IOException {
+ assume().withMessage("Skip on CF; too slow").that(isCuttlefish()).isFalse();
+
+ final int trialCount = 10;
+ final int[] trialNumCpus = {2, 4, 8};
+
+ for (int numCpus : trialNumCpus) {
+ List<Double> bootTimeMetrics = new ArrayList<>();
+ for (int i = 0; i < trialCount; i++) {
+ VirtualMachineConfig normalConfig =
+ newVmConfigBuilder()
+ .setPayloadBinaryPath("MicrodroidIdleNativeLib.so")
+ .setDebugLevel(DEBUG_LEVEL_NONE)
+ .setMemoryMib(256)
+ .setNumCpus(numCpus)
+ .build();
+ forceCreateNewVirtualMachine("test_vm_boot_time_multicore", normalConfig);
+
+ BootResult result = tryBootVm(TAG, "test_vm_boot_time_multicore");
+ assertThat(result.payloadStarted).isTrue();
+
+ bootTimeMetrics.add(result.endToEndNanoTime / NANO_TO_MILLI);
+ }
+
+ String metricName = "boot_time_" + numCpus + "cpus";
+ reportMetrics(bootTimeMetrics, metricName, "ms");
+ }
+ }
+
+ @Test
+ public void testMicrodroidDebugBootTime()
+ throws VirtualMachineException, InterruptedException, IOException {
+ assume().withMessage("Skip on CF; too slow").that(isCuttlefish()).isFalse();
+
+ final int trialCount = 10;
+
+ List<Double> vmStartingTimeMetrics = new ArrayList<>();
+ List<Double> bootTimeMetrics = new ArrayList<>();
+ List<Double> bootloaderTimeMetrics = new ArrayList<>();
+ List<Double> kernelBootTimeMetrics = new ArrayList<>();
+ List<Double> userspaceBootTimeMetrics = new ArrayList<>();
+
+ for (int i = 0; i < trialCount; i++) {
+ // To grab boot events from log, set debug mode to FULL
+ VirtualMachineConfig normalConfig =
+ newVmConfigBuilder()
+ .setPayloadBinaryPath("MicrodroidIdleNativeLib.so")
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .setMemoryMib(256)
+ .build();
+ forceCreateNewVirtualMachine("test_vm_boot_time_debug", normalConfig);
+
+ BootResult result = tryBootVm(TAG, "test_vm_boot_time_debug");
+ assertThat(result.payloadStarted).isTrue();
+
+ vmStartingTimeMetrics.add(result.getVMStartingElapsedNanoTime() / NANO_TO_MILLI);
+ bootTimeMetrics.add(result.endToEndNanoTime / NANO_TO_MILLI);
+ bootloaderTimeMetrics.add(result.getBootloaderElapsedNanoTime() / NANO_TO_MILLI);
+ kernelBootTimeMetrics.add(result.getKernelElapsedNanoTime() / NANO_TO_MILLI);
+ userspaceBootTimeMetrics.add(result.getUserspaceElapsedNanoTime() / NANO_TO_MILLI);
}
reportMetrics(vmStartingTimeMetrics, "vm_starting_time", "ms");
diff --git a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
index 536f663..d762310 100644
--- a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
+++ b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
@@ -24,6 +24,7 @@
import android.content.Context;
import android.os.ParcelFileDescriptor;
import android.os.SystemProperties;
+import android.system.Os;
import android.system.virtualmachine.VirtualMachine;
import android.system.virtualmachine.VirtualMachineCallback;
import android.system.virtualmachine.VirtualMachineConfig;
@@ -50,6 +51,8 @@
import java.util.concurrent.TimeUnit;
public abstract class MicrodroidDeviceTestBase {
+ private final String MAX_PERFORMANCE_TASK_PROFILE = "CPUSET_SP_TOP_APP";
+
public static boolean isCuttlefish() {
return DeviceProperties.create(SystemProperties::get).isCuttlefish();
}
@@ -73,6 +76,17 @@
permission);
}
+ protected final void setMaxPerformanceTaskProfile() throws IOException {
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ UiAutomation uiAutomation = instrumentation.getUiAutomation();
+ String cmd = "settaskprofile " + Os.gettid() + " " + MAX_PERFORMANCE_TASK_PROFILE;
+ String out = runInShell("MicrodroidDeviceTestBase", uiAutomation, cmd).trim();
+ String expect = "Profile " + MAX_PERFORMANCE_TASK_PROFILE + " is applied successfully!";
+ if (!expect.equals(out)) {
+ throw new IOException("Could not apply max performance task profile: " + out);
+ }
+ }
+
private Context mCtx;
private boolean mProtectedVm;
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..5616097 100644
--- a/virtualizationservice/src/virtmgr.rs
+++ b/virtualizationservice/src/virtmgr.rs
@@ -21,15 +21,16 @@
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};
use std::os::unix::io::{FromRawFd, OwnedFd, RawFd};
use clap::Parser;
+use nix::fcntl::{fcntl, F_GETFD, F_SETFD, FdFlag};
use nix::unistd::{Pid, Uid};
use std::os::unix::raw::{pid_t, uid_t};
@@ -66,8 +67,12 @@
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}"))?;
+ fcntl(raw_fd, F_GETFD).with_context(|| format!("Invalid file descriptor {raw_fd}"))?;
+
+ // The file descriptor had CLOEXEC disabled to be inherited from the parent.
+ // Re-enable it to make sure it is not accidentally inherited further.
+ fcntl(raw_fd, F_SETFD(FdFlag::FD_CLOEXEC))
+ .with_context(|| format!("Could not set CLOEXEC on 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) {
@@ -102,6 +107,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(