Merge "Test more combinations of debug level changes"
diff --git a/OWNERS b/OWNERS
index ecd24ed..310add7 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,5 +1,7 @@
# Welcome to Android KVM!
#
+# Bug component: 867125
+#
# If you are not a member of the project please send review requests
# to one of those listed below.
dbrazdil@google.com
diff --git a/authfs/fd_server/src/main.rs b/authfs/fd_server/src/main.rs
index f1fffdd..21d0e64 100644
--- a/authfs/fd_server/src/main.rs
+++ b/authfs/fd_server/src/main.rs
@@ -29,7 +29,7 @@
use clap::Parser;
use log::debug;
use nix::sys::stat::{umask, Mode};
-use rpcbinder::run_vsock_rpc_server;
+use rpcbinder::RpcServer;
use std::collections::BTreeMap;
use std::fs::File;
use std::os::unix::io::{FromRawFd, OwnedFd};
@@ -135,18 +135,14 @@
let old_umask = umask(Mode::empty());
debug!("Setting umask to 0 (old: {:03o})", old_umask.bits());
- let service = FdService::new_binder(fd_pool).as_binder();
debug!("fd_server is starting as a rpc service.");
- let retval = run_vsock_rpc_server(service, RPC_SERVICE_PORT, || {
- debug!("fd_server is ready");
- // Close the ready-fd if we were given one to signal our readiness.
- drop(ready_fd.take());
- });
+ let service = FdService::new_binder(fd_pool).as_binder();
+ let server = RpcServer::new_vsock(service, RPC_SERVICE_PORT)?;
+ debug!("fd_server is ready");
- if retval {
- debug!("RPC server has shut down gracefully");
- Ok(())
- } else {
- bail!("Premature termination of RPC server");
- }
+ // Close the ready-fd if we were given one to signal our readiness.
+ drop(ready_fd.take());
+
+ server.join();
+ Ok(())
}
diff --git a/authfs/service/src/main.rs b/authfs/service/src/main.rs
index 671c06a..e710f07 100644
--- a/authfs/service/src/main.rs
+++ b/authfs/service/src/main.rs
@@ -24,7 +24,7 @@
use anyhow::{bail, Result};
use log::*;
-use rpcbinder::run_init_unix_domain_rpc_server;
+use rpcbinder::RpcServer;
use std::ffi::OsString;
use std::fs::{create_dir, read_dir, remove_dir_all, remove_file};
use std::sync::atomic::{AtomicUsize, Ordering};
@@ -117,15 +117,11 @@
let service = AuthFsService::new_binder(debuggable).as_binder();
debug!("{} is starting as a rpc service.", AUTHFS_SERVICE_SOCKET_NAME);
- let retval = run_init_unix_domain_rpc_server(service, AUTHFS_SERVICE_SOCKET_NAME, || {
- info!("The RPC server '{}' is running.", AUTHFS_SERVICE_SOCKET_NAME);
- });
- if retval {
- info!("The RPC server at '{}' has shut down gracefully.", AUTHFS_SERVICE_SOCKET_NAME);
- Ok(())
- } else {
- bail!("Premature termination of the RPC server '{}'.", AUTHFS_SERVICE_SOCKET_NAME)
- }
+ let server = RpcServer::new_init_unix_domain(service, AUTHFS_SERVICE_SOCKET_NAME)?;
+ info!("The RPC server '{}' is running.", AUTHFS_SERVICE_SOCKET_NAME);
+ server.join();
+ info!("The RPC server at '{}' has shut down gracefully.", AUTHFS_SERVICE_SOCKET_NAME);
+ Ok(())
}
fn main() {
diff --git a/authfs/tests/benchmarks/src/java/com/android/fs/benchmarks/AuthFsBenchmarks.java b/authfs/tests/benchmarks/src/java/com/android/fs/benchmarks/AuthFsBenchmarks.java
index 8cee496..e67a309 100644
--- a/authfs/tests/benchmarks/src/java/com/android/fs/benchmarks/AuthFsBenchmarks.java
+++ b/authfs/tests/benchmarks/src/java/com/android/fs/benchmarks/AuthFsBenchmarks.java
@@ -28,12 +28,13 @@
import android.platform.test.annotations.RootPermissionTest;
import com.android.fs.common.AuthFsTestRule;
+import com.android.microdroid.test.common.DeviceProperties;
import com.android.microdroid.test.common.MetricsProcessor;
-import com.android.microdroid.test.host.MicrodroidHostTestCaseBase;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.metrics.proto.MetricMeasurement.DataType;
import com.android.tradefed.metrics.proto.MetricMeasurement.Measurements;
import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import org.junit.After;
import org.junit.AfterClass;
@@ -53,7 +54,7 @@
@RootPermissionTest
@RunWith(DeviceJUnit4Parameterized.class)
@UseParametersRunnerFactory(DeviceJUnit4ClassRunnerWithParameters.RunnerFactory.class)
-public class AuthFsBenchmarks extends MicrodroidHostTestCaseBase {
+public class AuthFsBenchmarks extends BaseHostJUnit4Test {
private static final int TRIAL_COUNT = 5;
/** Name of the measure_io binary on host. */
@@ -83,10 +84,10 @@
AuthFsTestRule.setUpAndroid(getTestInformation());
mAuthFsTestRule.setUpTest();
assumeTrue(AuthFsTestRule.getDevice().supportsMicrodroid(mProtectedVm));
- assumeFalse("Skip on CF; protected VM not supported", isCuttlefish());
- String metricsPrefix =
- MetricsProcessor.getMetricPrefix(
- getDevice().getProperty("debug.hypervisor.metrics_tag"));
+ DeviceProperties deviceProperties = DeviceProperties.create(getDevice()::getProperty);
+ assumeFalse(
+ "Skip on CF; no need to collect metrics on CF", deviceProperties.isCuttlefish());
+ String metricsPrefix = MetricsProcessor.getMetricPrefix(deviceProperties.getMetricsTag());
mMetricsProcessor = new MetricsProcessor(metricsPrefix + "authfs/");
AuthFsTestRule.startMicrodroid(mProtectedVm);
}
diff --git a/compos/common/compos_client.rs b/compos/common/compos_client.rs
index 02459b2..601c6fc 100644
--- a/compos/common/compos_client.rs
+++ b/compos/common/compos_client.rs
@@ -32,10 +32,8 @@
use log::{info, warn};
use rustutils::system_properties;
use std::fs::{self, File};
-use std::io::{BufRead, BufReader};
use std::num::NonZeroU32;
use std::path::{Path, PathBuf};
-use std::thread;
use vmclient::{DeathReason, ErrorCode, VmInstance, VmWaitError};
/// This owns an instance of the CompOS VM.
@@ -244,13 +242,6 @@
log::info!("VM payload started, cid = {}", cid);
}
- fn on_payload_stdio(&self, cid: i32, stream: &File) {
- if let Err(e) = start_logging(stream) {
- log::warn!("Can't log vm output: {}", e);
- };
- log::info!("VM payload forwarded its stdio, cid = {}", cid);
- }
-
fn on_payload_ready(&self, cid: i32) {
log::info!("VM payload ready, cid = {}", cid);
}
@@ -267,19 +258,3 @@
log::warn!("VM died, cid = {}, reason = {:?}", cid, death_reason);
}
}
-
-fn start_logging(file: &File) -> Result<()> {
- let reader = BufReader::new(file.try_clone().context("Cloning file failed")?);
- thread::spawn(move || {
- for line in reader.lines() {
- match line {
- Ok(line) => info!("VM: {}", line),
- Err(e) => {
- warn!("Reading VM output failed: {}", e);
- break;
- }
- }
- }
- });
- Ok(())
-}
diff --git a/compos/src/compsvc_main.rs b/compos/src/compsvc_main.rs
index c280956..206dd4b 100644
--- a/compos/src/compsvc_main.rs
+++ b/compos/src/compsvc_main.rs
@@ -22,12 +22,12 @@
mod compsvc;
mod fsverity;
-use anyhow::{bail, Result};
+use anyhow::Result;
use compos_common::COMPOS_VSOCK_PORT;
-use log::{debug, error, warn};
-use rpcbinder::run_vsock_rpc_server;
+use log::{debug, error};
+use rpcbinder::RpcServer;
use std::panic;
-use vm_payload_bindgen::{AVmPayload_notifyPayloadReady, AVmPayload_setupStdioProxy};
+use vm_payload_bindgen::AVmPayload_notifyPayloadReady;
fn main() {
if let Err(e) = try_main() {
@@ -44,21 +44,12 @@
panic::set_hook(Box::new(|panic_info| {
error!("{}", panic_info);
}));
- // Redirect stdio to the host.
- if !unsafe { AVmPayload_setupStdioProxy() } {
- warn!("Failed to setup stdio proxy");
- }
- let service = compsvc::new_binder()?.as_binder();
debug!("compsvc is starting as a rpc service.");
+ let service = compsvc::new_binder()?.as_binder();
+ let server = RpcServer::new_vsock(service, COMPOS_VSOCK_PORT)?;
// SAFETY: Invokes a method from the bindgen library `vm_payload_bindgen`.
- let retval = run_vsock_rpc_server(service, COMPOS_VSOCK_PORT, || unsafe {
- AVmPayload_notifyPayloadReady();
- });
- if retval {
- debug!("RPC server has shut down gracefully");
- Ok(())
- } else {
- bail!("Premature termination of RPC server");
- }
+ unsafe { AVmPayload_notifyPayloadReady() };
+ server.join();
+ Ok(())
}
diff --git a/demo/java/com/android/microdroid/demo/MainActivity.java b/demo/java/com/android/microdroid/demo/MainActivity.java
index ebc2bb3..8e870ea 100644
--- a/demo/java/com/android/microdroid/demo/MainActivity.java
+++ b/demo/java/com/android/microdroid/demo/MainActivity.java
@@ -42,7 +42,6 @@
import com.android.microdroid.testservice.ITestService;
import java.io.BufferedReader;
-import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
@@ -172,13 +171,6 @@
public void onPayloadStarted(VirtualMachine vm) {}
@Override
- public void onPayloadStdio(VirtualMachine vm, ParcelFileDescriptor stream) {
- mPayloadOutput.postValue("(Payload connected standard output...)");
- InputStream input = new FileInputStream(stream.getFileDescriptor());
- mService.execute(new Reader("payload", mPayloadOutput, input));
- }
-
- @Override
public void onPayloadReady(VirtualMachine vm) {
// This check doesn't 100% prevent race condition or UI hang.
// However, it's fine for demo.
diff --git a/encryptedstore/Android.bp b/encryptedstore/Android.bp
new file mode 100644
index 0000000..13ef1b9
--- /dev/null
+++ b/encryptedstore/Android.bp
@@ -0,0 +1,31 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+ name: "encryptedstore.defaults",
+ srcs: ["src/main.rs"],
+ edition: "2021",
+ prefer_rlib: true,
+ rustlibs: [
+ "libandroid_logger",
+ "libanyhow",
+ "liblibc",
+ "libclap",
+ "libhex",
+ "liblog_rust",
+ "libnix",
+ "libdm_rust",
+ ],
+ multilib: {
+ lib32: {
+ enabled: false,
+ },
+ },
+}
+
+rust_binary {
+ name: "encryptedstore",
+ defaults: ["encryptedstore.defaults"],
+ bootstrap: true,
+}
diff --git a/encryptedstore/src/main.rs b/encryptedstore/src/main.rs
new file mode 100644
index 0000000..d7d2382
--- /dev/null
+++ b/encryptedstore/src/main.rs
@@ -0,0 +1,159 @@
+/*
+ * 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.
+ */
+
+//! `encryptedstore` is a program that (as the name indicates) provides encrypted storage
+//! solution in a VM. This is based on dm-crypt & requires the (64 bytes') key & the backing device.
+//! It uses dm_rust lib.
+
+use anyhow::{ensure, Context, Result};
+use clap::{arg, App};
+use dm::{crypt::CipherType, util};
+use log::info;
+use std::ffi::CString;
+use std::fs::{create_dir_all, OpenOptions};
+use std::io::{Error, Read, Write};
+use std::os::unix::ffi::OsStrExt;
+use std::os::unix::fs::FileTypeExt;
+use std::path::{Path, PathBuf};
+use std::process::Command;
+
+const MK2FS_BIN: &str = "/system/bin/mke2fs";
+const UNFORMATTED_STORAGE_MAGIC: &str = "UNFORMATTED-STORAGE";
+
+fn main() -> Result<()> {
+ android_logger::init_once(
+ android_logger::Config::default()
+ .with_tag("encryptedstore")
+ .with_min_level(log::Level::Info),
+ );
+ info!("Starting encryptedstore binary");
+
+ let matches = App::new("encryptedstore")
+ .args(&[
+ arg!(--blkdevice <FILE> "the block device backing the encrypted storage")
+ .required(true),
+ arg!(--key <KEY> "key (in hex) equivalent to 64 bytes)").required(true),
+ arg!(--mountpoint <MOUNTPOINT> "mount point for the storage").required(true),
+ ])
+ .get_matches();
+
+ let blkdevice = Path::new(matches.value_of("blkdevice").unwrap());
+ let key = matches.value_of("key").unwrap();
+ let mountpoint = Path::new(matches.value_of("mountpoint").unwrap());
+ encryptedstore_init(blkdevice, key, mountpoint).context(format!(
+ "Unable to initialize encryptedstore on {:?} & mount at {:?}",
+ blkdevice, mountpoint
+ ))?;
+ Ok(())
+}
+
+fn encryptedstore_init(blkdevice: &Path, key: &str, mountpoint: &Path) -> Result<()> {
+ ensure!(
+ std::fs::metadata(&blkdevice)
+ .context(format!("Failed to get metadata of {:?}", blkdevice))?
+ .file_type()
+ .is_block_device(),
+ "The path:{:?} is not of a block device",
+ blkdevice
+ );
+
+ let needs_formatting =
+ needs_formatting(blkdevice).context("Unable to check if formatting is required")?;
+ let crypt_device =
+ enable_crypt(blkdevice, key, "cryptdev").context("Unable to map crypt device")?;
+
+ // We might need to format it with filesystem if this is a "seen-for-the-first-time" device.
+ if needs_formatting {
+ info!("Freshly formatting the crypt device");
+ format_ext4(&crypt_device)?;
+ }
+ mount(&crypt_device, mountpoint).context(format!("Unable to mount {:?}", crypt_device))?;
+ Ok(())
+}
+
+fn enable_crypt(data_device: &Path, key: &str, name: &str) -> Result<PathBuf> {
+ let dev_size = util::blkgetsize64(data_device)?;
+ let key = hex::decode(key).context("Unable to decode hex key")?;
+ ensure!(key.len() == 64, "We need 64 bytes' key for aes-xts cipher for block encryption");
+
+ // Create the dm-crypt spec
+ let target = dm::crypt::DmCryptTargetBuilder::default()
+ .data_device(data_device, dev_size)
+ .cipher(CipherType::AES256XTS) // TODO(b/259253336) Move to HCTR2 based encryption.
+ .key(&key)
+ .build()
+ .context("Couldn't build the DMCrypt target")?;
+ let dm = dm::DeviceMapper::new()?;
+ dm.create_crypt_device(name, &target).context("Failed to create dm-crypt device")
+}
+
+// The disk contains UNFORMATTED_STORAGE_MAGIC to indicate we need to format the crypt device.
+// This function looks for it, zeroing it, if present.
+fn needs_formatting(data_device: &Path) -> Result<bool> {
+ let mut file = OpenOptions::new()
+ .read(true)
+ .write(true)
+ .open(data_device)
+ .with_context(|| format!("Failed to open {:?}", data_device))?;
+
+ let mut buf = [0; UNFORMATTED_STORAGE_MAGIC.len()];
+ file.read_exact(&mut buf)?;
+
+ if buf == UNFORMATTED_STORAGE_MAGIC.as_bytes() {
+ buf.fill(0);
+ file.write_all(&buf)?;
+ return Ok(true);
+ }
+ Ok(false)
+}
+
+fn format_ext4(device: &Path) -> Result<()> {
+ let mkfs_options = [
+ "-j", // Create appropriate sized journal
+ "-O metadata_csum", // Metadata checksum for filesystem integrity
+ ];
+ let mut cmd = Command::new(MK2FS_BIN);
+ let status = cmd
+ .args(mkfs_options)
+ .arg(device)
+ .status()
+ .context(format!("failed to execute {}", MK2FS_BIN))?;
+ ensure!(status.success(), "mkfs failed with {:?}", status);
+ Ok(())
+}
+
+fn mount(source: &Path, mountpoint: &Path) -> Result<()> {
+ create_dir_all(mountpoint).context(format!("Failed to create {:?}", &mountpoint))?;
+ let mount_options = CString::new("").unwrap();
+ let source = CString::new(source.as_os_str().as_bytes())?;
+ let mountpoint = CString::new(mountpoint.as_os_str().as_bytes())?;
+ let fstype = CString::new("ext4").unwrap();
+
+ let ret = unsafe {
+ libc::mount(
+ source.as_ptr(),
+ mountpoint.as_ptr(),
+ fstype.as_ptr(),
+ libc::MS_NOSUID | libc::MS_NODEV | libc::MS_NOEXEC,
+ mount_options.as_ptr() as *const std::ffi::c_void,
+ )
+ };
+ if ret < 0 {
+ Err(Error::last_os_error()).context("mount failed")
+ } else {
+ Ok(())
+ }
+}
diff --git a/javalib/Android.bp b/javalib/Android.bp
index 9be0e9d..8421231 100644
--- a/javalib/Android.bp
+++ b/javalib/Android.bp
@@ -50,15 +50,15 @@
},
sdk_version: "core_platform",
+ stub_only_libs: [
+ "android_module_lib_stubs_current",
+ ],
impl_only_libs: [
"framework",
],
impl_library_visibility: [
"//packages/modules/Virtualization:__subpackages__",
],
-
- // TODO(b/243512044): remove once we have API tracking files in prebuilts/sdk
- unsafe_ignore_missing_latest_api: true,
}
prebuilt_apis {
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index c200d00..4435576 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -76,7 +76,6 @@
import java.io.InputStreamReader;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.lang.ref.WeakReference;
import java.nio.channels.FileChannel;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.FileVisitResult;
@@ -86,10 +85,7 @@
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
-import java.util.WeakHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
@@ -107,11 +103,6 @@
* @hide
*/
public class VirtualMachine implements AutoCloseable {
- /** Map from context to a map of all that context's VMs by name. */
- @GuardedBy("sCreateLock")
- private static final Map<Context, Map<String, WeakReference<VirtualMachine>>> sInstances =
- new WeakHashMap<>();
-
/** Name of the directory under the files directory where all VMs created for the app exist. */
private static final String VM_DIR = "vm";
@@ -208,17 +199,10 @@
private static final long INSTANCE_FILE_SIZE = 10 * 1024 * 1024;
// A note on lock ordering:
- // You can take mLock while holding sCreateLock, but not vice versa.
+ // You can take mLock while holding VirtualMachineManager.sCreateLock, but not vice versa.
// We never take any other lock while holding mCallbackLock; therefore you can
// take mCallbackLock while holding any other lock.
- /**
- * A lock used to synchronize the creation of virtual machines. It protects
- * {@link #sInstances}, but is also held throughout VM creation / retrieval / deletion, to
- * prevent these actions racing with each other.
- */
- static final Object sCreateLock = new Object();
-
/** Lock protecting our mutable state (other than callbacks). */
private final Object mLock = new Object();
@@ -281,12 +265,6 @@
mExtraApks = setupExtraApks(context, config, thisVmDir);
}
- @GuardedBy("sCreateLock")
- @NonNull
- private static Map<String, WeakReference<VirtualMachine>> getInstancesMap(Context context) {
- return sInstances.computeIfAbsent(context, unused -> new HashMap<>());
- }
-
/**
* Builds a virtual machine from an {@link VirtualMachineDescriptor} object and associates it
* with the given name.
@@ -297,7 +275,7 @@
* #delete}. The imported virtual machine is in {@link #STATUS_STOPPED} state. To run the VM,
* call {@link #run}.
*/
- @GuardedBy("sCreateLock")
+ @GuardedBy("VirtualMachineManager.sCreateLock")
@NonNull
static VirtualMachine fromDescriptor(
@NonNull Context context,
@@ -315,7 +293,6 @@
throw new VirtualMachineException("failed to create instance image", e);
}
vm.importInstanceFrom(vmDescriptor.getInstanceImgFd());
- getInstancesMap(context).put(name, new WeakReference<>(vm));
return vm;
} catch (VirtualMachineException | RuntimeException e) {
// If anything goes wrong, delete any files created so far and the VM's directory
@@ -333,7 +310,7 @@
* it is persisted until it is deleted by calling {@link #delete}. The created virtual machine
* is in {@link #STATUS_STOPPED} state. To run the VM, call {@link #run}.
*/
- @GuardedBy("sCreateLock")
+ @GuardedBy("VirtualMachineManager.sCreateLock")
@NonNull
static VirtualMachine create(
@NonNull Context context, @NonNull String name, @NonNull VirtualMachineConfig config)
@@ -365,9 +342,6 @@
} catch (ServiceSpecificException | IllegalArgumentException e) {
throw new VirtualMachineException("failed to create instance partition", e);
}
-
- getInstancesMap(context).put(name, new WeakReference<>(vm));
-
return vm;
} catch (VirtualMachineException | RuntimeException e) {
// If anything goes wrong, delete any files created so far and the VM's directory
@@ -381,10 +355,10 @@
}
/** Loads a virtual machine that is already created before. */
- @GuardedBy("sCreateLock")
+ @GuardedBy("VirtualMachineManager.sCreateLock")
@Nullable
- static VirtualMachine load(
- @NonNull Context context, @NonNull String name) throws VirtualMachineException {
+ static VirtualMachine load(@NonNull Context context, @NonNull String name)
+ throws VirtualMachineException {
File thisVmDir = getVmDir(context, name);
if (!thisVmDir.exists()) {
// The VM doesn't exist.
@@ -392,51 +366,33 @@
}
File configFilePath = new File(thisVmDir, CONFIG_FILE);
VirtualMachineConfig config = VirtualMachineConfig.from(configFilePath);
- Map<String, WeakReference<VirtualMachine>> instancesMap = getInstancesMap(context);
-
- VirtualMachine vm = null;
- if (instancesMap.containsKey(name)) {
- vm = instancesMap.get(name).get();
- }
- if (vm == null) {
- vm = new VirtualMachine(context, name, config);
- }
+ VirtualMachine vm = new VirtualMachine(context, name, config);
if (!vm.mInstanceFilePath.exists()) {
throw new VirtualMachineException("instance image missing");
}
- instancesMap.put(name, new WeakReference<>(vm));
-
return vm;
}
- @GuardedBy("sCreateLock")
- static void delete(Context context, String name) throws VirtualMachineException {
- Map<String, WeakReference<VirtualMachine>> instancesMap = sInstances.get(context);
- VirtualMachine vm;
- if (instancesMap != null && instancesMap.containsKey(name)) {
- vm = instancesMap.get(name).get();
- } else {
- vm = null;
+ @GuardedBy("VirtualMachineManager.sCreateLock")
+ void delete(Context context, String name) throws VirtualMachineException {
+ synchronized (mLock) {
+ checkStopped();
}
- if (vm != null) {
- synchronized (vm.mLock) {
- vm.checkStopped();
- }
- }
+ deleteVmDirectory(context, name);
+ }
+ static void deleteVmDirectory(Context context, String name) throws VirtualMachineException {
try {
deleteRecursively(getVmDir(context, name));
} catch (IOException e) {
throw new VirtualMachineException(e);
}
-
- if (instancesMap != null) instancesMap.remove(name);
}
- @GuardedBy("sCreateLock")
+ @GuardedBy("VirtualMachineManager.sCreateLock")
@NonNull
private static File createVmDir(@NonNull Context context, @NonNull String name)
throws VirtualMachineException {
@@ -686,12 +642,6 @@
}
@Override
- public void onPayloadStdio(int cid, ParcelFileDescriptor stream) {
- executeCallback(
- (cb) -> cb.onPayloadStdio(VirtualMachine.this, stream));
- }
-
- @Override
public void onPayloadReady(int cid) {
executeCallback((cb) -> cb.onPayloadReady(VirtualMachine.this));
}
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java b/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java
index 26b8ba2..1f94a8b 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java
@@ -137,9 +137,6 @@
/** Called when the payload starts in the VM. */
void onPayloadStarted(@NonNull VirtualMachine vm);
- /** Called when the payload creates a standard input/output stream. */
- void onPayloadStdio(@NonNull VirtualMachine vm, @NonNull ParcelFileDescriptor stream);
-
/**
* Called when the payload in the VM is ready to serve. See
* {@link VirtualMachine#connectToVsockServer(int)}.
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
index c357f50..0e96f43 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
@@ -25,6 +25,7 @@
import android.annotation.SuppressLint;
import android.content.Context;
import android.sysprop.HypervisorProperties;
+import android.util.ArrayMap;
import com.android.internal.annotations.GuardedBy;
@@ -47,6 +48,13 @@
* @hide
*/
public class VirtualMachineManager {
+ /**
+ * A lock used to synchronize the creation of virtual machines. It protects {@link #mVmsByName},
+ * but is also held throughout VM creation / retrieval / deletion, to prevent these actions
+ * racing with each other.
+ */
+ private static final Object sCreateLock = new Object();
+
@NonNull private final Context mContext;
private VirtualMachineManager(@NonNull Context context) {
@@ -57,6 +65,9 @@
private static final Map<Context, WeakReference<VirtualMachineManager>> sInstances =
new WeakHashMap<>();
+ @GuardedBy("sCreateLock")
+ private final Map<String, WeakReference<VirtualMachine>> mVmsByName = new ArrayMap<>();
+
/**
* Capabilities of the virtual machine implementation.
*
@@ -136,11 +147,48 @@
public VirtualMachine create(
@NonNull String name, @NonNull VirtualMachineConfig config)
throws VirtualMachineException {
- synchronized (VirtualMachine.sCreateLock) {
- return VirtualMachine.create(mContext, name, config);
+ synchronized (sCreateLock) {
+ return createLocked(name, config);
}
}
+ @NonNull
+ @GuardedBy("sCreateLock")
+ private VirtualMachine createLocked(@NonNull String name, @NonNull VirtualMachineConfig config)
+ throws VirtualMachineException {
+ VirtualMachine vm = VirtualMachine.create(mContext, name, config);
+ mVmsByName.put(name, new WeakReference<>(vm));
+ return vm;
+ }
+
+ /**
+ * Returns an existing {@link VirtualMachine} with the given name. Returns null if there is no
+ * such virtual machine.
+ *
+ * @throws VirtualMachineException if the virtual machine exists but could not be successfully
+ * retrieved.
+ * @hide
+ */
+ @Nullable
+ public VirtualMachine get(@NonNull String name) throws VirtualMachineException {
+ synchronized (sCreateLock) {
+ return getLocked(name);
+ }
+ }
+
+ @Nullable
+ @GuardedBy("sCreateLock")
+ private VirtualMachine getLocked(@NonNull String name) throws VirtualMachineException {
+ VirtualMachine vm = getVmByName(name);
+ if (vm != null) return vm;
+
+ vm = VirtualMachine.load(mContext, name);
+ if (vm != null) {
+ mVmsByName.put(name, new WeakReference<>(vm));
+ }
+ return vm;
+ }
+
/**
* Imports a virtual machine from an {@link VirtualMachineDescriptor} object and associates it
* with the given name.
@@ -154,23 +202,10 @@
public VirtualMachine importFromDescriptor(
@NonNull String name, @NonNull VirtualMachineDescriptor vmDescriptor)
throws VirtualMachineException {
- synchronized (VirtualMachine.sCreateLock) {
- return VirtualMachine.fromDescriptor(mContext, name, vmDescriptor);
- }
- }
-
- /**
- * Returns an existing {@link VirtualMachine} with the given name. Returns null if there is no
- * such virtual machine.
- *
- * @throws VirtualMachineException if the virtual machine exists but could not be successfully
- * retrieved.
- * @hide
- */
- @Nullable
- public VirtualMachine get(@NonNull String name) throws VirtualMachineException {
- synchronized (VirtualMachine.sCreateLock) {
- return VirtualMachine.load(mContext, name);
+ synchronized (sCreateLock) {
+ VirtualMachine vm = VirtualMachine.fromDescriptor(mContext, name, vmDescriptor);
+ mVmsByName.put(name, new WeakReference<>(vm));
+ return vm;
}
}
@@ -185,14 +220,14 @@
public VirtualMachine getOrCreate(
@NonNull String name, @NonNull VirtualMachineConfig config)
throws VirtualMachineException {
- VirtualMachine vm;
- synchronized (VirtualMachine.sCreateLock) {
- vm = get(name);
- if (vm == null) {
- vm = create(name, config);
+ synchronized (sCreateLock) {
+ VirtualMachine vm = getLocked(name);
+ if (vm != null) {
+ return vm;
+ } else {
+ return createLocked(name, config);
}
}
- return vm;
}
/**
@@ -207,9 +242,28 @@
* @hide
*/
public void delete(@NonNull String name) throws VirtualMachineException {
- requireNonNull(name);
- synchronized (VirtualMachine.sCreateLock) {
- VirtualMachine.delete(mContext, name);
+ synchronized (sCreateLock) {
+ VirtualMachine vm = getVmByName(name);
+ if (vm == null) {
+ VirtualMachine.deleteVmDirectory(mContext, name);
+ } else {
+ vm.delete(mContext, name);
+ }
+ mVmsByName.remove(name);
}
}
+
+ @Nullable
+ @GuardedBy("sCreateLock")
+ private VirtualMachine getVmByName(@NonNull String name) {
+ requireNonNull(name);
+ WeakReference<VirtualMachine> weakReference = mVmsByName.get(name);
+ if (weakReference != null) {
+ VirtualMachine vm = weakReference.get();
+ if (vm != null && vm.getStatus() != VirtualMachine.STATUS_DELETED) {
+ return vm;
+ }
+ }
+ return null;
+ }
}
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index c3e2692..f2b223d 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -72,7 +72,6 @@
"debuggerd",
"linker",
"linkerconfig",
- "servicemanager.microdroid",
"tombstoned.microdroid",
"tombstone_transmit.microdroid",
"cgroups.json",
@@ -85,6 +84,7 @@
"microdroid_plat_sepolicy_and_mapping.sha256",
"microdroid_property_contexts",
"microdroid_service_contexts",
+ "mke2fs",
// TODO(b/195425111) these should be added automatically
"libcrypto", // used by many (init_second_stage, microdroid_manager, toybox, etc)
@@ -108,6 +108,7 @@
"apkdmverity",
"authfs",
"authfs_service",
+ "encryptedstore",
"microdroid_crashdump_kernel",
"microdroid_kexec",
"microdroid_manager",
diff --git a/microdroid/init.rc b/microdroid/init.rc
index 9c62782..94ef940 100644
--- a/microdroid/init.rc
+++ b/microdroid/init.rc
@@ -26,20 +26,6 @@
setprop ro.log.file_logger.path /dev/null
on init
- # Mount binderfs
- mkdir /dev/binderfs
- mount binder binder /dev/binderfs stats=global
- chmod 0755 /dev/binderfs
-
- symlink /dev/binderfs/binder /dev/binder
- symlink /dev/binderfs/vndbinder /dev/vndbinder
-
- chmod 0666 /dev/binderfs/binder
- chmod 0666 /dev/binderfs/vndbinder
-
- start servicemanager
-
-on init
mkdir /mnt/apk 0755 system system
mkdir /mnt/extra-apk 0755 root root
# Microdroid_manager starts apkdmverity/zipfuse/apexd
diff --git a/microdroid/vm_payload/include/vm_payload.h b/microdroid/vm_payload/include/vm_payload.h
index d5853a1..48518ff 100644
--- a/microdroid/vm_payload/include/vm_payload.h
+++ b/microdroid/vm_payload/include/vm_payload.h
@@ -56,8 +56,9 @@
void (*on_ready)(void *param), void *param);
/**
- * Get a secret that is uniquely bound to this VM instance. The secrets are 32-byte values and the
- * value associated with an identifier will not change over the lifetime of the VM instance.
+ * Get a secret that is uniquely bound to this VM instance. The secrets are
+ * values up to 32 bytes long and the value associated with an identifier will
+ * not change over the lifetime of the VM instance.
*
* \param identifier identifier of the secret to return.
* \param identifier_size size of the secret identifier.
@@ -81,12 +82,16 @@
const char *AVmPayload_getApkContentsPath(void);
/**
- * Initiates a socket connection with the host and duplicates stdin, stdout and
- * stderr file descriptors to the socket.
+ * Gets the path to the encrypted persistent storage for the VM, if any. This is
+ * a directory under which any files or directories created will be stored on
+ * behalf of the VM by the host app. All data is encrypted using a key known
+ * only to the VM, so the host cannot decrypt it, but may delete it.
*
- * \return true on success and false on failure. If unsuccessful, the stdio FDs
- * may be in an inconsistent state.
+ * \return the path to the APK contents, or NULL if no encrypted storage was
+ * requested in the VM configuration. If non-null the returned string should not
+ * be deleted or freed by the application and remains valid for the lifetime of
+ * the VM.
*/
-bool AVmPayload_setupStdioProxy();
+const char *AVmPayload_getEncryptedStoragePath(void);
__END_DECLS
diff --git a/microdroid/vm_payload/src/lib.rs b/microdroid/vm_payload/src/lib.rs
index 65b59bf..be6cf93 100644
--- a/microdroid/vm_payload/src/lib.rs
+++ b/microdroid/vm_payload/src/lib.rs
@@ -18,5 +18,5 @@
pub use vm_payload_service::{
AVmPayload_getDiceAttestationCdi, AVmPayload_getDiceAttestationChain,
- AVmPayload_getVmInstanceSecret, AVmPayload_notifyPayloadReady, AVmPayload_setupStdioProxy,
+ AVmPayload_getVmInstanceSecret, AVmPayload_notifyPayloadReady,
};
diff --git a/microdroid/vm_payload/src/vm_payload_service.rs b/microdroid/vm_payload/src/vm_payload_service.rs
index e89f730..874b7e1 100644
--- a/microdroid/vm_payload/src/vm_payload_service.rs
+++ b/microdroid/vm_payload/src/vm_payload_service.rs
@@ -20,25 +20,47 @@
use binder::{Strong, unstable_api::{AIBinder, new_spibinder}};
use lazy_static::lazy_static;
use log::{error, info, Level};
-use rpcbinder::{get_unix_domain_rpc_interface, run_vsock_rpc_server};
-use std::io;
+use rpcbinder::{get_unix_domain_rpc_interface, RpcServer};
use std::ffi::CString;
-use std::fs::File;
use std::os::raw::{c_char, c_void};
-use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd};
+use std::ptr;
+use std::sync::Mutex;
lazy_static! {
static ref VM_APK_CONTENTS_PATH_C: CString =
CString::new(VM_APK_CONTENTS_PATH).expect("CString::new failed");
+ static ref PAYLOAD_CONNECTION: Mutex<Option<Strong<dyn IVmPayloadService>>> = Mutex::default();
+}
+
+/// Return a connection to the payload service in Microdroid Manager. Uses the existing connection
+/// if there is one, otherwise attempts to create a new one.
+fn get_vm_payload_service() -> Result<Strong<dyn IVmPayloadService>> {
+ let mut connection = PAYLOAD_CONNECTION.lock().unwrap();
+ if let Some(strong) = &*connection {
+ Ok(strong.clone())
+ } else {
+ let new_connection: Strong<dyn IVmPayloadService> = get_unix_domain_rpc_interface(
+ VM_PAYLOAD_SERVICE_SOCKET_NAME,
+ )
+ .context(format!("Failed to connect to service: {}", VM_PAYLOAD_SERVICE_SOCKET_NAME))?;
+ *connection = Some(new_connection.clone());
+ Ok(new_connection)
+ }
+}
+
+/// Make sure our logging goes to logcat. It is harmless to call this more than once.
+fn initialize_logging() {
+ android_logger::init_once(
+ android_logger::Config::default().with_tag("vm_payload").with_min_level(Level::Debug),
+ );
}
/// Notifies the host that the payload is ready.
/// Returns true if the notification succeeds else false.
#[no_mangle]
pub extern "C" fn AVmPayload_notifyPayloadReady() -> bool {
- android_logger::init_once(
- android_logger::Config::default().with_tag("vm_payload").with_min_level(Level::Debug),
- );
+ initialize_logging();
+
if let Err(e) = try_notify_payload_ready() {
error!("{:?}", e);
false
@@ -77,15 +99,25 @@
on_ready: Option<unsafe extern "C" fn(param: *mut c_void)>,
param: *mut c_void,
) -> bool {
+ initialize_logging();
+
// SAFETY: AIBinder returned has correct reference count, and the ownership can
// safely be taken by new_spibinder.
let service = new_spibinder(service);
if let Some(service) = service {
- run_vsock_rpc_server(service, port, || {
- if let Some(on_ready) = on_ready {
- on_ready(param);
+ match RpcServer::new_vsock(service, port) {
+ Ok(server) => {
+ if let Some(on_ready) = on_ready {
+ on_ready(param);
+ }
+ server.join();
+ true
}
- })
+ Err(err) => {
+ error!("Failed to start RpcServer: {:?}", err);
+ false
+ }
+ }
} else {
error!("Failed to convert the given service from AIBinder to SpIBinder.");
false
@@ -109,6 +141,8 @@
secret: *mut u8,
size: usize,
) -> bool {
+ initialize_logging();
+
let identifier = std::slice::from_raw_parts(identifier, identifier_size);
match try_get_vm_instance_secret(identifier, size) {
Err(e) => {
@@ -148,6 +182,8 @@
size: usize,
total: *mut usize,
) -> bool {
+ initialize_logging();
+
match try_get_dice_attestation_chain() {
Err(e) => {
error!("{:?}", e);
@@ -182,6 +218,8 @@
size: usize,
total: *mut usize,
) -> bool {
+ initialize_logging();
+
match try_get_dice_attestation_cdi() {
Err(e) => {
error!("{:?}", e);
@@ -201,41 +239,13 @@
(*VM_APK_CONTENTS_PATH_C).as_ptr()
}
+/// Gets the path to the VM's encrypted storage.
+#[no_mangle]
+pub extern "C" fn AVmPayload_getEncryptedStoragePath() -> *const c_char {
+ // TODO(b/254454578): Return a real path if storage is present
+ ptr::null()
+}
+
fn try_get_dice_attestation_cdi() -> Result<Vec<u8>> {
get_vm_payload_service()?.getDiceAttestationCdi().context("Cannot get attestation CDI")
}
-
-/// Creates a socket connection with the host and duplicates standard I/O
-/// file descriptors of the payload to that socket. Then notifies the host.
-#[no_mangle]
-pub extern "C" fn AVmPayload_setupStdioProxy() -> bool {
- if let Err(e) = try_setup_stdio_proxy() {
- error!("{:?}", e);
- false
- } else {
- info!("Successfully set up stdio proxy to the host");
- true
- }
-}
-
-fn dup2(old_fd: &File, new_fd: BorrowedFd) -> Result<(), io::Error> {
- // SAFETY - ownership does not change, only modifies the underlying raw FDs.
- match unsafe { libc::dup2(old_fd.as_raw_fd(), new_fd.as_raw_fd()) } {
- -1 => Err(io::Error::last_os_error()),
- _ => Ok(()),
- }
-}
-
-fn try_setup_stdio_proxy() -> Result<()> {
- let fd =
- get_vm_payload_service()?.setupStdioProxy().context("Could not connect a host socket")?;
- dup2(fd.as_ref(), io::stdin().as_fd()).context("Failed to dup stdin")?;
- dup2(fd.as_ref(), io::stdout().as_fd()).context("Failed to dup stdout")?;
- dup2(fd.as_ref(), io::stderr().as_fd()).context("Failed to dup stderr")?;
- Ok(())
-}
-
-fn get_vm_payload_service() -> Result<Strong<dyn IVmPayloadService>> {
- get_unix_domain_rpc_interface(VM_PAYLOAD_SERVICE_SOCKET_NAME)
- .context(format!("Failed to connect to service: {}", VM_PAYLOAD_SERVICE_SOCKET_NAME))
-}
diff --git a/microdroid_manager/Android.bp b/microdroid_manager/Android.bp
index 4b06b3e..44b4c01 100644
--- a/microdroid_manager/Android.bp
+++ b/microdroid_manager/Android.bp
@@ -23,6 +23,7 @@
"libdiced_sample_inputs",
"libdiced_utils",
"libglob",
+ "libhex",
"libitertools",
"libkernlog",
"libkeystore2_crypto_rust",
diff --git a/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl b/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
index 1141965..f8e7d34 100644
--- a/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
+++ b/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
@@ -16,8 +16,6 @@
package android.system.virtualization.payload;
-import android.os.ParcelFileDescriptor;
-
/**
* This interface regroups the tasks that payloads delegate to
* Microdroid Manager for execution.
@@ -63,16 +61,4 @@
* @throws SecurityException if the use of test APIs is not permitted.
*/
byte[] getDiceAttestationCdi();
-
- /**
- * Sets up a standard I/O proxy to the host.
- *
- * Creates a socket with the host and notifies its listeners that the stdio
- * proxy is ready.
- *
- * Temporarily uses a random free port allocated by the OS.
- * @return a file descriptor that the payload should dup() its standard I/O
- * file descriptors to.
- */
- ParcelFileDescriptor setupStdioProxy();
}
diff --git a/microdroid_manager/src/dice.rs b/microdroid_manager/src/dice.rs
index 3881db3..499835f 100644
--- a/microdroid_manager/src/dice.rs
+++ b/microdroid_manager/src/dice.rs
@@ -38,6 +38,16 @@
pub bcc: Vec<u8>,
}
+impl DiceContext {
+ pub fn get_sealing_key(&self, salt: &[u8], identifier: &[u8], keysize: u32) -> Result<ZVec> {
+ // Deterministically derive a key to use for sealing data based on salt. Use different salt
+ // for different keys.
+ let mut key = ZVec::new(keysize as usize)?;
+ hkdf(&mut key, Md::sha256(), &self.cdi_seal, salt, identifier)?;
+ Ok(key)
+ }
+}
+
/// Artifacts that are mapped into the process address space from the driver.
pub enum DiceDriver<'a> {
Real {
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 0ac4167..a53b401 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -83,6 +83,13 @@
// SYNC WITH virtualizationservice/src/crosvm.rs
const FAILURE_SERIAL_DEVICE: &str = "/dev/ttyS1";
+/// Identifier for the key used for encrypted store.
+const ENCRYPTEDSTORE_BACKING_DEVICE: &str = "/dev/block/by-name/encryptedstore";
+const ENCRYPTEDSTORE_BIN: &str = "/system/bin/encryptedstore";
+const ENCRYPTEDSTORE_KEY_IDENTIFIER: &str = "encryptedstore_key";
+const ENCRYPTEDSTORE_KEYSIZE: u32 = 64;
+const ENCRYPTEDSTORE_MOUNTPOINT: &str = "/mnt/encryptedstore";
+
#[derive(thiserror::Error, Debug)]
enum MicrodroidError {
#[error("Cannot connect to virtualization service: {0}")]
@@ -365,7 +372,15 @@
// To minimize the exposure to untrusted data, derive dice profile as soon as possible.
info!("DICE derivation for payload");
- let dice = dice_derivation(dice, &verified_data, &payload_metadata)?;
+ let dice_context = dice_derivation(dice, &verified_data, &payload_metadata)?;
+
+ // Run encryptedstore binary to prepare the storage
+ let encryptedstore_child = if Path::new(ENCRYPTEDSTORE_BACKING_DEVICE).exists() {
+ info!("Preparing encryptedstore ...");
+ Some(prepare_encryptedstore(&dice_context).context("encryptedstore run")?)
+ } else {
+ None
+ };
// Before reading a file from the APK, start zipfuse
run_zipfuse(
@@ -419,7 +434,12 @@
// Wait until zipfuse has mounted the APK so we can access the payload
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)?;
+ register_vm_payload_service(allow_restricted_apis, service.clone(), dice_context)?;
+
+ if let Some(mut child) = encryptedstore_child {
+ let exitcode = child.wait().context("Wait for encryptedstore child")?;
+ ensure!(exitcode.success(), "Unable to prepare encrypted storage. Exitcode={}", exitcode);
+ }
system_properties::write("dev.bootcomplete", "1").context("set dev.bootcomplete")?;
exec_task(task, service).context("Failed to run payload")
@@ -440,8 +460,6 @@
fn run_apkdmverity(args: &[ApkDmverityArgument]) -> Result<Child> {
let mut cmd = Command::new(APKDMVERITY_BIN);
- cmd.stdin(Stdio::null()).stdout(Stdio::null()).stderr(Stdio::null());
-
for argument in args {
cmd.arg("--apk").arg(argument.apk).arg(argument.idsig).arg(argument.name);
if let Some(root_hash) = argument.saved_root_hash {
@@ -473,15 +491,7 @@
if let Some(property_name) = ready_prop {
cmd.args(["-p", property_name]);
}
- cmd.arg("-o")
- .arg(option)
- .arg(zip_path)
- .arg(mount_dir)
- .stdin(Stdio::null())
- .stdout(Stdio::null())
- .stderr(Stdio::null())
- .spawn()
- .context("Spawn zipfuse")
+ cmd.arg("-o").arg(option).arg(zip_path).arg(mount_dir).spawn().context("Spawn zipfuse")
}
fn write_apex_payload_data(
@@ -790,3 +800,28 @@
fn to_hex_string(buf: &[u8]) -> String {
buf.iter().map(|b| format!("{:02X}", b)).collect()
}
+
+fn prepare_encryptedstore(dice: &DiceContext) -> Result<Child> {
+ // Use a fixed salt to scope the derivation to this API.
+ // Generated using hexdump -vn32 -e'14/1 "0x%02X, " 1 "\n"' /dev/urandom
+ // TODO(b/241541860) : Move this (& other salts) to a salt container, i.e. a global enum
+ let salt = [
+ 0xFC, 0x1D, 0x35, 0x7B, 0x96, 0xF3, 0xEF, 0x17, 0x78, 0x7D, 0x70, 0xED, 0xEA, 0xFE, 0x1D,
+ 0x6F, 0xB3, 0xF9, 0x40, 0xCE, 0xDD, 0x99, 0x40, 0xAA, 0xA7, 0x0E, 0x92, 0x73, 0x90, 0x86,
+ 0x4A, 0x75,
+ ];
+ let key = dice.get_sealing_key(
+ &salt,
+ ENCRYPTEDSTORE_KEY_IDENTIFIER.as_bytes(),
+ ENCRYPTEDSTORE_KEYSIZE,
+ )?;
+
+ let mut cmd = Command::new(ENCRYPTEDSTORE_BIN);
+ cmd.arg("--blkdevice")
+ .arg(ENCRYPTEDSTORE_BACKING_DEVICE)
+ .arg("--key")
+ .arg(hex::encode(&*key))
+ .args(["--mountpoint", ENCRYPTEDSTORE_MOUNTPOINT])
+ .spawn()
+ .context("encryptedstore failed")
+}
diff --git a/microdroid_manager/src/vm_payload_service.rs b/microdroid_manager/src/vm_payload_service.rs
index 126a8a9..98b9f2b 100644
--- a/microdroid_manager/src/vm_payload_service.rs
+++ b/microdroid_manager/src/vm_payload_service.rs
@@ -18,18 +18,12 @@
use android_system_virtualization_payload::aidl::android::system::virtualization::payload::IVmPayloadService::{
BnVmPayloadService, IVmPayloadService, VM_PAYLOAD_SERVICE_SOCKET_NAME};
use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::IVirtualMachineService;
-use anyhow::{bail, Context, Result};
-use binder::{Interface, BinderFeatures, ExceptionCode, ParcelFileDescriptor, Status, Strong};
+use anyhow::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::fs::File;
-use std::sync::mpsc;
-use std::thread;
-use std::time::Duration;
-use std::os::unix::io::{FromRawFd, IntoRawFd};
-use vsock::VsockListener;
+use rpcbinder::RpcServer;
/// Implementation of `IVmPayloadService`.
struct VmPayloadService {
@@ -70,16 +64,6 @@
self.check_restricted_apis_allowed()?;
Ok(self.dice.cdi_attest.to_vec())
}
-
- fn setupStdioProxy(&self) -> binder::Result<ParcelFileDescriptor> {
- let f = self.setup_payload_stdio_proxy().map_err(|e| {
- Status::new_service_specific_error_str(
- -1,
- Some(format!("Failed to create stdio proxy: {:?}", e)),
- )
- })?;
- Ok(ParcelFileDescriptor::new(f))
- }
}
impl Interface for VmPayloadService {}
@@ -102,22 +86,6 @@
Err(Status::new_exception_str(ExceptionCode::SECURITY, Some("Use of restricted APIs")))
}
}
-
- fn setup_payload_stdio_proxy(&self) -> Result<File> {
- // Instead of a predefined port in the host, we open up a port in the guest and have
- // the host connect to it. This makes it possible to have per-app instances of VS.
- const ANY_PORT: u32 = u32::MAX; // (u32)-1
- let listener = VsockListener::bind_with_cid_port(libc::VMADDR_CID_ANY, ANY_PORT)
- .context("Failed to create vsock listener")?;
- let addr = listener.local_addr().context("Failed to resolve listener port")?;
- self.virtual_machine_service
- .connectPayloadStdioProxy(addr.port() as i32)
- .context("Failed to connect to the host")?;
- let (stream, _) =
- listener.accept().context("Failed to accept vsock connection from the host")?;
- // SAFETY: ownership is transferred from stream to the new File
- Ok(unsafe { File::from_raw_fd(stream.into_raw_fd()) })
- }
}
/// Registers the `IVmPayloadService` service.
@@ -130,29 +98,16 @@
VmPayloadService::new(allow_restricted_apis, vm_service, dice),
BinderFeatures::default(),
);
- 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);
- }
+
+ let server = RpcServer::new_init_unix_domain(
+ vm_payload_binder.as_binder(),
+ VM_PAYLOAD_SERVICE_SOCKET_NAME,
+ )?;
+ info!("The RPC server '{}' is running.", VM_PAYLOAD_SERVICE_SOCKET_NAME);
+
+ // Move server reference into a background thread and run it forever.
+ std::thread::spawn(move || {
+ server.join();
});
- 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),
- }
+ Ok(())
}
diff --git a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
index e8c435f..eda4f75 100644
--- a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
+++ b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
@@ -36,4 +36,7 @@
/* get the APK contents path. */
String getApkContentsPath();
+
+ /* get the encrypted storage path. */
+ String getEncryptedStoragePath();
}
diff --git a/tests/helper/Android.bp b/tests/helper/Android.bp
index 86af955..7473dab 100644
--- a/tests/helper/Android.bp
+++ b/tests/helper/Android.bp
@@ -3,15 +3,12 @@
}
java_library_static {
- name: "VirtualizationTestHelper",
- srcs: ["src/java/com/android/virt/**/*.java"],
- host_supported: true,
-}
-
-java_library_static {
name: "MicrodroidTestHelper",
srcs: ["src/java/com/android/microdroid/test/common/*.java"],
host_supported: true,
+ libs: [
+ "framework-annotations-lib",
+ ],
}
java_library_static {
@@ -21,7 +18,6 @@
"androidx.test.runner",
"androidx.test.ext.junit",
"MicrodroidTestHelper",
- "VirtualizationTestHelper",
"truth-prebuilt",
],
// We need to compile against the .impl library which includes the hidden
diff --git a/tests/helper/src/java/com/android/microdroid/test/common/DeviceProperties.java b/tests/helper/src/java/com/android/microdroid/test/common/DeviceProperties.java
new file mode 100644
index 0000000..1fc163b
--- /dev/null
+++ b/tests/helper/src/java/com/android/microdroid/test/common/DeviceProperties.java
@@ -0,0 +1,69 @@
+/*
+ * 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.
+ */
+
+package com.android.microdroid.test.common;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/** This class can be used in both host tests and device tests to get the device properties. */
+public final class DeviceProperties {
+ /** PropertyGetter is used to get the property associated to a given key. */
+ public interface PropertyGetter {
+ @Nullable
+ String getProperty(@NonNull String key) throws Exception;
+ }
+
+ private static final String KEY_VENDOR_DEVICE = "ro.product.vendor.device";
+ private static final String KEY_METRICS_TAG = "debug.hypervisor.metrics_tag";
+
+ private static final String CUTTLEFISH_DEVICE_PREFIX = "vsoc_";
+
+ @NonNull private final PropertyGetter mPropertyGetter;
+
+ private DeviceProperties(@NonNull PropertyGetter propertyGetter) {
+ mPropertyGetter = requireNonNull(propertyGetter);
+ }
+
+ /** Creates a new instance of {@link DeviceProperties}. */
+ @NonNull
+ public static DeviceProperties create(@NonNull PropertyGetter propertyGetter) {
+ return new DeviceProperties(propertyGetter);
+ }
+
+ /**
+ * @return whether the device is a cuttlefish device.
+ */
+ public boolean isCuttlefish() {
+ String vendorDeviceName = getProperty(KEY_VENDOR_DEVICE);
+ return vendorDeviceName != null && vendorDeviceName.startsWith(CUTTLEFISH_DEVICE_PREFIX);
+ }
+
+ @Nullable
+ public String getMetricsTag() {
+ return getProperty(KEY_METRICS_TAG);
+ }
+
+ private String getProperty(String key) {
+ try {
+ return mPropertyGetter.getProperty(key);
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Cannot get property for the key: " + key, e);
+ }
+ }
+}
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 d1e1f6c..24e2049 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
@@ -35,8 +35,8 @@
import androidx.test.core.app.ApplicationProvider;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.microdroid.test.common.DeviceProperties;
import com.android.microdroid.test.common.MetricsProcessor;
-import com.android.virt.VirtualizationTestHelper;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
@@ -51,13 +51,12 @@
public abstract class MicrodroidDeviceTestBase {
public static boolean isCuttlefish() {
- return VirtualizationTestHelper.isCuttlefish(
- SystemProperties.get("ro.product.vendor.device"));
+ return DeviceProperties.create(SystemProperties::get).isCuttlefish();
}
public static String getMetricPrefix() {
return MetricsProcessor.getMetricPrefix(
- SystemProperties.get("debug.hypervisor.metrics_tag"));
+ DeviceProperties.create(SystemProperties::get).getMetricsTag());
}
protected final void grantPermission(String permission) {
@@ -230,9 +229,6 @@
public void onPayloadStarted(VirtualMachine vm) {}
@Override
- public void onPayloadStdio(VirtualMachine vm, ParcelFileDescriptor stream) {}
-
- @Override
public void onPayloadReady(VirtualMachine vm) {}
@Override
diff --git a/tests/hostside/helper/Android.bp b/tests/hostside/helper/Android.bp
index b2333ab..6196ec5 100644
--- a/tests/hostside/helper/Android.bp
+++ b/tests/hostside/helper/Android.bp
@@ -12,6 +12,5 @@
],
static_libs: [
"MicrodroidTestHelper",
- "VirtualizationTestHelper",
],
}
diff --git a/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java b/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
index 9bcd1d3..ac37ee0 100644
--- a/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
+++ b/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
@@ -26,6 +26,7 @@
import static org.junit.Assume.assumeTrue;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.microdroid.test.common.DeviceProperties;
import com.android.microdroid.test.common.MetricsProcessor;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.device.DeviceNotAvailableException;
@@ -34,7 +35,6 @@
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.RunUtil;
-import com.android.virt.VirtualizationTestHelper;
import java.io.File;
import java.io.FileNotFoundException;
@@ -42,17 +42,12 @@
public abstract class MicrodroidHostTestCaseBase extends BaseHostJUnit4Test {
protected static final String TEST_ROOT = "/data/local/tmp/virt/";
- protected static final String VIRT_APEX = "/apex/com.android.virt/";
protected static final String LOG_PATH = TEST_ROOT + "log.txt";
protected static final String CONSOLE_PATH = TEST_ROOT + "console.txt";
private static final int TEST_VM_ADB_PORT = 8000;
private static final String MICRODROID_SERIAL = "localhost:" + TEST_VM_ADB_PORT;
private static final String INSTANCE_IMG = "instance.img";
- // This is really slow on GCE (2m 40s) but fast on localhost or actual Android phones (< 10s).
- // Then there is time to run the actual task. Set the maximum timeout value big enough.
- private static final long MICRODROID_MAX_LIFETIME_MINUTES = 20;
-
private static final long MICRODROID_ADB_CONNECT_TIMEOUT_MINUTES = 5;
protected static final long MICRODROID_COMMAND_TIMEOUT_MILLIS = 30000;
private static final long MICRODROID_COMMAND_RETRY_INTERVAL_MILLIS = 500;
@@ -87,14 +82,13 @@
android.tryRun("rm", "-rf", "/data/misc/virtualizationservice/*");
}
- protected boolean isCuttlefish() throws Exception {
- return VirtualizationTestHelper.isCuttlefish(
- getDevice().getProperty("ro.product.vendor.device"));
+ protected boolean isCuttlefish() {
+ return DeviceProperties.create(getDevice()::getProperty).isCuttlefish();
}
- protected String getMetricPrefix() throws Exception {
+ protected String getMetricPrefix() {
return MetricsProcessor.getMetricPrefix(
- getDevice().getProperty("debug.hypervisor.metrics_tag"));
+ DeviceProperties.create(getDevice()::getProperty).getMetricsTag());
}
public static void testIfDeviceIsCapable(ITestDevice androidDevice) throws Exception {
@@ -196,25 +190,6 @@
return pathLine.substring("package:".length());
}
- private static void forwardFileToLog(CommandRunner android, String path, String tag)
- throws DeviceNotAvailableException {
- android.runWithTimeout(
- MICRODROID_MAX_LIFETIME_MINUTES * 60 * 1000,
- "logwrapper",
- "sh",
- "-c",
- "\"$'tail -f -n +0 " + path
- + " | sed \\'s/^/" + tag + ": /g\\''\""); // add tags in front of lines
- }
-
- public static void shutdownMicrodroid(ITestDevice androidDevice, String cid)
- throws DeviceNotAvailableException {
- CommandRunner android = new CommandRunner(androidDevice);
-
- // Shutdown the VM
- android.run(VIRT_APEX + "bin/vm", "stop", cid);
- }
-
// Establish an adb connection to microdroid by letting Android forward the connection to
// microdroid. Wait until the connection is established and microdroid is booted.
public static void adbConnectToMicrodroid(ITestDevice androidDevice, String cid) {
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index b872a73..cffaae1 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -42,6 +42,7 @@
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.device.TestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestMetrics;
import com.android.tradefed.util.CommandResult;
@@ -62,13 +63,19 @@
import org.xml.sax.Attributes;
import org.xml.sax.helpers.DefaultHandler;
+import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -78,6 +85,7 @@
private static final String APK_NAME = "MicrodroidTestApp.apk";
private static final String PACKAGE_NAME = "com.android.microdroid.test";
private static final String SHELL_PACKAGE_NAME = "com.android.shell";
+ private static final String VIRT_APEX = "/apex/com.android.virt/";
private static final int MIN_MEM_ARM64 = 145;
private static final int MIN_MEM_X86_64 = 196;
@@ -266,11 +274,11 @@
return new ActiveApexInfoList(list);
}
- private String runMicrodroidWithResignedImages(
+ private Process runMicrodroidWithResignedImages(
File key,
Map<String, File> keyOverrides,
boolean isProtected,
- boolean daemonize,
+ boolean waitForOnline,
String consolePath)
throws Exception {
CommandRunner android = new CommandRunner(getDevice());
@@ -375,19 +383,49 @@
final String configPath = TEST_ROOT + "raw_config.json";
getDevice().pushString(config.toString(), configPath);
- final String logPath = LOG_PATH;
- final String ret =
- android.runWithTimeout(
- 60 * 1000,
+ List<String> args =
+ Arrays.asList(
+ "adb",
+ "-s",
+ getDevice().getSerialNumber(),
+ "shell",
VIRT_APEX + "bin/vm run",
- daemonize ? "--daemonize" : "",
(consolePath != null) ? "--console " + consolePath : "",
- "--log " + logPath,
+ "--log " + LOG_PATH,
configPath);
+
+ PipedInputStream pis = new PipedInputStream();
+ Process process = RunUtil.getDefault().runCmdInBackground(args, new PipedOutputStream(pis));
+ BufferedReader stdout = new BufferedReader(new InputStreamReader(pis));
+
+ // Retrieve the CID from the vm tool output
+ String cid = null;
Pattern pattern = Pattern.compile("with CID (\\d+)");
- Matcher matcher = pattern.matcher(ret);
- assertWithMessage("Failed to find CID").that(matcher.find()).isTrue();
- return matcher.group(1);
+ try {
+ String line;
+ while ((line = stdout.readLine()) != null) {
+ CLog.i("VM output: " + line);
+ Matcher matcher = pattern.matcher(line);
+ if (matcher.find()) {
+ cid = matcher.group(1);
+ break;
+ }
+ }
+ } catch (IOException ex) {
+ throw new IllegalStateException(
+ "Could not find the CID of the VM. The process probably died.", ex);
+ }
+ if (cid == null) {
+ throw new IllegalStateException(
+ "Could not find the CID of the VM. Output does not contain the expected"
+ + " pattern.");
+ }
+
+ if (waitForOnline) {
+ adbConnectToMicrodroid(getDevice(), cid);
+ }
+
+ return process;
}
@Test
@@ -400,9 +438,12 @@
File key = findTestFile("test.com.android.virt.pem");
Map<String, File> keyOverrides = Map.of();
boolean isProtected = true;
- boolean daemonize = false; // VM should shut down due to boot failure.
+ boolean waitForOnline = false; // VM should shut down due to boot failure.
String consolePath = TEST_ROOT + "console";
- runMicrodroidWithResignedImages(key, keyOverrides, isProtected, daemonize, consolePath);
+ Process process =
+ runMicrodroidWithResignedImages(
+ key, keyOverrides, isProtected, waitForOnline, consolePath);
+ process.waitFor(5L, TimeUnit.SECONDS);
assertThat(getDevice().pullFileContents(consolePath), containsString("pvmfw boot failed"));
}
@@ -416,14 +457,12 @@
File key = findTestFile("test.com.android.virt.pem");
Map<String, File> keyOverrides = Map.of();
boolean isProtected = false;
- boolean daemonize = true;
+ boolean waitForOnline = true; // Device online means that boot must have succeeded.
String consolePath = TEST_ROOT + "console";
- String cid =
+ Process process =
runMicrodroidWithResignedImages(
- key, keyOverrides, isProtected, daemonize, consolePath);
- // Adb connection to the microdroid means that boot succeeded.
- adbConnectToMicrodroid(getDevice(), cid);
- shutdownMicrodroid(getDevice(), cid);
+ key, keyOverrides, isProtected, waitForOnline, consolePath);
+ process.destroy();
}
@Test
@@ -434,18 +473,18 @@
File key2 = findTestFile("test2.com.android.virt.pem");
Map<String, File> keyOverrides = Map.of("microdroid_vbmeta.img", key2);
boolean isProtected = false; // Not interested in pvwfw
- boolean daemonize = true; // Bootloader fails and enters prompts.
+ boolean waitForOnline = false; // Bootloader fails and enters prompts.
// To be able to stop it, it should be a daemon.
String consolePath = TEST_ROOT + "console";
- String cid =
+ Process process =
runMicrodroidWithResignedImages(
- key, keyOverrides, isProtected, daemonize, consolePath);
+ key, keyOverrides, isProtected, waitForOnline, consolePath);
// Wait so that init can print errors to console (time in cuttlefish >> in real device)
assertThatEventually(
100000,
() -> getDevice().pullFileContents(consolePath),
containsString("init: [libfs_avb]Failed to verify vbmeta digest"));
- shutdownMicrodroid(getDevice(), cid);
+ process.destroy();
}
private boolean isTombstoneGeneratedWithConfig(String configPath) throws Exception {
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 92155ee..17574c7 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -28,7 +28,6 @@
import android.content.Context;
import android.os.Build;
-import android.os.ParcelFileDescriptor;
import android.os.ServiceSpecificException;
import android.os.SystemProperties;
import android.system.virtualmachine.VirtualMachine;
@@ -123,6 +122,7 @@
assertThat(testResults.mAppRunProp).isEqualTo("true");
assertThat(testResults.mSublibRunProp).isEqualTo("true");
assertThat(testResults.mApkContentsPath).isEqualTo("/mnt/apk");
+ assertThat(testResults.mEncryptedStoragePath).isEqualTo("");
}
@Test
@@ -721,6 +721,7 @@
String mSublibRunProp;
String mExtraApkTestProp;
String mApkContentsPath;
+ String mEncryptedStoragePath;
}
private TestResults runVmTestService(VirtualMachine vm) throws Exception {
@@ -742,6 +743,8 @@
testResults.mExtraApkTestProp =
testService.readProperty("debug.microdroid.test.extra_apk");
testResults.mApkContentsPath = testService.getApkContentsPath();
+ testResults.mEncryptedStoragePath =
+ testService.getEncryptedStoragePath();
} catch (Exception e) {
testResults.mException = e;
}
@@ -760,13 +763,6 @@
Log.i(TAG, "onPayloadStarted");
payloadStarted.complete(true);
}
-
- @Override
- public void onPayloadStdio(VirtualMachine vm, ParcelFileDescriptor stream) {
- Log.i(TAG, "onPayloadStdio");
- logVmOutput(
- TAG, new FileInputStream(stream.getFileDescriptor()), "Payload");
- }
};
listener.runToFinish(TAG, vm);
assertThat(payloadStarted.getNow(false)).isTrue();
diff --git a/tests/testapk/src/native/testbinary.cpp b/tests/testapk/src/native/testbinary.cpp
index 1b18ce9..694f452 100644
--- a/tests/testapk/src/native/testbinary.cpp
+++ b/tests/testapk/src/native/testbinary.cpp
@@ -123,6 +123,16 @@
*out = path;
return ndk::ScopedAStatus::ok();
}
+
+ ndk::ScopedAStatus getEncryptedStoragePath(std::string* out) override {
+ const char* path_c = AVmPayload_getEncryptedStoragePath();
+ if (path_c == nullptr) {
+ out->clear();
+ } else {
+ *out = path_c;
+ }
+ return ndk::ScopedAStatus::ok();
+ }
};
auto testService = ndk::SharedRefBase::make<TestService>();
@@ -158,9 +168,6 @@
} // Anonymous namespace
extern "C" int AVmPayload_main() {
- // Forward standard I/O to the host.
- AVmPayload_setupStdioProxy();
-
// disable buffering to communicate seamlessly
setvbuf(stdin, nullptr, _IONBF, 0);
setvbuf(stdout, nullptr, _IONBF, 0);
diff --git a/virtualizationservice/Android.bp b/virtualizationservice/Android.bp
index d6f4607..b767013 100644
--- a/virtualizationservice/Android.bp
+++ b/virtualizationservice/Android.bp
@@ -22,6 +22,7 @@
rustlibs: [
"android.system.virtualizationcommon-rust",
"android.system.virtualizationservice-rust",
+ "android.system.virtualizationservice_internal-rust",
"android.system.virtualmachineservice-rust",
"android.os.permissions_aidl-rust",
"libandroid_logger",
diff --git a/virtualizationservice/aidl/Android.bp b/virtualizationservice/aidl/Android.bp
index da237f8..a0bbc00 100644
--- a/virtualizationservice/aidl/Android.bp
+++ b/virtualizationservice/aidl/Android.bp
@@ -34,6 +34,23 @@
}
aidl_interface {
+ name: "android.system.virtualizationservice_internal",
+ srcs: ["android/system/virtualizationservice_internal/**/*.aidl"],
+ unstable: true,
+ backend: {
+ java: {
+ sdk_version: "module_current",
+ },
+ rust: {
+ enabled: true,
+ apex_available: [
+ "com.android.virt",
+ ],
+ },
+ },
+}
+
+aidl_interface {
name: "android.system.virtualmachineservice",
srcs: ["android/system/virtualmachineservice/**/*.aidl"],
imports: ["android.system.virtualizationcommon"],
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl
index 521cf12..a329fa6 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl
@@ -29,11 +29,6 @@
void onPayloadStarted(int cid);
/**
- * Called when the payload provides access to its standard input/output via a socket.
- */
- void onPayloadStdio(int cid, in ParcelFileDescriptor fd);
-
- /**
* Called when the payload in the VM is ready to serve.
*/
void onPayloadReady(int cid);
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/PartitionType.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/PartitionType.aidl
index f25e674..774681a 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/PartitionType.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/PartitionType.aidl
@@ -29,4 +29,8 @@
* The partition is initialized as an instance image which is formatted to hold per-VM secrets
*/
ANDROID_VM_INSTANCE = 1,
+ /**
+ * The partition is initialized to back encryptedstore disk image formatted to indicate intent
+ */
+ ENCRYPTEDSTORE = 2,
}
diff --git a/tests/helper/src/java/com/android/virt/VirtualizationTestHelper.java b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IGlobalVmContext.aidl
similarity index 66%
rename from tests/helper/src/java/com/android/virt/VirtualizationTestHelper.java
rename to virtualizationservice/aidl/android/system/virtualizationservice_internal/IGlobalVmContext.aidl
index 4c27915..1a7aa4a 100644
--- a/tests/helper/src/java/com/android/virt/VirtualizationTestHelper.java
+++ b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IGlobalVmContext.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -13,10 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.virt;
+package android.system.virtualizationservice_internal;
-public abstract class VirtualizationTestHelper {
- public static boolean isCuttlefish(String vendorDeviceName) {
- return vendorDeviceName != null && vendorDeviceName.startsWith("vsoc_");
- }
+interface IGlobalVmContext {
+ /** Get the CID allocated to the VM. */
+ int getCid();
}
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
new file mode 100644
index 0000000..851ddf4
--- /dev/null
+++ b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.system.virtualizationservice_internal;
+
+import android.system.virtualizationservice_internal.IGlobalVmContext;
+
+interface IVirtualizationServiceInternal {
+ /**
+ * Allocates global context for a new VM.
+ *
+ * This allocates VM's globally unique resources such as the CID.
+ * The resources will not be recycled as long as there is a strong reference
+ * to the returned object.
+ */
+ IGlobalVmContext allocateGlobalVmContext();
+}
diff --git a/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl b/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
index deee662..f2d92af 100644
--- a/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
+++ b/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
@@ -50,9 +50,4 @@
* Notifies that an error has occurred inside the VM.
*/
void notifyError(ErrorCode errorCode, in String message);
-
- /**
- * Notifies that the guest has started a stdio proxy on the given port.
- */
- void connectPayloadStdioProxy(int port);
}
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 30b89da..578960c 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -36,6 +36,10 @@
VirtualMachineRawConfig::VirtualMachineRawConfig,
VirtualMachineState::VirtualMachineState,
};
+use android_system_virtualizationservice_internal::aidl::android::system::virtualizationservice_internal::{
+ IGlobalVmContext::{BnGlobalVmContext, IGlobalVmContext},
+ IVirtualizationServiceInternal::{BnVirtualizationServiceInternal, IVirtualizationServiceInternal},
+};
use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::{
BnVirtualMachineService, IVirtualMachineService, VM_BINDER_SERVICE_PORT,
VM_TOMBSTONES_SERVICE_PORT,
@@ -94,10 +98,96 @@
const MICRODROID_OS_NAME: &str = "microdroid";
-/// Implementation of `IVirtualizationService`, the entry point of the AIDL service.
+const UNFORMATTED_STORAGE_MAGIC: &str = "UNFORMATTED-STORAGE";
+
+/// Singleton service for allocating globally-unique VM resources, such as the CID, and running
+/// singleton servers, like tombstone receiver.
#[derive(Debug, Default)]
+pub struct VirtualizationServiceInternal {
+ state: Arc<Mutex<GlobalState>>,
+}
+
+impl VirtualizationServiceInternal {
+ pub fn init() -> VirtualizationServiceInternal {
+ let service = VirtualizationServiceInternal::default();
+
+ std::thread::spawn(|| {
+ if let Err(e) = handle_stream_connection_tombstoned() {
+ warn!("Error receiving tombstone from guest or writing them. Error: {:?}", e);
+ }
+ });
+
+ service
+ }
+}
+
+impl Interface for VirtualizationServiceInternal {}
+
+impl IVirtualizationServiceInternal for VirtualizationServiceInternal {
+ fn allocateGlobalVmContext(&self) -> binder::Result<Strong<dyn IGlobalVmContext>> {
+ let state = &mut *self.state.lock().unwrap();
+ let cid = state.allocate_cid().map_err(|e| {
+ Status::new_exception_str(ExceptionCode::ILLEGAL_STATE, Some(e.to_string()))
+ })?;
+ Ok(GlobalVmContext::create(cid))
+ }
+}
+
+/// The mutable state of the VirtualizationServiceInternal. There should only be one instance
+/// of this struct.
+#[derive(Debug, Default)]
+struct GlobalState {}
+
+impl GlobalState {
+ /// Get the next available CID, or an error if we have run out. The last CID used is stored in
+ /// a system property so that restart of virtualizationservice doesn't reuse CID while the host
+ /// Android is up.
+ fn allocate_cid(&mut self) -> Result<Cid> {
+ let cid = match system_properties::read(SYSPROP_LAST_CID)? {
+ Some(val) => match val.parse::<Cid>() {
+ Ok(num) => num.checked_add(1).ok_or_else(|| anyhow!("ran out of CIDs"))?,
+ Err(_) => {
+ error!("Invalid value '{}' of property '{}'", val, SYSPROP_LAST_CID);
+ FIRST_GUEST_CID
+ }
+ },
+ None => FIRST_GUEST_CID,
+ };
+ system_properties::write(SYSPROP_LAST_CID, &format!("{}", cid))?;
+ Ok(cid)
+ }
+}
+
+/// Implementation of the AIDL `IGlobalVmContext` interface.
+#[derive(Debug, Default)]
+struct GlobalVmContext {
+ /// The unique CID assigned to the VM for vsock communication.
+ cid: Cid,
+ /// Keeps our service process running as long as this VM instance exists.
+ #[allow(dead_code)]
+ lazy_service_guard: LazyServiceGuard,
+}
+
+impl GlobalVmContext {
+ fn create(cid: 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)
+ }
+}
+
+/// Implementation of `IVirtualizationService`, the entry point of the AIDL service.
+#[derive(Debug)]
pub struct VirtualizationService {
state: Arc<Mutex<State>>,
+ global_service: Strong<dyn IVirtualizationServiceInternal>,
}
impl Interface for VirtualizationService {
@@ -173,6 +263,7 @@
match partition_type {
PartitionType::RAW => Ok(()),
PartitionType::ANDROID_VM_INSTANCE => format_as_android_vm_instance(&mut part),
+ PartitionType::ENCRYPTEDSTORE => format_as_encryptedstore(&mut part),
_ => Err(Error::new(
ErrorKind::Unsupported,
format!("Unsupported partition type {:?}", partition_type),
@@ -299,13 +390,11 @@
impl VirtualizationService {
pub fn init() -> VirtualizationService {
- let service = VirtualizationService::default();
+ let global_service = VirtualizationServiceInternal::init();
+ let global_service =
+ BnVirtualizationServiceInternal::new_binder(global_service, BinderFeatures::default());
- std::thread::spawn(|| {
- if let Err(e) = handle_stream_connection_tombstoned() {
- warn!("Error receiving tombstone from guest or writing them. Error: {:?}", e);
- }
- });
+ let service = VirtualizationService { global_service, state: Default::default() };
// binder server for vm
// reference to state (not the state itself) is copied
@@ -346,12 +435,14 @@
check_use_custom_virtual_machine()?;
}
+ let vm_context = self.global_service.allocateGlobalVmContext()?;
+ let cid = vm_context.getCid()? as Cid;
+
let state = &mut *self.state.lock().unwrap();
let console_fd = console_fd.map(clone_file).transpose()?;
let log_fd = log_fd.map(clone_file).transpose()?;
let requester_uid = ThreadState::get_calling_uid();
let requester_debug_pid = ThreadState::get_calling_pid();
- let cid = state.next_cid().or(Err(ExceptionCode::ILLEGAL_STATE))?;
// Counter to generate unique IDs for temporary image files.
let mut next_temporary_image_id = 0;
@@ -468,14 +559,20 @@
detect_hangup: is_app_config,
};
let instance = Arc::new(
- VmInstance::new(crosvm_config, temporary_directory, requester_uid, requester_debug_pid)
- .map_err(|e| {
- error!("Failed to create VM with config {:?}: {:?}", config, e);
- Status::new_service_specific_error_str(
- -1,
- Some(format!("Failed to create VM: {:?}", e)),
- )
- })?,
+ VmInstance::new(
+ crosvm_config,
+ temporary_directory,
+ requester_uid,
+ requester_debug_pid,
+ vm_context,
+ )
+ .map_err(|e| {
+ error!("Failed to create VM with config {:?}: {:?}", config, e);
+ Status::new_service_specific_error_str(
+ -1,
+ Some(format!("Failed to create VM: {:?}", e)),
+ )
+ })?,
);
state.add_vm(Arc::downgrade(&instance));
Ok(VirtualMachine::create(instance))
@@ -499,6 +596,11 @@
part.flush()
}
+fn format_as_encryptedstore(part: &mut dyn Write) -> std::io::Result<()> {
+ part.write_all(UNFORMATTED_STORAGE_MAGIC.as_bytes())?;
+ part.flush()
+}
+
fn prepare_ramdump_file(ramdump_path: &Path) -> Result<File> {
File::create(ramdump_path).context(format!("Failed to create ramdump file {:?}", &ramdump_path))
}
@@ -859,16 +961,6 @@
}
}
- /// Call all registered callbacks to notify that the payload has provided a standard I/O proxy.
- pub fn notify_payload_stdio(&self, cid: Cid, fd: ParcelFileDescriptor) {
- let callbacks = &*self.0.lock().unwrap();
- for callback in callbacks {
- if let Err(e) = callback.onPayloadStdio(cid as i32, &fd) {
- error!("Error notifying payload stdio event from VM CID {}: {:?}", cid, e);
- }
- }
- }
-
/// Call all registered callbacks to say that the VM has died.
pub fn callback_on_died(&self, cid: Cid, reason: DeathReason) {
let callbacks = &*self.0.lock().unwrap();
@@ -943,27 +1035,6 @@
let vm = self.debug_held_vms.swap_remove(pos);
Some(vm)
}
-
- /// Get the next available CID, or an error if we have run out. The last CID used is stored in
- /// a system property so that restart of virtualizationservice doesn't reuse CID while the host
- /// Android is up.
- fn next_cid(&mut self) -> Result<Cid> {
- let next = if let Some(val) = system_properties::read(SYSPROP_LAST_CID)? {
- if let Ok(num) = val.parse::<u32>() {
- num.checked_add(1).ok_or_else(|| anyhow!("run out of CID"))?
- } else {
- error!("Invalid last CID {}. Using {}", &val, FIRST_GUEST_CID);
- FIRST_GUEST_CID
- }
- } else {
- // First VM since the boot
- FIRST_GUEST_CID
- };
- // Persist the last value for next use
- let str_val = format!("{}", next);
- system_properties::write(SYSPROP_LAST_CID, &str_val)?;
- Ok(next)
- }
}
/// Gets the `VirtualMachineState` of the given `VmInstance`.
@@ -1114,27 +1185,6 @@
))
}
}
-
- fn connectPayloadStdioProxy(&self, port: i32) -> binder::Result<()> {
- let cid = self.cid;
- if let Some(vm) = self.state.lock().unwrap().get_vm(cid) {
- info!("VM with CID {} started a stdio proxy", cid);
- let stream = VsockStream::connect_with_cid_port(cid, port as u32).map_err(|e| {
- Status::new_service_specific_error_str(
- -1,
- Some(format!("Failed to connect to guest stdio proxy: {:?}", e)),
- )
- })?;
- vm.callbacks.notify_payload_stdio(cid, vsock_stream_to_pfd(stream));
- Ok(())
- } else {
- error!("connectPayloadStdioProxy is called from an unknown CID {}", cid);
- Err(Status::new_service_specific_error_str(
- -1,
- Some(format!("cannot find a VM with CID {}", cid)),
- ))
- }
- }
}
impl VirtualMachineService {
diff --git a/virtualizationservice/src/crosvm.rs b/virtualizationservice/src/crosvm.rs
index db6da43..68324c5 100644
--- a/virtualizationservice/src/crosvm.rs
+++ b/virtualizationservice/src/crosvm.rs
@@ -38,6 +38,7 @@
use std::time::{Duration, SystemTime};
use std::thread;
use android_system_virtualizationservice::aidl::android::system::virtualizationservice::DeathReason::DeathReason;
+use android_system_virtualizationservice_internal::aidl::android::system::virtualizationservice_internal::IGlobalVmContext::IGlobalVmContext;
use binder::Strong;
use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::IVirtualMachineService;
use tombstoned_client::{TombstonedConnection, DebuggerdDumpType};
@@ -202,6 +203,9 @@
pub struct VmInstance {
/// The current state of the VM.
pub vm_state: Mutex<VmState>,
+ /// Handle to global resources allocated for this VM.
+ #[allow(dead_code)] // The handle is never read, we only need to hold it.
+ vm_context: Strong<dyn IGlobalVmContext>,
/// The CID assigned to the VM for vsock communication.
pub cid: Cid,
/// The name of the VM.
@@ -234,6 +238,7 @@
temporary_directory: PathBuf,
requester_uid: u32,
requester_debug_pid: i32,
+ vm_context: Strong<dyn IGlobalVmContext>,
) -> Result<VmInstance, Error> {
validate_config(&config)?;
let cid = config.cid;
@@ -241,6 +246,7 @@
let protected = config.protected;
Ok(VmInstance {
vm_state: Mutex::new(VmState::NotStarted { config }),
+ vm_context,
cid,
name,
protected,
diff --git a/virtualizationservice/src/payload.rs b/virtualizationservice/src/payload.rs
index 233e74b..f6e8a7b 100644
--- a/virtualizationservice/src/payload.rs
+++ b/virtualizationservice/src/payload.rs
@@ -420,7 +420,7 @@
if let Some(file) = storage_image {
writable_partitions.push(Partition {
- label: "encrypted-storage".to_owned(),
+ label: "encryptedstore".to_owned(),
image: Some(ParcelFileDescriptor::new(file)),
writable: true,
});
diff --git a/vm/src/run.rs b/vm/src/run.rs
index 7cd5a19..01b916b 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -27,7 +27,7 @@
use binder::ParcelFileDescriptor;
use microdroid_payload_config::VmPayloadConfig;
use std::fs::File;
-use std::io::{self, BufRead, BufReader};
+use std::io;
use std::os::unix::io::{AsRawFd, FromRawFd};
use std::path::{Path, PathBuf};
use vmclient::{ErrorCode, VmInstance};
@@ -103,7 +103,7 @@
service,
path,
storage_size.unwrap_or(10 * 1024 * 1024),
- PartitionType::RAW,
+ PartitionType::ENCRYPTEDSTORE,
)?;
}
Some(open_parcel_file(path, true)?)
@@ -280,20 +280,6 @@
eprintln!("payload started");
}
- fn on_payload_stdio(&self, _cid: i32, stream: &File) {
- eprintln!("connecting payload stdio...");
- // Show the output of the payload
- let mut reader = BufReader::new(stream.try_clone().unwrap());
- std::thread::spawn(move || loop {
- let mut s = String::new();
- match reader.read_line(&mut s) {
- Ok(0) => break,
- Ok(_) => print!("{}", s),
- Err(e) => eprintln!("error reading from virtual machine: {}", e),
- };
- });
- }
-
fn on_payload_ready(&self, _cid: i32) {
eprintln!("payload is ready");
}
diff --git a/vmclient/src/lib.rs b/vmclient/src/lib.rs
index 1dd553c..20b7f02 100644
--- a/vmclient/src/lib.rs
+++ b/vmclient/src/lib.rs
@@ -80,9 +80,6 @@
/// clients.
fn on_payload_ready(&self, cid: i32) {}
- /// Called by the payload to forward its standard I/O streams to the host.
- fn on_payload_stdio(&self, cid: i32, fd: &File);
-
/// Called when the payload has exited in the VM. `exit_code` is the exit code of the payload
/// process.
fn on_payload_finished(&self, cid: i32, exit_code: i32) {}
@@ -280,13 +277,6 @@
Ok(())
}
- fn onPayloadStdio(&self, cid: i32, stream: &ParcelFileDescriptor) -> BinderResult<()> {
- if let Some(ref callback) = self.client_callback {
- callback.on_payload_stdio(cid, stream.as_ref());
- }
- Ok(())
- }
-
fn onPayloadReady(&self, cid: i32) -> BinderResult<()> {
self.state.notify_state(VirtualMachineState::READY);
if let Some(ref callback) = self.client_callback {