Merge "virtmgr: Pass VM DTBO path to crosvm if devices assigned" into main
diff --git a/TEST_MAPPING b/TEST_MAPPING
index a9193d7..6983fde 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -34,6 +34,9 @@
},
{
"name": "libapkzip.test"
+ },
+ {
+ "name": "libsecretkeeper_comm.test"
}
],
"avf-postsubmit": [
diff --git a/apex/Android.bp b/apex/Android.bp
index e04dbd2..a889d08 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -73,11 +73,13 @@
config_namespace: "ANDROID",
bool_variables: [
"release_avf_enable_device_assignment",
+ "release_avf_enable_remote_attestation",
"release_avf_enable_vendor_modules",
],
properties: [
"arch",
"prebuilts",
+ "vintf_fragments",
],
}
@@ -112,7 +114,6 @@
"vm",
],
prebuilts: [
- "com.android.virt.init.rc",
"features_com.android.virt.xml",
"microdroid_initrd_debuggable",
"microdroid_initrd_normal",
@@ -122,7 +123,6 @@
],
host_required: [
"vm_shell",
- "prepare_device_vfio",
],
apps: [
"EmptyPayloadApp",
@@ -149,6 +149,15 @@
"microdroid_gki.json",
],
},
+ release_avf_enable_remote_attestation: {
+ prebuilts: ["com.android.virt.init_attestation_enabled.rc"],
+ vintf_fragments: [
+ "virtualizationservice.xml",
+ ],
+ conditions_default: {
+ prebuilts: ["com.android.virt.init.rc"],
+ },
+ },
},
}
@@ -172,7 +181,14 @@
prebuilt_etc {
name: "com.android.virt.init.rc",
src: "virtualizationservice.rc",
- filename: "init.rc",
+ filename: "virtualizationservice.rc",
+ installable: false,
+}
+
+prebuilt_etc {
+ name: "com.android.virt.init_attestation_enabled.rc",
+ src: "virtualizationservice_attestation_enabled.rc",
+ filename: "virtualizationservice.rc",
installable: false,
}
@@ -183,12 +199,6 @@
installable: false,
}
-sh_binary_host {
- name: "prepare_device_vfio",
- src: "prepare_device_vfio.sh",
- filename: "prepare_device_vfio.sh",
-}
-
// Virt apex needs a custom signer for its payload
python_binary_host {
name: "sign_virt_apex",
diff --git a/apex/prepare_device_vfio.sh b/apex/prepare_device_vfio.sh
deleted file mode 100755
index de2d502..0000000
--- a/apex/prepare_device_vfio.sh
+++ /dev/null
@@ -1,176 +0,0 @@
-#!/bin/bash
-
-# Copyright 2023 Google Inc. All rights reserved.
-#
-# 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.
-
-# prepare_device_vfio.sh: prepares a device for VFIO assignment by binding a VFIO driver to it
-
-adb="${ADB:="adb"}" # ADB command to use
-vfio_dir="/dev/vfio"
-platform_bus="/sys/bus/platform"
-vfio_reset_required="/sys/module/vfio_platform/parameters/reset_required"
-vfio_noiommu_param="/sys/module/vfio/parameters/enable_unsafe_noiommu_mode"
-vfio_unsafe_interrupts_param="/sys/module/vfio_iommu_type1/parameters/allow_unsafe_interrupts"
-
-function print_help() {
- echo "prepare_device_vfio.sh prepares a device for VFIO assignment"
- echo ""
- echo " Usage:"
- echo " $0 DEVICE_NAME"
- echo " Prepare device DEVICE_NAME for VFIO assignment."
- echo ""
- echo " help - prints this help message"
-}
-
-function cmd() {
- $adb shell $@
-}
-
-function tcmd() {
- trap "echo \"Error: adb shell command '$@' failed\" ; exit 1" ERR
- $adb shell $@
-}
-
-function ensure_root() {
- # Check user id
- if [ $(cmd "id -u") != 0 ]; then
- read -p "Must run as root; restart ADBD? [y/n] " answer
- case $answer in
- [Yy]* )
- $adb root && $adb wait-for-device && sleep 3 || exit 1
- ;;
- * )
- exit 1
- esac
- fi
-}
-
-function check_vfio() {
- cmd "[ -c $vfio_dir/vfio ]"
- if [ $? -ne 0 ]; then
- echo "cannot find $vfio_dir/vfio"
- exit 1
- fi
-
- cmd "[ -d $platform_bus/drivers/vfio-platform ]"
- if [ $? -ne 0 ]; then
- echo "VFIO-platform is not supported"
- exit 1
- fi
-}
-
-function check_device() {
- cmd "[ -d $device_sys ]"
- if [ $? -ne 0 ]; then
- echo "no device $device ($device_sys)"
- exit 1
- fi
-}
-
-function get_device_iommu_group() {
- local group=$(cmd "basename \$(readlink \"$device_sys/iommu_group\")")
- if [ $? -eq 0 ]; then
- echo $group
- else
- echo ""
- fi
-}
-
-function misc_setup() {
- # VFIO NOIOMMU check
- if [ -z "$group" ]; then
- echo "$device_sys does not have an IOMMU group - setting $vfio_noiommu_param"
- tcmd "echo y > \"$vfio_noiommu_param\""
- fi
-
- # Disable SELinux to allow virtualizationmanager and crosvm to access sysfs
- echo "[*WARN*] setenforce=0: SELinux is disabled"
- tcmd "setenforce 0"
-
- # Samsung IOMMU does not report interrupt remapping support, so enable unsafe uinterrupts
- if [ -n "$group" ]; then
- local iommu_drv=$(cmd "basename \$(readlink \"$device_sys/iommu/device/driver\")")
- if [ "$iommu_drv" = "samsung-sysmmu-v9" ]; then
- tcmd "echo y > \"$vfio_unsafe_interrupts_param\""
- fi
- fi
-}
-
-function bind_vfio_driver() {
- # Check if non-VFIO driver is currently bound, ie unbinding is needed
- cmd "[ -e \"$device_driver\" ] && \
- [ ! \$(basename \$(readlink \"$device_driver\")) = \"vfio-platform\" ]"
- if [ $? -eq 0 ]; then
- # Unbind current driver
- tcmd "echo \"$device\" > \"$device_driver/unbind\""
- fi
-
- # Bind to VFIO driver
- cmd "[ ! -e \"$device_driver\" ]"
- if [ $? -eq 0 ]; then
- # Bind vfio-platform driver
- tcmd "echo \"vfio-platform\" > \"$device_sys/driver_override\""
- tcmd "echo \"$device\" > \"$platform_bus/drivers_probe\""
- sleep 2
- fi
-}
-
-function verify_vfio_driver() {
- # Verify new VFIO file structure
- group=$(get_device_iommu_group)
- if [ -z "$group" ]; then
- echo "cannot setup VFIO-NOIOMMU for $device_sys"
- exit 1
- fi
-
- cmd "[ ! -c \"$vfio_dir/$group\" ] || \
- [ ! -e \"$device_driver\" ] || \
- [ ! \$(basename \$(readlink \"$device_driver\")) = \"vfio-platform\" ]"
- if [ $? -eq 0 ]; then
- echo "could not bind $device to VFIO platform driver"
-
- if [ $(cmd "cat $vfio_reset_required") = Y ]; then
- echo "VFIO device reset handler must be registered. Either unset $vfio_reset_required, \
-or register a reset handler for $device_sys"
- fi
- exit 1
- fi
-}
-
-function prepare_device() {
- device="$1"
- device_sys="/sys/bus/platform/devices/$device"
- device_driver="$device_sys/driver"
-
- ensure_root
- check_vfio
- check_device
- group=$(get_device_iommu_group)
- misc_setup
-
- bind_vfio_driver
- verify_vfio_driver
-
- echo "Device: $device_sys"
- echo "IOMMU group: $group"
- echo "VFIO group file: $vfio_dir/$group"
- echo "Ready!"
-}
-
-cmd=$1
-
-case $cmd in
- ""|help) print_help ;;
- *) prepare_device "$cmd" $@ ;;
-esac
diff --git a/apex/virtualizationservice.xml b/apex/virtualizationservice.xml
index 0ce1e10..60f466f 100644
--- a/apex/virtualizationservice.xml
+++ b/apex/virtualizationservice.xml
@@ -1,6 +1,6 @@
<manifest version="1.0" type="framework">
<hal format="aidl">
- <name>android.system.virtualization</name>
+ <name>android.hardware.security.keymint</name>
<version>3</version>
<fqname>IRemotelyProvisionedComponent/avf</fqname>
</hal>
diff --git a/apex/virtualizationservice_attestation_enabled.rc b/apex/virtualizationservice_attestation_enabled.rc
new file mode 100644
index 0000000..8eaccae
--- /dev/null
+++ b/apex/virtualizationservice_attestation_enabled.rc
@@ -0,0 +1,22 @@
+# Copyright (C) 2021 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 virtualizationservice /apex/com.android.virt/bin/virtualizationservice
+ class main
+ user system
+ group system
+ interface aidl android.system.virtualizationservice
+ interface aidl android.hardware.security.keymint.IRemotelyProvisionedComponent/avf
+ disabled
+ oneshot
diff --git a/libs/bssl/Android.bp b/libs/bssl/Android.bp
index 0a2f334..ff45af9 100644
--- a/libs/bssl/Android.bp
+++ b/libs/bssl/Android.bp
@@ -23,6 +23,7 @@
rustlibs: [
"libbssl_avf_error_nostd",
"libbssl_ffi_nostd",
+ "libciborium_nostd",
"libcoset_nostd",
"liblog_rust_nostd",
"libzeroize_nostd",
@@ -44,5 +45,6 @@
defaults: ["libbssl_avf_test_defaults"],
rustlibs: [
"libbssl_avf_nostd",
+ "libcoset_nostd",
],
}
diff --git a/libs/bssl/error/src/code.rs b/libs/bssl/error/src/code.rs
index 9b661e9..a318a07 100644
--- a/libs/bssl/error/src/code.rs
+++ b/libs/bssl/error/src/code.rs
@@ -25,6 +25,8 @@
NoError,
Global(GlobalError),
Cipher(CipherError),
+ Ec(EcError),
+ Ecdsa(EcdsaError),
Unknown(BsslReasonCode, BsslLibraryCode),
}
@@ -102,3 +104,86 @@
write!(f, "An error occurred in a Cipher function: {self:?}")
}
}
+
+/// Errors occurred in the EC functions.
+///
+/// The values are from:
+/// boringssl/src/include/openssl/ec.h
+#[allow(missing_docs)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub enum EcError {
+ BufferTooSmall,
+ CoordinatesOutOfRange,
+ D2IEcpkparametersFailure,
+ EcGroupNewByNameFailure,
+ Group2PkparametersFailure,
+ I2DEcpkparametersFailure,
+ IncompatibleObjects,
+ InvalidCompressedPoint,
+ InvalidCompressionBit,
+ InvalidEncoding,
+ InvalidField,
+ InvalidForm,
+ InvalidGroupOrder,
+ InvalidPrivateKey,
+ MissingParameters,
+ MissingPrivateKey,
+ NonNamedCurve,
+ NotInitialized,
+ Pkparameters2GroupFailure,
+ PointAtInfinity,
+ PointIsNotOnCurve,
+ SlotFull,
+ UndefinedGenerator,
+ UnknownGroup,
+ UnknownOrder,
+ WrongOrder,
+ BignumOutOfRange,
+ WrongCurveParameters,
+ DecodeError,
+ EncodeError,
+ GroupMismatch,
+ InvalidCofactor,
+ PublicKeyValidationFailed,
+ InvalidScalar,
+}
+
+impl From<EcError> for ReasonCode {
+ fn from(e: EcError) -> ReasonCode {
+ ReasonCode::Ec(e)
+ }
+}
+
+impl fmt::Display for EcError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "An error occurred in an EC function: {self:?}")
+ }
+}
+
+/// Errors occurred in the ECDSA functions.
+///
+/// The values are from:
+/// boringssl/src/include/openssl/ecdsa.h
+#[allow(missing_docs)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub enum EcdsaError {
+ BadSignature,
+ MissingParameters,
+ NeedNewSetupValues,
+ NotImplemented,
+ RandomNumberGenerationFailed,
+ EncodeError,
+ TooManyIterations,
+}
+
+impl From<EcdsaError> for ReasonCode {
+ fn from(e: EcdsaError) -> ReasonCode {
+ ReasonCode::Ecdsa(e)
+ }
+}
+
+impl fmt::Display for EcdsaError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "An error occurred in an ECDSA function: {self:?}")
+ }
+}
diff --git a/libs/bssl/error/src/lib.rs b/libs/bssl/error/src/lib.rs
index 88929af..89865d4 100644
--- a/libs/bssl/error/src/lib.rs
+++ b/libs/bssl/error/src/lib.rs
@@ -21,7 +21,7 @@
use core::{fmt, result};
use serde::{Deserialize, Serialize};
-pub use crate::code::{CipherError, GlobalError, ReasonCode};
+pub use crate::code::{CipherError, EcError, EcdsaError, GlobalError, ReasonCode};
/// libbssl_avf result type.
pub type Result<T> = result::Result<T, Error>;
@@ -34,6 +34,12 @@
/// An unexpected internal error occurred.
InternalError,
+
+ /// Failed to decode the COSE_Key.
+ CoseKeyDecodingFailed,
+
+ /// Unimplemented operation.
+ Unimplemented,
}
impl fmt::Display for Error {
@@ -43,6 +49,8 @@
write!(f, "Failed to invoke the BoringSSL API: {api_name:?}. Reason: {reason}")
}
Self::InternalError => write!(f, "An unexpected internal error occurred"),
+ Self::CoseKeyDecodingFailed => write!(f, "Failed to decode the COSE_Key"),
+ Self::Unimplemented => write!(f, "Unimplemented operation"),
}
}
}
@@ -53,6 +61,7 @@
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum ApiName {
BN_new,
+ BN_bin2bn,
BN_bn2bin_padded,
CBB_flush,
CBB_len,
@@ -64,11 +73,16 @@
EC_KEY_marshal_private_key,
EC_KEY_parse_private_key,
EC_KEY_new_by_curve_name,
+ EC_KEY_set_public_key_affine_coordinates,
EC_POINT_get_affine_coordinates,
+ ECDSA_sign,
+ ECDSA_size,
+ ECDSA_verify,
EVP_AEAD_CTX_new,
EVP_AEAD_CTX_open,
EVP_AEAD_CTX_seal,
HKDF,
HMAC,
RAND_bytes,
+ SHA256,
}
diff --git a/libs/bssl/src/ec_key.rs b/libs/bssl/src/ec_key.rs
index 4c1ba5c..7e677c4 100644
--- a/libs/bssl/src/ec_key.rs
+++ b/libs/bssl/src/ec_key.rs
@@ -18,19 +18,28 @@
use crate::cbb::CbbFixed;
use crate::cbs::Cbs;
use crate::util::{check_int_result, to_call_failed_error};
+use alloc::vec;
use alloc::vec::Vec;
use bssl_avf_error::{ApiName, Error, Result};
use bssl_ffi::{
- BN_bn2bin_padded, BN_clear_free, BN_new, CBB_flush, CBB_len, EC_GROUP_new_by_curve_name,
- EC_KEY_check_key, EC_KEY_free, EC_KEY_generate_key, EC_KEY_get0_group, EC_KEY_get0_public_key,
- EC_KEY_marshal_private_key, EC_KEY_new_by_curve_name, EC_KEY_parse_private_key,
+ BN_bin2bn, BN_bn2bin_padded, BN_clear_free, BN_new, CBB_flush, CBB_len, ECDSA_sign, ECDSA_size,
+ ECDSA_verify, EC_GROUP_new_by_curve_name, EC_KEY_check_key, EC_KEY_free, EC_KEY_generate_key,
+ EC_KEY_get0_group, EC_KEY_get0_public_key, EC_KEY_marshal_private_key,
+ EC_KEY_new_by_curve_name, EC_KEY_parse_private_key, EC_KEY_set_public_key_affine_coordinates,
EC_POINT_get_affine_coordinates, NID_X9_62_prime256v1, BIGNUM, EC_GROUP, EC_KEY, EC_POINT,
};
+use ciborium::Value;
use core::ptr::{self, NonNull};
use core::result;
-use coset::{iana, CoseKey, CoseKeyBuilder};
+use coset::{
+ iana::{self, EnumI64},
+ CborSerializable, CoseKey, CoseKeyBuilder, Label,
+};
+use log::error;
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
+const ES256_ALGO: iana::Algorithm = iana::Algorithm::ES256;
+const P256_CURVE: iana::EllipticCurve = iana::EllipticCurve::P_256;
const P256_AFFINE_COORDINATE_SIZE: usize = 32;
type Coordinate = [u8; P256_AFFINE_COORDINATE_SIZE];
@@ -53,10 +62,46 @@
let ec_key = unsafe {
EC_KEY_new_by_curve_name(NID_X9_62_prime256v1) // EC P-256 CURVE Nid
};
- let mut ec_key = NonNull::new(ec_key)
+ NonNull::new(ec_key)
.map(Self)
- .ok_or(to_call_failed_error(ApiName::EC_KEY_new_by_curve_name))?;
- ec_key.generate_key()?;
+ .ok_or(to_call_failed_error(ApiName::EC_KEY_new_by_curve_name))
+ }
+
+ /// Constructs an `EcKey` instance from the provided COSE_Key encoded public key slice.
+ pub fn from_cose_public_key(cose_key: &[u8]) -> Result<Self> {
+ let cose_key = CoseKey::from_slice(cose_key).map_err(|e| {
+ error!("Failed to deserialize COSE_Key: {e:?}");
+ Error::CoseKeyDecodingFailed
+ })?;
+ if cose_key.alg != Some(coset::Algorithm::Assigned(ES256_ALGO)) {
+ error!(
+ "Only ES256 algorithm is supported. Algo type in the COSE Key: {:?}",
+ cose_key.alg
+ );
+ return Err(Error::Unimplemented);
+ }
+ let crv = get_label_value(&cose_key, Label::Int(iana::Ec2KeyParameter::Crv.to_i64()))?;
+ if &Value::from(P256_CURVE.to_i64()) != crv {
+ error!("Only EC P-256 curve is supported. Curve type in the COSE Key: {crv:?}");
+ return Err(Error::Unimplemented);
+ }
+
+ let x = get_label_value_as_bytes(&cose_key, Label::Int(iana::Ec2KeyParameter::X.to_i64()))?;
+ let y = get_label_value_as_bytes(&cose_key, Label::Int(iana::Ec2KeyParameter::Y.to_i64()))?;
+
+ check_p256_affine_coordinate_size(x)?;
+ check_p256_affine_coordinate_size(y)?;
+
+ let x = BigNum::from_slice(x)?;
+ let y = BigNum::from_slice(y)?;
+
+ let ec_key = EcKey::new_p256()?;
+ // SAFETY: All the parameters are checked non-null and initialized.
+ // The function only reads the coordinates x and y within their bounds.
+ let ret = unsafe {
+ EC_KEY_set_public_key_affine_coordinates(ec_key.0.as_ptr(), x.as_ref(), y.as_ref())
+ };
+ check_int_result(ret, ApiName::EC_KEY_set_public_key_affine_coordinates)?;
Ok(ec_key)
}
@@ -70,9 +115,73 @@
check_int_result(ret, ApiName::EC_KEY_check_key)
}
+ /// Verifies the DER-encoded ECDSA `signature` of the `digest` with the current `EcKey`.
+ ///
+ /// Returns Ok(()) if the verification succeeds, otherwise an error will be returned.
+ pub fn ecdsa_verify(&self, signature: &[u8], digest: &[u8]) -> Result<()> {
+ // The `type` argument should be 0 as required in the BoringSSL spec.
+ const TYPE: i32 = 0;
+
+ // SAFETY: This function only reads the given data within its bounds.
+ // The `EC_KEY` passed to this function has been initialized and checked non-null.
+ let ret = unsafe {
+ ECDSA_verify(
+ TYPE,
+ digest.as_ptr(),
+ digest.len(),
+ signature.as_ptr(),
+ signature.len(),
+ self.0.as_ptr(),
+ )
+ };
+ check_int_result(ret, ApiName::ECDSA_verify)
+ }
+
+ /// Signs the `digest` with the current `EcKey` using ECDSA.
+ ///
+ /// Returns the DER-encoded ECDSA signature.
+ pub fn ecdsa_sign(&self, digest: &[u8]) -> Result<Vec<u8>> {
+ // The `type` argument should be 0 as required in the BoringSSL spec.
+ const TYPE: i32 = 0;
+
+ let mut signature = vec![0u8; self.ecdsa_size()?];
+ let mut signature_len = 0;
+ // SAFETY: This function only reads the given data within its bounds.
+ // The `EC_KEY` passed to this function has been initialized and checked non-null.
+ let ret = unsafe {
+ ECDSA_sign(
+ TYPE,
+ digest.as_ptr(),
+ digest.len(),
+ signature.as_mut_ptr(),
+ &mut signature_len,
+ self.0.as_ptr(),
+ )
+ };
+ check_int_result(ret, ApiName::ECDSA_sign)?;
+ if signature.len() < (signature_len as usize) {
+ Err(to_call_failed_error(ApiName::ECDSA_sign))
+ } else {
+ signature.truncate(signature_len as usize);
+ Ok(signature)
+ }
+ }
+
+ /// Returns the maximum size of an ECDSA signature using the current `EcKey`.
+ fn ecdsa_size(&self) -> Result<usize> {
+ // SAFETY: This function only reads the `EC_KEY` that has been initialized
+ // and checked non-null when this instance is created.
+ let size = unsafe { ECDSA_size(self.0.as_ptr()) };
+ if size == 0 {
+ Err(to_call_failed_error(ApiName::ECDSA_size))
+ } else {
+ Ok(size)
+ }
+ }
+
/// Generates a random, private key, calculates the corresponding public key and stores both
/// in the `EC_KEY`.
- fn generate_key(&mut self) -> Result<()> {
+ pub fn generate_key(&mut self) -> Result<()> {
// SAFETY: The non-null pointer is created with `EC_KEY_new_by_curve_name` and should
// point to a valid `EC_KEY`.
// The randomness is provided by `getentropy()` in `vmbase`.
@@ -82,12 +191,10 @@
/// Returns the `CoseKey` for the public key.
pub fn cose_public_key(&self) -> Result<CoseKey> {
- const ALGO: iana::Algorithm = iana::Algorithm::ES256;
- const CURVE: iana::EllipticCurve = iana::EllipticCurve::P_256;
-
let (x, y) = self.public_key_coordinates()?;
- let key =
- CoseKeyBuilder::new_ec2_pub_key(CURVE, x.to_vec(), y.to_vec()).algorithm(ALGO).build();
+ let key = CoseKeyBuilder::new_ec2_pub_key(P256_CURVE, x.to_vec(), y.to_vec())
+ .algorithm(ES256_ALGO)
+ .build();
Ok(key)
}
@@ -183,6 +290,30 @@
}
}
+fn get_label_value_as_bytes(key: &CoseKey, label: Label) -> Result<&[u8]> {
+ Ok(get_label_value(key, label)?.as_bytes().ok_or_else(|| {
+ error!("Value not a bstr.");
+ Error::CoseKeyDecodingFailed
+ })?)
+}
+
+fn get_label_value(key: &CoseKey, label: Label) -> Result<&Value> {
+ Ok(&key.params.iter().find(|(k, _)| k == &label).ok_or(Error::CoseKeyDecodingFailed)?.1)
+}
+
+fn check_p256_affine_coordinate_size(coordinate: &[u8]) -> Result<()> {
+ if P256_AFFINE_COORDINATE_SIZE == coordinate.len() {
+ Ok(())
+ } else {
+ error!(
+ "The size of the affine coordinate '{}' does not match the expected size '{}'",
+ coordinate.len(),
+ P256_AFFINE_COORDINATE_SIZE
+ );
+ Err(Error::CoseKeyDecodingFailed)
+ }
+}
+
/// A u8 vector that is zeroed when dropped.
#[derive(Zeroize, ZeroizeOnDrop)]
pub struct ZVec(Vec<u8>);
@@ -210,6 +341,13 @@
}
impl BigNum {
+ fn from_slice(x: &[u8]) -> Result<Self> {
+ // SAFETY: The function reads `x` within its bounds, and the returned
+ // pointer is checked below.
+ let bn = unsafe { BN_bin2bn(x.as_ptr(), x.len(), ptr::null_mut()) };
+ NonNull::new(bn).map(Self).ok_or(to_call_failed_error(ApiName::BN_bin2bn))
+ }
+
fn new() -> Result<Self> {
// SAFETY: The returned pointer is checked below.
let bn = unsafe { BN_new() };
@@ -221,6 +359,14 @@
}
}
+impl AsRef<BIGNUM> for BigNum {
+ fn as_ref(&self) -> &BIGNUM {
+ // SAFETY: The pointer is valid and points to an initialized instance of `BIGNUM`
+ // when the instance was created.
+ unsafe { self.0.as_ref() }
+ }
+}
+
/// Converts the `BigNum` to a big-endian integer. The integer is padded with leading zeros up to
/// size `N`. The conversion fails if `N` is smaller thanthe size of the integer.
impl<const N: usize> TryFrom<BigNum> for [u8; N] {
diff --git a/libs/bssl/src/err.rs b/libs/bssl/src/err.rs
index 1ee40c9..7040441 100644
--- a/libs/bssl/src/err.rs
+++ b/libs/bssl/src/err.rs
@@ -14,7 +14,7 @@
//! Wrappers of the error handling functions in BoringSSL err.h.
-use bssl_avf_error::{CipherError, GlobalError, ReasonCode};
+use bssl_avf_error::{CipherError, EcError, EcdsaError, GlobalError, ReasonCode};
use bssl_ffi::{self, ERR_get_error, ERR_GET_LIB_RUST, ERR_GET_REASON_RUST};
const NO_ERROR_REASON_CODE: i32 = 0;
@@ -75,6 +75,8 @@
fn map_library_reason_code(reason: i32, lib: i32) -> Option<ReasonCode> {
u32::try_from(lib).ok().and_then(|x| match x {
bssl_ffi::ERR_LIB_CIPHER => map_cipher_reason_code(reason).map(ReasonCode::Cipher),
+ bssl_ffi::ERR_LIB_EC => map_ec_reason_code(reason).map(ReasonCode::Ec),
+ bssl_ffi::ERR_LIB_ECDSA => map_ecdsa_reason_code(reason).map(ReasonCode::Ecdsa),
_ => None,
})
}
@@ -110,3 +112,60 @@
};
Some(error)
}
+
+fn map_ec_reason_code(reason: i32) -> Option<EcError> {
+ let error = match reason {
+ bssl_ffi::EC_R_BUFFER_TOO_SMALL => EcError::BufferTooSmall,
+ bssl_ffi::EC_R_COORDINATES_OUT_OF_RANGE => EcError::CoordinatesOutOfRange,
+ bssl_ffi::EC_R_D2I_ECPKPARAMETERS_FAILURE => EcError::D2IEcpkparametersFailure,
+ bssl_ffi::EC_R_EC_GROUP_NEW_BY_NAME_FAILURE => EcError::EcGroupNewByNameFailure,
+ bssl_ffi::EC_R_GROUP2PKPARAMETERS_FAILURE => EcError::Group2PkparametersFailure,
+ bssl_ffi::EC_R_I2D_ECPKPARAMETERS_FAILURE => EcError::I2DEcpkparametersFailure,
+ bssl_ffi::EC_R_INCOMPATIBLE_OBJECTS => EcError::IncompatibleObjects,
+ bssl_ffi::EC_R_INVALID_COMPRESSED_POINT => EcError::InvalidCompressedPoint,
+ bssl_ffi::EC_R_INVALID_COMPRESSION_BIT => EcError::InvalidCompressionBit,
+ bssl_ffi::EC_R_INVALID_ENCODING => EcError::InvalidEncoding,
+ bssl_ffi::EC_R_INVALID_FIELD => EcError::InvalidField,
+ bssl_ffi::EC_R_INVALID_FORM => EcError::InvalidForm,
+ bssl_ffi::EC_R_INVALID_GROUP_ORDER => EcError::InvalidGroupOrder,
+ bssl_ffi::EC_R_INVALID_PRIVATE_KEY => EcError::InvalidPrivateKey,
+ bssl_ffi::EC_R_MISSING_PARAMETERS => EcError::MissingParameters,
+ bssl_ffi::EC_R_MISSING_PRIVATE_KEY => EcError::MissingPrivateKey,
+ bssl_ffi::EC_R_NON_NAMED_CURVE => EcError::NonNamedCurve,
+ bssl_ffi::EC_R_NOT_INITIALIZED => EcError::NotInitialized,
+ bssl_ffi::EC_R_PKPARAMETERS2GROUP_FAILURE => EcError::Pkparameters2GroupFailure,
+ bssl_ffi::EC_R_POINT_AT_INFINITY => EcError::PointAtInfinity,
+ bssl_ffi::EC_R_POINT_IS_NOT_ON_CURVE => EcError::PointIsNotOnCurve,
+ bssl_ffi::EC_R_SLOT_FULL => EcError::SlotFull,
+ bssl_ffi::EC_R_UNDEFINED_GENERATOR => EcError::UndefinedGenerator,
+ bssl_ffi::EC_R_UNKNOWN_GROUP => EcError::UnknownGroup,
+ bssl_ffi::EC_R_UNKNOWN_ORDER => EcError::UnknownOrder,
+ bssl_ffi::EC_R_WRONG_ORDER => EcError::WrongOrder,
+ bssl_ffi::EC_R_BIGNUM_OUT_OF_RANGE => EcError::BignumOutOfRange,
+ bssl_ffi::EC_R_WRONG_CURVE_PARAMETERS => EcError::WrongCurveParameters,
+ bssl_ffi::EC_R_DECODE_ERROR => EcError::DecodeError,
+ bssl_ffi::EC_R_ENCODE_ERROR => EcError::EncodeError,
+ bssl_ffi::EC_R_GROUP_MISMATCH => EcError::GroupMismatch,
+ bssl_ffi::EC_R_INVALID_COFACTOR => EcError::InvalidCofactor,
+ bssl_ffi::EC_R_PUBLIC_KEY_VALIDATION_FAILED => EcError::PublicKeyValidationFailed,
+ bssl_ffi::EC_R_INVALID_SCALAR => EcError::InvalidScalar,
+ _ => return None,
+ };
+ Some(error)
+}
+
+fn map_ecdsa_reason_code(reason: i32) -> Option<EcdsaError> {
+ let error = match reason {
+ bssl_ffi::ECDSA_R_BAD_SIGNATURE => EcdsaError::BadSignature,
+ bssl_ffi::ECDSA_R_MISSING_PARAMETERS => EcdsaError::MissingParameters,
+ bssl_ffi::ECDSA_R_NEED_NEW_SETUP_VALUES => EcdsaError::NeedNewSetupValues,
+ bssl_ffi::ECDSA_R_NOT_IMPLEMENTED => EcdsaError::NotImplemented,
+ bssl_ffi::ECDSA_R_RANDOM_NUMBER_GENERATION_FAILED => {
+ EcdsaError::RandomNumberGenerationFailed
+ }
+ bssl_ffi::ECDSA_R_ENCODE_ERROR => EcdsaError::EncodeError,
+ bssl_ffi::ECDSA_R_TOO_MANY_ITERATIONS => EcdsaError::TooManyIterations,
+ _ => return None,
+ };
+ Some(error)
+}
diff --git a/libs/bssl/src/hmac.rs b/libs/bssl/src/hmac.rs
index ddbbe4a..1b3a403 100644
--- a/libs/bssl/src/hmac.rs
+++ b/libs/bssl/src/hmac.rs
@@ -15,15 +15,14 @@
//! Wrappers of the HMAC functions in BoringSSL hmac.h.
use crate::digest::Digester;
+use crate::sha::SHA256_DIGEST_LENGTH;
use crate::util::to_call_failed_error;
use bssl_avf_error::{ApiName, Result};
-use bssl_ffi::{HMAC, SHA256_DIGEST_LENGTH};
-
-const SHA256_LEN: usize = SHA256_DIGEST_LENGTH as usize;
+use bssl_ffi::HMAC;
/// Computes the HMAC using SHA-256 for the given `data` with the given `key`.
-pub fn hmac_sha256(key: &[u8], data: &[u8]) -> Result<[u8; SHA256_LEN]> {
- hmac::<SHA256_LEN>(key, data, Digester::sha256())
+pub fn hmac_sha256(key: &[u8], data: &[u8]) -> Result<[u8; SHA256_DIGEST_LENGTH]> {
+ hmac::<SHA256_DIGEST_LENGTH>(key, data, Digester::sha256())
}
/// Computes the HMAC for the given `data` with the given `key` and `digester`.
diff --git a/libs/bssl/src/lib.rs b/libs/bssl/src/lib.rs
index de81368..8e3abcf 100644
--- a/libs/bssl/src/lib.rs
+++ b/libs/bssl/src/lib.rs
@@ -27,9 +27,10 @@
mod hkdf;
mod hmac;
mod rand;
+mod sha;
mod util;
-pub use bssl_avf_error::{ApiName, CipherError, Error, ReasonCode, Result};
+pub use bssl_avf_error::{ApiName, CipherError, EcError, EcdsaError, Error, ReasonCode, Result};
pub use aead::{Aead, AeadContext, AES_GCM_NONCE_LENGTH};
pub use cbb::CbbFixed;
@@ -39,3 +40,4 @@
pub use hkdf::hkdf;
pub use hmac::hmac_sha256;
pub use rand::rand_bytes;
+pub use sha::sha256;
diff --git a/libs/bssl/src/sha.rs b/libs/bssl/src/sha.rs
new file mode 100644
index 0000000..6c65d7f
--- /dev/null
+++ b/libs/bssl/src/sha.rs
@@ -0,0 +1,35 @@
+// 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.
+
+//! Wrappers of the SHA functions in BoringSSL sha.h.
+
+use crate::util::to_call_failed_error;
+use bssl_avf_error::{ApiName, Result};
+use bssl_ffi::SHA256;
+
+/// The length of a SHA256 digest.
+pub(crate) const SHA256_DIGEST_LENGTH: usize = bssl_ffi::SHA256_DIGEST_LENGTH as usize;
+
+/// Computes the SHA256 digest of the provided `data``.
+pub fn sha256(data: &[u8]) -> Result<[u8; SHA256_DIGEST_LENGTH]> {
+ let mut out = [0u8; SHA256_DIGEST_LENGTH];
+ // SAFETY: This function reads `data` and writes to `out` within its bounds.
+ // `out` has `SHA256_DIGEST_LENGTH` bytes of space for write.
+ let ret = unsafe { SHA256(data.as_ptr(), data.len(), out.as_mut_ptr()) };
+ if ret.is_null() {
+ Err(to_call_failed_error(ApiName::SHA256))
+ } else {
+ Ok(out)
+ }
+}
diff --git a/libs/bssl/tests/eckey_test.rs b/libs/bssl/tests/eckey_test.rs
index a013fba..3dd243c 100644
--- a/libs/bssl/tests/eckey_test.rs
+++ b/libs/bssl/tests/eckey_test.rs
@@ -12,14 +12,70 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use bssl_avf::{EcKey, Result};
+use bssl_avf::{sha256, ApiName, EcKey, EcdsaError, Error, Result};
+use coset::CborSerializable;
+
+const MESSAGE1: &[u8] = b"test message 1";
+const MESSAGE2: &[u8] = b"test message 2";
#[test]
fn ec_private_key_serialization() -> Result<()> {
- let ec_key = EcKey::new_p256()?;
+ let mut ec_key = EcKey::new_p256()?;
+ ec_key.generate_key()?;
let der_encoded_ec_private_key = ec_key.ec_private_key()?;
let deserialized_ec_key = EcKey::from_ec_private_key(der_encoded_ec_private_key.as_slice())?;
assert_eq!(ec_key.cose_public_key()?, deserialized_ec_key.cose_public_key()?);
Ok(())
}
+
+#[test]
+fn cose_public_key_serialization() -> Result<()> {
+ let mut ec_key = EcKey::new_p256()?;
+ ec_key.generate_key()?;
+ let cose_key = ec_key.cose_public_key()?;
+ let cose_key_data = cose_key.clone().to_vec().unwrap();
+ let deserialized_ec_key = EcKey::from_cose_public_key(&cose_key_data)?;
+
+ assert_eq!(cose_key, deserialized_ec_key.cose_public_key()?);
+ Ok(())
+}
+
+#[test]
+fn ecdsa_p256_signing_and_verification_succeed() -> Result<()> {
+ let mut ec_key = EcKey::new_p256()?;
+ ec_key.generate_key()?;
+ let digest = sha256(MESSAGE1)?;
+
+ let signature = ec_key.ecdsa_sign(&digest)?;
+ ec_key.ecdsa_verify(&signature, &digest)
+}
+
+#[test]
+fn verifying_ecdsa_p256_signed_with_a_different_key_fails() -> Result<()> {
+ let mut ec_key1 = EcKey::new_p256()?;
+ ec_key1.generate_key()?;
+ let digest = sha256(MESSAGE1)?;
+ let signature = ec_key1.ecdsa_sign(&digest)?;
+
+ let mut ec_key2 = EcKey::new_p256()?;
+ ec_key2.generate_key()?;
+ let err = ec_key2.ecdsa_verify(&signature, &digest).unwrap_err();
+ let expected_err = Error::CallFailed(ApiName::ECDSA_verify, EcdsaError::BadSignature.into());
+ assert_eq!(expected_err, err);
+ Ok(())
+}
+
+#[test]
+fn verifying_ecdsa_p256_signed_with_a_different_message_fails() -> Result<()> {
+ let mut ec_key = EcKey::new_p256()?;
+ ec_key.generate_key()?;
+ let digest1 = sha256(MESSAGE1)?;
+ let signature = ec_key.ecdsa_sign(&digest1)?;
+ let digest2 = sha256(MESSAGE2)?;
+
+ let err = ec_key.ecdsa_verify(&signature, &digest2).unwrap_err();
+ let expected_err = Error::CallFailed(ApiName::ECDSA_verify, EcdsaError::BadSignature.into());
+ assert_eq!(expected_err, err);
+ Ok(())
+}
diff --git a/libs/hyp/src/hypervisor.rs b/libs/hyp/src/hypervisor.rs
index 309f967..3d42ccb 100644
--- a/libs/hyp/src/hypervisor.rs
+++ b/libs/hyp/src/hypervisor.rs
@@ -34,6 +34,8 @@
use smccc::hvc64;
use uuid::Uuid;
+use self::common::DeviceAssigningHypervisor;
+
enum HypervisorBackend {
RegularKvm,
Gunyah,
@@ -122,3 +124,8 @@
pub fn get_mem_sharer() -> Option<&'static dyn MemSharingHypervisor> {
get_hypervisor().as_mem_sharer()
}
+
+/// Gets the device assigning hypervisor singleton, if any.
+pub fn get_device_assigner() -> Option<&'static dyn DeviceAssigningHypervisor> {
+ get_hypervisor().as_device_assigner()
+}
diff --git a/libs/hyp/src/hypervisor/common.rs b/libs/hyp/src/hypervisor/common.rs
index 70fdd0a..2ea18f3 100644
--- a/libs/hyp/src/hypervisor/common.rs
+++ b/libs/hyp/src/hypervisor/common.rs
@@ -31,6 +31,11 @@
fn as_mem_sharer(&self) -> Option<&dyn MemSharingHypervisor> {
None
}
+
+ /// Returns the hypervisor's device assigning implementation, if any.
+ fn as_device_assigner(&self) -> Option<&dyn DeviceAssigningHypervisor> {
+ None
+ }
}
pub trait MmioGuardedHypervisor {
@@ -73,3 +78,11 @@
/// Returns the memory protection granule size in bytes.
fn granule(&self) -> Result<usize>;
}
+
+pub trait DeviceAssigningHypervisor {
+ /// Returns MMIO token.
+ fn get_phys_mmio_token(&self, base_ipa: u64, size: u64) -> Result<u64>;
+
+ /// Returns DMA token as a tuple of (phys_iommu_id, phys_sid).
+ fn get_phys_iommu_token(&self, pviommu_id: u64, vsid: u64) -> Result<(u64, u64)>;
+}
diff --git a/libs/hyp/src/hypervisor/kvm.rs b/libs/hyp/src/hypervisor/kvm.rs
index 5835346..720318e 100644
--- a/libs/hyp/src/hypervisor/kvm.rs
+++ b/libs/hyp/src/hypervisor/kvm.rs
@@ -14,7 +14,9 @@
//! Wrappers around calls to the KVM hypervisor.
-use super::common::{Hypervisor, MemSharingHypervisor, MmioGuardedHypervisor};
+use super::common::{
+ DeviceAssigningHypervisor, Hypervisor, MemSharingHypervisor, MmioGuardedHypervisor,
+};
use crate::error::{Error, Result};
use crate::util::page_address;
use core::fmt::{self, Display, Formatter};
@@ -70,6 +72,9 @@
const VENDOR_HYP_KVM_MMIO_GUARD_MAP_FUNC_ID: u32 = 0xc6000007;
const VENDOR_HYP_KVM_MMIO_GUARD_UNMAP_FUNC_ID: u32 = 0xc6000008;
+const VENDOR_HYP_KVM_DEV_REQ_MMIO_FUNC_ID: u32 = 0xc6000012;
+const VENDOR_HYP_KVM_DEV_REQ_DMA_FUNC_ID: u32 = 0xc6000013;
+
pub(super) struct RegularKvmHypervisor;
impl RegularKvmHypervisor {
@@ -90,6 +95,10 @@
fn as_mem_sharer(&self) -> Option<&dyn MemSharingHypervisor> {
Some(self)
}
+
+ fn as_device_assigner(&self) -> Option<&dyn DeviceAssigningHypervisor> {
+ Some(self)
+ }
}
impl MmioGuardedHypervisor for ProtectedKvmHypervisor {
@@ -153,6 +162,26 @@
}
}
+impl DeviceAssigningHypervisor for ProtectedKvmHypervisor {
+ fn get_phys_mmio_token(&self, base_ipa: u64, size: u64) -> Result<u64> {
+ let mut args = [0u64; 17];
+ args[0] = base_ipa;
+ args[1] = size;
+
+ let ret = checked_hvc64_expect_results(VENDOR_HYP_KVM_DEV_REQ_MMIO_FUNC_ID, args)?;
+ Ok(ret[0])
+ }
+
+ fn get_phys_iommu_token(&self, pviommu_id: u64, vsid: u64) -> Result<(u64, u64)> {
+ let mut args = [0u64; 17];
+ args[0] = pviommu_id;
+ args[1] = vsid;
+
+ let ret = checked_hvc64_expect_results(VENDOR_HYP_KVM_DEV_REQ_DMA_FUNC_ID, args)?;
+ Ok((ret[0], ret[1]))
+ }
+}
+
fn checked_hvc64_expect_zero(function: u32, args: [u64; 17]) -> Result<()> {
success_or_error_64(hvc64(function, args)[0]).map_err(|e| Error::KvmError(e, function))
}
@@ -160,3 +189,9 @@
fn checked_hvc64(function: u32, args: [u64; 17]) -> Result<u64> {
positive_or_error_64(hvc64(function, args)[0]).map_err(|e| Error::KvmError(e, function))
}
+
+fn checked_hvc64_expect_results(function: u32, args: [u64; 17]) -> Result<[u64; 17]> {
+ let [ret, results @ ..] = hvc64(function, args);
+ success_or_error_64(ret).map_err(|e| Error::KvmError(e, function))?;
+ Ok(results)
+}
diff --git a/libs/hyp/src/lib.rs b/libs/hyp/src/lib.rs
index 486a181..505aade 100644
--- a/libs/hyp/src/lib.rs
+++ b/libs/hyp/src/lib.rs
@@ -21,6 +21,8 @@
mod util;
pub use error::{Error, Result};
-pub use hypervisor::{get_mem_sharer, get_mmio_guard, KvmError, MMIO_GUARD_GRANULE_SIZE};
+pub use hypervisor::{
+ get_device_assigner, get_mem_sharer, get_mmio_guard, KvmError, MMIO_GUARD_GRANULE_SIZE,
+};
use hypervisor::GeniezoneError;
diff --git a/libs/libfdt/Android.bp b/libs/libfdt/Android.bp
index 5920d5d..ba9e971 100644
--- a/libs/libfdt/Android.bp
+++ b/libs/libfdt/Android.bp
@@ -69,22 +69,34 @@
genrule {
name: "fdt_test_tree_one_memory_range_dtb",
- defaults: ["dts_to_dtb"],
- srcs: ["tests/data/test_tree_one_memory_range.dts"],
+ tools: ["dtc"],
+ srcs: [
+ "tests/data/test_tree_one_memory_range.dts",
+ "tests/data/test_tree_no_memory_node.dts",
+ ],
+ cmd: "$(location dtc) -I dts -O dtb $(location tests/data/test_tree_one_memory_range.dts) -o $(out)",
out: ["data/test_tree_one_memory_range.dtb"],
}
genrule {
name: "fdt_test_tree_multiple_memory_ranges_dtb",
- defaults: ["dts_to_dtb"],
- srcs: ["tests/data/test_tree_multiple_memory_ranges.dts"],
+ tools: ["dtc"],
+ srcs: [
+ "tests/data/test_tree_multiple_memory_ranges.dts",
+ "tests/data/test_tree_no_memory_node.dts",
+ ],
+ cmd: "$(location dtc) -I dts -O dtb $(location tests/data/test_tree_multiple_memory_ranges.dts) -o $(out)",
out: ["data/test_tree_multiple_memory_ranges.dtb"],
}
genrule {
name: "fdt_test_tree_empty_memory_range_dtb",
- defaults: ["dts_to_dtb"],
- srcs: ["tests/data/test_tree_empty_memory_range.dts"],
+ tools: ["dtc"],
+ srcs: [
+ "tests/data/test_tree_empty_memory_range.dts",
+ "tests/data/test_tree_no_memory_node.dts",
+ ],
+ cmd: "$(location dtc) -I dts -O dtb $(location tests/data/test_tree_empty_memory_range.dts) -o $(out)",
out: ["data/test_tree_empty_memory_range.dtb"],
}
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index 23eadcf..c93cb4c 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -283,12 +283,14 @@
],
soong_config_variables: {
release_avf_enable_vendor_modules: {
- default_group: [
- {
- name: "vendor_a",
- filesystem: ":microdroid_vendor",
- },
- ],
+ conditions_default: {
+ default_group: [
+ {
+ name: "vendor_a",
+ filesystem: ":microdroid_vendor",
+ },
+ ],
+ },
},
},
}
diff --git a/pvmfw/Android.bp b/pvmfw/Android.bp
index b6f3940..103619f 100644
--- a/pvmfw/Android.bp
+++ b/pvmfw/Android.bp
@@ -82,6 +82,34 @@
out: ["test_pvmfw_devices_with_rng.dtb"],
}
+genrule {
+ name: "test_pvmfw_devices_with_rng_iommu",
+ defaults: ["dts_to_dtb"],
+ srcs: ["testdata/test_pvmfw_devices_with_rng_iommu.dts"],
+ out: ["test_pvmfw_devices_with_rng_iommu.dtb"],
+}
+
+genrule {
+ name: "test_pvmfw_devices_with_multiple_devices_iommus",
+ defaults: ["dts_to_dtb"],
+ srcs: ["testdata/test_pvmfw_devices_with_multiple_devices_iommus.dts"],
+ out: ["test_pvmfw_devices_with_multiple_devices_iommus.dtb"],
+}
+
+genrule {
+ name: "test_pvmfw_devices_with_iommu_sharing",
+ defaults: ["dts_to_dtb"],
+ srcs: ["testdata/test_pvmfw_devices_with_iommu_sharing.dts"],
+ out: ["test_pvmfw_devices_with_iommu_sharing.dtb"],
+}
+
+genrule {
+ name: "test_pvmfw_devices_with_iommu_id_conflict",
+ defaults: ["dts_to_dtb"],
+ srcs: ["testdata/test_pvmfw_devices_with_iommu_id_conflict.dts"],
+ out: ["test_pvmfw_devices_with_iommu_id_conflict.dtb"],
+}
+
rust_test {
name: "libpvmfw.device_assignment.test",
srcs: ["src/device_assignment.rs"],
@@ -101,6 +129,10 @@
":test_pvmfw_devices_vm_dtbo",
":test_pvmfw_devices_vm_dtbo_without_symbols",
":test_pvmfw_devices_with_rng",
+ ":test_pvmfw_devices_with_rng_iommu",
+ ":test_pvmfw_devices_with_multiple_devices_iommus",
+ ":test_pvmfw_devices_with_iommu_sharing",
+ ":test_pvmfw_devices_with_iommu_id_conflict",
],
// To use libpvmfw_fdt_template for testing
enabled: false,
diff --git a/pvmfw/src/device_assignment.rs b/pvmfw/src/device_assignment.rs
index a92b418..3f84a8d 100644
--- a/pvmfw/src/device_assignment.rs
+++ b/pvmfw/src/device_assignment.rs
@@ -19,6 +19,7 @@
#[cfg(test)]
extern crate alloc;
+use alloc::collections::{BTreeMap, BTreeSet};
use alloc::ffi::CString;
use alloc::fmt;
use alloc::vec;
@@ -26,7 +27,7 @@
use core::ffi::CStr;
use core::iter::Iterator;
use core::mem;
-use libfdt::{Fdt, FdtError, FdtNode};
+use libfdt::{Fdt, FdtError, FdtNode, Phandle};
// TODO(b/308694211): Use cstr! from vmbase instead.
macro_rules! cstr {
@@ -52,8 +53,16 @@
InvalidSymbols,
/// Invalid <interrupts>
InvalidInterrupts,
+ /// Invalid <iommus>
+ InvalidIommus,
+ /// Too many pvIOMMU
+ TooManyPvIommu,
+ /// Duplicated pvIOMMU IDs exist
+ DuplicatedPvIommuIds,
/// Unsupported overlay target syntax. Only supports <target-path> with full path.
UnsupportedOverlayTarget,
+ /// Internal error
+ Internal,
/// Unexpected error from libfdt
UnexpectedFdtError(FdtError),
}
@@ -73,9 +82,18 @@
"Invalid property in /__symbols__. Must point to valid assignable device node."
),
Self::InvalidInterrupts => write!(f, "Invalid <interrupts>"),
+ Self::InvalidIommus => write!(f, "Invalid <iommus>"),
+ Self::TooManyPvIommu => write!(
+ f,
+ "Too many pvIOMMU node. Insufficient pre-populated pvIOMMUs in platform DT"
+ ),
+ Self::DuplicatedPvIommuIds => {
+ write!(f, "Duplicated pvIOMMU IDs exist. IDs must unique")
+ }
Self::UnsupportedOverlayTarget => {
write!(f, "Unsupported overlay target. Only supports 'target-path = \"/\"'")
}
+ Self::Internal => write!(f, "Internal error"),
Self::UnexpectedFdtError(e) => write!(f, "Unexpected Error from libfdt: {e}"),
}
}
@@ -169,6 +187,19 @@
}
}
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
+struct PvIommu {
+ // ID from pvIOMMU node
+ id: u32,
+}
+
+impl PvIommu {
+ fn parse(node: &FdtNode) -> Result<Self> {
+ let id = node.getprop_u32(cstr!("id"))?.ok_or(DeviceAssignmentError::InvalidIommus)?;
+ Ok(Self { id })
+ }
+}
+
/// Assigned device information parsed from crosvm DT.
/// Keeps everything in the owned data because underlying FDT will be reused for platform DT.
#[derive(Debug, Eq, PartialEq)]
@@ -181,6 +212,8 @@
reg: Vec<u8>,
// <interrupts> property from the crosvm DT
interrupts: Vec<u8>,
+ // Parsed <iommus> property from the crosvm DT.
+ iommus: Vec<PvIommu>,
}
impl AssignedDeviceInfo {
@@ -199,41 +232,91 @@
Ok(node.getprop(cstr!("interrupts")).unwrap().unwrap().into())
}
- // TODO(b/277993056): Read and validate iommu
- fn parse(fdt: &Fdt, vm_dtbo: &VmDtbo, dtbo_node_path: &CStr) -> Result<Option<Self>> {
+ // TODO(b/277993056): Also validate /__local_fixups__ to ensure that <iommus> has phandle.
+ // TODO(b/277993056): Also keep vSID.
+ // TODO(b/277993056): Validate #iommu-cells values.
+ fn parse_iommus(node: &FdtNode, pviommus: &BTreeMap<Phandle, PvIommu>) -> Result<Vec<PvIommu>> {
+ let mut iommus = vec![];
+ let Some(cells) = node.getprop_cells(cstr!("iommus"))? else {
+ return Ok(iommus);
+ };
+ for cell in cells {
+ let phandle = Phandle::try_from(cell).or(Err(DeviceAssignmentError::InvalidIommus))?;
+ let pviommu = pviommus.get(&phandle).ok_or(DeviceAssignmentError::InvalidIommus)?;
+ iommus.push(*pviommu)
+ }
+ Ok(iommus)
+ }
+
+ fn parse(
+ fdt: &Fdt,
+ vm_dtbo: &VmDtbo,
+ dtbo_node_path: &CStr,
+ pviommus: &BTreeMap<Phandle, PvIommu>,
+ ) -> Result<Option<Self>> {
let node_path = vm_dtbo.locate_overlay_target_path(dtbo_node_path)?;
let Some(node) = fdt.node(&node_path)? else { return Ok(None) };
// TODO(b/277993056): Validate reg with HVC, and keep reg with FdtNode::reg()
let reg = node.getprop(cstr!("reg")).unwrap().unwrap();
-
let interrupts = Self::parse_interrupts(&node)?;
-
+ let iommus = Self::parse_iommus(&node, pviommus)?;
Ok(Some(Self {
node_path,
dtbo_node_path: dtbo_node_path.into(),
reg: reg.to_vec(),
- interrupts: interrupts.to_vec(),
+ interrupts,
+ iommus,
}))
}
- fn patch(&self, fdt: &mut Fdt) -> Result<()> {
+ fn patch(&self, fdt: &mut Fdt, pviommu_phandles: &BTreeMap<PvIommu, Phandle>) -> Result<()> {
let mut dst = fdt.node_mut(&self.node_path)?.unwrap();
dst.setprop(cstr!("reg"), &self.reg)?;
dst.setprop(cstr!("interrupts"), &self.interrupts)?;
- // TODO(b/277993056): Read and patch iommu
+
+ let iommus: Vec<u8> = self
+ .iommus
+ .iter()
+ .flat_map(|pviommu| {
+ let phandle = pviommu_phandles.get(pviommu).unwrap();
+ u32::from(*phandle).to_be_bytes()
+ })
+ .collect();
+ dst.setprop(cstr!("iommus"), &iommus)?;
+
Ok(())
}
}
#[derive(Debug, Default, Eq, PartialEq)]
pub struct DeviceAssignmentInfo {
+ pviommus: BTreeSet<PvIommu>,
assigned_devices: Vec<AssignedDeviceInfo>,
filtered_dtbo_paths: Vec<CString>,
}
impl DeviceAssignmentInfo {
+ const PVIOMMU_COMPATIBLE: &CStr = cstr!("pkvm,pviommu");
+
+ /// Parses pvIOMMUs in fdt
+ // Note: This will validate pvIOMMU ids' uniqueness, even when unassigned.
+ fn parse_pviommus(fdt: &Fdt) -> Result<BTreeMap<Phandle, PvIommu>> {
+ // TODO(b/277993056): Validated `<#iommu-cells>`.
+ let mut pviommus = BTreeMap::new();
+ for compatible in fdt.compatible_nodes(Self::PVIOMMU_COMPATIBLE)? {
+ let Some(phandle) = compatible.get_phandle()? else {
+ continue; // Skips unreachable pvIOMMU node
+ };
+ let pviommu = PvIommu::parse(&compatible)?;
+ if pviommus.insert(phandle, pviommu).is_some() {
+ return Err(FdtError::BadPhandle.into());
+ }
+ }
+ Ok(pviommus)
+ }
+
/// Parses fdt and vm_dtbo, and creates new DeviceAssignmentInfo
// TODO(b/277993056): Parse __local_fixups__
// TODO(b/277993056): Parse __fixups__
@@ -244,25 +327,32 @@
return Ok(None);
};
+ let pviommus = Self::parse_pviommus(fdt)?;
+ let unique_pviommus: BTreeSet<_> = pviommus.values().cloned().collect();
+ if pviommus.len() != unique_pviommus.len() {
+ return Err(DeviceAssignmentError::DuplicatedPvIommuIds);
+ }
+
let mut assigned_devices = vec![];
let mut filtered_dtbo_paths = vec![];
for symbol_prop in symbols_node.properties()? {
let symbol_prop_value = symbol_prop.value()?;
let dtbo_node_path = CStr::from_bytes_with_nul(symbol_prop_value)
.or(Err(DeviceAssignmentError::InvalidSymbols))?;
- let assigned_device = AssignedDeviceInfo::parse(fdt, vm_dtbo, dtbo_node_path)?;
+ let assigned_device =
+ AssignedDeviceInfo::parse(fdt, vm_dtbo, dtbo_node_path, &pviommus)?;
if let Some(assigned_device) = assigned_device {
assigned_devices.push(assigned_device);
} else {
filtered_dtbo_paths.push(dtbo_node_path.into());
}
}
- filtered_dtbo_paths.push(CString::new("/__symbols__").unwrap());
-
if assigned_devices.is_empty() {
return Ok(None);
}
- Ok(Some(Self { assigned_devices, filtered_dtbo_paths }))
+ filtered_dtbo_paths.push(CString::new("/__symbols__").unwrap());
+
+ Ok(Some(Self { pviommus: unique_pviommus, assigned_devices, filtered_dtbo_paths }))
}
/// Filters VM DTBO to only contain necessary information for booting pVM
@@ -290,16 +380,46 @@
for assigned_device in &self.assigned_devices {
let mut node = vm_dtbo.node_mut(&assigned_device.dtbo_node_path).unwrap().unwrap();
for prop in FILTERED_VM_DTBO_PROP {
- node.nop_property(prop)?;
+ match node.nop_property(prop) {
+ Err(FdtError::NotFound) => Ok(()), // allows not exists
+ other => other,
+ }?;
}
}
+
Ok(())
}
- pub fn patch(&self, fdt: &mut Fdt) -> Result<()> {
- for device in &self.assigned_devices {
- device.patch(fdt)?
+ fn patch_pviommus(&self, fdt: &mut Fdt) -> Result<BTreeMap<PvIommu, Phandle>> {
+ let mut compatible = fdt.root_mut()?.next_compatible(Self::PVIOMMU_COMPATIBLE)?;
+ let mut pviommu_phandles = BTreeMap::new();
+
+ for pviommu in &self.pviommus {
+ let mut node = compatible.ok_or(DeviceAssignmentError::TooManyPvIommu)?;
+ let phandle = node.as_node().get_phandle()?.ok_or(DeviceAssignmentError::Internal)?;
+ node.setprop_inplace(cstr!("id"), &pviommu.id.to_be_bytes())?;
+ if pviommu_phandles.insert(*pviommu, phandle).is_some() {
+ return Err(DeviceAssignmentError::Internal);
+ }
+ compatible = node.next_compatible(Self::PVIOMMU_COMPATIBLE)?;
}
+
+ // Filters pre-populated but unassigned pvIOMMUs.
+ while let Some(filtered_pviommu) = compatible {
+ compatible = filtered_pviommu.delete_and_next_compatible(Self::PVIOMMU_COMPATIBLE)?;
+ }
+
+ Ok(pviommu_phandles)
+ }
+
+ pub fn patch(&self, fdt: &mut Fdt) -> Result<()> {
+ let pviommu_phandles = self.patch_pviommus(fdt)?;
+
+ // Patches assigned devices
+ for device in &self.assigned_devices {
+ device.patch(fdt, &pviommu_phandles)?;
+ }
+
Ok(())
}
}
@@ -307,12 +427,68 @@
#[cfg(test)]
mod tests {
use super::*;
+ use alloc::collections::BTreeSet;
use std::fs;
const VM_DTBO_FILE_PATH: &str = "test_pvmfw_devices_vm_dtbo.dtbo";
const VM_DTBO_WITHOUT_SYMBOLS_FILE_PATH: &str =
"test_pvmfw_devices_vm_dtbo_without_symbols.dtbo";
const FDT_FILE_PATH: &str = "test_pvmfw_devices_with_rng.dtb";
+ const FDT_WITH_IOMMU_FILE_PATH: &str = "test_pvmfw_devices_with_rng_iommu.dtb";
+ const FDT_WITH_MULTIPLE_DEVICES_IOMMUS_FILE_PATH: &str =
+ "test_pvmfw_devices_with_multiple_devices_iommus.dtb";
+ const FDT_WITH_IOMMU_SHARING: &str = "test_pvmfw_devices_with_iommu_sharing.dtb";
+ const FDT_WITH_IOMMU_ID_CONFLICT: &str = "test_pvmfw_devices_with_iommu_id_conflict.dtb";
+
+ #[derive(Debug, Eq, PartialEq)]
+ struct AssignedDeviceNode {
+ path: CString,
+ reg: Vec<u8>,
+ interrupts: Vec<u8>,
+ iommus: Vec<u32>, // pvIOMMU ids
+ }
+
+ impl AssignedDeviceNode {
+ fn parse(fdt: &Fdt, path: &CStr) -> Result<Self> {
+ let Some(node) = fdt.node(path)? else {
+ return Err(FdtError::NotFound.into());
+ };
+
+ // TODO(b/277993056): Replace DeviceAssignmentError::Internal
+ let reg = node.getprop(cstr!("reg"))?.ok_or(DeviceAssignmentError::Internal)?;
+ let interrupts = node
+ .getprop(cstr!("interrupts"))?
+ .ok_or(DeviceAssignmentError::InvalidInterrupts)?;
+ let mut iommus = vec![];
+ if let Some(cells) = node.getprop_cells(cstr!("iommus"))? {
+ for cell in cells {
+ let phandle = Phandle::try_from(cell)?;
+ let pviommu = fdt
+ .node_with_phandle(phandle)?
+ .ok_or(DeviceAssignmentError::InvalidIommus)?;
+ let compatible = pviommu.getprop_str(cstr!("compatible"));
+ if compatible != Ok(Some(cstr!("pkvm,pviommu"))) {
+ return Err(DeviceAssignmentError::InvalidIommus);
+ }
+ let id = pviommu
+ .getprop_u32(cstr!("id"))?
+ .ok_or(DeviceAssignmentError::InvalidIommus)?;
+ iommus.push(id);
+ }
+ }
+ Ok(Self { path: path.into(), reg: reg.into(), interrupts: interrupts.into(), iommus })
+ }
+ }
+
+ fn collect_pviommus(fdt: &Fdt) -> Result<Vec<u32>> {
+ let mut pviommus = BTreeSet::new();
+ for pviommu in fdt.compatible_nodes(cstr!("pkvm,pviommu"))? {
+ if let Ok(Some(id)) = pviommu.getprop_u32(cstr!("id")) {
+ pviommus.insert(id);
+ }
+ }
+ Ok(pviommus.iter().cloned().collect())
+ }
fn into_fdt_prop(native_bytes: Vec<u32>) -> Vec<u8> {
let mut v = Vec::with_capacity(native_bytes.len() * 4);
@@ -347,16 +523,18 @@
dtbo_node_path: cstr!("/fragment@rng/__overlay__/rng").into(),
reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
+ iommus: vec![],
}];
assert_eq!(device_info.assigned_devices, expected);
}
+ // TODO(b/311655051): Test with real once instead of empty FDT.
#[test]
- fn device_info_new_without_assigned_devices() {
- let mut fdt_data: Vec<u8> = pvmfw_fdt_template::RAW.into();
+ fn device_info_new_with_empty_device_tree() {
+ let mut fdt_data = vec![0; pvmfw_fdt_template::RAW.len()];
let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
- let fdt = Fdt::from_mut_slice(fdt_data.as_mut_slice()).unwrap();
+ let fdt = Fdt::create_empty_tree(&mut fdt_data).unwrap();
let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap();
@@ -403,11 +581,13 @@
}
device_info.patch(platform_dt).unwrap();
+ // Note: Intentionally not using AssignedDeviceNode for matching all props.
type FdtResult<T> = libfdt::Result<T>;
let expected: Vec<(FdtResult<&CStr>, FdtResult<Vec<u8>>)> = vec![
(Ok(cstr!("android,rng,ignore-gctrl-reset")), Ok(Vec::new())),
(Ok(cstr!("compatible")), Ok(Vec::from(*b"android,rng\0"))),
(Ok(cstr!("interrupts")), Ok(into_fdt_prop(vec![0x0, 0xF, 0x4]))),
+ (Ok(cstr!("iommus")), Ok(Vec::new())),
(Ok(cstr!("reg")), Ok(into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]))),
];
@@ -425,4 +605,137 @@
assert_eq!(properties, expected);
}
+
+ #[test]
+ fn device_info_overlay_iommu() {
+ let mut fdt_data = fs::read(FDT_WITH_IOMMU_FILE_PATH).unwrap();
+ let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
+ let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
+ let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
+ let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
+ platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
+ let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
+ platform_dt.unpack().unwrap();
+
+ let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap().unwrap();
+ device_info.filter(vm_dtbo).unwrap();
+
+ // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
+ unsafe {
+ platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
+ }
+ device_info.patch(platform_dt).unwrap();
+
+ let expected = AssignedDeviceNode {
+ path: CString::new("/rng").unwrap(),
+ reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
+ interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
+ iommus: vec![0x4],
+ };
+
+ let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
+ assert_eq!(node, Ok(expected));
+
+ let pviommus = collect_pviommus(platform_dt);
+ assert_eq!(pviommus, Ok(vec![0x4]));
+ }
+
+ #[test]
+ fn device_info_multiple_devices_iommus() {
+ let mut fdt_data = fs::read(FDT_WITH_MULTIPLE_DEVICES_IOMMUS_FILE_PATH).unwrap();
+ let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
+ let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
+ let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
+ let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
+ platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
+ let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
+ platform_dt.unpack().unwrap();
+
+ let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap().unwrap();
+ device_info.filter(vm_dtbo).unwrap();
+
+ // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
+ unsafe {
+ platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
+ }
+ device_info.patch(platform_dt).unwrap();
+
+ let expected_devices = [
+ AssignedDeviceNode {
+ path: CString::new("/rng").unwrap(),
+ reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
+ interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
+ iommus: vec![0x4, 0x9],
+ },
+ AssignedDeviceNode {
+ path: CString::new("/light").unwrap(),
+ reg: into_fdt_prop(vec![0x100, 0x9]),
+ interrupts: into_fdt_prop(vec![0x0, 0xF, 0x5]),
+ iommus: vec![0x40, 0x50, 0x60],
+ },
+ ];
+
+ for expected in expected_devices {
+ let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
+ assert_eq!(node, Ok(expected));
+ }
+ let pviommus = collect_pviommus(platform_dt);
+ assert_eq!(pviommus, Ok(vec![0x4, 0x9, 0x40, 0x50, 0x60]));
+ }
+
+ #[test]
+ fn device_info_iommu_sharing() {
+ let mut fdt_data = fs::read(FDT_WITH_IOMMU_SHARING).unwrap();
+ let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
+ let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
+ let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
+ let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
+ platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
+ let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
+ platform_dt.unpack().unwrap();
+
+ let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap().unwrap();
+ device_info.filter(vm_dtbo).unwrap();
+
+ // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
+ unsafe {
+ platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
+ }
+ device_info.patch(platform_dt).unwrap();
+
+ let expected_devices = [
+ AssignedDeviceNode {
+ path: CString::new("/rng").unwrap(),
+ reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
+ interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
+ iommus: vec![0x4, 0x9],
+ },
+ AssignedDeviceNode {
+ path: CString::new("/light").unwrap(),
+ reg: into_fdt_prop(vec![0x100, 0x9]),
+ interrupts: into_fdt_prop(vec![0x0, 0xF, 0x5]),
+ iommus: vec![0x9, 0x40],
+ },
+ ];
+
+ for expected in expected_devices {
+ let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
+ assert_eq!(node, Ok(expected));
+ }
+
+ let pviommus = collect_pviommus(platform_dt);
+ assert_eq!(pviommus, Ok(vec![0x4, 0x9, 0x40]));
+ }
+
+ #[test]
+ fn device_info_iommu_id_conflict() {
+ let mut fdt_data = fs::read(FDT_WITH_IOMMU_ID_CONFLICT).unwrap();
+ let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
+ let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
+ let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
+
+ let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo);
+
+ assert_eq!(device_info, Err(DeviceAssignmentError::DuplicatedPvIommuIds));
+ }
}
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_iommu_id_conflict.dts b/pvmfw/testdata/test_pvmfw_devices_with_iommu_id_conflict.dts
new file mode 100644
index 0000000..f0a7162
--- /dev/null
+++ b/pvmfw/testdata/test_pvmfw_devices_with_iommu_id_conflict.dts
@@ -0,0 +1,84 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+ chosen {
+ stdout-path = "/uart@3f8";
+ linux,pci-probe-only = <1>;
+ };
+
+ memory {
+ device_type = "memory";
+ reg = <0x00 0x80000000 0xFFFFFFFF>;
+ };
+
+ reserved-memory {
+ #address-cells = <2>;
+ #size-cells = <2>;
+ ranges;
+ swiotlb: restricted_dma_reserved {
+ compatible = "restricted-dma-pool";
+ reg = <0xFFFFFFFF>;
+ size = <0xFFFFFFFF>;
+ alignment = <0xFFFFFFFF>;
+ };
+
+ dice {
+ compatible = "google,open-dice";
+ no-map;
+ reg = <0xFFFFFFFF>;
+ };
+ };
+
+ cpus {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ cpu@0 {
+ device_type = "cpu";
+ };
+ cpu@1 {
+ device_type = "cpu";
+ reg = <0x00 0x80000000 0xFFFFFFFF>;
+ };
+ };
+
+ rng@90000000 {
+ compatible = "android,rng";
+ reg = <0x0 0x9 0x0 0xFF>;
+ interrupts = <0x0 0xF 0x4>;
+ google,eh,ignore-gctrl-reset;
+ status = "okay";
+ iommus = <&pviommu_0>, <&pviommu_1>;
+ };
+
+ pviommu_0: pviommu0 {
+ compatible = "pkvm,pviommu";
+ id = <0x4>;
+ #iommu-cells = <0>;
+ };
+
+ pviommu_1: pviommu1 {
+ compatible = "pkvm,pviommu";
+ id = <0x9>;
+ #iommu-cells = <0>;
+ };
+
+ light@70000000 {
+ compatible = "android,light";
+ reg = <0x100 0x9>;
+ interrupts = <0x0 0xF 0x5>;
+ iommus = <&pviommu_0>, <&pviommu_a>, <&pviommu_b>;
+ };
+
+ pviommu_a: pviommua {
+ compatible = "pkvm,pviommu";
+ id = <0x40>;
+ #iommu-cells = <0>;
+ };
+
+ pviommu_b: pviommub {
+ compatible = "pkvm,pviommu";
+ id = <0x9>;
+ #iommu-cells = <0>;
+ };
+};
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_iommu_sharing.dts b/pvmfw/testdata/test_pvmfw_devices_with_iommu_sharing.dts
new file mode 100644
index 0000000..d6952fa
--- /dev/null
+++ b/pvmfw/testdata/test_pvmfw_devices_with_iommu_sharing.dts
@@ -0,0 +1,78 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+ chosen {
+ stdout-path = "/uart@3f8";
+ linux,pci-probe-only = <1>;
+ };
+
+ memory {
+ device_type = "memory";
+ reg = <0x00 0x80000000 0xFFFFFFFF>;
+ };
+
+ reserved-memory {
+ #address-cells = <2>;
+ #size-cells = <2>;
+ ranges;
+ swiotlb: restricted_dma_reserved {
+ compatible = "restricted-dma-pool";
+ reg = <0xFFFFFFFF>;
+ size = <0xFFFFFFFF>;
+ alignment = <0xFFFFFFFF>;
+ };
+
+ dice {
+ compatible = "google,open-dice";
+ no-map;
+ reg = <0xFFFFFFFF>;
+ };
+ };
+
+ cpus {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ cpu@0 {
+ device_type = "cpu";
+ };
+ cpu@1 {
+ device_type = "cpu";
+ reg = <0x00 0x80000000 0xFFFFFFFF>;
+ };
+ };
+
+ rng@90000000 {
+ compatible = "android,rng";
+ reg = <0x0 0x9 0x0 0xFF>;
+ interrupts = <0x0 0xF 0x4>;
+ google,eh,ignore-gctrl-reset;
+ status = "okay";
+ iommus = <&pviommu_0>, <&pviommu_1>;
+ };
+
+ light@70000000 {
+ compatible = "android,light";
+ reg = <0x100 0x9>;
+ interrupts = <0x0 0xF 0x5>;
+ iommus = <&pviommu_1>, <&pviommu_a>;
+ };
+
+ pviommu_0: pviommu0 {
+ compatible = "pkvm,pviommu";
+ id = <0x4>;
+ #iommu-cells = <0>;
+ };
+
+ pviommu_1: pviommu1 {
+ compatible = "pkvm,pviommu";
+ id = <0x9>;
+ #iommu-cells = <0>;
+ };
+
+ pviommu_a: pviommua {
+ compatible = "pkvm,pviommu";
+ id = <0x40>;
+ #iommu-cells = <0>;
+ };
+};
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_multiple_devices_iommus.dts b/pvmfw/testdata/test_pvmfw_devices_with_multiple_devices_iommus.dts
new file mode 100644
index 0000000..2609c45
--- /dev/null
+++ b/pvmfw/testdata/test_pvmfw_devices_with_multiple_devices_iommus.dts
@@ -0,0 +1,90 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+ chosen {
+ stdout-path = "/uart@3f8";
+ linux,pci-probe-only = <1>;
+ };
+
+ memory {
+ device_type = "memory";
+ reg = <0x00 0x80000000 0xFFFFFFFF>;
+ };
+
+ reserved-memory {
+ #address-cells = <2>;
+ #size-cells = <2>;
+ ranges;
+ swiotlb: restricted_dma_reserved {
+ compatible = "restricted-dma-pool";
+ reg = <0xFFFFFFFF>;
+ size = <0xFFFFFFFF>;
+ alignment = <0xFFFFFFFF>;
+ };
+
+ dice {
+ compatible = "google,open-dice";
+ no-map;
+ reg = <0xFFFFFFFF>;
+ };
+ };
+
+ cpus {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ cpu@0 {
+ device_type = "cpu";
+ };
+ cpu@1 {
+ device_type = "cpu";
+ reg = <0x00 0x80000000 0xFFFFFFFF>;
+ };
+ };
+
+ rng@90000000 {
+ compatible = "android,rng";
+ reg = <0x0 0x9 0x0 0xFF>;
+ interrupts = <0x0 0xF 0x4>;
+ google,eh,ignore-gctrl-reset;
+ status = "okay";
+ iommus = <&pviommu_0>, <&pviommu_1>;
+ };
+
+ pviommu_0: pviommu0 {
+ compatible = "pkvm,pviommu";
+ id = <0x4>;
+ #iommu-cells = <0>;
+ };
+
+ pviommu_1: pviommu1 {
+ compatible = "pkvm,pviommu";
+ id = <0x9>;
+ #iommu-cells = <0>;
+ };
+
+ light@70000000 {
+ compatible = "android,light";
+ reg = <0x100 0x9>;
+ interrupts = <0x0 0xF 0x5>;
+ iommus = <&pviommu_a>, <&pviommu_b>, <&pviommu_c>;
+ };
+
+ pviommu_a: pviommua {
+ compatible = "pkvm,pviommu";
+ id = <0x40>;
+ #iommu-cells = <0>;
+ };
+
+ pviommu_b: pviommub {
+ compatible = "pkvm,pviommu";
+ id = <0x50>;
+ #iommu-cells = <0>;
+ };
+
+ pviommu_c: pviommuc {
+ compatible = "pkvm,pviommu";
+ id = <0x60>;
+ #iommu-cells = <0>;
+ };
+};
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_rng_iommu.dts b/pvmfw/testdata/test_pvmfw_devices_with_rng_iommu.dts
new file mode 100644
index 0000000..6a5068c
--- /dev/null
+++ b/pvmfw/testdata/test_pvmfw_devices_with_rng_iommu.dts
@@ -0,0 +1,59 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+ chosen {
+ stdout-path = "/uart@3f8";
+ linux,pci-probe-only = <1>;
+ };
+
+ memory {
+ device_type = "memory";
+ reg = <0x00 0x80000000 0xFFFFFFFF>;
+ };
+
+ reserved-memory {
+ #address-cells = <2>;
+ #size-cells = <2>;
+ ranges;
+ swiotlb: restricted_dma_reserved {
+ compatible = "restricted-dma-pool";
+ reg = <0xFFFFFFFF>;
+ size = <0xFFFFFFFF>;
+ alignment = <0xFFFFFFFF>;
+ };
+
+ dice {
+ compatible = "google,open-dice";
+ no-map;
+ reg = <0xFFFFFFFF>;
+ };
+ };
+
+ cpus {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ cpu@0 {
+ device_type = "cpu";
+ };
+ cpu@1 {
+ device_type = "cpu";
+ reg = <0x00 0x80000000 0xFFFFFFFF>;
+ };
+ };
+
+ rng@90000000 {
+ compatible = "android,rng";
+ reg = <0x0 0x9 0x0 0xFF>;
+ interrupts = <0x0 0xF 0x4>;
+ google,eh,ignore-gctrl-reset;
+ status = "okay";
+ iommus = <&pviommu_0>;
+ };
+
+ pviommu_0: pviommu0 {
+ compatible = "pkvm,pviommu";
+ id = <0x4>;
+ #iommu-cells = <0>;
+ };
+};
diff --git a/secretkeeper/comm/Android.bp b/secretkeeper/comm/Android.bp
new file mode 100644
index 0000000..cb3e713
--- /dev/null
+++ b/secretkeeper/comm/Android.bp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+ name: "libsecretkeeper_comm.defaults",
+ crate_name: "secretkeeper_comm",
+ defaults: ["avf_build_flags_rust"],
+ edition: "2021",
+ lints: "android",
+ rustlibs: [
+ "libciborium",
+ "libcoset",
+ ],
+ proc_macros: ["libenumn"],
+ vendor_available: true,
+}
+
+rust_library {
+ name: "libsecretkeeper_comm_nostd",
+ defaults: ["libsecretkeeper_comm.defaults"],
+ srcs: ["src/lib.rs"],
+}
+
+rust_test {
+ name: "libsecretkeeper_comm.test",
+ defaults: [
+ "libsecretkeeper_comm.defaults",
+ "rdroidtest.defaults",
+ ],
+ srcs: ["tests/*.rs"],
+ test_suites: ["general-tests"],
+ rustlibs: [
+ "libsecretkeeper_comm_nostd",
+ ],
+}
diff --git a/secretkeeper/comm/src/cbor_convert.rs b/secretkeeper/comm/src/cbor_convert.rs
new file mode 100644
index 0000000..ab6ca3f
--- /dev/null
+++ b/secretkeeper/comm/src/cbor_convert.rs
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ */
+
+//! Implements various useful CBOR conversion method.
+
+use crate::data_types::error::Error;
+use alloc::vec::Vec;
+use ciborium::Value;
+
+/// Decodes the provided binary CBOR-encoded value and returns a
+/// [`ciborium::Value`] struct wrapped in Result.
+pub fn value_from_bytes(mut bytes: &[u8]) -> Result<Value, Error> {
+ let value = ciborium::de::from_reader(&mut bytes).map_err(|_| Error::ConversionError)?;
+ // Ciborium tries to read one Value, but doesn't care if there is trailing data after it. We do
+ if !bytes.is_empty() {
+ return Err(Error::ConversionError);
+ }
+ Ok(value)
+}
+
+/// Encodes a [`ciborium::Value`] into bytes.
+pub fn value_to_bytes(value: &Value) -> Result<Vec<u8>, Error> {
+ let mut bytes: Vec<u8> = Vec::new();
+ ciborium::ser::into_writer(&value, &mut bytes).map_err(|_| Error::UnexpectedError)?;
+ Ok(bytes)
+}
+
+// Useful to convert [`ciborium::Value`] to integer, we return largest integer range for
+// convenience, callers should downcast into appropriate type.
+pub fn value_to_integer(value: &Value) -> Result<i128, Error> {
+ let num = value.as_integer().ok_or(Error::ConversionError)?.into();
+ Ok(num)
+}
diff --git a/secretkeeper/comm/src/data_types/error.rs b/secretkeeper/comm/src/data_types/error.rs
new file mode 100644
index 0000000..6a5e24f
--- /dev/null
+++ b/secretkeeper/comm/src/data_types/error.rs
@@ -0,0 +1,94 @@
+/*
+ * 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.
+ */
+
+//! Error-like data structures. See `ResponsePacketError` in the CDDL
+
+// derive(N) generates a method that is missing a docstring.
+#![allow(missing_docs)]
+
+use crate::cbor_convert::value_to_integer;
+use crate::data_types::response::Response;
+use alloc::boxed::Box;
+use alloc::vec::Vec;
+use ciborium::Value;
+use enumn::N;
+
+/// 'Error code' corresponding to successful response.
+pub const ERROR_OK: u16 = 0; // All real errors must have non-zero error_codes
+
+/// Errors from Secretkeeper API. Keep in sync with `ErrorCode` defined for Secretkeeper HAL
+/// at SecretManagement.cddl
+#[derive(Clone, Copy, Debug, Eq, N, PartialEq)]
+pub enum SecretkeeperError {
+ // This is the Error code used if no other error codes explains the issue.
+ UnexpectedServerError = 1,
+ // Indicates the Request was malformed & hence couldn't be served.
+ RequestMalformed = 2,
+ // TODO(b/291228655): Add other errors such as DicePolicyError.
+}
+
+// [`SecretkeeperError`] is a valid [`Response`] type.
+// For more information see `ErrorCode` in SecretManagement.cddl alongside ISecretkeeper.aidl
+impl Response for SecretkeeperError {
+ fn new(response_cbor: Vec<Value>) -> Result<Box<Self>, Error> {
+ // TODO(b/291228655): This method currently discards the second value in response_cbor,
+ // which contains additional human-readable context in error. Include it!
+ if response_cbor.is_empty() || response_cbor.len() > 2 {
+ return Err(Error::ResponseMalformed);
+ }
+ let error_code: u16 = value_to_integer(&response_cbor[0])?.try_into()?;
+ SecretkeeperError::n(error_code)
+ .map_or_else(|| Err(Error::ResponseMalformed), |sk_err| Ok(Box::new(sk_err)))
+ }
+
+ fn error_code(&self) -> u16 {
+ *self as u16
+ }
+}
+
+/// Errors thrown internally by the library.
+#[derive(Debug, PartialEq)]
+pub enum Error {
+ /// Request was malformed.
+ RequestMalformed,
+ /// Response received from the server was malformed.
+ ResponseMalformed,
+ /// An error happened when serializing to/from a [`Value`].
+ CborValueError,
+ /// An error happened while casting a type to different type,
+ /// including one [`Value`] type to another.
+ ConversionError,
+ /// These are unexpected errors, which should never really happen.
+ UnexpectedError,
+}
+
+impl From<ciborium::value::Error> for Error {
+ fn from(_e: ciborium::value::Error) -> Self {
+ Self::CborValueError
+ }
+}
+
+impl From<ciborium::Value> for Error {
+ fn from(_e: ciborium::Value) -> Self {
+ Self::ConversionError
+ }
+}
+
+impl From<core::num::TryFromIntError> for Error {
+ fn from(_e: core::num::TryFromIntError) -> Self {
+ Self::ConversionError
+ }
+}
diff --git a/secretkeeper/comm/src/data_types/mod.rs b/secretkeeper/comm/src/data_types/mod.rs
new file mode 100644
index 0000000..096777f
--- /dev/null
+++ b/secretkeeper/comm/src/data_types/mod.rs
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+//! Implements the data structures specified by SecretManagement.cddl in Secretkeeper HAL.
+//! Data structures specified by SecretManagement.cddl in Secretkeeper HAL.
+//! Note this library must stay in sync with:
+//! platform/hardware/interfaces/security/\
+//! secretkeeper/aidl/android/hardware/security/secretkeeper/SecretManagement.cddl
+
+pub mod error;
+pub mod packet;
+pub mod request;
+pub mod request_response_impl;
+pub mod response;
diff --git a/secretkeeper/comm/src/data_types/packet.rs b/secretkeeper/comm/src/data_types/packet.rs
new file mode 100644
index 0000000..7a1e575
--- /dev/null
+++ b/secretkeeper/comm/src/data_types/packet.rs
@@ -0,0 +1,118 @@
+/*
+ * 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.
+ */
+
+//! Defines the packet structures passed between functional layer & the layer below.
+
+pub use ciborium::Value;
+
+use crate::cbor_convert::{value_from_bytes, value_to_bytes, value_to_integer};
+use crate::data_types::error::Error;
+use crate::data_types::error::ERROR_OK;
+use crate::data_types::request_response_impl::Opcode;
+use alloc::vec::Vec;
+
+/// Encapsulate Request-like data that functional layer operates on. All structures
+/// that implements `data_types::request::Request` can be serialized to [`ResponsePacket`].
+/// Similarly all [`RequestPacket`] can be deserialized to concrete Requests.
+/// Keep in sync with HAL spec (in particular RequestPacket):
+/// security/secretkeeper/aidl/android/hardware/security/secretkeeper/SecretManagement.cddl
+#[derive(Clone, Debug, PartialEq)]
+pub struct RequestPacket(Vec<Value>);
+
+impl RequestPacket {
+ /// Construct a [`RequestPacket`] from array of `ciborium::Value`
+ pub fn from(request_cbor: Vec<Value>) -> Self {
+ Self(request_cbor)
+ }
+
+ /// Get the containing CBOR. This can be used for getting concrete response objects.
+ /// Keep in sync with [`crate::data_types::request::Request::serialize_to_packet()`]
+ pub fn into_inner(self) -> Vec<Value> {
+ self.0
+ }
+
+ /// Extract [`Opcode`] corresponding to this packet. As defined in by the spec, this is
+ /// the first value in the CBOR array.
+ pub fn opcode(&self) -> Result<Opcode, Error> {
+ if self.0.is_empty() {
+ return Err(Error::RequestMalformed);
+ }
+ let num: u16 = value_to_integer(&self.0[0])?.try_into()?;
+
+ Opcode::n(num).ok_or(Error::RequestMalformed)
+ }
+
+ /// Serialize the [`ResponsePacket`] to bytes
+ pub fn into_bytes(self) -> Result<Vec<u8>, Error> {
+ value_to_bytes(&Value::Array(self.0))
+ }
+
+ /// Deserialize the bytes into [`ResponsePacket`]
+ pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
+ Ok(RequestPacket(value_from_bytes(bytes)?.into_array()?))
+ }
+}
+
+/// Encapsulate Response like data that the functional layer operates on. All structures
+/// that implements `data_types::response::Response` can be serialized to [`ResponsePacket`].
+/// Similarly all [`ResponsePacket`] can be deserialized to concrete Response.
+#[derive(Clone, Debug, PartialEq)]
+pub struct ResponsePacket(Vec<Value>);
+
+impl ResponsePacket {
+ /// Construct a [`ResponsePacket`] from array of `ciborium::Value`
+ pub fn from(response_cbor: Vec<Value>) -> Self {
+ Self(response_cbor)
+ }
+
+ /// Get raw content. This can be used for getting concrete response objects.
+ /// Keep in sync with `crate::data_types::response::Response::serialize_to_packet`
+ pub fn into_inner(self) -> Vec<Value> {
+ self.0
+ }
+
+ /// A [`ResponsePacket`] encapsulates different types of responses, find which one!
+ pub fn response_type(&self) -> Result<ResponseType, Error> {
+ if self.0.is_empty() {
+ return Err(Error::ResponseMalformed);
+ }
+ let error_code: u16 = value_to_integer(&self.0[0])?.try_into()?;
+ if error_code == ERROR_OK {
+ Ok(ResponseType::Success)
+ } else {
+ Ok(ResponseType::Error)
+ }
+ }
+
+ /// Serialize the [`ResponsePacket`] to bytes
+ pub fn into_bytes(self) -> Result<Vec<u8>, Error> {
+ value_to_bytes(&Value::Array(self.0))
+ }
+
+ /// Deserialize the bytes into [`ResponsePacket`]
+ pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
+ Ok(ResponsePacket(value_from_bytes(bytes)?.into_array()?))
+ }
+}
+
+/// Responses can be different type - `Success`-like or `Error`-like.
+#[derive(Debug, Eq, PartialEq)]
+pub enum ResponseType {
+ /// Indicates successful operation. See `ResponsePacketSuccess` in SecretManagement.cddl
+ Success,
+ /// Indicate failed operation. See `ResponsePacketError` in SecretManagement.cddl
+ Error,
+}
diff --git a/secretkeeper/comm/src/data_types/request.rs b/secretkeeper/comm/src/data_types/request.rs
new file mode 100644
index 0000000..0d54bcd
--- /dev/null
+++ b/secretkeeper/comm/src/data_types/request.rs
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ */
+
+//! Defines the shared behaviour of all request like data structures.
+
+use crate::data_types::error::Error;
+use crate::data_types::packet::RequestPacket;
+use crate::data_types::request_response_impl::Opcode;
+use alloc::boxed::Box;
+use alloc::vec::Vec;
+use ciborium::Value;
+
+/// Collection of methods defined for Secretkeeper's request-like data structures,
+/// e.g. `GetVersionRequestPacket` in the HAL spec.
+///
+/// Keep in sync with SecretManagement.cddl, in particular `RequestPacket` type.
+pub trait Request {
+ /// [`Opcode`] of the request: Each Request type is associated with an opcode. See `Opcode` in
+ /// SecretManagement.cddl.
+ const OPCODE: Opcode;
+
+ /// Constructor of the [`Request`] object. Implementation of this constructor should check
+ /// the args' type adheres to the HAL spec.
+ ///
+ /// # Arguments
+ /// * `args` - The vector of arguments associated with this request. Each argument is a
+ /// `ciborium::Value` type. See `Params` in `RequestPacket` in SecretManagement.cddl
+ fn new(args: Vec<Value>) -> Result<Box<Self>, Error>;
+
+ /// Get the 'arguments' of this request.
+ fn args(&self) -> Vec<Value>;
+
+ /// Serialize the request to a [`RequestPacket`], which, as per SecretManagement.cddl is:
+ /// ```
+ /// RequestPacket<Opcode, Params> = [
+ /// Opcode,
+ /// Params
+ /// ]
+ /// ```
+ fn serialize_to_packet(&self) -> RequestPacket {
+ let mut res = self.args();
+ res.insert(0, Value::from(Self::OPCODE as u16));
+ RequestPacket::from(res)
+ }
+
+ /// Construct the [`Request`] struct from given [`RequestPacket`].
+ fn deserialize_from_packet(packet: RequestPacket) -> Result<Box<Self>, Error> {
+ let mut req = packet.into_inner();
+ if req.get(0) != Some(&Value::from(Self::OPCODE as u16)) {
+ return Err(Error::RequestMalformed);
+ }
+ req.remove(0);
+ Self::new(req)
+ }
+}
diff --git a/secretkeeper/comm/src/data_types/request_response_impl.rs b/secretkeeper/comm/src/data_types/request_response_impl.rs
new file mode 100644
index 0000000..a7d29cc
--- /dev/null
+++ b/secretkeeper/comm/src/data_types/request_response_impl.rs
@@ -0,0 +1,97 @@
+/*
+ * 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.
+ */
+
+//! Implementation of request & response like data structures.
+
+// derive(N) generates a method that is missing a docstring.
+#![allow(missing_docs)]
+
+use crate::cbor_convert::value_to_integer;
+use crate::data_types::error::Error;
+use crate::data_types::error::ERROR_OK;
+use crate::data_types::request::Request;
+use crate::data_types::response::Response;
+use alloc::boxed::Box;
+use alloc::vec;
+use alloc::vec::Vec;
+use ciborium::Value;
+use enumn::N;
+
+/// Set of all possible `Opcode` supported by SecretManagement API of the HAL.
+/// See `Opcode` in SecretManagement.cddl
+#[derive(Clone, Copy, Debug, N, PartialEq)]
+#[non_exhaustive]
+pub enum Opcode {
+ /// Get version of the SecretManagement API.
+ GetVersion = 1,
+ /// Store a secret
+ StoreSecret = 2,
+ /// Get the secret
+ GetSecret = 3,
+}
+
+/// Corresponds to `GetVersionRequestPacket` defined in SecretManagement.cddl
+#[derive(Debug, Eq, PartialEq)]
+pub struct GetVersionRequest;
+
+impl Request for GetVersionRequest {
+ const OPCODE: Opcode = Opcode::GetVersion;
+
+ fn new(args: Vec<Value>) -> Result<Box<Self>, Error> {
+ if !args.is_empty() {
+ return Err(Error::RequestMalformed);
+ }
+ Ok(Box::new(Self))
+ }
+
+ fn args(&self) -> Vec<Value> {
+ Vec::new()
+ }
+}
+
+/// Success response corresponding to `GetVersionResponsePacket`.
+#[derive(Debug, Eq, PartialEq)]
+pub struct GetVersionResponse {
+ /// Version of SecretManagement API
+ version: u64,
+}
+
+impl GetVersionResponse {
+ pub fn new(version: u64) -> Self {
+ Self { version }
+ }
+ pub fn version(&self) -> u64 {
+ self.version
+ }
+}
+
+impl Response for GetVersionResponse {
+ fn new(res: Vec<Value>) -> Result<Box<Self>, Error> {
+ if res.len() != 2 {
+ return Err(Error::ResponseMalformed);
+ }
+ let error_code: u16 = value_to_integer(&res[0])?.try_into()?;
+ if error_code != ERROR_OK {
+ return Err(Error::ResponseMalformed);
+ }
+ let version: u64 = value_to_integer(&res[1])?.try_into()?;
+ Ok(Box::new(Self::new(version)))
+ }
+
+ fn result(&self) -> Vec<Value> {
+ vec![self.version.into()]
+ }
+}
diff --git a/secretkeeper/comm/src/data_types/response.rs b/secretkeeper/comm/src/data_types/response.rs
new file mode 100644
index 0000000..e975ebc
--- /dev/null
+++ b/secretkeeper/comm/src/data_types/response.rs
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+
+//! Defines the shared behaviour of all response like data structures.
+
+use crate::data_types::error::{Error, ERROR_OK};
+use crate::data_types::packet::ResponsePacket;
+use alloc::boxed::Box;
+use alloc::vec::Vec;
+use ciborium::Value;
+
+/// Shared behaviour of all Secretkeeper's response-like data structures,
+/// e.g. `GetVersionResponsePacket`. Note - A valid [`Response`] can be error as well, like
+/// `SecretkeeperError::RequestMalformed`.
+///
+/// Keep in sync with SecretManagement.cddl, in particular `ResponsePacket` type.
+pub trait Response {
+ /// Constructor of the Response object.
+ /// # Arguments
+ /// * `response_cbor`: A vector of `[ciborium::Value]` such that:
+ /// ```
+ /// For success-like responses:
+ /// ResponsePacketSuccess = [
+ /// 0, ; Indicates successful Response
+ /// result : Result
+ /// ]
+ /// For error responses:
+ /// ResponsePacketError = [
+ /// error_code: ErrorCode, ; Indicate the error
+ /// error_message: tstr ; Additional human-readable context
+ /// ]
+ /// ```
+ /// See ResponsePacket<Result> in SecretManagement.cddl alongside ISecretkeeper.aidl
+ fn new(response_cbor: Vec<Value>) -> Result<Box<Self>, Error>;
+
+ /// The result in the `Response`. By default this is empty, but [`Response`] structures like
+ /// `GetVersionResponse` must overwrite these to return the expected non-empty result.
+ fn result(&self) -> Vec<Value> {
+ Vec::new()
+ }
+
+ /// Error code corresponding to the response. The default value is 0 but that will work only
+ /// for successful responses. Error-like response structures must overwrite this method.
+ fn error_code(&self) -> u16 {
+ ERROR_OK // Indicates success
+ }
+
+ /// Serialize the response to a [`ResponsePacket`].
+ fn serialize_to_packet(&self) -> ResponsePacket {
+ let mut res = self.result();
+ res.insert(0, Value::from(self.error_code()));
+ ResponsePacket::from(res)
+ }
+
+ /// Construct the response struct from given [`ResponsePacket`].
+ fn deserialize_from_packet(packet: ResponsePacket) -> Result<Box<Self>, Error> {
+ let res = packet.into_inner();
+ // Empty response packet is not allowed, all responses in Secretkeeper HAL at least
+ // have `error_code` or '0'; so throw an error!
+ if res.is_empty() {
+ return Err(Error::ResponseMalformed);
+ }
+ Self::new(res)
+ }
+}
diff --git a/secretkeeper/comm/src/lib.rs b/secretkeeper/comm/src/lib.rs
new file mode 100644
index 0000000..9a10ac0
--- /dev/null
+++ b/secretkeeper/comm/src/lib.rs
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+//! This library exposes data structures and methods that can be used by Secretkeeper HAL & client
+//! implementation. This is compatible with Secretkeeper HAL specification.
+
+#![no_std]
+extern crate alloc;
+
+mod cbor_convert;
+pub mod data_types;
diff --git a/secretkeeper/comm/tests/data_types.rs b/secretkeeper/comm/tests/data_types.rs
new file mode 100644
index 0000000..68964fd
--- /dev/null
+++ b/secretkeeper/comm/tests/data_types.rs
@@ -0,0 +1,132 @@
+/*
+ * 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.
+ */
+
+//! Unit tests for testing serialization & deserialization of exported data_types.
+
+use ciborium::Value;
+use secretkeeper_comm::data_types::error::{Error, SecretkeeperError, ERROR_OK};
+use secretkeeper_comm::data_types::packet::{RequestPacket, ResponsePacket, ResponseType};
+use secretkeeper_comm::data_types::request::Request;
+use secretkeeper_comm::data_types::request_response_impl::Opcode;
+use secretkeeper_comm::data_types::request_response_impl::{GetVersionRequest, GetVersionResponse};
+use secretkeeper_comm::data_types::response::Response;
+
+#[cfg(test)]
+rdroidtest::test_main!();
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use rdroidtest::test;
+
+ test!(request_serialization_deserialization);
+ fn request_serialization_deserialization() {
+ let req = GetVersionRequest {};
+ let packet = req.serialize_to_packet();
+ assert_eq!(packet.opcode().unwrap(), Opcode::GetVersion);
+ assert_eq!(
+ RequestPacket::from_bytes(&packet.clone().into_bytes().unwrap()).unwrap(),
+ packet
+ );
+ let req_deserialized = *GetVersionRequest::deserialize_from_packet(packet).unwrap();
+ assert_eq!(req, req_deserialized);
+ }
+
+ test!(success_response_serialization_deserialization);
+ fn success_response_serialization_deserialization() {
+ let response = GetVersionResponse::new(1);
+ let packet = response.serialize_to_packet();
+ assert_eq!(packet.response_type().unwrap(), ResponseType::Success);
+ assert_eq!(
+ ResponsePacket::from_bytes(&packet.clone().into_bytes().unwrap()).unwrap(),
+ packet
+ );
+ let response_deserialized = *GetVersionResponse::deserialize_from_packet(packet).unwrap();
+ assert_eq!(response, response_deserialized);
+ }
+
+ test!(error_response_serialization_deserialization);
+ fn error_response_serialization_deserialization() {
+ let response = SecretkeeperError::RequestMalformed;
+ let packet = response.serialize_to_packet();
+ assert_eq!(packet.response_type().unwrap(), ResponseType::Error);
+ assert_eq!(
+ ResponsePacket::from_bytes(&packet.clone().into_bytes().unwrap()).unwrap(),
+ packet
+ );
+ let response_deserialized = *SecretkeeperError::deserialize_from_packet(packet).unwrap();
+ assert_eq!(response, response_deserialized);
+ }
+
+ test!(request_creation);
+ fn request_creation() {
+ let req: GetVersionRequest = *Request::new(vec![]).unwrap();
+ assert_eq!(req, GetVersionRequest {});
+ }
+
+ test!(response_creation);
+ fn response_creation() {
+ let res: GetVersionResponse =
+ *Response::new(vec![Value::from(ERROR_OK), Value::from(5)]).unwrap();
+ assert_eq!(res.version(), 5);
+ }
+
+ test!(invalid_get_version_request_creation);
+ fn invalid_get_version_request_creation() {
+ // A request with non-zero arg is considered invalid.
+ assert_eq!(
+ <GetVersionRequest as Request>::new(vec![Value::Null]).unwrap_err(),
+ Error::RequestMalformed
+ );
+ }
+
+ test!(invalid_get_version_response_creation);
+ fn invalid_get_version_response_creation() {
+ // A response with non-zero error_code is an invalid success response.
+ assert_eq!(
+ <GetVersionResponse as Response>::new(vec![
+ Value::from(SecretkeeperError::RequestMalformed as u16),
+ Value::from(5)
+ ])
+ .unwrap_err(),
+ Error::ResponseMalformed
+ );
+
+ // A response with incorrect size of array is invalid.
+ assert_eq!(
+ <GetVersionResponse as Response>::new(vec![
+ Value::from(ERROR_OK),
+ Value::from(5),
+ Value::from(7)
+ ])
+ .unwrap_err(),
+ Error::ResponseMalformed
+ );
+
+ // A response with incorrect type is invalid.
+ <GetVersionResponse as Response>::new(vec![Value::from(ERROR_OK), Value::from("a tstr")])
+ .unwrap_err();
+ }
+
+ test!(invalid_error_response_creation);
+ fn invalid_error_response_creation() {
+ // A response with ERROR_OK(0) as the error_code is an invalid error response.
+ assert_eq!(
+ <SecretkeeperError as Response>::new(vec![Value::from(ERROR_OK)]).unwrap_err(),
+ Error::ResponseMalformed
+ );
+ }
+}
diff --git a/secretkeeper/dice_policy/src/lib.rs b/secretkeeper/dice_policy/src/lib.rs
index 2e91305..076ba3b 100644
--- a/secretkeeper/dice_policy/src/lib.rs
+++ b/secretkeeper/dice_policy/src/lib.rs
@@ -213,10 +213,10 @@
ConstraintType::GreaterOrEqual => {
let value_in_node = value_in_node
.as_integer()
- .ok_or(anyhow!("Mismatch type: expected a cbor integer"))?;
+ .ok_or(anyhow!("Mismatch type: expected a CBOR integer"))?;
let value_min = value_in_constraint
.as_integer()
- .ok_or(anyhow!("Mismatch type: expected a cbor integer"))?;
+ .ok_or(anyhow!("Mismatch type: expected a CBOR integer"))?;
ensure!(value_in_node >= value_min);
}
};
@@ -260,9 +260,9 @@
Value::Bytes(b) => value_from_bytes(b)?
.into_map()
.map(Cow::Owned)
- .map_err(|e| anyhow!("Expected a cbor map: {:?}", e)),
+ .map_err(|e| anyhow!("Expected a CBOR map: {:?}", e)),
Value::Map(map) => Ok(Cow::Borrowed(map)),
- _ => bail!("/Expected a cbor map {:?}", cbor_map),
+ _ => bail!("Expected a CBOR map {:?}", cbor_map),
}
}
diff --git a/service_vm/requests/src/client_vm.rs b/service_vm/requests/src/client_vm.rs
index 74c26d3..612605f 100644
--- a/service_vm/requests/src/client_vm.rs
+++ b/service_vm/requests/src/client_vm.rs
@@ -17,22 +17,39 @@
use crate::keyblob::decrypt_private_key;
use alloc::vec::Vec;
-use bssl_avf::EcKey;
+use bssl_avf::{sha256, EcKey};
use core::result;
use coset::{CborSerializable, CoseSign};
use diced_open_dice::DiceArtifacts;
use log::error;
-use service_vm_comm::{ClientVmAttestationParams, Csr, RequestProcessingError};
+use service_vm_comm::{ClientVmAttestationParams, Csr, CsrPayload, RequestProcessingError};
type Result<T> = result::Result<T, RequestProcessingError>;
+const ATTESTATION_KEY_SIGNATURE_INDEX: usize = 1;
+
pub(super) fn request_attestation(
params: ClientVmAttestationParams,
dice_artifacts: &dyn DiceArtifacts,
) -> Result<Vec<u8>> {
let csr = Csr::from_cbor_slice(¶ms.csr)?;
- let _cose_sign = CoseSign::from_slice(&csr.signed_csr_payload)?;
- // TODO(b/309440321): Verify the signatures in the `_cose_sign`.
+ let cose_sign = CoseSign::from_slice(&csr.signed_csr_payload)?;
+ let csr_payload = cose_sign.payload.as_ref().ok_or_else(|| {
+ error!("No CsrPayload found in the CSR");
+ RequestProcessingError::InternalError
+ })?;
+ let csr_payload = CsrPayload::from_cbor_slice(csr_payload)?;
+
+ // AAD is empty as defined in service_vm/comm/client_vm_csr.cddl.
+ let aad = &[];
+
+ // TODO(b/310931749): Verify the first signature with CDI_Leaf_Pub of
+ // the DICE chain in `cose_sign`.
+
+ let ec_public_key = EcKey::from_cose_public_key(&csr_payload.public_key)?;
+ cose_sign.verify_signature(ATTESTATION_KEY_SIGNATURE_INDEX, aad, |signature, message| {
+ ecdsa_verify(&ec_public_key, signature, message)
+ })?;
// TODO(b/278717513): Compare client VM's DICE chain in the `csr` up to pvmfw
// cert with RKP VM's DICE chain.
@@ -49,3 +66,9 @@
// `_private_key`.
Err(RequestProcessingError::OperationUnimplemented)
}
+
+fn ecdsa_verify(key: &EcKey, signature: &[u8], message: &[u8]) -> bssl_avf::Result<()> {
+ // The message was signed with ECDSA with curve P-256 and SHA-256 at the signature generation.
+ let digest = sha256(message)?;
+ key.ecdsa_verify(signature, &digest)
+}
diff --git a/service_vm/requests/src/rkp.rs b/service_vm/requests/src/rkp.rs
index c2c13b3..9901a92 100644
--- a/service_vm/requests/src/rkp.rs
+++ b/service_vm/requests/src/rkp.rs
@@ -44,7 +44,8 @@
dice_artifacts: &dyn DiceArtifacts,
) -> Result<EcdsaP256KeyPair> {
let hmac_key = derive_hmac_key(dice_artifacts)?;
- let ec_key = EcKey::new_p256()?;
+ let mut ec_key = EcKey::new_p256()?;
+ ec_key.generate_key()?;
let maced_public_key = build_maced_public_key(ec_key.cose_public_key()?, hmac_key.as_ref())?;
let key_blob =
@@ -75,10 +76,13 @@
public_keys.push(public_key.to_cbor_value()?);
}
// Builds `CsrPayload`.
+ // TODO(b/299256925): The device information is currently empty as we do not
+ // have sufficient details to include.
+ let device_info = Value::Map(Vec::new());
let csr_payload = cbor!([
Value::Integer(CSR_PAYLOAD_SCHEMA_V3.into()),
Value::Text(String::from(CERTIFICATE_TYPE)),
- // TODO(b/299256925): Add device info in CBOR format here.
+ device_info,
Value::Array(public_keys),
])?;
let csr_payload = cbor_util::serialize(&csr_payload)?;
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index 001dfeb..886ca81 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -450,7 +450,9 @@
key, keyOverrides, /* isProtected= */ false, /* updateBootconfigs= */ true);
assertThatEventually(
100000,
- () -> getDevice().pullFileContents(LOG_PATH),
+ () ->
+ getDevice().pullFileContents(CONSOLE_PATH)
+ + getDevice().pullFileContents(LOG_PATH),
containsString("boot completed, time to run payload"));
vmInfo.mProcess.destroy();
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 c6291e4..b06eea6 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -1293,7 +1293,7 @@
assertThat(payloadStarted.getNow(false)).isTrue();
assertThat(exitCodeFuture.getNow(0)).isNotEqualTo(0);
- assertThat(listener.getLogOutput()).contains(reason);
+ assertThat(listener.getConsoleOutput() + listener.getLogOutput()).contains(reason);
}
@Test
@@ -1698,7 +1698,7 @@
.command(
"logcat",
"-e",
- "virtualizationmanager::aidl: Log.*executing main task",
+ "virtualizationmanager::aidl: (Console|Log).*executing main task",
"-t",
time)
.start();
diff --git a/virtualizationservice/Android.bp b/virtualizationservice/Android.bp
index bef7dd0..5cf2a39 100644
--- a/virtualizationservice/Android.bp
+++ b/virtualizationservice/Android.bp
@@ -31,6 +31,7 @@
"libanyhow",
"libavflog",
"libbinder_rs",
+ "libhypervisor_props",
"liblibc",
"liblog_rust",
"libnix",
diff --git a/virtualizationservice/src/main.rs b/virtualizationservice/src/main.rs
index d80ddd4..ea073bf 100644
--- a/virtualizationservice/src/main.rs
+++ b/virtualizationservice/src/main.rs
@@ -34,7 +34,7 @@
const LOG_TAG: &str = "VirtualizationService";
pub(crate) const REMOTELY_PROVISIONED_COMPONENT_SERVICE_NAME: &str =
- "android.system.virtualization.IRemotelyProvisionedComponent/avf";
+ "android.hardware.security.keymint.IRemotelyProvisionedComponent/avf";
fn get_calling_pid() -> pid_t {
ThreadState::get_calling_pid()
@@ -69,10 +69,17 @@
register_lazy_service(BINDER_SERVICE_IDENTIFIER, service.as_binder()).unwrap();
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.
+ if cfg!(remote_attestation) {
+ // The IRemotelyProvisionedComponent service is only supposed to be triggered by rkpd for
+ // RKP VM attestation.
+ let remote_provisioning_service = remote_provisioning::new_binder();
+ register_lazy_service(
+ REMOTELY_PROVISIONED_COMPONENT_SERVICE_NAME,
+ remote_provisioning_service.as_binder(),
+ )
+ .unwrap();
+ info!("Registered Binder service {}.", REMOTELY_PROVISIONED_COMPONENT_SERVICE_NAME);
+ }
ProcessState::join_thread_pool();
}
diff --git a/virtualizationservice/src/remote_provisioning.rs b/virtualizationservice/src/remote_provisioning.rs
index a9a07a5..40f54db 100644
--- a/virtualizationservice/src/remote_provisioning.rs
+++ b/virtualizationservice/src/remote_provisioning.rs
@@ -27,7 +27,11 @@
};
use anyhow::Context;
use avflog::LogResult;
-use binder::{BinderFeatures, Interface, IntoBinderResult, Result as BinderResult, Status, Strong};
+use binder::{
+ BinderFeatures, ExceptionCode, Interface, IntoBinderResult, Result as BinderResult, Status,
+ Strong,
+};
+use hypervisor_props::is_protected_vm_supported;
use service_vm_comm::{RequestProcessingError, Response};
/// Constructs a binder object that implements `IRemotelyProvisionedComponent`.
@@ -45,11 +49,13 @@
#[allow(non_snake_case)]
impl IRemotelyProvisionedComponent for AvfRemotelyProvisionedComponent {
fn getHardwareInfo(&self) -> BinderResult<RpcHardwareInfo> {
+ check_protected_vm_is_supported()?;
+
Ok(RpcHardwareInfo {
versionNumber: 3,
rpcAuthorName: String::from("Android Virtualization Framework"),
supportedEekCurve: CURVE_NONE,
- uniqueId: Some(String::from("Android Virtualization Framework 1")),
+ uniqueId: Some(String::from("AVF Remote Provisioning 1")),
supportedNumKeysInCsr: MIN_SUPPORTED_NUM_KEYS_IN_CSR,
})
}
@@ -59,6 +65,8 @@
testMode: bool,
macedPublicKey: &mut MacedPublicKey,
) -> BinderResult<Vec<u8>> {
+ check_protected_vm_is_supported()?;
+
if testMode {
return Err(Status::new_service_specific_error_str(
STATUS_REMOVED,
@@ -101,6 +109,8 @@
keysToSign: &[MacedPublicKey],
challenge: &[u8],
) -> BinderResult<Vec<u8>> {
+ check_protected_vm_is_supported()?;
+
const MAX_CHALLENGE_SIZE: usize = 64;
if challenge.len() > MAX_CHALLENGE_SIZE {
let message = format!(
@@ -123,6 +133,18 @@
}
}
+fn check_protected_vm_is_supported() -> BinderResult<()> {
+ if is_protected_vm_supported().unwrap_or(false) {
+ Ok(())
+ } else {
+ Err(Status::new_exception_str(
+ ExceptionCode::UNSUPPORTED_OPERATION,
+ Some("Protected VM support is missing for this operation"),
+ ))
+ .with_log()
+ }
+}
+
fn to_service_specific_error(response: Response) -> Status {
match response {
Response::Err(e) => match e {