Merge "Add ramdump tests with debug policy back" into main
diff --git a/Android.bp b/Android.bp
index d1086ba..22581b0 100644
--- a/Android.bp
+++ b/Android.bp
@@ -33,7 +33,10 @@
module_type: "rust_defaults",
config_namespace: "ANDROID",
bool_variables: [
+ "release_avf_enable_dice_changes",
+ "release_avf_enable_llpvm_changes",
"release_avf_enable_multi_tenant_microdroid_vm",
+ "release_avf_enable_remote_attestation",
"release_avf_enable_vendor_modules",
],
properties: [
@@ -44,9 +47,18 @@
avf_flag_aware_rust_defaults {
name: "avf_build_flags_rust",
soong_config_variables: {
+ release_avf_enable_dice_changes: {
+ cfgs: ["dice_changes"],
+ },
+ release_avf_enable_llpvm_changes: {
+ cfgs: ["llpvm_changes"],
+ },
release_avf_enable_multi_tenant_microdroid_vm: {
cfgs: ["payload_not_root"],
},
+ release_avf_enable_remote_attestation: {
+ cfgs: ["remote_attestation"],
+ },
release_avf_enable_vendor_modules: {
cfgs: ["vendor_modules"],
},
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 3bc7aba..171389b 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -28,6 +28,9 @@
},
{
"name": "initrd_bootconfig.test"
+ },
+ {
+ "name": "libdice_policy.test"
}
],
"avf-postsubmit": [
diff --git a/apex/sign_virt_apex.py b/apex/sign_virt_apex.py
index a42f5ec..8257aae 100644
--- a/apex/sign_virt_apex.py
+++ b/apex/sign_virt_apex.py
@@ -231,6 +231,9 @@
if additional_descriptors:
for image in additional_descriptors:
cmd.extend(['--include_descriptors_from_image', image])
+
+ if 'Rollback Index' in info:
+ cmd.extend(['--rollback_index', info['Rollback Index']])
RunCommand(args, cmd)
diff --git a/javalib/api/test-current.txt b/javalib/api/test-current.txt
index bedb267..7c61712 100644
--- a/javalib/api/test-current.txt
+++ b/javalib/api/test-current.txt
@@ -19,6 +19,7 @@
public class VirtualMachineManager {
method @RequiresPermission(android.system.virtualmachine.VirtualMachine.MANAGE_VIRTUAL_MACHINE_PERMISSION) public boolean isFeatureEnabled(String) throws android.system.virtualmachine.VirtualMachineException;
+ field public static final String FEATURE_DICE_CHANGES = "com.android.kvm.DICE_CHANGES";
field public static final String FEATURE_PAYLOAD_NOT_ROOT = "com.android.kvm.PAYLOAD_NON_ROOT";
field public static final String FEATURE_VENDOR_MODULES = "com.android.kvm.VENDOR_MODULES";
}
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
index 0a79553..e45fe99 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
@@ -107,10 +107,17 @@
@Retention(RetentionPolicy.SOURCE)
@StringDef(
prefix = "FEATURE_",
- value = {FEATURE_PAYLOAD_NOT_ROOT, FEATURE_VENDOR_MODULES})
+ value = {FEATURE_DICE_CHANGES, FEATURE_PAYLOAD_NOT_ROOT, FEATURE_VENDOR_MODULES})
public @interface Features {}
/**
+ * Feature to include new data in the VM DICE chain.
+ *
+ * @hide
+ */
+ @TestApi
+ public static final String FEATURE_DICE_CHANGES = IVirtualizationService.FEATURE_DICE_CHANGES;
+ /**
* Feature to run payload as non-root user.
*
* @hide
@@ -120,7 +127,7 @@
IVirtualizationService.FEATURE_PAYLOAD_NON_ROOT;
/**
- * Feature to run payload as non-root user.
+ * Feature to allow vendor modules in Microdroid.
*
* @hide
*/
diff --git a/libs/dice_policy/Android.bp b/libs/dice_policy/Android.bp
new file mode 100644
index 0000000..a7ac5b9
--- /dev/null
+++ b/libs/dice_policy/Android.bp
@@ -0,0 +1,35 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+ name: "libdice_policy.defaults",
+ crate_name: "dice_policy",
+ defaults: ["avf_build_flags_rust"],
+ srcs: ["src/lib.rs"],
+ edition: "2021",
+ prefer_rlib: true,
+ rustlibs: [
+ "libanyhow",
+ "libciborium",
+ "libcoset",
+ ],
+}
+
+rust_library {
+ name: "libdice_policy",
+ defaults: ["libdice_policy.defaults"],
+}
+
+rust_test {
+ name: "libdice_policy.test",
+ defaults: [
+ "libdice_policy.defaults",
+ "rdroidtest.defaults",
+ ],
+ test_suites: ["general-tests"],
+ rustlibs: [
+ "librustutils",
+ "libscopeguard",
+ ],
+}
diff --git a/libs/dice_policy/src/lib.rs b/libs/dice_policy/src/lib.rs
new file mode 100644
index 0000000..f5d117c
--- /dev/null
+++ b/libs/dice_policy/src/lib.rs
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! A “DICE policy” is a format for setting constraints on a DICE chain. A DICE chain policy
+//! verifier takes a policy and a DICE chain, and returns a boolean indicating whether the
+//! DICE chain meets the constraints set out on a policy.
+//!
+//! This forms the foundation of Dice Policy aware Authentication (DPA-Auth), where the server
+//! authenticates a client by comparing its dice chain against a set policy.
+//!
+//! Another use is "sealing", where clients can use an appropriately constructed dice policy to
+//! seal a secret. Unsealing is only permitted if dice chain of the component requesting unsealing
+//! complies with the policy.
+//!
+//! A typical policy will assert things like:
+//! # DK_pub must have this value
+//! # The DICE chain must be exactly five certificates long
+//! # authorityHash in the third certificate must have this value
+//! securityVersion in the fourth certificate must be an integer greater than 8
+//!
+//! These constraints used to express policy are (for now) limited to following 2 types:
+//! 1. Exact Match: useful for enforcing rules like authority hash should be exactly equal.
+//! 2. Greater than or equal to: Useful for setting policies that seal
+//! Anti-rollback protected entities (should be accessible to versions >= present).
+//!
+//! Dice Policy CDDL:
+//!
+//! dicePolicy = [
+//! 1, ; dice policy version
+//! + nodeConstraintList ; for each entry in dice chain
+//! ]
+//!
+//! nodeConstraintList = [
+//! * nodeConstraint
+//! ]
+//!
+//! ; We may add a hashConstraint item later
+//! nodeConstraint = exactMatchConstraint / geConstraint
+//!
+//! exactMatchConstraint = [1, keySpec, value]
+//! geConstraint = [2, keySpec, int]
+//!
+//! keySpec = [value+]
+//!
+//! value = bool / int / tstr / bstr
+
+use anyhow::{anyhow, bail, Context, Result};
+use ciborium::Value;
+use coset::{AsCborValue, CoseSign1};
+use std::borrow::Cow;
+
+const DICE_POLICY_VERSION: u64 = 1;
+
+/// Constraint Types supported in Dice policy.
+#[non_exhaustive]
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum ConstraintType {
+ /// Enforce exact match criteria, indicating the policy should match
+ /// if the dice chain has exact same specified values.
+ ExactMatch = 1,
+ /// Enforce Greater than or equal to criteria. When applied on security_version, this
+ /// can be useful to set policy that matches dice chains with same or upgraded images.
+ GreaterOrEqual = 2,
+}
+
+/// ConstraintSpec is used to specify which constraint type to apply and
+/// on which all entries in a dice node.
+/// See documentation of `from_dice_chain()` for examples.
+pub struct ConstraintSpec {
+ constraint_type: ConstraintType,
+ // path is essentially a list of label/int.
+ // It identifies which entry (in a dice node) to be applying constraints on.
+ path: Vec<i64>,
+}
+
+impl ConstraintSpec {
+ /// Construct the ConstraintSpec.
+ pub fn new(constraint_type: ConstraintType, path: Vec<i64>) -> Result<Self> {
+ Ok(ConstraintSpec { constraint_type, path })
+ }
+}
+
+// TODO(b/291238565): Restrict (nested_)key & value type to (bool/int/tstr/bstr).
+// and maybe convert it into struct.
+/// Each constraint (on a dice node) is a tuple: (ConstraintType, constraint_path, value)
+#[derive(Debug, PartialEq)]
+struct Constraint(u16, Vec<i64>, Value);
+
+/// List of all constraints on a dice node.
+#[derive(Debug, PartialEq)]
+struct NodeConstraints(Box<[Constraint]>);
+
+/// Module for working with dice policy.
+#[derive(Debug, PartialEq)]
+pub struct DicePolicy {
+ version: u64,
+ node_constraints_list: Box<[NodeConstraints]>, // Constraint on each entry in dice chain.
+}
+
+impl DicePolicy {
+ /// Construct a dice policy from a given dice chain.
+ /// This can be used by clients to construct a policy to seal secrets.
+ /// Constraints on all but first dice node is applied using constraint_spec argument.
+ /// For the first node (which is a ROT key), the constraint is ExactMatch of the whole node.
+ ///
+ /// # Arguments
+ /// `dice_chain`: The serialized CBOR encoded Dice chain, adhering to Android Profile for DICE.
+ /// https://pigweed.googlesource.com/open-dice/+/refs/heads/main/docs/android.md
+ ///
+ /// `constraint_spec`: List of constraints to be applied on dice node.
+ /// Each constraint is a ConstraintSpec object.
+ ///
+ /// Note: Dice node is treated as a nested map (& so the lookup is done in that fashion).
+ ///
+ /// Examples of constraint_spec:
+ /// 1. For exact_match on auth_hash & greater_or_equal on security_version
+ /// constraint_spec =[
+ /// (ConstraintType::ExactMatch, vec![AUTHORITY_HASH]),
+ /// (ConstraintType::GreaterOrEqual, vec![CONFIG_DESC, COMPONENT_NAME]),
+ /// ];
+ ///
+ /// 2. For hypothetical (and highly simplified) dice chain:
+ /// [ROT_KEY, [{1 : 'a', 2 : {200 : 5, 201 : 'b'}}]]
+ /// The following can be used
+ /// constraint_spec =[
+ /// ConstraintSpec(ConstraintType::ExactMatch, vec![1]), // exact_matches value 'a'
+ /// ConstraintSpec(ConstraintType::GreaterOrEqual, vec![2, 200]),// matches any value >= 5
+ /// ];
+ pub fn from_dice_chain(dice_chain: &[u8], constraint_spec: &[ConstraintSpec]) -> Result<Self> {
+ // TODO(b/298217847): Check if the given dice chain adheres to Explicit-key DiceCertChain
+ // format and if not, convert it before policy construction.
+ let dice_chain = value_from_bytes(dice_chain).context("Unable to decode top-level CBOR")?;
+ let dice_chain = match dice_chain {
+ Value::Array(array) if array.len() >= 2 => array,
+ _ => bail!("Expected an array of at least length 2, found: {:?}", dice_chain),
+ };
+ let mut constraints_list: Vec<NodeConstraints> = Vec::with_capacity(dice_chain.len());
+ let mut it = dice_chain.into_iter();
+
+ constraints_list.push(NodeConstraints(Box::new([Constraint(
+ ConstraintType::ExactMatch as u16,
+ Vec::new(),
+ it.next().unwrap(),
+ )])));
+
+ for (n, value) in it.enumerate() {
+ let entry = cbor_value_from_cose_sign(value)
+ .with_context(|| format!("Unable to get Cose payload at: {}", n))?;
+ constraints_list.push(payload_to_constraints(entry, constraint_spec)?);
+ }
+
+ Ok(DicePolicy {
+ version: DICE_POLICY_VERSION,
+ node_constraints_list: constraints_list.into_boxed_slice(),
+ })
+ }
+}
+
+// Take the payload of a dice node & construct the constraints on it.
+fn payload_to_constraints(
+ payload: Value,
+ constraint_spec: &[ConstraintSpec],
+) -> Result<NodeConstraints> {
+ let mut node_constraints: Vec<Constraint> = Vec::new();
+ for constraint_item in constraint_spec {
+ let constraint_path = constraint_item.path.to_vec();
+ if constraint_path.is_empty() {
+ bail!("Expected non-empty key spec");
+ }
+ let val = lookup_value_in_nested_map(&payload, &constraint_path)
+ .context(format!("Value not found for constraint_path {:?}", constraint_path))?;
+ let constraint = Constraint(constraint_item.constraint_type as u16, constraint_path, val);
+ node_constraints.push(constraint);
+ }
+ Ok(NodeConstraints(node_constraints.into_boxed_slice()))
+}
+
+// Lookup value corresponding to constraint path in nested map.
+// This function recursively calls itself.
+// The depth of recursion is limited by the size of constraint_path.
+fn lookup_value_in_nested_map(cbor_map: &Value, constraint_path: &[i64]) -> Result<Value> {
+ if constraint_path.is_empty() {
+ return Ok(cbor_map.clone());
+ }
+ let explicit_map = get_map_from_value(cbor_map)?;
+ let val = lookup_value_in_map(&explicit_map, constraint_path[0])
+ .ok_or(anyhow!("Value not found for constraint key: {:?}", constraint_path[0]))?;
+ lookup_value_in_nested_map(val, &constraint_path[1..])
+}
+
+fn get_map_from_value(cbor_map: &Value) -> Result<Cow<Vec<(Value, Value)>>> {
+ match cbor_map {
+ Value::Bytes(b) => value_from_bytes(b)?
+ .into_map()
+ .map(Cow::Owned)
+ .map_err(|e| anyhow!("Expected a cbor map: {:?}", e)),
+ Value::Map(map) => Ok(Cow::Borrowed(map)),
+ _ => bail!("/Expected a cbor map {:?}", cbor_map),
+ }
+}
+
+fn lookup_value_in_map(map: &[(Value, Value)], key: i64) -> Option<&Value> {
+ let key = Value::Integer(key.into());
+ for (k, v) in map.iter() {
+ if k == &key {
+ return Some(v);
+ }
+ }
+ None
+}
+
+/// Extract the payload from the COSE Sign
+fn cbor_value_from_cose_sign(cbor: Value) -> Result<Value> {
+ let sign1 =
+ CoseSign1::from_cbor_value(cbor).map_err(|e| anyhow!("Error extracting CoseKey: {}", e))?;
+ match sign1.payload {
+ None => bail!("Missing payload"),
+ Some(payload) => Ok(value_from_bytes(&payload)?),
+ }
+}
+
+/// Decodes the provided binary CBOR-encoded value and returns a
+/// ciborium::Value struct wrapped in Result.
+fn value_from_bytes(mut bytes: &[u8]) -> Result<Value> {
+ let value = ciborium::de::from_reader(&mut bytes)?;
+ // Ciborium tries to read one Value, & doesn't care if there is trailing data after it. We do.
+ if !bytes.is_empty() {
+ bail!("Unexpected trailing data while converting to CBOR value");
+ }
+ Ok(value)
+}
+
+#[cfg(test)]
+rdroidtest::test_main!();
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use ciborium::cbor;
+ use coset::{CoseKey, Header, ProtectedHeader};
+ use rdroidtest::test;
+
+ const AUTHORITY_HASH: i64 = -4670549;
+ const CONFIG_DESC: i64 = -4670548;
+ const COMPONENT_NAME: i64 = -70002;
+ const KEY_MODE: i64 = -4670551;
+
+ // This is the number of certs in compos bcc (including the first ROT)
+ // To analyze a bcc use hwtrust tool from /tools/security/remote_provisioning/hwtrust
+ // `hwtrust --verbose dice-chain [path]/composbcc`
+ const COMPOS_DICE_CHAIN_SIZE: usize = 5;
+ const EXAMPLE_STRING: &str = "testing_dice_policy";
+ const EXAMPLE_NUM: i64 = 59765;
+
+ test!(policy_dice_size_is_same);
+ fn policy_dice_size_is_same() {
+ let input_dice = include_bytes!("../testdata/composbcc");
+ let constraint_spec = [
+ ConstraintSpec::new(ConstraintType::ExactMatch, vec![AUTHORITY_HASH]).unwrap(),
+ ConstraintSpec::new(ConstraintType::ExactMatch, vec![KEY_MODE]).unwrap(),
+ ConstraintSpec::new(ConstraintType::GreaterOrEqual, vec![CONFIG_DESC, COMPONENT_NAME])
+ .unwrap(),
+ ];
+ let policy = DicePolicy::from_dice_chain(input_dice, &constraint_spec).unwrap();
+ assert_eq!(policy.node_constraints_list.len(), COMPOS_DICE_CHAIN_SIZE);
+ }
+
+ test!(policy_structure_check);
+ fn policy_structure_check() {
+ let rot_key = CoseKey::default().to_cbor_value().unwrap();
+ let nested_payload = cbor!({
+ 100 => EXAMPLE_NUM
+ })
+ .unwrap();
+ let payload = cbor!({
+ 1 => EXAMPLE_STRING,
+ 2 => "some_other_example_string",
+ 3 => Value::Bytes(value_to_bytes(&nested_payload).unwrap()),
+ })
+ .unwrap();
+ let payload = value_to_bytes(&payload).unwrap();
+ let dice_node = CoseSign1 {
+ protected: ProtectedHeader::default(),
+ unprotected: Header::default(),
+ payload: Some(payload),
+ signature: b"ddef".to_vec(),
+ }
+ .to_cbor_value()
+ .unwrap();
+ let input_dice = Value::Array([rot_key.clone(), dice_node].to_vec());
+
+ let input_dice = value_to_bytes(&input_dice).unwrap();
+ let constraint_spec = [
+ ConstraintSpec::new(ConstraintType::ExactMatch, vec![1]).unwrap(),
+ ConstraintSpec::new(ConstraintType::GreaterOrEqual, vec![3, 100]).unwrap(),
+ ];
+ let policy = DicePolicy::from_dice_chain(&input_dice, &constraint_spec).unwrap();
+
+ // Assert policy is exactly as expected!
+ assert_eq!(
+ policy,
+ DicePolicy {
+ version: 1,
+ node_constraints_list: Box::new([
+ NodeConstraints(Box::new([Constraint(
+ ConstraintType::ExactMatch as u16,
+ vec![],
+ rot_key
+ )])),
+ NodeConstraints(Box::new([
+ Constraint(
+ ConstraintType::ExactMatch as u16,
+ vec![1],
+ Value::Text(EXAMPLE_STRING.to_string())
+ ),
+ Constraint(
+ ConstraintType::GreaterOrEqual as u16,
+ vec![3, 100],
+ Value::from(EXAMPLE_NUM)
+ )
+ ])),
+ ])
+ }
+ );
+ }
+
+ /// Encodes a ciborium::Value into bytes.
+ fn value_to_bytes(value: &Value) -> Result<Vec<u8>> {
+ let mut bytes: Vec<u8> = Vec::new();
+ ciborium::ser::into_writer(&value, &mut bytes)?;
+ Ok(bytes)
+ }
+}
diff --git a/libs/dice_policy/testdata/composbcc b/libs/dice_policy/testdata/composbcc
new file mode 100644
index 0000000..fb3e006
--- /dev/null
+++ b/libs/dice_policy/testdata/composbcc
Binary files differ
diff --git a/libs/service_vm_comm/src/lib.rs b/libs/service_vm_comm/src/lib.rs
index 6f03209..ca97ca1 100644
--- a/libs/service_vm_comm/src/lib.rs
+++ b/libs/service_vm_comm/src/lib.rs
@@ -22,5 +22,7 @@
mod message;
mod vsock;
-pub use message::{EcdsaP256KeyPair, GenerateCertificateRequestParams, Request, Response};
-pub use vsock::host_port;
+pub use message::{
+ EcdsaP256KeyPair, GenerateCertificateRequestParams, Request, Response, ServiceVmRequest,
+};
+pub use vsock::VmType;
diff --git a/libs/service_vm_comm/src/message.rs b/libs/service_vm_comm/src/message.rs
index 0eddcfb..80956cb 100644
--- a/libs/service_vm_comm/src/message.rs
+++ b/libs/service_vm_comm/src/message.rs
@@ -21,7 +21,19 @@
type MacedPublicKey = Vec<u8>;
-/// Represents a request to be sent to the service VM.
+/// The main request type to be sent to the service VM.
+#[derive(Clone, Debug, Serialize, Deserialize)]
+pub enum ServiceVmRequest {
+ /// A request to be processed by the service VM.
+ ///
+ /// Each request has a corresponding response item.
+ Process(Request),
+
+ /// Shuts down the service VM. No response is expected from it.
+ Shutdown,
+}
+
+/// Represents a process request to be sent to the service VM.
///
/// Each request has a corresponding response item.
#[derive(Clone, Debug, Serialize, Deserialize)]
diff --git a/libs/service_vm_comm/src/vsock.rs b/libs/service_vm_comm/src/vsock.rs
index fd6f088..aa7166d 100644
--- a/libs/service_vm_comm/src/vsock.rs
+++ b/libs/service_vm_comm/src/vsock.rs
@@ -14,14 +14,34 @@
//! Vsock setup shared between the host and the service VM.
-/// Returns the host port number for the given VM protection state.
-pub fn host_port(is_protected_vm: bool) -> u32 {
- const PROTECTED_VM_PORT: u32 = 5679;
- const NON_PROTECTED_VM_PORT: u32 = 5680;
+const PROTECTED_VM_PORT: u32 = 5679;
+const NON_PROTECTED_VM_PORT: u32 = 5680;
- if is_protected_vm {
- PROTECTED_VM_PORT
- } else {
- NON_PROTECTED_VM_PORT
+/// VM Type.
+#[derive(Clone, Copy, Debug)]
+pub enum VmType {
+ /// Protected VM.
+ ProtectedVm,
+
+ /// NonProtectev VM.
+ NonProtectedVm,
+}
+
+impl VmType {
+ /// Returns the port number used for the vsock communication between
+ /// the host and the service VM.
+ pub fn port(&self) -> u32 {
+ match self {
+ Self::ProtectedVm => PROTECTED_VM_PORT,
+ Self::NonProtectedVm => NON_PROTECTED_VM_PORT,
+ }
+ }
+
+ /// Returns whether it is a protected VM.
+ pub fn is_protected(&self) -> bool {
+ match self {
+ Self::ProtectedVm => true,
+ Self::NonProtectedVm => false,
+ }
}
}
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index 00831dd..bac93a4 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -409,7 +409,19 @@
},
}
-avb_add_hash_footer {
+soong_config_module_type {
+ name: "flag_aware_avb_add_hash_footer",
+ module_type: "avb_add_hash_footer",
+ config_namespace: "ANDROID",
+ bool_variables: [
+ "release_avf_enable_llpvm_changes",
+ ],
+ properties: [
+ "rollback_index",
+ ],
+}
+
+flag_aware_avb_add_hash_footer {
name: "microdroid_kernel_signed",
src: ":empty_file",
filename: "microdroid_kernel",
@@ -431,6 +443,12 @@
":microdroid_initrd_normal_hashdesc",
":microdroid_initrd_debug_hashdesc",
],
+ // Below are properties that are conditionally set depending on value of build flags.
+ soong_config_variables: {
+ release_avf_enable_llpvm_changes: {
+ rollback_index: 1,
+ },
+ },
}
prebuilt_etc {
@@ -447,7 +465,7 @@
},
}
-avb_add_hash_footer {
+flag_aware_avb_add_hash_footer {
name: "microdroid_kernel_with_modules_signed",
src: ":empty_file",
filename: "microdroid_kernel_with_modules",
@@ -465,6 +483,12 @@
":microdroid_initrd_normal_hashdesc",
":microdroid_initrd_debug_hashdesc",
],
+ // Below are properties that are conditionally set depending on value of build flags.
+ soong_config_variables: {
+ release_avf_enable_llpvm_changes: {
+ rollback_index: 1,
+ },
+ },
}
prebuilt_etc {
diff --git a/microdroid_manager/src/dice.rs b/microdroid_manager/src/dice.rs
index 27ec7a5..27905c9 100644
--- a/microdroid_manager/src/dice.rs
+++ b/microdroid_manager/src/dice.rs
@@ -32,16 +32,6 @@
use std::ptr::null_mut;
use std::slice;
-/// Derives a sealing key from the DICE sealing CDI.
-pub fn derive_sealing_key(
- dice_artifacts: &dyn DiceArtifacts,
- salt: &[u8],
- info: &[u8],
- key: &mut [u8],
-) -> Result<()> {
- Ok(hkdf(key, Md::sha256(), dice_artifacts.cdi_seal(), salt, info)?)
-}
-
/// Artifacts that are mapped into the process address space from the driver.
pub enum DiceDriver<'a> {
Real {
@@ -109,7 +99,7 @@
// input key material is already cryptographically strong.
let mut key = ZVec::new(key_length)?;
let salt = &[];
- derive_sealing_key(self.dice_artifacts(), salt, identifier, &mut key)?;
+ hkdf(&mut key, Md::sha256(), self.dice_artifacts().cdi_seal(), salt, identifier)?;
Ok(key)
}
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 1c79452..a496d53 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -20,8 +20,9 @@
mod payload;
mod swap;
mod vm_payload_service;
+mod vm_secret;
-use crate::dice::{DiceDriver, derive_sealing_key, format_payload_config_descriptor};
+use crate::dice::{DiceDriver, format_payload_config_descriptor};
use crate::instance::{ApexData, ApkData, InstanceDisk, MicrodroidData, RootHash};
use crate::vm_payload_service::register_vm_payload_service;
use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::ErrorCode::ErrorCode;
@@ -63,6 +64,7 @@
use std::process::{Child, Command, Stdio};
use std::str;
use std::time::{Duration, SystemTime};
+use vm_secret::VmSecret;
const WAIT_TIMEOUT: Duration = Duration::from_secs(10);
const MAIN_APK_PATH: &str = "/dev/block/by-name/microdroid-apk";
@@ -88,7 +90,6 @@
const FAILURE_SERIAL_DEVICE: &str = "/dev/ttyS1";
const ENCRYPTEDSTORE_BACKING_DEVICE: &str = "/dev/block/by-name/encryptedstore";
-const ENCRYPTEDSTORE_KEY_IDENTIFIER: &str = "encryptedstore_key";
const ENCRYPTEDSTORE_KEYSIZE: usize = 32;
#[derive(thiserror::Error, Debug)]
@@ -143,18 +144,9 @@
Owned(format!("MICRODROID_UNKNOWN_RUNTIME_ERROR|{:?}", err))
};
- let death_reason_bytes = death_reason.as_bytes();
- let mut sent_total = 0;
- while sent_total < death_reason_bytes.len() {
+ for chunk in death_reason.as_bytes().chunks(16) {
// TODO(b/220071963): Sometimes, sending more than 16 bytes at once makes MM hang.
- let begin = sent_total;
- let end = std::cmp::min(begin.saturating_add(16), death_reason_bytes.len());
- OpenOptions::new()
- .read(false)
- .write(true)
- .open(FAILURE_SERIAL_DEVICE)?
- .write_all(&death_reason_bytes[begin..end])?;
- sent_total = end;
+ OpenOptions::new().read(false).write(true).open(FAILURE_SERIAL_DEVICE)?.write_all(chunk)?;
}
Ok(())
@@ -416,11 +408,12 @@
// To minimize the exposure to untrusted data, derive dice profile as soon as possible.
info!("DICE derivation for payload");
let dice_artifacts = dice_derivation(dice, &verified_data, &payload_metadata)?;
+ let vm_secret = VmSecret::new(dice_artifacts).context("Failed to create VM secrets")?;
// Run encryptedstore binary to prepare the storage
let encryptedstore_child = if Path::new(ENCRYPTEDSTORE_BACKING_DEVICE).exists() {
info!("Preparing encryptedstore ...");
- Some(prepare_encryptedstore(&dice_artifacts).context("encryptedstore run")?)
+ Some(prepare_encryptedstore(&vm_secret).context("encryptedstore run")?)
} else {
None
};
@@ -477,7 +470,7 @@
register_vm_payload_service(
allow_restricted_apis,
service.clone(),
- dice_artifacts,
+ vm_secret,
vm_payload_service_fd,
)?;
@@ -917,18 +910,9 @@
buf.iter().map(|b| format!("{:02X}", b)).collect()
}
-fn prepare_encryptedstore(dice_artifacts: &OwnedDiceArtifacts) -> Result<Child> {
- // Use a fixed salt to scope the derivation to this API.
- // Generated using hexdump -vn32 -e'14/1 "0x%02X, " 1 "\n"' /dev/urandom
- // TODO(b/241541860) : Move this (& other salts) to a salt container, i.e. a global enum
- let salt = [
- 0xFC, 0x1D, 0x35, 0x7B, 0x96, 0xF3, 0xEF, 0x17, 0x78, 0x7D, 0x70, 0xED, 0xEA, 0xFE, 0x1D,
- 0x6F, 0xB3, 0xF9, 0x40, 0xCE, 0xDD, 0x99, 0x40, 0xAA, 0xA7, 0x0E, 0x92, 0x73, 0x90, 0x86,
- 0x4A, 0x75,
- ];
+fn prepare_encryptedstore(vm_secret: &VmSecret) -> Result<Child> {
let mut key = ZVec::new(ENCRYPTEDSTORE_KEYSIZE)?;
- derive_sealing_key(dice_artifacts, &salt, ENCRYPTEDSTORE_KEY_IDENTIFIER.as_bytes(), &mut key)?;
-
+ vm_secret.derive_encryptedstore_key(&mut key)?;
let mut cmd = Command::new(ENCRYPTEDSTORE_BIN);
cmd.arg("--blkdevice")
.arg(ENCRYPTEDSTORE_BACKING_DEVICE)
diff --git a/microdroid_manager/src/vm_payload_service.rs b/microdroid_manager/src/vm_payload_service.rs
index 1e0b574..c611b11 100644
--- a/microdroid_manager/src/vm_payload_service.rs
+++ b/microdroid_manager/src/vm_payload_service.rs
@@ -14,23 +14,23 @@
//! Implementation of the AIDL interface `IVmPayloadService`.
-use crate::dice::derive_sealing_key;
use android_system_virtualization_payload::aidl::android::system::virtualization::payload::IVmPayloadService::{
BnVmPayloadService, IVmPayloadService, VM_PAYLOAD_SERVICE_SOCKET_NAME};
use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::IVirtualMachineService;
use anyhow::{anyhow, Context, Result};
use avflog::LogResult;
use binder::{Interface, BinderFeatures, ExceptionCode, Strong, IntoBinderResult};
-use diced_open_dice::{DiceArtifacts, OwnedDiceArtifacts};
+use diced_open_dice::DiceArtifacts;
use log::info;
use rpcbinder::RpcServer;
use std::os::unix::io::OwnedFd;
+use crate::vm_secret::{VmSecret};
/// Implementation of `IVmPayloadService`.
struct VmPayloadService {
allow_restricted_apis: bool,
virtual_machine_service: Strong<dyn IVirtualMachineService>,
- dice: OwnedDiceArtifacts,
+ secret: VmSecret,
}
impl IVmPayloadService for VmPayloadService {
@@ -43,23 +43,18 @@
return Err(anyhow!("size {size} not in range (0..=32)"))
.or_binder_exception(ExceptionCode::ILLEGAL_ARGUMENT);
}
- // Use a fixed salt to scope the derivation to this API. It was randomly generated.
- let salt = [
- 0x8B, 0x0F, 0xF0, 0xD3, 0xB1, 0x69, 0x2B, 0x95, 0x84, 0x2C, 0x9E, 0x3C, 0x99, 0x56,
- 0x7A, 0x22, 0x55, 0xF8, 0x08, 0x23, 0x81, 0x5F, 0xF5, 0x16, 0x20, 0x3E, 0xBE, 0xBA,
- 0xB7, 0xA8, 0x43, 0x92,
- ];
- let mut secret = vec![0; size.try_into().unwrap()];
- derive_sealing_key(&self.dice, &salt, identifier, &mut secret)
+ let mut instance_secret = vec![0; size.try_into().unwrap()];
+ self.secret
+ .derive_payload_sealing_key(identifier, &mut instance_secret)
.context("Failed to derive VM instance secret")
.with_log()
.or_service_specific_exception(-1)?;
- Ok(secret)
+ Ok(instance_secret)
}
fn getDiceAttestationChain(&self) -> binder::Result<Vec<u8>> {
self.check_restricted_apis_allowed()?;
- if let Some(bcc) = self.dice.bcc() {
+ if let Some(bcc) = self.secret.dice().bcc() {
Ok(bcc.to_vec())
} else {
Err(anyhow!("bcc is none")).or_binder_exception(ExceptionCode::ILLEGAL_STATE)
@@ -68,7 +63,7 @@
fn getDiceAttestationCdi(&self) -> binder::Result<Vec<u8>> {
self.check_restricted_apis_allowed()?;
- Ok(self.dice.cdi_attest().to_vec())
+ Ok(self.secret.dice().cdi_attest().to_vec())
}
fn requestCertificate(&self, csr: &[u8]) -> binder::Result<Vec<u8>> {
@@ -84,9 +79,9 @@
fn new(
allow_restricted_apis: bool,
vm_service: Strong<dyn IVirtualMachineService>,
- dice: OwnedDiceArtifacts,
- ) -> Self {
- Self { allow_restricted_apis, virtual_machine_service: vm_service, dice }
+ secret: VmSecret,
+ ) -> VmPayloadService {
+ Self { allow_restricted_apis, virtual_machine_service: vm_service, secret }
}
fn check_restricted_apis_allowed(&self) -> binder::Result<()> {
@@ -104,11 +99,11 @@
pub(crate) fn register_vm_payload_service(
allow_restricted_apis: bool,
vm_service: Strong<dyn IVirtualMachineService>,
- dice: OwnedDiceArtifacts,
+ secret: VmSecret,
vm_payload_service_fd: OwnedFd,
) -> Result<()> {
let vm_payload_binder = BnVmPayloadService::new_binder(
- VmPayloadService::new(allow_restricted_apis, vm_service, dice),
+ VmPayloadService::new(allow_restricted_apis, vm_service, secret),
BinderFeatures::default(),
);
diff --git a/microdroid_manager/src/vm_secret.rs b/microdroid_manager/src/vm_secret.rs
new file mode 100644
index 0000000..d84c2e2
--- /dev/null
+++ b/microdroid_manager/src/vm_secret.rs
@@ -0,0 +1,106 @@
+// Copyright 2023, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Class for encapsulating & managing represent VM secrets.
+
+use anyhow::Result;
+use diced_open_dice::{DiceArtifacts, OwnedDiceArtifacts};
+use keystore2_crypto::ZVec;
+use openssl::hkdf::hkdf;
+use openssl::md::Md;
+use openssl::sha;
+
+const ENCRYPTEDSTORE_KEY_IDENTIFIER: &str = "encryptedstore_key";
+
+// Size of the secret stored in Secretkeeper.
+const SK_SECRET_SIZE: usize = 64;
+
+// Generated using hexdump -vn32 -e'14/1 "0x%02X, " 1 "\n"' /dev/urandom
+const SALT_ENCRYPTED_STORE: &[u8] = &[
+ 0xFC, 0x1D, 0x35, 0x7B, 0x96, 0xF3, 0xEF, 0x17, 0x78, 0x7D, 0x70, 0xED, 0xEA, 0xFE, 0x1D, 0x6F,
+ 0xB3, 0xF9, 0x40, 0xCE, 0xDD, 0x99, 0x40, 0xAA, 0xA7, 0x0E, 0x92, 0x73, 0x90, 0x86, 0x4A, 0x75,
+];
+const SALT_PAYLOAD_SERVICE: &[u8] = &[
+ 0x8B, 0x0F, 0xF0, 0xD3, 0xB1, 0x69, 0x2B, 0x95, 0x84, 0x2C, 0x9E, 0x3C, 0x99, 0x56, 0x7A, 0x22,
+ 0x55, 0xF8, 0x08, 0x23, 0x81, 0x5F, 0xF5, 0x16, 0x20, 0x3E, 0xBE, 0xBA, 0xB7, 0xA8, 0x43, 0x92,
+];
+
+pub enum VmSecret {
+ // V2 secrets are derived from 2 independently secured secrets:
+ // 1. Secretkeeper protected secrets (skp secret).
+ // 2. Dice Sealing CDIs (Similar to V1).
+ //
+ // These are protected against rollback of boot images i.e. VM instance rebooted
+ // with downgraded images will not have access to VM's secret.
+ // V2 secrets require hardware support - Secretkeeper HAL, which (among other things)
+ // is backed by tamper-evident storage, providing rollback protection to these secrets.
+ V2 { dice: OwnedDiceArtifacts, skp_secret: ZVec },
+ // V1 secrets are not protected against rollback of boot images.
+ // They are reliable only if rollback of images was prevented by verified boot ie,
+ // each stage (including pvmfw/Microdroid/Microdroid Manager) prevents downgrade of next
+ // stage. These are now legacy secrets & used only when Secretkeeper HAL is not supported
+ // by device.
+ V1 { dice: OwnedDiceArtifacts },
+}
+
+impl VmSecret {
+ pub fn new(dice_artifacts: OwnedDiceArtifacts) -> Result<VmSecret> {
+ if is_sk_supported() {
+ // TODO(b/291213394): Change this to real Sk protected secret.
+ let fake_skp_secret = ZVec::new(SK_SECRET_SIZE)?;
+ return Ok(Self::V2 { dice: dice_artifacts, skp_secret: fake_skp_secret });
+ }
+ Ok(Self::V1 { dice: dice_artifacts })
+ }
+ pub fn dice(&self) -> &OwnedDiceArtifacts {
+ match self {
+ Self::V2 { dice, .. } => dice,
+ Self::V1 { dice } => dice,
+ }
+ }
+
+ fn get_vm_secret(&self, salt: &[u8], identifier: &[u8], key: &mut [u8]) -> Result<()> {
+ match self {
+ Self::V2 { dice, skp_secret } => {
+ let mut hasher = sha::Sha256::new();
+ hasher.update(dice.cdi_seal());
+ hasher.update(skp_secret);
+ hkdf(key, Md::sha256(), &hasher.finish(), salt, identifier)?
+ }
+ Self::V1 { dice } => hkdf(key, Md::sha256(), dice.cdi_seal(), salt, identifier)?,
+ }
+ Ok(())
+ }
+
+ /// Derive sealing key for payload with following identifier.
+ pub fn derive_payload_sealing_key(&self, identifier: &[u8], key: &mut [u8]) -> Result<()> {
+ self.get_vm_secret(SALT_PAYLOAD_SERVICE, identifier, key)
+ }
+
+ /// Derive encryptedstore key. This uses hardcoded random salt & fixed identifier.
+ pub fn derive_encryptedstore_key(&self, key: &mut [u8]) -> Result<()> {
+ self.get_vm_secret(SALT_ENCRYPTED_STORE, ENCRYPTEDSTORE_KEY_IDENTIFIER.as_bytes(), key)
+ }
+}
+
+// Does the hardware support Secretkeeper.
+fn is_sk_supported() -> bool {
+ if cfg!(llpvm_changes) {
+ return false;
+ };
+ // TODO(b/292209416): This value should be extracted from device tree.
+ // Note: this does not affect the security of pVM. pvmfw & microdroid_manager continue to block
+ // upgraded images. Setting this true is equivalent to including constant salt in vm secrets.
+ true
+}
diff --git a/pvmfw/src/fdt.rs b/pvmfw/src/fdt.rs
index 244b192..61de423 100644
--- a/pvmfw/src/fdt.rs
+++ b/pvmfw/src/fdt.rs
@@ -732,6 +732,7 @@
strict_boot: bool,
debug_policy: Option<&mut [u8]>,
debuggable: bool,
+ kaslr_seed: u64,
) -> libfdt::Result<()> {
if let Some(debug_policy) = debug_policy {
let backup = Vec::from(fdt.as_slice());
@@ -753,6 +754,7 @@
if let Some(mut chosen) = fdt.chosen_mut()? {
empty_or_delete_prop(&mut chosen, cstr!("avf,strict-boot"), strict_boot)?;
empty_or_delete_prop(&mut chosen, cstr!("avf,new-instance"), new_instance)?;
+ chosen.setprop_inplace(cstr!("kaslr-seed"), &kaslr_seed.to_be_bytes())?;
};
if !debuggable {
if let Some(bootargs) = read_bootargs_from(fdt)? {
diff --git a/pvmfw/src/main.rs b/pvmfw/src/main.rs
index ba453e7..d39d51c 100644
--- a/pvmfw/src/main.rs
+++ b/pvmfw/src/main.rs
@@ -38,6 +38,7 @@
use crate::fdt::modify_for_next_stage;
use crate::helpers::GUEST_PAGE_SIZE;
use crate::instance::get_or_generate_instance_salt;
+use alloc::borrow::Cow;
use alloc::boxed::Box;
use core::ops::Range;
use diced_open_dice::{bcc_handover_parse, DiceArtifacts};
@@ -51,6 +52,7 @@
use vmbase::heap;
use vmbase::memory::flush;
use vmbase::memory::MEMORY;
+use vmbase::rand;
use vmbase::virtio::pci;
const NEXT_BCC_SIZE: usize = GUEST_PAGE_SIZE;
@@ -132,30 +134,45 @@
})?;
trace!("Got salt from instance.img: {salt:x?}");
- // It is possible that the DICE chain we were given is rooted in the UDS. We do not want to give
- // such a chain to the payload, or even the associated CDIs. So remove the entire chain we
- // were given and taint the CDIs. Note that the resulting CDIs are still deterministically
- // derived from those we received, so will vary iff they do.
- // TODO(b/280405545): Remove this post Android 14.
- let truncated_bcc_handover = bcc::truncate(bcc_handover).map_err(|e| {
- error!("{e}");
- RebootReason::InternalError
- })?;
-
- dice_inputs.write_next_bcc(truncated_bcc_handover.as_slice(), &salt, next_bcc).map_err(
- |e| {
- error!("Failed to derive next-stage DICE secrets: {e:?}");
- RebootReason::SecretDerivationError
- },
- )?;
- flush(next_bcc);
-
- let strict_boot = true;
- modify_for_next_stage(fdt, next_bcc, new_instance, strict_boot, debug_policy, debuggable)
- .map_err(|e| {
- error!("Failed to configure device tree: {e}");
+ let new_bcc_handover = if cfg!(dice_changes) {
+ Cow::Borrowed(current_bcc_handover)
+ } else {
+ // It is possible that the DICE chain we were given is rooted in the UDS. We do not want to
+ // give such a chain to the payload, or even the associated CDIs. So remove the
+ // entire chain we were given and taint the CDIs. Note that the resulting CDIs are
+ // still deterministically derived from those we received, so will vary iff they do.
+ // TODO(b/280405545): Remove this post Android 14.
+ let truncated_bcc_handover = bcc::truncate(bcc_handover).map_err(|e| {
+ error!("{e}");
RebootReason::InternalError
})?;
+ Cow::Owned(truncated_bcc_handover)
+ };
+
+ dice_inputs.write_next_bcc(new_bcc_handover.as_ref(), &salt, next_bcc).map_err(|e| {
+ error!("Failed to derive next-stage DICE secrets: {e:?}");
+ RebootReason::SecretDerivationError
+ })?;
+ flush(next_bcc);
+
+ let kaslr_seed = u64::from_ne_bytes(rand::random_array().map_err(|e| {
+ error!("Failed to generated guest KASLR seed: {e}");
+ RebootReason::InternalError
+ })?);
+ let strict_boot = true;
+ modify_for_next_stage(
+ fdt,
+ next_bcc,
+ new_instance,
+ strict_boot,
+ debug_policy,
+ debuggable,
+ kaslr_seed,
+ )
+ .map_err(|e| {
+ error!("Failed to configure device tree: {e}");
+ RebootReason::InternalError
+ })?;
info!("Starting payload...");
diff --git a/rialto/Android.bp b/rialto/Android.bp
index 3dfcca1..d8e4536 100644
--- a/rialto/Android.bp
+++ b/rialto/Android.bp
@@ -103,16 +103,13 @@
"android.system.virtualizationservice-rust",
"libandroid_logger",
"libanyhow",
- "libciborium",
"liblibc",
"liblog_rust",
- "libnix",
"libservice_vm_comm",
+ "libservice_vm_manager",
"libvmclient",
- "libvsock",
],
data: [
- ":rialto_bin",
":rialto_unsigned",
],
test_suites: ["general-tests"],
diff --git a/rialto/src/communication.rs b/rialto/src/communication.rs
index ee4ecdb..50722f2 100644
--- a/rialto/src/communication.rs
+++ b/rialto/src/communication.rs
@@ -20,7 +20,7 @@
use core::mem;
use core::result;
use log::info;
-use service_vm_comm::{Request, Response};
+use service_vm_comm::{Response, ServiceVmRequest};
use tinyvec::ArrayVec;
use virtio_drivers::{
self,
@@ -84,7 +84,7 @@
}
}
- pub fn read_request(&mut self) -> Result<Request> {
+ pub fn read_request(&mut self) -> Result<ServiceVmRequest> {
Ok(ciborium::from_reader(self)?)
}
diff --git a/rialto/src/main.rs b/rialto/src/main.rs
index 0ecbe9d..4e91574 100644
--- a/rialto/src/main.rs
+++ b/rialto/src/main.rs
@@ -33,6 +33,7 @@
use hyp::{get_mem_sharer, get_mmio_guard};
use libfdt::FdtError;
use log::{debug, error, info};
+use service_vm_comm::{ServiceVmRequest, VmType};
use virtio_drivers::{
device::socket::{VsockAddr, VMADDR_CID_HOST},
transport::{pci::bus::PciRoot, DeviceType, Transport},
@@ -52,12 +53,16 @@
};
fn host_addr() -> VsockAddr {
- VsockAddr { cid: VMADDR_CID_HOST, port: service_vm_comm::host_port(is_protected_vm()) }
+ VsockAddr { cid: VMADDR_CID_HOST, port: vm_type().port() }
}
-fn is_protected_vm() -> bool {
+fn vm_type() -> VmType {
// Use MMIO support to determine whether the VM is protected.
- get_mmio_guard().is_some()
+ if get_mmio_guard().is_some() {
+ VmType::ProtectedVm
+ } else {
+ VmType::NonProtectedVm
+ }
}
fn new_page_table() -> Result<PageTable> {
@@ -135,9 +140,11 @@
debug!("Found socket device: guest cid = {:?}", socket_device.guest_cid());
let mut vsock_stream = VsockStream::new(socket_device, host_addr())?;
- let response = requests::process_request(vsock_stream.read_request()?)?;
- vsock_stream.write_response(&response)?;
- vsock_stream.flush()?;
+ while let ServiceVmRequest::Process(req) = vsock_stream.read_request()? {
+ let response = requests::process_request(req)?;
+ vsock_stream.write_response(&response)?;
+ vsock_stream.flush()?;
+ }
vsock_stream.shutdown()?;
Ok(())
diff --git a/rialto/tests/test.rs b/rialto/tests/test.rs
index e9bdab6..b8ced95 100644
--- a/rialto/tests/test.rs
+++ b/rialto/tests/test.rs
@@ -16,180 +16,120 @@
use android_system_virtualizationservice::{
aidl::android::system::virtualizationservice::{
- CpuTopology::CpuTopology, DiskImage::DiskImage, Partition::Partition,
- PartitionType::PartitionType, VirtualMachineConfig::VirtualMachineConfig,
+ VirtualMachineConfig::VirtualMachineConfig,
VirtualMachineRawConfig::VirtualMachineRawConfig,
},
binder::{ParcelFileDescriptor, ProcessState},
};
-use anyhow::{anyhow, bail, Context, Result};
+use anyhow::{bail, Context, Result};
use log::info;
-use service_vm_comm::{host_port, Request, Response};
+use service_vm_comm::{
+ EcdsaP256KeyPair, GenerateCertificateRequestParams, Request, Response, VmType,
+};
+use service_vm_manager::ServiceVm;
use std::fs::File;
-use std::io::{self, BufRead, BufReader, BufWriter, Write};
-use std::os::unix::io::FromRawFd;
use std::panic;
-use std::thread;
-use std::time::Duration;
-use vmclient::{DeathReason, VmInstance};
-use vsock::{VsockListener, VMADDR_CID_HOST};
+use std::path::PathBuf;
+use vmclient::VmInstance;
-const SIGNED_RIALTO_PATH: &str = "/data/local/tmp/rialto_test/arm64/rialto.bin";
const UNSIGNED_RIALTO_PATH: &str = "/data/local/tmp/rialto_test/arm64/rialto_unsigned.bin";
const INSTANCE_IMG_PATH: &str = "/data/local/tmp/rialto_test/arm64/instance.img";
-const INSTANCE_IMG_SIZE: i64 = 1024 * 1024; // 1MB
#[test]
-fn boot_rialto_in_protected_vm_successfully() -> Result<()> {
- boot_rialto_successfully(
- SIGNED_RIALTO_PATH,
- true, // protected_vm
- )
+fn process_requests_in_protected_vm() -> Result<()> {
+ check_processing_requests(VmType::ProtectedVm)
}
#[test]
-fn boot_rialto_in_unprotected_vm_successfully() -> Result<()> {
- boot_rialto_successfully(
- UNSIGNED_RIALTO_PATH,
- false, // protected_vm
- )
+fn process_requests_in_non_protected_vm() -> Result<()> {
+ check_processing_requests(VmType::NonProtectedVm)
}
-fn boot_rialto_successfully(rialto_path: &str, protected_vm: bool) -> Result<()> {
- android_logger::init_once(
- android_logger::Config::default().with_tag("rialto").with_min_level(log::Level::Debug),
- );
+fn check_processing_requests(vm_type: VmType) -> Result<()> {
+ let mut vm = start_service_vm(vm_type)?;
- // Redirect panic messages to logcat.
- panic::set_hook(Box::new(|panic_info| {
- log::error!("{}", panic_info);
- }));
-
- // We need to start the thread pool for Binder to work properly, especially link_to_death.
- ProcessState::start_thread_pool();
-
- let virtmgr =
- vmclient::VirtualizationService::new().context("Failed to spawn VirtualizationService")?;
- let service = virtmgr.connect().context("Failed to connect to VirtualizationService")?;
-
- let rialto = File::open(rialto_path).context("Failed to open Rialto kernel binary")?;
- let console = android_log_fd()?;
- let log = android_log_fd()?;
-
- let disks = if protected_vm {
- let instance_img = File::options()
- .create(true)
- .read(true)
- .write(true)
- .truncate(true)
- .open(INSTANCE_IMG_PATH)?;
- let instance_img = ParcelFileDescriptor::new(instance_img);
-
- service
- .initializeWritablePartition(
- &instance_img,
- INSTANCE_IMG_SIZE,
- PartitionType::ANDROID_VM_INSTANCE,
- )
- .context("Failed to initialize instange.img")?;
- let writable_partitions = vec![Partition {
- label: "vm-instance".to_owned(),
- image: Some(instance_img),
- writable: true,
- }];
- vec![DiskImage { image: None, partitions: writable_partitions, writable: true }]
- } else {
- vec![]
- };
-
- let config = VirtualMachineConfig::RawConfig(VirtualMachineRawConfig {
- name: String::from("RialtoTest"),
- kernel: None,
- initrd: None,
- params: None,
- bootloader: Some(ParcelFileDescriptor::new(rialto)),
- disks,
- protectedVm: protected_vm,
- memoryMib: 300,
- cpuTopology: CpuTopology::ONE_CPU,
- platformVersion: "~1.0".to_string(),
- gdbPort: 0, // No gdb
- ..Default::default()
- });
- let vm = VmInstance::create(
- service.as_ref(),
- &config,
- Some(console),
- /* consoleIn */ None,
- Some(log),
- None,
- )
- .context("Failed to create VM")?;
-
- let port = host_port(protected_vm);
- let check_socket_handle = thread::spawn(move || try_check_socket_connection(port).unwrap());
-
- vm.start().context("Failed to start VM")?;
-
- // Wait for VM to finish, and check that it shut down cleanly.
- let death_reason = vm
- .wait_for_death_with_timeout(Duration::from_secs(10))
- .ok_or_else(|| anyhow!("Timed out waiting for VM exit"))?;
- assert_eq!(death_reason, DeathReason::Shutdown);
-
- match check_socket_handle.join() {
- Ok(_) => {
- info!(
- "Received the echoed message. \
- The socket connection between the host and the service VM works correctly."
- )
- }
- Err(_) => bail!("The socket connection check failed."),
- }
+ check_processing_reverse_request(&mut vm)?;
+ check_processing_generating_key_pair_request(&mut vm)?;
+ check_processing_generating_certificate_request(&mut vm)?;
Ok(())
}
-fn android_log_fd() -> io::Result<File> {
- let (reader_fd, writer_fd) = nix::unistd::pipe()?;
-
- // SAFETY: These are new FDs with no previous owner.
- let reader = unsafe { File::from_raw_fd(reader_fd) };
- // SAFETY: These are new FDs with no previous owner.
- let writer = unsafe { File::from_raw_fd(writer_fd) };
-
- thread::spawn(|| {
- for line in BufReader::new(reader).lines() {
- info!("{}", line.unwrap());
- }
- });
- Ok(writer)
-}
-
-fn try_check_socket_connection(port: u32) -> Result<()> {
- info!("Setting up the listening socket on port {port}...");
- let listener = VsockListener::bind_with_cid_port(VMADDR_CID_HOST, port)?;
- info!("Listening on port {port}...");
-
- let mut vsock_stream =
- listener.incoming().next().ok_or_else(|| anyhow!("Failed to get vsock_stream"))??;
- info!("Accepted connection {:?}", vsock_stream);
- vsock_stream.set_read_timeout(Some(Duration::from_millis(1_000)))?;
-
- const WRITE_BUFFER_CAPACITY: usize = 512;
- let mut buffer = BufWriter::with_capacity(WRITE_BUFFER_CAPACITY, vsock_stream.clone());
-
+fn check_processing_reverse_request(vm: &mut ServiceVm) -> Result<()> {
// TODO(b/292080257): Test with message longer than the receiver's buffer capacity
// 1024 bytes once the guest virtio-vsock driver fixes the credit update in recv().
let message = "abc".repeat(166);
let request = Request::Reverse(message.as_bytes().to_vec());
- ciborium::into_writer(&request, &mut buffer)?;
- buffer.flush()?;
- info!("Sent request: {request:?}.");
- let response: Response = ciborium::from_reader(&mut vsock_stream)?;
+ let response = vm.process_request(request)?;
info!("Received response: {response:?}.");
let expected_response: Vec<u8> = message.as_bytes().iter().rev().cloned().collect();
assert_eq!(Response::Reverse(expected_response), response);
Ok(())
}
+
+fn check_processing_generating_key_pair_request(vm: &mut ServiceVm) -> Result<()> {
+ let request = Request::GenerateEcdsaP256KeyPair;
+
+ let response = vm.process_request(request)?;
+ info!("Received response: {response:?}.");
+
+ match response {
+ Response::GenerateEcdsaP256KeyPair(EcdsaP256KeyPair { .. }) => Ok(()),
+ _ => bail!("Incorrect response type"),
+ }
+}
+
+fn check_processing_generating_certificate_request(vm: &mut ServiceVm) -> Result<()> {
+ let params = GenerateCertificateRequestParams { keys_to_sign: vec![], challenge: vec![] };
+ let request = Request::GenerateCertificateRequest(params);
+
+ let response = vm.process_request(request)?;
+ info!("Received response: {response:?}.");
+
+ match response {
+ Response::GenerateCertificateRequest(_) => Ok(()),
+ _ => bail!("Incorrect response type"),
+ }
+}
+
+fn start_service_vm(vm_type: VmType) -> Result<ServiceVm> {
+ android_logger::init_once(
+ android_logger::Config::default().with_tag("rialto").with_min_level(log::Level::Debug),
+ );
+ // Redirect panic messages to logcat.
+ panic::set_hook(Box::new(|panic_info| {
+ log::error!("{}", panic_info);
+ }));
+ // We need to start the thread pool for Binder to work properly, especially link_to_death.
+ ProcessState::start_thread_pool();
+ ServiceVm::start_vm(vm_instance(vm_type)?, vm_type)
+}
+
+fn vm_instance(vm_type: VmType) -> Result<VmInstance> {
+ match vm_type {
+ VmType::ProtectedVm => {
+ service_vm_manager::protected_vm_instance(PathBuf::from(INSTANCE_IMG_PATH))
+ }
+ VmType::NonProtectedVm => nonprotected_vm_instance(),
+ }
+}
+
+fn nonprotected_vm_instance() -> Result<VmInstance> {
+ let rialto = File::open(UNSIGNED_RIALTO_PATH).context("Failed to open Rialto kernel binary")?;
+ let config = VirtualMachineConfig::RawConfig(VirtualMachineRawConfig {
+ name: String::from("Non protected rialto"),
+ bootloader: Some(ParcelFileDescriptor::new(rialto)),
+ protectedVm: false,
+ memoryMib: 300,
+ platformVersion: "~1.0".to_string(),
+ ..Default::default()
+ });
+ let console = Some(service_vm_manager::android_log_fd()?);
+ let log = Some(service_vm_manager::android_log_fd()?);
+ let virtmgr = vmclient::VirtualizationService::new().context("Failed to spawn VirtMgr")?;
+ let service = virtmgr.connect().context("Failed to connect to VirtMgr")?;
+ info!("Connected to VirtMgr for service VM");
+ VmInstance::create(service.as_ref(), &config, console, /* consoleIn */ None, log, None)
+ .context("Failed to create VM")
+}
diff --git a/rustfmt.toml b/rustfmt.toml
deleted file mode 120000
index 475ba8f..0000000
--- a/rustfmt.toml
+++ /dev/null
@@ -1 +0,0 @@
-../../../build/soong/scripts/rustfmt.toml
\ No newline at end of file
diff --git a/rustfmt.toml b/rustfmt.toml
new file mode 100644
index 0000000..aaf15f2
--- /dev/null
+++ b/rustfmt.toml
@@ -0,0 +1,10 @@
+# Android Format Style
+# Should be in sync with build/soong/scripts/rustfmt.toml
+
+edition = "2021"
+use_small_heuristics = "Max"
+newline_style = "Unix"
+
+# Local customizations
+wrap_comments = true
+comment_width = 100
diff --git a/service_vm/client_apk/src/main.rs b/service_vm/client_apk/src/main.rs
index 672dd4a..08d4168 100644
--- a/service_vm/client_apk/src/main.rs
+++ b/service_vm/client_apk/src/main.rs
@@ -41,6 +41,7 @@
fn try_main() -> Result<()> {
info!("Welcome to Service VM Client!");
let csr = b"Hello from Service VM";
+ info!("Sending: {:?}", csr);
let certificate = request_certificate(csr);
info!("Certificate: {:?}", certificate);
Ok(())
diff --git a/service_vm_manager/Android.bp b/service_vm_manager/Android.bp
new file mode 100644
index 0000000..6469212
--- /dev/null
+++ b/service_vm_manager/Android.bp
@@ -0,0 +1,25 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_library {
+ name: "libservice_vm_manager",
+ crate_name: "service_vm_manager",
+ defaults: ["avf_build_flags_rust"],
+ srcs: ["src/lib.rs"],
+ prefer_rlib: true,
+ rustlibs: [
+ "android.system.virtualizationservice-rust",
+ "libanyhow",
+ "libciborium",
+ "liblazy_static",
+ "liblog_rust",
+ "libnix",
+ "libservice_vm_comm",
+ "libvmclient",
+ "libvsock",
+ ],
+ apex_available: [
+ "com.android.virt",
+ ],
+}
diff --git a/service_vm_manager/src/lib.rs b/service_vm_manager/src/lib.rs
new file mode 100644
index 0000000..8dedec5
--- /dev/null
+++ b/service_vm_manager/src/lib.rs
@@ -0,0 +1,261 @@
+// Copyright 2023, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! This module contains the functions to start, stop and communicate with the
+//! Service VM.
+
+use android_system_virtualizationservice::{
+ aidl::android::system::virtualizationservice::{
+ CpuTopology::CpuTopology, DiskImage::DiskImage,
+ IVirtualizationService::IVirtualizationService, Partition::Partition,
+ PartitionType::PartitionType, VirtualMachineConfig::VirtualMachineConfig,
+ VirtualMachineRawConfig::VirtualMachineRawConfig,
+ },
+ binder::ParcelFileDescriptor,
+};
+use anyhow::{anyhow, ensure, Context, Result};
+use lazy_static::lazy_static;
+use log::{info, warn};
+use service_vm_comm::{Request, Response, ServiceVmRequest, VmType};
+use std::fs::{File, OpenOptions};
+use std::io::{self, BufRead, BufReader, BufWriter, Write};
+use std::os::unix::io::FromRawFd;
+use std::path::{Path, PathBuf};
+use std::sync::{Condvar, Mutex, MutexGuard};
+use std::thread;
+use std::time::Duration;
+use vmclient::{DeathReason, VmInstance};
+use vsock::{VsockListener, VsockStream, VMADDR_CID_HOST};
+
+const VIRT_DATA_DIR: &str = "/data/misc/apexdata/com.android.virt";
+const RIALTO_PATH: &str = "/apex/com.android.virt/etc/rialto.bin";
+const INSTANCE_IMG_NAME: &str = "service_vm_instance.img";
+const INSTANCE_IMG_SIZE_BYTES: i64 = 1 << 20; // 1MB
+const MEMORY_MB: i32 = 300;
+const WRITE_BUFFER_CAPACITY: usize = 512;
+const READ_TIMEOUT: Duration = Duration::from_secs(10);
+const WRITE_TIMEOUT: Duration = Duration::from_secs(10);
+
+lazy_static! {
+ static ref SERVICE_VM_STATE: State = State::default();
+}
+
+/// The running state of the Service VM.
+#[derive(Debug, Default)]
+struct State {
+ is_running: Mutex<bool>,
+ stopped: Condvar,
+}
+
+impl State {
+ fn wait_until_no_service_vm_running(&self) -> Result<MutexGuard<'_, bool>> {
+ // The real timeout can be longer than 10 seconds since the time to acquire
+ // is_running mutex is not counted in the 10 seconds.
+ let (guard, wait_result) = self
+ .stopped
+ .wait_timeout_while(
+ self.is_running.lock().unwrap(),
+ Duration::from_secs(10),
+ |&mut is_running| is_running,
+ )
+ .unwrap();
+ ensure!(
+ !wait_result.timed_out(),
+ "Timed out while waiting for the running service VM to stop."
+ );
+ Ok(guard)
+ }
+
+ fn notify_service_vm_shutdown(&self) {
+ let mut is_running_guard = self.is_running.lock().unwrap();
+ *is_running_guard = false;
+ self.stopped.notify_one();
+ }
+}
+
+/// Service VM.
+pub struct ServiceVm {
+ vsock_stream: VsockStream,
+ /// VmInstance will be dropped when ServiceVm goes out of scope, which will kill the VM.
+ vm: VmInstance,
+}
+
+impl ServiceVm {
+ /// Starts the service VM and returns its instance.
+ /// The same instance image is used for different VMs.
+ /// At any given time, only one service should be running. If a service VM is
+ /// already running, this function will start the service VM once the running one
+ /// shuts down.
+ pub fn start() -> Result<Self> {
+ let mut is_running_guard = SERVICE_VM_STATE.wait_until_no_service_vm_running()?;
+
+ let instance_img_path = Path::new(VIRT_DATA_DIR).join(INSTANCE_IMG_NAME);
+ let vm = protected_vm_instance(instance_img_path)?;
+
+ let vm = Self::start_vm(vm, VmType::ProtectedVm)?;
+ *is_running_guard = true;
+ Ok(vm)
+ }
+
+ /// Starts the given VM instance and sets up the vsock connection with it.
+ /// Returns a `ServiceVm` instance.
+ /// This function is exposed for testing.
+ pub fn start_vm(vm: VmInstance, vm_type: VmType) -> Result<Self> {
+ // Sets up the vsock server on the host.
+ let vsock_listener = VsockListener::bind_with_cid_port(VMADDR_CID_HOST, vm_type.port())?;
+
+ // Starts the service VM.
+ vm.start().context("Failed to start service VM")?;
+ info!("Service VM started");
+
+ // Accepts the connection from the service VM.
+ // TODO(b/299427101): Introduce a timeout for the accept.
+ let (vsock_stream, peer_addr) = vsock_listener.accept().context("Failed to accept")?;
+ info!("Accepted connection {:?}", vsock_stream);
+ ensure!(
+ peer_addr.cid() == u32::try_from(vm.cid()).unwrap(),
+ "The CID of the peer address {} doesn't match the service VM CID {}",
+ peer_addr,
+ vm.cid()
+ );
+ vsock_stream.set_read_timeout(Some(READ_TIMEOUT))?;
+ vsock_stream.set_write_timeout(Some(WRITE_TIMEOUT))?;
+
+ Ok(Self { vsock_stream, vm })
+ }
+
+ /// Processes the request in the service VM.
+ pub fn process_request(&mut self, request: Request) -> Result<Response> {
+ self.write_request(&ServiceVmRequest::Process(request))?;
+ self.read_response()
+ }
+
+ /// Sends the request to the service VM.
+ fn write_request(&mut self, request: &ServiceVmRequest) -> Result<()> {
+ let mut buffer = BufWriter::with_capacity(WRITE_BUFFER_CAPACITY, &mut self.vsock_stream);
+ ciborium::into_writer(request, &mut buffer)?;
+ buffer.flush().context("Failed to flush the buffer")?;
+ info!("Sent request to the service VM.");
+ Ok(())
+ }
+
+ /// Reads the response from the service VM.
+ fn read_response(&mut self) -> Result<Response> {
+ let response: Response = ciborium::from_reader(&mut self.vsock_stream)
+ .context("Failed to read the response from the service VM")?;
+ info!("Received response from the service VM.");
+ Ok(response)
+ }
+
+ /// Shuts down the service VM.
+ fn shutdown(&mut self) -> Result<DeathReason> {
+ self.write_request(&ServiceVmRequest::Shutdown)?;
+ self.vm
+ .wait_for_death_with_timeout(Duration::from_secs(10))
+ .ok_or_else(|| anyhow!("Timed out to exit the service VM"))
+ }
+}
+
+impl Drop for ServiceVm {
+ fn drop(&mut self) {
+ // Wait till the service VM finishes releasing all the resources.
+ match self.shutdown() {
+ Ok(reason) => info!("Exit the service VM successfully: {reason:?}"),
+ Err(e) => warn!("Service VM shutdown request failed '{e:?}', killing it."),
+ }
+ SERVICE_VM_STATE.notify_service_vm_shutdown();
+ }
+}
+
+/// Returns a `VmInstance` of a protected VM with the instance image from the given path.
+pub fn protected_vm_instance(instance_img_path: PathBuf) -> Result<VmInstance> {
+ let virtmgr = vmclient::VirtualizationService::new().context("Failed to spawn VirtMgr")?;
+ let service = virtmgr.connect().context("Failed to connect to VirtMgr")?;
+ info!("Connected to VirtMgr for service VM");
+
+ let instance_img = instance_img(service.as_ref(), instance_img_path)?;
+ let writable_partitions = vec![Partition {
+ label: "vm-instance".to_owned(),
+ image: Some(instance_img),
+ writable: true,
+ }];
+ let rialto = File::open(RIALTO_PATH).context("Failed to open Rialto kernel binary")?;
+ let config = VirtualMachineConfig::RawConfig(VirtualMachineRawConfig {
+ name: String::from("Service VM"),
+ bootloader: Some(ParcelFileDescriptor::new(rialto)),
+ disks: vec![DiskImage { image: None, partitions: writable_partitions, writable: true }],
+ protectedVm: true,
+ memoryMib: MEMORY_MB,
+ cpuTopology: CpuTopology::ONE_CPU,
+ platformVersion: "~1.0".to_string(),
+ gdbPort: 0, // No gdb
+ ..Default::default()
+ });
+ let console_out = Some(android_log_fd()?);
+ let console_in = None;
+ let log = Some(android_log_fd()?);
+ let callback = None;
+ VmInstance::create(service.as_ref(), &config, console_out, console_in, log, callback)
+ .context("Failed to create service VM")
+}
+
+/// Returns the file descriptor of the instance image at the given path.
+fn instance_img(
+ service: &dyn IVirtualizationService,
+ instance_img_path: PathBuf,
+) -> Result<ParcelFileDescriptor> {
+ if instance_img_path.exists() {
+ // TODO(b/298174584): Try to recover if the service VM is triggered by rkpd.
+ return Ok(OpenOptions::new()
+ .read(true)
+ .write(true)
+ .open(instance_img_path)
+ .map(ParcelFileDescriptor::new)?);
+ }
+ let instance_img = OpenOptions::new()
+ .create(true)
+ .read(true)
+ .write(true)
+ .open(instance_img_path)
+ .map(ParcelFileDescriptor::new)?;
+ service.initializeWritablePartition(
+ &instance_img,
+ INSTANCE_IMG_SIZE_BYTES,
+ PartitionType::ANDROID_VM_INSTANCE,
+ )?;
+ Ok(instance_img)
+}
+
+/// This function is only exposed for testing.
+pub fn android_log_fd() -> io::Result<File> {
+ let (reader_fd, writer_fd) = nix::unistd::pipe()?;
+
+ // SAFETY: These are new FDs with no previous owner.
+ let reader = unsafe { File::from_raw_fd(reader_fd) };
+ // SAFETY: These are new FDs with no previous owner.
+ let writer = unsafe { File::from_raw_fd(writer_fd) };
+
+ thread::spawn(|| {
+ for line in BufReader::new(reader).lines() {
+ match line {
+ Ok(l) => info!("{}", l),
+ Err(e) => {
+ warn!("Failed to read line: {e:?}");
+ break;
+ }
+ }
+ }
+ });
+ Ok(writer)
+}
diff --git a/tests/benchmark/Android.bp b/tests/benchmark/Android.bp
index 90ba575..80fdff7 100644
--- a/tests/benchmark/Android.bp
+++ b/tests/benchmark/Android.bp
@@ -28,6 +28,7 @@
compile_multilib: "64",
required: ["perf-setup"],
host_required: ["MicrodroidTestPreparer"],
+ data: [":test_microdroid_vendor_image"],
}
cc_library_shared {
diff --git a/tests/benchmark/AndroidTest.xml b/tests/benchmark/AndroidTest.xml
index 8c8bfbe..11b17f4 100644
--- a/tests/benchmark/AndroidTest.xml
+++ b/tests/benchmark/AndroidTest.xml
@@ -30,6 +30,14 @@
<option name="post-push" value="chmod 755 /data/local/tmp/perf-setup.sh;/data/local/tmp/perf-setup.sh" />
<option name="cleanup" value="true" />
</target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="mkdir -p /data/local/tmp/microdroid-bench" />
+ <option name="teardown-command" value="rm -rf /data/local/tmp/microdroid-bench" />
+ </target_preparer>
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="cleanup" value="true" />
+ <option name="push" value="test_microdroid_vendor_image.img->/data/local/tmp/microdroid-bench/microdroid_vendor_image.img" />
+ </target_preparer>
<target_preparer class="com.android.microdroid.test.preparer.DisableMicrodroidDebugPolicyPreparer" />
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.microdroid.benchmark" />
diff --git a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
index 625f26a..1917654 100644
--- a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
+++ b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
@@ -38,6 +38,7 @@
import android.system.virtualmachine.VirtualMachineConfig;
import android.system.virtualmachine.VirtualMachineException;
import android.system.Os;
+import android.system.virtualmachine.VirtualMachineManager;
import android.util.Log;
import com.android.microdroid.test.common.MetricsProcessor;
@@ -260,6 +261,26 @@
}
@Test
+ public void testMicrodroidDebugBootTime_withVendorPartition() throws Exception {
+ assumeFeatureEnabled(VirtualMachineManager.FEATURE_VENDOR_MODULES);
+
+ File vendorDiskImage =
+ new File("/data/local/tmp/microdroid-bench/microdroid_vendor_image.img");
+ BootTimeStats stats =
+ runBootTimeTest(
+ "test_vm_boot_time_debug_with_vendor_partition",
+ (builder) ->
+ builder.setDebugLevel(DEBUG_LEVEL_FULL)
+ .setVmOutputCaptured(true)
+ .setVendorDiskImage(vendorDiskImage));
+ reportMetrics(stats.get(BootTimeMetric.TOTAL), "boot_time", "ms");
+ reportMetrics(stats.get(BootTimeMetric.VM_START), "vm_starting_time", "ms");
+ reportMetrics(stats.get(BootTimeMetric.BOOTLOADER), "bootloader_time", "ms");
+ reportMetrics(stats.get(BootTimeMetric.KERNEL), "kernel_boot_time", "ms");
+ reportMetrics(stats.get(BootTimeMetric.USERSPACE), "userspace_boot_time", "ms");
+ }
+
+ @Test
public void testMicrodroidImageSize() throws IOException {
Bundle bundle = new Bundle();
for (File file : new File(APEX_ETC_FS).listFiles()) {
diff --git a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
index 9f03ab7..e2795ec 100644
--- a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
+++ b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
@@ -20,6 +20,8 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.TruthJUnit.assume;
+import static org.junit.Assume.assumeTrue;
+
import android.app.Instrumentation;
import android.app.UiAutomation;
import android.content.Context;
@@ -545,4 +547,12 @@
protected interface RunTestsAgainstTestService {
void runTests(ITestService testService, TestResults testResults) throws Exception;
}
+
+ protected void assumeFeatureEnabled(String featureName) throws Exception {
+ assumeTrue(featureName + " not enabled", isFeatureEnabled(featureName));
+ }
+
+ protected boolean isFeatureEnabled(String featureName) throws Exception {
+ return getVirtualMachineManager().isFeatureEnabled(featureName);
+ }
}
diff --git a/tests/hostside/Android.bp b/tests/hostside/Android.bp
index 6301a57..e3d9cbe 100644
--- a/tests/hostside/Android.bp
+++ b/tests/hostside/Android.bp
@@ -16,6 +16,7 @@
static_libs: [
"MicrodroidHostTestHelper",
"compatibility-host-util",
+ "cts-host-utils",
"cts-statsd-atom-host-test-utils",
"microdroid_payload_metadata",
],
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index 21960b4..d861761 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -30,6 +30,8 @@
import static java.util.stream.Collectors.toList;
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters;
+import android.cts.host.utils.DeviceJUnit4Parameterized;
import android.cts.statsdatom.lib.ConfigUtils;
import android.cts.statsdatom.lib.ReportUtils;
@@ -42,7 +44,6 @@
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.device.TestDevice;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestMetrics;
import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.FileUtil;
@@ -58,6 +59,8 @@
import org.junit.Test;
import org.junit.rules.TestName;
import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.UseParametersRunnerFactory;
import org.xml.sax.Attributes;
import org.xml.sax.helpers.DefaultHandler;
@@ -67,6 +70,7 @@
import java.io.PipedOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -77,7 +81,8 @@
import java.util.regex.Pattern;
import java.util.stream.Collectors;
-@RunWith(DeviceJUnit4ClassRunner.class)
+@RunWith(DeviceJUnit4Parameterized.class)
+@UseParametersRunnerFactory(DeviceJUnit4ClassRunnerWithParameters.RunnerFactory.class)
public class MicrodroidHostTests extends MicrodroidHostTestCaseBase {
private static final String APK_NAME = "MicrodroidTestApp.apk";
private static final String PACKAGE_NAME = "com.android.microdroid.test";
@@ -97,6 +102,14 @@
}
}
+ @Parameterized.Parameters(name = "protectedVm={0}")
+ public static Collection<Object[]> params() {
+ return List.of(new Object[] {true}, new Object[] {false});
+ }
+
+ @Parameterized.Parameter(0)
+ public boolean mProtectedVm;
+
@Rule public TestLogData mTestLogs = new TestLogData();
@Rule public TestName mTestName = new TestName();
@Rule public TestMetrics mMetrics = new TestMetrics();
@@ -259,9 +272,11 @@
File virtApexEtcDir = new File(virtApexDir, "etc");
// We need only etc/ directory for images
assertWithMessage("Failed to mkdir " + virtApexEtcDir)
- .that(virtApexEtcDir.mkdirs()).isTrue();
+ .that(virtApexEtcDir.mkdirs())
+ .isTrue();
assertWithMessage("Failed to pull " + VIRT_APEX + "etc")
- .that(getDevice().pullDir(VIRT_APEX + "etc", virtApexEtcDir)).isTrue();
+ .that(getDevice().pullDir(VIRT_APEX + "etc", virtApexEtcDir))
+ .isTrue();
resignVirtApex(virtApexDir, key, keyOverrides, updateBootconfigs);
@@ -320,10 +335,12 @@
final String initrdPath = TEST_ROOT + "etc/microdroid_initrd_debuggable.img";
config.put("initrd", initrdPath);
// Add instance image as a partition in disks[1]
- disks.put(new JSONObject()
- .put("writable", true)
- .put("partitions",
- new JSONArray().put(newPartition("vm-instance", instanceImgPath))));
+ disks.put(
+ new JSONObject()
+ .put("writable", true)
+ .put(
+ "partitions",
+ new JSONArray().put(newPartition("vm-instance", instanceImgPath))));
// Add payload image disk with partitions:
// - payload-metadata
// - apexes: com.android.os.statsd, com.android.adbd, [sharedlib apex](optional)
@@ -373,7 +390,7 @@
@CddTest(requirements = {"9.17/C-2-1", "9.17/C-2-2", "9.17/C-2-6"})
public void protectedVmRunsPvmfw() throws Exception {
// Arrange
- assumeProtectedVmSupported();
+ assumeProtectedVm();
final String configPath = "assets/vm_config_apex.json";
// Act
@@ -401,16 +418,16 @@
@CddTest(requirements = {"9.17/C-2-1", "9.17/C-2-2", "9.17/C-2-6"})
public void protectedVmWithImageSignedWithDifferentKeyRunsPvmfw() throws Exception {
// Arrange
- assumeProtectedVmSupported();
+ assumeProtectedVm();
File key = findTestFile("test.com.android.virt.pem");
// Act
VmInfo vmInfo =
runMicrodroidWithResignedImages(
key,
- /*keyOverrides=*/ Map.of(),
- /*isProtected=*/ true,
- /*updateBootconfigs=*/ true);
+ /* keyOverrides= */ Map.of(),
+ /* isProtected= */ true,
+ /* updateBootconfigs= */ true);
// Assert
vmInfo.mProcess.waitFor(5L, TimeUnit.SECONDS);
@@ -425,12 +442,12 @@
@CddTest(requirements = {"9.17/C-2-2", "9.17/C-2-6"})
public void testBootSucceedsWhenNonProtectedVmStartsWithImagesSignedWithDifferentKey()
throws Exception {
- assumeNonProtectedVmSupported();
+ assumeNonProtectedVm();
File key = findTestFile("test.com.android.virt.pem");
Map<String, File> keyOverrides = Map.of();
VmInfo vmInfo =
runMicrodroidWithResignedImages(
- key, keyOverrides, /*isProtected=*/ false, /*updateBootconfigs=*/ true);
+ key, keyOverrides, /* isProtected= */ false, /* updateBootconfigs= */ true);
assertThatEventually(
100000,
() -> getDevice().pullFileContents(LOG_PATH),
@@ -443,13 +460,13 @@
@CddTest(requirements = {"9.17/C-2-2", "9.17/C-2-6"})
public void testBootFailsWhenVbMetaDigestDoesNotMatchBootconfig() throws Exception {
// protectedVmWithImageSignedWithDifferentKeyRunsPvmfw() is the protected case.
- assumeNonProtectedVmSupported();
+ assumeNonProtectedVm();
// Sign everything with key1 except vbmeta
File key = findTestFile("test.com.android.virt.pem");
// To be able to stop it, it should be a daemon.
VmInfo vmInfo =
runMicrodroidWithResignedImages(
- key, Map.of(), /*isProtected=*/ false, /*updateBootconfigs=*/ false);
+ key, Map.of(), /* isProtected= */ false, /* updateBootconfigs= */ false);
// Wait so that init can print errors to console (time in cuttlefish >> in real device)
assertThatEventually(
100000,
@@ -521,26 +538,12 @@
}
@Test
- public void testTombstonesAreGeneratedUponUserspaceCrashOnNonPvm() throws Exception {
- assumeNonProtectedVmSupported();
+ public void testTombstonesAreGeneratedUponUserspaceCrash() throws Exception {
// TODO(b/291867858): tombstones are failing in HWASAN enabled Microdroid.
assumeFalse("tombstones are failing in HWASAN enabled Microdroid.", isHwasan());
- testTombstonesAreGeneratedUponUserspaceCrash(false);
- }
-
- @Test
- public void testTombstonesAreGeneratedUponUserspaceCrashOnPvm() throws Exception {
- assumeProtectedVmSupported();
- // TODO(b/291867858): tombstones are failing in HWASAN enabled Microdroid.
- assumeFalse("tombstones are failing in HWASAN enabled Microdroid.", isHwasan());
- testTombstonesAreGeneratedUponUserspaceCrash(true);
- }
-
- private void testTombstonesAreGeneratedUponUserspaceCrash(boolean protectedVm)
- throws Exception {
assertThat(
isTombstoneGeneratedWithCmd(
- protectedVm,
+ mProtectedVm,
"assets/vm_config.json",
"kill",
"-SIGSEGV",
@@ -549,28 +552,12 @@
}
@Test
- public void testTombstonesAreNotGeneratedIfNotExportedUponUserspaceCrashOnNonPvm()
- throws Exception {
- assumeNonProtectedVmSupported();
+ public void testTombstonesAreNotGeneratedIfNotExportedUponUserspaceCrash() throws Exception {
// TODO(b/291867858): tombstones are failing in HWASAN enabled Microdroid.
assumeFalse("tombstones are failing in HWASAN enabled Microdroid.", isHwasan());
- testTombstonesAreNotGeneratedIfNotExportedUponUserspaceCrash(false);
- }
-
- @Test
- public void testTombstonesAreNotGeneratedIfNotExportedUponUserspaceCrashOnPvm()
- throws Exception {
- assumeProtectedVmSupported();
- // TODO(b/291867858): tombstones are failing in HWASAN enabled Microdroid.
- assumeFalse("tombstones are failing in HWASAN enabled Microdroid.", isHwasan());
- testTombstonesAreNotGeneratedIfNotExportedUponUserspaceCrash(true);
- }
-
- private void testTombstonesAreNotGeneratedIfNotExportedUponUserspaceCrash(boolean protectedVm)
- throws Exception {
assertThat(
isTombstoneGeneratedWithCmd(
- protectedVm,
+ mProtectedVm,
"assets/vm_config_no_tombstone.json",
"kill",
"-SIGSEGV",
@@ -578,12 +565,13 @@
.isFalse();
}
- private void testTombstonesAreGeneratedUponKernelCrash(boolean protectedVm) throws Exception {
+ @Test
+ public void testTombstonesAreGeneratedUponKernelCrash() throws Exception {
assumeFalse("Cuttlefish is not supported", isCuttlefish());
assumeFalse("Skipping test because ramdump is disabled on user build", isUserBuild());
assertThat(
isTombstoneGeneratedWithCmd(
- protectedVm,
+ mProtectedVm,
"assets/vm_config.json",
"echo",
"c",
@@ -592,18 +580,6 @@
.isTrue();
}
- @Test
- public void testTombstonesAreGeneratedUponKernelCrashOnNonPvm() throws Exception {
- assumeNonProtectedVmSupported();
- testTombstonesAreGeneratedUponKernelCrash(/* protectedVm=*/ false);
- }
-
- @Test
- public void testTombstonesAreGeneratedUponKernelCrashOnPvm() throws Exception {
- assumeProtectedVmSupported();
- testTombstonesAreGeneratedUponKernelCrash(/* protectedVm=*/ true);
- }
-
private boolean isTombstoneGeneratedWithVmRunApp(
boolean protectedVm, boolean debuggable, String... additionalArgs) throws Exception {
// we can't use microdroid builder as it wants ADB connection (debuggable)
@@ -642,48 +618,18 @@
}
@Test
- public void testTombstonesAreGeneratedWithCrashPayloadOnPvm() throws Exception {
- assumeProtectedVmSupported();
+ public void testTombstonesAreGeneratedWithCrashPayload() throws Exception {
// TODO(b/291867858): tombstones are failing in HWASAN enabled Microdroid.
assumeFalse("tombstones are failing in HWASAN enabled Microdroid.", isHwasan());
- assertThat(
- isTombstoneGeneratedWithCrashPayload(
- /*protectedVm=*/ true, /*debuggable=*/ true))
+ assertThat(isTombstoneGeneratedWithCrashPayload(mProtectedVm, /* debuggable= */ true))
.isTrue();
}
@Test
- public void testTombstonesAreGeneratedWithCrashPayloadOnNonPvm() throws Exception {
- assumeNonProtectedVmSupported();
+ public void testTombstonesAreNotGeneratedWithCrashPayloadWhenNonDebuggable() throws Exception {
// TODO(b/291867858): tombstones are failing in HWASAN enabled Microdroid.
assumeFalse("tombstones are failing in HWASAN enabled Microdroid.", isHwasan());
- assertThat(
- isTombstoneGeneratedWithCrashPayload(
- /*protectedVm=*/ false, /*debuggable=*/ true))
- .isTrue();
- }
-
- @Test
- public void testTombstonesAreNotGeneratedWithCrashPayloadWhenNonDebuggableOnPvm()
- throws Exception {
- assumeProtectedVmSupported();
- // TODO(b/291867858): tombstones are failing in HWASAN enabled Microdroid.
- assumeFalse("tombstones are failing in HWASAN enabled Microdroid.", isHwasan());
- assertThat(
- isTombstoneGeneratedWithCrashPayload(
- /*protectedVm=*/ true, /*debuggable=*/ false))
- .isFalse();
- }
-
- @Test
- public void testTombstonesAreNotGeneratedWithCrashPayloadWhenNonDebuggableOnNonPvm()
- throws Exception {
- assumeNonProtectedVmSupported();
- // TODO(b/291867858): tombstones are failing in HWASAN enabled Microdroid.
- assumeFalse("tombstones are failing in HWASAN enabled Microdroid.", isHwasan());
- assertThat(
- isTombstoneGeneratedWithCrashPayload(
- /*protectedVm=*/ false, /*debuggable=*/ false))
+ assertThat(isTombstoneGeneratedWithCrashPayload(mProtectedVm, /* debuggable= */ false))
.isFalse();
}
@@ -694,62 +640,23 @@
}
@Test
- public void testTombstonesAreGeneratedWithCrashConfigOnPvm() throws Exception {
- assumeProtectedVmSupported();
+ public void testTombstonesAreGeneratedWithCrashConfig() throws Exception {
// TODO(b/291867858): tombstones are failing in HWASAN enabled Microdroid.
assumeFalse("tombstones are failing in HWASAN enabled Microdroid.", isHwasan());
- assertThat(isTombstoneGeneratedWithCrashConfig(/*protectedVm=*/ true, /*debuggable=*/ true))
+ assertThat(isTombstoneGeneratedWithCrashConfig(mProtectedVm, /* debuggable= */ true))
.isTrue();
}
@Test
- public void testTombstonesAreGeneratedWithCrashConfigOnNonPvm() throws Exception {
- assumeNonProtectedVmSupported();
+ public void testTombstonesAreNotGeneratedWithCrashConfigWhenNonDebuggable() throws Exception {
// TODO(b/291867858): tombstones are failing in HWASAN enabled Microdroid.
assumeFalse("tombstones are failing in HWASAN enabled Microdroid.", isHwasan());
- assertThat(
- isTombstoneGeneratedWithCrashConfig(
- /*protectedVm=*/ false, /*debuggable=*/ true))
- .isTrue();
- }
-
- @Test
- public void testTombstonesAreNotGeneratedWithCrashConfigWhenNonDebuggableOnPvm()
- throws Exception {
- assumeProtectedVmSupported();
- // TODO(b/291867858): tombstones are failing in HWASAN enabled Microdroid.
- assumeFalse("tombstones are failing in HWASAN enabled Microdroid.", isHwasan());
- assertThat(
- isTombstoneGeneratedWithCrashConfig(
- /*protectedVm=*/ true, /*debuggable=*/ false))
+ assertThat(isTombstoneGeneratedWithCrashConfig(mProtectedVm, /* debuggable= */ false))
.isFalse();
}
@Test
- public void testTombstonesAreNotGeneratedWithCrashConfigWhenNonDebuggableOnNonPvm()
- throws Exception {
- assumeNonProtectedVmSupported();
- // TODO(b/291867858): tombstones are failing in HWASAN enabled Microdroid.
- assumeFalse("tombstones are failing in HWASAN enabled Microdroid.", isHwasan());
- assertThat(
- isTombstoneGeneratedWithCrashConfig(
- /*protectedVm=*/ false, /*debuggable=*/ false))
- .isFalse();
- }
-
- @Test
- public void testTelemetryPushedAtomsOnNonPvm() throws Exception {
- assumeNonProtectedVmSupported();
- testTelemetryPushedAtoms(false);
- }
-
- @Test
- public void testTelemetryPushedAtomsOnPvm() throws Exception {
- assumeProtectedVmSupported();
- testTelemetryPushedAtoms(true);
- }
-
- private void testTelemetryPushedAtoms(boolean protectedVm) throws Exception {
+ public void testTelemetryPushedAtoms() throws Exception {
// Reset statsd config and report before the test
ConfigUtils.removeConfig(getDevice());
ReportUtils.clearReports(getDevice());
@@ -770,7 +677,7 @@
.debugLevel("full")
.memoryMib(minMemorySize())
.cpuTopology("match_host")
- .protectedVm(protectedVm)
+ .protectedVm(mProtectedVm)
.build(device);
microdroid.waitForBootComplete(BOOT_COMPLETE_TIMEOUT);
device.shutdownMicrodroid(microdroid);
@@ -797,7 +704,7 @@
data.get(0).getAtom().getVmCreationRequested();
assertThat(atomVmCreationRequested.getHypervisor())
.isEqualTo(AtomsProto.VmCreationRequested.Hypervisor.PKVM);
- assertThat(atomVmCreationRequested.getIsProtected()).isEqualTo(protectedVm);
+ assertThat(atomVmCreationRequested.getIsProtected()).isEqualTo(mProtectedVm);
assertThat(atomVmCreationRequested.getCreationSucceeded()).isTrue();
assertThat(atomVmCreationRequested.getBinderExceptionCode()).isEqualTo(0);
assertThat(atomVmCreationRequested.getVmIdentifier()).isEqualTo("VmRunApp");
@@ -826,19 +733,7 @@
@Test
@CddTest(requirements = {"9.17/C-1-1", "9.17/C-1-2", "9.17/C/1-3"})
- public void testMicrodroidBootsOnPvm() throws Exception {
- assumeProtectedVmSupported();
- testMicrodroidBoots(true);
- }
-
- @Test
- @CddTest(requirements = {"9.17/C-1-1", "9.17/C-1-2", "9.17/C/1-3"})
- public void testMicrodroidBootsOnNonPvm() throws Exception {
- assumeNonProtectedVmSupported();
- testMicrodroidBoots(false);
- }
-
- private void testMicrodroidBoots(boolean protectedVm) throws Exception {
+ public void testMicrodroidBoots() throws Exception {
CommandRunner android = new CommandRunner(getDevice());
final String configPath = "assets/vm_config.json"; // path inside the APK
@@ -847,7 +742,7 @@
.debugLevel("full")
.memoryMib(minMemorySize())
.cpuTopology("match_host")
- .protectedVm(protectedVm)
+ .protectedVm(mProtectedVm)
.build(getAndroidDevice());
mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT);
CommandRunner microdroid = new CommandRunner(mMicrodroidDevice);
@@ -906,25 +801,14 @@
}
@Test
- public void testMicrodroidRamUsageOnPvm() throws Exception {
- assumeProtectedVmSupported();
- testMicrodroidRamUsage(true);
- }
-
- @Test
- public void testMicrodroidRamUsageOnNonPvm() throws Exception {
- assumeNonProtectedVmSupported();
- testMicrodroidRamUsage(false);
- }
-
- private void testMicrodroidRamUsage(boolean protectedVm) throws Exception {
+ public void testMicrodroidRamUsage() throws Exception {
final String configPath = "assets/vm_config.json";
mMicrodroidDevice =
MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath)
.debugLevel("full")
.memoryMib(minMemorySize())
.cpuTopology("match_host")
- .protectedVm(protectedVm)
+ .protectedVm(mProtectedVm)
.build(getAndroidDevice());
mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT);
mMicrodroidDevice.enableAdbRoot();
@@ -949,8 +833,7 @@
for (Map.Entry<Integer, String> proc :
ProcessUtil.getProcessMap(microdroidExec).entrySet()) {
for (Map.Entry<String, Long> stat :
- ProcessUtil.getProcessSmapsRollup(proc.getKey(), microdroidExec)
- .entrySet()) {
+ ProcessUtil.getProcessSmapsRollup(proc.getKey(), microdroidExec).entrySet()) {
String name = stat.getKey().toLowerCase();
mMetrics.addTestMetric(
mMetricPrefix + "smaps/" + name + "/" + proc.getValue(),
@@ -1066,8 +949,8 @@
}
@Test
- public void testDevcieAssignment() throws Exception {
- assumeProtectedVmSupported();
+ public void testDeviceAssignment() throws Exception {
+ assumeProtectedVm();
assumeVfioPlatformSupported();
List<String> devices = getAssignableDevices();
@@ -1095,6 +978,11 @@
prepareVirtualizationTestSetup(getDevice());
getDevice().installPackage(findTestFile(APK_NAME), /* reinstall */ false);
+
+ // Skip test if given device doesn't support protected or non-protected VM.
+ assumeTrue(
+ "Microdroid is not supported for specific VM protection type",
+ getAndroidDevice().supportsMicrodroid(mProtectedVm));
}
@After
@@ -1119,16 +1007,12 @@
"android.permission.USE_CUSTOM_VIRTUAL_MACHINE");
}
- private void assumeProtectedVmSupported() throws Exception {
- assumeTrue(
- "Test skipped because protected VMs are not supported",
- getAndroidDevice().supportsMicrodroid(true));
+ private void assumeProtectedVm() throws Exception {
+ assumeTrue("This test is only for protected VM.", mProtectedVm);
}
- private void assumeNonProtectedVmSupported() throws Exception {
- assumeTrue(
- "Test skipped because non-protected VMs are not supported",
- getAndroidDevice().supportsMicrodroid(false));
+ private void assumeNonProtectedVm() throws Exception {
+ assumeFalse("This test is only for non-protected VM.", mProtectedVm);
}
private void assumeVfioPlatformSupported() throws Exception {
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
index 473a560..4b9f803 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -1103,11 +1103,18 @@
assertThat(dataItems.size()).isEqualTo(1);
assertThat(dataItems.get(0).getMajorType()).isEqualTo(MajorType.ARRAY);
List<DataItem> rootArrayItems = ((Array) dataItems.get(0)).getDataItems();
- assertThat(rootArrayItems.size()).isAtLeast(2); // Public key and one certificate
+ assertThat(rootArrayItems.size()).isAtLeast(2); // Root public key and one certificate
if (mProtectedVm) {
- // pvmfw truncates the DICE chain it gets, so we expect exactly entries for: public key,
- // Microdroid and app payload.
- assertThat(rootArrayItems.size()).isEqualTo(3);
+ if (isFeatureEnabled(VirtualMachineManager.FEATURE_DICE_CHANGES)) {
+ // When a true DICE chain is created, we expect the root public key, at least one
+ // entry for the boot before pvmfw, then pvmfw, vm_entry (Microdroid kernel) and
+ // Microdroid payload entries.
+ assertThat(rootArrayItems.size()).isAtLeast(5);
+ } else {
+ // pvmfw truncates the DICE chain it gets, so we expect exactly entries for
+ // public key, vm_entry (Microdroid kernel) and Microdroid payload.
+ assertThat(rootArrayItems.size()).isEqualTo(3);
+ }
}
}
@@ -2247,9 +2254,4 @@
.that(KERNEL_VERSION)
.isNotEqualTo("5.4");
}
-
- private void assumeFeatureEnabled(String featureName) throws Exception {
- VirtualMachineManager vmm = getVirtualMachineManager();
- assumeTrue(featureName + " not enabled", vmm.isFeatureEnabled(featureName));
- }
}
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index 781d83f..790cdb5 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -36,6 +36,7 @@
IVirtualizationService::IVirtualizationService,
IVirtualizationService::FEATURE_PAYLOAD_NON_ROOT,
IVirtualizationService::FEATURE_VENDOR_MODULES,
+ IVirtualizationService::FEATURE_DICE_CHANGES,
MemoryTrimLevel::MemoryTrimLevel,
Partition::Partition,
PartitionType::PartitionType,
@@ -274,10 +275,11 @@
// This approach is quite cumbersome, but will do the work for the short term.
// TODO(b/298012279): make this scalable.
match feature {
+ FEATURE_DICE_CHANGES => Ok(cfg!(dice_changes)),
FEATURE_PAYLOAD_NON_ROOT => Ok(cfg!(payload_not_root)),
FEATURE_VENDOR_MODULES => Ok(cfg!(vendor_modules)),
_ => {
- warn!("unknown feature {}", feature);
+ warn!("unknown feature {feature}");
Ok(false)
}
}
@@ -400,8 +402,9 @@
// Check if partition images are labeled incorrectly. This is to prevent random images
// which are not protected by the Android Verified Boot (e.g. bits downloaded by apps) from
- // being loaded in a pVM. This applies to everything but the instance image in the raw config,
- // and everything but the non-executable, generated partitions in the app config.
+ // being loaded in a pVM. This applies to everything but the instance image in the raw
+ // config, and everything but the non-executable, generated partitions in the app
+ // config.
config
.disks
.iter()
@@ -455,7 +458,7 @@
}
};
- let devices_dtbo = if !config.devices.is_empty() {
+ if !config.devices.is_empty() {
let mut set = HashSet::new();
for device in config.devices.iter() {
let path = canonicalize(device)
@@ -466,30 +469,8 @@
.or_binder_exception(ExceptionCode::ILLEGAL_ARGUMENT);
}
}
- let dtbo_path = temporary_directory.join("dtbo");
- // open a writable file descriptor for vfio_handler
- let dtbo = File::create(&dtbo_path).map_err(|e| {
- error!("Failed to create VM DTBO file {dtbo_path:?}: {e:?}");
- Status::new_service_specific_error_str(
- -1,
- Some(format!("Failed to create VM DTBO file {dtbo_path:?}: {e:?}")),
- )
- })?;
- GLOBAL_SERVICE
- .bindDevicesToVfioDriver(&config.devices, &ParcelFileDescriptor::new(dtbo))?;
-
- // open (again) a readable file descriptor for crosvm
- let dtbo = File::open(&dtbo_path).map_err(|e| {
- error!("Failed to open VM DTBO file {dtbo_path:?}: {e:?}");
- Status::new_service_specific_error_str(
- -1,
- Some(format!("Failed to open VM DTBO file {dtbo_path:?}: {e:?}")),
- )
- })?;
- Some(dtbo)
- } else {
- None
- };
+ GLOBAL_SERVICE.bindDevicesToVfioDriver(&config.devices)?;
+ }
// Actually start the VM.
let crosvm_config = CrosvmConfig {
@@ -515,7 +496,6 @@
detect_hangup: is_app_config,
gdb_port,
vfio_devices: config.devices.iter().map(PathBuf::from).collect(),
- devices_dtbo,
};
let instance = Arc::new(
VmInstance::new(
@@ -989,10 +969,10 @@
/// struct.
#[derive(Debug, Default)]
struct State {
- /// The VMs which have been started. When VMs are started a weak reference is added to this list
- /// while a strong reference is returned to the caller over Binder. Once all copies of the
- /// Binder client are dropped the weak reference here will become invalid, and will be removed
- /// from the list opportunistically the next time `add_vm` is called.
+ /// The VMs which have been started. When VMs are started a weak reference is added to this
+ /// list while a strong reference is returned to the caller over Binder. Once all copies of
+ /// the Binder client are dropped the weak reference here will become invalid, and will be
+ /// removed from the list opportunistically the next time `add_vm` is called.
vms: Vec<Weak<VmInstance>>,
}
diff --git a/virtualizationmanager/src/crosvm.rs b/virtualizationmanager/src/crosvm.rs
index 6372fa8..77dd76f 100644
--- a/virtualizationmanager/src/crosvm.rs
+++ b/virtualizationmanager/src/crosvm.rs
@@ -116,7 +116,6 @@
pub detect_hangup: bool,
pub gdb_port: Option<NonZeroU16>,
pub vfio_devices: Vec<PathBuf>,
- pub devices_dtbo: Option<File>,
}
/// A disk image to pass to crosvm for a VM.
@@ -719,9 +718,7 @@
for device in &config.vfio_devices {
command.arg(vfio_argument_for_platform_device(device)?);
}
- if let Some(_dtbo) = &config.devices_dtbo {
- // TODO(b/291192693): add dtbo to command line
- }
+ // TODO(b/291192693): add dtbo to command line when assigned device is not empty.
Ok(())
}
diff --git a/virtualizationservice/Android.bp b/virtualizationservice/Android.bp
index ac9c1df..c00445d 100644
--- a/virtualizationservice/Android.bp
+++ b/virtualizationservice/Android.bp
@@ -41,6 +41,8 @@
"libvsock",
"libserde",
"libserde_xml_rs",
+ "libservice_vm_comm",
+ "libservice_vm_manager",
],
apex_available: ["com.android.virt"],
}
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
index fa50d54..9255e1c 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
@@ -22,6 +22,7 @@
import android.system.virtualizationservice.VirtualMachineDebugInfo;
interface IVirtualizationService {
+ const String FEATURE_DICE_CHANGES = "com.android.kvm.DICE_CHANGES";
const String FEATURE_PAYLOAD_NON_ROOT = "com.android.kvm.PAYLOAD_NON_ROOT";
const String FEATURE_VENDOR_MODULES = "com.android.kvm.VENDOR_MODULES";
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVfioHandler.aidl b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVfioHandler.aidl
index cb3ed0b..01906cb 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVfioHandler.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVfioHandler.aidl
@@ -28,7 +28,13 @@
* Bind given devices to vfio driver.
*
* @param devices paths of sysfs nodes of devices to assign.
+ */
+ void bindDevicesToVfioDriver(in String[] devices);
+
+ /**
+ * Store VM DTBO via the file descriptor.
+ *
* @param dtbo writable file descriptor to store VM DTBO.
*/
- void bindDevicesToVfioDriver(in String[] devices, in ParcelFileDescriptor dtbo);
+ void writeVmDtbo(in ParcelFileDescriptor dtbo);
}
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
index 9d698ea..62d66c0 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
@@ -68,7 +68,6 @@
* Bind given devices to vfio driver.
*
* @param devices paths of sysfs nodes of devices to assign.
- * @param dtbo writable file descriptor to store VM DTBO.
*/
- void bindDevicesToVfioDriver(in String[] devices, in ParcelFileDescriptor dtbo);
+ void bindDevicesToVfioDriver(in String[] devices);
}
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 8586597..645a82b 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -40,7 +40,7 @@
use rustutils::system_properties;
use serde::Deserialize;
use std::collections::{HashMap, HashSet};
-use std::fs::{self, create_dir, remove_dir_all, set_permissions, Permissions};
+use std::fs::{self, create_dir, remove_dir_all, set_permissions, File, Permissions};
use std::io::{Read, Write};
use std::os::unix::fs::PermissionsExt;
use std::os::unix::raw::{pid_t, uid_t};
@@ -160,10 +160,20 @@
fn requestCertificate(&self, csr: &[u8]) -> binder::Result<Vec<u8>> {
check_manage_access()?;
info!("Received csr. Getting certificate...");
- request_certificate(csr)
- .context("Failed to get certificate")
+ if cfg!(remote_attestation) {
+ request_certificate(csr)
+ .context("Failed to get certificate")
+ .with_log()
+ .or_service_specific_exception(-1)
+ } else {
+ Err(Status::new_exception_str(
+ ExceptionCode::UNSUPPORTED_OPERATION,
+ Some(
+ "requestCertificate is not supported with the remote_attestation feature disabled",
+ ),
+ ))
.with_log()
- .or_service_specific_exception(-1)
+ }
}
fn getAssignableDevices(&self) -> binder::Result<Vec<AssignableDevice>> {
@@ -206,17 +216,23 @@
Ok(ret)
}
- fn bindDevicesToVfioDriver(
- &self,
- devices: &[String],
- dtbo: &ParcelFileDescriptor,
- ) -> binder::Result<()> {
+ fn bindDevicesToVfioDriver(&self, devices: &[String]) -> binder::Result<()> {
check_use_custom_virtual_machine()?;
let vfio_service: Strong<dyn IVfioHandler> =
wait_for_interface(<BpVfioHandler as IVfioHandler>::get_descriptor())?;
- vfio_service.bindDevicesToVfioDriver(devices, dtbo)?;
+ vfio_service.bindDevicesToVfioDriver(devices)?;
+
+ let dtbo_path = Path::new(TEMPORARY_DIRECTORY).join("common").join("dtbo");
+ if !dtbo_path.exists() {
+ // open a writable file descriptor for vfio_handler
+ let dtbo = File::create(&dtbo_path)
+ .context("Failed to create VM DTBO file")
+ .or_service_specific_exception(-1)?;
+ vfio_service.writeVmDtbo(&ParcelFileDescriptor::new(dtbo))?;
+ }
+
Ok(())
}
}
diff --git a/virtualizationservice/src/main.rs b/virtualizationservice/src/main.rs
index cdb3ac9..fd668bc 100644
--- a/virtualizationservice/src/main.rs
+++ b/virtualizationservice/src/main.rs
@@ -18,7 +18,6 @@
mod atom;
mod remote_provisioning;
mod rkpvm;
-mod service_vm;
use crate::aidl::{
remove_temporary_dir, BINDER_SERVICE_IDENTIFIER, TEMPORARY_DIRECTORY,
@@ -29,8 +28,9 @@
use anyhow::Error;
use binder::{register_lazy_service, BinderFeatures, ProcessState, ThreadState};
use log::{info, Level};
-use std::fs::read_dir;
+use std::fs::{create_dir, read_dir};
use std::os::unix::raw::{pid_t, uid_t};
+use std::path::Path;
const LOG_TAG: &str = "VirtualizationService";
const _REMOTELY_PROVISIONED_COMPONENT_SERVICE_NAME: &str =
@@ -59,6 +59,9 @@
clear_temporary_files().expect("Failed to delete old temporary files");
+ let common_dir_path = Path::new(TEMPORARY_DIRECTORY).join("common");
+ create_dir(common_dir_path).expect("Failed to create common directory");
+
ProcessState::start_thread_pool();
let service = VirtualizationServiceInternal::init();
diff --git a/virtualizationservice/src/remote_provisioning.rs b/virtualizationservice/src/remote_provisioning.rs
index 1acbcee..1c8d1e6 100644
--- a/virtualizationservice/src/remote_provisioning.rs
+++ b/virtualizationservice/src/remote_provisioning.rs
@@ -14,17 +14,20 @@
//! IRemotelyProvisionedComponent HAL implementation.
+use crate::rkpvm;
use android_hardware_security_rkp::aidl::android::hardware::security::keymint::{
DeviceInfo::DeviceInfo,
IRemotelyProvisionedComponent::{
- BnRemotelyProvisionedComponent, IRemotelyProvisionedComponent, STATUS_REMOVED,
+ BnRemotelyProvisionedComponent, IRemotelyProvisionedComponent, STATUS_FAILED,
+ STATUS_REMOVED,
},
MacedPublicKey::MacedPublicKey,
ProtectedData::ProtectedData,
RpcHardwareInfo::{RpcHardwareInfo, CURVE_NONE, MIN_SUPPORTED_NUM_KEYS_IN_CSR},
};
+use anyhow::Context;
use avflog::LogResult;
-use binder::{BinderFeatures, ExceptionCode, Interface, Result as BinderResult, Status, Strong};
+use binder::{BinderFeatures, Interface, IntoBinderResult, Result as BinderResult, Status, Strong};
/// Constructs a binder object that implements `IRemotelyProvisionedComponent`.
pub(crate) fn new_binder() -> Strong<dyn IRemotelyProvisionedComponent> {
@@ -52,11 +55,22 @@
fn generateEcdsaP256KeyPair(
&self,
- _testMode: bool,
- _macedPublicKey: &mut MacedPublicKey,
+ testMode: bool,
+ macedPublicKey: &mut MacedPublicKey,
) -> BinderResult<Vec<u8>> {
- // TODO(b/274881098): Implement this.
- Err(Status::new_exception(ExceptionCode::UNSUPPORTED_OPERATION, None)).with_log()
+ if testMode {
+ return Err(Status::new_service_specific_error_str(
+ STATUS_REMOVED,
+ Some("generateEcdsaP256KeyPair does not support test mode in IRPC v3+ HAL."),
+ ))
+ .with_log();
+ }
+ let key_pair = rkpvm::generate_ecdsa_p256_key_pair()
+ .context("Failed to generate ECDSA P-256 key pair")
+ .with_log()
+ .or_service_specific_exception(STATUS_FAILED)?;
+ macedPublicKey.macedKey = key_pair.maced_public_key;
+ Ok(key_pair.key_blob)
}
fn generateCertificateRequest(
@@ -77,10 +91,23 @@
fn generateCertificateRequestV2(
&self,
- _keysToSign: &[MacedPublicKey],
- _challenge: &[u8],
+ keysToSign: &[MacedPublicKey],
+ challenge: &[u8],
) -> BinderResult<Vec<u8>> {
- // TODO(b/274881098): Implement this.
- Err(Status::new_exception(ExceptionCode::UNSUPPORTED_OPERATION, None)).with_log()
+ const MAX_CHALLENGE_SIZE: usize = 64;
+ if challenge.len() > MAX_CHALLENGE_SIZE {
+ let message = format!(
+ "Challenge is too big. Actual: {:?}. Maximum: {:?}.",
+ challenge.len(),
+ MAX_CHALLENGE_SIZE
+ );
+ return Err(Status::new_service_specific_error_str(STATUS_FAILED, Some(message)))
+ .with_log();
+ }
+ // TODO(b/299259624): Validate the MAC of the keys to certify.
+ rkpvm::generate_certificate_request(keysToSign, challenge)
+ .context("Failed to generate certificate request")
+ .with_log()
+ .or_service_specific_exception(STATUS_FAILED)
}
}
diff --git a/virtualizationservice/src/rkpvm.rs b/virtualizationservice/src/rkpvm.rs
index bb05edd..80953b5 100644
--- a/virtualizationservice/src/rkpvm.rs
+++ b/virtualizationservice/src/rkpvm.rs
@@ -16,19 +16,45 @@
//! The RKP VM will be recognized and attested by the RKP server periodically and
//! serves as a trusted platform to attest a client VM.
-use crate::service_vm;
-use anyhow::{anyhow, Result};
-use log::info;
-use std::time::Duration;
+use android_hardware_security_rkp::aidl::android::hardware::security::keymint::MacedPublicKey::MacedPublicKey;
+use anyhow::{bail, Context, Result};
+use service_vm_comm::{EcdsaP256KeyPair, GenerateCertificateRequestParams, Request, Response};
+use service_vm_manager::ServiceVm;
pub(crate) fn request_certificate(csr: &[u8]) -> Result<Vec<u8>> {
- let vm = service_vm::start()?;
+ let mut vm = ServiceVm::start()?;
- // TODO(b/274441673): The host can send the CSR to the RKP VM for attestation.
- // Wait for VM to finish.
- vm.wait_for_death_with_timeout(Duration::from_secs(10))
- .ok_or_else(|| anyhow!("Timed out waiting for VM exit"))?;
+ // TODO(b/271275206): Send the correct request type with client VM's
+ // information to be attested.
+ let request = Request::Reverse(csr.to_vec());
+ match vm.process_request(request).context("Failed to process request")? {
+ Response::Reverse(cert) => Ok(cert),
+ _ => bail!("Incorrect response type"),
+ }
+}
- info!("service_vm: Finished getting the certificate");
- Ok([b"Return: ", csr].concat())
+pub(crate) fn generate_ecdsa_p256_key_pair() -> Result<EcdsaP256KeyPair> {
+ let mut vm = ServiceVm::start()?;
+ let request = Request::GenerateEcdsaP256KeyPair;
+ match vm.process_request(request).context("Failed to process request")? {
+ Response::GenerateEcdsaP256KeyPair(key_pair) => Ok(key_pair),
+ _ => bail!("Incorrect response type"),
+ }
+}
+
+pub(crate) fn generate_certificate_request(
+ keys_to_sign: &[MacedPublicKey],
+ challenge: &[u8],
+) -> Result<Vec<u8>> {
+ let params = GenerateCertificateRequestParams {
+ keys_to_sign: keys_to_sign.iter().map(|v| v.macedKey.to_vec()).collect(),
+ challenge: challenge.to_vec(),
+ };
+ let request = Request::GenerateCertificateRequest(params);
+
+ let mut vm = ServiceVm::start()?;
+ match vm.process_request(request).context("Failed to process request")? {
+ Response::GenerateCertificateRequest(csr) => Ok(csr),
+ _ => bail!("Incorrect response type"),
+ }
}
diff --git a/virtualizationservice/src/service_vm.rs b/virtualizationservice/src/service_vm.rs
deleted file mode 100644
index 5a1744f..0000000
--- a/virtualizationservice/src/service_vm.rs
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright 2023, The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-//! Service VM.
-
-use android_system_virtualizationservice::{
- aidl::android::system::virtualizationservice::{
- CpuTopology::CpuTopology, DiskImage::DiskImage,
- IVirtualizationService::IVirtualizationService, Partition::Partition,
- PartitionType::PartitionType, VirtualMachineConfig::VirtualMachineConfig,
- VirtualMachineRawConfig::VirtualMachineRawConfig,
- },
- binder::ParcelFileDescriptor,
-};
-use anyhow::{Context, Result};
-use log::info;
-use std::fs::{File, OpenOptions};
-use std::path::Path;
-use vmclient::VmInstance;
-
-const VIRT_DATA_DIR: &str = "/data/misc/apexdata/com.android.virt";
-const RIALTO_PATH: &str = "/apex/com.android.virt/etc/rialto.bin";
-const INSTANCE_IMG_NAME: &str = "service_vm_instance.img";
-const INSTANCE_IMG_SIZE_BYTES: i64 = 1 << 20; // 1MB
-const MEMORY_MB: i32 = 300;
-
-/// Starts the service VM and returns its instance.
-/// The same instance image is used for different VMs.
-/// TODO(b/278858244): Allow only one service VM running at each time.
-pub fn start() -> Result<VmInstance> {
- let virtmgr = vmclient::VirtualizationService::new().context("Failed to spawn VirtMgr")?;
- let service = virtmgr.connect().context("Failed to connect to VirtMgr")?;
- info!("Connected to VirtMgr for service VM");
-
- let vm = vm_instance(service.as_ref())?;
-
- vm.start().context("Failed to start service VM")?;
- info!("Service VM started");
- Ok(vm)
-}
-
-fn vm_instance(service: &dyn IVirtualizationService) -> Result<VmInstance> {
- let instance_img = instance_img(service)?;
- let writable_partitions = vec![Partition {
- label: "vm-instance".to_owned(),
- image: Some(instance_img),
- writable: true,
- }];
- let rialto = File::open(RIALTO_PATH).context("Failed to open Rialto kernel binary")?;
- let config = VirtualMachineConfig::RawConfig(VirtualMachineRawConfig {
- name: String::from("Service VM"),
- bootloader: Some(ParcelFileDescriptor::new(rialto)),
- disks: vec![DiskImage { image: None, partitions: writable_partitions, writable: true }],
- protectedVm: true,
- memoryMib: MEMORY_MB,
- cpuTopology: CpuTopology::ONE_CPU,
- platformVersion: "~1.0".to_string(),
- gdbPort: 0, // No gdb
- ..Default::default()
- });
- let console_out = None;
- let console_in = None;
- let log = None;
- let callback = None;
- VmInstance::create(service, &config, console_out, console_in, log, callback)
- .context("Failed to create service VM")
-}
-
-fn instance_img(service: &dyn IVirtualizationService) -> Result<ParcelFileDescriptor> {
- let instance_img_path = Path::new(VIRT_DATA_DIR).join(INSTANCE_IMG_NAME);
- if instance_img_path.exists() {
- // TODO(b/298174584): Try to recover if the service VM is triggered by rkpd.
- return Ok(OpenOptions::new()
- .read(true)
- .write(true)
- .open(instance_img_path)
- .map(ParcelFileDescriptor::new)?);
- }
- let instance_img = OpenOptions::new()
- .create(true)
- .read(true)
- .write(true)
- .open(instance_img_path)
- .map(ParcelFileDescriptor::new)?;
- service.initializeWritablePartition(
- &instance_img,
- INSTANCE_IMG_SIZE_BYTES,
- PartitionType::ANDROID_VM_INSTANCE,
- )?;
- Ok(instance_img)
-}
diff --git a/virtualizationservice/vfio_handler/src/aidl.rs b/virtualizationservice/vfio_handler/src/aidl.rs
index bb9faf1..1c3c5d9 100644
--- a/virtualizationservice/vfio_handler/src/aidl.rs
+++ b/virtualizationservice/vfio_handler/src/aidl.rs
@@ -41,20 +41,34 @@
impl Interface for VfioHandler {}
impl IVfioHandler for VfioHandler {
- fn bindDevicesToVfioDriver(
- &self,
- devices: &[String],
- dtbo: &ParcelFileDescriptor,
- ) -> binder::Result<()> {
+ fn bindDevicesToVfioDriver(&self, devices: &[String]) -> binder::Result<()> {
// permission check is already done by IVirtualizationServiceInternal.
if !*IS_VFIO_SUPPORTED {
return Err(anyhow!("VFIO-platform not supported"))
.or_binder_exception(ExceptionCode::UNSUPPORTED_OPERATION);
}
devices.iter().try_for_each(|x| bind_device(Path::new(x)))?;
+ Ok(())
+ }
- write_dtbo(dtbo)?;
+ fn writeVmDtbo(&self, dtbo_fd: &ParcelFileDescriptor) -> binder::Result<()> {
+ let dtbo_path = get_dtbo_img_path()?;
+ let mut dtbo_img = File::open(dtbo_path)
+ .context("Failed to open DTBO partition")
+ .or_service_specific_exception(-1)?;
+ let dt_table_header = get_dt_table_header(&mut dtbo_img)?;
+ let vm_dtbo_idx = system_properties::read("ro.boot.hypervisor.vm_dtbo_idx")
+ .context("Failed to read vm_dtbo_idx")
+ .or_service_specific_exception(-1)?
+ .ok_or_else(|| anyhow!("vm_dtbo_idx is none"))
+ .or_service_specific_exception(-1)?;
+ let vm_dtbo_idx = vm_dtbo_idx
+ .parse()
+ .context("vm_dtbo_idx is not an integer")
+ .or_service_specific_exception(-1)?;
+ let dt_table_entry = get_dt_table_entry(&mut dtbo_img, &dt_table_header, vm_dtbo_idx)?;
+ write_vm_full_dtbo_from_img(&mut dtbo_img, &dt_table_entry, dtbo_fd)?;
Ok(())
}
}
@@ -254,7 +268,7 @@
Ok(dt_table_entry)
}
-fn filter_dtbo_from_img(
+fn write_vm_full_dtbo_from_img(
dtbo_img_file: &mut File,
entry: &DtTableEntry,
dtbo_fd: &ParcelFileDescriptor,
@@ -273,31 +287,9 @@
.context("Failed to clone File from ParcelFileDescriptor")
.or_binder_exception(ExceptionCode::BAD_PARCELABLE)?;
- // TODO(b/296796644): Filter dtbo.img, not writing all information.
dtbo_fd
.write_all(&buffer)
.context("Failed to write dtbo file")
.or_service_specific_exception(-1)?;
Ok(())
}
-
-fn write_dtbo(dtbo_fd: &ParcelFileDescriptor) -> binder::Result<()> {
- let dtbo_path = get_dtbo_img_path()?;
- let mut dtbo_img = File::open(dtbo_path)
- .context("Failed to open DTBO partition")
- .or_service_specific_exception(-1)?;
-
- let dt_table_header = get_dt_table_header(&mut dtbo_img)?;
- let vm_dtbo_idx = system_properties::read("ro.boot.hypervisor.vm_dtbo_idx")
- .context("Failed to read vm_dtbo_idx")
- .or_service_specific_exception(-1)?
- .ok_or_else(|| anyhow!("vm_dtbo_idx is none"))
- .or_service_specific_exception(-1)?;
- let vm_dtbo_idx = vm_dtbo_idx
- .parse()
- .context("vm_dtbo_idx is not an integer")
- .or_service_specific_exception(-1)?;
- let dt_table_entry = get_dt_table_entry(&mut dtbo_img, &dt_table_header, vm_dtbo_idx)?;
- filter_dtbo_from_img(&mut dtbo_img, &dt_table_entry, dtbo_fd)?;
- Ok(())
-}
diff --git a/vm_payload/include/vm_payload.h b/vm_payload/include/vm_payload.h
index 5cc2d1e..c28cd42 100644
--- a/vm_payload/include/vm_payload.h
+++ b/vm_payload/include/vm_payload.h
@@ -105,10 +105,10 @@
* behalf of the VM by the host app. All data is encrypted using a key known
* only to the VM, so the host cannot decrypt it, but may delete it.
*
- * \return the path to the APK contents, or NULL if no encrypted storage was
- * requested in the VM configuration. If non-null the returned string should not
- * be deleted or freed by the application and remains valid for the lifetime of
- * the VM.
+ * \return the path to the encrypted storage directory, or NULL if no encrypted
+ * storage was requested in the VM configuration. If non-null the returned
+ * string should not be deleted or freed by the application and remains valid
+ * for the lifetime of the VM.
*/
const char* _Nullable AVmPayload_getEncryptedStoragePath(void);
diff --git a/vmclient/src/error_code.rs b/vmclient/src/error_code.rs
index a7c442f..10e6d4d 100644
--- a/vmclient/src/error_code.rs
+++ b/vmclient/src/error_code.rs
@@ -20,8 +20,8 @@
/// Error code for all other errors not listed below.
Unknown,
- /// Error code indicating that the payload can't be verified due to various reasons (e.g invalid
- /// merkle tree, invalid formats, etc).
+ /// Error code indicating that the payload can't be verified due to various reasons (e.g
+ /// invalid merkle tree, invalid formats, etc).
PayloadVerificationFailed,
/// Error code indicating that the payload is verified, but has changed since the last boot.
diff --git a/vmclient/src/lib.rs b/vmclient/src/lib.rs
index 7c0383b..9f1d7d1 100644
--- a/vmclient/src/lib.rs
+++ b/vmclient/src/lib.rs
@@ -35,8 +35,8 @@
VirtualMachineState::VirtualMachineState,
},
binder::{
- wait_for_interface, BinderFeatures, DeathRecipient, FromIBinder, IBinder, Interface,
- ParcelFileDescriptor, Result as BinderResult, StatusCode, Strong,
+ BinderFeatures, DeathRecipient, FromIBinder, IBinder, Interface, ParcelFileDescriptor,
+ Result as BinderResult, StatusCode, Strong,
},
};
use command_fds::CommandFdExt;
@@ -53,9 +53,6 @@
time::Duration,
};
-const VIRTUALIZATION_SERVICE_BINDER_SERVICE_IDENTIFIER: &str =
- "android.system.virtualizationservice";
-
const VIRTMGR_PATH: &str = "/apex/com.android.virt/bin/virtmgr";
const VIRTMGR_THREADS: usize = 2;
@@ -128,11 +125,6 @@
}
}
-/// Connects to the VirtualizationService AIDL service.
-pub fn connect() -> Result<Strong<dyn IVirtualizationService>, StatusCode> {
- wait_for_interface(VIRTUALIZATION_SERVICE_BINDER_SERVICE_IDENTIFIER)
-}
-
/// A virtual machine which has been started by the VirtualizationService.
pub struct VmInstance {
/// The `IVirtualMachine` Binder object representing the VM.