Merge "Use generated sysprop library." into main
diff --git a/Android.bp b/Android.bp
index f50007f..2091a90 100644
--- a/Android.bp
+++ b/Android.bp
@@ -68,6 +68,7 @@
module_type: "cc_defaults",
config_namespace: "ANDROID",
bool_variables: [
+ "release_avf_enable_dice_changes",
"release_avf_enable_virt_cpufreq",
],
properties: [
@@ -78,6 +79,9 @@
avf_flag_aware_cc_defaults {
name: "avf_build_flags_cc",
soong_config_variables: {
+ release_avf_enable_dice_changes: {
+ cflags: ["-DAVF_OPEN_DICE_CHANGES=1"],
+ },
release_avf_enable_virt_cpufreq: {
cflags: ["-DAVF_ENABLE_VIRT_CPUFREQ=1"],
},
diff --git a/TEST_MAPPING b/TEST_MAPPING
index ec9042c..5b0c000 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -62,6 +62,9 @@
// TODO(b/325610326): Add this target to presubmit once there is enough
// SLO data for it.
"name": "AvfRkpdAppIntegrationTests"
+ },
+ {
+ "name": "AvfRkpdVmAttestationTestApp"
}
],
"postsubmit": [
diff --git a/flags/cpp/Android.bp b/flags/cpp/Android.bp
new file mode 100644
index 0000000..da4158a
--- /dev/null
+++ b/flags/cpp/Android.bp
@@ -0,0 +1,13 @@
+cc_library_static {
+ name: "libavf_cc_flags",
+ defaults: ["avf_build_flags_cc"],
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.virt",
+ ],
+
+ export_include_dirs: ["include"],
+ local_include_dirs: ["include"],
+ ramdisk_available: true,
+ recovery_available: true,
+}
diff --git a/flags/cpp/include/android/avf_cc_flags.h b/flags/cpp/include/android/avf_cc_flags.h
new file mode 100644
index 0000000..536ea9f
--- /dev/null
+++ b/flags/cpp/include/android/avf_cc_flags.h
@@ -0,0 +1,31 @@
+// Copyright 2024, 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.
+
+#pragma once
+
+// TODO(b/309090563): remove this file once build flags are exposed to aconfig.
+
+namespace android {
+namespace virtualization {
+
+inline bool IsOpenDiceChangesFlagEnabled() {
+#ifdef AVF_OPEN_DICE_CHANGES
+ return AVF_OPEN_DICE_CHANGES;
+#else
+ return false;
+#endif
+}
+
+} // namespace virtualization
+} // namespace android
diff --git a/java/framework/Android.bp b/java/framework/Android.bp
index 32b2aee..26ea214 100644
--- a/java/framework/Android.bp
+++ b/java/framework/Android.bp
@@ -43,4 +43,7 @@
impl_library_visibility: [
"//packages/modules/Virtualization:__subpackages__",
],
+ aconfig_declarations: [
+ "avf_aconfig_flags",
+ ],
}
diff --git a/libs/dice/driver/Android.bp b/libs/dice/driver/Android.bp
new file mode 100644
index 0000000..c93bd7d
--- /dev/null
+++ b/libs/dice/driver/Android.bp
@@ -0,0 +1,50 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+ name: "libdice_driver_defaults",
+ crate_name: "dice_driver",
+ defaults: [
+ "avf_build_flags_rust",
+ ],
+ srcs: ["src/lib.rs"],
+ edition: "2021",
+ prefer_rlib: true,
+ rustlibs: [
+ "libanyhow",
+ "libbyteorder",
+ "libcoset",
+ "libdice_policy_builder",
+ "libdiced_open_dice",
+ "libdiced_sample_inputs",
+ "libkeystore2_crypto_rust",
+ "liblibc",
+ "liblog_rust",
+ "libnix",
+ "libonce_cell",
+ "libopenssl",
+ "libthiserror",
+ "libserde_cbor",
+ ],
+ multilib: {
+ lib32: {
+ enabled: false,
+ },
+ },
+}
+
+rust_library {
+ name: "libdice_driver",
+ defaults: ["libdice_driver_defaults"],
+}
+
+rust_test {
+ name: "libdice_driver_test",
+ defaults: ["libdice_driver_defaults"],
+ test_suites: ["general-tests"],
+ rustlibs: [
+ "libhex",
+ "libtempfile",
+ ],
+}
diff --git a/microdroid_manager/src/dice_driver.rs b/libs/dice/driver/src/lib.rs
similarity index 70%
rename from microdroid_manager/src/dice_driver.rs
rename to libs/dice/driver/src/lib.rs
index 229f3e0..79edb51 100644
--- a/microdroid_manager/src/dice_driver.rs
+++ b/libs/dice/driver/src/lib.rs
@@ -32,13 +32,26 @@
/// Artifacts that are mapped into the process address space from the driver.
pub enum DiceDriver<'a> {
+ /// Implementation that reads bcc handover from the dice driver.
Real {
+ /// Path to the driver character device (e.g. /dev/open-dice0).
driver_path: PathBuf,
+ /// Address of the memory to mmap driver to.
mmap_addr: *mut c_void,
+ /// Size of the mmap.
mmap_size: usize,
+ /// BCC handover.
bcc_handover: BccHandover<'a>,
},
+ /// Fake implementation used in tests and non-protected VMs.
Fake(OwnedDiceArtifacts),
+ /// Implementation that reads bcc handover from the file.
+ FromFile {
+ /// Path to the file to read dice chain from,
+ file_path: PathBuf,
+ /// Dice artifacts read from file_path,
+ dice_artifacts: OwnedDiceArtifacts,
+ },
}
impl DiceDriver<'_> {
@@ -46,13 +59,15 @@
match self {
Self::Real { bcc_handover, .. } => bcc_handover,
Self::Fake(owned_dice_artifacts) => owned_dice_artifacts,
+ Self::FromFile { dice_artifacts, .. } => dice_artifacts,
}
}
- pub fn new(driver_path: &Path) -> Result<Self> {
+ /// Creates a new dice driver from the given driver_path.
+ pub fn new(driver_path: &Path, is_strict_boot: bool) -> Result<Self> {
if driver_path.exists() {
log::info!("Using DICE values from driver");
- } else if super::is_strict_boot() {
+ } else if is_strict_boot {
bail!("Strict boot requires DICE value from driver but none were found");
} else {
log::warn!("Using sample DICE values");
@@ -90,6 +105,15 @@
})
}
+ /// Create a new dice driver that reads dice_artifacts from the given file.
+ pub fn from_file(file_path: &Path) -> Result<Self> {
+ let file =
+ fs::File::open(file_path).map_err(|error| Error::new(error).context("open file"))?;
+ let dice_artifacts = serde_cbor::from_reader(file)
+ .map_err(|error| Error::new(error).context("read file"))?;
+ Ok(Self::FromFile { file_path: file_path.to_path_buf(), dice_artifacts })
+ }
+
/// Derives a sealing key of `key_length` bytes from the DICE sealing CDI.
pub fn get_sealing_key(&self, identifier: &[u8], key_length: usize) -> Result<ZVec> {
// Deterministically derive a key to use for sealing data, rather than using the CDI
@@ -101,6 +125,7 @@
Ok(key)
}
+ /// Derives a new dice chain.
pub fn derive(
self,
code_hash: Hash,
@@ -147,3 +172,36 @@
}
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ fn assert_eq_bytes(expected: &[u8], actual: &[u8]) {
+ assert_eq!(
+ expected,
+ actual,
+ "Expected {}, got {}",
+ hex::encode(expected),
+ hex::encode(actual)
+ )
+ }
+
+ #[test]
+ fn test_write_bcc_to_file_read_from_file() -> Result<()> {
+ let dice_artifacts = diced_sample_inputs::make_sample_bcc_and_cdis()?;
+
+ let test_file = tempfile::NamedTempFile::new()?;
+ serde_cbor::to_writer(test_file.as_file(), &dice_artifacts)?;
+ test_file.as_file().sync_all()?;
+
+ let dice = DiceDriver::from_file(test_file.as_ref())?;
+
+ let dice_artifacts2 = dice.dice_artifacts();
+ assert_eq_bytes(dice_artifacts.cdi_attest(), dice_artifacts2.cdi_attest());
+ assert_eq_bytes(dice_artifacts.cdi_seal(), dice_artifacts2.cdi_seal());
+ assert_eq_bytes(dice_artifacts.bcc().expect("bcc"), dice_artifacts2.bcc().expect("bcc"));
+
+ Ok(())
+ }
+}
diff --git a/libs/dice/open_dice/Android.bp b/libs/dice/open_dice/Android.bp
index 79d0b96..ab3220e 100644
--- a/libs/dice/open_dice/Android.bp
+++ b/libs/dice/open_dice/Android.bp
@@ -39,12 +39,15 @@
rustlibs: [
"libopen_dice_android_bindgen",
"libopen_dice_cbor_bindgen",
+ "libserde",
"libzeroize",
],
features: [
"alloc",
+ "serde_derive",
"std",
],
+ proc_macros: ["libserde_derive"],
shared_libs: [
"libcrypto",
],
@@ -69,7 +72,7 @@
rust_defaults {
name: "libdiced_open_dice_test_defaults",
crate_name: "diced_open_dice_test",
- srcs: ["tests/*.rs"],
+ srcs: ["tests/api_test.rs"],
test_suites: ["general-tests"],
}
diff --git a/libs/dice/open_dice/src/dice.rs b/libs/dice/open_dice/src/dice.rs
index e42e373..e330e00 100644
--- a/libs/dice/open_dice/src/dice.rs
+++ b/libs/dice/open_dice/src/dice.rs
@@ -23,6 +23,8 @@
DICE_INLINE_CONFIG_SIZE, DICE_PRIVATE_KEY_SEED_SIZE, DICE_PRIVATE_KEY_SIZE,
DICE_PUBLIC_KEY_SIZE, DICE_SIGNATURE_SIZE,
};
+#[cfg(feature = "serde_derive")]
+use serde_derive::{Deserialize, Serialize};
use std::{marker::PhantomData, ptr};
use zeroize::{Zeroize, ZeroizeOnDrop};
@@ -82,6 +84,7 @@
/// for sensitive data like CDI values and private key.
/// CDI Values.
#[derive(Debug, Zeroize, ZeroizeOnDrop, Default)]
+#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
pub struct CdiValues {
/// Attestation CDI.
pub cdi_attest: [u8; CDI_SIZE],
diff --git a/libs/dice/open_dice/src/retry.rs b/libs/dice/open_dice/src/retry.rs
index a6303bd..d9551f3 100644
--- a/libs/dice/open_dice/src/retry.rs
+++ b/libs/dice/open_dice/src/retry.rs
@@ -25,12 +25,15 @@
use crate::ops::generate_certificate;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
+#[cfg(feature = "serde_derive")]
+use serde_derive::{Deserialize, Serialize};
/// Artifacts stores a set of dice artifacts comprising CDI_ATTEST, CDI_SEAL,
/// and the BCC formatted attestation certificate chain.
/// As we align with the DICE standards today, this is the certificate chain
/// is also called DICE certificate chain.
#[derive(Debug)]
+#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
pub struct OwnedDiceArtifacts {
/// CDI Values.
cdi_values: CdiValues,
diff --git a/libs/dice/sample_inputs/Android.bp b/libs/dice/sample_inputs/Android.bp
index 013038c..36fe8c7 100644
--- a/libs/dice/sample_inputs/Android.bp
+++ b/libs/dice/sample_inputs/Android.bp
@@ -54,7 +54,7 @@
rust_defaults {
name: "libdiced_sample_inputs_test_defaults",
crate_name: "diced_sample_inputs_test",
- srcs: ["tests/*.rs"],
+ srcs: ["tests/api_test.rs"],
test_suites: ["general-tests"],
rustlibs: [
"libanyhow",
diff --git a/libs/libfdt/Android.bp b/libs/libfdt/Android.bp
index 1bb5692..7a7d71f 100644
--- a/libs/libfdt/Android.bp
+++ b/libs/libfdt/Android.bp
@@ -53,7 +53,7 @@
name: "liblibfdt.integration_test",
crate_name: "libfdt_test",
defaults: ["avf_build_flags_rust"],
- srcs: ["tests/*.rs"],
+ srcs: ["tests/api_test.rs"],
test_suites: ["general-tests"],
data: [
":fdt_test_tree_one_memory_range_dtb",
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index 999dc52..e19a343 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -49,10 +49,12 @@
module_type: "android_system_image",
config_namespace: "ANDROID",
bool_variables: [
+ "release_avf_enable_dice_changes",
"release_avf_enable_multi_tenant_microdroid_vm",
],
properties: [
"deps",
+ "dirs",
],
}
@@ -154,6 +156,14 @@
// Below are dependencies that are conditionally enabled depending on value of build flags.
soong_config_variables: {
+ release_avf_enable_dice_changes: {
+ deps: [
+ "derive_microdroid_vendor_dice_node",
+ ],
+ dirs: [
+ "microdroid_resources",
+ ],
+ },
release_avf_enable_multi_tenant_microdroid_vm: {
deps: [
"microdroid_etc_passwd",
@@ -295,7 +305,19 @@
},
}
-android_filesystem {
+soong_config_module_type {
+ name: "flag_aware_microdroid_filesystem",
+ module_type: "android_filesystem",
+ config_namespace: "ANDROID",
+ bool_variables: [
+ "release_avf_enable_dice_changes",
+ ],
+ properties: [
+ "dirs",
+ ],
+}
+
+flag_aware_microdroid_filesystem {
name: "microdroid_ramdisk",
deps: [
"init_first_stage.microdroid",
@@ -305,14 +327,23 @@
"proc",
"sys",
- // TODO(jiyong): remove these
"mnt",
"debug_ramdisk",
"second_stage_resources",
],
type: "compressed_cpio",
+
+ // Below are dependencies that are conditionally enabled depending on value of build flags.
+ soong_config_variables: {
+ release_avf_enable_dice_changes: {
+ dirs: [
+ "microdroid_resources",
+ ],
+ },
+ },
}
+// TODO(ioffe): rename to microdroid_first_stage_ramdisk
android_filesystem {
name: "microdroid_fstab_ramdisk",
deps: [
diff --git a/microdroid/derive_microdroid_vendor_dice_node/Android.bp b/microdroid/derive_microdroid_vendor_dice_node/Android.bp
new file mode 100644
index 0000000..de1bef7
--- /dev/null
+++ b/microdroid/derive_microdroid_vendor_dice_node/Android.bp
@@ -0,0 +1,22 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+ name: "derive_microdroid_vendor_dice_node_defaults",
+ crate_name: "derive_microdroid_vendor_dice_node",
+ defaults: ["avf_build_flags_rust"],
+ srcs: ["src/main.rs"],
+ rustlibs: [
+ "libanyhow",
+ "libclap",
+ ],
+ bootstrap: true,
+ prefer_rlib: true,
+}
+
+rust_binary {
+ name: "derive_microdroid_vendor_dice_node",
+ defaults: ["derive_microdroid_vendor_dice_node_defaults"],
+ stem: "derive_microdroid_vendor_dice_node",
+}
diff --git a/microdroid/derive_microdroid_vendor_dice_node/src/main.rs b/microdroid/derive_microdroid_vendor_dice_node/src/main.rs
new file mode 100644
index 0000000..1d5db0d
--- /dev/null
+++ b/microdroid/derive_microdroid_vendor_dice_node/src/main.rs
@@ -0,0 +1,38 @@
+// Copyright 2024, 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.
+
+//! Derives microdroid vendor dice node.
+
+use anyhow::Error;
+use clap::Parser;
+use std::path::PathBuf;
+
+#[derive(Parser)]
+struct Args {
+ /// Path to the dice driver (e.g. /dev/open-dice0)
+ #[arg(long)]
+ dice_driver: PathBuf,
+ /// Path to the microdroid-vendor.img disk image.
+ #[arg(long)]
+ microdroid_vendor_disk_image: PathBuf,
+ /// File to save resulting dice chain to.
+ #[arg(long)]
+ output: PathBuf,
+}
+
+fn main() -> Result<(), Error> {
+ let args = Args::parse();
+ eprintln!("{:?} {:?} {:?}", args.dice_driver, args.microdroid_vendor_disk_image, args.output);
+ Ok(())
+}
diff --git a/microdroid_manager/Android.bp b/microdroid_manager/Android.bp
index 81bb409..9c9a3d0 100644
--- a/microdroid_manager/Android.bp
+++ b/microdroid_manager/Android.bp
@@ -29,6 +29,7 @@
"libclient_vm_csr",
"libciborium",
"libcoset",
+ "libdice_driver",
"libdice_policy_builder",
"libdiced_open_dice",
"libdiced_sample_inputs",
diff --git a/microdroid_manager/src/dice.rs b/microdroid_manager/src/dice.rs
index a8b88aa..7f65159 100644
--- a/microdroid_manager/src/dice.rs
+++ b/microdroid_manager/src/dice.rs
@@ -12,12 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use crate::dice_driver::DiceDriver;
use crate::instance::{ApexData, ApkData};
use crate::{is_debuggable, MicrodroidData};
use anyhow::{bail, Context, Result};
use ciborium::{cbor, Value};
use coset::CborSerializable;
+use dice_driver::DiceDriver;
use diced_open_dice::OwnedDiceArtifacts;
use microdroid_metadata::PayloadMetadata;
use openssl::sha::{sha512, Sha512};
diff --git a/microdroid_manager/src/instance.rs b/microdroid_manager/src/instance.rs
index 7a9f0e0..888c451 100644
--- a/microdroid_manager/src/instance.rs
+++ b/microdroid_manager/src/instance.rs
@@ -33,11 +33,11 @@
//! The payload of a partition is encrypted/signed by a key that is unique to the loader and to the
//! VM as well. Failing to decrypt/authenticate a partition by a loader stops the boot process.
-use crate::dice_driver::DiceDriver;
use crate::ioutil;
use anyhow::{anyhow, bail, Context, Result};
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
+use dice_driver::DiceDriver;
use openssl::symm::{decrypt_aead, encrypt_aead, Cipher};
use serde::{Deserialize, Serialize};
use std::fs::{File, OpenOptions};
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 0d67632..8d2c629 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -15,7 +15,6 @@
//! Microdroid Manager
mod dice;
-mod dice_driver;
mod instance;
mod ioutil;
mod payload;
@@ -33,12 +32,12 @@
};
use crate::dice::dice_derivation;
-use crate::dice_driver::DiceDriver;
use crate::instance::{InstanceDisk, MicrodroidData};
use crate::verify::verify_payload;
use crate::vm_payload_service::register_vm_payload_service;
use anyhow::{anyhow, bail, ensure, Context, Error, Result};
use binder::Strong;
+use dice_driver::DiceDriver;
use keystore2_crypto::ZVec;
use libc::VMADDR_CID_HOST;
use log::{error, info};
@@ -241,7 +240,8 @@
vm_payload_service_fd: OwnedFd,
) -> Result<i32> {
let metadata = load_metadata().context("Failed to load payload metadata")?;
- let dice = DiceDriver::new(Path::new("/dev/open-dice0")).context("Failed to load DICE")?;
+ let dice = DiceDriver::new(Path::new("/dev/open-dice0"), is_strict_boot())
+ .context("Failed to load DICE")?;
let mut instance = InstanceDisk::new().context("Failed to load instance.img")?;
let saved_data =
diff --git a/pvmfw/Android.bp b/pvmfw/Android.bp
index 6a6d199..cce0e73 100644
--- a/pvmfw/Android.bp
+++ b/pvmfw/Android.bp
@@ -83,6 +83,7 @@
":test_pvmfw_devices_vm_dtbo",
":test_pvmfw_devices_vm_dtbo_without_symbols",
":test_pvmfw_devices_vm_dtbo_with_duplicated_iommus",
+ ":test_pvmfw_devices_overlapping_pvmfw",
":test_pvmfw_devices_with_rng",
":test_pvmfw_devices_with_multiple_devices_iommus",
":test_pvmfw_devices_with_iommu_sharing",
@@ -142,6 +143,13 @@
}
genrule {
+ name: "test_pvmfw_devices_overlapping_pvmfw",
+ defaults: ["test_device_assignment_dts_to_dtb"],
+ srcs: ["testdata/test_pvmfw_devices_overlapping_pvmfw.dts"],
+ out: ["test_pvmfw_devices_overlapping_pvmfw.dtb"],
+}
+
+genrule {
name: "test_pvmfw_devices_with_rng",
defaults: ["test_device_assignment_dts_to_dtb"],
srcs: ["testdata/test_pvmfw_devices_with_rng.dts"],
diff --git a/pvmfw/README.md b/pvmfw/README.md
index 2758a5d..053e4f7 100644
--- a/pvmfw/README.md
+++ b/pvmfw/README.md
@@ -30,11 +30,11 @@
hypervisor, although trusted, is also validated.
Once it has been determined that the platform can be trusted, pvmfw derives
-unique secrets for the guest through the [_Boot Certificate Chain_][BCC]
-("BCC", see [Open Profile for DICE][open-dice]) that can be used to prove the
-identity of the pVM to local and remote actors. If any operation or check fails,
-or in case of a missing prerequisite, pvmfw will abort the boot process of the
-pVM, effectively preventing non-compliant pVMs and/or guests from running.
+unique secrets for the guest through the [_DICE Chain_][android-dice] (see
+[Open Profile for DICE][open-dice]) that can be used to prove the identity of
+the pVM to local and remote actors. If any operation or check fails, or in case
+of a missing prerequisite, pvmfw will abort the boot process of the pVM,
+effectively preventing non-compliant pVMs and/or guests from running.
Otherwise, it hands over the pVM to the guest kernel by jumping to its first
instruction, similarly to a bootloader.
@@ -42,7 +42,7 @@
[AVF]: https://source.android.com/docs/core/virtualization
[why-avf]: https://source.android.com/docs/core/virtualization/whyavf
-[BCC]: https://pigweed.googlesource.com/open-dice/+/refs/heads/main/docs/android.md
+[android-dice]: https://pigweed.googlesource.com/open-dice/+/refs/heads/main/docs/android.md
[pKVM]: https://source.android.com/docs/core/virtualization/architecture#hypervisor
[open-dice]: https://pigweed.googlesource.com/open-dice/+/refs/heads/main/docs/specification.md
@@ -153,7 +153,7 @@
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+
| (Padding to 8-byte alignment) |
+===============================+ <-- FIRST
-| {First blob: BCC} |
+| {First blob: DICE chain} |
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ <-- FIRST_END
| (Padding to 8-byte alignment) |
+===============================+ <-- SECOND
@@ -189,7 +189,7 @@
The header format itself is agnostic of the internal format of the individual
blos it refers to. In version 1.0, it describes two blobs:
-- entry 0 must point to a valid BCC Handover (see below)
+- entry 0 must point to a valid DICE chain handover (see below)
- entry 1 may point to a [DTBO] to be applied to the pVM device tree. See
[debug policy][debug_policy] for an example.
@@ -230,39 +230,39 @@
[secretkeeper_key]: https://android.googlesource.com/platform/system/secretkeeper/+/refs/heads/main/README.md#secretkeeper-public-key
[vendor_hashtree_digest]: ../microdroid/README.md#verification-of-vendor-image
-#### Virtual Platform Boot Certificate Chain Handover
+#### Virtual Platform DICE Chain Handover
-The format of the BCC entry mentioned above, compatible with the
-[`BccHandover`][BccHandover] defined by the Open Profile for DICE reference
-implementation, is described by the following [CDDL][CDDL]:
+The format of the DICE chain entry mentioned above, compatible with the
+[`AndroidDiceHandover`][AndroidDiceHandover] defined by the Open Profile for
+DICE reference implementation, is described by the following [CDDL][CDDL]:
```
-PvmfwBccHandover = {
+PvmfwDiceHandover = {
1 : bstr .size 32, ; CDI_Attest
2 : bstr .size 32, ; CDI_Seal
- 3 : Bcc, ; Certificate chain
+ 3 : DiceCertChain, ; Android DICE chain
}
```
and contains the _Compound Device Identifiers_ ("CDIs"), used to derive the
next-stage secret, and a certificate chain, intended for pVM attestation. Note
-that it differs from the `BccHandover` defined by the specification in that its
-`Bcc` field is mandatory (while optional in the original).
+that it differs from the `AndroidDiceHandover` defined by the specification in
+that its `DiceCertChain` field is mandatory (while optional in the original).
Devices that fully implement DICE should provide a certificate rooted at the
Unique Device Secret (UDS) in a boot stage preceding the pvmfw loader (typically
-ABL), in such a way that it would receive a valid `BccHandover`, that can be
-passed to [`BccHandoverMainFlow`][BccHandoverMainFlow] along with the inputs
-described below.
+ABL), in such a way that it would receive a valid `AndroidDiceHandover`, that
+can be passed to [`DiceAndroidHandoverMainFlow`][DiceAndroidHandoverMainFlow] along with
+the inputs described below.
Otherwise, as an intermediate step towards supporting DICE throughout the
-software stack of the device, incomplete implementations may root the BCC at the
-pvmfw loader, using an arbitrary constant as initial CDI. The pvmfw loader can
-easily do so by:
+software stack of the device, incomplete implementations may root the DICE chain
+at the pvmfw loader, using an arbitrary constant as initial CDI. The pvmfw
+loader can easily do so by:
-1. Building a BCC-less `BccHandover` using CBOR operations
- ([example][Trusty-BCC]) and containing the constant CDIs
-1. Passing the resulting `BccHandover` to `BccHandoverMainFlow` as described
- above
+1. Building an "empty" `AndroidDiceHandover` using CBOR operations only
+ containing constant CDIs ([example][Trusty-BCC])
+1. Passing the resulting `AndroidDiceHandover` to `DiceAndroidHandoverMainFlow`
+ as described above
The recommended DICE inputs at this stage are:
@@ -278,13 +278,14 @@
storage and changes during every factory reset) or similar that changes as
part of the device lifecycle (_e.g._ reset)
-The resulting `BccHandover` is then used by pvmfw in a similar way to derive
-another [DICE layer][Layering], passed to the guest through a `/reserved-memory`
-device tree node marked as [`compatible=”google,open-dice”`][dice-dt].
+The resulting `AndroidDiceHandover` is then used by pvmfw in a similar way to
+derive another [DICE layer][Layering], passed to the guest through a
+`/reserved-memory` device tree node marked as
+[`compatible=”google,open-dice”`][dice-dt].
[AVB]: https://source.android.com/docs/security/features/verifiedboot/boot-flow
-[BccHandover]: https://pigweed.googlesource.com/open-dice/+/825e3beb6c/src/android/bcc.c#260
-[BccHandoverMainFlow]: https://pigweed.googlesource.com/open-dice/+/825e3beb6c/src/android/bcc.c#199
+[AndroidDiceHandover]: https://pigweed.googlesource.com/open-dice/+/42ae7760023/src/android.c#212
+[DiceAndroidHandoverMainFlow]: https://pigweed.googlesource.com/open-dice/+/42ae7760023/src/android.c#221
[CDDL]: https://datatracker.ietf.org/doc/rfc8610
[dice-mode]: https://pigweed.googlesource.com/open-dice/+/refs/heads/main/docs/specification.md#Mode-Value-Details
[dice-dt]: https://www.kernel.org/doc/Documentation/devicetree/bindings/reserved-memory/google%2Copen-dice.yaml
@@ -383,7 +384,7 @@
After verifying the guest kernel, pvmfw boots it using the Linux ABI described
above. It uses the device tree to pass the following:
-- a reserved memory node containing the produced BCC:
+- a reserved memory node containing the produced DICE chain:
```
/ {
@@ -462,15 +463,15 @@
above must be replicated to produce a single file containing the pvmfw binary
and its configuration data.
-As a quick prototyping solution, a valid BCC (such as the [bcc.dat] test file)
-can be appended to the `pvmfw.bin` image with `pvmfw-tool`.
+As a quick prototyping solution, a valid DICE chain (such as this [test
+file][bcc.dat]) can be appended to the `pvmfw.bin` image with `pvmfw-tool`.
```shell
m pvmfw-tool pvmfw_bin
PVMFW_BIN=${ANDROID_PRODUCT_OUT}/system/etc/pvmfw.bin
-BCC_DAT=${ANDROID_BUILD_TOP}/packages/modules/Virtualization/tests/pvmfw/assets/bcc.dat
+DICE=${ANDROID_BUILD_TOP}/packages/modules/Virtualization/tests/pvmfw/assets/bcc.dat
-pvmfw-tool custom_pvmfw ${PVMFW_BIN} ${BCC_DAT}
+pvmfw-tool custom_pvmfw ${PVMFW_BIN} ${DICE}
```
The result can then be pushed to the device. Pointing the system property
diff --git a/pvmfw/avb/Android.bp b/pvmfw/avb/Android.bp
index 6101a0c..558152d 100644
--- a/pvmfw/avb/Android.bp
+++ b/pvmfw/avb/Android.bp
@@ -25,7 +25,7 @@
name: "libpvmfw_avb.integration_test",
crate_name: "pvmfw_avb_test",
defaults: ["avf_build_flags_rust"],
- srcs: ["tests/*.rs"],
+ srcs: ["tests/api_test.rs"],
test_suites: ["general-tests"],
data: [
":avb_testkey_rsa2048_pub_bin",
diff --git a/pvmfw/src/device_assignment.rs b/pvmfw/src/device_assignment.rs
index c732822..c3ccf96 100644
--- a/pvmfw/src/device_assignment.rs
+++ b/pvmfw/src/device_assignment.rs
@@ -27,6 +27,7 @@
use core::ffi::CStr;
use core::iter::Iterator;
use core::mem;
+use core::ops::Range;
use hyp::DeviceAssigningHypervisor;
use libfdt::{Fdt, FdtError, FdtNode, Phandle, Reg};
use log::error;
@@ -55,8 +56,10 @@
InvalidSymbols,
/// Malformed <reg>. Can't parse.
MalformedReg,
- /// Invalid <reg>. Failed to validate with HVC.
- InvalidReg,
+ /// Invalid physical <reg> of assigned device.
+ InvalidPhysReg(u64, u64),
+ /// Invalid virtual <reg> of assigned device.
+ InvalidReg(u64, u64),
/// Invalid <interrupts>
InvalidInterrupts,
/// Malformed <iommus>
@@ -104,7 +107,12 @@
"Invalid property in /__symbols__. Must point to valid assignable device node."
),
Self::MalformedReg => write!(f, "Malformed <reg>. Can't parse"),
- Self::InvalidReg => write!(f, "Invalid <reg>. Failed to validate with hypervisor"),
+ Self::InvalidReg(addr, size) => {
+ write!(f, "Invalid guest MMIO region (addr: {addr:#x}, size: {size:#x})")
+ }
+ Self::InvalidPhysReg(addr, size) => {
+ write!(f, "Invalid physical MMIO region (addr: {addr:#x}, size: {size:#x})")
+ }
Self::InvalidInterrupts => write!(f, "Invalid <interrupts>"),
Self::MalformedIommus => write!(f, "Malformed <iommus>. Can't parse."),
Self::InvalidIommus => {
@@ -412,6 +420,12 @@
size: u64,
}
+impl DeviceReg {
+ pub fn overlaps(&self, range: &Range<u64>) -> bool {
+ self.addr < range.end && range.start < self.addr.checked_add(self.size).unwrap()
+ }
+}
+
impl TryFrom<Reg<u64>> for DeviceReg {
type Error = DeviceAssignmentError;
@@ -521,21 +535,35 @@
physical_device_reg: &[DeviceReg],
hypervisor: &dyn DeviceAssigningHypervisor,
) -> Result<()> {
- if device_reg.len() != physical_device_reg.len() {
- return Err(DeviceAssignmentError::InvalidReg);
- }
+ let mut virt_regs = device_reg.iter();
+ let mut phys_regs = physical_device_reg.iter();
+ // TODO(b/308694211): Move this constant to vmbase::layout once vmbase is std-compatible.
+ const PVMFW_RANGE: Range<u64> = 0x7fc0_0000..0x8000_0000;
// PV reg and physical reg should have 1:1 match in order.
- for (reg, phys_reg) in device_reg.iter().zip(physical_device_reg.iter()) {
+ for (reg, phys_reg) in virt_regs.by_ref().zip(phys_regs.by_ref()) {
+ if reg.overlaps(&PVMFW_RANGE) {
+ return Err(DeviceAssignmentError::InvalidReg(reg.addr, reg.size));
+ }
+ // If this call returns successfully, hyp has mapped the MMIO region at `reg`.
let addr = hypervisor.get_phys_mmio_token(reg.addr, reg.size).map_err(|e| {
- error!("Failed to validate device <reg>, error={e:?}, reg={reg:x?}");
- DeviceAssignmentError::InvalidReg
+ error!("Hypervisor error while requesting MMIO token: {e}");
+ DeviceAssignmentError::InvalidReg(reg.addr, reg.size)
})?;
- // Only check address because hypervisor guaranatees size match when success.
+ // Only check address because hypervisor guarantees size match when success.
if phys_reg.addr != addr {
- error!("Failed to validate device <reg>. No matching phys reg for reg={reg:x?}");
- return Err(DeviceAssignmentError::InvalidReg);
+ error!("Assigned device {reg:x?} has unexpected physical address");
+ return Err(DeviceAssignmentError::InvalidPhysReg(addr, reg.size));
}
}
+
+ if let Some(DeviceReg { addr, size }) = virt_regs.next() {
+ return Err(DeviceAssignmentError::InvalidReg(*addr, *size));
+ }
+
+ if let Some(DeviceReg { addr, size }) = phys_regs.next() {
+ return Err(DeviceAssignmentError::InvalidPhysReg(*addr, *size));
+ }
+
Ok(())
}
@@ -592,11 +620,11 @@
// So we need to mark what's matched or not.
let mut physical_device_iommu = physical_device_iommu.to_vec();
for (pviommu, vsid) in iommus {
- let (id, sid) = hypervisor.get_phys_iommu_token(pviommu.id.into(), vsid.0.into())
- .map_err(|e| {
- error!("Failed to validate device <iommus>, error={e:?}, pviommu={pviommu:?}, vsid={vsid:?}");
- DeviceAssignmentError::InvalidIommus
- })?;
+ let (id, sid) =
+ hypervisor.get_phys_iommu_token(pviommu.id.into(), vsid.0.into()).map_err(|e| {
+ error!("Hypervisor error while requesting IOMMU token ({pviommu:?}, {vsid:?}): {e}");
+ DeviceAssignmentError::InvalidIommus
+ })?;
let pos = physical_device_iommu
.iter()
@@ -842,6 +870,7 @@
const FDT_WITHOUT_IOMMUS_FILE_PATH: &str = "test_pvmfw_devices_without_iommus.dtb";
const FDT_WITHOUT_DEVICE_FILE_PATH: &str = "test_pvmfw_devices_without_device.dtb";
const FDT_FILE_PATH: &str = "test_pvmfw_devices_with_rng.dtb";
+ const FDT_WITH_DEVICE_OVERLAPPING_PVMFW: &str = "test_pvmfw_devices_overlapping_pvmfw.dtb";
const FDT_WITH_MULTIPLE_DEVICES_IOMMUS_FILE_PATH: &str =
"test_pvmfw_devices_with_multiple_devices_iommus.dtb";
const FDT_WITH_IOMMU_SHARING: &str = "test_pvmfw_devices_with_iommu_sharing.dtb";
@@ -1308,7 +1337,7 @@
};
let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
- assert_eq!(device_info, Err(DeviceAssignmentError::InvalidReg));
+ assert_eq!(device_info, Err(DeviceAssignmentError::InvalidReg(0x9, 0xFF)));
}
#[test]
@@ -1324,7 +1353,7 @@
};
let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
- assert_eq!(device_info, Err(DeviceAssignmentError::InvalidReg));
+ assert_eq!(device_info, Err(DeviceAssignmentError::InvalidPhysReg(0xF10000, 0x1000)));
}
#[test]
@@ -1392,6 +1421,22 @@
}
#[test]
+ fn device_info_overlaps_pvmfw() {
+ let mut fdt_data = fs::read(FDT_WITH_DEVICE_OVERLAPPING_PVMFW).unwrap();
+ let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
+ let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
+ let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
+
+ let hypervisor = MockHypervisor {
+ mmio_tokens: [((0x7fee0000, 0x1000), 0xF00000)].into(),
+ iommu_tokens: [((0xFF, 0xF), (0x40000, 0x4))].into(),
+ };
+ let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
+
+ assert_eq!(device_info, Err(DeviceAssignmentError::InvalidReg(0x7fee0000, 0x1000)));
+ }
+
+ #[test]
fn device_assignment_clean() {
let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
diff --git a/pvmfw/testdata/test_crosvm_dt_base.dtsi b/pvmfw/testdata/test_crosvm_dt_base.dtsi
index 0c1a311..10d7e6d 100644
--- a/pvmfw/testdata/test_crosvm_dt_base.dtsi
+++ b/pvmfw/testdata/test_crosvm_dt_base.dtsi
@@ -1,5 +1,4 @@
/dts-v1/;
-/plugin/;
// This is generated manually by removing unassigned pvIOMMU nodes
// from patched platform.dts.
diff --git a/pvmfw/testdata/test_pvmfw_devices_overlapping_pvmfw.dts b/pvmfw/testdata/test_pvmfw_devices_overlapping_pvmfw.dts
new file mode 100644
index 0000000..2743dd8
--- /dev/null
+++ b/pvmfw/testdata/test_pvmfw_devices_overlapping_pvmfw.dts
@@ -0,0 +1,16 @@
+/dts-v1/;
+
+/include/ "test_crosvm_dt_base.dtsi"
+
+/ {
+ light {
+ reg = <0x0 0x7fee0000 0x0 0x1000>;
+ iommus = <&pviommu_0 0xF>;
+ };
+
+ pviommu_0: pviommu0 {
+ compatible = "pkvm,pviommu";
+ id = <0xFF>;
+ #iommu-cells = <1>;
+ };
+};
diff --git a/pvmfw/testdata/test_pvmfw_devices_vm_dtbo_without_symbols.dts b/pvmfw/testdata/test_pvmfw_devices_vm_dtbo_without_symbols.dts
index 20a8c1b..495a0eb 100644
--- a/pvmfw/testdata/test_pvmfw_devices_vm_dtbo_without_symbols.dts
+++ b/pvmfw/testdata/test_pvmfw_devices_vm_dtbo_without_symbols.dts
@@ -1,4 +1,5 @@
/dts-v1/;
+// /plugin/ omitted as this DTBO has been written by hand as a DTB in this DTS.
/ {
host {
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_duplicated_pviommus.dts b/pvmfw/testdata/test_pvmfw_devices_with_duplicated_pviommus.dts
index 4ebf034..5646c7f 100644
--- a/pvmfw/testdata/test_pvmfw_devices_with_duplicated_pviommus.dts
+++ b/pvmfw/testdata/test_pvmfw_devices_with_duplicated_pviommus.dts
@@ -1,5 +1,4 @@
/dts-v1/;
-/plugin/;
/include/ "test_crosvm_dt_base.dtsi"
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_iommu_id_conflict.dts b/pvmfw/testdata/test_pvmfw_devices_with_iommu_id_conflict.dts
index a9e30be..04052fa 100644
--- a/pvmfw/testdata/test_pvmfw_devices_with_iommu_id_conflict.dts
+++ b/pvmfw/testdata/test_pvmfw_devices_with_iommu_id_conflict.dts
@@ -1,5 +1,4 @@
/dts-v1/;
-/plugin/;
/include/ "test_crosvm_dt_base.dtsi"
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_iommu_sharing.dts b/pvmfw/testdata/test_pvmfw_devices_with_iommu_sharing.dts
index 2470725..32e5610 100644
--- a/pvmfw/testdata/test_pvmfw_devices_with_iommu_sharing.dts
+++ b/pvmfw/testdata/test_pvmfw_devices_with_iommu_sharing.dts
@@ -1,5 +1,4 @@
/dts-v1/;
-/plugin/;
/include/ "test_crosvm_dt_base.dtsi"
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_multiple_devices_iommus.dts b/pvmfw/testdata/test_pvmfw_devices_with_multiple_devices_iommus.dts
index 3aaafdd..3698c1d 100644
--- a/pvmfw/testdata/test_pvmfw_devices_with_multiple_devices_iommus.dts
+++ b/pvmfw/testdata/test_pvmfw_devices_with_multiple_devices_iommus.dts
@@ -1,5 +1,4 @@
/dts-v1/;
-/plugin/;
/include/ "test_crosvm_dt_base.dtsi"
/ {
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_multiple_reg_iommus.dts b/pvmfw/testdata/test_pvmfw_devices_with_multiple_reg_iommus.dts
index 0676aa3..94fe18e 100644
--- a/pvmfw/testdata/test_pvmfw_devices_with_multiple_reg_iommus.dts
+++ b/pvmfw/testdata/test_pvmfw_devices_with_multiple_reg_iommus.dts
@@ -1,5 +1,4 @@
/dts-v1/;
-/plugin/;
/include/ "test_crosvm_dt_base.dtsi"
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_rng.dts b/pvmfw/testdata/test_pvmfw_devices_with_rng.dts
index a987098..429771c 100644
--- a/pvmfw/testdata/test_pvmfw_devices_with_rng.dts
+++ b/pvmfw/testdata/test_pvmfw_devices_with_rng.dts
@@ -1,5 +1,4 @@
/dts-v1/;
-/plugin/;
/include/ "test_crosvm_dt_base.dtsi"
diff --git a/pvmfw/testdata/test_pvmfw_devices_without_device.dts b/pvmfw/testdata/test_pvmfw_devices_without_device.dts
index ee0be3a..05daaa7 100644
--- a/pvmfw/testdata/test_pvmfw_devices_without_device.dts
+++ b/pvmfw/testdata/test_pvmfw_devices_without_device.dts
@@ -1,5 +1,4 @@
/dts-v1/;
-/plugin/;
/include/ "test_crosvm_dt_base.dtsi"
diff --git a/pvmfw/testdata/test_pvmfw_devices_without_iommus.dts b/pvmfw/testdata/test_pvmfw_devices_without_iommus.dts
index 1a12c87..96fb073 100644
--- a/pvmfw/testdata/test_pvmfw_devices_without_iommus.dts
+++ b/pvmfw/testdata/test_pvmfw_devices_without_iommus.dts
@@ -1,5 +1,4 @@
/dts-v1/;
-/plugin/;
/include/ "test_crosvm_dt_base.dtsi"
diff --git a/service_vm/comm/Android.bp b/service_vm/comm/Android.bp
index bf923a4..15b4ef8 100644
--- a/service_vm/comm/Android.bp
+++ b/service_vm/comm/Android.bp
@@ -50,7 +50,7 @@
rust_defaults {
name: "libservice_vm_comm_test_defaults",
crate_name: "diced_open_dice_test",
- srcs: ["tests/*.rs"],
+ srcs: ["tests/api_test.rs"],
test_suites: ["general-tests"],
prefer_rlib: true,
rustlibs: [
diff --git a/service_vm/test_apk/Android.bp b/service_vm/test_apk/Android.bp
index 8f5fb41..de731f6 100644
--- a/service_vm/test_apk/Android.bp
+++ b/service_vm/test_apk/Android.bp
@@ -2,12 +2,11 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
-android_test {
- name: "VmAttestationTestApp",
+java_defaults {
+ name: "vm_attestation_testapp_defaults",
test_suites: [
"general-tests",
],
- srcs: ["src/java/**/*.java"],
static_libs: [
"MicrodroidDeviceTestHelper",
"androidx.test.runner",
@@ -19,7 +18,12 @@
jni_uses_platform_apis: true,
use_embedded_native_libs: true,
sdk_version: "test_current",
- compile_multilib: "first",
+}
+
+android_test {
+ name: "VmAttestationTestApp",
+ srcs: ["src/java/com/android/virt/vm_attestation/testapp/*.java"],
+ defaults: ["vm_attestation_testapp_defaults"],
}
rust_defaults {
@@ -41,4 +45,21 @@
rust_ffi {
name: "libvm_attestation_test_payload",
defaults: ["vm_attestation_test_payload_defaults"],
+ visibility: [":__subpackages__"],
+}
+
+android_test {
+ name: "AvfRkpdVmAttestationTestApp",
+ srcs: ["src/java/com/android/virt/rkpd/vm_attestation/testapp/*.java"],
+ defaults: ["vm_attestation_testapp_defaults"],
+ manifest: "AndroidManifest.rkpd.xml",
+ test_config: "AndroidTest.rkpd.xml",
+ static_libs: [
+ "RkpdAppTestUtil",
+ "androidx.work_work-testing",
+ ],
+ instrumentation_for: "rkpdapp",
+ // This app is a variation of rkpdapp, with additional permissions to run
+ // a VM. It is defined in packages/modules/RemoteKeyProvisioning.
+ data: [":avf-rkpdapp"],
}
diff --git a/service_vm/test_apk/AndroidManifest.rkpd.xml b/service_vm/test_apk/AndroidManifest.rkpd.xml
new file mode 100644
index 0000000..6ecc5a9
--- /dev/null
+++ b/service_vm/test_apk/AndroidManifest.rkpd.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.virt.rkpd.vm_attestation.testapp">
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.rkpdapp"
+ android:label="AVF rkpd app integration tests" />
+</manifest>
diff --git a/service_vm/test_apk/AndroidTest.rkpd.xml b/service_vm/test_apk/AndroidTest.rkpd.xml
new file mode 100644
index 0000000..39eca32
--- /dev/null
+++ b/service_vm/test_apk/AndroidTest.rkpd.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 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.
+-->
+<configuration description="VM attestation integration tests with the rkpd app.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-instrumentation" />
+
+ <!-- Need to disable SELinux policy to allow com.android.rkpdapp to run a VM. -->
+ <target_preparer class="com.android.tradefed.targetprep.DisableSELinuxTargetPreparer"/>
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="test-file-name" value="AvfRkpdVmAttestationTestApp.apk" />
+ <option name="test-file-name" value="avf-rkpdapp.apk" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.virt.rkpd.vm_attestation.testapp" />
+ </test>
+
+ <!-- Only run if RKPD mainline module is installed -->
+ <object type="module_controller"
+ class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+ <option name="enable" value="true" />
+ <option name="mainline-module-package-name" value="com.android.rkpd" />
+ </object>
+</configuration>
diff --git a/service_vm/test_apk/aidl/com/android/virt/vm_attestation/testservice/IAttestationService.aidl b/service_vm/test_apk/aidl/com/android/virt/vm_attestation/testservice/IAttestationService.aidl
index 94a7b8d..34c8549 100644
--- a/service_vm/test_apk/aidl/com/android/virt/vm_attestation/testservice/IAttestationService.aidl
+++ b/service_vm/test_apk/aidl/com/android/virt/vm_attestation/testservice/IAttestationService.aidl
@@ -21,7 +21,31 @@
const int PORT = 5679;
/**
- * Requests attestation for testing.
+ * The result of signing a message with the attested key.
+ */
+ parcelable SigningResult {
+ /** The DER-encoded ECDSA signature of the message. */
+ byte[] signature;
+
+ /** The DER-encoded attestation X509 certificate chain. */
+ byte[] certificateChain;
+ }
+
+ /**
+ * Requests attestation with {@link AVmPayload_requestAttestation} API and signs the
+ * given message with the attested key.
+ *
+ * The remotely provisioned keys are retrieved from RKPD and are provisioned from the
+ * real RKP server.
+ *
+ * @param challenge the challenge to include in the attestation output.
+ * @param message the message to sign.
+ * @return the result of signing the message with the attested key.
+ */
+ SigningResult signWithAttestationKey(in byte[] challenge, in byte[] message);
+
+ /**
+ * Requests attestation for testing with {@link AVmPayload_requestAttestationForTesting} API.
*
* A fake key pair should be provisioned with the call to
* {@link VirtualMachine#enableTestAttestation()} before calling this method.
diff --git a/service_vm/test_apk/src/java/com/android/virt/rkpd/vm_attestation/testapp/RkpdVmAttestationTest.java b/service_vm/test_apk/src/java/com/android/virt/rkpd/vm_attestation/testapp/RkpdVmAttestationTest.java
new file mode 100644
index 0000000..e7061e1
--- /dev/null
+++ b/service_vm/test_apk/src/java/com/android/virt/rkpd/vm_attestation/testapp/RkpdVmAttestationTest.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2024 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.virt.rkpd.vm_attestation.testapp;
+
+import static android.system.virtualmachine.VirtualMachineConfig.DEBUG_LEVEL_FULL;
+
+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 android.content.Context;
+import android.hardware.security.keymint.IRemotelyProvisionedComponent;
+import android.os.SystemProperties;
+import android.system.virtualmachine.VirtualMachine;
+import android.system.virtualmachine.VirtualMachineConfig;
+
+import androidx.work.ListenableWorker;
+import androidx.work.testing.TestWorkerBuilder;
+
+import com.android.microdroid.test.device.MicrodroidDeviceTestBase;
+import com.android.rkpdapp.database.ProvisionedKeyDao;
+import com.android.rkpdapp.database.RkpdDatabase;
+import com.android.rkpdapp.interfaces.ServerInterface;
+import com.android.rkpdapp.interfaces.ServiceManagerInterface;
+import com.android.rkpdapp.interfaces.SystemInterface;
+import com.android.rkpdapp.provisioner.PeriodicProvisioner;
+import com.android.rkpdapp.testutil.SystemInterfaceSelector;
+import com.android.rkpdapp.utils.Settings;
+import com.android.rkpdapp.utils.X509Utils;
+import com.android.virt.vm_attestation.testservice.IAttestationService;
+import com.android.virt.vm_attestation.testservice.IAttestationService.SigningResult;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.security.Signature;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executors;
+
+/**
+ * End-to-end test for the pVM remote attestation.
+ *
+ * <p>The test checks the two major steps of the pVM remote attestation:
+ *
+ * <p>1. Key provisioning: The test provisions AVF keys from the RKP server and verifies that the
+ * keys are for AVF.
+ *
+ * <p>2. VM attestation: The test creates a VM with a payload binary that requests to attest the VM,
+ * and then signs a message with the attestation key.
+ *
+ * <p>To run this test, you need to:
+ *
+ * <p>- Have an arm64 device supporting protected VMs.
+ *
+ * <p>- Have a stable network connection on the device.
+ *
+ * <p>- Have the RKP server hostname configured in the device. If not, you can set it using: $ adb
+ * shell setprop remote_provisioning.hostname remoteprovisioning.googleapis.com
+ */
+@RunWith(Parameterized.class)
+public class RkpdVmAttestationTest extends MicrodroidDeviceTestBase {
+ private static final String TAG = "RkpdVmAttestationTest";
+ private static final String AVF_ATTESTATION_EXTENSION_OID = "1.3.6.1.4.1.11129.2.1.29.1";
+ private static final String SERVICE_NAME = IRemotelyProvisionedComponent.DESCRIPTOR + "/avf";
+ private static final String VM_PAYLOAD_PATH = "libvm_attestation_test_payload.so";
+ private static final String MESSAGE = "Hello RKP from AVF!";
+ private static final String TEST_APP_PACKAGE_NAME =
+ "com.android.virt.rkpd.vm_attestation.testapp";
+
+ private ProvisionedKeyDao mKeyDao;
+ private PeriodicProvisioner mProvisioner;
+
+ @Parameterized.Parameter(0)
+ public String mGki;
+
+ @Parameterized.Parameters(name = "gki={0}")
+ public static Collection<Object[]> params() {
+ List<Object[]> ret = new ArrayList<>();
+ ret.add(new Object[] {null /* use microdroid kernel */});
+ for (String gki : SUPPORTED_GKI_VERSIONS) {
+ ret.add(new Object[] {gki});
+ }
+ return ret;
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ assume().withMessage("The RKP server hostname is not configured -- assume RKP disabled.")
+ .that(SystemProperties.get("remote_provisioning.hostname"))
+ .isNotEmpty();
+ assume().withMessage("RKP Integration tests rely on network availability.")
+ .that(ServerInterface.isNetworkConnected(getContext()))
+ .isTrue();
+ // TODO(b/329652894): Assume that pVM remote attestation feature is supported.
+
+ prepareTestSetup(true /* protectedVm */, mGki);
+
+ Settings.clearPreferences(getContext());
+ mKeyDao = RkpdDatabase.getDatabase(getContext()).provisionedKeyDao();
+ mKeyDao.deleteAllKeys();
+
+ mProvisioner =
+ TestWorkerBuilder.from(
+ getContext(),
+ PeriodicProvisioner.class,
+ Executors.newSingleThreadExecutor())
+ .build();
+
+ SystemInterface systemInterface =
+ SystemInterfaceSelector.getSystemInterfaceForServiceName(SERVICE_NAME);
+ ServiceManagerInterface.setInstances(new SystemInterface[] {systemInterface});
+
+ setMaxPerformanceTaskProfile();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ ServiceManagerInterface.setInstances(null);
+ if (mKeyDao != null) {
+ mKeyDao.deleteAllKeys();
+ }
+ Settings.clearPreferences(getContext());
+ }
+
+ @Test
+ public void usingProvisionedKeyForVmAttestationSucceeds() throws Exception {
+ // Provision keys.
+ assertThat(mProvisioner.doWork()).isEqualTo(ListenableWorker.Result.success());
+ assertThat(mKeyDao.getTotalUnassignedKeysForIrpc(SERVICE_NAME)).isGreaterThan(0);
+
+ // Arrange.
+ Context ctx = getContext();
+ Context otherAppCtx = ctx.createPackageContext(TEST_APP_PACKAGE_NAME, 0);
+ VirtualMachineConfig config =
+ new VirtualMachineConfig.Builder(otherAppCtx)
+ .setProtectedVm(true)
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .setPayloadBinaryName(VM_PAYLOAD_PATH)
+ .setVmOutputCaptured(true)
+ .build();
+ VirtualMachine vm = forceCreateNewVirtualMachine("attestation_with_rkpd_client", config);
+ byte[] challenge = new byte[32];
+ Arrays.fill(challenge, (byte) 0xab);
+
+ // Act.
+ CompletableFuture<Exception> exception = new CompletableFuture<>();
+ CompletableFuture<Boolean> payloadReady = new CompletableFuture<>();
+ CompletableFuture<SigningResult> signingResultFuture = new CompletableFuture<>();
+ VmEventListener listener =
+ new VmEventListener() {
+ @Override
+ public void onPayloadReady(VirtualMachine vm) {
+ payloadReady.complete(true);
+ try {
+ IAttestationService service =
+ IAttestationService.Stub.asInterface(
+ vm.connectToVsockServer(IAttestationService.PORT));
+ signingResultFuture.complete(
+ service.signWithAttestationKey(challenge, MESSAGE.getBytes()));
+ } catch (Exception e) {
+ exception.complete(e);
+ } finally {
+ forceStop(vm);
+ }
+ }
+ };
+ listener.runToFinish(TAG, vm);
+
+ // Assert.
+ assertThat(payloadReady.getNow(false)).isTrue();
+ assertThat(exception.getNow(null)).isNull();
+ SigningResult signingResult = signingResultFuture.getNow(null);
+ assertThat(signingResult).isNotNull();
+
+ // Parsing the certificate chain successfully indicates that the certificate
+ // chain is valid, that each certificate is signed by the next one and the last
+ // one is self-signed.
+ X509Certificate[] certs = X509Utils.formatX509Certs(signingResult.certificateChain);
+ assertThat(certs.length).isGreaterThan(2);
+ assertWithMessage("The first certificate should be generated in the RKP VM")
+ .that(certs[0].getSubjectX500Principal().getName())
+ .isEqualTo("CN=Android Protected Virtual Machine Key");
+ checkAvfAttestationExtension(certs[0], challenge);
+ assertWithMessage("The second certificate should contain AVF in the subject")
+ .that(certs[1].getSubjectX500Principal().getName())
+ .contains("O=AVF");
+
+ // Verify the signature using the public key from the leaf certificate generated
+ // in the RKP VM.
+ Signature sig = Signature.getInstance("SHA256withECDSA");
+ sig.initVerify(certs[0].getPublicKey());
+ sig.update(MESSAGE.getBytes());
+ assertThat(sig.verify(signingResult.signature)).isTrue();
+ }
+
+ private void checkAvfAttestationExtension(X509Certificate cert, byte[] challenge) {
+ byte[] extension = cert.getExtensionValue(AVF_ATTESTATION_EXTENSION_OID);
+ assertThat(extension).isNotNull();
+ // TODO(b/325610326): Use bouncycastle to parse the extension and check other fields.
+ assertWithMessage("The extension should contain the challenge")
+ .that(containsSubarray(extension, challenge))
+ .isTrue();
+ }
+
+ private boolean containsSubarray(byte[] array, byte[] subarray) {
+ for (int i = 0; i < array.length - subarray.length + 1; i++) {
+ boolean found = true;
+ for (int j = 0; j < subarray.length; j++) {
+ if (array[i + j] != subarray[j]) {
+ found = false;
+ break;
+ }
+ }
+ if (found) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/service_vm/test_apk/src/native/main.rs b/service_vm/test_apk/src/native/main.rs
index 199b45c..a04fb1f 100644
--- a/service_vm/test_apk/src/native/main.rs
+++ b/service_vm/test_apk/src/native/main.rs
@@ -18,7 +18,7 @@
use avflog::LogResult;
use com_android_virt_vm_attestation_testservice::{
aidl::com::android::virt::vm_attestation::testservice::IAttestationService::{
- BnAttestationService, IAttestationService, PORT,
+ BnAttestationService, IAttestationService, SigningResult::SigningResult, PORT,
},
binder::{self, unstable_api::AsNative, BinderFeatures, Interface, IntoBinderResult, Strong},
};
@@ -34,7 +34,7 @@
AIBinder, AVmAttestationResult, AVmAttestationResult_free,
AVmAttestationResult_getCertificateAt, AVmAttestationResult_getCertificateCount,
AVmAttestationResult_getPrivateKey, AVmAttestationResult_sign, AVmAttestationStatus,
- AVmAttestationStatus_toString, AVmPayload_notifyPayloadReady,
+ AVmAttestationStatus_toString, AVmPayload_notifyPayloadReady, AVmPayload_requestAttestation,
AVmPayload_requestAttestationForTesting, AVmPayload_runVsockRpcServer,
};
@@ -89,13 +89,8 @@
impl IAttestationService for AttestationService {
fn requestAttestationForTesting(&self) -> binder::Result<()> {
- // The data below is only a placeholder generated randomly with urandom
- let challenge = &[
- 0x6c, 0xad, 0x52, 0x50, 0x15, 0xe7, 0xf4, 0x1d, 0xa5, 0x60, 0x7e, 0xd2, 0x7d, 0xf1,
- 0x51, 0x67, 0xc3, 0x3e, 0x73, 0x9b, 0x30, 0xbd, 0x04, 0x20, 0x2e, 0xde, 0x3b, 0x1d,
- 0xc8, 0x07, 0x11, 0x7b,
- ];
- let res = AttestationResult::request_attestation(challenge)
+ const CHALLENGE: &[u8] = &[0xaa; 32];
+ let res = AttestationResult::request_attestation_for_testing(CHALLENGE)
.map_err(|e| anyhow!("Unexpected status: {:?}", status_to_cstr(e)))
.with_log()
.or_service_specific_exception(-1)?;
@@ -103,6 +98,21 @@
Ok(())
}
+ fn signWithAttestationKey(
+ &self,
+ challenge: &[u8],
+ message: &[u8],
+ ) -> binder::Result<SigningResult> {
+ let res = AttestationResult::request_attestation(challenge)
+ .map_err(|e| anyhow!("Unexpected status: {:?}", status_to_cstr(e)))
+ .with_log()
+ .or_service_specific_exception(-1)?;
+ let certificate_chain =
+ res.certificate_chain().with_log().or_service_specific_exception(-1)?;
+ let signature = res.sign(message).with_log().or_service_specific_exception(-1)?;
+ Ok(SigningResult { certificateChain: certificate_chain, signature })
+ }
+
fn validateAttestationResult(&self) -> binder::Result<()> {
// TODO(b/191073073): Returns the attestation result to the host for validation.
self.res.lock().unwrap().as_ref().unwrap().log().or_service_specific_exception(-1)
@@ -116,7 +126,9 @@
unsafe impl Send for AttestationResult {}
impl AttestationResult {
- fn request_attestation(challenge: &[u8]) -> result::Result<Self, AVmAttestationStatus> {
+ fn request_attestation_for_testing(
+ challenge: &[u8],
+ ) -> result::Result<Self, AVmAttestationStatus> {
let mut res: *mut AVmAttestationResult = ptr::null_mut();
// SAFETY: It is safe as we only read the challenge within its bounds and the
// function does not retain any reference to it.
@@ -136,11 +148,31 @@
}
}
- fn certificate_chain(&self) -> Result<Vec<Box<[u8]>>> {
+ fn request_attestation(challenge: &[u8]) -> result::Result<Self, AVmAttestationStatus> {
+ let mut res: *mut AVmAttestationResult = ptr::null_mut();
+ // SAFETY: It is safe as we only read the challenge within its bounds and the
+ // function does not retain any reference to it.
+ let status = unsafe {
+ AVmPayload_requestAttestation(
+ challenge.as_ptr() as *const c_void,
+ challenge.len(),
+ &mut res,
+ )
+ };
+ if status == AVmAttestationStatus::ATTESTATION_OK {
+ info!("Attestation succeeds. Status: {:?}", status_to_cstr(status));
+ let res = NonNull::new(res).expect("The attestation result is null");
+ Ok(Self(res))
+ } else {
+ Err(status)
+ }
+ }
+
+ fn certificate_chain(&self) -> Result<Vec<u8>> {
let num_certs = get_certificate_count(self.as_ref());
- let mut certs = Vec::with_capacity(num_certs);
+ let mut certs = Vec::new();
for i in 0..num_certs {
- certs.push(get_certificate_at(self.as_ref(), i)?);
+ certs.extend(get_certificate_at(self.as_ref(), i)?.iter());
}
Ok(certs)
}
@@ -149,7 +181,7 @@
get_private_key(self.as_ref())
}
- fn sign(&self, message: &[u8]) -> Result<Box<[u8]>> {
+ fn sign(&self, message: &[u8]) -> Result<Vec<u8>> {
sign_with_attested_key(self.as_ref(), message)
}
@@ -231,7 +263,7 @@
Ok(private_key.into_boxed_slice())
}
-fn sign_with_attested_key(res: &AVmAttestationResult, message: &[u8]) -> Result<Box<[u8]>> {
+fn sign_with_attested_key(res: &AVmAttestationResult, message: &[u8]) -> Result<Vec<u8>> {
// SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
// before getting freed.
let size = unsafe {
@@ -258,7 +290,7 @@
};
ensure!(size <= signature.len());
signature.truncate(size);
- Ok(signature.into_boxed_slice())
+ Ok(signature)
}
fn status_to_cstr(status: AVmAttestationStatus) -> &'static CStr {
diff --git a/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java b/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
index 2abf110..14cc0ae 100644
--- a/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
+++ b/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
@@ -31,12 +31,13 @@
import com.android.tradefed.device.TestDevice;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.RunUtil;
import org.json.JSONArray;
import java.io.File;
-import java.io.FileNotFoundException;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -143,15 +144,29 @@
}
public File findTestFile(String name) {
- return findTestFile(getBuild(), name);
- }
+ String moduleName = getInvocationContext().getConfigurationDescriptor().getModuleName();
+ IBuildInfo buildInfo = getBuild();
+ CompatibilityBuildHelper helper = new CompatibilityBuildHelper(buildInfo);
- private static File findTestFile(IBuildInfo buildInfo, String name) {
+ // We're not using helper.getTestFile here because it sometimes picks a file
+ // from a different module, which may be old and/or wrong. See b/328779049.
try {
- return (new CompatibilityBuildHelper(buildInfo)).getTestFile(name);
- } catch (FileNotFoundException e) {
- throw new AssertionError("Missing test file: " + name, e);
+ File testsDir = helper.getTestsDir().getAbsoluteFile();
+
+ for (File subDir : FileUtil.findDirsUnder(testsDir, testsDir.getParentFile())) {
+ if (!subDir.getName().equals(moduleName)) {
+ continue;
+ }
+ File testFile = FileUtil.findFile(subDir, name);
+ if (testFile != null) {
+ return testFile;
+ }
+ }
+ } catch (IOException e) {
+ throw new AssertionError(
+ "Failed to find test file " + name + " for module " + moduleName, e);
}
+ throw new AssertionError("Failed to find test file " + name + " for module " + moduleName);
}
public String getPathForPackage(String packageName)
diff --git a/tests/pvmfw/java/com/android/pvmfw/test/CustomPvmfwHostTestCaseBase.java b/tests/pvmfw/java/com/android/pvmfw/test/CustomPvmfwHostTestCaseBase.java
new file mode 100644
index 0000000..d9d425a
--- /dev/null
+++ b/tests/pvmfw/java/com/android/pvmfw/test/CustomPvmfwHostTestCaseBase.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2024 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.pvmfw.test;
+
+import static com.android.tradefed.device.TestDevice.MicrodroidBuilder;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.microdroid.test.host.MicrodroidHostTestCaseBase;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.device.TestDevice;
+import com.android.tradefed.util.FileUtil;
+
+import org.junit.After;
+import org.junit.Before;
+
+import java.io.File;
+import java.util.Objects;
+import java.util.Map;
+
+/** Base class for testing custom pvmfw */
+public class CustomPvmfwHostTestCaseBase extends MicrodroidHostTestCaseBase {
+ @NonNull public static final String PVMFW_FILE_NAME = "pvmfw_test.bin";
+ @NonNull public static final String BCC_FILE_NAME = "bcc.dat";
+ @NonNull public static final String PACKAGE_FILE_NAME = "MicrodroidTestApp.apk";
+ @NonNull public static final String PACKAGE_NAME = "com.android.microdroid.test";
+ @NonNull public static final String MICRODROID_DEBUG_FULL = "full";
+ @NonNull public static final String MICRODROID_DEBUG_NONE = "none";
+
+ @NonNull
+ public static final String MICRODROID_CONFIG_PATH = "assets/microdroid/vm_config_apex.json";
+
+ @NonNull public static final String MICRODROID_LOG_PATH = TEST_ROOT + "log.txt";
+ public static final int BOOT_COMPLETE_TIMEOUT_MS = 30000; // 30 seconds
+ public static final int BOOT_FAILURE_WAIT_TIME_MS = 10000; // 10 seconds
+ public static final int CONSOLE_OUTPUT_WAIT_MS = 5000; // 5 seconds
+
+ @NonNull public static final String CUSTOM_PVMFW_FILE_PREFIX = "pvmfw";
+ @NonNull public static final String CUSTOM_PVMFW_FILE_SUFFIX = ".bin";
+ @NonNull public static final String CUSTOM_PVMFW_IMG_PATH = TEST_ROOT + PVMFW_FILE_NAME;
+ @NonNull public static final String CUSTOM_PVMFW_IMG_PATH_PROP = "hypervisor.pvmfw.path";
+
+ @Nullable private static File mPvmfwBinFileOnHost;
+ @Nullable private static File mBccFileOnHost;
+
+ @Nullable private TestDevice mAndroidDevice;
+ @Nullable private ITestDevice mMicrodroidDevice;
+
+ @Nullable private File mCustomPvmfwFileOnHost;
+
+ @Before
+ public void setUp() throws Exception {
+ mAndroidDevice = (TestDevice) Objects.requireNonNull(getDevice());
+
+ // Check device capabilities
+ assumeDeviceIsCapable(mAndroidDevice);
+ assumeTrue(
+ "Skip if protected VMs are not supported",
+ mAndroidDevice.supportsMicrodroid(/* protectedVm= */ true));
+
+ // tradefed copies the test artifacts under /tmp when running tests,
+ // so we should *find* the artifacts with the file name.
+ mPvmfwBinFileOnHost =
+ getTestInformation().getDependencyFile(PVMFW_FILE_NAME, /* targetFirst= */ false);
+ mBccFileOnHost =
+ getTestInformation().getDependencyFile(BCC_FILE_NAME, /* targetFirst= */ false);
+
+ // Prepare for system properties for custom pvmfw.img.
+ // File will be prepared later in individual test and then pushed to device
+ // when launching with launchProtectedVmAndWaitForBootCompleted().
+ mCustomPvmfwFileOnHost =
+ FileUtil.createTempFile(CUSTOM_PVMFW_FILE_PREFIX, CUSTOM_PVMFW_FILE_SUFFIX);
+ setPropertyOrThrow(mAndroidDevice, CUSTOM_PVMFW_IMG_PATH_PROP, CUSTOM_PVMFW_IMG_PATH);
+
+ // Prepare for launching microdroid
+ mAndroidDevice.installPackage(findTestFile(PACKAGE_FILE_NAME), /* reinstall */ false);
+ prepareVirtualizationTestSetup(mAndroidDevice);
+ mMicrodroidDevice = null;
+ }
+
+ @After
+ public void shutdown() throws Exception {
+ if (!mAndroidDevice.supportsMicrodroid(/* protectedVm= */ true)) {
+ return;
+ }
+ if (mMicrodroidDevice != null) {
+ mAndroidDevice.shutdownMicrodroid(mMicrodroidDevice);
+ mMicrodroidDevice = null;
+ }
+ mAndroidDevice.uninstallPackage(PACKAGE_NAME);
+
+ // Cleanup for custom pvmfw.img
+ setPropertyOrThrow(mAndroidDevice, CUSTOM_PVMFW_IMG_PATH_PROP, "");
+ FileUtil.deleteFile(mCustomPvmfwFileOnHost);
+
+ cleanUpVirtualizationTestSetup(mAndroidDevice);
+ }
+
+ /** Returns pvmfw.bin file on host for building custom pvmfw with */
+ public File getPvmfwBinFile() {
+ return mPvmfwBinFileOnHost;
+ }
+
+ /** Returns BCC file on host for building custom pvmfw with */
+ public File getBccFile() {
+ return mBccFileOnHost;
+ }
+
+ /**
+ * Returns a custom pvmfw file.
+ *
+ * <p>This is a temporary file on host. The file should been prepared as a custom pvmfw because
+ * calling {@link #launchProtectedVmAndWaitForBootCompleted}, so virtualization manager can read
+ * the file path from sysprop and boot pVM with it.
+ */
+ public File getCustomPvmfwFile() {
+ return mCustomPvmfwFileOnHost;
+ }
+
+ /**
+ * Launches protected VM with custom pvmfw ({@link #getCustomPvmfwFile}) and wait for boot
+ * completed. Throws exception when boot failed.
+ */
+ public ITestDevice launchProtectedVmAndWaitForBootCompleted(
+ String debugLevel, long adbTimeoutMs, @NonNull Map<String, File> bootFiles)
+ throws DeviceNotAvailableException {
+ MicrodroidBuilder builder =
+ MicrodroidBuilder.fromDevicePath(
+ getPathForPackage(PACKAGE_NAME), MICRODROID_CONFIG_PATH)
+ .debugLevel(debugLevel)
+ .protectedVm(/* protectedVm= */ true)
+ .addBootFile(mCustomPvmfwFileOnHost, PVMFW_FILE_NAME)
+ .setAdbConnectTimeoutMs(adbTimeoutMs);
+ for (String name : bootFiles.keySet()) {
+ File file = bootFiles.get(name);
+ builder.addBootFile(file, name);
+ }
+
+ mMicrodroidDevice = builder.build(mAndroidDevice);
+
+ assertThat(mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT_MS)).isTrue();
+ assertThat(mMicrodroidDevice.enableAdbRoot()).isTrue();
+ return mMicrodroidDevice;
+ }
+}
diff --git a/tests/pvmfw/java/com/android/pvmfw/test/DebugPolicyHostTests.java b/tests/pvmfw/java/com/android/pvmfw/test/DebugPolicyHostTests.java
index 26f5993..803405d 100644
--- a/tests/pvmfw/java/com/android/pvmfw/test/DebugPolicyHostTests.java
+++ b/tests/pvmfw/java/com/android/pvmfw/test/DebugPolicyHostTests.java
@@ -16,28 +16,22 @@
package com.android.pvmfw.test;
-import static com.android.tradefed.device.TestDevice.MicrodroidBuilder;
-
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
-import static org.junit.Assume.assumeTrue;
import static org.junit.Assert.assertThrows;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.microdroid.test.host.CommandRunner;
-import com.android.microdroid.test.host.MicrodroidHostTestCaseBase;
import com.android.pvmfw.test.host.Pvmfw;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.DeviceRuntimeException;
import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.device.TestDevice;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.util.CommandStatus;
import com.android.tradefed.util.CommandResult;
-import com.android.tradefed.util.FileUtil;
import org.junit.After;
import org.junit.Before;
@@ -45,32 +39,13 @@
import org.junit.runner.RunWith;
import java.io.File;
-import java.util.Objects;
+import java.util.Collections;
+import java.util.Map;
import java.util.concurrent.TimeUnit;
/** Tests debug policy */
@RunWith(DeviceJUnit4ClassRunner.class)
-public class DebugPolicyHostTests extends MicrodroidHostTestCaseBase {
- @NonNull private static final String PVMFW_FILE_NAME = "pvmfw_test.bin";
- @NonNull private static final String BCC_FILE_NAME = "bcc.dat";
- @NonNull private static final String PACKAGE_FILE_NAME = "MicrodroidTestApp.apk";
- @NonNull private static final String PACKAGE_NAME = "com.android.microdroid.test";
- @NonNull private static final String MICRODROID_DEBUG_FULL = "full";
- @NonNull private static final String MICRODROID_DEBUG_NONE = "none";
-
- @NonNull
- private static final String MICRODROID_CONFIG_PATH = "assets/microdroid/vm_config_apex.json";
-
- @NonNull private static final String MICRODROID_LOG_PATH = TEST_ROOT + "log.txt";
- private static final int BOOT_COMPLETE_TIMEOUT_MS = 30000; // 30 seconds
- private static final int BOOT_FAILURE_WAIT_TIME_MS = 10000; // 10 seconds
- private static final int CONSOLE_OUTPUT_WAIT_MS = 5000; // 5 seconds
-
- @NonNull private static final String CUSTOM_PVMFW_FILE_PREFIX = "pvmfw";
- @NonNull private static final String CUSTOM_PVMFW_FILE_SUFFIX = ".bin";
- @NonNull private static final String CUSTOM_PVMFW_IMG_PATH = TEST_ROOT + PVMFW_FILE_NAME;
- @NonNull private static final String CUSTOM_PVMFW_IMG_PATH_PROP = "hypervisor.pvmfw.path";
-
+public class DebugPolicyHostTests extends CustomPvmfwHostTestCaseBase {
@NonNull private static final String CUSTOM_DEBUG_POLICY_FILE_NAME = "debug_policy.dtb";
@NonNull
@@ -98,63 +73,22 @@
@NonNull private static final String HEX_STRING_ZERO = "00000000";
@NonNull private static final String HEX_STRING_ONE = "00000001";
- @Nullable private static File mPvmfwBinFileOnHost;
- @Nullable private static File mBccFileOnHost;
-
- @Nullable private TestDevice mAndroidDevice;
- @Nullable private ITestDevice mMicrodroidDevice;
- @Nullable private File mCustomPvmfwBinFileOnHost;
@Nullable private File mCustomDebugPolicyFileOnHost;
@Before
public void setUp() throws Exception {
- mAndroidDevice = (TestDevice) Objects.requireNonNull(getDevice());
+ super.setUp();
- // Check device capabilities
- assumeDeviceIsCapable(mAndroidDevice);
- assumeTrue(
- "Skip if protected VMs are not supported",
- mAndroidDevice.supportsMicrodroid(/* protectedVm= */ true));
-
- // tradefed copies the test artfacts under /tmp when running tests,
- // so we should *find* the artifacts with the file name.
- mPvmfwBinFileOnHost =
- getTestInformation().getDependencyFile(PVMFW_FILE_NAME, /* targetFirst= */ false);
- mBccFileOnHost =
- getTestInformation().getDependencyFile(BCC_FILE_NAME, /* targetFirst= */ false);
-
- // Prepare for system properties for custom debug policy.
- // File will be prepared later in individual test by setupCustomDebugPolicy()
- // and then pushed to device when launching with launchProtectedVmAndWaitForBootCompleted()
- // or tryLaunchProtectedNonDebuggableVm().
- mCustomPvmfwBinFileOnHost =
- FileUtil.createTempFile(CUSTOM_PVMFW_FILE_PREFIX, CUSTOM_PVMFW_FILE_SUFFIX);
- setPropertyOrThrow(mAndroidDevice, CUSTOM_PVMFW_IMG_PATH_PROP, CUSTOM_PVMFW_IMG_PATH);
- setPropertyOrThrow(mAndroidDevice, CUSTOM_DEBUG_POLICY_PATH_PROP, CUSTOM_DEBUG_POLICY_PATH);
-
- // Prepare for launching microdroid
- mAndroidDevice.installPackage(findTestFile(PACKAGE_FILE_NAME), /* reinstall */ false);
- prepareVirtualizationTestSetup(mAndroidDevice);
- mMicrodroidDevice = null;
+ // Prepare system properties for custom debug policy.
+ setPropertyOrThrow(getDevice(), CUSTOM_DEBUG_POLICY_PATH_PROP, CUSTOM_DEBUG_POLICY_PATH);
}
@After
public void shutdown() throws Exception {
- if (!mAndroidDevice.supportsMicrodroid(/* protectedVm= */ true)) {
- return;
- }
- if (mMicrodroidDevice != null) {
- mAndroidDevice.shutdownMicrodroid(mMicrodroidDevice);
- mMicrodroidDevice = null;
- }
- mAndroidDevice.uninstallPackage(PACKAGE_NAME);
+ super.shutdown();
// Cleanup for custom debug policies
- setPropertyOrThrow(mAndroidDevice, CUSTOM_DEBUG_POLICY_PATH_PROP, "");
- setPropertyOrThrow(mAndroidDevice, CUSTOM_PVMFW_IMG_PATH_PROP, "");
- FileUtil.deleteFile(mCustomPvmfwBinFileOnHost);
-
- cleanUpVirtualizationTestSetup(mAndroidDevice);
+ setPropertyOrThrow(getDevice(), CUSTOM_DEBUG_POLICY_PATH_PROP, "");
}
@Test
@@ -198,43 +132,41 @@
@Test
public void testRamdumpInDebugPolicy_withDebugLevelNone_hasRamdumpArgs() throws Exception {
prepareCustomDebugPolicy("avf_debug_policy_with_ramdump.dtbo");
- mMicrodroidDevice = launchProtectedVmAndWaitForBootCompleted(MICRODROID_DEBUG_NONE);
+ ITestDevice device = launchProtectedVmAndWaitForBootCompleted(MICRODROID_DEBUG_NONE);
- assertThat(readMicrodroidFileAsString(MICRODROID_CMDLINE_PATH)).contains("crashkernel=");
- assertThat(readMicrodroidFileAsString(MICRODROID_DT_BOOTARGS_PATH))
- .contains("crashkernel=");
- assertThat(readMicrodroidFileAsHexString(MICRODROID_DT_RAMDUMP_PATH))
+ assertThat(readFileAsString(device, MICRODROID_CMDLINE_PATH)).contains("crashkernel=");
+ assertThat(readFileAsString(device, MICRODROID_DT_BOOTARGS_PATH)).contains("crashkernel=");
+ assertThat(readFileAsHexString(device, MICRODROID_DT_RAMDUMP_PATH))
.isEqualTo(HEX_STRING_ONE);
}
@Test
public void testNoRamdumpInDebugPolicy_withDebugLevelNone_noRamdumpArgs() throws Exception {
prepareCustomDebugPolicy("avf_debug_policy_without_ramdump.dtbo");
- mMicrodroidDevice = launchProtectedVmAndWaitForBootCompleted(MICRODROID_DEBUG_NONE);
+ ITestDevice device = launchProtectedVmAndWaitForBootCompleted(MICRODROID_DEBUG_NONE);
- assertThat(readMicrodroidFileAsString(MICRODROID_CMDLINE_PATH))
+ assertThat(readFileAsString(device, MICRODROID_CMDLINE_PATH))
.doesNotContain("crashkernel=");
- assertThat(readMicrodroidFileAsString(MICRODROID_DT_BOOTARGS_PATH))
+ assertThat(readFileAsString(device, MICRODROID_DT_BOOTARGS_PATH))
.doesNotContain("crashkernel=");
- assertThat(readMicrodroidFileAsHexString(MICRODROID_DT_RAMDUMP_PATH))
+ assertThat(readFileAsHexString(device, MICRODROID_DT_RAMDUMP_PATH))
.isEqualTo(HEX_STRING_ZERO);
}
@Test
public void testNoRamdumpInDebugPolicy_withDebugLevelFull_hasRamdumpArgs() throws Exception {
prepareCustomDebugPolicy("avf_debug_policy_without_ramdump.dtbo");
- mMicrodroidDevice = launchProtectedVmAndWaitForBootCompleted(MICRODROID_DEBUG_FULL);
+ ITestDevice device = launchProtectedVmAndWaitForBootCompleted(MICRODROID_DEBUG_FULL);
- assertThat(readMicrodroidFileAsString(MICRODROID_CMDLINE_PATH)).contains("crashkernel=");
- assertThat(readMicrodroidFileAsString(MICRODROID_DT_BOOTARGS_PATH))
- .contains("crashkernel=");
- assertThat(readMicrodroidFileAsHexString(MICRODROID_DT_RAMDUMP_PATH))
+ assertThat(readFileAsString(device, MICRODROID_CMDLINE_PATH)).contains("crashkernel=");
+ assertThat(readFileAsString(device, MICRODROID_DT_BOOTARGS_PATH)).contains("crashkernel=");
+ assertThat(readFileAsHexString(device, MICRODROID_DT_RAMDUMP_PATH))
.isEqualTo(HEX_STRING_ZERO);
}
private boolean isDebugPolicyEnabled(@NonNull String dtPropertyPath)
throws DeviceNotAvailableException {
- CommandRunner runner = new CommandRunner(mAndroidDevice);
+ CommandRunner runner = new CommandRunner(getDevice());
CommandResult result =
runner.runForResult("xxd", "-p", "/proc/device-tree" + dtPropertyPath);
if (result.getStatus() == CommandStatus.SUCCESS) {
@@ -244,15 +176,15 @@
}
@NonNull
- private String readMicrodroidFileAsString(@NonNull String path)
+ private String readFileAsString(@NonNull ITestDevice device, @NonNull String path)
throws DeviceNotAvailableException {
- return new CommandRunner(mMicrodroidDevice).run("cat", path);
+ return new CommandRunner(device).run("cat", path);
}
@NonNull
- private String readMicrodroidFileAsHexString(@NonNull String path)
+ private String readFileAsHexString(@NonNull ITestDevice device, @NonNull String path)
throws DeviceNotAvailableException {
- return new CommandRunner(mMicrodroidDevice).run("xxd", "-p", path);
+ return new CommandRunner(device).run("xxd", "-p", path);
}
private void prepareCustomDebugPolicy(@NonNull String debugPolicyFileName) throws Exception {
@@ -261,10 +193,10 @@
.getDependencyFile(debugPolicyFileName, /* targetFirst= */ false);
Pvmfw pvmfw =
- new Pvmfw.Builder(mPvmfwBinFileOnHost, mBccFileOnHost)
+ new Pvmfw.Builder(getPvmfwBinFile(), getBccFile())
.setDebugPolicyOverlay(mCustomDebugPolicyFileOnHost)
.build();
- pvmfw.serialize(mCustomPvmfwBinFileOnHost);
+ pvmfw.serialize(getCustomPvmfwFile());
}
private boolean hasConsoleOutput(@NonNull CommandResult result)
@@ -274,29 +206,22 @@
private boolean hasMicrodroidLogcatOutput() throws DeviceNotAvailableException {
CommandResult result =
- new CommandRunner(mAndroidDevice).runForResult("test", "-s", MICRODROID_LOG_PATH);
+ new CommandRunner(getDevice()).runForResult("test", "-s", MICRODROID_LOG_PATH);
return result.getExitCode() == 0;
}
- private ITestDevice launchProtectedVmAndWaitForBootCompleted(String debugLevel)
+ public ITestDevice launchProtectedVmAndWaitForBootCompleted(String debugLevel)
throws DeviceNotAvailableException {
return launchProtectedVmAndWaitForBootCompleted(debugLevel, BOOT_COMPLETE_TIMEOUT_MS);
}
- private ITestDevice launchProtectedVmAndWaitForBootCompleted(
+ public ITestDevice launchProtectedVmAndWaitForBootCompleted(
String debugLevel, long adbTimeoutMs) throws DeviceNotAvailableException {
- mMicrodroidDevice =
- MicrodroidBuilder.fromDevicePath(
- getPathForPackage(PACKAGE_NAME), MICRODROID_CONFIG_PATH)
- .debugLevel(debugLevel)
- .protectedVm(/* protectedVm= */ true)
- .addBootFile(mCustomPvmfwBinFileOnHost, PVMFW_FILE_NAME)
- .addBootFile(mCustomDebugPolicyFileOnHost, CUSTOM_DEBUG_POLICY_FILE_NAME)
- .setAdbConnectTimeoutMs(adbTimeoutMs)
- .build(mAndroidDevice);
- assertThat(mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT_MS)).isTrue();
- assertThat(mMicrodroidDevice.enableAdbRoot()).isTrue();
- return mMicrodroidDevice;
+ Map<String, File> bootFiles =
+ Collections.singletonMap(
+ CUSTOM_DEBUG_POLICY_FILE_NAME, mCustomDebugPolicyFileOnHost);
+
+ return launchProtectedVmAndWaitForBootCompleted(debugLevel, adbTimeoutMs, bootFiles);
}
// Try to launch protected non-debuggable VM for a while and quit.
@@ -304,10 +229,10 @@
private CommandResult tryLaunchProtectedNonDebuggableVm() throws Exception {
// Can't use MicrodroidBuilder because it expects adb connection
// but non-debuggable VM may not enable adb.
- CommandRunner runner = new CommandRunner(mAndroidDevice);
+ CommandRunner runner = new CommandRunner(getDevice());
runner.run("mkdir", "-p", TEST_ROOT);
- mAndroidDevice.pushFile(mCustomPvmfwBinFileOnHost, CUSTOM_PVMFW_IMG_PATH);
- mAndroidDevice.pushFile(mCustomDebugPolicyFileOnHost, CUSTOM_DEBUG_POLICY_PATH);
+ getDevice().pushFile(getCustomPvmfwFile(), CUSTOM_PVMFW_IMG_PATH);
+ getDevice().pushFile(mCustomDebugPolicyFileOnHost, CUSTOM_DEBUG_POLICY_PATH);
// This will fail because app wouldn't finish itself.
// But let's run the app once and get logs.
@@ -327,7 +252,11 @@
if (isFeatureEnabled("com.android.kvm.LLPVM_CHANGES")) {
command = String.join(" ", command, "--instance-id-file", TEST_ROOT + "instance_id");
}
- return mAndroidDevice.executeShellV2Command(
- command, CONSOLE_OUTPUT_WAIT_MS, TimeUnit.MILLISECONDS, /* retryAttempts= */ 0);
+ return getDevice()
+ .executeShellV2Command(
+ command,
+ CONSOLE_OUTPUT_WAIT_MS,
+ TimeUnit.MILLISECONDS,
+ /* retryAttempts= */ 0);
}
}
diff --git a/tests/pvmfw/java/com/android/pvmfw/test/PvmfwImgTest.java b/tests/pvmfw/java/com/android/pvmfw/test/PvmfwImgTest.java
index 9fbbd87..b68316d 100644
--- a/tests/pvmfw/java/com/android/pvmfw/test/PvmfwImgTest.java
+++ b/tests/pvmfw/java/com/android/pvmfw/test/PvmfwImgTest.java
@@ -16,124 +16,36 @@
package com.android.pvmfw.test;
-import static com.android.tradefed.device.TestDevice.MicrodroidBuilder;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assume.assumeTrue;
import static org.junit.Assert.assertThrows;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.microdroid.test.host.MicrodroidHostTestCaseBase;
import com.android.pvmfw.test.host.Pvmfw;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.DeviceRuntimeException;
import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.device.TestDevice;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.util.FileUtil;
-import org.junit.After;
-import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.io.File;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
-import java.util.Objects;
/** Tests pvmfw.img and pvmfw */
@RunWith(DeviceJUnit4ClassRunner.class)
-public class PvmfwImgTest extends MicrodroidHostTestCaseBase {
- @NonNull private static final String PVMFW_FILE_NAME = "pvmfw_test.bin";
- @NonNull private static final String BCC_FILE_NAME = "bcc.dat";
- @NonNull private static final String PACKAGE_FILE_NAME = "MicrodroidTestApp.apk";
- @NonNull private static final String PACKAGE_NAME = "com.android.microdroid.test";
- @NonNull private static final String MICRODROID_DEBUG_FULL = "full";
-
- @NonNull
- private static final String MICRODROID_CONFIG_PATH = "assets/microdroid/vm_config_apex.json";
-
- private static final int BOOT_COMPLETE_TIMEOUT_MS = 30000; // 30 seconds
- private static final int BOOT_FAILURE_WAIT_TIME_MS = 10000; // 10 seconds
-
- @NonNull private static final String CUSTOM_PVMFW_FILE_PREFIX = "pvmfw";
- @NonNull private static final String CUSTOM_PVMFW_FILE_SUFFIX = ".bin";
- @NonNull private static final String CUSTOM_PVMFW_IMG_PATH = TEST_ROOT + PVMFW_FILE_NAME;
- @NonNull private static final String CUSTOM_PVMFW_IMG_PATH_PROP = "hypervisor.pvmfw.path";
-
- @Nullable private static File mPvmfwBinFileOnHost;
- @Nullable private static File mBccFileOnHost;
-
- @Nullable private TestDevice mAndroidDevice;
- @Nullable private ITestDevice mMicrodroidDevice;
- @Nullable private File mCustomPvmfwBinFileOnHost;
-
- @Before
- public void setUp() throws Exception {
- mAndroidDevice = (TestDevice) Objects.requireNonNull(getDevice());
-
- // Check device capabilities
- assumeDeviceIsCapable(mAndroidDevice);
- assumeTrue(
- "Skip if protected VMs are not supported",
- mAndroidDevice.supportsMicrodroid(/* protectedVm= */ true));
-
- // tradefed copies the test artfacts under /tmp when running tests,
- // so we should *find* the artifacts with the file name.
- mPvmfwBinFileOnHost =
- getTestInformation().getDependencyFile(PVMFW_FILE_NAME, /* targetFirst= */ false);
- mBccFileOnHost =
- getTestInformation().getDependencyFile(BCC_FILE_NAME, /* targetFirst= */ false);
-
- // Prepare for system properties for custom pvmfw.img.
- // File will be prepared later in individual test and then pushed to device
- // when launching with launchProtectedVmAndWaitForBootCompleted().
- mCustomPvmfwBinFileOnHost =
- FileUtil.createTempFile(CUSTOM_PVMFW_FILE_PREFIX, CUSTOM_PVMFW_FILE_SUFFIX);
- setPropertyOrThrow(mAndroidDevice, CUSTOM_PVMFW_IMG_PATH_PROP, CUSTOM_PVMFW_IMG_PATH);
-
- // Prepare for launching microdroid
- mAndroidDevice.installPackage(findTestFile(PACKAGE_FILE_NAME), /* reinstall */ false);
- prepareVirtualizationTestSetup(mAndroidDevice);
- mMicrodroidDevice = null;
- }
-
- @After
- public void shutdown() throws Exception {
- if (!mAndroidDevice.supportsMicrodroid(/* protectedVm= */ true)) {
- return;
- }
- if (mMicrodroidDevice != null) {
- mAndroidDevice.shutdownMicrodroid(mMicrodroidDevice);
- mMicrodroidDevice = null;
- }
- mAndroidDevice.uninstallPackage(PACKAGE_NAME);
-
- // Cleanup for custom pvmfw.img
- setPropertyOrThrow(mAndroidDevice, CUSTOM_PVMFW_IMG_PATH_PROP, "");
- FileUtil.deleteFile(mCustomPvmfwBinFileOnHost);
-
- cleanUpVirtualizationTestSetup(mAndroidDevice);
- }
-
+public class PvmfwImgTest extends CustomPvmfwHostTestCaseBase {
@Test
public void testConfigVersion1_0_boots() throws Exception {
- Pvmfw pvmfw =
- new Pvmfw.Builder(mPvmfwBinFileOnHost, mBccFileOnHost).setVersion(1, 0).build();
- pvmfw.serialize(mCustomPvmfwBinFileOnHost);
+ Pvmfw pvmfw = new Pvmfw.Builder(getPvmfwBinFile(), getBccFile()).setVersion(1, 0).build();
+ pvmfw.serialize(getCustomPvmfwFile());
launchProtectedVmAndWaitForBootCompleted(BOOT_COMPLETE_TIMEOUT_MS);
}
@Test
public void testConfigVersion1_1_boots() throws Exception {
- Pvmfw pvmfw =
- new Pvmfw.Builder(mPvmfwBinFileOnHost, mBccFileOnHost).setVersion(1, 1).build();
- pvmfw.serialize(mCustomPvmfwBinFileOnHost);
+ Pvmfw pvmfw = new Pvmfw.Builder(getPvmfwBinFile(), getBccFile()).setVersion(1, 1).build();
+ pvmfw.serialize(getCustomPvmfwFile());
launchProtectedVmAndWaitForBootCompleted(BOOT_COMPLETE_TIMEOUT_MS);
}
@@ -153,7 +65,7 @@
new int[] {0xFFFF, 1},
new int[] {0xFFFF, 0xFFFF});
- Pvmfw.Builder builder = new Pvmfw.Builder(mPvmfwBinFileOnHost, mBccFileOnHost);
+ Pvmfw.Builder builder = new Pvmfw.Builder(getPvmfwBinFile(), getBccFile());
for (int[] pair : invalid_versions) {
int major = pair[0];
@@ -161,7 +73,7 @@
String version = "v" + major + "." + minor;
Pvmfw pvmfw = builder.setVersion(major, minor).build();
- pvmfw.serialize(mCustomPvmfwBinFileOnHost);
+ pvmfw.serialize(getCustomPvmfwFile());
assertThrows(
"pvmfw shouldn't boot with invalid version " + version,
@@ -170,17 +82,9 @@
}
}
- private ITestDevice launchProtectedVmAndWaitForBootCompleted(long adbTimeoutMs)
+ public ITestDevice launchProtectedVmAndWaitForBootCompleted(long adbTimeoutMs)
throws DeviceNotAvailableException {
- mMicrodroidDevice =
- MicrodroidBuilder.fromDevicePath(
- getPathForPackage(PACKAGE_NAME), MICRODROID_CONFIG_PATH)
- .debugLevel(MICRODROID_DEBUG_FULL)
- .protectedVm(true)
- .addBootFile(mCustomPvmfwBinFileOnHost, PVMFW_FILE_NAME)
- .setAdbConnectTimeoutMs(adbTimeoutMs)
- .build(mAndroidDevice);
- assertThat(mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT_MS)).isTrue();
- return mMicrodroidDevice;
+ return launchProtectedVmAndWaitForBootCompleted(
+ MICRODROID_DEBUG_FULL, adbTimeoutMs, Collections.emptyMap());
}
}
diff --git a/tests/testapk/Android.bp b/tests/testapk/Android.bp
index 2a04103..732be94 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -17,6 +17,7 @@
name: "MicrodroidTestAppsDefaults",
test_suites: [
"cts",
+ "vts",
"general-tests",
],
static_libs: [
diff --git a/tests/testapk/AndroidTest.xml b/tests/testapk/AndroidTest.xml
index 8a4c367..22cd0dc 100644
--- a/tests/testapk/AndroidTest.xml
+++ b/tests/testapk/AndroidTest.xml
@@ -15,6 +15,7 @@
-->
<configuration description="Runs Microdroid device-side tests.">
<option name="test-suite-tag" value="cts" />
+ <option name="test-suite-tag" value="vts" />
<option name="config-descriptor:metadata" key="component" value="security" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidCapabilitiesTest.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidCapabilitiesTest.java
index eb23e21..61f4cba 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidCapabilitiesTest.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidCapabilitiesTest.java
@@ -15,11 +15,16 @@
*/
package com.android.microdroid.test;
-import static com.google.common.truth.Truth.assertWithMessage;
+import static android.content.pm.PackageManager.FEATURE_VIRTUALIZATION_FRAMEWORK;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static com.google.common.truth.TruthJUnit.assume;
+
+import android.os.SystemProperties;
import android.system.virtualmachine.VirtualMachineManager;
import com.android.compatibility.common.util.CddTest;
+import com.android.compatibility.common.util.VsrTest;
import com.android.microdroid.test.device.MicrodroidDeviceTestBase;
import org.junit.Ignore;
@@ -28,16 +33,16 @@
import org.junit.runners.JUnit4;
/**
- * Test the advertised AVF capabilities include the ability to start some type of VM.
+ * Test the device's AVF capabilities.
*
* <p>Tests in MicrodroidTests run on either protected or non-protected VMs, provided they are
* supported. If neither is they are all skipped. So we need a separate test (that doesn't call
- * {@link #prepareTestSetup}) to make sure that at least one of these is available.
+ * {@link #prepareTestSetup}) when we need to run on such devices.
*/
@RunWith(JUnit4.class)
public class MicrodroidCapabilitiesTest extends MicrodroidDeviceTestBase {
@Test
- @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+ @CddTest(requirements = "9.17/C-1-6")
@Ignore("b/326092480")
public void supportForProtectedOrNonProtectedVms() {
assumeSupportedDevice();
@@ -57,4 +62,16 @@
.that(vmCapabilities)
.isNotEqualTo(0);
}
+
+ @Test
+ @VsrTest(requirements = "VSR-7.1-001.005")
+ public void avfIsRequired() {
+ int vendorApiLevel = SystemProperties.getInt("ro.vendor.api_level", 0);
+ assume().withMessage("Requirement doesn't apply due to vendor API level")
+ .that(vendorApiLevel)
+ .isAtLeast(202404);
+ boolean avfSupported =
+ getContext().getPackageManager().hasSystemFeature(FEATURE_VIRTUALIZATION_FRAMEWORK);
+ assertWithMessage("Device doesn't support AVF").that(avfSupported).isTrue();
+ }
}
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index 278365c..22bea58 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -434,11 +434,7 @@
None
};
- let debug_level = match config {
- VirtualMachineConfig::AppConfig(config) => config.debugLevel,
- _ => DebugLevel::NONE,
- };
- let debug_config = DebugConfig::new(debug_level);
+ let debug_config = DebugConfig::new(config);
let ramdump = if debug_config.is_ramdump_needed() {
Some(prepare_ramdump_file(&temporary_directory)?)
diff --git a/virtualizationmanager/src/debug_config.rs b/virtualizationmanager/src/debug_config.rs
index e2b657a..451d1c6 100644
--- a/virtualizationmanager/src/debug_config.rs
+++ b/virtualizationmanager/src/debug_config.rs
@@ -15,17 +15,17 @@
//! Functions for AVF debug policy and debug level
use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
- VirtualMachineAppConfig::DebugLevel::DebugLevel,
+ VirtualMachineAppConfig::DebugLevel::DebugLevel, VirtualMachineConfig::VirtualMachineConfig,
};
use anyhow::{anyhow, Context, Error, Result};
+use lazy_static::lazy_static;
+use libfdt::{Fdt, FdtError};
+use log::{info, warn};
+use rustutils::system_properties;
+use std::ffi::{CString, NulError};
use std::fs;
use std::io::ErrorKind;
use std::path::{Path, PathBuf};
-use std::ffi::{CString, NulError};
-use log::{warn, info};
-use rustutils::system_properties;
-use libfdt::{Fdt, FdtError};
-use lazy_static::lazy_static;
const CUSTOM_DEBUG_POLICY_OVERLAY_SYSPROP: &str =
"hypervisor.virtualizationmanager.debug_policy.path";
@@ -156,7 +156,12 @@
}
impl DebugConfig {
- pub fn new(debug_level: DebugLevel) -> Self {
+ pub fn new(config: &VirtualMachineConfig) -> Self {
+ let debug_level = match config {
+ VirtualMachineConfig::AppConfig(config) => config.debugLevel,
+ _ => DebugLevel::NONE,
+ };
+
match system_properties::read(CUSTOM_DEBUG_POLICY_OVERLAY_SYSPROP).unwrap_or_default() {
Some(path) if !path.is_empty() => {
match Self::from_custom_debug_overlay_policy(debug_level, Path::new(&path)) {
@@ -179,6 +184,11 @@
}
info!("Debug policy is disabled");
+ Self::new_with_debug_level(debug_level)
+ }
+
+ /// Creates a new DebugConfig with debug level. Only use this for test purpose.
+ pub fn new_with_debug_level(debug_level: DebugLevel) -> Self {
Self {
debug_level,
debug_policy_log: false,
@@ -203,7 +213,6 @@
self.debug_level != DebugLevel::NONE || self.debug_policy_ramdump
}
- // TODO: Remove this code path in user build for removing libfdt depenency.
fn from_custom_debug_overlay_policy(debug_level: DebugLevel, path: &Path) -> Result<Self> {
match OwnedFdt::from_overlay_onto_new_fdt(path) {
Ok(fdt) => Ok(Self {
@@ -229,14 +238,6 @@
#[cfg(test)]
mod tests {
use super::*;
- use anyhow::ensure;
-
- fn can_set_sysprop() -> bool {
- if let Ok(Some(value)) = system_properties::read("ro.build.type") {
- return "user".eq(&value);
- }
- false // if we're in doubt, skip test.
- }
#[test]
fn test_read_avf_debug_policy_with_ramdump() -> Result<()> {
@@ -317,40 +318,4 @@
Ok(())
}
-
- fn test_new_with_custom_policy_internal() -> Result<()> {
- let debug_config = DebugConfig::new(DebugLevel::NONE);
-
- ensure!(debug_config.debug_level == DebugLevel::NONE);
- ensure!(!debug_config.debug_policy_log);
- ensure!(!debug_config.debug_policy_ramdump);
- ensure!(debug_config.debug_policy_adb);
-
- Ok(())
- }
-
- #[test]
- fn test_new_with_custom_policy() -> Result<()> {
- if !can_set_sysprop() {
- // Skip test if we can't override sysprop.
- return Ok(());
- }
-
- // Setup
- let old_sysprop = system_properties::read(CUSTOM_DEBUG_POLICY_OVERLAY_SYSPROP)
- .context("Failed to read existing sysprop")?
- .unwrap_or_default();
- let file_name = "avf_debug_policy_with_adb.dtbo";
- system_properties::write(CUSTOM_DEBUG_POLICY_OVERLAY_SYSPROP, file_name)
- .context("Failed to set sysprop")?;
-
- // Run test
- let test_result = test_new_with_custom_policy_internal();
-
- // Clean up.
- system_properties::write(CUSTOM_DEBUG_POLICY_OVERLAY_SYSPROP, &old_sysprop)
- .context("Failed to restore sysprop")?;
-
- test_result
- }
}
diff --git a/virtualizationmanager/src/payload.rs b/virtualizationmanager/src/payload.rs
index 05626d3..9d0c7d6 100644
--- a/virtualizationmanager/src/payload.rs
+++ b/virtualizationmanager/src/payload.rs
@@ -631,7 +631,7 @@
collect_apex_infos(
&apex_info_list,
&apex_configs,
- &DebugConfig::new(DebugLevel::FULL)
+ &DebugConfig::new_with_debug_level(DebugLevel::FULL)
)?,
vec![
// Pass active/required APEXes
@@ -660,8 +660,11 @@
};
let apex_configs = vec![ApexConfig { name: "apex-vendor".to_string() }];
- let ret =
- collect_apex_infos(&apex_info_list, &apex_configs, &DebugConfig::new(DebugLevel::NONE));
+ let ret = collect_apex_infos(
+ &apex_info_list,
+ &apex_configs,
+ &DebugConfig::new_with_debug_level(DebugLevel::NONE),
+ );
assert!(ret
.is_err_and(|ret| ret.to_string()
== "Non-system APEX apex-vendor is not supported in Microdroid"));
@@ -687,7 +690,7 @@
collect_apex_infos(
&apex_info_list,
&apex_configs,
- &DebugConfig::new(DebugLevel::NONE)
+ &DebugConfig::new_with_debug_level(DebugLevel::NONE)
)?,
vec![&apex_info_list.list[0]]
);