Merge "Update source for Rust 1.66.1"
diff --git a/encryptedstore/src/main.rs b/encryptedstore/src/main.rs
index 96c80db..7a41f13 100644
--- a/encryptedstore/src/main.rs
+++ b/encryptedstore/src/main.rs
@@ -95,6 +95,8 @@
.data_device(data_device, dev_size)
.cipher(CipherType::AES256HCTR2)
.key(&key)
+ .opt_param("sector_size:4096")
+ .opt_param("iv_large_sectors")
.build()
.context("Couldn't build the DMCrypt target")?;
let dm = dm::DeviceMapper::new()?;
@@ -125,6 +127,7 @@
let mkfs_options = [
"-j", // Create appropriate sized journal
"-O metadata_csum", // Metadata checksum for filesystem integrity
+ "-b 4096", // block size in the filesystem
];
let mut cmd = Command::new(MK2FS_BIN);
let status = cmd
diff --git a/javalib/api/system-current.txt b/javalib/api/system-current.txt
index 1ba479f..3170d43 100644
--- a/javalib/api/system-current.txt
+++ b/javalib/api/system-current.txt
@@ -85,7 +85,8 @@
method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setVmOutputCaptured(boolean);
}
- public final class VirtualMachineDescriptor implements android.os.Parcelable {
+ public final class VirtualMachineDescriptor implements java.io.Closeable android.os.Parcelable {
+ method public void close() throws java.io.IOException;
method public int describeContents();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.system.virtualmachine.VirtualMachineDescriptor> CREATOR;
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index ba7174e..9902cb5 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -393,27 +393,31 @@
@NonNull String name,
@NonNull VirtualMachineDescriptor vmDescriptor)
throws VirtualMachineException {
- VirtualMachineConfig config = VirtualMachineConfig.from(vmDescriptor.getConfigFd());
File vmDir = createVmDir(context, name);
try {
- VirtualMachine vm =
- new VirtualMachine(context, name, config, VirtualizationService.getInstance());
- config.serialize(vm.mConfigFilePath);
- try {
- vm.mInstanceFilePath.createNewFile();
- } catch (IOException e) {
- throw new VirtualMachineException("failed to create instance image", e);
- }
- vm.importInstanceFrom(vmDescriptor.getInstanceImgFd());
-
- if (vmDescriptor.getEncryptedStoreFd() != null) {
+ VirtualMachine vm;
+ try (vmDescriptor) {
+ VirtualMachineConfig config = VirtualMachineConfig.from(vmDescriptor.getConfigFd());
+ vm = new VirtualMachine(context, name, config, VirtualizationService.getInstance());
+ config.serialize(vm.mConfigFilePath);
try {
- vm.mEncryptedStoreFilePath.createNewFile();
+ vm.mInstanceFilePath.createNewFile();
} catch (IOException e) {
- throw new VirtualMachineException(
- "failed to create encrypted storage image", e);
+ throw new VirtualMachineException("failed to create instance image", e);
}
- vm.importEncryptedStoreFrom(vmDescriptor.getEncryptedStoreFd());
+ vm.importInstanceFrom(vmDescriptor.getInstanceImgFd());
+
+ if (vmDescriptor.getEncryptedStoreFd() != null) {
+ try {
+ vm.mEncryptedStoreFilePath.createNewFile();
+ } catch (IOException e) {
+ throw new VirtualMachineException(
+ "failed to create encrypted storage image", e);
+ }
+ vm.importEncryptedStoreFrom(vmDescriptor.getEncryptedStoreFd());
+ }
+ } catch (IOException e) {
+ throw new VirtualMachineException(e);
}
return vm;
} catch (VirtualMachineException | RuntimeException e) {
@@ -1227,8 +1231,7 @@
if (configPath == null) {
return Collections.emptyList();
}
- try {
- ZipFile zipFile = new ZipFile(context.getPackageCodePath());
+ try (ZipFile zipFile = new ZipFile(context.getPackageCodePath())) {
InputStream inputStream =
zipFile.getInputStream(zipFile.getEntry(configPath));
List<String> apkList =
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
index 5842d01..c364b42 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -69,7 +69,6 @@
private static final String KEY_DEBUGLEVEL = "debugLevel";
private static final String KEY_PROTECTED_VM = "protectedVm";
private static final String KEY_MEMORY_BYTES = "memoryBytes";
- private static final String KEY_NUM_CPUS = "numCpus";
private static final String KEY_CPU_TOPOLOGY = "cpuTopology";
private static final String KEY_ENCRYPTED_STORAGE_BYTES = "encryptedStorageBytes";
private static final String KEY_VM_OUTPUT_CAPTURED = "vmOutputCaptured";
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java b/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java
index 483779a..1304af3 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java
@@ -25,6 +25,9 @@
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
+import java.io.Closeable;
+import java.io.IOException;
+
/**
* A VM descriptor that captures the state of a Virtual Machine.
*
@@ -34,8 +37,10 @@
*
* @hide
*/
+// TODO(b/268613460): should implement autocloseable.
@SystemApi
-public final class VirtualMachineDescriptor implements Parcelable {
+public final class VirtualMachineDescriptor implements Parcelable, Closeable {
+ private volatile boolean mClosed = false;
@NonNull private final ParcelFileDescriptor mConfigFd;
@NonNull private final ParcelFileDescriptor mInstanceImgFd;
// File descriptor of the image backing the encrypted storage - Will be null if encrypted
@@ -49,9 +54,10 @@
@Override
public void writeToParcel(@NonNull Parcel out, int flags) {
- mConfigFd.writeToParcel(out, flags);
- mInstanceImgFd.writeToParcel(out, flags);
- if (mEncryptedStoreFd != null) mEncryptedStoreFd.writeToParcel(out, flags);
+ checkNotClosed();
+ out.writeParcelable(mConfigFd, flags);
+ out.writeParcelable(mInstanceImgFd, flags);
+ out.writeParcelable(mEncryptedStoreFd, flags);
}
@NonNull
@@ -71,6 +77,7 @@
*/
@NonNull
ParcelFileDescriptor getConfigFd() {
+ checkNotClosed();
return mConfigFd;
}
@@ -79,6 +86,7 @@
*/
@NonNull
ParcelFileDescriptor getInstanceImgFd() {
+ checkNotClosed();
return mInstanceImgFd;
}
@@ -88,6 +96,7 @@
*/
@Nullable
ParcelFileDescriptor getEncryptedStoreFd() {
+ checkNotClosed();
return mEncryptedStoreFd;
}
@@ -95,14 +104,34 @@
@NonNull ParcelFileDescriptor configFd,
@NonNull ParcelFileDescriptor instanceImgFd,
@Nullable ParcelFileDescriptor encryptedStoreFd) {
- mConfigFd = configFd;
- mInstanceImgFd = instanceImgFd;
+ mConfigFd = requireNonNull(configFd);
+ mInstanceImgFd = requireNonNull(instanceImgFd);
mEncryptedStoreFd = encryptedStoreFd;
}
private VirtualMachineDescriptor(Parcel in) {
- mConfigFd = requireNonNull(in.readFileDescriptor());
- mInstanceImgFd = requireNonNull(in.readFileDescriptor());
- mEncryptedStoreFd = in.readFileDescriptor();
+ mConfigFd = requireNonNull(readParcelFileDescriptor(in));
+ mInstanceImgFd = requireNonNull(readParcelFileDescriptor(in));
+ mEncryptedStoreFd = readParcelFileDescriptor(in);
+ }
+
+ private ParcelFileDescriptor readParcelFileDescriptor(Parcel in) {
+ return in.readParcelable(
+ ParcelFileDescriptor.class.getClassLoader(), ParcelFileDescriptor.class);
+ }
+
+ @Override
+ public void close() throws IOException {
+ mClosed = true;
+ // Let the compiler do the work: close everything, throw if any of them fail, skipping null.
+ try (mConfigFd;
+ mInstanceImgFd;
+ mEncryptedStoreFd) {}
+ }
+
+ private void checkNotClosed() {
+ if (mClosed) {
+ throw new IllegalStateException("Descriptor has been closed");
+ }
}
}
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
index 7773cb5..7c9af63 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
@@ -191,7 +191,8 @@
* Imports a virtual machine from an {@link VirtualMachineDescriptor} object and associates it
* with the given name.
*
- * <p>The new virtual machine will be in the same state as the descriptor indicates.
+ * <p>The new virtual machine will be in the same state as the descriptor indicates. The
+ * descriptor is automatically closed and cannot be used again.
*
* <p>NOTE: This method may block and should not be called on the main thread.
*
diff --git a/libs/devicemapper/src/crypt.rs b/libs/devicemapper/src/crypt.rs
index b2e677a..8281b34 100644
--- a/libs/devicemapper/src/crypt.rs
+++ b/libs/devicemapper/src/crypt.rs
@@ -76,7 +76,7 @@
device_path: Option<&'a Path>,
offset: u64,
device_size: u64,
- // TODO(b/238179332) Extend this to include opt_params, in particular 'integrity'
+ opt_params: Vec<&'a str>,
}
impl<'a> Default for DmCryptTargetBuilder<'a> {
@@ -88,6 +88,7 @@
device_path: None,
offset: 0,
device_size: 0,
+ opt_params: Vec::new(),
}
}
}
@@ -124,6 +125,12 @@
self
}
+ /// Add additional optional parameter
+ pub fn opt_param(&mut self, param: &'a str) -> &mut Self {
+ self.opt_params.push(param);
+ self
+ }
+
/// Constructs a `DmCryptTarget`.
pub fn build(&self) -> Result<DmCryptTarget> {
// The `DmCryptTarget` struct actually is a flattened data consisting of a header and
@@ -154,6 +161,7 @@
write!(&mut body, "{} ", self.iv_offset)?;
write!(&mut body, "{} ", device_path)?;
write!(&mut body, "{} ", self.offset)?;
+ write!(&mut body, "{} {} ", self.opt_params.len(), self.opt_params.join(" "))?;
write!(&mut body, "\0")?; // null terminator
let size = size_of::<DmTargetSpec>() + body.len();
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index 9264692..dc59fff 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -85,9 +85,12 @@
"microdroid_property_contexts",
"mke2fs.microdroid",
- // TODO(b/195425111) these should be added automatically
- "libcrypto", // used by many (init_second_stage, microdroid_manager, toybox, etc)
- "liblzma", // used by init_second_stage
+ // Adding more libs manually for unbundled build.
+ // TODO(b/268557568) these should be added automatically.
+ "libcrypto",
+ "liblzma",
+ "libc++",
+ "libssl",
"libvm_payload", // used by payload to interact with microdroid manager
diff --git a/microdroid_manager/src/dice.rs b/microdroid_manager/src/dice.rs
index cf5e73e..9a2648f 100644
--- a/microdroid_manager/src/dice.rs
+++ b/microdroid_manager/src/dice.rs
@@ -17,7 +17,7 @@
use anyhow::{bail, Context, Error, Result};
use byteorder::{NativeEndian, ReadBytesExt};
use diced_open_dice::{
- retry_bcc_main_flow, Config, DiceMode, Hash, Hidden, InputValues, OwnedDiceArtifacts, CDI_SIZE,
+ retry_bcc_main_flow, Cdi, Config, DiceMode, Hash, Hidden, InputValues, OwnedDiceArtifacts,
};
use keystore2_crypto::ZVec;
use libc::{c_void, mmap, munmap, MAP_FAILED, MAP_PRIVATE, PROT_READ};
@@ -29,33 +29,16 @@
use std::ptr::null_mut;
use std::slice;
-/// Artifacts that are kept in the process address space after the artifacts from the driver have
-/// been consumed.
-/// TODO(b/267575445): Replace with `OwnedDiceArtifacts` from the library `diced_open_dice`.
-pub struct DiceContext {
- pub cdi_attest: [u8; CDI_SIZE],
- pub cdi_seal: [u8; CDI_SIZE],
- pub bcc: Vec<u8>,
-}
-
-impl From<OwnedDiceArtifacts> for DiceContext {
- fn from(dice_artifacts: OwnedDiceArtifacts) -> Self {
- Self {
- cdi_attest: dice_artifacts.cdi_values.cdi_attest,
- cdi_seal: dice_artifacts.cdi_values.cdi_seal,
- bcc: dice_artifacts.bcc[..].to_vec(),
- }
- }
-}
-
-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)
- }
+/// Derives a sealing key from the DICE sealing CDI.
+pub fn derive_sealing_key(
+ cdi_seal: &Cdi,
+ salt: &[u8],
+ info: &[u8],
+ keysize: usize,
+) -> Result<ZVec> {
+ let mut key = ZVec::new(keysize)?;
+ hkdf(&mut key, Md::sha256(), cdi_seal, salt, info)?;
+ Ok(key)
}
/// Artifacts that are mapped into the process address space from the driver.
@@ -64,11 +47,11 @@
driver_path: PathBuf,
mmap_addr: *mut c_void,
mmap_size: usize,
- cdi_attest: &'a [u8; CDI_SIZE],
- cdi_seal: &'a [u8; CDI_SIZE],
+ cdi_attest: &'a Cdi,
+ cdi_seal: &'a Cdi,
bcc: &'a [u8],
},
- Fake(DiceContext),
+ Fake(OwnedDiceArtifacts),
}
impl DiceDriver<'_> {
@@ -81,7 +64,7 @@
log::warn!("Using sample DICE values");
let dice_artifacts = diced_sample_inputs::make_sample_bcc_and_cdis()
.expect("Failed to create sample dice artifacts.");
- return Ok(Self::Fake(dice_artifacts.into()));
+ return Ok(Self::Fake(dice_artifacts));
};
let mut file = fs::File::open(driver_path)
@@ -133,12 +116,10 @@
// input key material is already cryptographically strong.
let cdi_seal = match self {
Self::Real { cdi_seal, .. } => cdi_seal,
- Self::Fake(fake) => &fake.cdi_seal,
+ Self::Fake(fake) => &fake.cdi_values.cdi_seal,
};
let salt = &[];
- let mut key = ZVec::new(32)?;
- hkdf(&mut key, Md::sha256(), cdi_seal, salt, identifier)?;
- Ok(key)
+ derive_sealing_key(cdi_seal, salt, identifier, 32)
}
pub fn derive(
@@ -148,7 +129,7 @@
authority_hash: Hash,
debug: bool,
hidden: Hidden,
- ) -> Result<DiceContext> {
+ ) -> Result<OwnedDiceArtifacts> {
let input_values = InputValues::new(
code_hash,
Config::Descriptor(config_desc),
@@ -158,7 +139,9 @@
);
let (cdi_attest, cdi_seal, bcc) = match &self {
Self::Real { cdi_attest, cdi_seal, bcc, .. } => (*cdi_attest, *cdi_seal, *bcc),
- Self::Fake(fake) => (&fake.cdi_attest, &fake.cdi_seal, fake.bcc.as_slice()),
+ Self::Fake(fake) => {
+ (&fake.cdi_values.cdi_attest, &fake.cdi_values.cdi_seal, fake.bcc.as_slice())
+ }
};
let dice_artifacts = retry_bcc_main_flow(cdi_attest, cdi_seal, bcc, &input_values)
.context("DICE derive from driver")?;
@@ -168,7 +151,7 @@
fs::write(driver_path, "wipe")
.map_err(|err| Error::new(err).context("Wiping driver"))?;
}
- Ok(dice_artifacts.into())
+ Ok(dice_artifacts)
}
}
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 777a42c..7ca0d3c 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -21,7 +21,7 @@
mod swap;
mod vm_payload_service;
-use crate::dice::{DiceContext, DiceDriver};
+use crate::dice::{DiceDriver, derive_sealing_key};
use crate::instance::{ApexData, ApkData, InstanceDisk, MicrodroidData, RootHash};
use crate::vm_payload_service::register_vm_payload_service;
use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::ErrorCode::ErrorCode;
@@ -34,6 +34,7 @@
use anyhow::{anyhow, bail, ensure, Context, Error, Result};
use apkverify::{get_public_key_der, verify, V4Signature};
use binder::Strong;
+use diced_open_dice::OwnedDiceArtifacts;
use diced_utils::cbor::{encode_header, encode_number};
use glob::glob;
use itertools::sorted;
@@ -88,7 +89,7 @@
const ENCRYPTEDSTORE_BACKING_DEVICE: &str = "/dev/block/by-name/encryptedstore";
const ENCRYPTEDSTORE_KEY_IDENTIFIER: &str = "encryptedstore_key";
-const ENCRYPTEDSTORE_KEYSIZE: u32 = 32;
+const ENCRYPTEDSTORE_KEYSIZE: usize = 32;
#[derive(thiserror::Error, Debug)]
enum MicrodroidError {
@@ -268,7 +269,7 @@
dice: DiceDriver,
verified_data: &MicrodroidData,
payload_metadata: &PayloadMetadata,
-) -> Result<DiceContext> {
+) -> Result<OwnedDiceArtifacts> {
// Calculate compound digests of code and authorities
let mut code_hash_ctx = Sha512::new();
let mut authority_hash_ctx = Sha512::new();
@@ -413,12 +414,12 @@
// To minimize the exposure to untrusted data, derive dice profile as soon as possible.
info!("DICE derivation for payload");
- let dice_context = dice_derivation(dice, &verified_data, &payload_metadata)?;
+ let dice_artifacts = 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")?)
+ Some(prepare_encryptedstore(&dice_artifacts).context("encryptedstore run")?)
} else {
None
};
@@ -473,7 +474,7 @@
// Wait until zipfuse has mounted the APKs so we can access the payload
zipfuse.wait_until_done()?;
- register_vm_payload_service(allow_restricted_apis, service.clone(), dice_context)?;
+ register_vm_payload_service(allow_restricted_apis, service.clone(), dice_artifacts)?;
// Wait for encryptedstore to finish mounting the storage (if enabled) before setting
// microdroid_manager.init_done. Reason is init stops uneventd after that.
@@ -907,7 +908,7 @@
buf.iter().map(|b| format!("{:02X}", b)).collect()
}
-fn prepare_encryptedstore(dice: &DiceContext) -> Result<Child> {
+fn prepare_encryptedstore(dice_artifacts: &OwnedDiceArtifacts) -> 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
@@ -916,7 +917,8 @@
0x6F, 0xB3, 0xF9, 0x40, 0xCE, 0xDD, 0x99, 0x40, 0xAA, 0xA7, 0x0E, 0x92, 0x73, 0x90, 0x86,
0x4A, 0x75,
];
- let key = dice.get_sealing_key(
+ let key = derive_sealing_key(
+ &dice_artifacts.cdi_values.cdi_seal,
&salt,
ENCRYPTEDSTORE_KEY_IDENTIFIER.as_bytes(),
ENCRYPTEDSTORE_KEYSIZE,
diff --git a/microdroid_manager/src/vm_payload_service.rs b/microdroid_manager/src/vm_payload_service.rs
index 98b9f2b..ac8f60a 100644
--- a/microdroid_manager/src/vm_payload_service.rs
+++ b/microdroid_manager/src/vm_payload_service.rs
@@ -14,12 +14,12 @@
//! Implementation of the AIDL interface `IVmPayloadService`.
-use crate::dice::DiceContext;
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::Result;
use binder::{Interface, BinderFeatures, ExceptionCode, Status, Strong};
+use diced_open_dice::OwnedDiceArtifacts;
use log::{error, info};
use openssl::hkdf::hkdf;
use openssl::md::Md;
@@ -29,7 +29,7 @@
struct VmPayloadService {
allow_restricted_apis: bool,
virtual_machine_service: Strong<dyn IVirtualMachineService>,
- dice: DiceContext,
+ dice: OwnedDiceArtifacts,
}
impl IVmPayloadService for VmPayloadService {
@@ -48,10 +48,11 @@
0xB7, 0xA8, 0x43, 0x92,
];
let mut secret = vec![0; size.try_into().unwrap()];
- hkdf(&mut secret, Md::sha256(), &self.dice.cdi_seal, &salt, identifier).map_err(|e| {
- error!("Failed to derive VM instance secret: {:?}", e);
- Status::new_service_specific_error(-1, None)
- })?;
+ hkdf(&mut secret, Md::sha256(), &self.dice.cdi_values.cdi_seal, &salt, identifier)
+ .map_err(|e| {
+ error!("Failed to derive VM instance secret: {:?}", e);
+ Status::new_service_specific_error(-1, None)
+ })?;
Ok(secret)
}
@@ -62,7 +63,7 @@
fn getDiceAttestationCdi(&self) -> binder::Result<Vec<u8>> {
self.check_restricted_apis_allowed()?;
- Ok(self.dice.cdi_attest.to_vec())
+ Ok(self.dice.cdi_values.cdi_attest.to_vec())
}
}
@@ -73,7 +74,7 @@
fn new(
allow_restricted_apis: bool,
vm_service: Strong<dyn IVirtualMachineService>,
- dice: DiceContext,
+ dice: OwnedDiceArtifacts,
) -> Self {
Self { allow_restricted_apis, virtual_machine_service: vm_service, dice }
}
@@ -92,7 +93,7 @@
pub(crate) fn register_vm_payload_service(
allow_restricted_apis: bool,
vm_service: Strong<dyn IVirtualMachineService>,
- dice: DiceContext,
+ dice: OwnedDiceArtifacts,
) -> Result<()> {
let vm_payload_binder = BnVmPayloadService::new_binder(
VmPayloadService::new(allow_restricted_apis, vm_service, dice),
diff --git a/tests/testapk/Android.bp b/tests/testapk/Android.bp
index 3c487ee..bafab53 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -8,6 +8,10 @@
"cts",
"general-tests",
],
+ static_libs: [
+ "com.android.microdroid.testservice-java",
+ "com.android.microdroid.test.vmshare_service-java",
+ ],
sdk_version: "test_current",
jni_uses_platform_apis: true,
use_embedded_native_libs: true,
@@ -25,7 +29,6 @@
"androidx.test.ext.junit",
"authfs_test_apk_assets",
"cbor-java",
- "com.android.microdroid.testservice-java",
"truth-prebuilt",
"compatibility-common-util-devicesidelib",
],
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 4a76ead..9b29fa3 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -18,23 +18,29 @@
import static android.system.virtualmachine.VirtualMachine.STATUS_DELETED;
import static android.system.virtualmachine.VirtualMachine.STATUS_RUNNING;
import static android.system.virtualmachine.VirtualMachine.STATUS_STOPPED;
-import static android.system.virtualmachine.VirtualMachineConfig.CPU_TOPOLOGY_ONE_CPU;
import static android.system.virtualmachine.VirtualMachineConfig.CPU_TOPOLOGY_MATCH_HOST;
+import static android.system.virtualmachine.VirtualMachineConfig.CPU_TOPOLOGY_ONE_CPU;
import static android.system.virtualmachine.VirtualMachineConfig.DEBUG_LEVEL_FULL;
import static android.system.virtualmachine.VirtualMachineConfig.DEBUG_LEVEL_NONE;
import static android.system.virtualmachine.VirtualMachineManager.CAPABILITY_NON_PROTECTED_VM;
import static android.system.virtualmachine.VirtualMachineManager.CAPABILITY_PROTECTED_VM;
-
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.common.truth.TruthJUnit.assume;
-
+import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
import static org.junit.Assert.assertThrows;
-import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
+import com.google.common.base.Strings;
+import com.google.common.truth.BooleanSubject;
+import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.ServiceConnection;
import android.os.Build;
+import android.os.IBinder;
+import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.ParcelFileDescriptor.AutoCloseInputStream;
import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
@@ -47,15 +53,11 @@
import android.system.virtualmachine.VirtualMachineManager;
import android.util.Log;
-import androidx.test.core.app.ApplicationProvider;
-
import com.android.compatibility.common.util.CddTest;
import com.android.microdroid.test.device.MicrodroidDeviceTestBase;
+import com.android.microdroid.test.vmshare.IVmShareTestService;
import com.android.microdroid.testservice.ITestService;
-import com.google.common.base.Strings;
-import com.google.common.truth.BooleanSubject;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
@@ -87,6 +89,8 @@
import java.util.OptionalLong;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import co.nstant.in.cbor.CborDecoder;
@@ -152,7 +156,7 @@
tr.mApkContentsPath = ts.getApkContentsPath();
tr.mEncryptedStoragePath = ts.getEncryptedStoragePath();
});
- assertThat(testResults.mException).isNull();
+ testResults.assertNoException();
assertThat(testResults.mAddInteger).isEqualTo(123 + 456);
assertThat(testResults.mAppRunProp).isEqualTo("true");
assertThat(testResults.mSublibRunProp).isEqualTo("true");
@@ -177,12 +181,8 @@
VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
TestResults testResults =
- runVmTestService(
- vm,
- (ts, tr) -> {
- tr.mAddInteger = ts.addInteger(37, 73);
- });
- assertThat(testResults.mException).isNull();
+ runVmTestService(vm, (ts, tr) -> tr.mAddInteger = ts.addInteger(37, 73));
+ testResults.assertNoException();
assertThat(testResults.mAddInteger).isEqualTo(37 + 73);
}
@@ -213,7 +213,7 @@
}
@Test
- @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+ @CddTest(requirements = {"9.17/C-1-1"})
public void autoCloseVm() throws Exception {
assumeSupportedKernel();
@@ -244,7 +244,61 @@
}
@Test
- @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+ @CddTest(requirements = {"9.17/C-1-1"})
+ public void autoCloseVmDescriptor() throws Exception {
+ VirtualMachineConfig config =
+ newVmConfigBuilder()
+ .setPayloadBinaryName("MicrodroidTestNativeLib.so")
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .build();
+ VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
+ VirtualMachineDescriptor descriptor = vm.toDescriptor();
+
+ Parcel parcel = Parcel.obtain();
+ try (descriptor) {
+ // It should be ok to use at this point
+ descriptor.writeToParcel(parcel, 0);
+ }
+
+ // But not now - it's been closed.
+ assertThrows(IllegalStateException.class, () -> descriptor.writeToParcel(parcel, 0));
+ assertThrows(
+ IllegalStateException.class,
+ () -> getVirtualMachineManager().importFromDescriptor("imported_vm", descriptor));
+
+ // Closing again is fine.
+ descriptor.close();
+
+ // Tidy up
+ parcel.recycle();
+ }
+
+ @Test
+ @CddTest(requirements = {"9.17/C-1-1"})
+ public void vmDescriptorClosedOnImport() throws Exception {
+ VirtualMachineConfig config =
+ newVmConfigBuilder()
+ .setPayloadBinaryName("MicrodroidTestNativeLib.so")
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .build();
+ VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
+ VirtualMachineDescriptor descriptor = vm.toDescriptor();
+
+ getVirtualMachineManager().importFromDescriptor("imported_vm", descriptor);
+ try {
+ // Descriptor has been implicitly closed
+ assertThrows(
+ IllegalStateException.class,
+ () ->
+ getVirtualMachineManager()
+ .importFromDescriptor("imported_vm2", descriptor));
+ } finally {
+ getVirtualMachineManager().delete("imported_vm");
+ }
+ }
+
+ @Test
+ @CddTest(requirements = {"9.17/C-1-1"})
public void vmLifecycleChecks() throws Exception {
assumeSupportedKernel();
@@ -305,19 +359,14 @@
.build();
VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_vsock", config);
- AtomicReference<Exception> exception = new AtomicReference<>();
AtomicReference<String> response = new AtomicReference<>();
String request = "Look not into the abyss";
- VmEventListener listener =
- new VmEventListener() {
- @Override
- public void onPayloadReady(VirtualMachine vm) {
- try (vm) {
- ITestService testService =
- ITestService.Stub.asInterface(
- vm.connectToVsockServer(ITestService.SERVICE_PORT));
- testService.runEchoReverseServer();
+ TestResults testResults =
+ runVmTestService(
+ vm,
+ (service, results) -> {
+ service.runEchoReverseServer();
ParcelFileDescriptor pfd =
vm.connectVsock(ITestService.ECHO_REVERSE_PORT);
@@ -330,15 +379,8 @@
writer.flush();
response.set(reader.readLine());
}
- } catch (Exception e) {
- exception.set(e);
- }
- }
- };
- listener.runToFinish(TAG, vm);
- if (exception.get() != null) {
- throw new RuntimeException(exception.get());
- }
+ });
+ testResults.assertNoException();
assertThat(response.get()).isEqualTo(new StringBuilder(request).reverse().toString());
}
@@ -362,7 +404,6 @@
// Maximal has everything that can be set to some non-default value. (And has different
// values than minimal for the required fields.)
- int maxCpus = Runtime.getRuntime().availableProcessors();
VirtualMachineConfig.Builder maximalBuilder =
new VirtualMachineConfig.Builder(getContext())
.setProtectedVm(mProtectedVm)
@@ -724,7 +765,7 @@
(ts, tr) -> {
tr.mApkContentsPath = ts.getApkContentsPath();
});
- assertThat(testResults.mException).isNull();
+ testResults.assertNoException();
assertThat(testResults.mApkContentsPath).isEqualTo("/mnt/apk");
}
@@ -847,15 +888,16 @@
private VmCdis launchVmAndGetCdis(String instanceName) throws Exception {
VirtualMachine vm = getVirtualMachineManager().get(instanceName);
- final VmCdis vmCdis = new VmCdis();
- final CompletableFuture<Exception> exception = new CompletableFuture<>();
+ VmCdis vmCdis = new VmCdis();
+ CompletableFuture<Exception> exception = new CompletableFuture<>();
VmEventListener listener =
new VmEventListener() {
@Override
public void onPayloadReady(VirtualMachine vm) {
try {
- ITestService testService = ITestService.Stub.asInterface(
- vm.connectToVsockServer(ITestService.SERVICE_PORT));
+ ITestService testService =
+ ITestService.Stub.asInterface(
+ vm.connectToVsockServer(ITestService.SERVICE_PORT));
vmCdis.cdiAttest = testService.insecurelyExposeAttestationCdi();
vmCdis.instanceSecret = testService.insecurelyExposeVmInstanceSecret();
} catch (Exception e) {
@@ -939,26 +981,14 @@
.setDebugLevel(DEBUG_LEVEL_FULL)
.build();
VirtualMachine vm = forceCreateNewVirtualMachine("bcc_vm", normalConfig);
- final CompletableFuture<byte[]> bcc = new CompletableFuture<>();
- final CompletableFuture<Exception> exception = new CompletableFuture<>();
- VmEventListener listener =
- new VmEventListener() {
- @Override
- public void onPayloadReady(VirtualMachine vm) {
- try {
- ITestService testService = ITestService.Stub.asInterface(
- vm.connectToVsockServer(ITestService.SERVICE_PORT));
- bcc.complete(testService.getBcc());
- } catch (Exception e) {
- exception.complete(e);
- } finally {
- forceStop(vm);
- }
- }
- };
- listener.runToFinish(TAG, vm);
- byte[] bccBytes = bcc.getNow(null);
- assertThat(exception.getNow(null)).isNull();
+ TestResults testResults =
+ runVmTestService(
+ vm,
+ (service, results) -> {
+ results.mBcc = service.getBcc();
+ });
+ testResults.assertNoException();
+ byte[] bccBytes = testResults.mBcc;
assertThat(bccBytes).isNotNull();
ByteArrayInputStream bais = new ByteArrayInputStream(bccBytes);
@@ -995,10 +1025,6 @@
private static final UUID MICRODROID_PARTITION_UUID =
UUID.fromString("cf9afe9a-0662-11ec-a329-c32663a09d75");
- private static final UUID U_BOOT_AVB_PARTITION_UUID =
- UUID.fromString("7e8221e7-03e6-4969-948b-73a4c809a4f2");
- private static final UUID U_BOOT_ENV_PARTITION_UUID =
- UUID.fromString("0ab72d30-86ae-4d05-81b2-c1760be2b1f9");
private static final UUID PVM_FW_PARTITION_UUID =
UUID.fromString("90d2174a-038a-4bc6-adf3-824848fc5825");
private static final long BLOCK_SIZE = 512;
@@ -1197,7 +1223,6 @@
VirtualMachine vmOrig = forceCreateNewVirtualMachine(vmNameOrig, config);
VmCdis origCdis = launchVmAndGetCdis(vmNameOrig);
assertThat(origCdis.instanceSecret).isNotNull();
- VirtualMachineDescriptor descriptor = vmOrig.toDescriptor();
VirtualMachineManager vmm = getVirtualMachineManager();
if (vmm.get(vmNameImport) != null) {
vmm.delete(vmNameImport);
@@ -1205,7 +1230,7 @@
// Action
// The imported VM will be fetched by name later.
- VirtualMachine unusedVmImport = vmm.importFromDescriptor(vmNameImport, descriptor);
+ vmm.importFromDescriptor(vmNameImport, vmOrig.toDescriptor());
// Asserts
VmCdis importCdis = launchVmAndGetCdis(vmNameImport);
@@ -1213,14 +1238,14 @@
}
@Test
- @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+ @CddTest(requirements = {"9.17/C-1-1"})
public void importedVmIsEqualToTheOriginalVm_WithoutStorage() throws Exception {
TestResults testResults = importedVmIsEqualToTheOriginalVm(false);
assertThat(testResults.mEncryptedStoragePath).isEqualTo("");
}
@Test
- @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+ @CddTest(requirements = {"9.17/C-1-1"})
public void importedVmIsEqualToTheOriginalVm_WithStorage() throws Exception {
TestResults testResults = importedVmIsEqualToTheOriginalVm(true);
assertThat(testResults.mEncryptedStoragePath).isEqualTo("/mnt/encryptedstore");
@@ -1248,16 +1273,15 @@
tr.mAddInteger = ts.addInteger(123, 456);
tr.mEncryptedStoragePath = ts.getEncryptedStoragePath();
});
- assertThat(origTestResults.mException).isNull();
+ origTestResults.assertNoException();
assertThat(origTestResults.mAddInteger).isEqualTo(123 + 456);
- VirtualMachineDescriptor descriptor = vmOrig.toDescriptor();
VirtualMachineManager vmm = getVirtualMachineManager();
if (vmm.get(vmNameImport) != null) {
vmm.delete(vmNameImport);
}
// Action
- VirtualMachine vmImport = vmm.importFromDescriptor(vmNameImport, descriptor);
+ VirtualMachine vmImport = vmm.importFromDescriptor(vmNameImport, vmOrig.toDescriptor());
// Asserts
assertFileContentsAreEqualInTwoVms("config.xml", vmNameOrig, vmNameImport);
@@ -1275,13 +1299,13 @@
tr.mAddInteger = ts.addInteger(123, 456);
tr.mEncryptedStoragePath = ts.getEncryptedStoragePath();
});
- assertThat(testResults.mException).isNull();
+ testResults.assertNoException();
assertThat(testResults.mAddInteger).isEqualTo(123 + 456);
return testResults;
}
@Test
- @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+ @CddTest(requirements = {"9.17/C-1-1"})
public void encryptedStorageAvailable() throws Exception {
assumeSupportedKernel();
@@ -1304,7 +1328,7 @@
}
@Test
- @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+ @CddTest(requirements = {"9.17/C-1-1"})
public void encryptedStorageIsInaccessibleToDifferentVm() throws Exception {
assumeSupportedKernel();
@@ -1314,7 +1338,6 @@
.setMemoryBytes(minMemoryRequired())
.setEncryptedStorageBytes(4_000_000)
.setDebugLevel(DEBUG_LEVEL_FULL)
- .setVmOutputCaptured(true)
.build();
VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
@@ -1327,7 +1350,7 @@
/* content= */ EXAMPLE_STRING,
/* path= */ "/mnt/encryptedstore/test_file");
});
- assertThat(testResults.mException).isNull();
+ testResults.assertNoException();
// Start a different vm (this changes the vm identity)
VirtualMachine diff_test_vm = forceCreateNewVirtualMachine("diff_test_vm", config);
@@ -1339,7 +1362,8 @@
assertFileContentsAreEqualInTwoVms("storage.img", "test_vm", "diff_test_vm");
CompletableFuture<Boolean> onPayloadReadyExecuted = new CompletableFuture<>();
- CompletableFuture<Boolean> onStoppedExecuted = new CompletableFuture<>();
+ CompletableFuture<Boolean> onErrorExecuted = new CompletableFuture<>();
+ CompletableFuture<String> errorMessage = new CompletableFuture<>();
VmEventListener listener =
new VmEventListener() {
@Override
@@ -1349,17 +1373,18 @@
}
@Override
- public void onStopped(VirtualMachine vm, int reason) {
- onStoppedExecuted.complete(true);
- super.onStopped(vm, reason);
+ public void onError(VirtualMachine vm, int errorCode, String message) {
+ onErrorExecuted.complete(true);
+ errorMessage.complete(message);
+ super.onError(vm, errorCode, message);
}
};
listener.runToFinish(TAG, diff_test_vm);
- // Assert that payload never started & logs contains encryptedstore initialization error
- assertThat(onStoppedExecuted.getNow(false)).isTrue();
+ // Assert that payload never started & error message reflects storage error.
assertThat(onPayloadReadyExecuted.getNow(false)).isFalse();
- assertThat(listener.getConsoleOutput()).contains("Unable to initialize encryptedstore");
+ assertThat(onErrorExecuted.getNow(false)).isTrue();
+ assertThat(errorMessage.getNow("")).contains("Unable to prepare encrypted storage");
}
@Test
@@ -1382,12 +1407,12 @@
tr.mEffectiveCapabilities = ts.getEffectiveCapabilities();
});
- assertThat(testResults.mException).isNull();
+ testResults.assertNoException();
assertThat(testResults.mEffectiveCapabilities).isEmpty();
}
@Test
- @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+ @CddTest(requirements = {"9.17/C-1-1"})
public void encryptedStorageIsPersistent() throws Exception {
assumeSupportedKernel();
@@ -1407,7 +1432,7 @@
/* content= */ EXAMPLE_STRING,
/* path= */ "/mnt/encryptedstore/test_file");
});
- assertThat(testResults.mException).isNull();
+ testResults.assertNoException();
// Re-run the same VM & verify the file persisted. Note, the previous `runVmTestService`
// stopped the VM
@@ -1417,7 +1442,7 @@
(ts, tr) -> {
tr.mFileContent = ts.readFromFile("/mnt/encryptedstore/test_file");
});
- assertThat(testResults.mException).isNull();
+ testResults.assertNoException();
assertThat(testResults.mFileContent).isEqualTo(EXAMPLE_STRING);
}
@@ -1441,7 +1466,7 @@
ts.mFileContent = testService.readFromFile("/mnt/apk/assets/file.txt");
});
- assertThat(testResults.mException).isNull();
+ testResults.assertNoException();
assertThat(testResults.mFileContent).isEqualTo("Hello, I am a file!");
}
@@ -1450,7 +1475,7 @@
assumeSupportedKernel();
final VirtualMachineConfig vmConfig =
- new VirtualMachineConfig.Builder(ApplicationProvider.getApplicationContext())
+ new VirtualMachineConfig.Builder(getContext())
.setProtectedVm(mProtectedVm)
.setPayloadBinaryName("MicrodroidTestNativeLib.so")
.setDebugLevel(DEBUG_LEVEL_FULL)
@@ -1472,7 +1497,7 @@
String time =
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"));
final VirtualMachineConfig vmConfig =
- new VirtualMachineConfig.Builder(ApplicationProvider.getApplicationContext())
+ new VirtualMachineConfig.Builder(getContext())
.setProtectedVm(mProtectedVm)
.setPayloadBinaryName("MicrodroidTestNativeLib.so")
.setDebugLevel(debuggable ? DEBUG_LEVEL_FULL : DEBUG_LEVEL_NONE)
@@ -1480,14 +1505,7 @@
.build();
final VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_logcat", vmConfig);
- VmEventListener listener =
- new VmEventListener() {
- @Override
- public void onPayloadStarted(VirtualMachine vm) {
- forceStop(vm);
- }
- };
- listener.runToFinish(TAG, vm);
+ runVmTestService(vm, (service, results) -> {});
// only check logs printed after this test
Process logcatProcess =
@@ -1546,6 +1564,221 @@
getVirtualMachineManager().delete("vm_from_another_app");
}
+ @Test
+ public void testVmDescriptorParcelUnparcel_noTrustedStorage() throws Exception {
+ assumeSupportedKernel();
+
+ VirtualMachineConfig config =
+ newVmConfigBuilder()
+ .setPayloadBinaryName("MicrodroidTestNativeLib.so")
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .build();
+
+ VirtualMachine originalVm = forceCreateNewVirtualMachine("original_vm", config);
+ // Just start & stop the VM.
+ runVmTestService(originalVm, (ts, tr) -> {});
+
+ // Now create the descriptor and manually parcel & unparcel it.
+ VirtualMachineDescriptor vmDescriptor = toParcelFromParcel(originalVm.toDescriptor());
+
+ if (getVirtualMachineManager().get("import_vm_from_unparceled") != null) {
+ getVirtualMachineManager().delete("import_vm_from_unparceled");
+ }
+
+ VirtualMachine importVm =
+ getVirtualMachineManager()
+ .importFromDescriptor("import_vm_from_unparceled", vmDescriptor);
+
+ assertFileContentsAreEqualInTwoVms(
+ "config.xml", "original_vm", "import_vm_from_unparceled");
+ assertFileContentsAreEqualInTwoVms(
+ "instance.img", "original_vm", "import_vm_from_unparceled");
+
+ // Check that we can start and stop imported vm as well
+ runVmTestService(importVm, (ts, tr) -> {});
+ }
+
+ @Test
+ public void testVmDescriptorParcelUnparcel_withTrustedStorage() throws Exception {
+ assumeSupportedKernel();
+
+ VirtualMachineConfig config =
+ newVmConfigBuilder()
+ .setPayloadBinaryName("MicrodroidTestNativeLib.so")
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .setEncryptedStorageBytes(1_000_000)
+ .build();
+
+ VirtualMachine originalVm = forceCreateNewVirtualMachine("original_vm", config);
+ // Just start & stop the VM.
+ {
+ TestResults testResults =
+ runVmTestService(
+ originalVm,
+ (ts, tr) -> {
+ ts.writeToFile("not a secret!", "/mnt/encryptedstore/secret.txt");
+ });
+ assertThat(testResults.mException).isNull();
+ }
+
+ // Now create the descriptor and manually parcel & unparcel it.
+ VirtualMachineDescriptor vmDescriptor = toParcelFromParcel(originalVm.toDescriptor());
+
+ if (getVirtualMachineManager().get("import_vm_from_unparceled") != null) {
+ getVirtualMachineManager().delete("import_vm_from_unparceled");
+ }
+
+ VirtualMachine importVm =
+ getVirtualMachineManager()
+ .importFromDescriptor("import_vm_from_unparceled", vmDescriptor);
+
+ assertFileContentsAreEqualInTwoVms(
+ "config.xml", "original_vm", "import_vm_from_unparceled");
+ assertFileContentsAreEqualInTwoVms(
+ "instance.img", "original_vm", "import_vm_from_unparceled");
+ assertFileContentsAreEqualInTwoVms(
+ "storage.img", "original_vm", "import_vm_from_unparceled");
+
+ TestResults testResults =
+ runVmTestService(
+ importVm,
+ (ts, tr) -> {
+ tr.mFileContent = ts.readFromFile("/mnt/encryptedstore/secret.txt");
+ });
+
+ assertThat(testResults.mException).isNull();
+ assertThat(testResults.mFileContent).isEqualTo("not a secret!");
+ }
+
+ @Test
+ public void testShareVmWithAnotherApp() throws Exception {
+ assumeSupportedKernel();
+
+ Context ctx = getContext();
+ Context otherAppCtx = ctx.createPackageContext(VM_SHARE_APP_PACKAGE_NAME, 0);
+
+ VirtualMachineConfig config =
+ new VirtualMachineConfig.Builder(otherAppCtx)
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .setProtectedVm(isProtectedVm())
+ .setPayloadBinaryName("MicrodroidPayloadInOtherAppNativeLib.so")
+ .build();
+
+ VirtualMachine vm = forceCreateNewVirtualMachine("vm_to_share", config);
+ // Just start & stop the VM.
+ runVmTestService(vm, (ts, tr) -> {});
+ // Get a descriptor that we will share with another app (VM_SHARE_APP_PACKAGE_NAME)
+ VirtualMachineDescriptor vmDesc = vm.toDescriptor();
+
+ Intent serviceIntent = new Intent();
+ serviceIntent.setComponent(
+ new ComponentName(
+ VM_SHARE_APP_PACKAGE_NAME,
+ "com.android.microdroid.test.sharevm.VmShareServiceImpl"));
+
+ VmShareServiceConnection connection = new VmShareServiceConnection();
+ boolean ret = ctx.bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE);
+ assertWithMessage("Failed to bind to " + serviceIntent).that(ret).isTrue();
+
+ IVmShareTestService service = connection.waitForService();
+ assertWithMessage("Timed out connecting to " + serviceIntent).that(service).isNotNull();
+
+ try {
+ // Send the VM descriptor to the other app. When received, it will reconstruct the VM
+ // from the descriptor, start it, connect to the ITestService in it, creates a "proxy"
+ // ITestService binder that delegates all the calls to the VM, and share it with this
+ // app. It will allow us to verify assertions on the running VM in the other app.
+ ITestService testServiceProxy = service.startVm(vmDesc);
+
+ int result = testServiceProxy.addInteger(37, 73);
+ assertThat(result).isEqualTo(110);
+ } finally {
+ ctx.unbindService(connection);
+ }
+ }
+
+ @Test
+ public void testShareVmWithAnotherApp_encryptedStorage() throws Exception {
+ assumeSupportedKernel();
+
+ Context ctx = getContext();
+ Context otherAppCtx = ctx.createPackageContext(VM_SHARE_APP_PACKAGE_NAME, 0);
+
+ VirtualMachineConfig config =
+ new VirtualMachineConfig.Builder(otherAppCtx)
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .setProtectedVm(isProtectedVm())
+ .setEncryptedStorageBytes(3_000_000)
+ .setPayloadBinaryName("MicrodroidPayloadInOtherAppNativeLib.so")
+ .build();
+
+ VirtualMachine vm = forceCreateNewVirtualMachine("vm_to_share", config);
+ // Just start & stop the VM.
+ runVmTestService(
+ vm,
+ (ts, tr) -> {
+ ts.writeToFile(EXAMPLE_STRING, "/mnt/encryptedstore/private.key");
+ });
+ // Get a descriptor that we will share with another app (VM_SHARE_APP_PACKAGE_NAME)
+ VirtualMachineDescriptor vmDesc = vm.toDescriptor();
+
+ Intent serviceIntent = new Intent();
+ serviceIntent.setComponent(
+ new ComponentName(
+ VM_SHARE_APP_PACKAGE_NAME,
+ "com.android.microdroid.test.sharevm.VmShareServiceImpl"));
+
+ VmShareServiceConnection connection = new VmShareServiceConnection();
+ boolean ret = ctx.bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE);
+ assertWithMessage("Failed to bind to " + serviceIntent).that(ret).isTrue();
+
+ IVmShareTestService service = connection.waitForService();
+ assertWithMessage("Timed out connecting to " + serviceIntent).that(service).isNotNull();
+
+ try {
+ // Send the VM descriptor to the other app. When received, it will reconstruct the VM
+ // from the descriptor, start it, connect to the ITestService in it, creates a "proxy"
+ // ITestService binder that delegates all the calls to the VM, and share it with this
+ // app. It will allow us to verify assertions on the running VM in the other app.
+ ITestService testServiceProxy = service.startVm(vmDesc);
+
+ String result = testServiceProxy.readFromFile("/mnt/encryptedstore/private.key");
+ assertThat(result).isEqualTo(EXAMPLE_STRING);
+ } finally {
+ ctx.unbindService(connection);
+ }
+ }
+
+ private static class VmShareServiceConnection implements ServiceConnection {
+
+ private final CountDownLatch mLatch = new CountDownLatch(1);
+
+ private IVmShareTestService mVmShareTestService;
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mVmShareTestService = IVmShareTestService.Stub.asInterface(service);
+ mLatch.countDown();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {}
+
+ private IVmShareTestService waitForService() throws Exception {
+ if (!mLatch.await(1, TimeUnit.MINUTES)) {
+ return null;
+ }
+ return mVmShareTestService;
+ }
+ }
+
+ private VirtualMachineDescriptor toParcelFromParcel(VirtualMachineDescriptor descriptor) {
+ Parcel parcel = Parcel.obtain();
+ descriptor.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ return VirtualMachineDescriptor.CREATOR.createFromParcel(parcel);
+ }
+
private void assertFileContentsAreEqualInTwoVms(String fileName, String vmName1, String vmName2)
throws IOException {
File file1 = getVmFile(vmName1, fileName);
@@ -1557,7 +1790,7 @@
}
private File getVmFile(String vmName, String fileName) {
- Context context = ApplicationProvider.getApplicationContext();
+ Context context = getContext();
Path filePath = Paths.get(context.getDataDir().getPath(), "vm", vmName, fileName);
return filePath.toFile();
}
@@ -1593,6 +1826,7 @@
}
static class TestResults {
+
Exception mException;
Integer mAddInteger;
String mAppRunProp;
@@ -1602,6 +1836,15 @@
String mEncryptedStoragePath;
String[] mEffectiveCapabilities;
String mFileContent;
+ byte[] mBcc;
+
+ void assertNoException() {
+ if (mException != null) {
+ // Rethrow, wrapped in a new exception, so we get stack traces of the original
+ // failure as well as the body of the test.
+ throw new RuntimeException(mException);
+ }
+ }
}
private TestResults runVmTestService(VirtualMachine vm, RunTestsAgainstTestService testsToRun)
@@ -1638,7 +1881,7 @@
}
}
- private void quitVMService(VirtualMachine vm) {
+ private void quitVMService() {
try {
mTestService.quit();
} catch (Exception e) {
@@ -1651,7 +1894,7 @@
Log.i(TAG, "onPayloadReady");
payloadReady.complete(true);
testVMService(vm);
- quitVMService(vm);
+ quitVMService();
}
@Override
diff --git a/tests/vmshareapp/Android.bp b/tests/vmshareapp/Android.bp
index 2b117a1..6c2c9e4 100644
--- a/tests/vmshareapp/Android.bp
+++ b/tests/vmshareapp/Android.bp
@@ -5,6 +5,7 @@
// Helper app to verify that we can create a VM using others app payload, and share VMs between apps
android_test_helper_app {
name: "MicrodroidVmShareApp",
+ srcs: ["src/java/**/*.java"],
// Defaults are defined in ../testapk/Android.bp
defaults: ["MicrodroidTestAppsDefaults"],
jni_libs: [
diff --git a/tests/vmshareapp/AndroidManifest.xml b/tests/vmshareapp/AndroidManifest.xml
index eed3364..b623f7f 100644
--- a/tests/vmshareapp/AndroidManifest.xml
+++ b/tests/vmshareapp/AndroidManifest.xml
@@ -20,5 +20,13 @@
<uses-feature android:name="android.software.virtualization_framework"
android:required="false" />
- <application />
+ <application>
+ <service android:name="com.android.microdroid.test.sharevm.VmShareServiceImpl"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="com.android.microdroid.test.sharevm.VmShareService"/>
+ </intent-filter>
+ </service>
+ </application>
+
</manifest>
diff --git a/tests/vmshareapp/aidl/Android.bp b/tests/vmshareapp/aidl/Android.bp
new file mode 100644
index 0000000..df4a4b4
--- /dev/null
+++ b/tests/vmshareapp/aidl/Android.bp
@@ -0,0 +1,15 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+// Unfortunatelly aidl_interface doesn't work well with .aidl files that depend on java-only
+// parcelables (e.g. Bundle, VirtualMachineDescriptor), hence this java_library.
+java_library {
+ name: "com.android.microdroid.test.vmshare_service-java",
+ srcs: ["com/**/*.aidl"],
+ sdk_version: "test_current",
+ static_libs: ["com.android.microdroid.testservice-java"],
+ aidl: {
+ include_dirs: ["packages/modules/Virtualization/tests/aidl/"],
+ },
+}
diff --git a/tests/vmshareapp/aidl/com/android/microdroid/test/vmshare/IVmShareTestService.aidl b/tests/vmshareapp/aidl/com/android/microdroid/test/vmshare/IVmShareTestService.aidl
new file mode 100644
index 0000000..fe6ca43
--- /dev/null
+++ b/tests/vmshareapp/aidl/com/android/microdroid/test/vmshare/IVmShareTestService.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2023 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.vmshare;
+
+import android.system.virtualmachine.VirtualMachineDescriptor;
+import com.android.microdroid.testservice.ITestService;
+
+/** {@hide} */
+interface IVmShareTestService {
+ ITestService startVm(in VirtualMachineDescriptor vmDesc);
+}
diff --git a/tests/vmshareapp/src/java/com/android/microdroid/test/sharevm/VmShareServiceImpl.java b/tests/vmshareapp/src/java/com/android/microdroid/test/sharevm/VmShareServiceImpl.java
new file mode 100644
index 0000000..278e1a2
--- /dev/null
+++ b/tests/vmshareapp/src/java/com/android/microdroid/test/sharevm/VmShareServiceImpl.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2023 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.sharevm;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.system.virtualmachine.VirtualMachine;
+import android.system.virtualmachine.VirtualMachineCallback;
+import android.system.virtualmachine.VirtualMachineDescriptor;
+import android.system.virtualmachine.VirtualMachineException;
+import android.system.virtualmachine.VirtualMachineManager;
+import android.util.Log;
+
+import com.android.microdroid.test.vmshare.IVmShareTestService;
+import com.android.microdroid.testservice.ITestService;
+
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * A {@link Service} that is used in end-to-end tests of the {@link VirtualMachine} sharing
+ * functionality.
+ *
+ * <p>During the test {@link com.android.microdroid.test.MicrodroidTests} will bind to this service,
+ * and call {@link #startVm(VirtualMachineDescriptor)} to share the VM. This service then will
+ * create a {@link VirtualMachine} from that descriptor, {@link VirtualMachine#run() run} it, and
+ * send back {@link RemoteTestServiceDelegate}. The {@code MicrodroidTests} can use that {@link
+ * RemoteTestServiceDelegate} to assert conditions on the VM running in the {@link
+ * VmShareServiceImpl}.
+ *
+ * <p>The {@link VirtualMachine} running in this service will be stopped on {@link
+ * #onUnbind(Intent)}.
+ *
+ * @see com.android.microdroid.test.MicrodroidTests#testShareVmWithAnotherApp
+ */
+public class VmShareServiceImpl extends Service {
+
+ private static final String TAG = "VmShareApp";
+
+ private IVmShareTestService.Stub mBinder;
+
+ private VirtualMachine mVirtualMachine;
+
+ @Override
+ public void onCreate() {
+ mBinder = new ServiceImpl();
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ Log.i(TAG, "onBind " + intent + " binder = " + mBinder);
+ return mBinder;
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ deleteVm();
+ // Tell framework that it shouldn't call onRebind.
+ return false;
+ }
+
+ private void deleteVm() {
+ if (mVirtualMachine == null) {
+ return;
+ }
+ try {
+ mVirtualMachine.stop();
+ String name = mVirtualMachine.getName();
+ VirtualMachineManager vmm = getSystemService(VirtualMachineManager.class);
+ vmm.delete(name);
+ mVirtualMachine = null;
+ } catch (VirtualMachineException e) {
+ Log.e(TAG, "Failed to stop " + mVirtualMachine, e);
+ }
+ }
+
+ public ITestService startVm(VirtualMachineDescriptor vmDesc) throws Exception {
+ // Cleanup VM left from the previous test.
+ deleteVm();
+
+ VirtualMachineManager vmm = getSystemService(VirtualMachineManager.class);
+
+ // Add random uuid to make sure that different tests that bind to this service don't trip
+ // over each other.
+ String vmName = "imported_vm" + UUID.randomUUID();
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ VirtualMachineCallback callback =
+ new VirtualMachineCallback() {
+
+ @Override
+ public void onPayloadStarted(VirtualMachine vm) {
+ // Ignored
+ }
+
+ @Override
+ public void onPayloadReady(VirtualMachine vm) {
+ latch.countDown();
+ }
+
+ @Override
+ public void onPayloadFinished(VirtualMachine vm, int exitCode) {
+ // Ignored
+ }
+
+ @Override
+ public void onError(VirtualMachine vm, int errorCode, String message) {
+ throw new RuntimeException(
+ "VM failed with error " + errorCode + " : " + message);
+ }
+
+ @Override
+ public void onStopped(VirtualMachine vm, int reason) {
+ // Ignored
+ }
+ };
+
+ mVirtualMachine = vmm.importFromDescriptor(vmName, vmDesc);
+ mVirtualMachine.setCallback(getMainExecutor(), callback);
+
+ Log.i(TAG, "Starting VM " + vmName);
+ mVirtualMachine.run();
+ if (!latch.await(1, TimeUnit.MINUTES)) {
+ throw new TimeoutException("Timed out starting VM");
+ }
+
+ Log.i(
+ TAG,
+ "Payload is ready, connecting to the vsock service at port "
+ + ITestService.SERVICE_PORT);
+ ITestService testService =
+ ITestService.Stub.asInterface(
+ mVirtualMachine.connectToVsockServer(ITestService.SERVICE_PORT));
+ return new RemoteTestServiceDelegate(testService);
+ }
+
+ final class ServiceImpl extends IVmShareTestService.Stub {
+
+ @Override
+ public ITestService startVm(VirtualMachineDescriptor vmDesc) {
+ Log.i(TAG, "startVm binder call received");
+ try {
+ return VmShareServiceImpl.this.startVm(vmDesc);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to startVm", e);
+ throw new IllegalStateException("Failed to startVm", e);
+ }
+ }
+ }
+
+ private static class RemoteTestServiceDelegate extends ITestService.Stub {
+
+ private final ITestService mServiceInVm;
+
+ private RemoteTestServiceDelegate(ITestService serviceInVm) {
+ mServiceInVm = serviceInVm;
+ }
+
+ @Override
+ public int addInteger(int a, int b) throws RemoteException {
+ return mServiceInVm.addInteger(a, b);
+ }
+
+ @Override
+ public String readProperty(String prop) throws RemoteException {
+ throw new UnsupportedOperationException("Not supported");
+ }
+
+ @Override
+ public byte[] insecurelyExposeVmInstanceSecret() throws RemoteException {
+ throw new UnsupportedOperationException("Not supported");
+ }
+
+ @Override
+ public byte[] insecurelyExposeAttestationCdi() throws RemoteException {
+ throw new UnsupportedOperationException("Not supported");
+ }
+
+ @Override
+ public byte[] getBcc() throws RemoteException {
+ throw new UnsupportedOperationException("Not supported");
+ }
+
+ @Override
+ public String getApkContentsPath() throws RemoteException {
+ throw new UnsupportedOperationException("Not supported");
+ }
+
+ @Override
+ public String getEncryptedStoragePath() throws RemoteException {
+ throw new UnsupportedOperationException("Not supported");
+ }
+
+ @Override
+ public void runEchoReverseServer() throws RemoteException {
+ throw new UnsupportedOperationException("Not supported");
+ }
+
+ @Override
+ public String[] getEffectiveCapabilities() throws RemoteException {
+ throw new UnsupportedOperationException("Not supported");
+ }
+
+ @Override
+ public void writeToFile(String content, String path) throws RemoteException {
+ throw new UnsupportedOperationException("Not supported");
+ }
+
+ @Override
+ public String readFromFile(String path) throws RemoteException {
+ return mServiceInVm.readFromFile(path);
+ }
+
+ @Override
+ public void quit() throws RemoteException {
+ throw new UnsupportedOperationException("Not supported");
+ }
+ }
+}