Merge "secretkeeper_comm library: for Sk & client impl" into main
diff --git a/Android.bp b/Android.bp
index 550a6be..9c17c7f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -22,6 +22,7 @@
module_type: "rust_defaults",
config_namespace: "ANDROID",
bool_variables: [
+ "release_avf_enable_device_assignment",
"release_avf_enable_dice_changes",
"release_avf_enable_llpvm_changes",
"release_avf_enable_multi_tenant_microdroid_vm",
@@ -36,6 +37,9 @@
avf_flag_aware_rust_defaults {
name: "avf_build_flags_rust",
soong_config_variables: {
+ release_avf_enable_device_assignment: {
+ cfgs: ["device_assignment"],
+ },
release_avf_enable_dice_changes: {
cfgs: ["dice_changes"],
},
diff --git a/apex/Android.bp b/apex/Android.bp
index a4c8861..f2a0d64 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -67,7 +67,21 @@
],
}
-apex_defaults {
+soong_config_module_type {
+ name: "avf_flag_aware_apex_defaults",
+ module_type: "apex_defaults",
+ config_namespace: "ANDROID",
+ bool_variables: [
+ "release_avf_enable_device_assignment",
+ "release_avf_enable_vendor_modules",
+ ],
+ properties: [
+ "arch",
+ "prebuilts",
+ ],
+}
+
+avf_flag_aware_apex_defaults {
name: "com.android.virt_avf_enabled",
defaults: ["com.android.virt_common"],
@@ -79,7 +93,6 @@
arm64: {
binaries: [
"crosvm",
- "vfio_handler",
"virtmgr",
"virtualizationservice",
],
@@ -88,7 +101,6 @@
x86_64: {
binaries: [
"crosvm",
- "vfio_handler",
"virtmgr",
"virtualizationservice",
],
@@ -110,11 +122,33 @@
],
host_required: [
"vm_shell",
- "prepare_device_vfio",
],
apps: [
"EmptyPayloadApp",
],
+ soong_config_variables: {
+ release_avf_enable_device_assignment: {
+ prebuilts: [
+ "com.android.virt.vfio_handler.rc",
+ ],
+ arch: {
+ arm64: {
+ binaries: ["vfio_handler"],
+ },
+ x86_64: {
+ binaries: ["vfio_handler"],
+ },
+ },
+ },
+ release_avf_enable_vendor_modules: {
+ prebuilts: [
+ "microdroid_gki_initrd_debuggable",
+ "microdroid_gki_initrd_normal",
+ "microdroid_gki_kernel",
+ "microdroid_gki.json",
+ ],
+ },
+ },
}
apex_defaults {
@@ -141,10 +175,11 @@
installable: false,
}
-sh_binary_host {
- name: "prepare_device_vfio",
- src: "prepare_device_vfio.sh",
- filename: "prepare_device_vfio.sh",
+prebuilt_etc {
+ name: "com.android.virt.vfio_handler.rc",
+ src: "vfio_handler.rc",
+ filename: "vfio_handler.rc",
+ installable: false,
}
// Virt apex needs a custom signer for its payload
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/sign_virt_apex.py b/apex/sign_virt_apex.py
index 029ac76..7393636 100644
--- a/apex/sign_virt_apex.py
+++ b/apex/sign_virt_apex.py
@@ -413,10 +413,13 @@
# dict of (key, file) for re-sign/verification. keys are un-versioned for readability.
virt_apex_files = {
'kernel': 'etc/fs/microdroid_kernel',
+ 'gki_kernel': 'etc/fs/microdroid_gki_kernel',
'vbmeta.img': 'etc/fs/microdroid_vbmeta.img',
'super.img': 'etc/fs/microdroid_super.img',
'initrd_normal.img': 'etc/microdroid_initrd_normal.img',
+ 'gki_initrd_normal.img': 'etc/microdroid_gki_initrd_normal.img',
'initrd_debuggable.img': 'etc/microdroid_initrd_debuggable.img',
+ 'gki_initrd_debuggable.img': 'etc/microdroid_gki_initrd_debuggable.img',
}
@@ -458,26 +461,40 @@
images=images,
wait=images_f)
+ has_gki_kernel = os.path.isfile(files['gki_kernel'])
+
vbmeta_bc_f = None
if not args.do_not_update_bootconfigs:
- vbmeta_bc_f = Async(UpdateVbmetaBootconfig, args,
- [files['initrd_normal.img'],
- files['initrd_debuggable.img']], files['vbmeta.img'],
+ initrd_files = [files['initrd_normal.img'], files['initrd_debuggable.img']]
+ if has_gki_kernel:
+ initrd_files += [files['gki_initrd_normal.img'], files['gki_initrd_debuggable.img']]
+ vbmeta_bc_f = Async(UpdateVbmetaBootconfig, args, initrd_files,
+ files['vbmeta.img'],
wait=[vbmeta_f])
# Re-sign kernel. Note kernel's vbmeta contain addition descriptor from ramdisk(s)
- initrd_normal_hashdesc = tempfile.NamedTemporaryFile(delete=False).name
- initrd_debug_hashdesc = tempfile.NamedTemporaryFile(delete=False).name
- initrd_n_f = Async(GenVbmetaImage, args, files['initrd_normal.img'],
- initrd_normal_hashdesc, "initrd_normal",
- wait=[vbmeta_bc_f] if vbmeta_bc_f is not None else [])
- initrd_d_f = Async(GenVbmetaImage, args, files['initrd_debuggable.img'],
- initrd_debug_hashdesc, "initrd_debug",
- wait=[vbmeta_bc_f] if vbmeta_bc_f is not None else [])
- Async(AddHashFooter, args, key, files['kernel'], partition_name="boot",
- additional_descriptors=[
- initrd_normal_hashdesc, initrd_debug_hashdesc],
- wait=[initrd_n_f, initrd_d_f])
+ def resign_kernel(kernel, initrd_normal, initrd_debug):
+ kernel_file = files[kernel]
+ initrd_normal_file = files[initrd_normal]
+ initrd_debug_file = files[initrd_debug]
+
+ initrd_normal_hashdesc = tempfile.NamedTemporaryFile(delete=False).name
+ initrd_debug_hashdesc = tempfile.NamedTemporaryFile(delete=False).name
+ initrd_n_f = Async(GenVbmetaImage, args, initrd_normal_file,
+ initrd_normal_hashdesc, "initrd_normal",
+ wait=[vbmeta_bc_f] if vbmeta_bc_f is not None else [])
+ initrd_d_f = Async(GenVbmetaImage, args, initrd_debug_file,
+ initrd_debug_hashdesc, "initrd_debug",
+ wait=[vbmeta_bc_f] if vbmeta_bc_f is not None else [])
+ Async(AddHashFooter, args, key, kernel_file, partition_name="boot",
+ additional_descriptors=[
+ initrd_normal_hashdesc, initrd_debug_hashdesc],
+ wait=[initrd_n_f, initrd_d_f])
+
+ resign_kernel('kernel', 'initrd_normal.img', 'initrd_debuggable.img')
+
+ if has_gki_kernel:
+ resign_kernel('gki_kernel', 'gki_initrd_normal.img', 'gki_initrd_debuggable.img')
def VerifyVirtApex(args):
@@ -502,7 +519,8 @@
assert info['Public key (sha1)'] == pubkey_digest, f'pubkey mismatch: {file}'
for f in files.values():
- if f in (files['initrd_normal.img'], files['initrd_debuggable.img']):
+ if f in (files['initrd_normal.img'], files['initrd_debuggable.img'],
+ files['gki_initrd_normal.img'], files['gki_initrd_debuggable.img']):
# TODO(b/245277660): Verify that ramdisks contain the correct vbmeta digest
continue
if f == files['super.img']:
diff --git a/apex/vfio_handler.rc b/apex/vfio_handler.rc
new file mode 100644
index 0000000..419acef
--- /dev/null
+++ b/apex/vfio_handler.rc
@@ -0,0 +1,20 @@
+# 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.
+
+service vfio_handler /apex/com.android.virt/bin/vfio_handler
+ user root
+ group system
+ interface aidl android.system.virtualizationservice_internal.IVfioHandler
+ disabled
+ oneshot
diff --git a/apex/virtualizationservice.rc b/apex/virtualizationservice.rc
index 8283594..02b2081 100644
--- a/apex/virtualizationservice.rc
+++ b/apex/virtualizationservice.rc
@@ -19,10 +19,3 @@
interface aidl android.system.virtualizationservice
disabled
oneshot
-
-service vfio_handler /apex/com.android.virt/bin/vfio_handler
- user root
- group system
- interface aidl android.system.virtualizationservice_internal.IVfioHandler
- 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/cstr/Android.bp b/libs/cstr/Android.bp
new file mode 100644
index 0000000..4ea87df
--- /dev/null
+++ b/libs/cstr/Android.bp
@@ -0,0 +1,36 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_library_rlib {
+ name: "libcstr",
+ crate_name: "cstr",
+ defaults: ["avf_build_flags_rust"],
+ srcs: ["src/lib.rs"],
+ edition: "2021",
+ host_supported: true,
+ prefer_rlib: true,
+ target: {
+ android: {
+ no_stdlibs: true,
+ stdlibs: [
+ "libcompiler_builtins.rust_sysroot",
+ "libcore.rust_sysroot",
+ ],
+ },
+ },
+ apex_available: [
+ "//apex_available:platform",
+ "//apex_available:anyapex",
+ ],
+}
+
+rust_test {
+ name: "libcstr.tests",
+ crate_name: "libcstr_test",
+ defaults: ["avf_build_flags_rust"],
+ srcs: ["src/lib.rs"],
+ test_suites: ["general-tests"],
+ prefer_rlib: true,
+ rustlibs: ["libcstr"],
+}
diff --git a/libs/cstr/src/lib.rs b/libs/cstr/src/lib.rs
new file mode 100644
index 0000000..ddf20fc
--- /dev/null
+++ b/libs/cstr/src/lib.rs
@@ -0,0 +1,50 @@
+// 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.
+
+//! Provide a safe const-compatible no_std macro for readable &'static CStr.
+
+#![no_std]
+
+/// Create &CStr out of &str literal
+#[macro_export]
+macro_rules! cstr {
+ ($str:literal) => {{
+ const S: &str = concat!($str, "\0");
+ const C: &::core::ffi::CStr = match ::core::ffi::CStr::from_bytes_with_nul(S.as_bytes()) {
+ Ok(v) => v,
+ Err(_) => panic!("string contains interior NUL"),
+ };
+ C
+ }};
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::ffi::CString;
+
+ #[test]
+ fn valid_input_string() {
+ let expected = CString::new("aaa").unwrap();
+ assert_eq!(cstr!("aaa"), expected.as_c_str());
+ }
+
+ #[test]
+ fn valid_empty_string() {
+ let expected = CString::new("").unwrap();
+ assert_eq!(cstr!(""), expected.as_c_str());
+ }
+
+ // As cstr!() panics at compile time, tests covering invalid inputs fail to compile!
+}
diff --git a/libs/libfdt/Android.bp b/libs/libfdt/Android.bp
index b889ee5..ba9e971 100644
--- a/libs/libfdt/Android.bp
+++ b/libs/libfdt/Android.bp
@@ -37,6 +37,7 @@
"libcore.rust_sysroot",
],
rustlibs: [
+ "libcstr",
"liblibfdt_bindgen",
"libzerocopy_nostd",
],
@@ -61,28 +62,41 @@
],
prefer_rlib: true,
rustlibs: [
+ "libcstr",
"liblibfdt",
],
}
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/libs/libfdt/src/lib.rs b/libs/libfdt/src/lib.rs
index b369390..b513649 100644
--- a/libs/libfdt/src/lib.rs
+++ b/libs/libfdt/src/lib.rs
@@ -31,15 +31,9 @@
use core::ops::Range;
use core::ptr;
use core::result;
+use cstr::cstr;
use zerocopy::AsBytes as _;
-// TODO(b/308694211): Use cstr!() from vmbase
-macro_rules! cstr {
- ($str:literal) => {{
- core::ffi::CStr::from_bytes_with_nul(concat!($str, "\0").as_bytes()).unwrap()
- }};
-}
-
/// Error type corresponding to libfdt error codes.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum FdtError {
@@ -526,7 +520,7 @@
/// Phandle of a FDT node
#[repr(transparent)]
-#[derive(Debug, Copy, Clone, PartialEq)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub struct Phandle(u32);
impl Phandle {
diff --git a/libs/libfdt/tests/api_test.rs b/libs/libfdt/tests/api_test.rs
index d76b1a4..63cbdee 100644
--- a/libs/libfdt/tests/api_test.rs
+++ b/libs/libfdt/tests/api_test.rs
@@ -17,23 +17,12 @@
//! Integration tests of the library libfdt.
use core::ffi::CStr;
+use cstr::cstr;
use libfdt::{Fdt, FdtError, FdtNodeMut, Phandle};
use std::ffi::CString;
use std::fs;
use std::ops::Range;
-// TODO(b/308694211): Use cstr!() from vmbase
-macro_rules! cstr {
- ($str:literal) => {{
- const S: &str = concat!($str, "\0");
- const C: &::core::ffi::CStr = match ::core::ffi::CStr::from_bytes_with_nul(S.as_bytes()) {
- Ok(v) => v,
- Err(_) => panic!("string contains interior NUL"),
- };
- C
- }};
-}
-
const TEST_TREE_WITH_ONE_MEMORY_RANGE_PATH: &str = "data/test_tree_one_memory_range.dtb";
const TEST_TREE_WITH_MULTIPLE_MEMORY_RANGES_PATH: &str =
"data/test_tree_multiple_memory_ranges.dtb";
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index 4e735e6..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",
+ },
+ ],
+ },
},
},
}
@@ -330,6 +332,22 @@
],
}
+android_filesystem {
+ name: "microdroid_gki_modules-6.1-arm64",
+ deps: [
+ "microdroid_gki_kernel_modules-6.1-arm64",
+ ],
+ type: "compressed_cpio",
+}
+
+android_filesystem {
+ name: "microdroid_gki_modules-6.1-x86_64",
+ deps: [
+ "microdroid_gki_kernel_modules-6.1-x86_64",
+ ],
+ type: "compressed_cpio",
+}
+
genrule {
name: "microdroid_bootconfig_arm64_gen",
srcs: [
@@ -400,6 +418,11 @@
}
prebuilt_etc {
+ name: "microdroid_gki.json",
+ src: "microdroid_gki.json",
+}
+
+prebuilt_etc {
name: "microdroid_manifest",
src: "microdroid_manifest.xml",
filename: "manifest.xml",
@@ -444,6 +467,23 @@
},
}
+avb_gen_vbmeta_image {
+ name: "microdroid_gki_initrd_normal_hashdesc",
+ src: ":microdroid_gki_initrd_normal",
+ partition_name: "initrd_normal",
+ salt: initrd_normal_salt,
+ enabled: false,
+ arch: {
+ // Microdroid kernel is only available in these architectures.
+ arm64: {
+ enabled: true,
+ },
+ x86_64: {
+ enabled: true,
+ },
+ },
+}
+
// python -c "import hashlib; print(hashlib.sha256(b'initrd_debug').hexdigest())"
initrd_debug_salt = "8ab9dc9cb7e6456700ff6ef18c6b4c3acc24c5fa5381b829563f8d7a415d869a"
@@ -464,6 +504,23 @@
},
}
+avb_gen_vbmeta_image {
+ name: "microdroid_gki_initrd_debug_hashdesc",
+ src: ":microdroid_gki_initrd_debuggable",
+ partition_name: "initrd_debug",
+ salt: initrd_debug_salt,
+ enabled: false,
+ arch: {
+ // Microdroid kernel is only available in these architectures.
+ arm64: {
+ enabled: true,
+ },
+ x86_64: {
+ enabled: true,
+ },
+ },
+}
+
soong_config_module_type {
name: "flag_aware_avb_add_hash_footer",
module_type: "avb_add_hash_footer",
@@ -513,6 +570,42 @@
},
}
+flag_aware_avb_add_hash_footer {
+ name: "microdroid_gki_kernel_signed",
+ src: ":empty_file",
+ filename: "microdroid_gki_kernel",
+ partition_name: "boot",
+ private_key: ":microdroid_sign_key",
+ salt: bootloader_salt,
+ enabled: false,
+ arch: {
+ arm64: {
+ src: ":microdroid_gki_kernel_prebuilts-6.1-arm64",
+ enabled: true,
+ },
+ x86_64: {
+ src: ":microdroid_gki_kernel_prebuilts-6.1-x86_64",
+ enabled: true,
+ },
+ },
+ include_descriptors_from_images: [
+ ":microdroid_gki_initrd_normal_hashdesc",
+ ":microdroid_gki_initrd_debug_hashdesc",
+ ],
+ // Below are properties that are conditionally set depending on value of build flags.
+ soong_config_variables: {
+ release_avf_enable_llpvm_changes: {
+ rollback_index: 1,
+ props: [
+ {
+ name: "com.android.virt.cap",
+ value: "secretkeeper_protection",
+ },
+ ],
+ },
+ },
+}
+
prebuilt_etc {
name: "microdroid_kernel",
src: ":empty_file",
@@ -526,3 +619,17 @@
},
},
}
+
+prebuilt_etc {
+ name: "microdroid_gki_kernel",
+ src: ":empty_file",
+ relative_install_path: "fs",
+ arch: {
+ arm64: {
+ src: ":microdroid_gki_kernel_signed",
+ },
+ x86_64: {
+ src: ":microdroid_gki_kernel_signed",
+ },
+ },
+}
diff --git a/microdroid/initrd/Android.bp b/microdroid/initrd/Android.bp
index de28d8a..6cd84fa 100644
--- a/microdroid/initrd/Android.bp
+++ b/microdroid/initrd/Android.bp
@@ -40,6 +40,28 @@
cmd: "cat $(in) > $(out)",
}
+genrule {
+ name: "microdroid_gki_initrd_gen_arm64",
+ srcs: [
+ ":microdroid_ramdisk",
+ ":microdroid_fstab_ramdisk",
+ ":microdroid_gki_modules-6.1-arm64",
+ ],
+ out: ["microdroid_initrd.img"],
+ cmd: "cat $(in) > $(out)",
+}
+
+genrule {
+ name: "microdroid_gki_initrd_gen_x86_64",
+ srcs: [
+ ":microdroid_ramdisk",
+ ":microdroid_fstab_ramdisk",
+ ":microdroid_gki_modules-6.1-x86_64",
+ ],
+ out: ["microdroid_initrd.img"],
+ cmd: "cat $(in) > $(out)",
+}
+
// This contains vbmeta hashes & related (boot)configs which are passed to kernel/init
genrule {
name: "microdroid_vbmeta_bootconfig_gen",
@@ -74,6 +96,17 @@
}
genrule {
+ name: "microdroid_gki_initrd_debuggable_arm64",
+ tools: ["initrd_bootconfig"],
+ srcs: [
+ ":microdroid_gki_initrd_gen_arm64",
+ ":microdroid_bootconfig_debuggable_src",
+ ] + bootconfigs_arm64,
+ out: ["microdroid_gki_initrd_debuggable_arm64"],
+ cmd: "$(location initrd_bootconfig) attach --output $(out) $(in)",
+}
+
+genrule {
name: "microdroid_initrd_debuggable_x86_64",
tools: ["initrd_bootconfig"],
srcs: [
@@ -85,6 +118,17 @@
}
genrule {
+ name: "microdroid_gki_initrd_debuggable_x86_64",
+ tools: ["initrd_bootconfig"],
+ srcs: [
+ ":microdroid_gki_initrd_gen_x86_64",
+ ":microdroid_bootconfig_debuggable_src",
+ ] + bootconfigs_x86_64,
+ out: ["microdroid_gki_initrd_debuggable_x86_64"],
+ cmd: "$(location initrd_bootconfig) attach --output $(out) $(in)",
+}
+
+genrule {
name: "microdroid_initrd_normal_arm64",
tools: ["initrd_bootconfig"],
srcs: [
@@ -96,6 +140,17 @@
}
genrule {
+ name: "microdroid_gki_initrd_normal_arm64",
+ tools: ["initrd_bootconfig"],
+ srcs: [
+ ":microdroid_gki_initrd_gen_arm64",
+ ":microdroid_bootconfig_normal_src",
+ ] + bootconfigs_arm64,
+ out: ["microdroid_gki_initrd_normal_arm64"],
+ cmd: "$(location initrd_bootconfig) attach --output $(out) $(in)",
+}
+
+genrule {
name: "microdroid_initrd_normal_x86_64",
tools: ["initrd_bootconfig"],
srcs: [
@@ -106,6 +161,17 @@
cmd: "$(location initrd_bootconfig) attach --output $(out) $(in)",
}
+genrule {
+ name: "microdroid_gki_initrd_normal_x86_64",
+ tools: ["initrd_bootconfig"],
+ srcs: [
+ ":microdroid_gki_initrd_gen_x86_64",
+ ":microdroid_bootconfig_normal_src",
+ ] + bootconfigs_x86_64,
+ out: ["microdroid_gki_initrd_normal_x86_64"],
+ cmd: "$(location initrd_bootconfig) attach --output $(out) $(in)",
+}
+
prebuilt_etc {
name: "microdroid_initrd_debuggable",
// We don't have ramdisk for architectures other than x86_64 & arm64
@@ -122,6 +188,21 @@
}
prebuilt_etc {
+ name: "microdroid_gki_initrd_debuggable",
+ // We don't have ramdisk for architectures other than x86_64 & arm64
+ src: ":empty_file",
+ arch: {
+ x86_64: {
+ src: ":microdroid_gki_initrd_debuggable_x86_64",
+ },
+ arm64: {
+ src: ":microdroid_gki_initrd_debuggable_arm64",
+ },
+ },
+ filename: "microdroid_gki_initrd_debuggable.img",
+}
+
+prebuilt_etc {
name: "microdroid_initrd_normal",
// We don't have ramdisk for architectures other than x86_64 & arm64
src: ":empty_file",
@@ -135,3 +216,18 @@
},
filename: "microdroid_initrd_normal.img",
}
+
+prebuilt_etc {
+ name: "microdroid_gki_initrd_normal",
+ // We don't have ramdisk for architectures other than x86_64 & arm64
+ src: ":empty_file",
+ arch: {
+ x86_64: {
+ src: ":microdroid_gki_initrd_normal_x86_64",
+ },
+ arm64: {
+ src: ":microdroid_gki_initrd_normal_arm64",
+ },
+ },
+ filename: "microdroid_gki_initrd_normal.img",
+}
diff --git a/microdroid/microdroid_gki.json b/microdroid/microdroid_gki.json
new file mode 100644
index 0000000..d7ba53e
--- /dev/null
+++ b/microdroid/microdroid_gki.json
@@ -0,0 +1,20 @@
+{
+ "kernel": "/apex/com.android.virt/etc/fs/microdroid_gki_kernel",
+ "disks": [
+ {
+ "partitions": [
+ {
+ "label": "vbmeta_a",
+ "path": "/apex/com.android.virt/etc/fs/microdroid_vbmeta.img"
+ },
+ {
+ "label": "super",
+ "path": "/apex/com.android.virt/etc/fs/microdroid_super.img"
+ }
+ ],
+ "writable": false
+ }
+ ],
+ "memory_mib": 256,
+ "platform_version": "~1.0"
+}
diff --git a/pvmfw/Android.bp b/pvmfw/Android.bp
index b7b5900..103619f 100644
--- a/pvmfw/Android.bp
+++ b/pvmfw/Android.bp
@@ -16,6 +16,7 @@
"libbssl_ffi_nostd",
"libciborium_nostd",
"libciborium_io_nostd",
+ "libcstr",
"libdiced_open_dice_nostd",
"libfdtpci",
"libhyp",
@@ -55,6 +56,7 @@
unit_test: true,
},
rustlibs: [
+ "libcstr",
"libzeroize",
],
}
@@ -80,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"],
@@ -90,6 +120,7 @@
},
prefer_rlib: true,
rustlibs: [
+ "libcstr",
"liblibfdt",
"liblog_rust",
"libpvmfw_fdt_template",
@@ -98,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/bootargs.rs b/pvmfw/src/bootargs.rs
index a089a67..aacd8e0 100644
--- a/pvmfw/src/bootargs.rs
+++ b/pvmfw/src/bootargs.rs
@@ -108,19 +108,7 @@
#[cfg(test)]
mod tests {
use super::*;
-
- // TODO(b/308694211): Use cstr!() from vmbase
- macro_rules! cstr {
- ($str:literal) => {{
- const S: &str = concat!($str, "\0");
- const C: &::core::ffi::CStr = match ::core::ffi::CStr::from_bytes_with_nul(S.as_bytes())
- {
- Ok(v) => v,
- Err(_) => panic!("string contains interior NUL"),
- };
- C
- }};
- }
+ use cstr::cstr;
fn check(raw: &CStr, expected: Result<&[(&str, Option<&str>)], ()>) {
let actual = BootArgsIterator::new(raw);
diff --git a/pvmfw/src/crypto.rs b/pvmfw/src/crypto.rs
index 2b3d921..8f31553 100644
--- a/pvmfw/src/crypto.rs
+++ b/pvmfw/src/crypto.rs
@@ -33,7 +33,7 @@
use bssl_ffi::EVP_aead_aes_256_gcm_randnonce;
use bssl_ffi::EVP_AEAD;
use bssl_ffi::EVP_AEAD_CTX;
-use vmbase::cstr;
+use cstr::cstr;
#[derive(Debug)]
pub struct Error {
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/src/dice.rs b/pvmfw/src/dice.rs
index cc31f34..112c24c 100644
--- a/pvmfw/src/dice.rs
+++ b/pvmfw/src/dice.rs
@@ -17,12 +17,12 @@
use core::ffi::c_void;
use core::mem::size_of;
use core::slice;
+use cstr::cstr;
use diced_open_dice::{
bcc_format_config_descriptor, bcc_handover_main_flow, hash, Config, DiceConfigValues, DiceMode,
Hash, InputValues, HIDDEN_SIZE,
};
use pvmfw_avb::{DebugLevel, Digest, VerifiedBootData};
-use vmbase::cstr;
use vmbase::memory::flushed_zeroize;
fn to_dice_mode(debug_level: DebugLevel) -> DiceMode {
diff --git a/pvmfw/src/fdt.rs b/pvmfw/src/fdt.rs
index 7655614..4fe2c34 100644
--- a/pvmfw/src/fdt.rs
+++ b/pvmfw/src/fdt.rs
@@ -28,6 +28,7 @@
use core::fmt;
use core::mem::size_of;
use core::ops::Range;
+use cstr::cstr;
use fdtpci::PciMemoryFlags;
use fdtpci::PciRangeType;
use libfdt::AddressRange;
@@ -41,7 +42,6 @@
use log::info;
use log::warn;
use tinyvec::ArrayVec;
-use vmbase::cstr;
use vmbase::fdt::SwiotlbInfo;
use vmbase::layout::{crosvm::MEM_START, MAX_VIRT_ADDR};
use vmbase::memory::SIZE_4KB;
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/rialto/Android.bp b/rialto/Android.bp
index 28c261e..728c1eb 100644
--- a/rialto/Android.bp
+++ b/rialto/Android.bp
@@ -13,6 +13,7 @@
"libbssl_ffi_nostd",
"libciborium_io_nostd",
"libciborium_nostd",
+ "libcstr",
"libdiced_open_dice_nostd",
"libdiced_sample_inputs_nostd",
"libhyp",
diff --git a/rialto/src/fdt.rs b/rialto/src/fdt.rs
index 8bb40c3..09cdd36 100644
--- a/rialto/src/fdt.rs
+++ b/rialto/src/fdt.rs
@@ -15,8 +15,8 @@
//! High-level FDT functions.
use core::ops::Range;
+use cstr::cstr;
use libfdt::{Fdt, FdtError};
-use vmbase::cstr;
/// Reads the DICE data range from the given `fdt`.
pub fn read_dice_range_from(fdt: &Fdt) -> libfdt::Result<Range<usize>> {
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..2c01451 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 =
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index c6a30aa..6ae3bbd 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -101,6 +101,8 @@
const MICRODROID_OS_NAME: &str = "microdroid";
+const MICRODROID_GKI_OS_NAME: &str = "microdroid_gki";
+
const UNFORMATTED_STORAGE_MAGIC: &str = "UNFORMATTED-STORAGE";
/// Roughly estimated sufficient size for storing vendor public key into DTBO.
@@ -694,6 +696,16 @@
}
}
+fn is_valid_os(os_name: &str) -> bool {
+ if os_name == MICRODROID_OS_NAME {
+ return true;
+ }
+ if cfg!(vendor_modules) && os_name == MICRODROID_GKI_OS_NAME {
+ return true;
+ }
+ false
+}
+
fn load_app_config(
config: &VirtualMachineAppConfig,
debug_config: &DebugConfig,
@@ -717,9 +729,9 @@
Payload::PayloadConfig(payload_config) => create_vm_payload_config(payload_config)?,
};
- // For now, the only supported OS is Microdroid
+ // For now, the only supported OS is Microdroid and Microdroid GKI
let os_name = vm_payload_config.os.name.as_str();
- if os_name != MICRODROID_OS_NAME {
+ if !is_valid_os(os_name) {
bail!("Unknown OS \"{}\"", os_name);
}
@@ -753,7 +765,7 @@
vm_config.cpuTopology = config.cpuTopology;
// Microdroid takes additional init ramdisk & (optionally) storage image
- add_microdroid_system_images(config, instance_file, storage_image, &mut vm_config)?;
+ add_microdroid_system_images(config, instance_file, storage_image, os_name, &mut vm_config)?;
// Include Microdroid payload disk (contains apks, idsigs) in vm config
add_microdroid_payload_images(
@@ -788,8 +800,9 @@
}
let task = Task { type_: TaskType::MicrodroidLauncher, command: payload_binary_name.clone() };
+ let name = payload_config.osName.clone();
Ok(VmPayloadConfig {
- os: OsConfig { name: MICRODROID_OS_NAME.to_owned() },
+ os: OsConfig { name },
task: Some(task),
apexes: vec![],
extra_apks: vec![],
@@ -871,7 +884,7 @@
/// Check that a file SELinux label is acceptable.
///
/// We only want to allow code in a VM to be sourced from places that apps, and the
-/// system, do not have write access to.
+/// system or vendor, do not have write access to.
///
/// Note that sepolicy must also grant read access for these types to both virtualization
/// service and crosvm.
@@ -885,6 +898,7 @@
| "staging_data_file" // updated/staged APEX images
| "system_file" // immutable dm-verity protected partition
| "virtualizationservice_data_file" // files created by VS / VirtMgr
+ | "vendor_microdroid_file" // immutable dm-verity protected partition (/vendor/etc/avf/microdroid/.*)
=> Ok(()),
_ => bail!("Label {} is not allowed", context),
}
@@ -1195,10 +1209,24 @@
Ok(())
}
+fn check_no_devices(config: &VirtualMachineConfig) -> binder::Result<()> {
+ let VirtualMachineConfig::AppConfig(config) = config else { return Ok(()) };
+ if let Some(custom_config) = &config.customConfig {
+ if !custom_config.devices.is_empty() {
+ return Err(anyhow!("device assignment feature is disabled"))
+ .or_binder_exception(ExceptionCode::UNSUPPORTED_OPERATION);
+ }
+ }
+ Ok(())
+}
+
fn check_config_features(config: &VirtualMachineConfig) -> binder::Result<()> {
if !cfg!(vendor_modules) {
check_no_vendor_modules(config)?;
}
+ if !cfg!(device_assignment) {
+ check_no_devices(config)?;
+ }
Ok(())
}
@@ -1332,7 +1360,7 @@
}
fn requestAttestation(&self, csr: &[u8]) -> binder::Result<Vec<Certificate>> {
- GLOBAL_SERVICE.requestAttestation(csr)
+ GLOBAL_SERVICE.requestAttestation(csr, get_calling_uid() as i32)
}
}
diff --git a/virtualizationmanager/src/payload.rs b/virtualizationmanager/src/payload.rs
index 3bfad33..c19c103 100644
--- a/virtualizationmanager/src/payload.rs
+++ b/virtualizationmanager/src/payload.rs
@@ -425,6 +425,7 @@
config: &VirtualMachineAppConfig,
instance_file: File,
storage_image: Option<File>,
+ os_name: &str,
vm_config: &mut VirtualMachineRawConfig,
) -> Result<()> {
let debug_suffix = match config.debugLevel {
@@ -432,7 +433,7 @@
DebugLevel::FULL => "debuggable",
_ => return Err(anyhow!("unsupported debug level: {:?}", config.debugLevel)),
};
- let initrd = format!("/apex/com.android.virt/etc/microdroid_initrd_{}.img", debug_suffix);
+ let initrd = format!("/apex/com.android.virt/etc/{os_name}_initrd_{debug_suffix}.img");
vm_config.initrd = Some(open_parcel_file(Path::new(&initrd), false)?);
let mut writable_partitions = vec![Partition {
diff --git a/virtualizationservice/Android.bp b/virtualizationservice/Android.bp
index c00445d..bef7dd0 100644
--- a/virtualizationservice/Android.bp
+++ b/virtualizationservice/Android.bp
@@ -34,6 +34,7 @@
"liblibc",
"liblog_rust",
"libnix",
+ "librkpd_client",
"librustutils",
"libvmclient",
"libstatslog_virtualization_rust",
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachinePayloadConfig.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachinePayloadConfig.aidl
index 55c2f5d..f3f54f3 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachinePayloadConfig.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachinePayloadConfig.aidl
@@ -23,4 +23,9 @@
* function invoked.
*/
@utf8InCpp String payloadBinaryName;
+
+ /**
+ * Name of the OS to run the payload. Currently "microdroid" and "microdroid_gki" is supported.
+ */
+ @utf8InCpp String osName = "microdroid";
}
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
index 2592135..c384a6f 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
@@ -60,10 +60,14 @@
* Requests a certificate chain for the provided certificate signing request (CSR).
*
* @param csr The certificate signing request.
+ * @param requesterUid The UID of the app that requests remote attestation. The client VM to be
+ * attested is owned by this app.
+ * The uniqueness of the UID ensures that no two VMs owned by different apps
+ * are able to correlate keys.
* @return A sequence of DER-encoded X.509 certificates that make up the attestation
* key's certificate chain. The attestation key is provided in the CSR.
*/
- Certificate[] requestAttestation(in byte[] csr);
+ Certificate[] requestAttestation(in byte[] csr, int requesterUid);
/**
* Get a list of assignable devices.
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 2be2b19..938225e 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -14,7 +14,7 @@
//! Implementation of the AIDL interface of the VirtualizationService.
-use crate::{get_calling_pid, get_calling_uid};
+use crate::{get_calling_pid, get_calling_uid, REMOTELY_PROVISIONED_COMPONENT_SERVICE_NAME};
use crate::atom::{forward_vm_booted_atom, forward_vm_creation_atom, forward_vm_exited_atom};
use crate::rkpvm::request_attestation;
use android_os_permissions_aidl::aidl::android::os::IPermissionController;
@@ -39,6 +39,7 @@
use binder::{self, wait_for_interface, BinderFeatures, ExceptionCode, Interface, LazyServiceGuard, Status, Strong, IntoBinderResult};
use libc::VMADDR_CID_HOST;
use log::{error, info, warn};
+use rkpd_client::get_rkpd_attestation_key;
use rustutils::system_properties;
use serde::Deserialize;
use std::collections::{HashMap, HashSet};
@@ -159,14 +160,31 @@
Ok(cids)
}
- fn requestAttestation(&self, csr: &[u8]) -> binder::Result<Vec<Certificate>> {
+ fn requestAttestation(
+ &self,
+ csr: &[u8],
+ requester_uid: i32,
+ ) -> binder::Result<Vec<Certificate>> {
check_manage_access()?;
info!("Received csr. Requestting attestation...");
if cfg!(remote_attestation) {
- request_attestation(csr)
+ let attestation_key = get_rkpd_attestation_key(
+ REMOTELY_PROVISIONED_COMPONENT_SERVICE_NAME,
+ requester_uid as u32,
+ )
+ .context("Failed to retrieve the remotely provisioned keys")
+ .with_log()
+ .or_service_specific_exception(-1)?;
+ let certificate = request_attestation(csr, &attestation_key.keyBlob)
.context("Failed to request attestation")
.with_log()
- .or_service_specific_exception(-1)
+ .or_service_specific_exception(-1)?;
+ // TODO(b/309780089): Parse the remotely provisioned certificate chain into
+ // individual certificates.
+ let mut certificate_chain =
+ vec![Certificate { encodedCertificate: attestation_key.encodedCertChain }];
+ certificate_chain.push(Certificate { encodedCertificate: certificate });
+ Ok(certificate_chain)
} else {
Err(Status::new_exception_str(
ExceptionCode::UNSUPPORTED_OPERATION,
diff --git a/virtualizationservice/src/main.rs b/virtualizationservice/src/main.rs
index fd668bc..d80ddd4 100644
--- a/virtualizationservice/src/main.rs
+++ b/virtualizationservice/src/main.rs
@@ -33,7 +33,7 @@
use std::path::Path;
const LOG_TAG: &str = "VirtualizationService";
-const _REMOTELY_PROVISIONED_COMPONENT_SERVICE_NAME: &str =
+pub(crate) const REMOTELY_PROVISIONED_COMPONENT_SERVICE_NAME: &str =
"android.system.virtualization.IRemotelyProvisionedComponent/avf";
fn get_calling_pid() -> pid_t {
diff --git a/virtualizationservice/src/rkpvm.rs b/virtualizationservice/src/rkpvm.rs
index 8f1de6b..5087120 100644
--- a/virtualizationservice/src/rkpvm.rs
+++ b/virtualizationservice/src/rkpvm.rs
@@ -17,23 +17,25 @@
//! serves as a trusted platform to attest a client VM.
use android_hardware_security_rkp::aidl::android::hardware::security::keymint::MacedPublicKey::MacedPublicKey;
-use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::Certificate::Certificate;
use anyhow::{bail, Context, Result};
-use service_vm_comm::{GenerateCertificateRequestParams, Request, Response};
+use service_vm_comm::{
+ ClientVmAttestationParams, GenerateCertificateRequestParams, Request, Response,
+};
use service_vm_manager::ServiceVm;
-pub(crate) fn request_attestation(csr: &[u8]) -> Result<Vec<Certificate>> {
+pub(crate) fn request_attestation(
+ csr: &[u8],
+ remotely_provisioned_keyblob: &[u8],
+) -> Result<Vec<u8>> {
let mut vm = ServiceVm::start()?;
- // TODO(b/271275206): Send the correct request type with client VM's
- // information to be attested.
- let request = Request::Reverse(csr.to_vec());
+ let params = ClientVmAttestationParams {
+ csr: csr.to_vec(),
+ remotely_provisioned_key_blob: remotely_provisioned_keyblob.to_vec(),
+ };
+ let request = Request::RequestClientVmAttestation(params);
match vm.process_request(request).context("Failed to process request")? {
- // TODO(b/271275206): Adjust the response type.
- Response::Reverse(cert) => {
- let cert = Certificate { encodedCertificate: cert };
- Ok(vec![cert])
- }
+ Response::RequestClientVmAttestation(cert) => Ok(cert),
_ => bail!("Incorrect response type"),
}
}
diff --git a/vm/src/main.rs b/vm/src/main.rs
index 0af9791..87278bc 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -97,33 +97,24 @@
#[arg(long)]
storage_size: Option<u64>,
- /// Path to custom kernel image to use when booting Microdroid.
- #[cfg(vendor_modules)]
- #[arg(long)]
- kernel: Option<PathBuf>,
-
/// Path to disk image containing vendor-specific modules.
#[cfg(vendor_modules)]
#[arg(long)]
vendor: Option<PathBuf>,
/// SysFS nodes of devices to assign to VM
+ #[cfg(device_assignment)]
#[arg(long)]
devices: Vec<PathBuf>,
+
+ /// If set, use GKI instead of microdroid kernel
+ #[cfg(vendor_modules)]
+ #[arg(long)]
+ gki: bool,
}
impl MicrodroidConfig {
#[cfg(vendor_modules)]
- fn kernel(&self) -> &Option<PathBuf> {
- &self.kernel
- }
-
- #[cfg(not(vendor_modules))]
- fn kernel(&self) -> Option<PathBuf> {
- None
- }
-
- #[cfg(vendor_modules)]
fn vendor(&self) -> &Option<PathBuf> {
&self.vendor
}
@@ -132,6 +123,26 @@
fn vendor(&self) -> Option<PathBuf> {
None
}
+
+ #[cfg(vendor_modules)]
+ fn gki(&self) -> bool {
+ self.gki
+ }
+
+ #[cfg(not(vendor_modules))]
+ fn gki(&self) -> bool {
+ false
+ }
+
+ #[cfg(device_assignment)]
+ fn devices(&self) -> &Vec<PathBuf> {
+ &self.devices
+ }
+
+ #[cfg(not(device_assignment))]
+ fn devices(&self) -> Vec<PathBuf> {
+ Vec::new()
+ }
}
#[derive(Args)]
diff --git a/vm/src/run.rs b/vm/src/run.rs
index 1ba9dec..44ba9af 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -98,9 +98,6 @@
None
};
- let kernel =
- config.microdroid.kernel().as_ref().map(|p| open_parcel_file(p, false)).transpose()?;
-
let vendor =
config.microdroid.vendor().as_ref().map(|p| open_parcel_file(p, false)).transpose()?;
@@ -114,8 +111,11 @@
}
Payload::ConfigPath(config_path)
} else if let Some(payload_binary_name) = config.payload_binary_name {
+ let os_name =
+ if config.microdroid.gki() { "microdroid_gki" } else { "microdroid" }.to_owned();
Payload::PayloadConfig(VirtualMachinePayloadConfig {
payloadBinaryName: payload_binary_name,
+ osName: os_name,
})
} else {
bail!("Either --config-path or --payload-binary-name must be defined")
@@ -124,13 +124,13 @@
let payload_config_str = format!("{:?}!{:?}", config.apk, payload);
let custom_config = CustomConfig {
- customKernelImage: kernel,
+ customKernelImage: None,
gdbPort: config.debug.gdb.map(u16::from).unwrap_or(0) as i32, // 0 means no gdb
taskProfiles: config.common.task_profiles,
vendorImage: vendor,
devices: config
.microdroid
- .devices
+ .devices()
.iter()
.map(|x| {
x.to_str().map(String::from).ok_or(anyhow!("Failed to convert {x:?} to String"))
diff --git a/vmbase/Android.bp b/vmbase/Android.bp
index b2b1549..e682773 100644
--- a/vmbase/Android.bp
+++ b/vmbase/Android.bp
@@ -76,6 +76,7 @@
rustlibs: [
"libaarch64_paging",
"libbuddy_system_allocator",
+ "libcstr",
"libfdtpci",
"libhyp",
"liblibfdt",
diff --git a/vmbase/example/Android.bp b/vmbase/example/Android.bp
index ae1a593..fe9de44 100644
--- a/vmbase/example/Android.bp
+++ b/vmbase/example/Android.bp
@@ -9,6 +9,7 @@
srcs: ["src/main.rs"],
rustlibs: [
"libaarch64_paging",
+ "libcstr",
"libdiced_open_dice_nostd",
"libfdtpci",
"liblibfdt",
diff --git a/vmbase/example/src/main.rs b/vmbase/example/src/main.rs
index ebd981c..6f513ee 100644
--- a/vmbase/example/src/main.rs
+++ b/vmbase/example/src/main.rs
@@ -28,11 +28,12 @@
use aarch64_paging::paging::MemoryRegion;
use aarch64_paging::MapError;
use alloc::{vec, vec::Vec};
+use cstr::cstr;
use fdtpci::PciInfo;
use libfdt::Fdt;
use log::{debug, error, info, trace, warn, LevelFilter};
use vmbase::{
- bionic, configure_heap, cstr,
+ bionic, configure_heap,
layout::{dtb_range, rodata_range, scratch_range, text_range},
linker, logger, main,
memory::{PageTable, SIZE_64KB},
diff --git a/vmbase/src/bionic.rs b/vmbase/src/bionic.rs
index f8db1fe..a049616 100644
--- a/vmbase/src/bionic.rs
+++ b/vmbase/src/bionic.rs
@@ -22,11 +22,12 @@
use core::str;
use crate::console;
-use crate::cstr;
use crate::eprintln;
use crate::rand::fill_with_entropy;
use crate::read_sysreg;
+use cstr::cstr;
+
const EOF: c_int = -1;
const EIO: c_int = 5;
diff --git a/vmbase/src/fdt.rs b/vmbase/src/fdt.rs
index 537ca03..4101f7e 100644
--- a/vmbase/src/fdt.rs
+++ b/vmbase/src/fdt.rs
@@ -14,8 +14,8 @@
//! High-level FDT functions.
-use crate::cstr;
use core::ops::Range;
+use cstr::cstr;
use libfdt::{self, Fdt, FdtError};
/// Represents information about a SWIOTLB buffer.
diff --git a/vmbase/src/util.rs b/vmbase/src/util.rs
index 25586bc..8c230a1 100644
--- a/vmbase/src/util.rs
+++ b/vmbase/src/util.rs
@@ -16,19 +16,6 @@
use core::ops::Range;
-/// Create &CStr out of &str literal
-#[macro_export]
-macro_rules! cstr {
- ($str:literal) => {{
- const S: &str = concat!($str, "\0");
- const C: &::core::ffi::CStr = match ::core::ffi::CStr::from_bytes_with_nul(S.as_bytes()) {
- Ok(v) => v,
- Err(_) => panic!("string contains interior NUL"),
- };
- C
- }};
-}
-
/// Flatten [[T; N]] into &[T]
/// TODO: use slice::flatten when it graduates from experimental
pub fn flatten<T, const N: usize>(original: &[[T; N]]) -> &[T] {