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");
+        }
+    }
+}