Merge "Remove DEBUG_LEVEL_APP_ONLY"
diff --git a/javalib/api/test-current.txt b/javalib/api/test-current.txt
index 42ad060..8b7ec11 100644
--- a/javalib/api/test-current.txt
+++ b/javalib/api/test-current.txt
@@ -1,6 +1,10 @@
// Signature format: 2.0
package android.system.virtualmachine {
+ public class VirtualMachine implements java.lang.AutoCloseable {
+ method @NonNull public java.io.File getRootDir();
+ }
+
public final class VirtualMachineConfig {
method @Nullable public String getPayloadConfigPath();
}
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index 0305650..b8be703 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -49,6 +49,7 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.content.ComponentCallbacks2;
import android.content.Context;
import android.content.res.Configuration;
@@ -984,6 +985,18 @@
}
/**
+ * Returns the root directory where all files related to this {@link VirtualMachine} (e.g.
+ * {@code instance.img}, {@code apk.idsig}, etc) are stored.
+ *
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ public File getRootDir() {
+ return mVmRootPath;
+ }
+
+ /**
* Captures the current state of the VM in a {@link VirtualMachineDescriptor} instance. The VM
* needs to be stopped to avoid inconsistency in its state representation.
*
diff --git a/libs/dice/src/bcc.rs b/libs/dice/src/bcc.rs
new file mode 100644
index 0000000..849dfa0
--- /dev/null
+++ b/libs/dice/src/bcc.rs
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! Wrapper around dice/android/bcc.h.
+
+use core::mem;
+use core::ptr;
+
+use open_dice_bcc_bindgen::BccHandoverParse;
+
+use crate::check_call;
+use crate::Cdi;
+use crate::Error;
+use crate::Result;
+
+/// Boot Chain Certificate handover format combining the BCC and CDIs in a single CBOR object.
+#[derive(Clone, Debug)]
+pub struct Handover<'a> {
+ /// Attestation CDI.
+ pub cdi_attest: &'a Cdi,
+ /// Sealing CDI.
+ pub cdi_seal: &'a Cdi,
+ /// Boot Chain Certificate (optional).
+ pub bcc: Option<&'a [u8]>,
+}
+
+impl<'a> Handover<'a> {
+ /// Validates and extracts the fields of a BCC handover buffer.
+ pub fn new(buffer: &'a [u8]) -> Result<Self> {
+ let mut cdi_attest: *const u8 = ptr::null();
+ let mut cdi_seal: *const u8 = ptr::null();
+ let mut bcc: *const u8 = ptr::null();
+ let mut bcc_size: usize = 0;
+
+ // SAFETY - The buffer is only read and never stored and the returned pointers should all
+ // point within the address range of the buffer or be NULL.
+ check_call(unsafe {
+ BccHandoverParse(
+ buffer.as_ptr(),
+ buffer.len(),
+ &mut cdi_attest as *mut *const u8,
+ &mut cdi_seal as *mut *const u8,
+ &mut bcc as *mut *const u8,
+ &mut bcc_size as *mut usize,
+ )
+ })?;
+
+ let cdi_attest = {
+ let i = index_from_ptr(buffer, cdi_attest).ok_or(Error::PlatformError)?;
+ let s = buffer.get(i..(i + mem::size_of::<Cdi>())).ok_or(Error::PlatformError)?;
+ s.try_into().map_err(|_| Error::PlatformError)?
+ };
+ let cdi_seal = {
+ let i = index_from_ptr(buffer, cdi_seal).ok_or(Error::PlatformError)?;
+ let s = buffer.get(i..(i + mem::size_of::<Cdi>())).ok_or(Error::PlatformError)?;
+ s.try_into().map_err(|_| Error::PlatformError)?
+ };
+ let bcc = if bcc.is_null() {
+ None
+ } else {
+ let i = index_from_ptr(buffer, bcc).ok_or(Error::PlatformError)?;
+ Some(buffer.get(i..(i + bcc_size)).ok_or(Error::PlatformError)?)
+ };
+
+ Ok(Self { cdi_attest, cdi_seal, bcc })
+ }
+}
+
+fn index_from_ptr(slice: &[u8], pointer: *const u8) -> Option<usize> {
+ if slice.as_ptr_range().contains(&pointer) {
+ (pointer as usize).checked_sub(slice.as_ptr() as usize)
+ } else {
+ None
+ }
+}
diff --git a/libs/dice/src/lib.rs b/libs/dice/src/lib.rs
index 9e39436..43d167f 100644
--- a/libs/dice/src/lib.rs
+++ b/libs/dice/src/lib.rs
@@ -18,16 +18,23 @@
#![no_std]
-use core::fmt::{self, Debug};
-use open_dice_cbor_bindgen::{
- DiceHash, DiceResult, DiceResult_kDiceResultBufferTooSmall as DICE_RESULT_BUFFER_TOO_SMALL,
- DiceResult_kDiceResultInvalidInput as DICE_RESULT_INVALID_INPUT,
- DiceResult_kDiceResultOk as DICE_RESULT_OK,
- DiceResult_kDiceResultPlatformError as DICE_RESULT_PLATFORM_ERROR,
-};
+use core::fmt;
+use core::result;
+use open_dice_cbor_bindgen::DiceHash;
+use open_dice_cbor_bindgen::DiceResult;
+use open_dice_cbor_bindgen::DiceResult_kDiceResultBufferTooSmall as DICE_RESULT_BUFFER_TOO_SMALL;
+use open_dice_cbor_bindgen::DiceResult_kDiceResultInvalidInput as DICE_RESULT_INVALID_INPUT;
+use open_dice_cbor_bindgen::DiceResult_kDiceResultOk as DICE_RESULT_OK;
+use open_dice_cbor_bindgen::DiceResult_kDiceResultPlatformError as DICE_RESULT_PLATFORM_ERROR;
+
+pub mod bcc;
+
+const CDI_SIZE: usize = open_dice_cbor_bindgen::DICE_CDI_SIZE as usize;
const HASH_SIZE: usize = open_dice_cbor_bindgen::DICE_HASH_SIZE as usize;
+/// Array type of CDIs.
+pub type Cdi = [u8; CDI_SIZE];
/// Array type of hashes used by DICE.
pub type Hash = [u8; HASH_SIZE];
@@ -43,7 +50,7 @@
Unknown(DiceResult),
}
-impl Debug for Error {
+impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::InvalidInput => write!(f, "invalid input"),
@@ -54,7 +61,10 @@
}
}
-fn check_call(ret: DiceResult) -> Result<(), Error> {
+/// Result of DICE functions.
+pub type Result<T> = result::Result<T, Error>;
+
+fn check_call(ret: DiceResult) -> Result<()> {
match ret {
DICE_RESULT_OK => Ok(()),
DICE_RESULT_INVALID_INPUT => Err(Error::InvalidInput),
@@ -69,7 +79,7 @@
}
/// Hash the provided input using DICE's default hash function.
-pub fn hash(bytes: &[u8]) -> Result<Hash, Error> {
+pub fn hash(bytes: &[u8]) -> Result<Hash> {
let mut output: Hash = [0; HASH_SIZE];
// SAFETY - DiceHash takes a sized input buffer and writes to a constant-sized output buffer.
check_call(unsafe { DiceHash(ctx(), bytes.as_ptr(), bytes.len(), output.as_mut_ptr()) })?;
diff --git a/pvmfw/Android.bp b/pvmfw/Android.bp
index 0da24c7..6a01713 100644
--- a/pvmfw/Android.bp
+++ b/pvmfw/Android.bp
@@ -15,6 +15,7 @@
"libaarch64_paging",
"libavb_nostd",
"libbuddy_system_allocator",
+ "libdice_nostd",
"liblibfdt",
"liblog_rust_nostd",
"libpvmfw_embedded_key",
diff --git a/pvmfw/src/entry.rs b/pvmfw/src/entry.rs
index efbb179..bffc140 100644
--- a/pvmfw/src/entry.rs
+++ b/pvmfw/src/entry.rs
@@ -24,6 +24,7 @@
use core::arch::asm;
use core::num::NonZeroUsize;
use core::slice;
+use dice::bcc::Handover;
use log::debug;
use log::error;
use log::info;
@@ -32,7 +33,7 @@
use vmbase::{console, layout, logger, main, power::reboot};
#[derive(Debug, Clone)]
-pub(crate) enum RebootReason {
+pub enum RebootReason {
/// A malformed BCC was received.
InvalidBcc,
/// An invalid configuration was appended to pvmfw.
@@ -228,8 +229,9 @@
RebootReason::InvalidConfig
})?;
- let bcc = appended.get_bcc_mut().ok_or_else(|| {
- error!("Invalid BCC");
+ let bcc_slice = appended.get_bcc_mut();
+ let bcc = Handover::new(bcc_slice).map_err(|e| {
+ error!("Invalid BCC Handover: {e:?}");
RebootReason::InvalidBcc
})?;
@@ -243,10 +245,15 @@
let slices = MemorySlices::new(fdt, payload, payload_size, &mut memory)?;
// This wrapper allows main() to be blissfully ignorant of platform details.
- crate::main(slices.fdt, slices.kernel, slices.ramdisk, bcc)?;
+ crate::main(slices.fdt, slices.kernel, slices.ramdisk, &bcc, &mut memory)?;
// TODO: Overwrite BCC before jumping to payload to avoid leaking our sealing key.
+ info!("Expecting a bug making MMIO_GUARD_UNMAP return NOT_SUPPORTED on success");
+ memory.mmio_unmap_all().map_err(|e| {
+ error!("Failed to unshare MMIO ranges: {e}");
+ RebootReason::InternalError
+ })?;
mmio_guard::unmap(console::BASE_ADDRESS).map_err(|e| {
error!("Failed to unshare the UART: {e}");
RebootReason::InternalError
@@ -361,12 +368,10 @@
}
}
- fn get_bcc_mut(&mut self) -> Option<&mut [u8]> {
- let bcc = match self {
+ fn get_bcc_mut(&mut self) -> &mut [u8] {
+ match self {
Self::LegacyBcc(ref mut bcc) => bcc,
Self::Config(ref mut cfg) => cfg.get_bcc_mut(),
- };
- // TODO(b/256148034): return None if BccHandoverParse(bcc) != kDiceResultOk.
- Some(bcc)
+ }
}
}
diff --git a/pvmfw/src/main.rs b/pvmfw/src/main.rs
index d453e26..07cbd0c 100644
--- a/pvmfw/src/main.rs
+++ b/pvmfw/src/main.rs
@@ -29,18 +29,26 @@
mod memory;
mod mmio_guard;
mod mmu;
+mod pci;
mod smccc;
-use crate::entry::RebootReason;
+use crate::{
+ entry::RebootReason,
+ memory::MemoryTracker,
+ pci::{map_cam, pci_node},
+};
use avb::PUBLIC_KEY;
use avb_nostd::verify_image;
-use log::{debug, error, info};
+use dice::bcc;
+use libfdt::Fdt;
+use log::{debug, error, info, trace};
fn main(
- fdt: &libfdt::Fdt,
+ fdt: &Fdt,
signed_kernel: &[u8],
ramdisk: Option<&[u8]>,
- bcc: &[u8],
+ bcc: &bcc::Handover,
+ memory: &mut MemoryTracker,
) -> Result<(), RebootReason> {
info!("pVM firmware");
debug!("FDT: {:?}", fdt as *const libfdt::Fdt);
@@ -50,7 +58,12 @@
} else {
debug!("Ramdisk: None");
}
- debug!("BCC: {:?} ({:#x} bytes)", bcc.as_ptr(), bcc.len());
+ trace!("BCC: {bcc:x?}");
+
+ // Set up PCI bus for VirtIO devices.
+ let pci_node = pci_node(fdt)?;
+ map_cam(&pci_node, memory)?;
+
verify_image(signed_kernel, PUBLIC_KEY).map_err(|e| {
error!("Failed to verify the payload: {e}");
RebootReason::PayloadVerificationError
diff --git a/pvmfw/src/memory.rs b/pvmfw/src/memory.rs
index ca0b886..ca1024d 100644
--- a/pvmfw/src/memory.rs
+++ b/pvmfw/src/memory.rs
@@ -14,7 +14,8 @@
//! Low-level allocation and tracking of main memory.
-use crate::helpers;
+use crate::helpers::{self, page_4kb_of, SIZE_4KB};
+use crate::mmio_guard;
use crate::mmu;
use core::cmp::max;
use core::cmp::min;
@@ -43,8 +44,7 @@
impl MemoryRegion {
/// True if the instance overlaps with the passed range.
pub fn overlaps(&self, range: &MemoryRange) -> bool {
- let our: &MemoryRange = self.as_ref();
- max(our.start, range.start) < min(our.end, range.end)
+ overlaps(&self.range, range)
}
/// True if the instance is fully contained within the passed range.
@@ -60,11 +60,17 @@
}
}
+/// Returns true if one range overlaps with the other at all.
+fn overlaps<T: Copy + Ord>(a: &Range<T>, b: &Range<T>) -> bool {
+ max(a.start, b.start) < min(a.end, b.end)
+}
+
/// Tracks non-overlapping slices of main memory.
pub struct MemoryTracker {
- regions: ArrayVec<[MemoryRegion; MemoryTracker::CAPACITY]>,
total: MemoryRange,
page_table: mmu::PageTable,
+ regions: ArrayVec<[MemoryRegion; MemoryTracker::CAPACITY]>,
+ mmio_regions: ArrayVec<[MemoryRange; MemoryTracker::MMIO_CAPACITY]>,
}
/// Errors for MemoryTracker operations.
@@ -84,6 +90,8 @@
Overlaps,
/// Region couldn't be mapped.
FailedToMap,
+ /// Error from an MMIO guard call.
+ MmioGuard(mmio_guard::Error),
}
impl fmt::Display for MemoryTrackerError {
@@ -96,14 +104,22 @@
Self::OutOfRange => write!(f, "Region is out of the tracked memory address space"),
Self::Overlaps => write!(f, "New region overlaps with tracked regions"),
Self::FailedToMap => write!(f, "Failed to map the new region"),
+ Self::MmioGuard(e) => e.fmt(f),
}
}
}
+impl From<mmio_guard::Error> for MemoryTrackerError {
+ fn from(e: mmio_guard::Error) -> Self {
+ Self::MmioGuard(e)
+ }
+}
+
type Result<T> = result::Result<T, MemoryTrackerError>;
impl MemoryTracker {
const CAPACITY: usize = 5;
+ const MMIO_CAPACITY: usize = 5;
/// Base of the system's contiguous "main" memory.
const BASE: usize = 0x8000_0000;
/// First address that can't be translated by a level 1 TTBR0_EL1.
@@ -111,7 +127,12 @@
/// Create a new instance from an active page table, covering the maximum RAM size.
pub fn new(page_table: mmu::PageTable) -> Self {
- Self { total: Self::BASE..Self::MAX_ADDR, page_table, regions: ArrayVec::new() }
+ Self {
+ total: Self::BASE..Self::MAX_ADDR,
+ page_table,
+ regions: ArrayVec::new(),
+ mmio_regions: ArrayVec::new(),
+ }
}
/// Resize the total RAM size.
@@ -134,20 +155,24 @@
/// Allocate the address range for a const slice; returns None if failed.
pub fn alloc_range(&mut self, range: &MemoryRange) -> Result<MemoryRange> {
+ let region = MemoryRegion { range: range.clone(), mem_type: MemoryType::ReadOnly };
+ self.check(®ion)?;
self.page_table.map_rodata(range).map_err(|e| {
error!("Error during range allocation: {e}");
MemoryTrackerError::FailedToMap
})?;
- self.add(MemoryRegion { range: range.clone(), mem_type: MemoryType::ReadOnly })
+ self.add(region)
}
/// Allocate the address range for a mutable slice; returns None if failed.
pub fn alloc_range_mut(&mut self, range: &MemoryRange) -> Result<MemoryRange> {
+ let region = MemoryRegion { range: range.clone(), mem_type: MemoryType::ReadWrite };
+ self.check(®ion)?;
self.page_table.map_data(range).map_err(|e| {
error!("Error during mutable range allocation: {e}");
MemoryTrackerError::FailedToMap
})?;
- self.add(MemoryRegion { range: range.clone(), mem_type: MemoryType::ReadWrite })
+ self.add(region)
}
/// Allocate the address range for a const slice; returns None if failed.
@@ -160,24 +185,77 @@
self.alloc_range_mut(&(base..(base + size.get())))
}
- fn add(&mut self, region: MemoryRegion) -> Result<MemoryRange> {
+ /// Checks that the given range of addresses is within the MMIO region, and then maps it
+ /// appropriately.
+ pub fn map_mmio_range(&mut self, range: MemoryRange) -> Result<()> {
+ // MMIO space is below the main memory region.
+ if range.end > self.total.start {
+ return Err(MemoryTrackerError::OutOfRange);
+ }
+ if self.mmio_regions.iter().any(|r| overlaps(r, &range)) {
+ return Err(MemoryTrackerError::Overlaps);
+ }
+ if self.mmio_regions.len() == self.mmio_regions.capacity() {
+ return Err(MemoryTrackerError::Full);
+ }
+
+ self.page_table.map_device(&range).map_err(|e| {
+ error!("Error during MMIO device mapping: {e}");
+ MemoryTrackerError::FailedToMap
+ })?;
+
+ for page_base in page_iterator(&range) {
+ mmio_guard::map(page_base)?;
+ }
+
+ if self.mmio_regions.try_push(range).is_some() {
+ return Err(MemoryTrackerError::Full);
+ }
+
+ Ok(())
+ }
+
+ /// Checks that the given region is within the range of the `MemoryTracker` and doesn't overlap
+ /// with any other previously allocated regions, and that the regions ArrayVec has capacity to
+ /// add it.
+ fn check(&self, region: &MemoryRegion) -> Result<()> {
if !region.is_within(&self.total) {
return Err(MemoryTrackerError::OutOfRange);
}
- if self.regions.iter().any(|r| r.overlaps(region.as_ref())) {
+ if self.regions.iter().any(|r| r.overlaps(®ion.range)) {
return Err(MemoryTrackerError::Overlaps);
}
+ if self.regions.len() == self.regions.capacity() {
+ return Err(MemoryTrackerError::Full);
+ }
+ Ok(())
+ }
+
+ fn add(&mut self, region: MemoryRegion) -> Result<MemoryRange> {
if self.regions.try_push(region).is_some() {
return Err(MemoryTrackerError::Full);
}
Ok(self.regions.last().unwrap().as_ref().clone())
}
+
+ /// Unmaps all tracked MMIO regions from the MMIO guard.
+ ///
+ /// Note that they are not unmapped from the page table.
+ pub fn mmio_unmap_all(&self) -> Result<()> {
+ for region in &self.mmio_regions {
+ for page_base in page_iterator(region) {
+ mmio_guard::unmap(page_base)?;
+ }
+ }
+
+ Ok(())
+ }
}
impl Drop for MemoryTracker {
fn drop(&mut self) {
- for region in self.regions.iter() {
+ for region in &self.regions {
match region.mem_type {
MemoryType::ReadWrite => {
// TODO: Use page table's dirty bit to only flush pages that were touched.
@@ -188,3 +266,8 @@
}
}
}
+
+/// Returns an iterator which yields the base address of each 4 KiB page within the given range.
+fn page_iterator(range: &MemoryRange) -> impl Iterator<Item = usize> {
+ (page_4kb_of(range.start)..range.end).step_by(SIZE_4KB)
+}
diff --git a/pvmfw/src/mmio_guard.rs b/pvmfw/src/mmio_guard.rs
index eb6c1fa..28f928f 100644
--- a/pvmfw/src/mmio_guard.rs
+++ b/pvmfw/src/mmio_guard.rs
@@ -105,7 +105,6 @@
args[0] = ipa;
// TODO(b/251426790): pKVM currently returns NOT_SUPPORTED for SUCCESS.
- info!("Expecting a bug making MMIO_GUARD_UNMAP return NOT_SUPPORTED on success");
match smccc::checked_hvc64_expect_zero(VENDOR_HYP_KVM_MMIO_GUARD_UNMAP_FUNC_ID, args) {
Err(smccc::Error::NotSupported) | Ok(_) => Ok(()),
x => x,
diff --git a/pvmfw/src/pci.rs b/pvmfw/src/pci.rs
new file mode 100644
index 0000000..7baabed
--- /dev/null
+++ b/pvmfw/src/pci.rs
@@ -0,0 +1,74 @@
+// Copyright 2022, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Functions to scan the PCI bus for VirtIO device and allocate BARs.
+
+use crate::{entry::RebootReason, memory::MemoryTracker};
+use core::ffi::CStr;
+use libfdt::{Fdt, FdtNode};
+use log::{debug, error};
+
+/// PCI MMIO configuration region size.
+const PCI_CFG_SIZE: usize = 0x100_0000;
+
+/// Finds an FDT node with compatible=pci-host-cam-generic.
+pub fn pci_node(fdt: &Fdt) -> Result<FdtNode, RebootReason> {
+ fdt.compatible_nodes(CStr::from_bytes_with_nul(b"pci-host-cam-generic\0").unwrap())
+ .map_err(|e| {
+ error!("Failed to find PCI bus in FDT: {}", e);
+ RebootReason::InvalidFdt
+ })?
+ .next()
+ .ok_or(RebootReason::InvalidFdt)
+}
+
+pub fn map_cam(pci_node: &FdtNode, memory: &mut MemoryTracker) -> Result<(), RebootReason> {
+ // Parse reg property to find CAM.
+ let pci_reg = pci_node
+ .reg()
+ .map_err(|e| {
+ error!("Error getting reg property from PCI node: {}", e);
+ RebootReason::InvalidFdt
+ })?
+ .ok_or_else(|| {
+ error!("PCI node missing reg property.");
+ RebootReason::InvalidFdt
+ })?
+ .next()
+ .ok_or_else(|| {
+ error!("Empty reg property on PCI node.");
+ RebootReason::InvalidFdt
+ })?;
+ let cam_addr = pci_reg.addr as usize;
+ let cam_size = pci_reg.size.ok_or_else(|| {
+ error!("PCI reg property missing size.");
+ RebootReason::InvalidFdt
+ })? as usize;
+ debug!("Found PCI CAM at {:#x}-{:#x}", cam_addr, cam_addr + cam_size);
+ // Check that the CAM is the size we expect, so we don't later try accessing it beyond its
+ // bounds. If it is a different size then something is very wrong and we shouldn't continue to
+ // access it; maybe there is some new version of PCI we don't know about.
+ if cam_size != PCI_CFG_SIZE {
+ error!("FDT says PCI CAM is {} bytes but we expected {}.", cam_size, PCI_CFG_SIZE);
+ return Err(RebootReason::InvalidFdt);
+ }
+
+ // Map the CAM as MMIO.
+ memory.map_mmio_range(cam_addr..cam_addr + cam_size).map_err(|e| {
+ error!("Failed to map PCI CAM: {}", e);
+ RebootReason::InternalError
+ })?;
+
+ Ok(())
+}
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 cc1bbfd..fa8be93 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -324,6 +324,52 @@
}
@Test
+ @CddTest(requirements = {"9.17/C-1-1"})
+ public void vmFilesStoredInDeDirWhenCreatedFromDEContext() throws Exception {
+ final Context ctx = getContext().createDeviceProtectedStorageContext();
+ final int userId = ctx.getUserId();
+ final VirtualMachineManager vmm = ctx.getSystemService(VirtualMachineManager.class);
+ VirtualMachineConfig config =
+ newVmConfigBuilder().setPayloadBinaryPath("binary/path").build();
+ try {
+ VirtualMachine vm = vmm.create("vm-name", config);
+ // TODO(b/261430346): what about non-primary user?
+ assertThat(vm.getRootDir().getAbsolutePath())
+ .isEqualTo(
+ "/data/user_de/" + userId + "/com.android.microdroid.test/vm/vm-name");
+ } finally {
+ vmm.delete("vm-name");
+ }
+ }
+
+ @Test
+ @CddTest(requirements = {"9.17/C-1-1"})
+ public void vmFilesStoredInCeDirWhenCreatedFromCEContext() throws Exception {
+ final Context ctx = getContext().createCredentialProtectedStorageContext();
+ final int userId = ctx.getUserId();
+ final VirtualMachineManager vmm = ctx.getSystemService(VirtualMachineManager.class);
+ VirtualMachineConfig config =
+ newVmConfigBuilder().setPayloadBinaryPath("binary/path").build();
+ try {
+ VirtualMachine vm = vmm.create("vm-name", config);
+ // TODO(b/261430346): what about non-primary user?
+ assertThat(vm.getRootDir().getAbsolutePath())
+ .isEqualTo("/data/user/" + userId + "/com.android.microdroid.test/vm/vm-name");
+ } finally {
+ vmm.delete("vm-name");
+ }
+ }
+
+ @Test
+ @CddTest(requirements = {"9.17/C-1-1"})
+ public void differentManagersForDifferentContexts() throws Exception {
+ final Context ceCtx = getContext().createCredentialProtectedStorageContext();
+ final Context deCtx = getContext().createDeviceProtectedStorageContext();
+ assertThat(ceCtx.getSystemService(VirtualMachineManager.class))
+ .isNotSameInstanceAs(deCtx.getSystemService(VirtualMachineManager.class));
+ }
+
+ @Test
@CddTest(requirements = {
"9.17/C-1-1",
"9.17/C-1-2",