Merge "Create full VM DTBO only once under /data/misc/virtualizationservice" into main
diff --git a/Android.bp b/Android.bp
index 1fae793..d1086ba 100644
--- a/Android.bp
+++ b/Android.bp
@@ -34,6 +34,7 @@
config_namespace: "ANDROID",
bool_variables: [
"release_avf_enable_multi_tenant_microdroid_vm",
+ "release_avf_enable_vendor_modules",
],
properties: [
"cfgs",
@@ -46,5 +47,8 @@
release_avf_enable_multi_tenant_microdroid_vm: {
cfgs: ["payload_not_root"],
},
+ 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/product_packages.mk b/apex/product_packages.mk
index ef84551..4c03836 100644
--- a/apex/product_packages.mk
+++ b/apex/product_packages.mk
@@ -37,3 +37,10 @@
PRODUCT_FSVERITY_GENERATE_METADATA := true
PRODUCT_AVF_ENABLED := true
+
+# The cheap build flags dependency management system until there is a proper one.
+ifdef RELEASE_AVF_ENABLE_DEVICE_ASSIGNMENT
+ ifndef RELEASE_AVF_ENABLE_VENDOR_MODULES
+ $(error RELEASE_AVF_ENABLE_VENDOR_MODULES must also be enabled)
+ endif
+endif
diff --git a/apex/virtualizationservice.xml b/apex/virtualizationservice.xml
new file mode 100644
index 0000000..0ce1e10
--- /dev/null
+++ b/apex/virtualizationservice.xml
@@ -0,0 +1,7 @@
+<manifest version="1.0" type="framework">
+ <hal format="aidl">
+ <name>android.system.virtualization</name>
+ <version>3</version>
+ <fqname>IRemotelyProvisionedComponent/avf</fqname>
+ </hal>
+</manifest>
diff --git a/javalib/api/test-current.txt b/javalib/api/test-current.txt
index cf95770..bedb267 100644
--- a/javalib/api/test-current.txt
+++ b/javalib/api/test-current.txt
@@ -13,9 +13,15 @@
public static final class VirtualMachineConfig.Builder {
method @NonNull @RequiresPermission(android.system.virtualmachine.VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION) public android.system.virtualmachine.VirtualMachineConfig.Builder setPayloadConfigPath(@NonNull String);
- method @NonNull @RequiresPermission(android.system.virtualmachine.VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION) public android.system.virtualmachine.VirtualMachineConfig.Builder setVendorDiskImage(@NonNull java.io.File);
+ method @FlaggedApi("RELEASE_AVF_ENABLE_VENDOR_MODULES") @NonNull @RequiresPermission(android.system.virtualmachine.VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION) public android.system.virtualmachine.VirtualMachineConfig.Builder setVendorDiskImage(@NonNull java.io.File);
method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setVmConsoleInputSupported(boolean);
}
+ 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_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/VirtualMachineConfig.java b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
index b307854..cc8f65b 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -21,6 +21,7 @@
import static java.util.Objects.requireNonNull;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -903,6 +904,7 @@
*/
@TestApi
@RequiresPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION)
+ @FlaggedApi("RELEASE_AVF_ENABLE_VENDOR_MODULES")
@NonNull
public Builder setVendorDiskImage(@NonNull File vendorDiskImage) {
mVendorDiskImage = vendorDiskImage;
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
index b7ea22c..a0ba0c6 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
@@ -23,11 +23,15 @@
import android.annotation.Nullable;
import android.annotation.RequiresFeature;
import android.annotation.RequiresPermission;
+import android.annotation.StringDef;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.annotation.WorkerThread;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.os.RemoteException;
import android.sysprop.HypervisorProperties;
+import android.system.virtualizationservice.IVirtualizationService;
import android.util.ArrayMap;
import com.android.internal.annotations.GuardedBy;
@@ -96,6 +100,35 @@
public static final int CAPABILITY_NON_PROTECTED_VM = 2;
/**
+ * Features provided by {@link VirtualMachineManager}.
+ *
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @StringDef(
+ prefix = "FEATURE_",
+ value = {FEATURE_PAYLOAD_NOT_ROOT, FEATURE_VENDOR_MODULES})
+ public @interface Features {}
+
+ /**
+ * Feature to run payload as non-root user.
+ *
+ * @hide
+ */
+ @TestApi
+ public static final String FEATURE_PAYLOAD_NOT_ROOT =
+ IVirtualizationService.FEATURE_PAYLOAD_NON_ROOT;
+
+ /**
+ * Feature to allow vendor modules in Microdroid.
+ *
+ * @hide
+ */
+ @TestApi
+ public static final String FEATURE_VENDOR_MODULES =
+ IVirtualizationService.FEATURE_VENDOR_MODULES;
+
+ /**
* Returns a set of flags indicating what this implementation of virtualization is capable of.
*
* @see #CAPABILITY_PROTECTED_VM
@@ -277,4 +310,22 @@
}
return null;
}
+
+ /**
+ * Returns {@code true} if given {@code featureName} is enabled.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(VirtualMachine.MANAGE_VIRTUAL_MACHINE_PERMISSION)
+ public boolean isFeatureEnabled(@Features String featureName) throws VirtualMachineException {
+ synchronized (sCreateLock) {
+ VirtualizationService service = VirtualizationService.getInstance();
+ try {
+ return service.getBinder().isFeatureEnabled(featureName);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+ }
}
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 ef5e8bb..3b53b63 100644
--- a/libs/service_vm_comm/src/lib.rs
+++ b/libs/service_vm_comm/src/lib.rs
@@ -22,5 +22,5 @@
mod message;
mod vsock;
-pub use message::{Request, Response};
-pub use vsock::host_port;
+pub use message::{EcdsaP256KeyPair, GenerateCertificateRequestParams, Request, Response};
+pub use vsock::VmType;
diff --git a/libs/service_vm_comm/src/message.rs b/libs/service_vm_comm/src/message.rs
index ebbefcb..0eddcfb 100644
--- a/libs/service_vm_comm/src/message.rs
+++ b/libs/service_vm_comm/src/message.rs
@@ -19,6 +19,8 @@
use serde::{Deserialize, Serialize};
+type MacedPublicKey = Vec<u8>;
+
/// Represents a request to be sent to the service VM.
///
/// Each request has a corresponding response item.
@@ -27,6 +29,14 @@
/// Reverse the order of the bytes in the provided byte array.
/// Currently this is only used for testing.
Reverse(Vec<u8>),
+
+ /// Generates a new ECDSA P-256 key pair that can be attested by the remote
+ /// server.
+ GenerateEcdsaP256KeyPair,
+
+ /// Creates a certificate signing request to be sent to the
+ /// provisioning server.
+ GenerateCertificateRequest(GenerateCertificateRequestParams),
}
/// Represents a response to a request sent to the service VM.
@@ -36,4 +46,34 @@
pub enum Response {
/// Reverse the order of the bytes in the provided byte array.
Reverse(Vec<u8>),
+
+ /// Returns the new ECDSA P-256 key pair.
+ GenerateEcdsaP256KeyPair(EcdsaP256KeyPair),
+
+ /// Returns a CBOR Certificate Signing Request (Csr) serialized into a byte array.
+ GenerateCertificateRequest(Vec<u8>),
+}
+
+/// Represents the params passed to GenerateCertificateRequest
+#[derive(Clone, Debug, Serialize, Deserialize)]
+pub struct GenerateCertificateRequestParams {
+ /// Contains the set of keys to certify.
+ pub keys_to_sign: Vec<MacedPublicKey>,
+
+ /// challenge contains a byte strong from the provisioning server which will be
+ /// included in the signed data of the CSR structure.
+ /// The supported sizes is between 0 and 64 bytes, inclusive.
+ pub challenge: Vec<u8>,
+}
+
+/// Represents an ECDSA P-256 key pair.
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub struct EcdsaP256KeyPair {
+ /// Contains a CBOR-encoded public key specified in:
+ ///
+ /// hardware/interfaces/security/rkp/aidl/android/hardware/security/keymint/MacedPublicKey.aidl
+ pub maced_public_key: MacedPublicKey,
+
+ /// Contains a handle to the private key.
+ pub key_blob: Vec<u8>,
}
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/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/main.rs b/rialto/src/main.rs
index b34b9de..d777b2d 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::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,7 +140,7 @@
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()?);
+ let response = requests::process_request(vsock_stream.read_request()?)?;
vsock_stream.write_response(&response)?;
vsock_stream.flush()?;
vsock_stream.shutdown()?;
diff --git a/rialto/src/requests/api.rs b/rialto/src/requests/api.rs
index 11fdde4..c4b2d8e 100644
--- a/rialto/src/requests/api.rs
+++ b/rialto/src/requests/api.rs
@@ -14,16 +14,27 @@
//! This module contains the main API for the request processing module.
+use super::rkp;
+use crate::error::Result;
use alloc::vec::Vec;
use service_vm_comm::{Request, Response};
/// Processes a request and returns the corresponding response.
/// This function serves as the entry point for the request processing
/// module.
-pub fn process_request(request: Request) -> Response {
- match request {
+pub fn process_request(request: Request) -> Result<Response> {
+ let response = match request {
Request::Reverse(v) => Response::Reverse(reverse(v)),
- }
+ Request::GenerateEcdsaP256KeyPair => {
+ let res = rkp::generate_ecdsa_p256_key_pair()?;
+ Response::GenerateEcdsaP256KeyPair(res)
+ }
+ Request::GenerateCertificateRequest(p) => {
+ let res = rkp::generate_certificate_request(p)?;
+ Response::GenerateCertificateRequest(res)
+ }
+ };
+ Ok(response)
}
fn reverse(payload: Vec<u8>) -> Vec<u8> {
diff --git a/rialto/src/requests/mod.rs b/rialto/src/requests/mod.rs
index ca22777..2ed568c 100644
--- a/rialto/src/requests/mod.rs
+++ b/rialto/src/requests/mod.rs
@@ -15,5 +15,6 @@
//! This module contains functions for the request processing.
mod api;
+mod rkp;
pub use api::process_request;
diff --git a/rialto/src/requests/rkp.rs b/rialto/src/requests/rkp.rs
new file mode 100644
index 0000000..5977bfb
--- /dev/null
+++ b/rialto/src/requests/rkp.rs
@@ -0,0 +1,33 @@
+// 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 functions related to the attestation of the
+//! service VM via the RKP (Remote Key Provisionning) server.
+
+use crate::error::Result;
+use alloc::vec::Vec;
+use service_vm_comm::{EcdsaP256KeyPair, GenerateCertificateRequestParams};
+
+pub(super) fn generate_ecdsa_p256_key_pair() -> Result<EcdsaP256KeyPair> {
+ // TODO(b/299055662): Generate the key pair.
+ let key_pair = EcdsaP256KeyPair { maced_public_key: Vec::new(), key_blob: Vec::new() };
+ Ok(key_pair)
+}
+
+pub(super) fn generate_certificate_request(
+ _params: GenerateCertificateRequestParams,
+) -> Result<Vec<u8>> {
+ // TODO(b/299256925): Generate the certificate request
+ Ok(Vec::new())
+}
diff --git a/rialto/tests/test.rs b/rialto/tests/test.rs
index e9bdab6..20d00b5 100644
--- a/rialto/tests/test.rs
+++ b/rialto/tests/test.rs
@@ -16,180 +16,90 @@
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::{Context, Result};
use log::info;
-use service_vm_comm::{host_port, Request, Response};
+use service_vm_comm::{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<()> {
+ let mut vm = start_service_vm(VmType::ProtectedVm)?;
-#[test]
-fn boot_rialto_in_unprotected_vm_successfully() -> Result<()> {
- boot_rialto_successfully(
- UNSIGNED_RIALTO_PATH,
- false, // protected_vm
- )
-}
-
-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),
- );
-
- // 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)?;
Ok(())
}
-fn android_log_fd() -> io::Result<File> {
- let (reader_fd, writer_fd) = nix::unistd::pipe()?;
+#[test]
+fn process_requests_in_non_protected_vm() -> Result<()> {
+ let mut vm = start_service_vm(VmType::NonProtectedVm)?;
- // 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)
+ check_processing_reverse_request(&mut vm)?;
+ Ok(())
}
-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)?;
- info!("Received response: {response:?}.");
+ let response = vm.process_request(&request)?;
+ info!("Received response '{response:?}' for the request '{request:?}'.");
let expected_response: Vec<u8> = message.as_bytes().iter().rev().cloned().collect();
assert_eq!(Response::Reverse(expected_response), response);
Ok(())
}
+
+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..b3618a6
--- /dev/null
+++ b/service_vm_manager/Android.bp
@@ -0,0 +1,24 @@
+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",
+ "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..c27570c
--- /dev/null
+++ b/service_vm_manager/src/lib.rs
@@ -0,0 +1,206 @@
+// 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::{ensure, Context, Result};
+use log::{info, warn};
+use service_vm_comm::{Request, Response, 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::thread;
+use std::time::Duration;
+use vmclient::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);
+
+/// 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.
+ /// TODO(b/278858244): Allow only one service VM running at each time.
+ pub fn start() -> Result<Self> {
+ let instance_img_path = Path::new(VIRT_DATA_DIR).join(INSTANCE_IMG_NAME);
+ let vm = protected_vm_instance(instance_img_path)?;
+ Self::start_vm(vm, VmType::ProtectedVm)
+ }
+
+ /// 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(request)?;
+ self.read_response()
+ }
+
+ /// Sends the request to the service VM.
+ fn write_request(&mut self, request: &Request) -> 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)
+ }
+}
+
+impl Drop for ServiceVm {
+ fn drop(&mut self) {
+ // Wait till the service VM finishes releasing all the resources.
+ match self.vm.wait_for_death_with_timeout(Duration::from_secs(10)) {
+ Some(e) => info!("Exit the service VM: {e:?}"),
+ None => warn!("Timed out waiting for service VM exit"),
+ }
+ }
+}
+
+/// 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/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
index a928dcf..473a560 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -72,7 +72,6 @@
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.function.ThrowingRunnable;
@@ -1130,6 +1129,17 @@
assertThrows(Exception.class, () -> launchVmAndGetCdis("test_vm"));
}
+ @Test
+ public void isFeatureEnabled_requiresManagePermission() throws Exception {
+ revokePermission(VirtualMachine.MANAGE_VIRTUAL_MACHINE_PERMISSION);
+
+ VirtualMachineManager vmm = getVirtualMachineManager();
+ SecurityException e =
+ assertThrows(SecurityException.class, () -> vmm.isFeatureEnabled("whatever"));
+ assertThat(e)
+ .hasMessageThat()
+ .contains("android.permission.MANAGE_VIRTUAL_MACHINE permission");
+ }
private static final UUID MICRODROID_PARTITION_UUID =
UUID.fromString("cf9afe9a-0662-11ec-a329-c32663a09d75");
@@ -1524,10 +1534,10 @@
}
@Test
- @Ignore // Figure out how to run this conditionally
@CddTest(requirements = {"9.17/C-1-1"})
public void payloadIsNotRoot() throws Exception {
assumeSupportedDevice();
+ assumeFeatureEnabled(VirtualMachineManager.FEATURE_PAYLOAD_NOT_ROOT);
VirtualMachineConfig config =
newVmConfigBuilder()
@@ -2084,6 +2094,7 @@
@Test
public void configuringVendorDiskImageRequiresCustomPermission() throws Exception {
assumeSupportedDevice();
+ assumeFeatureEnabled(VirtualMachineManager.FEATURE_VENDOR_MODULES);
File vendorDiskImage =
new File("/data/local/tmp/cts/microdroid/test_microdroid_vendor_image.img");
@@ -2108,6 +2119,7 @@
@Test
public void bootsWithVendorPartition() throws Exception {
assumeSupportedDevice();
+ assumeFeatureEnabled(VirtualMachineManager.FEATURE_VENDOR_MODULES);
grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
@@ -2235,4 +2247,9 @@
.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 fa99c63..49773a9 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -34,6 +34,8 @@
IVirtualMachine::{BnVirtualMachine, IVirtualMachine},
IVirtualMachineCallback::IVirtualMachineCallback,
IVirtualizationService::IVirtualizationService,
+ IVirtualizationService::FEATURE_PAYLOAD_NON_ROOT,
+ IVirtualizationService::FEATURE_VENDOR_MODULES,
MemoryTrimLevel::MemoryTrimLevel,
Partition::Partition,
PartitionType::PartitionType,
@@ -264,6 +266,22 @@
// Delegate to the global service, including checking the permission.
GLOBAL_SERVICE.getAssignableDevices()
}
+
+ /// Returns whether given feature is enabled
+ fn isFeatureEnabled(&self, feature: &str) -> binder::Result<bool> {
+ check_manage_access()?;
+
+ // This approach is quite cumbersome, but will do the work for the short term.
+ // TODO(b/298012279): make this scalable.
+ match feature {
+ FEATURE_PAYLOAD_NON_ROOT => Ok(cfg!(payload_not_root)),
+ FEATURE_VENDOR_MODULES => Ok(cfg!(vendor_modules)),
+ _ => {
+ warn!("unknown feature {}", feature);
+ Ok(false)
+ }
+ }
+ }
}
impl VirtualizationService {
@@ -310,6 +328,8 @@
let requester_uid = get_calling_uid();
let requester_debug_pid = get_calling_pid();
+ check_config_features(config)?;
+
// Allocating VM context checks the MANAGE_VIRTUAL_MACHINE permission.
let (vm_context, cid, temporary_directory) = self.create_vm_context(requester_debug_pid)?;
@@ -380,8 +400,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()
@@ -946,10 +967,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>>,
}
@@ -1066,6 +1087,24 @@
}
}
+fn check_no_vendor_modules(config: &VirtualMachineConfig) -> binder::Result<()> {
+ let VirtualMachineConfig::AppConfig(config) = config else { return Ok(()) };
+ if let Some(custom_config) = &config.customConfig {
+ if custom_config.vendorImage.is_some() || custom_config.customKernelImage.is_some() {
+ return Err(anyhow!("vendor modules feature is disabled"))
+ .or_binder_exception(ExceptionCode::UNSUPPORTED_OPERATION);
+ }
+ }
+ Ok(())
+}
+
+fn check_config_features(config: &VirtualMachineConfig) -> binder::Result<()> {
+ if !cfg!(vendor_modules) {
+ check_no_vendor_modules(config)?;
+ }
+ Ok(())
+}
+
fn clone_or_prepare_logger_fd(
debug_config: &DebugConfig,
fd: Option<&ParcelFileDescriptor>,
diff --git a/virtualizationservice/Android.bp b/virtualizationservice/Android.bp
index 74f88c5..c00445d 100644
--- a/virtualizationservice/Android.bp
+++ b/virtualizationservice/Android.bp
@@ -21,6 +21,7 @@
},
prefer_rlib: true,
rustlibs: [
+ "android.hardware.security.rkp-V3-rust",
"android.system.virtualizationcommon-rust",
"android.system.virtualizationservice-rust",
"android.system.virtualizationservice_internal-rust",
@@ -40,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 df72e49..fa50d54 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
@@ -22,6 +22,9 @@
import android.system.virtualizationservice.VirtualMachineDebugInfo;
interface IVirtualizationService {
+ const String FEATURE_PAYLOAD_NON_ROOT = "com.android.kvm.PAYLOAD_NON_ROOT";
+ const String FEATURE_VENDOR_MODULES = "com.android.kvm.VENDOR_MODULES";
+
/**
* Create the VM with the given config file, and return a handle to it ready to start it. If
* `consoleOutFd` is provided then console output from the VM will be sent to it. If
@@ -61,4 +64,7 @@
* Get a list of assignable device types.
*/
AssignableDevice[] getAssignableDevices();
+
+ /** Returns whether given feature is enabled. */
+ boolean isFeatureEnabled(in String feature);
}
diff --git a/virtualizationservice/src/main.rs b/virtualizationservice/src/main.rs
index 18b026d..fd668bc 100644
--- a/virtualizationservice/src/main.rs
+++ b/virtualizationservice/src/main.rs
@@ -16,8 +16,8 @@
mod aidl;
mod atom;
+mod remote_provisioning;
mod rkpvm;
-mod service_vm;
use crate::aidl::{
remove_temporary_dir, BINDER_SERVICE_IDENTIFIER, TEMPORARY_DIRECTORY,
@@ -33,6 +33,8 @@
use std::path::Path;
const LOG_TAG: &str = "VirtualizationService";
+const _REMOTELY_PROVISIONED_COMPONENT_SERVICE_NAME: &str =
+ "android.system.virtualization.IRemotelyProvisionedComponent/avf";
fn get_calling_pid() -> pid_t {
ThreadState::get_calling_pid()
@@ -65,7 +67,13 @@
let service = VirtualizationServiceInternal::init();
let service = BnVirtualizationServiceInternal::new_binder(service, BinderFeatures::default());
register_lazy_service(BINDER_SERVICE_IDENTIFIER, service.as_binder()).unwrap();
- info!("Registered Binder service, joining threadpool.");
+ info!("Registered Binder service {}.", BINDER_SERVICE_IDENTIFIER);
+
+ // The IRemotelyProvisionedComponent service is only supposed to be triggered by rkpd for
+ // RKP VM attestation.
+ let _remote_provisioning_service = remote_provisioning::new_binder();
+ // TODO(b/274881098): Register the RKP service when the implementation is ready.
+
ProcessState::join_thread_pool();
}
diff --git a/virtualizationservice/src/remote_provisioning.rs b/virtualizationservice/src/remote_provisioning.rs
new file mode 100644
index 0000000..1acbcee
--- /dev/null
+++ b/virtualizationservice/src/remote_provisioning.rs
@@ -0,0 +1,86 @@
+// 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.
+
+//! IRemotelyProvisionedComponent HAL implementation.
+
+use android_hardware_security_rkp::aidl::android::hardware::security::keymint::{
+ DeviceInfo::DeviceInfo,
+ IRemotelyProvisionedComponent::{
+ BnRemotelyProvisionedComponent, IRemotelyProvisionedComponent, STATUS_REMOVED,
+ },
+ MacedPublicKey::MacedPublicKey,
+ ProtectedData::ProtectedData,
+ RpcHardwareInfo::{RpcHardwareInfo, CURVE_NONE, MIN_SUPPORTED_NUM_KEYS_IN_CSR},
+};
+use avflog::LogResult;
+use binder::{BinderFeatures, ExceptionCode, Interface, Result as BinderResult, Status, Strong};
+
+/// Constructs a binder object that implements `IRemotelyProvisionedComponent`.
+pub(crate) fn new_binder() -> Strong<dyn IRemotelyProvisionedComponent> {
+ BnRemotelyProvisionedComponent::new_binder(
+ AvfRemotelyProvisionedComponent {},
+ BinderFeatures::default(),
+ )
+}
+
+struct AvfRemotelyProvisionedComponent {}
+
+impl Interface for AvfRemotelyProvisionedComponent {}
+
+#[allow(non_snake_case)]
+impl IRemotelyProvisionedComponent for AvfRemotelyProvisionedComponent {
+ fn getHardwareInfo(&self) -> BinderResult<RpcHardwareInfo> {
+ Ok(RpcHardwareInfo {
+ versionNumber: 3,
+ rpcAuthorName: String::from("Android Virtualization Framework"),
+ supportedEekCurve: CURVE_NONE,
+ uniqueId: Some(String::from("Android Virtualization Framework 1")),
+ supportedNumKeysInCsr: MIN_SUPPORTED_NUM_KEYS_IN_CSR,
+ })
+ }
+
+ fn generateEcdsaP256KeyPair(
+ &self,
+ _testMode: bool,
+ _macedPublicKey: &mut MacedPublicKey,
+ ) -> BinderResult<Vec<u8>> {
+ // TODO(b/274881098): Implement this.
+ Err(Status::new_exception(ExceptionCode::UNSUPPORTED_OPERATION, None)).with_log()
+ }
+
+ fn generateCertificateRequest(
+ &self,
+ _testMode: bool,
+ _keysToSign: &[MacedPublicKey],
+ _endpointEncryptionCertChain: &[u8],
+ _challenge: &[u8],
+ _deviceInfo: &mut DeviceInfo,
+ _protectedData: &mut ProtectedData,
+ ) -> BinderResult<Vec<u8>> {
+ Err(Status::new_service_specific_error_str(
+ STATUS_REMOVED,
+ Some("This method was deprecated in v3 of the interface."),
+ ))
+ .with_log()
+ }
+
+ fn generateCertificateRequestV2(
+ &self,
+ _keysToSign: &[MacedPublicKey],
+ _challenge: &[u8],
+ ) -> BinderResult<Vec<u8>> {
+ // TODO(b/274881098): Implement this.
+ Err(Status::new_exception(ExceptionCode::UNSUPPORTED_OPERATION, None)).with_log()
+ }
+}
diff --git a/virtualizationservice/src/rkpvm.rs b/virtualizationservice/src/rkpvm.rs
index bb05edd..2c9230b 100644
--- a/virtualizationservice/src/rkpvm.rs
+++ b/virtualizationservice/src/rkpvm.rs
@@ -16,19 +16,18 @@
//! 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 anyhow::{bail, Context, Result};
+use service_vm_comm::{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"))?;
-
- info!("service_vm: Finished getting the certificate");
- Ok([b"Return: ", csr].concat())
+ // 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"),
+ }
}
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/vm/src/main.rs b/vm/src/main.rs
index 4c44496..0af9791 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -98,10 +98,12 @@
storage_size: Option<u64>,
/// Path to custom kernel image to use when booting Microdroid.
+ #[cfg(vendor_modules)]
#[arg(long)]
kernel: Option<PathBuf>,
/// Path to disk image containing vendor-specific modules.
+ #[cfg(vendor_modules)]
#[arg(long)]
vendor: Option<PathBuf>,
@@ -110,6 +112,28 @@
devices: Vec<PathBuf>,
}
+impl MicrodroidConfig {
+ #[cfg(vendor_modules)]
+ fn kernel(&self) -> &Option<PathBuf> {
+ &self.kernel
+ }
+
+ #[cfg(not(vendor_modules))]
+ fn kernel(&self) -> Option<PathBuf> {
+ None
+ }
+
+ #[cfg(vendor_modules)]
+ fn vendor(&self) -> &Option<PathBuf> {
+ &self.vendor
+ }
+
+ #[cfg(not(vendor_modules))]
+ fn vendor(&self) -> Option<PathBuf> {
+ None
+ }
+}
+
#[derive(Args)]
/// Flags for the run_app subcommand
pub struct RunAppConfig {
diff --git a/vm/src/run.rs b/vm/src/run.rs
index fc8d7e0..1ba9dec 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -84,25 +84,25 @@
)?;
}
- let storage = if let Some(path) = config.microdroid.storage {
+ let storage = if let Some(ref path) = config.microdroid.storage {
if !path.exists() {
command_create_partition(
service.as_ref(),
- &path,
+ path,
config.microdroid.storage_size.unwrap_or(10 * 1024 * 1024),
PartitionType::ENCRYPTEDSTORE,
)?;
}
- Some(open_parcel_file(&path, true)?)
+ Some(open_parcel_file(path, true)?)
} else {
None
};
let kernel =
- config.microdroid.kernel.as_ref().map(|p| open_parcel_file(p, false)).transpose()?;
+ config.microdroid.kernel().as_ref().map(|p| open_parcel_file(p, false)).transpose()?;
let vendor =
- config.microdroid.vendor.as_ref().map(|p| open_parcel_file(p, false)).transpose()?;
+ config.microdroid.vendor().as_ref().map(|p| open_parcel_file(p, false)).transpose()?;
let extra_idsig_files: Result<Vec<File>, _> =
config.extra_idsigs.iter().map(File::open).collect();
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);