diff --git a/crates/Cargo.toml b/crates/Cargo.toml index eba93151..716151c5 100644 --- a/crates/Cargo.toml +++ b/crates/Cargo.toml @@ -49,6 +49,7 @@ optee-utee = { version = "0.10.0", path = "optee-utee" } optee-utee-macros = { version = "0.10.0", path = "optee-utee-macros" } optee-utee-sys = { version = "0.10.0", path = "optee-utee-sys" } +aligned-vec = { version = "0.6.4", default-features = false } bitflags = "2.13.2" num_enum = { version = "0.7.6", default-features = false } uuid = { version = "1.29.0", default-features = false } diff --git a/crates/optee-utee-sys/src/tee_api_types.rs b/crates/optee-utee-sys/src/tee_api_types.rs index 21302a60..c527eefa 100644 --- a/crates/optee-utee-sys/src/tee_api_types.rs +++ b/crates/optee-utee-sys/src/tee_api_types.rs @@ -160,11 +160,17 @@ pub struct TEE_OperationInfo { } #[repr(C)] +#[derive(Clone, Copy)] pub struct TEE_OperationInfoKey { pub keySize: u32, pub requiredKeyUsage: u32, } +/// Fixed header of a C flexible-array object. +/// +/// This type is deliberately not `Copy` or `Clone`: a copy would duplicate +/// only this header, not the `numberOfKeys` key entries that follow it in +/// the same allocation. #[repr(C)] pub struct TEE_OperationInfoMultiple { pub algorithm: u32, @@ -175,7 +181,11 @@ pub struct TEE_OperationInfoMultiple { pub handleState: u32, pub operationState: u32, pub numberOfKeys: u32, - pub keyInformation: *mut TEE_OperationInfoKey, + /// C flexible array member: the fixed part ends here and + /// `numberOfKeys` entries of [`TEE_OperationInfoKey`] follow the + /// struct in the same allocation. A zero-length array gives the + /// field the right offset without adding to `size_of`. + pub keyInformation: [TEE_OperationInfoKey; 0], } // Time & Date API diff --git a/crates/optee-utee-systest/build.rs b/crates/optee-utee-systest/build.rs index 4ad31e56..97d9bf68 100644 --- a/crates/optee-utee-systest/build.rs +++ b/crates/optee-utee-systest/build.rs @@ -91,7 +91,7 @@ fn generate_cfg(ta_include_path: PathBuf) -> ctest::TestGenerator { let s = s.ident(); let field = field.ident(); (s == "ta_head" && field == "entry") - || (s == "TEE_OperationInfoMultiple" && field == "keyInformation") // va_list + || (s == "TEE_OperationInfoMultiple" && field == "keyInformation") // flexible array member || (s == "TEE_Attribute" && field == "content") // anonymous union fields }) .skip_fn(|s| { diff --git a/crates/optee-utee/Cargo.toml b/crates/optee-utee/Cargo.toml index 28d28d2e..2db8fc9f 100644 --- a/crates/optee-utee/Cargo.toml +++ b/crates/optee-utee/Cargo.toml @@ -27,6 +27,7 @@ edition.workspace = true [dependencies] optee-utee-sys.workspace = true optee-utee-macros.workspace = true +aligned-vec.workspace = true bitflags.workspace = true uuid.workspace = true hex.workspace = true diff --git a/crates/optee-utee/src/crypto_op.rs b/crates/optee-utee/src/crypto_op.rs index 2eb90f37..c1c25866 100644 --- a/crates/optee-utee/src/crypto_op.rs +++ b/crates/optee-utee/src/crypto_op.rs @@ -15,12 +15,13 @@ // specific language governing permissions and limitations // under the License. +use aligned_vec::{AVec, ConstAlign}; use alloc::{boxed::Box, vec::Vec}; use core::{mem, ptr}; use optee_utee_sys as raw; -use crate::{Attribute, GenericObject, Result, TransientObject, tee_check}; +use crate::{Attribute, ErrorKind, GenericObject, Result, TransientObject, tee_check}; /// Specify one of the available cryptographic operations. #[repr(u32)] @@ -115,49 +116,102 @@ pub enum OperationConstant { KeyDerivation = 8, } -/// Represent the information about a crypto information which uses multiple keys. +const HEADER_SIZE: usize = mem::size_of::(); +const KEY_SIZE: usize = mem::size_of::(); +const HEADER_ALIGN: usize = mem::align_of::(); + +/// The characteristics of a cryptographic operation that may use multiple keys. +/// +/// This type owns its C-layout buffer. +/// +/// # Migration +/// +/// - `info_multiple(&mut [u8])` keeps its signature and now returns an owned +/// value. +/// - `from_raw(ptr, size)` is now [`from_buffer`](Self::from_buffer); convert +/// the C pointer to a byte slice first. +/// - `raw()` now returns a header reference. +/// - `size()` can be derived from `raw().numberOfKeys` and the C layout sizes. pub struct OperationInfoMultiple { - raw: *mut raw::TEE_OperationInfoMultiple, - size: usize, + storage: AVec>, } impl OperationInfoMultiple { - /// Return the `OperationInfoMultiple` struct based on the raw struct `TEE_OperationInfo`. + /// Copy the operation information out of an already filled buffer. /// - /// The raw structure contains following fields: + /// The first bytes are interpreted as a `TEE_OperationInfoMultiple` + /// header and the bytes after it as `numberOfKeys` key entries. The buffer + /// must be at least `size_of::()` bytes and + /// aligned to `align_of::()` (currently 32 and + /// 4 bytes). /// - /// 1) `algorithm`: One of the algorithm of [AlgorithmId](AlgorithmId). - /// 2) `mode`: One of the mode of [OperationMode](OperationMode). - /// 3) `maxKeySize`: The maximum key sizes of different algorithms as defined in - /// [TransientObjectType](../object/enum.TransientObjectType.html). - /// 4) `operationClass`: One of the constants from [OperationConstant](OperationConstant). - /// 5) `digestLength`: For a [Mac](Mac), [AE](AE), or [Digest](Digest), describes the number of bytes in the digest or tag. - /// 6) `handleState`: A bit vector describing the current state of the operation. Contains one or more of the - /// [HandleFlag](../object/struct.HandleFlag.html). - /// 7) `operationState`: Every operation has two states which are defined as - /// [OperationStates](OperationStates). - /// 8) `numberOfKeys`: This is set to the number of keys required by this operation. May be 0 for an operation which requires no keys. - /// 9) `keyInformation`: This array contains numberOfKeys entries, each of which defines the details for one key used by the operation, - /// in the order they are defined. - /// If the buffer is larger than required to support `numberOfKeys` entries, the additional space is not initialized or modified. - /// For each element: - /// 9.1) `keySize`: If a key is programmed in the operation, the actual size of this key, otherwise 0. - /// 9.2) `requiredKeyUsage`: A bit vector that describes the necessary bits in the object usage for `set_key` or `set_key_2` to succeed without panicking. - pub fn from_raw(raw: *mut raw::TEE_OperationInfoMultiple, size: usize) -> Self { - Self { raw, size } + /// # Errors + /// + /// 1) `BadParameters`: If the buffer is too small, misaligned, or reports + /// a `numberOfKeys` that does not fit in the buffer. + /// 2) `OutOfMemory`: If the owned buffer cannot be allocated. + pub fn from_buffer(info_buf: &[u8]) -> Result { + check_info_multiple_buffer(info_buf)?; + + // SAFETY: the buffer is aligned and large enough for the header. + let header = unsafe { &*info_buf.as_ptr().cast::() }; + let size = checked_encoded_size(header.numberOfKeys as usize) + .filter(|&size| size <= info_buf.len()) + .ok_or(ErrorKind::BadParameters)?; + + let mut storage = allocate_storage(size)?; + storage.as_mut_slice().copy_from_slice(&info_buf[..size]); + Ok(Self { storage }) } - /// Return the raw struct `TEE_OperationInfoMultiple`. - pub fn raw(&self) -> *mut raw::TEE_OperationInfoMultiple { - self.raw + /// Return the raw header. + /// + /// The C flexible array member is not part of this reference; use + /// [`keys`](Self::keys) to access it. + pub fn raw(&self) -> &raw::TEE_OperationInfoMultiple { + // SAFETY: storage is aligned and contains a valid header. + unsafe { &*self.storage.as_ptr().cast() } } - /// Return the `size` field of the raw structure `TEE_OperationInfoMultiple`. - pub fn size(&self) -> usize { - self.size + /// Return the key information. + pub fn keys(&self) -> &[raw::TEE_OperationInfoKey] { + let number_of_keys = self.raw().numberOfKeys as usize; + + // SAFETY: constructors validate `numberOfKeys` against capacity. + // The pointer is derived from the whole buffer, so its provenance + // covers the trailing keys. + unsafe { + core::slice::from_raw_parts( + self.storage + .as_ptr() + .add(HEADER_SIZE) + .cast::(), + number_of_keys, + ) + } } } +fn checked_encoded_size(number_of_keys: usize) -> Option { + HEADER_SIZE.checked_add(number_of_keys.checked_mul(KEY_SIZE)?) +} + +fn allocate_storage(size: usize) -> Result>> { + let mut storage = AVec::>::new(HEADER_ALIGN); + storage + .try_reserve_exact(size) + .map_err(|_| ErrorKind::OutOfMemory)?; + storage.resize(size, 0); + Ok(storage) +} + +fn check_info_multiple_buffer(info_buf: &[u8]) -> Result<()> { + if info_buf.len() < HEADER_SIZE || !(info_buf.as_ptr() as usize).is_multiple_of(HEADER_ALIGN) { + return Err(ErrorKind::BadParameters.into()); + } + Ok(()) +} + /// An opaque reference that identifies a particular cryptographic operation. pub struct OperationHandle { raw: *mut raw::TEE_OperationHandle, @@ -196,14 +250,18 @@ impl OperationHandle { } fn info_multiple(&self, info_buf: &mut [u8]) -> Result { - let mut tmp_size: usize = 0; + check_info_multiple_buffer(info_buf)?; + + let mut size = info_buf.len(); tee_check(unsafe { - raw::TEE_GetOperationInfoMultiple(self.handle(), info_buf.as_ptr() as _, &mut tmp_size) + raw::TEE_GetOperationInfoMultiple( + self.handle(), + info_buf.as_mut_ptr().cast(), + &mut size, + ) })?; - Ok(OperationInfoMultiple::from_raw( - info_buf.as_ptr() as _, - tmp_size, - )) + + OperationInfoMultiple::from_buffer(info_buf) } fn reset(&mut self) { @@ -413,47 +471,39 @@ impl Digest { self.0.info() } - /// Return the characteristics of a Digest operation with multiple keys. + /// Return the characteristics of an operation which may use multiple keys. /// - /// # Parameters - /// - /// 1) `info_buf`: The buffer is supposed to save multiple keys, and its size should be large enough before passed in. - /// The number of keys about this operation can be calculated as: OperationInfoMultiple::size - - /// size_of([OperationInfoMultiple](OperationInfoMultiple)) / size_of ( raw::TEE_OperationInfoKey)+1. + /// `info_buf` must be at least `size_of::()` + /// bytes and aligned to `align_of::()`. OP-TEE + /// currently reports at most two keys, so a 48-byte buffer is sufficient; + /// the result is copied into an owned value. /// /// # Example /// /// ``` rust,no_run /// # use optee_utee::{Digest, AlgorithmId}; /// # fn main() -> optee_utee::Result<()> { - /// match Digest::allocate(AlgorithmId::Md5) { - /// Ok(operation) => - /// { - /// let mut buffer = [0u8, 12]; - /// match operation.info_multiple(&mut buffer) { - /// Ok(info_multiple) => { - /// // ... - /// Ok(()) - /// } - /// Err(e) => Err(e), - /// } - /// } - /// Err(e) => Err(e), - /// } + /// #[repr(align(4))] + /// struct AlignedBuffer([u8; 48]); + /// + /// let operation = Digest::allocate(AlgorithmId::Md5)?; + /// let mut buffer = AlignedBuffer([0; 48]); + /// let info = operation.info_multiple(&mut buffer.0)?; + /// # let _ = info; + /// # Ok(()) /// # } /// ``` /// - /// # Errors: + /// # Errors /// - /// 1) `ShortBuffer`: If the `info_buf` is not large enough to hold an - /// [OperationInfoMultiple](OperationInfoMultiple) and the corresponding keys. + /// 1) `BadParameters`: If `info_buf` is too small or not 4-byte aligned. + /// 2) `ShortBuffer`: If `info_buf` cannot hold the reported keys. + /// 3) `OutOfMemory`: If the owned copy cannot be allocated. /// - /// # Panics: + /// # Panics /// /// 1) If operation is not a valid opened object. /// 2) If the Implementation detects any other error. - // Here the multiple info total size is not sure - // Passed in array is supposed to provide enough size for this struct pub fn info_multiple(&self, info_buf: &mut [u8]) -> Result { self.0.info_multiple(info_buf) } @@ -1840,8 +1890,11 @@ pub enum ElementId { #[cfg(test)] mod tests { + extern crate std; + use super::*; use crate::{AttributeId, TransientObjectType}; + use optee_utee_sys::{mock_api, mock_utils::SERIAL_TEST_LOCK}; #[test] // Guarantee: every high-level PBKDF2 `AlgorithmId` variant maps 1:1 @@ -1908,4 +1961,141 @@ mod tests { assert_eq!(attr as u32, expected, "ID mismatch for {attr}"); } } + + const TEST_BUFFER_SIZE: usize = HEADER_SIZE + 2 * KEY_SIZE; + + #[repr(align(4))] + struct AlignedBuffer([u8; TEST_BUFFER_SIZE]); + + impl AlignedBuffer { + fn with_keys(number_of_keys: u32) -> Self { + let mut buffer = Self([0; TEST_BUFFER_SIZE]); + let base = buffer.0.as_mut_ptr(); + + // SAFETY: the buffer is 4-byte aligned and large enough for the + // header plus two keys. + unsafe { + let header = base.cast::(); + (*header).numberOfKeys = number_of_keys; + + let keys = base + .add(mem::size_of::()) + .cast::(); + for index in 0..number_of_keys as usize { + (*keys.add(index)).keySize = index as u32 + 1; + } + } + buffer + } + } + + #[test] + // Guarantee: from_buffer exposes exactly `numberOfKeys` entries and the + // buffer-size helper matches the C layout. + fn operation_info_multiple_from_buffer() { + for number_of_keys in 0..=2_u32 { + let storage = AlignedBuffer::with_keys(number_of_keys); + let info = OperationInfoMultiple::from_buffer(&storage.0).unwrap(); + + assert_eq!(info.raw().numberOfKeys, number_of_keys); + assert_eq!(info.keys().len(), number_of_keys as usize); + for (index, key) in info.keys().iter().enumerate() { + assert_eq!(key.keySize, index as u32 + 1); + } + } + } + + #[test] + // Guarantee: a buffer that is short, misaligned, or too small for the + // reported keys is rejected. + fn operation_info_multiple_rejects_bad_input() { + let storage = AlignedBuffer::with_keys(0); + + let Err(short) = OperationInfoMultiple::from_buffer(&storage.0[..HEADER_SIZE - 1]) else { + panic!("a buffer shorter than the header must be rejected"); + }; + assert_eq!(short.kind(), ErrorKind::BadParameters); + + let Err(unaligned) = OperationInfoMultiple::from_buffer(&storage.0[1..]) else { + panic!("an unaligned buffer must be rejected"); + }; + assert_eq!(unaligned.kind(), ErrorKind::BadParameters); + + let storage = AlignedBuffer::with_keys(2); + let Err(too_small) = + OperationInfoMultiple::from_buffer(&storage.0[..HEADER_SIZE + KEY_SIZE]) + else { + panic!("keys beyond the buffer capacity must be rejected"); + }; + assert_eq!(too_small.kind(), ErrorKind::BadParameters); + } + + fn fake_operation() -> OperationHandle { + let handle = core::ptr::NonNull::::dangling().as_ptr(); + OperationHandle::from_raw(Box::into_raw(Box::new(handle))) + } + + fn expect_free_operation() -> impl Sized { + let free = mock_api::TEE_FreeOperation_context(); + free.expect().times(1).returning(|_| {}); + free + } + + unsafe fn write_header(info: *mut raw::TEE_OperationInfoMultiple, number_of_keys: u32) { + unsafe { (*info).numberOfKeys = number_of_keys }; + } + + unsafe fn write_keys(info: *mut raw::TEE_OperationInfoMultiple, number_of_keys: u32) { + let keys = unsafe { + info.cast::() + .add(HEADER_SIZE) + .cast::() + }; + for index in 0..number_of_keys as usize { + unsafe { (*keys.add(index)).keySize = index as u32 + 1 }; + } + } + + #[test] + // Guarantee: info_multiple propagates SHORT_BUFFER without retrying. + fn info_multiple_propagates_short_buffer() { + let _lock = SERIAL_TEST_LOCK.lock().expect("should get the lock"); + let _free = expect_free_operation(); + + let get = mock_api::TEE_GetOperationInfoMultiple_context(); + get.expect() + .times(1) + .return_const(raw::TEE_ERROR_SHORT_BUFFER); + + let operation = fake_operation(); + let mut buffer = AlignedBuffer::with_keys(0); + let Err(error) = operation.info_multiple(&mut buffer.0) else { + panic!("SHORT_BUFFER must be propagated"); + }; + assert_eq!(error.kind(), ErrorKind::ShortBuffer); + } + + #[test] + // Guarantee: info_multiple returns the header and keys reported by OP-TEE. + fn info_multiple_success() { + let _lock = SERIAL_TEST_LOCK.lock().expect("should get the lock"); + let _free = expect_free_operation(); + + let get = mock_api::TEE_GetOperationInfoMultiple_context(); + get.expect().times(1).returning(|_op, info, size| { + unsafe { + write_header(info, 2); + write_keys(info, 2); + *size = HEADER_SIZE + 2 * KEY_SIZE; + } + raw::TEE_SUCCESS + }); + + let operation = fake_operation(); + let mut buffer = AlignedBuffer::with_keys(0); + let info = operation.info_multiple(&mut buffer.0).unwrap(); + + assert_eq!(info.raw().numberOfKeys, 2); + assert_eq!(info.keys()[1].keySize, 2); + } } diff --git a/examples/ta/Cargo.lock b/examples/ta/Cargo.lock index bf968d39..af1abf31 100644 --- a/examples/ta/Cargo.lock +++ b/examples/ta/Cargo.lock @@ -67,6 +67,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "aligned-vec" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] + [[package]] name = "allocator-api2" version = "0.2.21" @@ -636,6 +645,26 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc2d050bdc5c21e0862a89256ed8029ae6c290a93aecefc73084b3002cdebb01" +[[package]] +name = "equator" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" +dependencies = [ + "equator-macro", +] + +[[package]] +name = "equator-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "equivalent" version = "1.0.3" @@ -1032,6 +1061,7 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" name = "optee-utee" version = "0.10.0" dependencies = [ + "aligned-vec", "bitflags", "document-features", "hex",