Merge "libfdt: Add FdtNode::name()" into main
diff --git a/Android.bp b/Android.bp
index 22581b0..2f6fc20 100644
--- a/Android.bp
+++ b/Android.bp
@@ -54,7 +54,7 @@
cfgs: ["llpvm_changes"],
},
release_avf_enable_multi_tenant_microdroid_vm: {
- cfgs: ["payload_not_root"],
+ cfgs: ["multi_tenant"],
},
release_avf_enable_remote_attestation: {
cfgs: ["remote_attestation"],
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 171389b..adf6309 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -72,6 +72,9 @@
"path": "packages/modules/Virtualization/libs/avb"
},
{
+ "path": "packages/modules/Virtualization/libs/bssl"
+ },
+ {
"path": "packages/modules/Virtualization/libs/capabilities"
},
{
diff --git a/encryptedstore/src/main.rs b/encryptedstore/src/main.rs
index db3d4f6..dcb1cba 100644
--- a/encryptedstore/src/main.rs
+++ b/encryptedstore/src/main.rs
@@ -94,7 +94,7 @@
}
mount(&crypt_device, mountpoint)
.with_context(|| format!("Unable to mount {:?}", crypt_device))?;
- if cfg!(payload_not_root) && needs_formatting {
+ if cfg!(multi_tenant) && needs_formatting {
set_root_dir_permissions(mountpoint)?;
}
Ok(())
diff --git a/javalib/Android.bp b/javalib/Android.bp
index a124af7..cbc2a17 100644
--- a/javalib/Android.bp
+++ b/javalib/Android.bp
@@ -2,12 +2,25 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
+soong_config_module_type {
+ name: "avf_flag_aware_android_app",
+ module_type: "android_app",
+ config_namespace: "ANDROID",
+ bool_variables: ["release_avf_allow_preinstalled_apps"],
+ properties: ["manifest"],
+}
+
// Defines our permissions
-android_app {
+avf_flag_aware_android_app {
name: "android.system.virtualmachine.res",
installable: true,
apex_available: ["com.android.virt"],
platform_apis: true,
+ soong_config_variables: {
+ release_avf_allow_preinstalled_apps: {
+ manifest: "AndroidManifestNext.xml",
+ },
+ },
}
java_sdk_library {
diff --git a/javalib/AndroidManifestNext.xml b/javalib/AndroidManifestNext.xml
new file mode 100644
index 0000000..ebcb8ba
--- /dev/null
+++ b/javalib/AndroidManifestNext.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.virtualmachine.res">
+
+ <!-- @SystemApi Allows an application to create and run a Virtual Machine
+ using the Virtualization Framework APIs
+ (android.system.virtualmachine.*).
+ <p>Protection level: signature|preinstalled|development
+ @hide
+ -->
+ <permission android:name="android.permission.MANAGE_VIRTUAL_MACHINE"
+ android:protectionLevel="signature|preinstalled|development" />
+
+ <!-- @hide Allows an application to run a Virtual Machine with a custom
+ kernel or a Microdroid configuration file.
+ <p>Not for use by third-party applications.
+ -->
+ <permission android:name="android.permission.USE_CUSTOM_VIRTUAL_MACHINE"
+ android:protectionLevel="signature|development" />
+
+ <!-- @hide Allows an application to access various Virtual Machine debug
+ facilities, e.g. list all running VMs.
+ <p>Not for use by third-party applications.
+ -->
+ <permission android:name="android.permission.DEBUG_VIRTUAL_MACHINE"
+ android:protectionLevel="signature" />
+
+ <application android:hasCode="false" />
+</manifest>
diff --git a/javalib/api/test-current.txt b/javalib/api/test-current.txt
index 7c61712..12c099d 100644
--- a/javalib/api/test-current.txt
+++ b/javalib/api/test-current.txt
@@ -20,7 +20,7 @@
public class VirtualMachineManager {
method @RequiresPermission(android.system.virtualmachine.VirtualMachine.MANAGE_VIRTUAL_MACHINE_PERMISSION) public boolean isFeatureEnabled(String) throws android.system.virtualmachine.VirtualMachineException;
field public static final String FEATURE_DICE_CHANGES = "com.android.kvm.DICE_CHANGES";
- field public static final String FEATURE_PAYLOAD_NOT_ROOT = "com.android.kvm.PAYLOAD_NON_ROOT";
+ field public static final String FEATURE_MULTI_TENANT = "com.android.kvm.MULTI_TENANT";
field public static final String FEATURE_VENDOR_MODULES = "com.android.kvm.VENDOR_MODULES";
}
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
index e45fe99..a4927db 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
@@ -107,7 +107,7 @@
@Retention(RetentionPolicy.SOURCE)
@StringDef(
prefix = "FEATURE_",
- value = {FEATURE_DICE_CHANGES, FEATURE_PAYLOAD_NOT_ROOT, FEATURE_VENDOR_MODULES})
+ value = {FEATURE_DICE_CHANGES, FEATURE_MULTI_TENANT, FEATURE_VENDOR_MODULES})
public @interface Features {}
/**
@@ -123,8 +123,7 @@
* @hide
*/
@TestApi
- public static final String FEATURE_PAYLOAD_NOT_ROOT =
- IVirtualizationService.FEATURE_PAYLOAD_NON_ROOT;
+ public static final String FEATURE_MULTI_TENANT = IVirtualizationService.FEATURE_MULTI_TENANT;
/**
* Feature to allow vendor modules in Microdroid.
diff --git a/libs/bssl/Android.bp b/libs/bssl/Android.bp
new file mode 100644
index 0000000..0a2f334
--- /dev/null
+++ b/libs/bssl/Android.bp
@@ -0,0 +1,48 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+ name: "libbssl_avf_defaults",
+ crate_name: "bssl_avf",
+ srcs: ["src/lib.rs"],
+ prefer_rlib: true,
+ apex_available: [
+ "com.android.virt",
+ ],
+}
+
+rust_library_rlib {
+ name: "libbssl_avf_nostd",
+ defaults: ["libbssl_avf_defaults"],
+ no_stdlibs: true,
+ stdlibs: [
+ "libcompiler_builtins.rust_sysroot",
+ "libcore.rust_sysroot",
+ ],
+ rustlibs: [
+ "libbssl_avf_error_nostd",
+ "libbssl_ffi_nostd",
+ "libcoset_nostd",
+ "liblog_rust_nostd",
+ "libzeroize_nostd",
+ ],
+}
+
+rust_defaults {
+ name: "libbssl_avf_test_defaults",
+ crate_name: "bssl_avf_test",
+ srcs: ["tests/tests.rs"],
+ test_suites: ["general-tests"],
+ static_libs: [
+ "libcrypto_baremetal",
+ ],
+}
+
+rust_test {
+ name: "libbssl_avf_nostd.test",
+ defaults: ["libbssl_avf_test_defaults"],
+ rustlibs: [
+ "libbssl_avf_nostd",
+ ],
+}
diff --git a/libs/bssl/TEST_MAPPING b/libs/bssl/TEST_MAPPING
new file mode 100644
index 0000000..a91e8c5
--- /dev/null
+++ b/libs/bssl/TEST_MAPPING
@@ -0,0 +1,9 @@
+// When adding or removing tests here, don't forget to amend _all_modules list in
+// wireless/android/busytown/ath_config/configs/prod/avf/tests.gcl
+{
+ "avf-presubmit" : [
+ {
+ "name" : "libbssl_avf_nostd.test"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/libs/service_vm_comm/Android.bp b/libs/bssl/error/Android.bp
similarity index 61%
copy from libs/service_vm_comm/Android.bp
copy to libs/bssl/error/Android.bp
index cdb8fc3..dc2902e 100644
--- a/libs/service_vm_comm/Android.bp
+++ b/libs/bssl/error/Android.bp
@@ -3,9 +3,8 @@
}
rust_defaults {
- name: "libservice_vm_comm_defaults",
- crate_name: "service_vm_comm",
- defaults: ["avf_build_flags_rust"],
+ name: "libbssl_avf_error_defaults",
+ crate_name: "bssl_avf_error",
srcs: ["src/lib.rs"],
prefer_rlib: true,
apex_available: [
@@ -14,10 +13,11 @@
}
rust_library_rlib {
- name: "libservice_vm_comm_nostd",
- defaults: ["libservice_vm_comm_defaults"],
+ name: "libbssl_avf_error_nostd",
+ defaults: ["libbssl_avf_error_defaults"],
no_stdlibs: true,
stdlibs: [
+ "libcompiler_builtins.rust_sysroot",
"libcore.rust_sysroot",
],
rustlibs: [
@@ -26,12 +26,12 @@
}
rust_library {
- name: "libservice_vm_comm",
- defaults: ["libservice_vm_comm_defaults"],
- rustlibs: [
- "libserde",
- ],
+ name: "libbssl_avf_error",
+ defaults: ["libbssl_avf_error_defaults"],
features: [
"std",
],
+ rustlibs: [
+ "libserde",
+ ],
}
diff --git a/libs/bssl/error/src/code.rs b/libs/bssl/error/src/code.rs
new file mode 100644
index 0000000..7fb36c4
--- /dev/null
+++ b/libs/bssl/error/src/code.rs
@@ -0,0 +1,98 @@
+// 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.
+
+use core::fmt;
+use serde::{Deserialize, Serialize};
+
+type BsslReasonCode = i32;
+type BsslLibraryCode = i32;
+
+/// BoringSSL reason code.
+#[allow(missing_docs)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub enum ReasonCode {
+ NoError,
+ Global(GlobalError),
+ Cipher(CipherError),
+ Unknown(BsslReasonCode, BsslLibraryCode),
+}
+
+impl fmt::Display for ReasonCode {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Self::NoError => write!(f, "No error in the BoringSSL error queue."),
+ Self::Unknown(code, lib) => {
+ write!(f, "Unknown reason code '{code}' from the library '{lib}'")
+ }
+ other => write!(f, "{other:?}"),
+ }
+ }
+}
+
+/// Global errors may occur in any library.
+///
+/// The values are from:
+/// boringssl/src/include/openssl/err.h
+#[allow(missing_docs)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub enum GlobalError {
+ Fatal,
+ MallocFailure,
+ ShouldNotHaveBeenCalled,
+ PassedNullParameter,
+ InternalError,
+ Overflow,
+}
+
+impl fmt::Display for GlobalError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "A global error occurred: {self:?}")
+ }
+}
+
+/// Errors occurred in the Cipher functions.
+///
+/// The values are from:
+/// boringssl/src/include/openssl/cipher.h
+#[allow(missing_docs)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub enum CipherError {
+ AesKeySetupFailed,
+ BadDecrypt,
+ BadKeyLength,
+ BufferTooSmall,
+ CtrlNotImplemented,
+ CtrlOperationNotImplemented,
+ DataNotMultipleOfBlockLength,
+ InitializationError,
+ InputNotInitialized,
+ InvalidAdSize,
+ InvalidKeyLength,
+ InvalidNonceSize,
+ InvalidOperation,
+ IvTooLarge,
+ NoCipherSet,
+ OutputAliasesInput,
+ TagTooLarge,
+ TooLarge,
+ WrongFinalBlockLength,
+ NoDirectionSet,
+ InvalidNonce,
+}
+
+impl fmt::Display for CipherError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "An error occurred in a Cipher function: {self:?}")
+ }
+}
diff --git a/libs/bssl/error/src/lib.rs b/libs/bssl/error/src/lib.rs
new file mode 100644
index 0000000..547ad43
--- /dev/null
+++ b/libs/bssl/error/src/lib.rs
@@ -0,0 +1,68 @@
+// 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.
+
+//! Errors and relating structs thrown by the BoringSSL wrapper library.
+
+#![cfg_attr(not(feature = "std"), no_std)]
+
+mod code;
+
+use core::{fmt, result};
+use serde::{Deserialize, Serialize};
+
+pub use crate::code::{CipherError, GlobalError, ReasonCode};
+
+/// libbssl_avf result type.
+pub type Result<T> = result::Result<T, Error>;
+
+/// Error type used by libbssl_avf.
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub enum Error {
+ /// Failed to invoke a BoringSSL API.
+ CallFailed(ApiName, ReasonCode),
+
+ /// An unexpected internal error occurred.
+ InternalError,
+}
+
+impl fmt::Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Self::CallFailed(api_name, reason) => {
+ write!(f, "Failed to invoke the BoringSSL API: {api_name:?}. Reason: {reason}")
+ }
+ Self::InternalError => write!(f, "An unexpected internal error occurred"),
+ }
+ }
+}
+
+/// BoringSSL API names.
+#[allow(missing_docs)]
+#[allow(non_camel_case_types)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub enum ApiName {
+ BN_new,
+ BN_bn2bin_padded,
+ CBB_flush,
+ CBB_len,
+ EC_KEY_check_key,
+ 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_POINT_get_affine_coordinates,
+ HKDF,
+ HMAC,
+}
diff --git a/libs/bssl/src/cbb.rs b/libs/bssl/src/cbb.rs
new file mode 100644
index 0000000..9b5f7fe
--- /dev/null
+++ b/libs/bssl/src/cbb.rs
@@ -0,0 +1,53 @@
+// 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.
+
+//! Helpers for using BoringSSL CBB (crypto byte builder) objects.
+
+use bssl_ffi::{CBB_init_fixed, CBB};
+use core::marker::PhantomData;
+use core::mem::MaybeUninit;
+
+/// Wraps a CBB that references a existing fixed-sized buffer; no memory is allocated, but the
+/// buffer cannot grow.
+pub struct CbbFixed<'a> {
+ cbb: CBB,
+ /// The CBB contains a mutable reference to the buffer, disguised as a pointer.
+ /// Make sure the borrow checker knows that.
+ _buffer: PhantomData<&'a mut [u8]>,
+}
+
+impl<'a> CbbFixed<'a> {
+ /// Create a new CBB that writes to the given buffer.
+ pub fn new(buffer: &'a mut [u8]) -> Self {
+ let mut cbb = MaybeUninit::uninit();
+ // SAFETY: `CBB_init_fixed()` is infallible and always returns one.
+ // The buffer remains valid during the lifetime of `cbb`.
+ unsafe { CBB_init_fixed(cbb.as_mut_ptr(), buffer.as_mut_ptr(), buffer.len()) };
+ // SAFETY: `cbb` has just been initialized by `CBB_init_fixed()`.
+ let cbb = unsafe { cbb.assume_init() };
+ Self { cbb, _buffer: PhantomData }
+ }
+}
+
+impl<'a> AsRef<CBB> for CbbFixed<'a> {
+ fn as_ref(&self) -> &CBB {
+ &self.cbb
+ }
+}
+
+impl<'a> AsMut<CBB> for CbbFixed<'a> {
+ fn as_mut(&mut self) -> &mut CBB {
+ &mut self.cbb
+ }
+}
diff --git a/libs/bssl/src/digest.rs b/libs/bssl/src/digest.rs
new file mode 100644
index 0000000..49e66e6
--- /dev/null
+++ b/libs/bssl/src/digest.rs
@@ -0,0 +1,49 @@
+// 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 digest functions in BoringSSL digest.h.
+
+use bssl_ffi::{EVP_MD_size, EVP_sha256, EVP_sha512, EVP_MD};
+
+/// Message digester wrapping `EVP_MD`.
+#[derive(Clone, Debug)]
+pub struct Digester(pub(crate) &'static EVP_MD);
+
+impl Digester {
+ /// Returns a `Digester` implementing `SHA-256` algorithm.
+ pub fn sha256() -> Self {
+ // SAFETY: This function does not access any Rust variables and simply returns
+ // a pointer to the static variable in BoringSSL.
+ let p = unsafe { EVP_sha256() };
+ // SAFETY: The returned pointer should always be valid and points to a static
+ // `EVP_MD`.
+ Self(unsafe { &*p })
+ }
+
+ /// Returns a `Digester` implementing `SHA-512` algorithm.
+ pub fn sha512() -> Self {
+ // SAFETY: This function does not access any Rust variables and simply returns
+ // a pointer to the static variable in BoringSSL.
+ let p = unsafe { EVP_sha512() };
+ // SAFETY: The returned pointer should always be valid and points to a static
+ // `EVP_MD`.
+ Self(unsafe { &*p })
+ }
+
+ /// Returns the digest size in bytes.
+ pub fn size(&self) -> usize {
+ // SAFETY: The inner pointer is fetched from EVP_* hash functions in BoringSSL digest.h
+ unsafe { EVP_MD_size(self.0) }
+ }
+}
diff --git a/libs/bssl/src/ec_key.rs b/libs/bssl/src/ec_key.rs
new file mode 100644
index 0000000..7038e21
--- /dev/null
+++ b/libs/bssl/src/ec_key.rs
@@ -0,0 +1,203 @@
+// 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.
+
+//! Contains struct and functions that wraps the API related to EC_KEY in
+//! BoringSSL.
+
+use crate::cbb::CbbFixed;
+use crate::util::{check_int_result, to_call_failed_error};
+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_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_POINT_get_affine_coordinates, NID_X9_62_prime256v1, BIGNUM,
+ EC_GROUP, EC_KEY, EC_POINT,
+};
+use core::ptr::{self, NonNull};
+use core::result;
+use coset::{iana, CoseKey, CoseKeyBuilder};
+use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
+
+const P256_AFFINE_COORDINATE_SIZE: usize = 32;
+
+type Coordinate = [u8; P256_AFFINE_COORDINATE_SIZE];
+
+/// Wrapper of an `EC_KEY` object, representing a public or private EC key.
+pub struct EcKey(NonNull<EC_KEY>);
+
+impl Drop for EcKey {
+ fn drop(&mut self) {
+ // SAFETY: It is safe because the key has been allocated by BoringSSL and isn't
+ // used after this.
+ unsafe { EC_KEY_free(self.0.as_ptr()) }
+ }
+}
+
+impl EcKey {
+ /// Creates a new EC P-256 key pair.
+ pub fn new_p256() -> Result<Self> {
+ // SAFETY: The returned pointer is checked below.
+ 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)
+ .map(Self)
+ .ok_or(to_call_failed_error(ApiName::EC_KEY_new_by_curve_name))?;
+ ec_key.generate_key()?;
+ Ok(ec_key)
+ }
+
+ /// Generates a random, private key, calculates the corresponding public key and stores both
+ /// in the `EC_KEY`.
+ 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`.
+ let ret = unsafe { EC_KEY_generate_key(self.0.as_ptr()) };
+ check_int_result(ret, ApiName::EC_KEY_generate_key)
+ }
+
+ /// 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();
+ Ok(key)
+ }
+
+ /// Returns the x and y coordinates of the public key.
+ fn public_key_coordinates(&self) -> Result<(Coordinate, Coordinate)> {
+ let ec_group = self.ec_group()?;
+ let ec_point = self.public_key_ec_point()?;
+ let mut x = BigNum::new()?;
+ let mut y = BigNum::new()?;
+ let ctx = ptr::null_mut();
+ // SAFETY: All the parameters are checked non-null and initialized when needed.
+ // The last parameter `ctx` is generated when needed inside the function.
+ let ret = unsafe {
+ EC_POINT_get_affine_coordinates(ec_group, ec_point, x.as_mut_ptr(), y.as_mut_ptr(), ctx)
+ };
+ check_int_result(ret, ApiName::EC_POINT_get_affine_coordinates)?;
+ Ok((x.try_into()?, y.try_into()?))
+ }
+
+ /// Returns a pointer to the public key point inside `EC_KEY`. The memory region pointed
+ /// by the pointer is owned by the `EC_KEY`.
+ fn public_key_ec_point(&self) -> Result<*const EC_POINT> {
+ let ec_point =
+ // SAFETY: It is safe since the key pair has been generated and stored in the
+ // `EC_KEY` pointer.
+ unsafe { EC_KEY_get0_public_key(self.0.as_ptr()) };
+ if ec_point.is_null() {
+ Err(to_call_failed_error(ApiName::EC_KEY_get0_public_key))
+ } else {
+ Ok(ec_point)
+ }
+ }
+
+ /// Returns a pointer to the `EC_GROUP` object inside `EC_KEY`. The memory region pointed
+ /// by the pointer is owned by the `EC_KEY`.
+ fn ec_group(&self) -> Result<*const EC_GROUP> {
+ let group =
+ // SAFETY: It is safe since the key pair has been generated and stored in the
+ // `EC_KEY` pointer.
+ unsafe { EC_KEY_get0_group(self.0.as_ptr()) };
+ if group.is_null() {
+ Err(to_call_failed_error(ApiName::EC_KEY_get0_group))
+ } else {
+ Ok(group)
+ }
+ }
+
+ /// Returns the DER-encoded ECPrivateKey structure described in RFC 5915 Section 3:
+ ///
+ /// https://datatracker.ietf.org/doc/html/rfc5915#section-3
+ pub fn private_key(&self) -> Result<ZVec> {
+ const CAPACITY: usize = 256;
+ let mut buf = Zeroizing::new([0u8; CAPACITY]);
+ let mut cbb = CbbFixed::new(buf.as_mut());
+ let enc_flags = 0;
+ let ret =
+ // SAFETY: The function only write bytes to the buffer managed by the valid `CBB`
+ // object, and the key has been allocated by BoringSSL.
+ unsafe { EC_KEY_marshal_private_key(cbb.as_mut(), self.0.as_ptr(), enc_flags) };
+
+ check_int_result(ret, ApiName::EC_KEY_marshal_private_key)?;
+ // SAFETY: This is safe because the CBB pointer is a valid pointer initialized with
+ // `CBB_init_fixed()`.
+ check_int_result(unsafe { CBB_flush(cbb.as_mut()) }, ApiName::CBB_flush)?;
+ // SAFETY: This is safe because the CBB pointer is initialized with `CBB_init_fixed()`,
+ // and it has been flushed, thus it has no active children.
+ let len = unsafe { CBB_len(cbb.as_ref()) };
+ Ok(buf.get(0..len).ok_or(to_call_failed_error(ApiName::CBB_len))?.to_vec().into())
+ }
+}
+
+/// A u8 vector that is zeroed when dropped.
+#[derive(Zeroize, ZeroizeOnDrop)]
+pub struct ZVec(Vec<u8>);
+
+impl ZVec {
+ /// Extracts a slice containing the entire vector.
+ pub fn as_slice(&self) -> &[u8] {
+ &self.0[..]
+ }
+}
+
+impl From<Vec<u8>> for ZVec {
+ fn from(v: Vec<u8>) -> Self {
+ Self(v)
+ }
+}
+
+struct BigNum(NonNull<BIGNUM>);
+
+impl Drop for BigNum {
+ fn drop(&mut self) {
+ // SAFETY: The pointer has been created with `BN_new`.
+ unsafe { BN_clear_free(self.as_mut_ptr()) }
+ }
+}
+
+impl BigNum {
+ fn new() -> Result<Self> {
+ // SAFETY: The returned pointer is checked below.
+ let bn = unsafe { BN_new() };
+ NonNull::new(bn).map(Self).ok_or(to_call_failed_error(ApiName::BN_new))
+ }
+
+ fn as_mut_ptr(&mut self) -> *mut BIGNUM {
+ self.0.as_ptr()
+ }
+}
+
+/// 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] {
+ type Error = Error;
+
+ fn try_from(bn: BigNum) -> result::Result<Self, Self::Error> {
+ let mut num = [0u8; N];
+ // SAFETY: The `BIGNUM` pointer has been created with `BN_new`.
+ let ret = unsafe { BN_bn2bin_padded(num.as_mut_ptr(), num.len(), bn.0.as_ptr()) };
+ check_int_result(ret, ApiName::BN_bn2bin_padded)?;
+ Ok(num)
+ }
+}
+
+// TODO(b/301068421): Unit tests the EcKey.
diff --git a/libs/bssl/src/err.rs b/libs/bssl/src/err.rs
new file mode 100644
index 0000000..1ee40c9
--- /dev/null
+++ b/libs/bssl/src/err.rs
@@ -0,0 +1,112 @@
+// 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 error handling functions in BoringSSL err.h.
+
+use bssl_avf_error::{CipherError, GlobalError, ReasonCode};
+use bssl_ffi::{self, ERR_get_error, ERR_GET_LIB_RUST, ERR_GET_REASON_RUST};
+
+const NO_ERROR_REASON_CODE: i32 = 0;
+
+/// Returns the reason code for the least recent error and removes that
+/// error from the error queue.
+pub(crate) fn get_error_reason_code() -> ReasonCode {
+ let packed_error = get_packed_error();
+ let reason = get_reason(packed_error);
+ let lib = get_lib(packed_error);
+ map_to_reason_code(reason, lib)
+}
+
+/// Returns the packed error code for the least recent error and removes that
+/// error from the error queue.
+///
+/// Returns 0 if there are no errors in the queue.
+fn get_packed_error() -> u32 {
+ // SAFETY: This function only reads the error queue.
+ unsafe { ERR_get_error() }
+}
+
+fn get_reason(packed_error: u32) -> i32 {
+ // SAFETY: This function only reads the given error code.
+ unsafe { ERR_GET_REASON_RUST(packed_error) }
+}
+
+/// Returns the library code for the error.
+fn get_lib(packed_error: u32) -> i32 {
+ // SAFETY: This function only reads the given error code.
+ unsafe { ERR_GET_LIB_RUST(packed_error) }
+}
+
+fn map_to_reason_code(reason: i32, lib: i32) -> ReasonCode {
+ if reason == NO_ERROR_REASON_CODE {
+ return ReasonCode::NoError;
+ }
+ map_global_reason_code(reason)
+ .map(ReasonCode::Global)
+ .or_else(|| map_library_reason_code(reason, lib))
+ .unwrap_or(ReasonCode::Unknown(reason, lib))
+}
+
+/// Global errors may occur in any library.
+fn map_global_reason_code(reason: i32) -> Option<GlobalError> {
+ let reason = match reason {
+ bssl_ffi::ERR_R_FATAL => GlobalError::Fatal,
+ bssl_ffi::ERR_R_MALLOC_FAILURE => GlobalError::MallocFailure,
+ bssl_ffi::ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED => GlobalError::ShouldNotHaveBeenCalled,
+ bssl_ffi::ERR_R_PASSED_NULL_PARAMETER => GlobalError::PassedNullParameter,
+ bssl_ffi::ERR_R_INTERNAL_ERROR => GlobalError::InternalError,
+ bssl_ffi::ERR_R_OVERFLOW => GlobalError::Overflow,
+ _ => return None,
+ };
+ Some(reason)
+}
+
+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),
+ _ => None,
+ })
+}
+
+fn map_cipher_reason_code(reason: i32) -> Option<CipherError> {
+ let error = match reason {
+ bssl_ffi::CIPHER_R_AES_KEY_SETUP_FAILED => CipherError::AesKeySetupFailed,
+ bssl_ffi::CIPHER_R_BAD_DECRYPT => CipherError::BadDecrypt,
+ bssl_ffi::CIPHER_R_BAD_KEY_LENGTH => CipherError::BadKeyLength,
+ bssl_ffi::CIPHER_R_BUFFER_TOO_SMALL => CipherError::BufferTooSmall,
+ bssl_ffi::CIPHER_R_CTRL_NOT_IMPLEMENTED => CipherError::CtrlNotImplemented,
+ bssl_ffi::CIPHER_R_CTRL_OPERATION_NOT_IMPLEMENTED => {
+ CipherError::CtrlOperationNotImplemented
+ }
+ bssl_ffi::CIPHER_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH => {
+ CipherError::DataNotMultipleOfBlockLength
+ }
+ bssl_ffi::CIPHER_R_INITIALIZATION_ERROR => CipherError::InitializationError,
+ bssl_ffi::CIPHER_R_INPUT_NOT_INITIALIZED => CipherError::InputNotInitialized,
+ bssl_ffi::CIPHER_R_INVALID_AD_SIZE => CipherError::InvalidAdSize,
+ bssl_ffi::CIPHER_R_INVALID_KEY_LENGTH => CipherError::InvalidKeyLength,
+ bssl_ffi::CIPHER_R_INVALID_NONCE_SIZE => CipherError::InvalidNonceSize,
+ bssl_ffi::CIPHER_R_INVALID_OPERATION => CipherError::InvalidOperation,
+ bssl_ffi::CIPHER_R_IV_TOO_LARGE => CipherError::IvTooLarge,
+ bssl_ffi::CIPHER_R_NO_CIPHER_SET => CipherError::NoCipherSet,
+ bssl_ffi::CIPHER_R_OUTPUT_ALIASES_INPUT => CipherError::OutputAliasesInput,
+ bssl_ffi::CIPHER_R_TAG_TOO_LARGE => CipherError::TagTooLarge,
+ bssl_ffi::CIPHER_R_TOO_LARGE => CipherError::TooLarge,
+ bssl_ffi::CIPHER_R_WRONG_FINAL_BLOCK_LENGTH => CipherError::WrongFinalBlockLength,
+ bssl_ffi::CIPHER_R_NO_DIRECTION_SET => CipherError::NoDirectionSet,
+ bssl_ffi::CIPHER_R_INVALID_NONCE => CipherError::InvalidNonce,
+ _ => return None,
+ };
+ Some(error)
+}
diff --git a/libs/bssl/src/hkdf.rs b/libs/bssl/src/hkdf.rs
new file mode 100644
index 0000000..5dc6876
--- /dev/null
+++ b/libs/bssl/src/hkdf.rs
@@ -0,0 +1,49 @@
+// 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 HKDF functions in BoringSSL hkdf.h.
+
+use crate::digest::Digester;
+use crate::util::check_int_result;
+use bssl_avf_error::{ApiName, Result};
+use bssl_ffi::HKDF;
+
+/// Computes HKDF (as specified by [RFC 5869]) of initial keying material `secret` with
+/// `salt` and `info` using the given `digester`.
+///
+/// [RFC 5869]: https://www.rfc-editor.org/rfc/rfc5869.html
+pub fn hkdf<const N: usize>(
+ secret: &[u8],
+ salt: &[u8],
+ info: &[u8],
+ digester: Digester,
+) -> Result<[u8; N]> {
+ let mut key = [0u8; N];
+ // SAFETY: Only reads from/writes to the provided slices and the digester was non-null.
+ let ret = unsafe {
+ HKDF(
+ key.as_mut_ptr(),
+ key.len(),
+ digester.0,
+ secret.as_ptr(),
+ secret.len(),
+ salt.as_ptr(),
+ salt.len(),
+ info.as_ptr(),
+ info.len(),
+ )
+ };
+ check_int_result(ret, ApiName::HKDF)?;
+ Ok(key)
+}
diff --git a/libs/bssl/src/hmac.rs b/libs/bssl/src/hmac.rs
new file mode 100644
index 0000000..ddbbe4a
--- /dev/null
+++ b/libs/bssl/src/hmac.rs
@@ -0,0 +1,59 @@
+// 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 HMAC functions in BoringSSL hmac.h.
+
+use crate::digest::Digester;
+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;
+
+/// 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())
+}
+
+/// Computes the HMAC for the given `data` with the given `key` and `digester`.
+///
+/// The output size `HASH_LEN` should correspond to the length of the hash function's
+/// digest size in bytes.
+fn hmac<const HASH_LEN: usize>(
+ key: &[u8],
+ data: &[u8],
+ digester: Digester,
+) -> Result<[u8; HASH_LEN]> {
+ assert_eq!(digester.size(), HASH_LEN);
+
+ let mut out = [0u8; HASH_LEN];
+ let mut out_len = 0;
+ // SAFETY: Only reads from/writes to the provided slices and the digester was non-null.
+ let ret = unsafe {
+ HMAC(
+ digester.0,
+ key.as_ptr() as *const _,
+ key.len(),
+ data.as_ptr(),
+ data.len(),
+ out.as_mut_ptr(),
+ &mut out_len,
+ )
+ };
+ if !ret.is_null() && out_len == (out.len() as u32) {
+ Ok(out)
+ } else {
+ Err(to_call_failed_error(ApiName::HMAC))
+ }
+}
diff --git a/libs/service_vm_comm/src/lib.rs b/libs/bssl/src/lib.rs
similarity index 67%
copy from libs/service_vm_comm/src/lib.rs
copy to libs/bssl/src/lib.rs
index ca97ca1..8b38f5b 100644
--- a/libs/service_vm_comm/src/lib.rs
+++ b/libs/bssl/src/lib.rs
@@ -12,17 +12,24 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-//! This library contains the communication protocol used between the host
-//! and the service VM.
+//! Safe wrappers around the BoringSSL API.
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
-mod message;
-mod vsock;
+mod cbb;
+mod digest;
+mod ec_key;
+mod err;
+mod hkdf;
+mod hmac;
+mod util;
-pub use message::{
- EcdsaP256KeyPair, GenerateCertificateRequestParams, Request, Response, ServiceVmRequest,
-};
-pub use vsock::VmType;
+pub use bssl_avf_error::{ApiName, CipherError, Error, ReasonCode, Result};
+
+pub use cbb::CbbFixed;
+pub use digest::Digester;
+pub use ec_key::{EcKey, ZVec};
+pub use hkdf::hkdf;
+pub use hmac::hmac_sha256;
diff --git a/libs/bssl/src/util.rs b/libs/bssl/src/util.rs
new file mode 100644
index 0000000..880c85b
--- /dev/null
+++ b/libs/bssl/src/util.rs
@@ -0,0 +1,37 @@
+// 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.
+
+//! Utility functions.
+
+use crate::err::get_error_reason_code;
+use bssl_avf_error::{ApiName, Error, Result};
+use log::error;
+
+pub(crate) fn check_int_result(ret: i32, api_name: ApiName) -> Result<()> {
+ match ret {
+ 1 => Ok(()),
+ 0 => Err(Error::CallFailed(api_name, get_error_reason_code())),
+ _ => {
+ error!(
+ "Received a return value ({}) other than 0 or 1 from the BoringSSL API: {:?}",
+ ret, api_name
+ );
+ Err(Error::InternalError)
+ }
+ }
+}
+
+pub(crate) fn to_call_failed_error(api_name: ApiName) -> Error {
+ Error::CallFailed(api_name, get_error_reason_code())
+}
diff --git a/libs/bssl/tests/hkdf_test.rs b/libs/bssl/tests/hkdf_test.rs
new file mode 100644
index 0000000..1cda042
--- /dev/null
+++ b/libs/bssl/tests/hkdf_test.rs
@@ -0,0 +1,95 @@
+// 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.
+
+//! Test HKDF with the test cases in [RFC 5869] Appendix A
+//!
+//! [RFC 5869]: https://datatracker.ietf.org/doc/html/rfc5869
+
+use bssl_avf::{hkdf, Digester, Result};
+
+#[test]
+fn rfc5869_test_case_1() -> Result<()> {
+ const IKM: [u8; 22] = [
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ ];
+ const SALT: [u8; 13] =
+ [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c];
+ const INFO: [u8; 10] = [0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9];
+ const L: usize = 42;
+ const OKM: [u8; L] = [
+ 0x3c, 0xb2, 0x5f, 0x25, 0xfa, 0xac, 0xd5, 0x7a, 0x90, 0x43, 0x4f, 0x64, 0xd0, 0x36, 0x2f,
+ 0x2a, 0x2d, 0x2d, 0x0a, 0x90, 0xcf, 0x1a, 0x5a, 0x4c, 0x5d, 0xb0, 0x2d, 0x56, 0xec, 0xc4,
+ 0xc5, 0xbf, 0x34, 0x00, 0x72, 0x08, 0xd5, 0xb8, 0x87, 0x18, 0x58, 0x65,
+ ];
+ assert_eq!(OKM, hkdf::<L>(&IKM, &SALT, &INFO, Digester::sha256())?);
+ Ok(())
+}
+
+#[test]
+fn rfc5869_test_case_2() -> Result<()> {
+ const IKM: [u8; 80] = [
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
+ 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d,
+ 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c,
+ 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
+ 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
+ 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
+ ];
+ const SALT: [u8; 80] = [
+ 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e,
+ 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d,
+ 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c,
+ 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b,
+ 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa,
+ 0xab, 0xac, 0xad, 0xae, 0xaf,
+ ];
+ const INFO: [u8; 80] = [
+ 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe,
+ 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd,
+ 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc,
+ 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb,
+ 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa,
+ 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
+ ];
+ const L: usize = 82;
+ const OKM: [u8; L] = [
+ 0xb1, 0x1e, 0x39, 0x8d, 0xc8, 0x03, 0x27, 0xa1, 0xc8, 0xe7, 0xf7, 0x8c, 0x59, 0x6a, 0x49,
+ 0x34, 0x4f, 0x01, 0x2e, 0xda, 0x2d, 0x4e, 0xfa, 0xd8, 0xa0, 0x50, 0xcc, 0x4c, 0x19, 0xaf,
+ 0xa9, 0x7c, 0x59, 0x04, 0x5a, 0x99, 0xca, 0xc7, 0x82, 0x72, 0x71, 0xcb, 0x41, 0xc6, 0x5e,
+ 0x59, 0x0e, 0x09, 0xda, 0x32, 0x75, 0x60, 0x0c, 0x2f, 0x09, 0xb8, 0x36, 0x77, 0x93, 0xa9,
+ 0xac, 0xa3, 0xdb, 0x71, 0xcc, 0x30, 0xc5, 0x81, 0x79, 0xec, 0x3e, 0x87, 0xc1, 0x4c, 0x01,
+ 0xd5, 0xc1, 0xf3, 0x43, 0x4f, 0x1d, 0x87,
+ ];
+ assert_eq!(OKM, hkdf::<L>(&IKM, &SALT, &INFO, Digester::sha256())?);
+ Ok(())
+}
+
+#[test]
+fn rfc5869_test_case_3() -> Result<()> {
+ const IKM: [u8; 22] = [
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ ];
+ const SALT: [u8; 0] = [];
+ const INFO: [u8; 0] = [];
+ const L: usize = 42;
+ const OKM: [u8; L] = [
+ 0x8d, 0xa4, 0xe7, 0x75, 0xa5, 0x63, 0xc1, 0x8f, 0x71, 0x5f, 0x80, 0x2a, 0x06, 0x3c, 0x5a,
+ 0x31, 0xb8, 0xa1, 0x1f, 0x5c, 0x5e, 0xe1, 0x87, 0x9e, 0xc3, 0x45, 0x4e, 0x5f, 0x3c, 0x73,
+ 0x8d, 0x2d, 0x9d, 0x20, 0x13, 0x95, 0xfa, 0xa4, 0xb6, 0x1a, 0x96, 0xc8,
+ ];
+ assert_eq!(OKM, hkdf::<L>(&IKM, &SALT, &INFO, Digester::sha256())?);
+ Ok(())
+}
diff --git a/libs/bssl/tests/hmac_test.rs b/libs/bssl/tests/hmac_test.rs
new file mode 100644
index 0000000..c09a863
--- /dev/null
+++ b/libs/bssl/tests/hmac_test.rs
@@ -0,0 +1,115 @@
+// 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.
+
+//! Tests HMAC with the test cases in [RFC 4231] Section 4
+//!
+//! [RFC 4231]: https://datatracker.ietf.org/doc/html/rfc4231
+
+use bssl_avf::{hmac_sha256, Result};
+
+#[test]
+fn rfc4231_test_case_1() -> Result<()> {
+ const KEY: &[u8; 20] = &[0x0b; 20];
+ const DATA: &[u8] = b"Hi There";
+ const HMAC_SHA256: [u8; 32] = [
+ 0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0x0b, 0xf1,
+ 0x2b, 0x88, 0x1d, 0xc2, 0x00, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c, 0x2e, 0x32,
+ 0xcf, 0xf7,
+ ];
+ assert_eq!(HMAC_SHA256, hmac_sha256(KEY, DATA)?);
+ Ok(())
+}
+
+#[test]
+fn rfc4231_test_case_2() -> Result<()> {
+ const KEY: &[u8] = b"Jefe";
+ const DATA: &[u8] = b"what do ya want for nothing?";
+ const HMAC_SHA256: [u8; 32] = [
+ 0x5b, 0xdc, 0xc1, 0x46, 0xbf, 0x60, 0x75, 0x4e, 0x6a, 0x04, 0x24, 0x26, 0x08, 0x95, 0x75,
+ 0xc7, 0x5a, 0x00, 0x3f, 0x08, 0x9d, 0x27, 0x39, 0x83, 0x9d, 0xec, 0x58, 0xb9, 0x64, 0xec,
+ 0x38, 0x43,
+ ];
+ assert_eq!(HMAC_SHA256, hmac_sha256(KEY, DATA)?);
+ Ok(())
+}
+
+#[test]
+fn rfc4231_test_case_3() -> Result<()> {
+ const KEY: &[u8; 20] = &[0xaa; 20];
+ const DATA: &[u8; 50] = &[0xdd; 50];
+ const HMAC_SHA256: [u8; 32] = [
+ 0x77, 0x3e, 0xa9, 0x1e, 0x36, 0x80, 0x0e, 0x46, 0x85, 0x4d, 0xb8, 0xeb, 0xd0, 0x91, 0x81,
+ 0xa7, 0x29, 0x59, 0x09, 0x8b, 0x3e, 0xf8, 0xc1, 0x22, 0xd9, 0x63, 0x55, 0x14, 0xce, 0xd5,
+ 0x65, 0xfe,
+ ];
+ assert_eq!(HMAC_SHA256, hmac_sha256(KEY, DATA)?);
+ Ok(())
+}
+
+#[test]
+fn rfc4231_test_case_4() -> Result<()> {
+ const KEY: &[u8; 25] = &[
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
+ ];
+ const DATA: &[u8; 50] = &[0xcd; 50];
+ const HMAC_SHA256: [u8; 32] = [
+ 0x82, 0x55, 0x8a, 0x38, 0x9a, 0x44, 0x3c, 0x0e, 0xa4, 0xcc, 0x81, 0x98, 0x99, 0xf2, 0x08,
+ 0x3a, 0x85, 0xf0, 0xfa, 0xa3, 0xe5, 0x78, 0xf8, 0x07, 0x7a, 0x2e, 0x3f, 0xf4, 0x67, 0x29,
+ 0x66, 0x5b,
+ ];
+ assert_eq!(HMAC_SHA256, hmac_sha256(KEY, DATA)?);
+ Ok(())
+}
+
+/// Test with a truncation of output to 128 bits.
+#[test]
+fn rfc4231_test_case_5() -> Result<()> {
+ const KEY: &[u8; 20] = &[0x0c; 20];
+ const DATA: &[u8] = b"Test With Truncation";
+ const HMAC_SHA256: [u8; 16] = [
+ 0xa3, 0xb6, 0x16, 0x74, 0x73, 0x10, 0x0e, 0xe0, 0x6e, 0x0c, 0x79, 0x6c, 0x29, 0x55, 0x55,
+ 0x2b,
+ ];
+ let res = hmac_sha256(KEY, DATA)?;
+ assert_eq!(HMAC_SHA256, res[..16]);
+ Ok(())
+}
+
+#[test]
+fn rfc4231_test_case_6() -> Result<()> {
+ const KEY: &[u8; 131] = &[0xaa; 131];
+ const DATA: &[u8] = b"Test Using Larger Than Block-Size Key - Hash Key First";
+ const HMAC_SHA256: [u8; 32] = [
+ 0x60, 0xe4, 0x31, 0x59, 0x1e, 0xe0, 0xb6, 0x7f, 0x0d, 0x8a, 0x26, 0xaa, 0xcb, 0xf5, 0xb7,
+ 0x7f, 0x8e, 0x0b, 0xc6, 0x21, 0x37, 0x28, 0xc5, 0x14, 0x05, 0x46, 0x04, 0x0f, 0x0e, 0xe3,
+ 0x7f, 0x54,
+ ];
+ assert_eq!(HMAC_SHA256, hmac_sha256(KEY, DATA)?);
+ Ok(())
+}
+
+#[test]
+fn rfc4231_test_case_7() -> Result<()> {
+ const KEY: &[u8; 131] = &[0xaa; 131];
+ const DATA: &str = "This is a test using a larger than block-size key and a larger than \
+ block-size data. The key needs to be hashed before being used by the HMAC algorithm.";
+ const HMAC_SHA256: [u8; 32] = [
+ 0x9b, 0x09, 0xff, 0xa7, 0x1b, 0x94, 0x2f, 0xcb, 0x27, 0x63, 0x5f, 0xbc, 0xd5, 0xb0, 0xe9,
+ 0x44, 0xbf, 0xdc, 0x63, 0x64, 0x4f, 0x07, 0x13, 0x93, 0x8a, 0x7f, 0x51, 0x53, 0x5c, 0x3a,
+ 0x35, 0xe2,
+ ];
+ assert_eq!(HMAC_SHA256, hmac_sha256(KEY, DATA.as_bytes())?);
+ Ok(())
+}
diff --git a/rialto/src/requests/mod.rs b/libs/bssl/tests/tests.rs
similarity index 83%
copy from rialto/src/requests/mod.rs
copy to libs/bssl/tests/tests.rs
index 2ed568c..1077787 100644
--- a/rialto/src/requests/mod.rs
+++ b/libs/bssl/tests/tests.rs
@@ -12,9 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-//! This module contains functions for the request processing.
+//! API tests of the crate `bssl_avf`.
-mod api;
-mod rkp;
-
-pub use api::process_request;
+mod hkdf_test;
+mod hmac_test;
diff --git a/libs/hyp/src/hypervisor/mod.rs b/libs/hyp/src/hypervisor.rs
similarity index 100%
rename from libs/hyp/src/hypervisor/mod.rs
rename to libs/hyp/src/hypervisor.rs
diff --git a/libs/libfdt/src/iterators.rs b/libs/libfdt/src/iterators.rs
index 05fdb4a..280257b 100644
--- a/libs/libfdt/src/iterators.rs
+++ b/libs/libfdt/src/iterators.rs
@@ -14,6 +14,7 @@
//! Iterators over cells, and various layers on top of them.
+use crate::FdtError;
use crate::{AddrCells, SizeCells};
use core::marker::PhantomData;
use core::{mem::size_of, ops::Range, slice::ChunksExact};
@@ -59,6 +60,21 @@
pub size: Option<T>,
}
+impl<T: TryInto<usize>> TryFrom<Reg<T>> for Range<usize> {
+ type Error = FdtError;
+
+ fn try_from(reg: Reg<T>) -> Result<Self, Self::Error> {
+ let addr = to_usize(reg.addr)?;
+ let size = to_usize(reg.size.ok_or(FdtError::NotFound)?)?;
+ let end = addr.checked_add(size).ok_or(FdtError::BadValue)?;
+ Ok(addr..end)
+ }
+}
+
+fn to_usize<T: TryInto<usize>>(num: T) -> Result<usize, FdtError> {
+ num.try_into().map_err(|_| FdtError::BadValue)
+}
+
impl<'a> RegIterator<'a> {
pub(crate) fn new(
cells: CellIterator<'a>,
diff --git a/libs/libfdt/src/lib.rs b/libs/libfdt/src/lib.rs
index 43eadae..19ce0f7 100644
--- a/libs/libfdt/src/lib.rs
+++ b/libs/libfdt/src/lib.rs
@@ -349,7 +349,8 @@
self.fdt
}
- fn next_compatible(self, compatible: &CStr) -> Result<Option<Self>> {
+ /// Returns the compatible node of the given name that is next after this node.
+ pub fn next_compatible(self, compatible: &CStr) -> Result<Option<Self>> {
// SAFETY: Accesses (read-only) are constrained to the DT totalsize.
let ret = unsafe {
libfdt_bindgen::fdt_node_offset_by_compatible(
@@ -362,6 +363,11 @@
Ok(fdt_err_or_option(ret)?.map(|offset| Self { fdt: self.fdt, offset }))
}
+ /// Returns the first range of `reg` in this node.
+ pub fn first_reg(&self) -> Result<Reg<u64>> {
+ self.reg()?.ok_or(FdtError::NotFound)?.next().ok_or(FdtError::NotFound)
+ }
+
fn address_cells(&self) -> Result<AddrCells> {
// SAFETY: Accesses are constrained to the DT totalsize (validated by ctor).
unsafe { libfdt_bindgen::fdt_address_cells(self.fdt.as_ptr(), self.offset) }
@@ -534,7 +540,7 @@
Ok(FdtNode { fdt: &*self.fdt, offset: fdt_err(ret)? })
}
- /// Return the compatible node of the given name that is next to this node
+ /// Returns the compatible node of the given name that is next after this node
pub fn next_compatible(self, compatible: &CStr) -> Result<Option<Self>> {
// SAFETY: Accesses (read-only) are constrained to the DT totalsize.
let ret = unsafe {
@@ -804,13 +810,12 @@
}
fn check_full(&self) -> Result<()> {
- let len = self.buffer.len();
// SAFETY: Only performs read accesses within the limits of the slice. If successful, this
// call guarantees to other unsafe calls that the header contains a valid totalsize (w.r.t.
// 'len' i.e. the self.fdt slice) that those C functions can use to perform bounds
// checking. The library doesn't maintain an internal state (such as pointers) between
// calls as it expects the client code to keep track of the objects (DT, nodes, ...).
- let ret = unsafe { libfdt_bindgen::fdt_check_full(self.as_ptr(), len) };
+ let ret = unsafe { libfdt_bindgen::fdt_check_full(self.as_ptr(), self.capacity()) };
fdt_err_expect_zero(ret)
}
diff --git a/libs/microdroid_uids/src/lib.rs b/libs/microdroid_uids/src/lib.rs
index 04dc190..0248c61 100644
--- a/libs/microdroid_uids/src/lib.rs
+++ b/libs/microdroid_uids/src/lib.rs
@@ -29,7 +29,7 @@
// helps avoid confusion.)
/// Group ID shared by all payload users.
-pub const MICRODROID_PAYLOAD_GID: u32 = if cfg!(payload_not_root) { 6000 } else { 0 };
+pub const MICRODROID_PAYLOAD_GID: u32 = if cfg!(multi_tenant) { 6000 } else { 0 };
/// User ID for the initial payload user.
-pub const MICRODROID_PAYLOAD_UID: u32 = if cfg!(payload_not_root) { 6000 } else { 0 };
+pub const MICRODROID_PAYLOAD_UID: u32 = if cfg!(multi_tenant) { 6000 } else { 0 };
diff --git a/libs/service_vm_comm/src/message.rs b/libs/service_vm_comm/src/message.rs
deleted file mode 100644
index 80956cb..0000000
--- a/libs/service_vm_comm/src/message.rs
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright 2023, The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-//! This module contains the requests and responses definitions exchanged
-//! between the host and the service VM.
-
-use alloc::vec::Vec;
-
-use serde::{Deserialize, Serialize};
-
-type MacedPublicKey = Vec<u8>;
-
-/// The main request type to be sent to the service VM.
-#[derive(Clone, Debug, Serialize, Deserialize)]
-pub enum ServiceVmRequest {
- /// A request to be processed by the service VM.
- ///
- /// Each request has a corresponding response item.
- Process(Request),
-
- /// Shuts down the service VM. No response is expected from it.
- Shutdown,
-}
-
-/// Represents a process request to be sent to the service VM.
-///
-/// Each request has a corresponding response item.
-#[derive(Clone, Debug, Serialize, Deserialize)]
-pub enum Request {
- /// Reverse the order of the bytes in the provided byte array.
- /// Currently this is only used for testing.
- Reverse(Vec<u8>),
-
- /// Generates a new ECDSA P-256 key pair that can be attested by the remote
- /// server.
- GenerateEcdsaP256KeyPair,
-
- /// Creates a certificate signing request to be sent to the
- /// provisioning server.
- GenerateCertificateRequest(GenerateCertificateRequestParams),
-}
-
-/// Represents a response to a request sent to the service VM.
-///
-/// Each response corresponds to a specific request.
-#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
-pub enum Response {
- /// Reverse the order of the bytes in the provided byte array.
- Reverse(Vec<u8>),
-
- /// Returns the new ECDSA P-256 key pair.
- GenerateEcdsaP256KeyPair(EcdsaP256KeyPair),
-
- /// Returns a CBOR Certificate Signing Request (Csr) serialized into a byte array.
- GenerateCertificateRequest(Vec<u8>),
-}
-
-/// Represents the params passed to GenerateCertificateRequest
-#[derive(Clone, Debug, Serialize, Deserialize)]
-pub struct GenerateCertificateRequestParams {
- /// Contains the set of keys to certify.
- pub keys_to_sign: Vec<MacedPublicKey>,
-
- /// challenge contains a byte strong from the provisioning server which will be
- /// included in the signed data of the CSR structure.
- /// The supported sizes is between 0 and 64 bytes, inclusive.
- pub challenge: Vec<u8>,
-}
-
-/// Represents an ECDSA P-256 key pair.
-#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
-pub struct EcdsaP256KeyPair {
- /// Contains a CBOR-encoded public key specified in:
- ///
- /// hardware/interfaces/security/rkp/aidl/android/hardware/security/keymint/MacedPublicKey.aidl
- pub maced_public_key: MacedPublicKey,
-
- /// Contains a handle to the private key.
- pub key_blob: Vec<u8>,
-}
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index bac93a4..b494cfa 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -418,6 +418,7 @@
],
properties: [
"rollback_index",
+ "props",
],
}
@@ -447,6 +448,12 @@
soong_config_variables: {
release_avf_enable_llpvm_changes: {
rollback_index: 1,
+ props: [
+ {
+ name: "com.android.virt.cap",
+ value: "secretkeeper_protection",
+ },
+ ],
},
},
}
@@ -487,6 +494,12 @@
soong_config_variables: {
release_avf_enable_llpvm_changes: {
rollback_index: 1,
+ props: [
+ {
+ name: "com.android.virt.cap",
+ value: "secretkeeper_protection",
+ },
+ ],
},
},
}
diff --git a/microdroid/init_debug_policy/Android.bp b/microdroid/init_debug_policy/Android.bp
index ed017e5..2a87ac9 100644
--- a/microdroid/init_debug_policy/Android.bp
+++ b/microdroid/init_debug_policy/Android.bp
@@ -10,7 +10,6 @@
rustlibs: [
"librustutils",
],
- installable: false, // match with microdroid_init_rc.
bootstrap: true,
prefer_rlib: true,
}
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index e9cb0ec..dd0ddbb 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -144,18 +144,9 @@
Owned(format!("MICRODROID_UNKNOWN_RUNTIME_ERROR|{:?}", err))
};
- let death_reason_bytes = death_reason.as_bytes();
- let mut sent_total = 0;
- while sent_total < death_reason_bytes.len() {
+ for chunk in death_reason.as_bytes().chunks(16) {
// TODO(b/220071963): Sometimes, sending more than 16 bytes at once makes MM hang.
- let begin = sent_total;
- let end = std::cmp::min(begin.saturating_add(16), death_reason_bytes.len());
- OpenOptions::new()
- .read(false)
- .write(true)
- .open(FAILURE_SERIAL_DEVICE)?
- .write_all(&death_reason_bytes[begin..end])?;
- sent_total = end;
+ OpenOptions::new().read(false).write(true).open(FAILURE_SERIAL_DEVICE)?.write_all(chunk)?;
}
Ok(())
@@ -738,9 +729,11 @@
let mount_dir = format!("/mnt/extra-apk/{i}");
create_dir(Path::new(&mount_dir)).context("Failed to create mount dir for extra apks")?;
- // don't wait, just detach
+ let mount_for_exec =
+ if cfg!(multi_tenant) { MountForExec::Allowed } else { MountForExec::Disallowed };
+ // These run asynchronously in parallel - we wait later for them to complete.
zipfuse.mount(
- MountForExec::Disallowed,
+ mount_for_exec,
"fscontext=u:object_r:zipfusefs:s0,context=u:object_r:extra_apk_file:s0",
Path::new(&format!("/dev/block/mapper/extra-apk-{i}")),
Path::new(&mount_dir),
diff --git a/pvmfw/Android.bp b/pvmfw/Android.bp
index 523334f..8c21030 100644
--- a/pvmfw/Android.bp
+++ b/pvmfw/Android.bp
@@ -12,6 +12,7 @@
],
rustlibs: [
"libaarch64_paging",
+ "libbssl_avf_nostd",
"libbssl_ffi_nostd",
"libciborium_nostd",
"libciborium_io_nostd",
diff --git a/pvmfw/avb/Android.bp b/pvmfw/avb/Android.bp
index 73d188b..6df1c4d 100644
--- a/pvmfw/avb/Android.bp
+++ b/pvmfw/avb/Android.bp
@@ -42,6 +42,8 @@
":test_image_with_unknown_vm_type_prop",
":test_image_with_multiple_props",
":test_image_with_duplicated_capability",
+ ":test_image_with_rollback_index_5",
+ ":test_image_with_multiple_capabilities",
":unsigned_test_image",
],
prefer_rlib: true,
@@ -194,3 +196,26 @@
private_key: ":pvmfw_sign_key",
salt: "1111",
}
+
+avb_add_hash_footer {
+ name: "test_image_with_rollback_index_5",
+ src: ":unsigned_test_image",
+ partition_name: "boot",
+ private_key: ":pvmfw_sign_key",
+ salt: "1211",
+ rollback_index: 5,
+}
+
+avb_add_hash_footer {
+ name: "test_image_with_multiple_capabilities",
+ src: ":unsigned_test_image",
+ partition_name: "boot",
+ private_key: ":pvmfw_sign_key",
+ salt: "2134",
+ props: [
+ {
+ name: "com.android.virt.cap",
+ value: "remote_attest|secretkeeper_protection",
+ },
+ ],
+}
diff --git a/pvmfw/avb/src/descriptor/mod.rs b/pvmfw/avb/src/descriptor.rs
similarity index 100%
rename from pvmfw/avb/src/descriptor/mod.rs
rename to pvmfw/avb/src/descriptor.rs
diff --git a/pvmfw/avb/src/ops.rs b/pvmfw/avb/src/ops.rs
index 539291b..c7b8b01 100644
--- a/pvmfw/avb/src/ops.rs
+++ b/pvmfw/avb/src/ops.rs
@@ -229,10 +229,14 @@
_rollback_index_location: usize,
out_rollback_index: *mut u64,
) -> AvbIOResult {
- // Rollback protection is not yet implemented, but this method is required by
- // `avb_slot_verify()`.
- // We set `out_rollback_index` to 0 to ensure that the default rollback index (0)
- // is never smaller than it, thus the rollback index check will pass.
+ // This method is used by `avb_slot_verify()` to read the stored_rollback_index at
+ // rollback_index_location.
+
+ // TODO(291213394) : Refine this comment once capability for rollback protection is defined.
+ // pvmfw does not compare stored_rollback_index with rollback_index for Antirollback protection
+ // Hence, we set `out_rollback_index` to 0 to ensure that the
+ // rollback_index (including default: 0) is never smaller than it,
+ // thus the rollback index check will pass.
result_to_io_enum(write(out_rollback_index, 0))
}
@@ -334,4 +338,8 @@
unsafe { slice::from_raw_parts(data.loaded_partitions, data.num_loaded_partitions) };
Ok(loaded_partitions)
}
+
+ pub(crate) fn rollback_indexes(&self) -> &[u64] {
+ &self.as_ref().rollback_indexes
+ }
}
diff --git a/pvmfw/avb/src/verify.rs b/pvmfw/avb/src/verify.rs
index ac945e2..492d387 100644
--- a/pvmfw/avb/src/verify.rs
+++ b/pvmfw/avb/src/verify.rs
@@ -23,6 +23,9 @@
use avb_bindgen::{AvbPartitionData, AvbVBMetaData};
use core::ffi::c_char;
+// We use this for the rollback_index field if AvbSlotVerifyDataWrap has empty rollback_indexes
+const DEFAULT_ROLLBACK_INDEX: u64 = 0;
+
/// Verified data returned when the payload verification succeeds.
#[derive(Debug, PartialEq, Eq)]
pub struct VerifiedBootData<'a> {
@@ -36,6 +39,15 @@
pub public_key: &'a [u8],
/// VM capabilities.
pub capabilities: Vec<Capability>,
+ /// Rollback index of kernel.
+ pub rollback_index: u64,
+}
+
+impl VerifiedBootData<'_> {
+ /// Returns whether the kernel have the given capability
+ pub fn has_capability(&self, cap: Capability) -> bool {
+ self.capabilities.contains(&cap)
+ }
}
/// This enum corresponds to the `DebugLevel` in `VirtualMachineConfig`.
@@ -48,15 +60,18 @@
}
/// VM Capability.
-#[derive(Debug, PartialEq, Eq)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Capability {
/// Remote attestation.
RemoteAttest,
+ /// Secretkeeper protected secrets.
+ SecretkeeperProtection,
}
impl Capability {
const KEY: &[u8] = b"com.android.virt.cap";
const REMOTE_ATTEST: &[u8] = b"remote_attest";
+ const SECRETKEEPER_PROTECTION: &[u8] = b"secretkeeper_protection";
const SEPARATOR: u8 = b'|';
fn get_capabilities(property_value: &[u8]) -> Result<Vec<Self>, PvmfwVerifyError> {
@@ -65,6 +80,7 @@
for v in property_value.split(|b| *b == Self::SEPARATOR) {
let cap = match v {
Self::REMOTE_ATTEST => Self::RemoteAttest,
+ Self::SECRETKEEPER_PROTECTION => Self::SecretkeeperProtection,
_ => return Err(PvmfwVerifyError::UnknownVbmetaProperty),
};
if res.contains(&cap) {
@@ -153,6 +169,10 @@
let kernel_verify_result = ops.verify_partition(PartitionName::Kernel.as_cstr())?;
let vbmeta_images = kernel_verify_result.vbmeta_images()?;
+ // TODO(b/302093437): Use explicit rollback_index_location instead of default
+ // location (first element).
+ let rollback_index =
+ *kernel_verify_result.rollback_indexes().first().unwrap_or(&DEFAULT_ROLLBACK_INDEX);
verify_only_one_vbmeta_exists(vbmeta_images)?;
let vbmeta_image = vbmeta_images[0];
verify_vbmeta_is_from_kernel_partition(&vbmeta_image)?;
@@ -171,6 +191,7 @@
initrd_digest: None,
public_key: trusted_public_key,
capabilities,
+ rollback_index,
});
}
@@ -196,5 +217,6 @@
initrd_digest: Some(*initrd_descriptor.digest),
public_key: trusted_public_key,
capabilities,
+ rollback_index,
})
}
diff --git a/pvmfw/avb/tests/api_test.rs b/pvmfw/avb/tests/api_test.rs
index e7e4dcc..6344433 100644
--- a/pvmfw/avb/tests/api_test.rs
+++ b/pvmfw/avb/tests/api_test.rs
@@ -23,6 +23,7 @@
use utils::*;
const TEST_IMG_WITH_ONE_HASHDESC_PATH: &str = "test_image_with_one_hashdesc.img";
+const TEST_IMG_WITH_ROLLBACK_INDEX_5: &str = "test_image_with_rollback_index_5.img";
const TEST_IMG_WITH_PROP_DESC_PATH: &str = "test_image_with_prop_desc.img";
const TEST_IMG_WITH_SERVICE_VM_PROP_PATH: &str = "test_image_with_service_vm_prop.img";
const TEST_IMG_WITH_UNKNOWN_VM_TYPE_PROP_PATH: &str = "test_image_with_unknown_vm_type_prop.img";
@@ -31,6 +32,7 @@
const TEST_IMG_WITH_NON_INITRD_HASHDESC_PATH: &str = "test_image_with_non_initrd_hashdesc.img";
const TEST_IMG_WITH_INITRD_AND_NON_INITRD_DESC_PATH: &str =
"test_image_with_initrd_and_non_initrd_desc.img";
+const TEST_IMG_WITH_MULTIPLE_CAPABILITIES: &str = "test_image_with_multiple_capabilities.img";
const UNSIGNED_TEST_IMG_PATH: &str = "unsigned_test.img";
const RANDOM_FOOTER_POS: usize = 30;
@@ -60,7 +62,7 @@
let public_key = load_trusted_public_key()?;
let verified_boot_data = verify_payload(
&fs::read(TEST_IMG_WITH_ONE_HASHDESC_PATH)?,
- /*initrd=*/ None,
+ /* initrd= */ None,
&public_key,
)
.map_err(|e| anyhow!("Verification failed. Error: {}", e))?;
@@ -72,6 +74,7 @@
initrd_digest: None,
public_key: &public_key,
capabilities: vec![],
+ rollback_index: 0,
};
assert_eq!(expected_boot_data, verified_boot_data);
@@ -82,7 +85,7 @@
fn payload_with_non_initrd_descriptor_fails_verification_with_no_initrd() -> Result<()> {
assert_payload_verification_fails(
&fs::read(TEST_IMG_WITH_NON_INITRD_HASHDESC_PATH)?,
- /*initrd=*/ None,
+ /* initrd= */ None,
&load_trusted_public_key()?,
PvmfwVerifyError::InvalidDescriptors(avb::IoError::NoSuchPartition),
)
@@ -103,7 +106,7 @@
let public_key = load_trusted_public_key()?;
let verified_boot_data = verify_payload(
&fs::read(TEST_IMG_WITH_SERVICE_VM_PROP_PATH)?,
- /*initrd=*/ None,
+ /* initrd= */ None,
&public_key,
)
.map_err(|e| anyhow!("Verification failed. Error: {}", e))?;
@@ -115,6 +118,7 @@
initrd_digest: None,
public_key: &public_key,
capabilities: vec![Capability::RemoteAttest],
+ rollback_index: 0,
};
assert_eq!(expected_boot_data, verified_boot_data);
@@ -125,7 +129,7 @@
fn payload_with_unknown_vm_type_fails_verification_with_no_initrd() -> Result<()> {
assert_payload_verification_fails(
&fs::read(TEST_IMG_WITH_UNKNOWN_VM_TYPE_PROP_PATH)?,
- /*initrd=*/ None,
+ /* initrd= */ None,
&load_trusted_public_key()?,
PvmfwVerifyError::UnknownVbmetaProperty,
)
@@ -135,7 +139,7 @@
fn payload_with_multiple_props_fails_verification_with_no_initrd() -> Result<()> {
assert_payload_verification_fails(
&fs::read(TEST_IMG_WITH_MULTIPLE_PROPS_PATH)?,
- /*initrd=*/ None,
+ /* initrd= */ None,
&load_trusted_public_key()?,
PvmfwVerifyError::InvalidDescriptors(avb::IoError::Io),
)
@@ -145,7 +149,7 @@
fn payload_with_duplicated_capability_fails_verification_with_no_initrd() -> Result<()> {
assert_payload_verification_fails(
&fs::read(TEST_IMG_WITH_DUPLICATED_CAP_PATH)?,
- /*initrd=*/ None,
+ /* initrd= */ None,
&load_trusted_public_key()?,
avb::SlotVerifyError::InvalidMetadata.into(),
)
@@ -155,7 +159,7 @@
fn payload_with_prop_descriptor_fails_verification_with_no_initrd() -> Result<()> {
assert_payload_verification_fails(
&fs::read(TEST_IMG_WITH_PROP_DESC_PATH)?,
- /*initrd=*/ None,
+ /* initrd= */ None,
&load_trusted_public_key()?,
PvmfwVerifyError::UnknownVbmetaProperty,
)
@@ -165,7 +169,7 @@
fn payload_expecting_initrd_fails_verification_with_no_initrd() -> Result<()> {
assert_payload_verification_fails(
&load_latest_signed_kernel()?,
- /*initrd=*/ None,
+ /* initrd= */ None,
&load_trusted_public_key()?,
avb::SlotVerifyError::InvalidMetadata.into(),
)
@@ -176,7 +180,7 @@
assert_payload_verification_with_initrd_fails(
&load_latest_signed_kernel()?,
&load_latest_initrd_normal()?,
- /*trusted_public_key=*/ &[0u8; 0],
+ /* trusted_public_key= */ &[0u8; 0],
avb::SlotVerifyError::PublicKeyRejected.into(),
)
}
@@ -186,7 +190,7 @@
assert_payload_verification_with_initrd_fails(
&load_latest_signed_kernel()?,
&load_latest_initrd_normal()?,
- /*trusted_public_key=*/ &[0u8; 512],
+ /* trusted_public_key= */ &[0u8; 512],
avb::SlotVerifyError::PublicKeyRejected.into(),
)
}
@@ -205,7 +209,7 @@
fn payload_with_an_invalid_initrd_fails_verification() -> Result<()> {
assert_payload_verification_with_initrd_fails(
&load_latest_signed_kernel()?,
- /*initrd=*/ &fs::read(UNSIGNED_TEST_IMG_PATH)?,
+ /* initrd= */ &fs::read(UNSIGNED_TEST_IMG_PATH)?,
&load_trusted_public_key()?,
avb::SlotVerifyError::Verification.into(),
)
@@ -383,3 +387,41 @@
avb::SlotVerifyError::Verification.into(),
)
}
+
+#[test]
+fn payload_with_rollback_index() -> Result<()> {
+ let public_key = load_trusted_public_key()?;
+ let verified_boot_data = verify_payload(
+ &fs::read(TEST_IMG_WITH_ROLLBACK_INDEX_5)?,
+ /* initrd= */ None,
+ &public_key,
+ )
+ .map_err(|e| anyhow!("Verification failed. Error: {}", e))?;
+
+ let kernel_digest = hash(&[&hex::decode("1211")?, &fs::read(UNSIGNED_TEST_IMG_PATH)?]);
+ let expected_boot_data = VerifiedBootData {
+ debug_level: DebugLevel::None,
+ kernel_digest,
+ initrd_digest: None,
+ public_key: &public_key,
+ capabilities: vec![],
+ rollback_index: 5,
+ };
+ assert_eq!(expected_boot_data, verified_boot_data);
+ Ok(())
+}
+
+#[test]
+fn payload_with_multiple_capabilities() -> Result<()> {
+ let public_key = load_trusted_public_key()?;
+ let verified_boot_data = verify_payload(
+ &fs::read(TEST_IMG_WITH_MULTIPLE_CAPABILITIES)?,
+ /* initrd= */ None,
+ &public_key,
+ )
+ .map_err(|e| anyhow!("Verification failed. Error: {}", e))?;
+
+ assert!(verified_boot_data.has_capability(Capability::RemoteAttest));
+ assert!(verified_boot_data.has_capability(Capability::SecretkeeperProtection));
+ Ok(())
+}
diff --git a/pvmfw/avb/tests/utils.rs b/pvmfw/avb/tests/utils.rs
index 86d2398..70eba5f 100644
--- a/pvmfw/avb/tests/utils.rs
+++ b/pvmfw/avb/tests/utils.rs
@@ -117,6 +117,7 @@
initrd_digest,
public_key: &public_key,
capabilities: vec![],
+ rollback_index: if cfg!(llpvm_changes) { 1 } else { 0 },
};
assert_eq!(expected_boot_data, verified_boot_data);
diff --git a/pvmfw/src/crypto.rs b/pvmfw/src/crypto.rs
index 94714c0..2b3d921 100644
--- a/pvmfw/src/crypto.rs
+++ b/pvmfw/src/crypto.rs
@@ -31,10 +31,8 @@
use bssl_ffi::EVP_AEAD_CTX_seal;
use bssl_ffi::EVP_AEAD_max_overhead;
use bssl_ffi::EVP_aead_aes_256_gcm_randnonce;
-use bssl_ffi::EVP_sha512;
use bssl_ffi::EVP_AEAD;
use bssl_ffi::EVP_AEAD_CTX;
-use bssl_ffi::HKDF;
use vmbase::cstr;
#[derive(Debug)]
@@ -267,36 +265,6 @@
}
}
-pub fn hkdf_sh512<const N: usize>(secret: &[u8], salt: &[u8], info: &[u8]) -> Result<[u8; N]> {
- let mut key = [0; N];
- // SAFETY: The function shouldn't access any Rust variable and the returned value is accepted
- // as a potentially NULL pointer.
- let digest = unsafe { EVP_sha512() };
-
- assert!(!digest.is_null());
- // SAFETY: Only reads from/writes to the provided slices and supports digest was checked not
- // be NULL.
- let result = unsafe {
- HKDF(
- key.as_mut_ptr(),
- key.len(),
- digest,
- secret.as_ptr(),
- secret.len(),
- salt.as_ptr(),
- salt.len(),
- info.as_ptr(),
- info.len(),
- )
- };
-
- if result == 1 {
- Ok(key)
- } else {
- Err(ErrorIterator {})
- }
-}
-
pub fn init() {
// SAFETY: Configures the internal state of the library - may be called multiple times.
unsafe { CRYPTO_library_init() }
diff --git a/pvmfw/src/dice.rs b/pvmfw/src/dice.rs
index 9542429..cc31f34 100644
--- a/pvmfw/src/dice.rs
+++ b/pvmfw/src/dice.rs
@@ -45,6 +45,7 @@
pub code_hash: Hash,
pub auth_hash: Hash,
pub mode: DiceMode,
+ pub security_version: u64,
}
impl PartialInputs {
@@ -52,8 +53,10 @@
let code_hash = to_dice_hash(data)?;
let auth_hash = hash(data.public_key)?;
let mode = to_dice_mode(data.debug_level);
+ // We use rollback_index from vbmeta as the security_version field in dice certificate.
+ let security_version = data.rollback_index;
- Ok(Self { code_hash, auth_hash, mode })
+ Ok(Self { code_hash, auth_hash, mode, security_version })
}
pub fn write_next_bcc(
@@ -63,8 +66,12 @@
next_bcc: &mut [u8],
) -> diced_open_dice::Result<()> {
let mut config_descriptor_buffer = [0; 128];
- let config_values =
- DiceConfigValues { component_name: Some(cstr!("vm_entry")), ..Default::default() };
+ let config_values = DiceConfigValues {
+ component_name: Some(cstr!("vm_entry")),
+ security_version: if cfg!(llpvm_changes) { Some(self.security_version) } else { None },
+ ..Default::default()
+ };
+
let config_descriptor_size =
bcc_format_config_descriptor(&config_values, &mut config_descriptor_buffer)?;
let config = &config_descriptor_buffer[..config_descriptor_size];
diff --git a/pvmfw/src/fdt.rs b/pvmfw/src/fdt.rs
index 61de423..1f87dcc 100644
--- a/pvmfw/src/fdt.rs
+++ b/pvmfw/src/fdt.rs
@@ -443,7 +443,7 @@
fn read_serial_info_from(fdt: &Fdt) -> libfdt::Result<SerialInfo> {
let mut addrs: ArrayVec<[u64; SerialInfo::MAX_SERIALS]> = Default::default();
for node in fdt.compatible_nodes(cstr!("ns16550a"))?.take(SerialInfo::MAX_SERIALS) {
- let reg = node.reg()?.ok_or(FdtError::NotFound)?.next().ok_or(FdtError::NotFound)?;
+ let reg = node.first_reg()?;
addrs.push(reg.addr);
}
Ok(SerialInfo { addrs })
diff --git a/pvmfw/src/instance.rs b/pvmfw/src/instance.rs
index f2b34da..75bc3d3 100644
--- a/pvmfw/src/instance.rs
+++ b/pvmfw/src/instance.rs
@@ -15,12 +15,12 @@
//! Support for reading and writing to the instance.img.
use crate::crypto;
-use crate::crypto::hkdf_sh512;
use crate::crypto::AeadCtx;
use crate::dice::PartialInputs;
use crate::gpt;
use crate::gpt::Partition;
use crate::gpt::Partitions;
+use bssl_avf::{self, hkdf, Digester};
use core::fmt;
use core::mem::size_of;
use diced_open_dice::DiceMode;
@@ -63,6 +63,8 @@
UnsupportedEntrySize(usize),
/// Failed to create VirtIO Block device.
VirtIOBlkCreationFailed(virtio_drivers::Error),
+ /// An error happened during the interaction with BoringSSL.
+ BoringSslFailed(bssl_avf::Error),
}
impl fmt::Display for Error {
@@ -95,10 +97,19 @@
Self::VirtIOBlkCreationFailed(e) => {
write!(f, "Failed to create VirtIO Block device: {e}")
}
+ Self::BoringSslFailed(e) => {
+ write!(f, "An error happened during the interaction with BoringSSL: {e}")
+ }
}
}
}
+impl From<bssl_avf::Error> for Error {
+ fn from(e: bssl_avf::Error) -> Self {
+ Self::BoringSslFailed(e)
+ }
+}
+
pub type Result<T> = core::result::Result<T, Error>;
pub fn get_or_generate_instance_salt(
@@ -111,7 +122,7 @@
let entry = locate_entry(&mut instance_img)?;
trace!("Found pvmfw instance.img entry: {entry:?}");
- let key = hkdf_sh512::<32>(secret, /*salt=*/ &[], b"vm-instance");
+ let key = hkdf::<32>(secret, /* salt= */ &[], b"vm-instance", Digester::sha512())?;
let mut blk = [0; BLK_SIZE];
match entry {
PvmfwEntry::Existing { header_index, payload_size } => {
@@ -124,7 +135,6 @@
let payload = &blk[..payload_size];
let mut entry = [0; size_of::<EntryBody>()];
- let key = key.map_err(Error::FailedOpen)?;
let aead = AeadCtx::new_aes_256_gcm_randnonce(&key).map_err(Error::FailedOpen)?;
let decrypted = aead.open(&mut entry, payload).map_err(Error::FailedOpen)?;
@@ -143,7 +153,6 @@
let salt = rand::random_array().map_err(Error::FailedSaltGeneration)?;
let body = EntryBody::new(dice_inputs, &salt);
- let key = key.map_err(Error::FailedSeal)?;
let aead = AeadCtx::new_aes_256_gcm_randnonce(&key).map_err(Error::FailedSeal)?;
// We currently only support single-blk entries.
let plaintext = body.as_bytes();
diff --git a/pvmfw/src/main.rs b/pvmfw/src/main.rs
index d39d51c..b8cbf1b 100644
--- a/pvmfw/src/main.rs
+++ b/pvmfw/src/main.rs
@@ -112,10 +112,22 @@
info!("Please disregard any previous libavb ERROR about initrd_normal.");
}
- if verified_boot_data.capabilities.contains(&Capability::RemoteAttest) {
+ if verified_boot_data.has_capability(Capability::RemoteAttest) {
info!("Service VM capable of remote attestation detected");
}
+ if verified_boot_data.has_capability(Capability::SecretkeeperProtection) {
+ info!("Guest OS is capable of Secretkeeper protection");
+ // For Secretkeeper based Antirollback protection, rollback_index of the image > 0
+ if verified_boot_data.rollback_index == 0 {
+ error!(
+ "Expected positive rollback_index, found {:?}",
+ verified_boot_data.rollback_index
+ );
+ return Err(RebootReason::InvalidPayload);
+ };
+ }
+
let next_bcc = heap::aligned_boxed_slice(NEXT_BCC_SIZE, GUEST_PAGE_SIZE).ok_or_else(|| {
error!("Failed to allocate the next-stage BCC");
RebootReason::InternalError
diff --git a/rialto/Android.bp b/rialto/Android.bp
index d8e4536..bc08d8c 100644
--- a/rialto/Android.bp
+++ b/rialto/Android.bp
@@ -9,8 +9,13 @@
defaults: ["vmbase_ffi_defaults"],
rustlibs: [
"libaarch64_paging",
+ "libbssl_avf_nostd",
+ "libbssl_ffi_nostd",
"libciborium_io_nostd",
"libciborium_nostd",
+ "libcoset_nostd",
+ "libdiced_open_dice_nostd",
+ "libdiced_sample_inputs_nostd",
"libhyp",
"libfdtpci",
"liblibfdt",
@@ -19,6 +24,7 @@
"libtinyvec_nostd",
"libvirtio_drivers",
"libvmbase",
+ "libzeroize_nostd",
],
}
@@ -103,6 +109,7 @@
"android.system.virtualizationservice-rust",
"libandroid_logger",
"libanyhow",
+ "libciborium",
"liblibc",
"liblog_rust",
"libservice_vm_comm",
diff --git a/rialto/src/error.rs b/rialto/src/error.rs
index 23667ed..d2bdbbe 100644
--- a/rialto/src/error.rs
+++ b/rialto/src/error.rs
@@ -16,9 +16,11 @@
use aarch64_paging::MapError;
use core::{fmt, result};
+use diced_open_dice::DiceError;
use fdtpci::PciError;
use hyp::Error as HypervisorError;
use libfdt::FdtError;
+use service_vm_comm::RequestProcessingError;
use vmbase::{memory::MemoryTrackerError, virtio::pci};
pub type Result<T> = result::Result<T, Error>;
@@ -50,6 +52,10 @@
SerializationFailed(CiboriumSerError),
/// Failed to deserialize.
DeserializationFailed(CiboriumDeError),
+ /// Failed DICE operation.
+ DiceOperationFailed(DiceError),
+ /// Failed to process request.
+ RequestProcessingFailed(RequestProcessingError),
}
impl fmt::Display for Error {
@@ -72,6 +78,8 @@
}
Self::SerializationFailed(e) => write!(f, "Failed to serialize: {e}"),
Self::DeserializationFailed(e) => write!(f, "Failed to deserialize: {e}"),
+ Self::DiceOperationFailed(e) => write!(f, "Failed DICE operation: {e}"),
+ Self::RequestProcessingFailed(e) => write!(f, "Failed to process request: {e}"),
}
}
}
@@ -123,3 +131,15 @@
Self::DeserializationFailed(e)
}
}
+
+impl From<DiceError> for Error {
+ fn from(e: DiceError) -> Self {
+ Self::DiceOperationFailed(e)
+ }
+}
+
+impl From<RequestProcessingError> for Error {
+ fn from(e: RequestProcessingError) -> Self {
+ Self::RequestProcessingFailed(e)
+ }
+}
diff --git a/rialto/src/fdt.rs b/rialto/src/fdt.rs
new file mode 100644
index 0000000..8bb40c3
--- /dev/null
+++ b/rialto/src/fdt.rs
@@ -0,0 +1,26 @@
+// 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.
+
+//! High-level FDT functions.
+
+use core::ops::Range;
+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>> {
+ let node = fdt.node(cstr!("/reserved-memory"))?.ok_or(FdtError::NotFound)?;
+ let node = node.next_compatible(cstr!("google,open-dice"))?.ok_or(FdtError::NotFound)?;
+ node.first_reg()?.try_into()
+}
diff --git a/rialto/src/main.rs b/rialto/src/main.rs
index 4e91574..43215a0 100644
--- a/rialto/src/main.rs
+++ b/rialto/src/main.rs
@@ -20,15 +20,20 @@
mod communication;
mod error;
mod exceptions;
+mod fdt;
mod requests;
extern crate alloc;
use crate::communication::VsockStream;
use crate::error::{Error, Result};
+use crate::fdt::read_dice_range_from;
+use alloc::boxed::Box;
+use bssl_ffi::CRYPTO_library_init;
use ciborium_io::Write;
use core::num::NonZeroUsize;
use core::slice;
+use diced_open_dice::{bcc_handover_parse, DiceArtifacts};
use fdtpci::PciInfo;
use hyp::{get_mem_sharer, get_mmio_guard};
use libfdt::FdtError;
@@ -131,6 +136,38 @@
})?;
}
+ // Initializes the crypto library before any crypto operations and after the heap is
+ // initialized.
+ // SAFETY: It is safe to call this function multiple times and concurrently.
+ unsafe {
+ CRYPTO_library_init();
+ }
+ let bcc_handover: Box<dyn DiceArtifacts> = match vm_type() {
+ VmType::ProtectedVm => {
+ let dice_range = read_dice_range_from(fdt)?;
+ info!("DICE range: {dice_range:#x?}");
+ // SAFETY: This region was written by pvmfw in its writable_data region. The region
+ // has no overlap with the main memory region and is safe to be mapped as read-only
+ // data.
+ let res = unsafe {
+ MEMORY.lock().as_mut().unwrap().alloc_range_outside_main_memory(&dice_range)
+ };
+ res.map_err(|e| {
+ error!("Failed to use DICE range from DT: {dice_range:#x?}");
+ e
+ })?;
+ let dice_start = dice_range.start as *const u8;
+ // SAFETY: There's no memory overlap and the region is mapped as read-only data.
+ let bcc_handover = unsafe { slice::from_raw_parts(dice_start, dice_range.len()) };
+ Box::new(bcc_handover_parse(bcc_handover)?)
+ }
+ // Currently, a sample DICE data is used for non-protected VMs, as these VMs only run
+ // in tests at the moment.
+ // If we intend to run non-protected rialto in production, we should retrieve real
+ // DICE chain data instead.
+ VmType::NonProtectedVm => Box::new(diced_sample_inputs::make_sample_bcc_and_cdis()?),
+ };
+
let pci_info = PciInfo::from_fdt(fdt)?;
debug!("PCI: {pci_info:#x?}");
let mut pci_root = pci::initialize(pci_info, MEMORY.lock().as_mut().unwrap())
@@ -141,7 +178,7 @@
let mut vsock_stream = VsockStream::new(socket_device, host_addr())?;
while let ServiceVmRequest::Process(req) = vsock_stream.read_request()? {
- let response = requests::process_request(req)?;
+ let response = requests::process_request(req, bcc_handover.as_ref())?;
vsock_stream.write_response(&response)?;
vsock_stream.flush()?;
}
diff --git a/rialto/src/requests/mod.rs b/rialto/src/requests.rs
similarity index 97%
rename from rialto/src/requests/mod.rs
rename to rialto/src/requests.rs
index 2ed568c..d9e6f37 100644
--- a/rialto/src/requests/mod.rs
+++ b/rialto/src/requests.rs
@@ -15,6 +15,7 @@
//! This module contains functions for the request processing.
mod api;
+mod pub_key;
mod rkp;
pub use api::process_request;
diff --git a/rialto/src/requests/api.rs b/rialto/src/requests/api.rs
index c4b2d8e..59a7aed 100644
--- a/rialto/src/requests/api.rs
+++ b/rialto/src/requests/api.rs
@@ -17,21 +17,20 @@
use super::rkp;
use crate::error::Result;
use alloc::vec::Vec;
+use diced_open_dice::DiceArtifacts;
use service_vm_comm::{Request, Response};
/// Processes a request and returns the corresponding response.
/// This function serves as the entry point for the request processing
/// module.
-pub fn process_request(request: Request) -> Result<Response> {
+pub fn process_request(request: Request, dice_artifacts: &dyn DiceArtifacts) -> Result<Response> {
let response = match request {
Request::Reverse(v) => Response::Reverse(reverse(v)),
- Request::GenerateEcdsaP256KeyPair => {
- let res = rkp::generate_ecdsa_p256_key_pair()?;
- Response::GenerateEcdsaP256KeyPair(res)
- }
+ Request::GenerateEcdsaP256KeyPair => rkp::generate_ecdsa_p256_key_pair(dice_artifacts)
+ .map_or_else(Response::Err, Response::GenerateEcdsaP256KeyPair),
Request::GenerateCertificateRequest(p) => {
- let res = rkp::generate_certificate_request(p)?;
- Response::GenerateCertificateRequest(res)
+ rkp::generate_certificate_request(p, dice_artifacts)
+ .map_or_else(Response::Err, Response::GenerateCertificateRequest)
}
};
Ok(response)
diff --git a/rialto/src/requests/pub_key.rs b/rialto/src/requests/pub_key.rs
new file mode 100644
index 0000000..110e3d2
--- /dev/null
+++ b/rialto/src/requests/pub_key.rs
@@ -0,0 +1,54 @@
+// 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.
+
+//! Handles the construction of the MACed public key.
+
+use alloc::vec::Vec;
+use bssl_avf::hmac_sha256;
+use core::result;
+use coset::{iana, CborSerializable, CoseKey, CoseMac0, CoseMac0Builder, HeaderBuilder};
+use service_vm_comm::RequestProcessingError;
+
+type Result<T> = result::Result<T, RequestProcessingError>;
+
+/// Verifies the MAC of the given public key.
+pub fn validate_public_key(maced_public_key: &[u8], hmac_key: &[u8]) -> Result<CoseKey> {
+ let cose_mac = CoseMac0::from_slice(maced_public_key)?;
+ cose_mac.verify_tag(&[], |tag, data| verify_tag(tag, data, hmac_key))?;
+ let payload = cose_mac.payload.ok_or(RequestProcessingError::KeyToSignHasEmptyPayload)?;
+ Ok(CoseKey::from_slice(&payload)?)
+}
+
+fn verify_tag(tag: &[u8], data: &[u8], hmac_key: &[u8]) -> Result<()> {
+ let computed_tag = hmac_sha256(hmac_key, data)?;
+ if tag == computed_tag {
+ Ok(())
+ } else {
+ Err(RequestProcessingError::InvalidMac)
+ }
+}
+
+/// Returns the MACed public key.
+pub fn build_maced_public_key(public_key: CoseKey, hmac_key: &[u8]) -> Result<Vec<u8>> {
+ const ALGO: iana::Algorithm = iana::Algorithm::HMAC_256_256;
+
+ let external_aad = &[];
+ let protected = HeaderBuilder::new().algorithm(ALGO).build();
+ let cose_mac = CoseMac0Builder::new()
+ .protected(protected)
+ .payload(public_key.to_vec()?)
+ .try_create_tag(external_aad, |data| hmac_sha256(hmac_key, data).map(|v| v.to_vec()))?
+ .build();
+ Ok(cose_mac.to_vec()?)
+}
diff --git a/rialto/src/requests/rkp.rs b/rialto/src/requests/rkp.rs
index 5977bfb..f96b85d 100644
--- a/rialto/src/requests/rkp.rs
+++ b/rialto/src/requests/rkp.rs
@@ -13,21 +13,153 @@
// limitations under the License.
//! This module contains functions related to the attestation of the
-//! service VM via the RKP (Remote Key Provisionning) server.
+//! service VM via the RKP (Remote Key Provisioning) server.
-use crate::error::Result;
+use super::pub_key::{build_maced_public_key, validate_public_key};
+use alloc::string::String;
+use alloc::vec;
use alloc::vec::Vec;
-use service_vm_comm::{EcdsaP256KeyPair, GenerateCertificateRequestParams};
+use bssl_avf::EcKey;
+use ciborium::{cbor, value::Value};
+use core::result;
+use coset::{iana, AsCborValue, CoseSign1, CoseSign1Builder, HeaderBuilder};
+use diced_open_dice::{kdf, keypair_from_seed, sign, DiceArtifacts, PrivateKey};
+use log::error;
+use service_vm_comm::{EcdsaP256KeyPair, GenerateCertificateRequestParams, RequestProcessingError};
+use zeroize::Zeroizing;
-pub(super) fn generate_ecdsa_p256_key_pair() -> Result<EcdsaP256KeyPair> {
- // TODO(b/299055662): Generate the key pair.
- let key_pair = EcdsaP256KeyPair { maced_public_key: Vec::new(), key_blob: Vec::new() };
+type Result<T> = result::Result<T, RequestProcessingError>;
+
+/// The salt is generated randomly with:
+/// hexdump -vn32 -e'16/1 "0x%02X, " 1 "\n"' /dev/urandom
+const HMAC_KEY_SALT: [u8; 32] = [
+ 0x82, 0x80, 0xFA, 0xD3, 0xA8, 0x0A, 0x9A, 0x4B, 0xF7, 0xA5, 0x7D, 0x7B, 0xE9, 0xC3, 0xAB, 0x13,
+ 0x89, 0xDC, 0x7B, 0x46, 0xEE, 0x71, 0x22, 0xB4, 0x5F, 0x4C, 0x3F, 0xE2, 0x40, 0x04, 0x3B, 0x6C,
+];
+const HMAC_KEY_INFO: &[u8] = b"rialto hmac key";
+const HMAC_KEY_LENGTH: usize = 32;
+
+pub(super) fn generate_ecdsa_p256_key_pair(
+ dice_artifacts: &dyn DiceArtifacts,
+) -> Result<EcdsaP256KeyPair> {
+ let hmac_key = derive_hmac_key(dice_artifacts)?;
+ let ec_key = EcKey::new_p256()?;
+ let maced_public_key = build_maced_public_key(ec_key.cose_public_key()?, hmac_key.as_ref())?;
+
+ // TODO(b/279425980): Encrypt the private key in a key blob.
+ // Remove the printing of the private key.
+ log::debug!("Private key: {:?}", ec_key.private_key()?.as_slice());
+
+ let key_pair = EcdsaP256KeyPair { maced_public_key, key_blob: Vec::new() };
Ok(key_pair)
}
+const CSR_PAYLOAD_SCHEMA_V3: u8 = 3;
+const AUTH_REQ_SCHEMA_V1: u8 = 1;
+// TODO(b/300624493): Add a new certificate type for AVF CSR.
+const CERTIFICATE_TYPE: &str = "keymint";
+
+/// Builds the CSR described in:
+///
+/// hardware/interfaces/security/rkp/aidl/android/hardware/security/keymint/
+/// generateCertificateRequestV2.cddl
pub(super) fn generate_certificate_request(
- _params: GenerateCertificateRequestParams,
+ params: GenerateCertificateRequestParams,
+ dice_artifacts: &dyn DiceArtifacts,
) -> Result<Vec<u8>> {
- // TODO(b/299256925): Generate the certificate request
- Ok(Vec::new())
+ let hmac_key = derive_hmac_key(dice_artifacts)?;
+ let mut public_keys: Vec<Value> = Vec::new();
+ for key_to_sign in params.keys_to_sign {
+ let public_key = validate_public_key(&key_to_sign, hmac_key.as_ref())?;
+ public_keys.push(public_key.to_cbor_value()?);
+ }
+ // Builds `CsrPayload`.
+ let csr_payload = cbor!([
+ Value::Integer(CSR_PAYLOAD_SCHEMA_V3.into()),
+ Value::Text(String::from(CERTIFICATE_TYPE)),
+ // TODO(b/299256925): Add device info in CBOR format here.
+ Value::Array(public_keys),
+ ])?;
+ let csr_payload = cbor_to_vec(&csr_payload)?;
+
+ // Builds `SignedData`.
+ let signed_data_payload =
+ cbor!([Value::Bytes(params.challenge.to_vec()), Value::Bytes(csr_payload)])?;
+ let signed_data = build_signed_data(&signed_data_payload, dice_artifacts)?.to_cbor_value()?;
+
+ // Builds `AuthenticatedRequest<CsrPayload>`.
+ // Currently `UdsCerts` is left empty because it is only needed for Samsung devices.
+ // Check http://b/301574013#comment3 for more information.
+ let uds_certs = Value::Map(Vec::new());
+ let dice_cert_chain = dice_artifacts
+ .bcc()
+ .map(read_to_value)
+ .ok_or(RequestProcessingError::MissingDiceChain)??;
+ let auth_req = cbor!([
+ Value::Integer(AUTH_REQ_SCHEMA_V1.into()),
+ uds_certs,
+ dice_cert_chain,
+ signed_data,
+ ])?;
+ cbor_to_vec(&auth_req)
+}
+
+fn derive_hmac_key(dice_artifacts: &dyn DiceArtifacts) -> Result<Zeroizing<[u8; HMAC_KEY_LENGTH]>> {
+ let mut key = Zeroizing::new([0u8; HMAC_KEY_LENGTH]);
+ kdf(dice_artifacts.cdi_seal(), &HMAC_KEY_SALT, HMAC_KEY_INFO, key.as_mut()).map_err(|e| {
+ error!("Failed to compute the HMAC key: {e}");
+ RequestProcessingError::InternalError
+ })?;
+ Ok(key)
+}
+
+/// Builds the `SignedData` for the given payload.
+fn build_signed_data(payload: &Value, dice_artifacts: &dyn DiceArtifacts) -> Result<CoseSign1> {
+ let cdi_leaf_priv = derive_cdi_leaf_priv(dice_artifacts).map_err(|e| {
+ error!("Failed to derive the CDI_Leaf_Priv: {e}");
+ RequestProcessingError::InternalError
+ })?;
+ let signing_algorithm = iana::Algorithm::EdDSA;
+ let protected = HeaderBuilder::new().algorithm(signing_algorithm).build();
+ let signed_data = CoseSign1Builder::new()
+ .protected(protected)
+ .payload(cbor_to_vec(payload)?)
+ .try_create_signature(&[], |message| sign_message(message, &cdi_leaf_priv))?
+ .build();
+ Ok(signed_data)
+}
+
+fn derive_cdi_leaf_priv(dice_artifacts: &dyn DiceArtifacts) -> diced_open_dice::Result<PrivateKey> {
+ let (_, private_key) = keypair_from_seed(dice_artifacts.cdi_attest())?;
+ Ok(private_key)
+}
+
+fn sign_message(message: &[u8], private_key: &PrivateKey) -> Result<Vec<u8>> {
+ Ok(sign(message, private_key.as_array())
+ .map_err(|e| {
+ error!("Failed to sign the CSR: {e}");
+ RequestProcessingError::InternalError
+ })?
+ .to_vec())
+}
+
+fn cbor_to_vec(v: &Value) -> Result<Vec<u8>> {
+ let mut data = Vec::new();
+ ciborium::into_writer(v, &mut data).map_err(coset::CoseError::from)?;
+ Ok(data)
+}
+
+/// Read a CBOR `Value` from a byte slice, failing if any extra data remains
+/// after the `Value` has been read.
+fn read_to_value(mut data: &[u8]) -> Result<Value> {
+ let value = ciborium::from_reader(&mut data).map_err(|e| {
+ error!("Failed to deserialize the data into CBOR value: {e}");
+ RequestProcessingError::CborValueError
+ })?;
+ if data.is_empty() {
+ Ok(value)
+ } else {
+ error!("CBOR input has extra data.");
+ Err(RequestProcessingError::CborValueError)
+ }
}
diff --git a/rialto/tests/test.rs b/rialto/tests/test.rs
index b8ced95..6a6dcf4 100644
--- a/rialto/tests/test.rs
+++ b/rialto/tests/test.rs
@@ -22,12 +22,14 @@
binder::{ParcelFileDescriptor, ProcessState},
};
use anyhow::{bail, Context, Result};
+use ciborium::value::Value;
use log::info;
use service_vm_comm::{
EcdsaP256KeyPair, GenerateCertificateRequestParams, Request, Response, VmType,
};
use service_vm_manager::ServiceVm;
use std::fs::File;
+use std::io;
use std::panic;
use std::path::PathBuf;
use vmclient::VmInstance;
@@ -49,8 +51,8 @@
let mut vm = start_service_vm(vm_type)?;
check_processing_reverse_request(&mut vm)?;
- check_processing_generating_key_pair_request(&mut vm)?;
- check_processing_generating_certificate_request(&mut vm)?;
+ let maced_public_key = check_processing_generating_key_pair_request(&mut vm)?;
+ check_processing_generating_certificate_request(&mut vm, maced_public_key)?;
Ok(())
}
@@ -68,31 +70,57 @@
Ok(())
}
-fn check_processing_generating_key_pair_request(vm: &mut ServiceVm) -> Result<()> {
+fn check_processing_generating_key_pair_request(vm: &mut ServiceVm) -> Result<Vec<u8>> {
let request = Request::GenerateEcdsaP256KeyPair;
let response = vm.process_request(request)?;
info!("Received response: {response:?}.");
match response {
- Response::GenerateEcdsaP256KeyPair(EcdsaP256KeyPair { .. }) => Ok(()),
- _ => bail!("Incorrect response type"),
+ Response::GenerateEcdsaP256KeyPair(EcdsaP256KeyPair { maced_public_key, .. }) => {
+ assert_array_has_nonzero(&maced_public_key[..]);
+ Ok(maced_public_key)
+ }
+ _ => bail!("Incorrect response type: {response:?}"),
}
}
-fn check_processing_generating_certificate_request(vm: &mut ServiceVm) -> Result<()> {
- let params = GenerateCertificateRequestParams { keys_to_sign: vec![], challenge: vec![] };
+fn assert_array_has_nonzero(v: &[u8]) {
+ assert!(v.iter().any(|&x| x != 0))
+}
+
+fn check_processing_generating_certificate_request(
+ vm: &mut ServiceVm,
+ maced_public_key: Vec<u8>,
+) -> Result<()> {
+ let params = GenerateCertificateRequestParams {
+ keys_to_sign: vec![maced_public_key],
+ challenge: vec![],
+ };
let request = Request::GenerateCertificateRequest(params);
let response = vm.process_request(request)?;
info!("Received response: {response:?}.");
match response {
- Response::GenerateCertificateRequest(_) => Ok(()),
- _ => bail!("Incorrect response type"),
+ Response::GenerateCertificateRequest(csr) => check_csr(csr),
+ _ => bail!("Incorrect response type: {response:?}"),
}
}
+/// TODO(b/300625792): Check the CSR with libhwtrust once the CSR is complete.
+fn check_csr(csr: Vec<u8>) -> Result<()> {
+ let mut reader = io::Cursor::new(csr);
+ let csr: Value = ciborium::from_reader(&mut reader)?;
+ match csr {
+ Value::Array(arr) => {
+ assert_eq!(4, arr.len());
+ }
+ _ => bail!("Incorrect CSR format: {csr:?}"),
+ }
+ Ok(())
+}
+
fn start_service_vm(vm_type: VmType) -> Result<ServiceVm> {
android_logger::init_once(
android_logger::Config::default().with_tag("rialto").with_min_level(log::Level::Debug),
diff --git a/libs/service_vm_comm/Android.bp b/service_vm/comm/Android.bp
similarity index 76%
rename from libs/service_vm_comm/Android.bp
rename to service_vm/comm/Android.bp
index cdb8fc3..3a18052 100644
--- a/libs/service_vm_comm/Android.bp
+++ b/service_vm/comm/Android.bp
@@ -21,6 +21,10 @@
"libcore.rust_sysroot",
],
rustlibs: [
+ "libbssl_avf_error_nostd",
+ "libciborium_nostd",
+ "libcoset_nostd",
+ "liblog_rust_nostd",
"libserde_nostd",
],
}
@@ -29,6 +33,10 @@
name: "libservice_vm_comm",
defaults: ["libservice_vm_comm_defaults"],
rustlibs: [
+ "libbssl_avf_error",
+ "libciborium",
+ "libcoset",
+ "liblog_rust",
"libserde",
],
features: [
diff --git a/libs/service_vm_comm/src/lib.rs b/service_vm/comm/src/lib.rs
similarity index 93%
rename from libs/service_vm_comm/src/lib.rs
rename to service_vm/comm/src/lib.rs
index ca97ca1..d8f7bd7 100644
--- a/libs/service_vm_comm/src/lib.rs
+++ b/service_vm/comm/src/lib.rs
@@ -23,6 +23,7 @@
mod vsock;
pub use message::{
- EcdsaP256KeyPair, GenerateCertificateRequestParams, Request, Response, ServiceVmRequest,
+ EcdsaP256KeyPair, GenerateCertificateRequestParams, Request, RequestProcessingError, Response,
+ ServiceVmRequest,
};
pub use vsock::VmType;
diff --git a/service_vm/comm/src/message.rs b/service_vm/comm/src/message.rs
new file mode 100644
index 0000000..f8d7420
--- /dev/null
+++ b/service_vm/comm/src/message.rs
@@ -0,0 +1,158 @@
+// Copyright 2023, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! This module contains the requests and responses definitions exchanged
+//! between the host and the service VM.
+
+use alloc::vec::Vec;
+use core::fmt;
+use log::error;
+use serde::{Deserialize, Serialize};
+
+type MacedPublicKey = Vec<u8>;
+
+/// The main request type to be sent to the service VM.
+#[derive(Clone, Debug, Serialize, Deserialize)]
+pub enum ServiceVmRequest {
+ /// A request to be processed by the service VM.
+ ///
+ /// Each request has a corresponding response item.
+ Process(Request),
+
+ /// Shuts down the service VM. No response is expected from it.
+ Shutdown,
+}
+
+/// Represents a process request to be sent to the service VM.
+///
+/// Each request has a corresponding response item.
+#[derive(Clone, Debug, Serialize, Deserialize)]
+pub enum Request {
+ /// Reverse the order of the bytes in the provided byte array.
+ /// Currently this is only used for testing.
+ Reverse(Vec<u8>),
+
+ /// Generates a new ECDSA P-256 key pair that can be attested by the remote
+ /// server.
+ GenerateEcdsaP256KeyPair,
+
+ /// Creates a certificate signing request to be sent to the
+ /// provisioning server.
+ GenerateCertificateRequest(GenerateCertificateRequestParams),
+}
+
+/// Represents a response to a request sent to the service VM.
+///
+/// Each response corresponds to a specific request.
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub enum Response {
+ /// Reverse the order of the bytes in the provided byte array.
+ Reverse(Vec<u8>),
+
+ /// Returns the new ECDSA P-256 key pair.
+ GenerateEcdsaP256KeyPair(EcdsaP256KeyPair),
+
+ /// Returns a CBOR Certificate Signing Request (Csr) serialized into a byte array.
+ GenerateCertificateRequest(Vec<u8>),
+
+ /// Encountered an error during the request processing.
+ Err(RequestProcessingError),
+}
+
+/// Errors related to request processing.
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub enum RequestProcessingError {
+ /// An error happened during the interaction with BoringSSL.
+ BoringSslError(bssl_avf_error::Error),
+
+ /// An error happened during the interaction with coset.
+ CosetError,
+
+ /// An unexpected internal error occurred.
+ InternalError,
+
+ /// Any key to sign lacks a valid MAC. Maps to `STATUS_INVALID_MAC`.
+ InvalidMac,
+
+ /// No payload found in a key to sign.
+ KeyToSignHasEmptyPayload,
+
+ /// An error happened when serializing to/from a `Value`.
+ CborValueError,
+
+ /// The DICE chain of the service VM is missing.
+ MissingDiceChain,
+}
+
+impl fmt::Display for RequestProcessingError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Self::BoringSslError(e) => {
+ write!(f, "An error happened during the interaction with BoringSSL: {e}")
+ }
+ Self::CosetError => write!(f, "Encountered an error with coset"),
+ Self::InternalError => write!(f, "An unexpected internal error occurred"),
+ Self::InvalidMac => write!(f, "A key to sign lacks a valid MAC."),
+ Self::KeyToSignHasEmptyPayload => write!(f, "No payload found in a key to sign."),
+ Self::CborValueError => {
+ write!(f, "An error happened when serializing to/from a CBOR Value.")
+ }
+ Self::MissingDiceChain => write!(f, "The DICE chain of the service VM is missing"),
+ }
+ }
+}
+
+impl From<bssl_avf_error::Error> for RequestProcessingError {
+ fn from(e: bssl_avf_error::Error) -> Self {
+ Self::BoringSslError(e)
+ }
+}
+
+impl From<coset::CoseError> for RequestProcessingError {
+ fn from(e: coset::CoseError) -> Self {
+ error!("Coset error: {e}");
+ Self::CosetError
+ }
+}
+
+impl From<ciborium::value::Error> for RequestProcessingError {
+ fn from(e: ciborium::value::Error) -> Self {
+ error!("CborValueError: {e}");
+ Self::CborValueError
+ }
+}
+
+/// Represents the params passed to GenerateCertificateRequest
+#[derive(Clone, Debug, Serialize, Deserialize)]
+pub struct GenerateCertificateRequestParams {
+ /// Contains the set of keys to certify.
+ pub keys_to_sign: Vec<MacedPublicKey>,
+
+ /// challenge contains a byte strong from the provisioning server which will be
+ /// included in the signed data of the CSR structure.
+ /// The supported sizes is between 0 and 64 bytes, inclusive.
+ pub challenge: Vec<u8>,
+}
+
+/// Represents an ECDSA P-256 key pair.
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub struct EcdsaP256KeyPair {
+ /// Contains a CBOR-encoded public key specified in:
+ ///
+ /// hardware/interfaces/security/rkp/aidl/android/hardware/security/keymint/MacedPublicKey.aidl
+ pub maced_public_key: MacedPublicKey,
+
+ /// Contains a handle to the private key.
+ pub key_blob: Vec<u8>,
+}
diff --git a/libs/service_vm_comm/src/vsock.rs b/service_vm/comm/src/vsock.rs
similarity index 100%
rename from libs/service_vm_comm/src/vsock.rs
rename to service_vm/comm/src/vsock.rs
diff --git a/service_vm_manager/Android.bp b/service_vm/manager/Android.bp
similarity index 100%
rename from service_vm_manager/Android.bp
rename to service_vm/manager/Android.bp
diff --git a/service_vm_manager/src/lib.rs b/service_vm/manager/src/lib.rs
similarity index 100%
rename from service_vm_manager/src/lib.rs
rename to service_vm/manager/src/lib.rs
diff --git a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
index e2795ec..b7a34ae 100644
--- a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
+++ b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
@@ -555,4 +555,8 @@
protected boolean isFeatureEnabled(String featureName) throws Exception {
return getVirtualMachineManager().isFeatureEnabled(featureName);
}
+
+ protected void assumeProtectedVM() {
+ assumeTrue("Skip on non-protected VM", mProtectedVm);
+ }
}
diff --git a/tests/pvmfw/Android.bp b/tests/pvmfw/Android.bp
index 61667f3..474c62e 100644
--- a/tests/pvmfw/Android.bp
+++ b/tests/pvmfw/Android.bp
@@ -9,6 +9,20 @@
}
genrule {
+ name: "test_avf_debug_policy_with_ramdump",
+ defaults: ["test_avf_dts_to_dtb"],
+ srcs: ["assets/avf_debug_policy_with_ramdump.dts"],
+ out: ["avf_debug_policy_with_ramdump.dtbo"],
+}
+
+genrule {
+ name: "test_avf_debug_policy_without_ramdump",
+ defaults: ["test_avf_dts_to_dtb"],
+ srcs: ["assets/avf_debug_policy_without_ramdump.dts"],
+ out: ["avf_debug_policy_without_ramdump.dtbo"],
+}
+
+genrule {
name: "test_avf_debug_policy_with_adb",
defaults: ["test_avf_dts_to_dtb"],
srcs: ["assets/avf_debug_policy_with_adb.dts"],
@@ -39,6 +53,8 @@
data: [
":MicrodroidTestApp",
":pvmfw_test",
+ ":test_avf_debug_policy_with_ramdump",
+ ":test_avf_debug_policy_without_ramdump",
":test_avf_debug_policy_with_adb",
":test_avf_debug_policy_without_adb",
"assets/bcc.dat",
diff --git a/tests/pvmfw/assets/avf_debug_policy_with_ramdump.dts b/tests/pvmfw/assets/avf_debug_policy_with_ramdump.dts
new file mode 100644
index 0000000..139d28e
--- /dev/null
+++ b/tests/pvmfw/assets/avf_debug_policy_with_ramdump.dts
@@ -0,0 +1,22 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+ fragment@avf {
+ target-path = "/";
+
+ __overlay__ {
+ avf {
+ guest {
+ common {
+ ramdump = <1>;
+ };
+ microdroid {
+ adb = <1>; // adb is required to check VM's bootargs.
+ };
+ };
+ };
+ };
+ };
+};
+
diff --git a/tests/pvmfw/assets/avf_debug_policy_without_ramdump.dts b/tests/pvmfw/assets/avf_debug_policy_without_ramdump.dts
new file mode 100644
index 0000000..8e0e44c
--- /dev/null
+++ b/tests/pvmfw/assets/avf_debug_policy_without_ramdump.dts
@@ -0,0 +1,22 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+ fragment@avf {
+ target-path = "/";
+
+ __overlay__ {
+ avf {
+ guest {
+ common {
+ ramdump = <0>;
+ };
+ microdroid {
+ adb = <1>; // adb is required to check VM's bootargs.
+ };
+ };
+ };
+ };
+ };
+};
+
diff --git a/tests/pvmfw/java/com/android/pvmfw/test/DebugPolicyHostTests.java b/tests/pvmfw/java/com/android/pvmfw/test/DebugPolicyHostTests.java
index 410e6e0..7d0faa4 100644
--- a/tests/pvmfw/java/com/android/pvmfw/test/DebugPolicyHostTests.java
+++ b/tests/pvmfw/java/com/android/pvmfw/test/DebugPolicyHostTests.java
@@ -192,6 +192,43 @@
launchProtectedVmAndWaitForBootCompleted(MICRODROID_DEBUG_FULL);
}
+ @Test
+ public void testRamdumpInDebugPolicy_withDebugLevelNone_hasRamdumpArgs() throws Exception {
+ prepareCustomDebugPolicy("avf_debug_policy_with_ramdump.dtbo");
+ mMicrodroidDevice = launchProtectedVmAndWaitForBootCompleted(MICRODROID_DEBUG_NONE);
+
+ assertThat(readMicrodroidFileAsString(MICRODROID_CMDLINE_PATH)).contains("crashkernel=");
+ assertThat(readMicrodroidFileAsString(MICRODROID_DT_BOOTARGS_PATH))
+ .contains("crashkernel=");
+ assertThat(readMicrodroidFileAsHexString(MICRODROID_DT_RAMDUMP_PATH))
+ .isEqualTo(HEX_STRING_ONE);
+ }
+
+ @Test
+ public void testNoRamdumpInDebugPolicy_withDebugLevelNone_noRamdumpArgs() throws Exception {
+ prepareCustomDebugPolicy("avf_debug_policy_without_ramdump.dtbo");
+ mMicrodroidDevice = launchProtectedVmAndWaitForBootCompleted(MICRODROID_DEBUG_NONE);
+
+ assertThat(readMicrodroidFileAsString(MICRODROID_CMDLINE_PATH))
+ .doesNotContain("crashkernel=");
+ assertThat(readMicrodroidFileAsString(MICRODROID_DT_BOOTARGS_PATH))
+ .doesNotContain("crashkernel=");
+ assertThat(readMicrodroidFileAsHexString(MICRODROID_DT_RAMDUMP_PATH))
+ .isEqualTo(HEX_STRING_ZERO);
+ }
+
+ @Test
+ public void testNoRamdumpInDebugPolicy_withDebugLevelFull_hasRamdumpArgs() throws Exception {
+ prepareCustomDebugPolicy("avf_debug_policy_without_ramdump.dtbo");
+ mMicrodroidDevice = launchProtectedVmAndWaitForBootCompleted(MICRODROID_DEBUG_FULL);
+
+ assertThat(readMicrodroidFileAsString(MICRODROID_CMDLINE_PATH)).contains("crashkernel=");
+ assertThat(readMicrodroidFileAsString(MICRODROID_DT_BOOTARGS_PATH))
+ .contains("crashkernel=");
+ assertThat(readMicrodroidFileAsHexString(MICRODROID_DT_RAMDUMP_PATH))
+ .isEqualTo(HEX_STRING_ZERO);
+ }
+
private boolean isDebugPolicyEnabled(@NonNull String dtPropertyPath)
throws DeviceNotAvailableException {
CommandRunner runner = new CommandRunner(mAndroidDevice);
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
index 4b9f803..40c5cae 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -946,12 +946,18 @@
@Test
@CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-7"})
public void changingNonDebuggableVmDebuggableInvalidatesVmIdentity() throws Exception {
+ // Debuggability changes initrd which is verified by pvmfw.
+ // Therefore, skip this on non-protected VM.
+ assumeProtectedVM();
changeDebugLevel(DEBUG_LEVEL_NONE, DEBUG_LEVEL_FULL);
}
@Test
@CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-7"})
public void changingDebuggableVmNonDebuggableInvalidatesVmIdentity() throws Exception {
+ // Debuggability changes initrd which is verified by pvmfw.
+ // Therefore, skip this on non-protected VM.
+ assumeProtectedVM();
changeDebugLevel(DEBUG_LEVEL_FULL, DEBUG_LEVEL_NONE);
}
@@ -1544,7 +1550,7 @@
@CddTest(requirements = {"9.17/C-1-1"})
public void payloadIsNotRoot() throws Exception {
assumeSupportedDevice();
- assumeFeatureEnabled(VirtualMachineManager.FEATURE_PAYLOAD_NOT_ROOT);
+ assumeFeatureEnabled(VirtualMachineManager.FEATURE_MULTI_TENANT);
VirtualMachineConfig config =
newVmConfigBuilder()
diff --git a/virtualizationmanager/Android.bp b/virtualizationmanager/Android.bp
index c660414..12d8724 100644
--- a/virtualizationmanager/Android.bp
+++ b/virtualizationmanager/Android.bp
@@ -82,6 +82,8 @@
"libtempfile",
],
data: [
+ ":test_avf_debug_policy_with_ramdump",
+ ":test_avf_debug_policy_without_ramdump",
":test_avf_debug_policy_with_adb",
":test_avf_debug_policy_without_adb",
],
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index 790cdb5..684aa64 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -18,7 +18,7 @@
use crate::atom::{
write_vm_booted_stats, write_vm_creation_stats};
use crate::composite::make_composite_image;
-use crate::crosvm::{CrosvmConfig, DiskFile, PayloadState, VmContext, VmInstance, VmState};
+use crate::crosvm::{CrosvmConfig, DiskFile, PayloadState, VfioDevice, VmContext, VmInstance, VmState};
use crate::debug_config::DebugConfig;
use crate::payload::{add_microdroid_payload_images, add_microdroid_system_images, add_microdroid_vendor_image};
use crate::selinux::{getfilecon, SeContext};
@@ -34,7 +34,7 @@
IVirtualMachine::{BnVirtualMachine, IVirtualMachine},
IVirtualMachineCallback::IVirtualMachineCallback,
IVirtualizationService::IVirtualizationService,
- IVirtualizationService::FEATURE_PAYLOAD_NON_ROOT,
+ IVirtualizationService::FEATURE_MULTI_TENANT,
IVirtualizationService::FEATURE_VENDOR_MODULES,
IVirtualizationService::FEATURE_DICE_CHANGES,
MemoryTrimLevel::MemoryTrimLevel,
@@ -276,7 +276,7 @@
// TODO(b/298012279): make this scalable.
match feature {
FEATURE_DICE_CHANGES => Ok(cfg!(dice_changes)),
- FEATURE_PAYLOAD_NON_ROOT => Ok(cfg!(payload_not_root)),
+ FEATURE_MULTI_TENANT => Ok(cfg!(multi_tenant)),
FEATURE_VENDOR_MODULES => Ok(cfg!(vendor_modules)),
_ => {
warn!("unknown feature {feature}");
@@ -458,7 +458,7 @@
}
};
- if !config.devices.is_empty() {
+ let vfio_devices = if !config.devices.is_empty() {
let mut set = HashSet::new();
for device in config.devices.iter() {
let path = canonicalize(device)
@@ -469,8 +469,17 @@
.or_binder_exception(ExceptionCode::ILLEGAL_ARGUMENT);
}
}
- GLOBAL_SERVICE.bindDevicesToVfioDriver(&config.devices)?;
- }
+ GLOBAL_SERVICE
+ .bindDevicesToVfioDriver(&config.devices)?
+ .into_iter()
+ .map(|x| VfioDevice {
+ sysfs_path: PathBuf::from(&x.sysfsPath),
+ dtbo_node: x.dtboNode,
+ })
+ .collect::<Vec<_>>()
+ } else {
+ vec![]
+ };
// Actually start the VM.
let crosvm_config = CrosvmConfig {
@@ -495,7 +504,7 @@
platform_version: parse_platform_version_req(&config.platformVersion)?,
detect_hangup: is_app_config,
gdb_port,
- vfio_devices: config.devices.iter().map(PathBuf::from).collect(),
+ vfio_devices,
};
let instance = Arc::new(
VmInstance::new(
diff --git a/virtualizationmanager/src/crosvm.rs b/virtualizationmanager/src/crosvm.rs
index 77dd76f..b053d99 100644
--- a/virtualizationmanager/src/crosvm.rs
+++ b/virtualizationmanager/src/crosvm.rs
@@ -115,7 +115,7 @@
pub platform_version: VersionReq,
pub detect_hangup: bool,
pub gdb_port: Option<NonZeroU16>,
- pub vfio_devices: Vec<PathBuf>,
+ pub vfio_devices: Vec<VfioDevice>,
}
/// A disk image to pass to crosvm for a VM.
@@ -125,6 +125,12 @@
pub writable: bool,
}
+#[derive(Clone, Debug)]
+pub struct VfioDevice {
+ pub sysfs_path: PathBuf,
+ pub dtbo_node: String,
+}
+
/// The lifecycle state which the payload in the VM has reported itself to be in.
///
/// Note that the order of enum variants is significant; only forward transitions are allowed by
@@ -144,7 +150,7 @@
/// The VM has not yet tried to start.
NotStarted {
///The configuration needed to start the VM, if it has not yet been started.
- config: CrosvmConfig,
+ config: Box<CrosvmConfig>,
},
/// The VM has been started.
Running {
@@ -171,7 +177,8 @@
pub struct VmMetric {
/// Recorded timestamp when the VM is started.
pub start_timestamp: Option<SystemTime>,
- /// Update most recent guest_time periodically from /proc/[crosvm pid]/stat while VM is running.
+ /// Update most recent guest_time periodically from /proc/[crosvm pid]/stat while VM is
+ /// running.
pub cpu_guest_time: Option<i64>,
/// Update maximum RSS values periodically from /proc/[crosvm pid]/smaps while VM is running.
pub rss: Option<Rss>,
@@ -184,6 +191,7 @@
fn start(&mut self, instance: Arc<VmInstance>) -> Result<(), Error> {
let state = mem::replace(self, VmState::Failed);
if let VmState::NotStarted { config } = state {
+ let config = *config;
let detect_hangup = config.detect_hangup;
let (failure_pipe_read, failure_pipe_write) = create_pipe()?;
let vfio_devices = config.vfio_devices.clone();
@@ -303,7 +311,7 @@
.flatten()
.map_or_else(|| format!("{}", requester_uid), |u| u.name);
let instance = VmInstance {
- vm_state: Mutex::new(VmState::NotStarted { config }),
+ vm_state: Mutex::new(VmState::NotStarted { config: Box::new(config) }),
vm_context,
cid,
crosvm_control_socket_path: temporary_directory.join("crosvm.sock"),
@@ -342,7 +350,7 @@
&self,
child: Arc<SharedChild>,
mut failure_pipe_read: File,
- vfio_devices: Vec<PathBuf>,
+ vfio_devices: Vec<VfioDevice>,
) {
let result = child.wait();
match &result {
@@ -641,10 +649,10 @@
}
fn death_reason(result: &Result<ExitStatus, io::Error>, mut failure_reason: &str) -> DeathReason {
- if let Some(position) = failure_reason.find('|') {
+ if let Some((reason, info)) = failure_reason.split_once('|') {
// Separator indicates extra context information is present after the failure name.
- error!("Failure info: {}", &failure_reason[(position + 1)..]);
- failure_reason = &failure_reason[..position];
+ error!("Failure info: {info}");
+ failure_reason = reason;
}
if let Ok(status) = result {
match failure_reason {
@@ -694,9 +702,9 @@
const SYSFS_PLATFORM_DEVICES_PATH: &str = "/sys/devices/platform/";
const VFIO_PLATFORM_DRIVER_PATH: &str = "/sys/bus/platform/drivers/vfio-platform";
-fn vfio_argument_for_platform_device(path: &Path) -> Result<String, Error> {
+fn vfio_argument_for_platform_device(device: &VfioDevice) -> Result<String, Error> {
// Check platform device exists
- let path = path.canonicalize()?;
+ let path = device.sysfs_path.canonicalize()?;
if !path.starts_with(SYSFS_PLATFORM_DEVICES_PATH) {
bail!("{path:?} is not a platform device");
}
@@ -708,7 +716,7 @@
}
if let Some(p) = path.to_str() {
- Ok(format!("--vfio={p},iommu=viommu"))
+ Ok(format!("--vfio={p},iommu=viommu,dt-symbol={0}", device.dtbo_node))
} else {
bail!("invalid path {path:?}");
}
diff --git a/virtualizationmanager/src/debug_config.rs b/virtualizationmanager/src/debug_config.rs
index 9b13475..5d22f59 100644
--- a/virtualizationmanager/src/debug_config.rs
+++ b/virtualizationmanager/src/debug_config.rs
@@ -239,6 +239,38 @@
}
#[test]
+ fn test_read_avf_debug_policy_with_ramdump() -> Result<()> {
+ let debug_config = DebugConfig::from_custom_debug_overlay_policy(
+ DebugLevel::FULL,
+ "avf_debug_policy_with_ramdump.dtbo".as_ref(),
+ )
+ .unwrap();
+
+ assert_eq!(DebugLevel::FULL, debug_config.debug_level);
+ assert!(!debug_config.debug_policy_log);
+ assert!(debug_config.debug_policy_ramdump);
+ assert!(debug_config.debug_policy_adb);
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_read_avf_debug_policy_without_ramdump() -> Result<()> {
+ let debug_config = DebugConfig::from_custom_debug_overlay_policy(
+ DebugLevel::FULL,
+ "avf_debug_policy_without_ramdump.dtbo".as_ref(),
+ )
+ .unwrap();
+
+ assert_eq!(DebugLevel::FULL, debug_config.debug_level);
+ assert!(!debug_config.debug_policy_log);
+ assert!(!debug_config.debug_policy_ramdump);
+ assert!(debug_config.debug_policy_adb);
+
+ Ok(())
+ }
+
+ #[test]
fn test_read_avf_debug_policy_with_adb() -> Result<()> {
let debug_config = DebugConfig::from_custom_debug_overlay_policy(
DebugLevel::FULL,
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
index 9255e1c..d6a1299 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
@@ -23,7 +23,7 @@
interface IVirtualizationService {
const String FEATURE_DICE_CHANGES = "com.android.kvm.DICE_CHANGES";
- const String FEATURE_PAYLOAD_NON_ROOT = "com.android.kvm.PAYLOAD_NON_ROOT";
+ const String FEATURE_MULTI_TENANT = "com.android.kvm.MULTI_TENANT";
const String FEATURE_VENDOR_MODULES = "com.android.kvm.VENDOR_MODULES";
/**
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
index 62d66c0..f3a7617 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
@@ -23,6 +23,10 @@
import android.system.virtualizationservice_internal.IGlobalVmContext;
interface IVirtualizationServiceInternal {
+ parcelable BoundDevice {
+ String sysfsPath;
+ String dtboNode;
+ }
/**
* Removes the memlock rlimit of the calling process.
*
@@ -68,6 +72,7 @@
* Bind given devices to vfio driver.
*
* @param devices paths of sysfs nodes of devices to assign.
+ * @return a list of pairs (sysfs path, DTBO node label) for devices.
*/
- void bindDevicesToVfioDriver(in String[] devices);
+ BoundDevice[] bindDevicesToVfioDriver(in String[] devices);
}
diff --git a/virtualizationservice/assignable_devices.xsd b/virtualizationservice/assignable_devices.xsd
index 842542e..8f43019 100644
--- a/virtualizationservice/assignable_devices.xsd
+++ b/virtualizationservice/assignable_devices.xsd
@@ -25,6 +25,7 @@
</xs:element>
<xs:complexType name="device">
<xs:attribute name="kind" type="xs:string"/>
+ <xs:attribute name="dtbo_node" type="xs:string"/>
<xs:attribute name="sysfs_path" type="xs:string"/>
</xs:complexType>
</xs:schema>
diff --git a/virtualizationservice/schema/current.txt b/virtualizationservice/schema/current.txt
index ed0763a..ef99294 100644
--- a/virtualizationservice/schema/current.txt
+++ b/virtualizationservice/schema/current.txt
@@ -3,8 +3,10 @@
public class Device {
ctor public Device();
+ method public String getDtbo_node();
method public String getKind();
method public String getSysfs_path();
+ method public void setDtbo_node(String);
method public void setKind(String);
method public void setSysfs_path(String);
}
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 645a82b..ed5c513 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -28,6 +28,7 @@
AtomVmCreationRequested::AtomVmCreationRequested,
AtomVmExited::AtomVmExited,
IGlobalVmContext::{BnGlobalVmContext, IGlobalVmContext},
+ IVirtualizationServiceInternal::BoundDevice::BoundDevice,
IVirtualizationServiceInternal::IVirtualizationServiceInternal,
IVfioHandler::{BpVfioHandler, IVfioHandler},
};
@@ -179,44 +180,14 @@
fn getAssignableDevices(&self) -> binder::Result<Vec<AssignableDevice>> {
check_use_custom_virtual_machine()?;
- let mut ret = Vec::new();
- let xml_path = Path::new("/vendor/etc/avf/assignable_devices.xml");
- if !xml_path.exists() {
- return Ok(ret);
- }
-
- let xml = fs::read(xml_path)
- .context("Failed to read assignable_devices.xml")
- .with_log()
- .or_service_specific_exception(-1)?;
-
- let xml = String::from_utf8(xml)
- .context("assignable_devices.xml is not a valid UTF-8 file")
- .with_log()
- .or_service_specific_exception(-1)?;
-
- let devices: Devices = serde_xml_rs::from_str(&xml)
- .context("can't parse assignable_devices.xml")
- .with_log()
- .or_service_specific_exception(-1)?;
-
- let mut device_set = HashSet::new();
-
- for device in devices.device.into_iter() {
- if device_set.contains(&device.sysfs_path) {
- warn!("duplicated assignable device {device:?}; ignoring...")
- } else if Path::new(&device.sysfs_path).exists() {
- device_set.insert(device.sysfs_path.clone());
- ret.push(AssignableDevice { kind: device.kind, node: device.sysfs_path });
- } else {
- warn!("assignable device {device:?} doesn't exist; ignoring...");
- }
- }
-
- Ok(ret)
+ Ok(get_assignable_devices()?
+ .device
+ .into_iter()
+ .map(|x| AssignableDevice { node: x.sysfs_path, kind: x.kind })
+ .collect::<Vec<_>>())
}
- fn bindDevicesToVfioDriver(&self, devices: &[String]) -> binder::Result<()> {
+ fn bindDevicesToVfioDriver(&self, devices: &[String]) -> binder::Result<Vec<BoundDevice>> {
check_use_custom_virtual_machine()?;
let vfio_service: Strong<dyn IVfioHandler> =
@@ -233,7 +204,17 @@
vfio_service.writeVmDtbo(&ParcelFileDescriptor::new(dtbo))?;
}
- Ok(())
+ Ok(get_assignable_devices()?
+ .device
+ .into_iter()
+ .filter_map(|x| {
+ if devices.contains(&x.sysfs_path) {
+ Some(BoundDevice { sysfsPath: x.sysfs_path, dtboNode: x.dtbo_node })
+ } else {
+ None
+ }
+ })
+ .collect::<Vec<_>>())
}
}
@@ -241,14 +222,54 @@
#[derive(Debug, Deserialize)]
struct Device {
kind: String,
+ dtbo_node: String,
sysfs_path: String,
}
-#[derive(Debug, Deserialize)]
+#[derive(Debug, Default, Deserialize)]
struct Devices {
device: Vec<Device>,
}
+fn get_assignable_devices() -> binder::Result<Devices> {
+ let xml_path = Path::new("/vendor/etc/avf/assignable_devices.xml");
+ if !xml_path.exists() {
+ return Ok(Devices { ..Default::default() });
+ }
+
+ let xml = fs::read(xml_path)
+ .context("Failed to read assignable_devices.xml")
+ .with_log()
+ .or_service_specific_exception(-1)?;
+
+ let xml = String::from_utf8(xml)
+ .context("assignable_devices.xml is not a valid UTF-8 file")
+ .with_log()
+ .or_service_specific_exception(-1)?;
+
+ let mut devices: Devices = serde_xml_rs::from_str(&xml)
+ .context("can't parse assignable_devices.xml")
+ .with_log()
+ .or_service_specific_exception(-1)?;
+
+ let mut device_set = HashSet::new();
+ devices.device.retain(move |device| {
+ if device_set.contains(&device.sysfs_path) {
+ warn!("duplicated assignable device {device:?}; ignoring...");
+ return false;
+ }
+
+ if !Path::new(&device.sysfs_path).exists() {
+ warn!("assignable device {device:?} doesn't exist; ignoring...");
+ return false;
+ }
+
+ device_set.insert(device.sysfs_path.clone());
+ true
+ });
+ Ok(devices)
+}
+
#[derive(Debug, Default)]
struct GlobalVmInstance {
/// The unique CID assigned to the VM for vsock communication.
diff --git a/virtualizationservice/src/remote_provisioning.rs b/virtualizationservice/src/remote_provisioning.rs
index 06f8ad4..a9a07a5 100644
--- a/virtualizationservice/src/remote_provisioning.rs
+++ b/virtualizationservice/src/remote_provisioning.rs
@@ -19,7 +19,7 @@
DeviceInfo::DeviceInfo,
IRemotelyProvisionedComponent::{
BnRemotelyProvisionedComponent, IRemotelyProvisionedComponent, STATUS_FAILED,
- STATUS_REMOVED,
+ STATUS_INVALID_MAC, STATUS_REMOVED,
},
MacedPublicKey::MacedPublicKey,
ProtectedData::ProtectedData,
@@ -28,6 +28,7 @@
use anyhow::Context;
use avflog::LogResult;
use binder::{BinderFeatures, Interface, IntoBinderResult, Result as BinderResult, Status, Strong};
+use service_vm_comm::{RequestProcessingError, Response};
/// Constructs a binder object that implements `IRemotelyProvisionedComponent`.
pub(crate) fn new_binder() -> Strong<dyn IRemotelyProvisionedComponent> {
@@ -65,12 +66,18 @@
))
.with_log();
}
- let key_pair = rkpvm::generate_ecdsa_p256_key_pair()
+ let res = rkpvm::generate_ecdsa_p256_key_pair()
.context("Failed to generate ECDSA P-256 key pair")
.with_log()
.or_service_specific_exception(STATUS_FAILED)?;
- macedPublicKey.macedKey = key_pair.maced_public_key;
- Ok(key_pair.key_blob)
+ match res {
+ Response::GenerateEcdsaP256KeyPair(key_pair) => {
+ macedPublicKey.macedKey = key_pair.maced_public_key;
+ Ok(key_pair.key_blob)
+ }
+ _ => Err(to_service_specific_error(res)),
+ }
+ .with_log()
}
fn generateCertificateRequest(
@@ -94,10 +101,42 @@
keysToSign: &[MacedPublicKey],
challenge: &[u8],
) -> BinderResult<Vec<u8>> {
- // TODO(b/299259624): Validate the MAC of the keys to certify.
- rkpvm::generate_certificate_request(keysToSign, challenge)
+ const MAX_CHALLENGE_SIZE: usize = 64;
+ if challenge.len() > MAX_CHALLENGE_SIZE {
+ let message = format!(
+ "Challenge is too big. Actual: {:?}. Maximum: {:?}.",
+ challenge.len(),
+ MAX_CHALLENGE_SIZE
+ );
+ return Err(Status::new_service_specific_error_str(STATUS_FAILED, Some(message)))
+ .with_log();
+ }
+ let res = rkpvm::generate_certificate_request(keysToSign, challenge)
.context("Failed to generate certificate request")
.with_log()
- .or_service_specific_exception(STATUS_FAILED)
+ .or_service_specific_exception(STATUS_FAILED)?;
+ match res {
+ Response::GenerateCertificateRequest(res) => Ok(res),
+ _ => Err(to_service_specific_error(res)),
+ }
+ .with_log()
+ }
+}
+
+fn to_service_specific_error(response: Response) -> Status {
+ match response {
+ Response::Err(e) => match e {
+ RequestProcessingError::InvalidMac => {
+ Status::new_service_specific_error_str(STATUS_INVALID_MAC, Some(format!("{e}")))
+ }
+ _ => Status::new_service_specific_error_str(
+ STATUS_FAILED,
+ Some(format!("Failed to process request: {e}.")),
+ ),
+ },
+ other => Status::new_service_specific_error_str(
+ STATUS_FAILED,
+ Some(format!("Incorrect response type: {other:?}")),
+ ),
}
}
diff --git a/virtualizationservice/src/rkpvm.rs b/virtualizationservice/src/rkpvm.rs
index 80953b5..d6e87eb 100644
--- a/virtualizationservice/src/rkpvm.rs
+++ b/virtualizationservice/src/rkpvm.rs
@@ -18,7 +18,7 @@
use android_hardware_security_rkp::aidl::android::hardware::security::keymint::MacedPublicKey::MacedPublicKey;
use anyhow::{bail, Context, Result};
-use service_vm_comm::{EcdsaP256KeyPair, GenerateCertificateRequestParams, Request, Response};
+use service_vm_comm::{GenerateCertificateRequestParams, Request, Response};
use service_vm_manager::ServiceVm;
pub(crate) fn request_certificate(csr: &[u8]) -> Result<Vec<u8>> {
@@ -33,19 +33,16 @@
}
}
-pub(crate) fn generate_ecdsa_p256_key_pair() -> Result<EcdsaP256KeyPair> {
+pub(crate) fn generate_ecdsa_p256_key_pair() -> Result<Response> {
let mut vm = ServiceVm::start()?;
let request = Request::GenerateEcdsaP256KeyPair;
- match vm.process_request(request).context("Failed to process request")? {
- Response::GenerateEcdsaP256KeyPair(key_pair) => Ok(key_pair),
- _ => bail!("Incorrect response type"),
- }
+ vm.process_request(request).context("Failed to process request")
}
pub(crate) fn generate_certificate_request(
keys_to_sign: &[MacedPublicKey],
challenge: &[u8],
-) -> Result<Vec<u8>> {
+) -> Result<Response> {
let params = GenerateCertificateRequestParams {
keys_to_sign: keys_to_sign.iter().map(|v| v.macedKey.to_vec()).collect(),
challenge: challenge.to_vec(),
@@ -53,8 +50,5 @@
let request = Request::GenerateCertificateRequest(params);
let mut vm = ServiceVm::start()?;
- match vm.process_request(request).context("Failed to process request")? {
- Response::GenerateCertificateRequest(csr) => Ok(csr),
- _ => bail!("Incorrect response type"),
- }
+ vm.process_request(request).context("Failed to process request")
}
diff --git a/vmbase/src/layout/mod.rs b/vmbase/src/layout.rs
similarity index 100%
rename from vmbase/src/layout/mod.rs
rename to vmbase/src/layout.rs
diff --git a/vmbase/src/memory/mod.rs b/vmbase/src/memory.rs
similarity index 100%
rename from vmbase/src/memory/mod.rs
rename to vmbase/src/memory.rs
diff --git a/vmbase/src/memory/shared.rs b/vmbase/src/memory/shared.rs
index dfa29e4..6c8a844 100644
--- a/vmbase/src/memory/shared.rs
+++ b/vmbase/src/memory/shared.rs
@@ -135,7 +135,26 @@
/// Allocate the address range for a const slice; returns None if failed.
pub fn alloc_range(&mut self, range: &MemoryRange) -> Result<MemoryRange> {
let region = MemoryRegion { range: range.clone(), mem_type: MemoryType::ReadOnly };
- self.check(®ion)?;
+ self.check_allocatable(®ion)?;
+ self.page_table.map_rodata(&get_va_range(range)).map_err(|e| {
+ error!("Error during range allocation: {e}");
+ MemoryTrackerError::FailedToMap
+ })?;
+ self.add(region)
+ }
+
+ /// Allocates the address range for a const slice.
+ ///
+ /// # Safety
+ ///
+ /// Callers of this method need to ensure that the `range` is valid for mapping as read-only
+ /// data.
+ pub unsafe fn alloc_range_outside_main_memory(
+ &mut self,
+ range: &MemoryRange,
+ ) -> Result<MemoryRange> {
+ let region = MemoryRegion { range: range.clone(), mem_type: MemoryType::ReadOnly };
+ self.check_no_overlap(®ion)?;
self.page_table.map_rodata(&get_va_range(range)).map_err(|e| {
error!("Error during range allocation: {e}");
MemoryTrackerError::FailedToMap
@@ -146,7 +165,7 @@
/// Allocate the address range for a mutable slice; returns None if failed.
pub fn alloc_range_mut(&mut self, range: &MemoryRange) -> Result<MemoryRange> {
let region = MemoryRegion { range: range.clone(), mem_type: MemoryType::ReadWrite };
- self.check(®ion)?;
+ self.check_allocatable(®ion)?;
self.page_table.map_data_dbm(&get_va_range(range)).map_err(|e| {
error!("Error during mutable range allocation: {e}");
MemoryTrackerError::FailedToMap
@@ -196,13 +215,20 @@
Ok(())
}
- /// Checks that the given region is within the range of the `MemoryTracker` and doesn't overlap
- /// with any other previously allocated regions, and that the regions ArrayVec has capacity to
- /// add it.
- fn check(&self, region: &MemoryRegion) -> Result<()> {
+ /// Checks that the memory region meets the following criteria:
+ /// - It is within the range of the `MemoryTracker`.
+ /// - It does not overlap with any previously allocated regions.
+ /// - The `regions` ArrayVec has sufficient capacity to add it.
+ fn check_allocatable(&self, region: &MemoryRegion) -> Result<()> {
if !region.range.is_within(&self.total) {
return Err(MemoryTrackerError::OutOfRange);
}
+ self.check_no_overlap(region)
+ }
+
+ /// Checks that the given region doesn't overlap with any other previously allocated regions,
+ /// and that the regions ArrayVec has capacity to add it.
+ fn check_no_overlap(&self, region: &MemoryRegion) -> Result<()> {
if self.regions.iter().any(|r| region.range.overlaps(&r.range)) {
return Err(MemoryTrackerError::Overlaps);
}
diff --git a/vmbase/src/virtio/mod.rs b/vmbase/src/virtio.rs
similarity index 100%
rename from vmbase/src/virtio/mod.rs
rename to vmbase/src/virtio.rs