From e6d1c4aa0da198697ea8014d2a40ea37f97acb02 Mon Sep 17 00:00:00 2001 From: Sangho Lee Date: Thu, 12 Feb 2026 19:54:07 +0000 Subject: [PATCH 1/9] Add OpteeMsgArgsHeader to deal with optional RPC args --- Cargo.lock | 1 + litebox_common_optee/src/lib.rs | 369 +++++++++++++++++++++++++- litebox_runner_lvbs/Cargo.toml | 1 + litebox_runner_lvbs/src/lib.rs | 106 +++++++- litebox_shim_optee/src/msg_handler.rs | 169 +++++++++++- 5 files changed, 610 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 542c2a4ad..bc1479fa3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1005,6 +1005,7 @@ dependencies = [ "once_cell", "spin 0.10.0", "x86_64", + "zerocopy", ] [[package]] diff --git a/litebox_common_optee/src/lib.rs b/litebox_common_optee/src/lib.rs index 8659fe00a..219eb4a9b 100644 --- a/litebox_common_optee/src/lib.rs +++ b/litebox_common_optee/src/lib.rs @@ -9,11 +9,12 @@ extern crate alloc; use alloc::boxed::Box; +use core::mem::size_of; use litebox::platform::RawConstPointer as _; use litebox::utils::TruncateExt; -use litebox_common_linux::{PtRegs, errno::Errno}; +use litebox_common_linux::{errno::Errno, PtRegs}; use modular_bitfield::prelude::*; -use modular_bitfield::specifiers::{B8, B54}; +use modular_bitfield::specifiers::{B54, B8}; use num_enum::TryFromPrimitive; use syscall_nr::{LdelfSyscallNr, TeeSyscallNr}; use zerocopy::{FromBytes, Immutable, IntoBytes}; @@ -403,6 +404,12 @@ pub struct UteeParams { } const TEE_NUM_PARAMS: usize = 4; +/// Number of RPC parameters reported to the Linux driver during `EXCHANGE_CAPABILITIES`. +/// The Linux driver allocates space for an appended RPC `optee_msg_arg` with this many +/// params when `OPTEE_SMC_SEC_CAP_RPC_ARG` is set. Upstream OP-TEE typically uses 1-2; +/// we use 4 to match `TEE_NUM_PARAMS` for flexibility. +pub const NUM_RPC_PARAMS: usize = 4; + #[expect( clippy::identity_op, reason = "the macro auto-generates this, but some issue causes it to still bubble up; this suppresses it the hard way" @@ -1365,6 +1372,27 @@ pub struct OpteeMsgParam { } impl OpteeMsgParam { + /// Deserialize an `OpteeMsgParam` from a byte slice of exactly `size_of::()` bytes. + /// + /// # Safety rationale + /// + /// `OpteeMsgParam` is `#[repr(C)]` and every bit pattern is valid (the attr is a bitfield + /// and the union's `octets` variant accepts any bytes), so `read_unaligned` is safe here. + /// Alignment is not required because we use `read_unaligned`. + pub fn from_bytes(bytes: &[u8]) -> Option { + if bytes.len() < size_of::() { + return None; + } + // SAFETY: OpteeMsgParam is repr(C), 32 bytes, and any bit pattern is valid. + Some(unsafe { core::ptr::read_unaligned(bytes.as_ptr().cast::()) }) + } + + /// Serialize this `OpteeMsgParam` as a byte slice of `size_of::()` bytes. + pub fn as_bytes(&self) -> &[u8; size_of::()] { + // SAFETY: OpteeMsgParam is repr(C) and 32 bytes. Reinterpreting as bytes is safe. + unsafe { &*(&raw const *self).cast::<[u8; size_of::()]>() } + } + pub fn attr_type(&self) -> OpteeMsgAttrType { OpteeMsgAttrType::try_from(self.attr.typ()).unwrap_or(OpteeMsgAttrType::None) } @@ -1418,6 +1446,71 @@ impl OpteeMsgParam { } } +/// Compute the total byte size of an `optee_msg_arg` with `num_params` parameters. +/// Equivalent to the C macro `OPTEE_MSG_GET_ARG_SIZE(num_params)`. +/// +/// `num_params` is the total count of entries in `params[]`, which includes both meta +/// parameters and client parameters. For example, `OpenSession` uses `TEE_NUM_PARAMS + 2` +/// (4 client + 2 meta), `InvokeCommand` uses up to `TEE_NUM_PARAMS` (4 client), and +/// RPC args use the count negotiated during `EXCHANGE_CAPABILITIES`. +/// +/// # Safety invariant +/// +/// Callers must ensure `num_params` has been validated against `OpteeMsgArgs::MAX_PARAMS` +/// before calling this function. An unvalidated `num_params` from normal world memory +/// could produce an oversized result, leading to out-of-bounds access on fixed-size arrays. +/// See CVE-2022-46152 (OP-TEE OOB via unvalidated `num_params`). +pub const fn optee_msg_get_arg_size(num_params: u32) -> usize { + // Defense-in-depth: catch unvalidated num_params during development/testing. + // This cannot be a runtime check in a const fn, but will fire in non-const calls. + debug_assert!( + num_params as usize <= OpteeMsgArgs::MAX_PARAMS, + "optee_msg_get_arg_size: num_params exceeds MAX_PARAMS" + ); + core::mem::size_of::() + + core::mem::size_of::() * num_params as usize +} + +/// Zerocopy-friendly header of `optee_msg_arg`. +/// +/// This struct represents the fixed 32-byte header of the C `struct optee_msg_arg`. +/// Unlike `OpteeMsgArgs`, all fields are plain `u32` so it can derive `FromBytes`/`IntoBytes`. +/// Used as phase 1 of the two-phase read: read this header to learn `num_params`, +/// then compute the total size and read the full blob. +/// +/// A single `optee_msg_arg` on the wire: +/// +/// ```text +/// byte offset +/// 0 cmd (u32) +/// 4 func (u32) +/// 8 session (u32) +/// 12 cancel_id (u32) +/// 16 pad (u32) +/// 20 ret (u32) +/// 24 ret_origin (u32) +/// 28 num_params (u32) N = num_params +/// ---- header: 32 bytes (OpteeMsgArgsHeader) ---- +/// 32 params[0] (32 bytes each, OpteeMsgParam) +/// 64 params[1] +/// ... +/// 32 + N*32 (end) +/// ``` +/// +/// Total size = `size_of::() + N * size_of::()` +#[derive(Clone, Copy, Debug, FromBytes, IntoBytes, Immutable)] +#[repr(C)] +pub struct OpteeMsgArgsHeader { + pub cmd: u32, + pub func: u32, + pub session: u32, + pub cancel_id: u32, + pub pad: u32, + pub ret: u32, + pub ret_origin: u32, + pub num_params: u32, +} + /// `optee_msg_arg` from `optee_os/core/include/optee_msg.h` /// OP-TEE message argument structure that the normal world (or VTL0) OP-TEE driver and OP-TEE OS use to /// exchange messages. @@ -1439,15 +1532,21 @@ pub struct OpteeMsgArgs { pub ret: TeeResult, /// Origin of the return value pub ret_origin: TeeOrigin, - /// Number of parameters contained in `params` + /// Number of parameters contained in `params`. + /// + /// For main args: includes both meta parameters (e.g. TA UUID, client identity for + /// `OpenSession`) and client parameters. Typical values: 0 (close/cancel), + /// `TEE_NUM_PARAMS` (invoke), `TEE_NUM_PARAMS + 2` (open_session). + /// For RPC args: the count negotiated during `EXCHANGE_CAPABILITIES`. pub num_params: u32, - /// Parameters to be passed to the secure world. If `cmd == OpenSession`, the first two params contain - /// a TA UUID and they are not delivered to the TA. - /// Note that, originally, the length of this array is variable. We fix it to `TEE_NUM_PARAMS + 2` to - /// simplify the implementation (our OP-TEE Shim supports up to four parameters as well). + /// Parameters to be passed to/from the secure world. If `cmd == OpenSession`, the first + /// two params are meta parameters (TA UUID and client identity, marked with + /// `OPTEE_MSG_ATTR_META`) and are not delivered to the TA. /// - /// TODO: To support OP-TEE RPC, we should make this array length dynamic. Consider to use - /// a trailing unsized slice (DST) or other mechanisms. + /// The C `struct optee_msg_arg` uses a flexible array member `params[]` whose length + /// is determined by `num_params`. We fix it to `TEE_NUM_PARAMS + 2` (= `MAX_PARAMS`) + /// to match the Linux driver's `MAX_ARG_PARAM_COUNT`. The variable-length wire format + /// is handled by the read/write proxy in `msg_handler.rs` and `write_msg_args_to_normal_world`. pub params: [OpteeMsgParam; TEE_NUM_PARAMS + 2], } @@ -1529,6 +1628,107 @@ impl OpteeMsgArgs { Ok(()) } } + + /// Maximum number of parameters that `OpteeMsgArgs` can hold. + /// + /// This is `TEE_NUM_PARAMS + 2` = 6, matching the Linux driver's `MAX_ARG_PARAM_COUNT`. + /// The `+2` accounts for the meta parameters (TA UUID + client identity) that the + /// Linux driver prepends for `OpenSession` commands. For `InvokeCommand`, only + /// `TEE_NUM_PARAMS` (4) client parameters are used. For RPC args, the actual + /// `num_params` is negotiated during `EXCHANGE_CAPABILITIES` (typically 1-2 in + /// upstream OP-TEE), but we cap it to this same array size. + pub const MAX_PARAMS: usize = TEE_NUM_PARAMS + 2; + + /// Construct an `OpteeMsgArgs` from a zerocopy header and a raw parameter byte slice. + /// + /// `raw_params` must contain at least `header.num_params * size_of::()` bytes. + /// `header.num_params` must not exceed `MAX_PARAMS` (6). Note that `num_params` counts + /// **all** entries in the `params[]` array, including meta parameters — e.g. for + /// `OpenSession`, the Linux driver sets `num_params = TEE_NUM_PARAMS + 2` (4 client + /// params + 2 meta params for TA UUID and client identity). For RPC args, `num_params` + /// is the count negotiated during `EXCHANGE_CAPABILITIES`. + /// + /// This bounds check is critical defense-in-depth against CVE-2022-46152-style attacks + /// where an unvalidated `num_params` from normal world causes OOB access on fixed arrays. + pub fn from_header_and_raw_params( + header: &OpteeMsgArgsHeader, + raw_params: &[u8], + ) -> Result { + let num = header.num_params as usize; + if num > Self::MAX_PARAMS { + return Err(OpteeSmcReturnCode::EBadCmd); + } + let needed = num * size_of::(); + if raw_params.len() < needed { + return Err(OpteeSmcReturnCode::EBadAddr); + } + + let cmd = + OpteeMessageCommand::try_from(header.cmd).map_err(|_| OpteeSmcReturnCode::EBadCmd)?; + + let ret = TeeResult::try_from(header.ret).map_err(|_| OpteeSmcReturnCode::EBadCmd)?; + + let ret_origin = TeeOrigin::read_from_bytes(header.ret_origin.as_bytes()) + .map_err(|_| OpteeSmcReturnCode::EBadCmd)?; + + let mut params = [OpteeMsgParam { + attr: OpteeMsgAttr::default(), + u: OpteeMsgParamUnion { octets: [0u8; 24] }, + }; Self::MAX_PARAMS]; + + for (i, param) in params.iter_mut().enumerate().take(num) { + let offset = i * size_of::(); + let param_bytes = &raw_params[offset..offset + size_of::()]; + *param = OpteeMsgParam::from_bytes(param_bytes).ok_or(OpteeSmcReturnCode::EBadAddr)?; + } + + Ok(Self { + cmd, + func: header.func, + session: header.session, + cancel_id: header.cancel_id, + pad: header.pad, + ret, + ret_origin, + num_params: header.num_params, + params, + }) + } + + /// Convert the header portion of this `OpteeMsgArgs` back to an `OpteeMsgArgsHeader`. + pub fn to_header(&self) -> OpteeMsgArgsHeader { + OpteeMsgArgsHeader { + cmd: self.cmd as u32, + func: self.func, + session: self.session, + cancel_id: self.cancel_id, + pad: self.pad, + ret: self.ret as u32, + ret_origin: *self.ret_origin.value(), + num_params: self.num_params, + } + } + + /// Serialize the params portion (up to `num_params`) as raw bytes into `buf`. + /// + /// Re-validates `num_params <= MAX_PARAMS` before using as loop bound. + /// See CVE-2022-46152 for why this defense-in-depth matters. + pub fn write_raw_params(&self, buf: &mut [u8]) -> Result { + let num = self.num_params as usize; + if num > Self::MAX_PARAMS { + return Err(OpteeSmcReturnCode::EBadCmd); + } + let needed = num * size_of::(); + if buf.len() < needed { + return Err(OpteeSmcReturnCode::EBadAddr); + } + for i in 0..num { + let offset = i * size_of::(); + buf[offset..offset + size_of::()] + .copy_from_slice(self.params[i].as_bytes()); + } + Ok(needed) + } } /// A memory page to exchange OP-TEE SMC call arguments. @@ -1659,6 +1859,7 @@ pub enum OpteeSmcResult<'a> { }, CallWithArg { msg_args: Box, + rpc_args: Option>, }, } @@ -1800,7 +2001,7 @@ impl From for litebox_common_linux::errno::Errno { /// * `elf_data` - Raw bytes of the ELF binary pub fn parse_ta_head(elf_data: &[u8]) -> Option { use core::mem::size_of; - use elf::{ElfBytes, endian::AnyEndian}; + use elf::{endian::AnyEndian, ElfBytes}; let elf = ElfBytes::::minimal_parse(elf_data).ok()?; let (shdrs, strtab) = elf.section_headers_with_strtab().ok()?; @@ -1827,6 +2028,20 @@ pub fn parse_ta_head(elf_data: &[u8]) -> Option { mod tests { use super::*; + #[test] + fn test_optee_msg_args_header_size_and_layout() { + use core::mem::{offset_of, size_of}; + assert_eq!(size_of::(), 32); + assert_eq!(offset_of!(OpteeMsgArgsHeader, cmd), 0); + assert_eq!(offset_of!(OpteeMsgArgsHeader, func), 4); + assert_eq!(offset_of!(OpteeMsgArgsHeader, session), 8); + assert_eq!(offset_of!(OpteeMsgArgsHeader, cancel_id), 12); + assert_eq!(offset_of!(OpteeMsgArgsHeader, pad), 16); + assert_eq!(offset_of!(OpteeMsgArgsHeader, ret), 20); + assert_eq!(offset_of!(OpteeMsgArgsHeader, ret_origin), 24); + assert_eq!(offset_of!(OpteeMsgArgsHeader, num_params), 28); + } + #[test] fn test_tee_uuid_from_u64_array() { // Test with OP-TEE's well-known UUID: 384fb3e0-e7f8-11e3-af63-0002a5d5c51b @@ -1845,4 +2060,138 @@ mod tests { [0xaf, 0x63, 0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b] ); } + + #[test] + fn test_header_to_msg_args_too_many_params() { + let header = OpteeMsgArgsHeader { + cmd: 0, + func: 0, + session: 0, + cancel_id: 0, + pad: 0, + ret: 0, + ret_origin: 0, + num_params: 7, // exceeds MAX_PARAMS = 6 + }; + let result = OpteeMsgArgs::from_header_and_raw_params(&header, &[0u8; 224]); + assert!(result.is_err()); + } + + #[test] + fn test_header_to_msg_args_raw_params_too_short() { + let header = OpteeMsgArgsHeader { + cmd: 0, + func: 0, + session: 0, + cancel_id: 0, + pad: 0, + ret: 0, + ret_origin: 0, + num_params: 4, + }; + let result = OpteeMsgArgs::from_header_and_raw_params(&header, &[0u8; 64]); + assert!(result.is_err()); + } + + #[test] + fn test_roundtrip_header_params_write_read() { + use alloc::vec; + + let header = OpteeMsgArgsHeader { + cmd: 1, // InvokeCommand + func: 0x1234, + session: 0xABCD, + cancel_id: 0, + pad: 0, + ret: 0, + ret_origin: 0, + num_params: 3, + }; + let mut params_in = vec![0u8; 3 * size_of::()]; + for (i, byte) in params_in.iter_mut().enumerate() { + *byte = (i & 0xFF) as u8; + } + + let msg_args = match OpteeMsgArgs::from_header_and_raw_params(&header, ¶ms_in) { + Ok(m) => m, + Err(_) => panic!("expected Ok"), + }; + assert_eq!(msg_args.func, 0x1234); + assert_eq!(msg_args.session, 0xABCD); + assert_eq!(msg_args.num_params, 3); + + let header_out = msg_args.to_header(); + assert_eq!(header_out.cmd, 1); + assert_eq!(header_out.func, 0x1234); + assert_eq!(header_out.session, 0xABCD); + assert_eq!(header_out.num_params, 3); + + let mut params_out = vec![0u8; 3 * size_of::()]; + let written = match msg_args.write_raw_params(&mut params_out) { + Ok(n) => n, + Err(_) => panic!("expected Ok"), + }; + assert_eq!(written, 3 * size_of::()); + assert_eq!(¶ms_out[..written], ¶ms_in[..written]); + } + + #[test] + fn test_zero_params() { + let header = OpteeMsgArgsHeader { + cmd: 3, // Cancel + func: 0, + session: 1, + cancel_id: 42, + pad: 0, + ret: 0, + ret_origin: 0, + num_params: 0, + }; + let msg_args = match OpteeMsgArgs::from_header_and_raw_params(&header, &[]) { + Ok(m) => m, + Err(_) => panic!("expected Ok"), + }; + assert_eq!(msg_args.num_params, 0); + assert_eq!(msg_args.cancel_id, 42); + + let header_out = msg_args.to_header(); + assert_eq!(header_out.num_params, 0); + + let mut buf = [0u8; 0]; + let written = match msg_args.write_raw_params(&mut buf) { + Ok(n) => n, + Err(_) => panic!("expected Ok"), + }; + assert_eq!(written, 0); + } + + #[test] + fn test_max_params() { + use alloc::vec; + + let header = OpteeMsgArgsHeader { + cmd: 0, // OpenSession + func: 0, + session: 0, + cancel_id: 0, + pad: 0, + ret: 0, + ret_origin: 0, + num_params: 6, // MAX_PARAMS + }; + let params = vec![0xABu8; 6 * size_of::()]; + let msg_args = match OpteeMsgArgs::from_header_and_raw_params(&header, ¶ms) { + Ok(m) => m, + Err(_) => panic!("expected Ok"), + }; + assert_eq!(msg_args.num_params, 6); + + let mut params_out = vec![0u8; 6 * size_of::()]; + let written = match msg_args.write_raw_params(&mut params_out) { + Ok(n) => n, + Err(_) => panic!("expected Ok"), + }; + assert_eq!(written, 6 * size_of::()); + assert_eq!(params_out, params); + } } diff --git a/litebox_runner_lvbs/Cargo.toml b/litebox_runner_lvbs/Cargo.toml index 0c22037a5..6d6dbedb4 100644 --- a/litebox_runner_lvbs/Cargo.toml +++ b/litebox_runner_lvbs/Cargo.toml @@ -14,6 +14,7 @@ spin = { version = "0.10.0", default-features = false, features = ["spin_mutex"] hashbrown = "0.15.2" once_cell = { version = "1.21.3", default-features = false, features = ["race", "alloc"] } num_enum = { version = "0.7.4", default-features = false } +zerocopy = { version = "0.8", default-features = false } [target.'cfg(target_arch = "x86_64")'.dependencies] diff --git a/litebox_runner_lvbs/src/lib.rs b/litebox_runner_lvbs/src/lib.rs index 1ca9e28fd..4b5041712 100644 --- a/litebox_runner_lvbs/src/lib.rs +++ b/litebox_runner_lvbs/src/lib.rs @@ -7,6 +7,7 @@ extern crate alloc; use alloc::boxed::Box; use alloc::sync::Arc; +use core::mem::size_of; use core::{ops::Neg, panic::PanicInfo}; use litebox::{ mm::linux::PAGE_SIZE, @@ -15,8 +16,9 @@ use litebox::{ }; use litebox_common_linux::errno::Errno; use litebox_common_optee::{ - OpteeMessageCommand, OpteeMsgArgs, OpteeSmcArgs, OpteeSmcResult, OpteeSmcReturnCode, TeeOrigin, - TeeResult, UteeEntryFunc, UteeParams, + optee_msg_get_arg_size, OpteeMessageCommand, OpteeMsgArgs, OpteeMsgArgsHeader, OpteeMsgParam, + OpteeSmcArgs, OpteeSmcResult, OpteeSmcReturnCode, TeeOrigin, TeeResult, UteeEntryFunc, + UteeParams, NUM_RPC_PARAMS, }; use litebox_platform_lvbs::{ arch::{gdt, get_core_id, instrs::hlt_loop, interrupts}, @@ -24,14 +26,15 @@ use litebox_platform_lvbs::{ host::{bootparam::get_vtl1_memory_info, per_cpu_variables::allocate_per_cpu_variables}, mm::MemoryProvider, mshv::{ - NUM_VTLCALL_PARAMS, VsmFunction, hvcall, + hvcall, vsm::vsm_dispatch, vsm_intercept::raise_vtl0_gp_fault, - vtl_switch::{vtl_switch, vtl_switch_init}, vtl1_mem_layout::{ - VTL1_INIT_HEAP_SIZE, VTL1_INIT_HEAP_START_PAGE, VTL1_PML4E_PAGE, - VTL1_PRE_POPULATED_MEMORY_SIZE, get_heap_start_address, + get_heap_start_address, VTL1_INIT_HEAP_SIZE, VTL1_INIT_HEAP_START_PAGE, + VTL1_PML4E_PAGE, VTL1_PRE_POPULATED_MEMORY_SIZE, }, + vtl_switch::{vtl_switch, vtl_switch_init}, + VsmFunction, NUM_VTLCALL_PARAMS, }, serial_println, }; @@ -40,11 +43,12 @@ use litebox_shim_optee::msg_handler::{ decode_ta_request, handle_optee_msg_args, handle_optee_smc_args, update_optee_msg_args, }; use litebox_shim_optee::session::{ - MAX_TA_INSTANCES, SessionIdGuard, SessionManager, TaInstance, allocate_session_id, + allocate_session_id, SessionIdGuard, SessionManager, TaInstance, MAX_TA_INSTANCES, }; use litebox_shim_optee::{NormalWorldConstPtr, NormalWorldMutPtr, UserConstPtr}; use once_cell::race::OnceBox; use spin::mutex::SpinMutex; +use zerocopy::IntoBytes; /// # Panics /// @@ -311,7 +315,10 @@ fn optee_smc_handler(smc_args_addr: usize) -> OpteeSmcArgs { return *smc_args; }; match smc_result { - OpteeSmcResult::CallWithArg { msg_args } => { + OpteeSmcResult::CallWithArg { + msg_args, + rpc_args: _rpc_args, + } => { let mut msg_args = *msg_args; debug_serial_println!("OP-TEE SMC with MsgArgs Command: {:?}", msg_args.cmd); let result = match msg_args.cmd { @@ -493,6 +500,7 @@ fn open_session_single_instance( None, // No session ID on failure Some(&ta_params), Some(ta_req_info), + None, )?; session_manager().remove_single_instance(&ta_uuid); @@ -518,6 +526,7 @@ fn open_session_single_instance( None, // No session ID on failure Some(&ta_params), Some(ta_req_info), + None, )?; return Ok(()); @@ -535,6 +544,7 @@ fn open_session_single_instance( Some(runner_session_id), Some(&ta_params), Some(ta_req_info), + None, )?; debug_serial_println!( @@ -640,6 +650,7 @@ fn open_session_new_instance( None, // No session ID on failure None, Some(ta_req_info), + None, )?; return Ok(()); @@ -706,6 +717,7 @@ fn open_session_new_instance( None, // No session ID on failure Some(&ta_params), Some(ta_req_info), + None, )?; // Tear down the page table - no session was registered @@ -739,6 +751,7 @@ fn open_session_new_instance( Some(runner_session_id), Some(&ta_params), Some(ta_req_info), + None, )?; debug_serial_println!( @@ -856,6 +869,7 @@ fn handle_invoke_command( None, Some(&ta_params), Some(&ta_req_info), + None, )?; if is_last_session { @@ -888,6 +902,7 @@ fn handle_invoke_command( None, Some(&ta_params), Some(&ta_req_info), + None, )?; Ok(()) @@ -956,6 +971,7 @@ fn handle_close_session( None, None, None, + None, )?; // Clone the instance Arc before dropping the lock for later cleanup check @@ -1020,12 +1036,36 @@ fn handle_close_session( Ok(()) } +/// Maximum byte size of the serialized write-back stack buffer: main `optee_msg_arg` with +/// `MAX_PARAMS` parameters plus RPC `optee_msg_arg` with `NUM_RPC_PARAMS` parameters. +const MAX_WRITE_BLOB_SIZE: usize = (size_of::() + + size_of::() * OpteeMsgArgs::MAX_PARAMS) + + (size_of::() + size_of::() * NUM_RPC_PARAMS); + /// Update msg_args with return values and write back to normal world memory. /// +/// Serializes the main `OpteeMsgArgs` (and optional RPC args) into a contiguous byte +/// blob and writes it to the VTL0 physical address. +/// /// Per OP-TEE OS semantics: /// - `TeeOrigin::Tee` is used when the error comes from TEE itself (panic/TARGET_DEAD) /// - `TeeOrigin::TrustedApp` is used when the error comes from the TA /// +/// Write-back layout at `msg_args_phys_addr`: +/// +/// ```text +/// blob[0] blob[main_size] +/// | | +/// v v +/// +--------+-----------+ +--------+-----------+ +/// | main | main | | rpc | rpc | +/// | header | params | | header | params | +/// | 32B | N*32B | | 32B | M*32B | +/// +--------+-----------+ +--------+-----------+ +/// |<--- main_size ---->| |<--- rpc_size ----->| +/// (only if rpc_args is Some) +/// ``` +/// /// # Security Note /// /// This function accesses TA userspace memory via `update_optee_msg_args` to copy out @@ -1043,6 +1083,7 @@ fn write_msg_args_to_normal_world( session_id: Option, ta_params: Option<&UteeParams>, ta_req_info: Option<&litebox_shim_optee::msg_handler::TaRequestInfo>, + rpc_args: Option<&OpteeMsgArgs>, ) -> Result<(), OpteeSmcReturnCode> { // Ensure we're on a task page table, not the base page table. // Accessing TA userspace memory requires the TA's page table to be active. @@ -1067,10 +1108,51 @@ fn write_msg_args_to_normal_world( ta_req_info, msg_args, )?; - let mut ptr = - NormalWorldMutPtr::::with_usize(msg_args_phys_addr.truncate())?; - // SAFETY: Writing msg_args back to normal world memory at a valid address. - unsafe { ptr.write_at_offset(0, *msg_args) }?; + + // Compute variable-length sizes for the main args + let main_size = optee_msg_get_arg_size(msg_args.num_params); + + // Compute total size including optional RPC args + let (total_size, rpc_size) = if let Some(rpc) = rpc_args { + let rs = optee_msg_get_arg_size(rpc.num_params); + (main_size + rs, Some(rs)) + } else { + (main_size, None) + }; + + // Use a stack-allocated buffer: max size is 2 × optee_msg_get_arg_size(MAX_PARAMS) + // = 2 × (32 + 6×32) = 448 bytes, well within stack limits. + assert!(total_size <= MAX_WRITE_BLOB_SIZE); + let mut blob = [0u8; MAX_WRITE_BLOB_SIZE]; + let blob = &mut blob[..total_size]; + + // Serialize main args header + let header = msg_args.to_header(); + let header_bytes = header.as_bytes(); + blob[..size_of::()].copy_from_slice(header_bytes); + + // Serialize main args params + msg_args.write_raw_params(&mut blob[size_of::()..main_size])?; + + // Serialize optional RPC args after the main args + if let Some((rpc, rpc_size)) = rpc_args.zip(rpc_size) { + let rpc_header = rpc.to_header(); + let rpc_header_bytes = rpc_header.as_bytes(); + blob[main_size..main_size + size_of::()] + .copy_from_slice(rpc_header_bytes); + rpc.write_raw_params( + &mut blob[main_size + size_of::()..main_size + rpc_size], + )?; + } + + // Write the blob to normal world physical memory + let mut ptr = NormalWorldMutPtr::::with_contiguous_pages( + msg_args_phys_addr.truncate(), + total_size, + )?; + // SAFETY: Writing msg_args back to normal world memory at a valid physical address. + // The blob contains the serialized variable-length optee_msg_arg structure(s). + unsafe { ptr.write_slice_at_offset(0, blob) }?; Ok(()) } diff --git a/litebox_shim_optee/src/msg_handler.rs b/litebox_shim_optee/src/msg_handler.rs index 730b1b794..bfe067d80 100644 --- a/litebox_shim_optee/src/msg_handler.rs +++ b/litebox_shim_optee/src/msg_handler.rs @@ -17,16 +17,19 @@ //! shared memory). use crate::{NormalWorldConstPtr, NormalWorldMutPtr}; use alloc::{boxed::Box, vec::Vec}; +use core::mem::size_of; use hashbrown::HashMap; use litebox::{mm::linux::PAGE_SIZE, utils::TruncateExt}; use litebox_common_linux::vmap::{PhysPageAddr, PhysPointerError}; use litebox_common_optee::{ - OpteeMessageCommand, OpteeMsgArgs, OpteeMsgAttrType, OpteeMsgParamRmem, OpteeMsgParamTmem, - OpteeMsgParamValue, OpteeSecureWorldCapabilities, OpteeSmcArgs, OpteeSmcFunction, - OpteeSmcResult, OpteeSmcReturnCode, TeeIdentity, TeeLogin, TeeOrigin, TeeParamType, TeeResult, - TeeUuid, UteeEntryFunc, UteeParamOwned, UteeParams, + optee_msg_get_arg_size, OpteeMessageCommand, OpteeMsgArgs, OpteeMsgArgsHeader, + OpteeMsgAttrType, OpteeMsgParam, OpteeMsgParamRmem, OpteeMsgParamTmem, OpteeMsgParamValue, + OpteeSecureWorldCapabilities, OpteeSmcArgs, OpteeSmcFunction, OpteeSmcResult, + OpteeSmcReturnCode, TeeIdentity, TeeLogin, TeeOrigin, TeeParamType, TeeResult, TeeUuid, + UteeEntryFunc, UteeParamOwned, UteeParams, NUM_RPC_PARAMS, }; use once_cell::race::OnceBox; +use zerocopy::FromBytes; // OP-TEE version and build info (2.0) // TODO: Consider replacing it with our own version info @@ -50,7 +53,6 @@ const OPTEE_MSG_OS_OPTEE_UUID_3: u32 = 0xa5d5_c51b; // We do not support notification for now const MAX_NOTIF_VALUE: usize = 0; -const NUM_RPC_PARMS: usize = 4; #[inline] fn page_align_down(address: u64) -> u64 { @@ -62,6 +64,142 @@ fn page_align_up(len: u64) -> u64 { len.next_multiple_of(PAGE_SIZE as u64) } +/// Maximum byte size of a single-copy read buffer: main `optee_msg_arg` with `MAX_PARAMS` +/// parameters plus RPC `optee_msg_arg` with `NUM_RPC_PARAMS` parameters. +const MAX_COPY_SIZE: usize = (size_of::() + + size_of::() * OpteeMsgArgs::MAX_PARAMS) + + (size_of::() + size_of::() * NUM_RPC_PARAMS); + +/// Read `OpteeMsgArgs` from a VTL0 physical address using a single-copy approach. +/// +/// Copies the maximum possible size in one shot into a private VTL1 buffer, then +/// parses entirely from that buffer. This eliminates TOCTOU (double-fetch) issues: +/// VTL0 cannot mutate the data between validation and use because we only touch +/// our private copy after the single read. +/// +/// The copy size is determined by known-good upper bounds, not by untrusted data: +/// - Main args: `optee_msg_get_arg_size(MAX_PARAMS)` = 224 bytes (the Linux driver +/// always allocates at least this much for the main arg). +/// - RPC args (when present): `optee_msg_get_arg_size(rpc_num_params)`, where +/// `rpc_num_params` is our own negotiated value from `EXCHANGE_CAPABILITIES`. +/// +/// If `has_rpc_arg` is true, expects an appended RPC `optee_msg_arg` immediately after +/// the main one at offset `optee_msg_get_arg_size(num_params)` (the *actual* `num_params`, +/// not `MAX_PARAMS`). This matches the Linux driver's layout. +/// +/// VTL0 physical memory layout at `phys_addr`: +/// +/// ```text +/// phys_addr +/// | +/// v main_size main_max +/// |<---------------------->| | +/// +--------+------+--------+--------+------+-----------+ +/// | header |par[0]|par[N-1]| header |par[0]|par[R-1] | +/// | 32B | ... | 32B | 32B | ... | 32B | +/// +--------+------+--------+--------+------+-----------+ +/// |<-- main_size -------->|<--- rpc_size -->| | +/// | ^ | +/// | RPC starts here main_max + rpc_max +/// |<----- copy_size = main_max + rpc_max ------------->| +/// +/// N = actual num_params from header (validated <= MAX_PARAMS after copy) +/// R = rpc_num_params (negotiated during OPTEE_SMC_EXCHANGE_CAPABILITIES) +/// +/// We copy main_max + rpc_max bytes (the upper bound), which covers the +/// actual data (main_size + rpc_size) plus some unused padding between +/// main_size and main_max. We parse using main_size from our private +/// snapshot, so the RPC arg is found at its correct offset. +/// ``` +/// +/// Returns `(main_args, Option)`. +/// +/// # Panics +/// +/// Panics if `copy_size` exceeds `MAX_COPY_SIZE` (448 bytes). This should never happen +/// since `rpc_num_params` is validated against `MAX_PARAMS` before computing `copy_size`. +pub fn read_optee_msg_args_from_phys( + phys_addr: usize, + has_rpc_arg: bool, + rpc_num_params: usize, +) -> Result<(Box, Option>), OpteeSmcReturnCode> { + // Validate rpc_num_params against our array capacity (this is our own value, not + // from VTL0, but defense-in-depth). + if has_rpc_arg && rpc_num_params > OpteeMsgArgs::MAX_PARAMS { + return Err(OpteeSmcReturnCode::EBadCmd); + } + + // Compute copy size from known-good upper bounds — no untrusted data involved. + // The Linux driver always allocates optee_msg_get_arg_size(MAX_ARG_PARAM_COUNT=6) + // for the main arg, so we can safely read that much. The RPC arg in VTL0 sits at + // offset main_size (actual num_params), but we don't know num_params yet, so we + // copy main_max + rpc_max and index into the snapshot using validated num_params. + // Worst case we copy (MAX_PARAMS - actual_num_params) * 32 extra bytes of padding. + let main_max = optee_msg_get_arg_size(OpteeMsgArgs::MAX_PARAMS.truncate()); + let copy_size = if has_rpc_arg { + let rpc_size = optee_msg_get_arg_size(rpc_num_params.truncate()); + main_max + .checked_add(rpc_size) + .ok_or(OpteeSmcReturnCode::EBadAddr)? + } else { + main_max + }; + + // Single copy from VTL0 into private VTL1 buffer. After this point we never + // touch VTL0 memory again — all parsing is from our snapshot. + assert!(copy_size <= MAX_COPY_SIZE); + let mut blob = [0u8; MAX_COPY_SIZE]; + let blob = &mut blob[..copy_size]; + + let mut blob_ptr = + NormalWorldConstPtr::::with_contiguous_pages(phys_addr, copy_size) + .map_err(|_| OpteeSmcReturnCode::EBadAddr)?; + unsafe { blob_ptr.read_slice_at_offset(0, blob) }.map_err(|_| OpteeSmcReturnCode::EBadAddr)?; + + // Parse main header from the private buffer. + let header_end = size_of::(); + if blob.len() < header_end { + return Err(OpteeSmcReturnCode::EBadAddr); + } + let main_header = OpteeMsgArgsHeader::read_from_bytes(&blob[..header_end]) + .map_err(|_| OpteeSmcReturnCode::EBadAddr)?; + + // Validate num_params from the snapshot. See CVE-2022-46152: OP-TEE OOB read via + // unvalidated num_params allowed normal world to read beyond stack-allocated param arrays. + if main_header.num_params as usize > OpteeMsgArgs::MAX_PARAMS { + return Err(OpteeSmcReturnCode::EBadCmd); + } + + let main_size = optee_msg_get_arg_size(main_header.num_params); + let main_params = &blob[size_of::()..main_size]; + let main_args = OpteeMsgArgs::from_header_and_raw_params(&main_header, main_params)?; + + // Parse RPC args if present (from the same private buffer snapshot). + // The Linux driver places the RPC arg at offset main_size (based on the actual + // num_params, not MAX_PARAMS). Since we copied main_max bytes which is >= main_size, + // and main_size is computed from our own validated snapshot, this is safe. + let rpc_args = if has_rpc_arg { + let rpc_blob = &blob[main_size..]; + let rpc_header_end = size_of::(); + if rpc_blob.len() < rpc_header_end { + return Err(OpteeSmcReturnCode::EBadAddr); + } + let rpc_header = OpteeMsgArgsHeader::read_from_bytes(&rpc_blob[..rpc_header_end]) + .map_err(|_| OpteeSmcReturnCode::EBadAddr)?; + // Re-validate RPC num_params from the snapshot against our negotiated limit. + if rpc_header.num_params as usize > NUM_RPC_PARAMS { + return Err(OpteeSmcReturnCode::EBadCmd); + } + let rpc_params = &rpc_blob[rpc_header_end..]; + let rpc = OpteeMsgArgs::from_header_and_raw_params(&rpc_header, rpc_params)?; + Some(Box::new(rpc)) + } else { + None + }; + + Ok((Box::new(main_args), rpc_args)) +} + /// This function handles `OpteeSmcArgs` passed from the normal world (VTL0) via an OP-TEE SMC call. /// It returns an `OpteeSmcResult` representing the result of the SMC call or `OpteeMsgArgs` it contains /// if the SMC call involves with an OP-TEE message which should be handled by @@ -77,19 +215,22 @@ pub fn handle_optee_smc_args( func_id ); match func_id { - OpteeSmcFunction::CallWithArg - | OpteeSmcFunction::CallWithRpcArg - | OpteeSmcFunction::CallWithRegdArg => { + OpteeSmcFunction::CallWithArg => { let msg_args_addr = smc.optee_msg_args_phys_addr()?; let msg_args_addr: usize = msg_args_addr.truncate(); - let mut ptr = NormalWorldConstPtr::::with_usize(msg_args_addr) - .map_err(|_| OpteeSmcReturnCode::EBadAddr)?; - let msg_args = - unsafe { ptr.read_at_offset(0) }.map_err(|_| OpteeSmcReturnCode::EBadAddr)?; + let (msg_args, _) = read_optee_msg_args_from_phys(msg_args_addr, false, 0)?; Ok(OpteeSmcResult::CallWithArg { - msg_args: Box::new(*msg_args), + msg_args, + rpc_args: None, }) } + OpteeSmcFunction::CallWithRpcArg | OpteeSmcFunction::CallWithRegdArg => { + let msg_args_addr = smc.optee_msg_args_phys_addr()?; + let msg_args_addr: usize = msg_args_addr.truncate(); + let (msg_args, rpc_args) = + read_optee_msg_args_from_phys(msg_args_addr, true, NUM_RPC_PARAMS)?; + Ok(OpteeSmcResult::CallWithArg { msg_args, rpc_args }) + } OpteeSmcFunction::ExchangeCapabilities => { // TODO: update the below when we support more features let default_cap = OpteeSecureWorldCapabilities::DYNAMIC_SHM @@ -99,7 +240,7 @@ pub fn handle_optee_smc_args( status: OpteeSmcReturnCode::Ok, capabilities: default_cap, max_notif_value: MAX_NOTIF_VALUE, - data: NUM_RPC_PARMS, + data: NUM_RPC_PARAMS, }) } OpteeSmcFunction::DisableShmCache => { From 1dc97161b178275e80c9353bee3b21b5175c967e Mon Sep 17 00:00:00 2001 From: Sangho Lee Date: Thu, 12 Feb 2026 23:49:34 +0000 Subject: [PATCH 2/9] OpteeRpcArgs and other improvements --- litebox_common_optee/src/lib.rs | 327 +++++++++++++++++++++++--- litebox_runner_lvbs/src/lib.rs | 68 ++---- litebox_shim_optee/src/msg_handler.rs | 99 +++----- 3 files changed, 352 insertions(+), 142 deletions(-) diff --git a/litebox_common_optee/src/lib.rs b/litebox_common_optee/src/lib.rs index 219eb4a9b..07a6802c0 100644 --- a/litebox_common_optee/src/lib.rs +++ b/litebox_common_optee/src/lib.rs @@ -12,9 +12,9 @@ use alloc::boxed::Box; use core::mem::size_of; use litebox::platform::RawConstPointer as _; use litebox::utils::TruncateExt; -use litebox_common_linux::{errno::Errno, PtRegs}; +use litebox_common_linux::{PtRegs, errno::Errno}; use modular_bitfield::prelude::*; -use modular_bitfield::specifiers::{B54, B8}; +use modular_bitfield::specifiers::{B8, B54}; use num_enum::TryFromPrimitive; use syscall_nr::{LdelfSyscallNr, TeeSyscallNr}; use zerocopy::{FromBytes, Immutable, IntoBytes}; @@ -402,12 +402,13 @@ pub struct UteeParams { pub types: UteeParamsTypes, pub vals: [u64; TEE_NUM_PARAMS * 2], } + +/// Number of TEE parameters to be passed to TAs. const TEE_NUM_PARAMS: usize = 4; -/// Number of RPC parameters reported to the Linux driver during `EXCHANGE_CAPABILITIES`. -/// The Linux driver allocates space for an appended RPC `optee_msg_arg` with this many -/// params when `OPTEE_SMC_SEC_CAP_RPC_ARG` is set. Upstream OP-TEE typically uses 1-2; -/// we use 4 to match `TEE_NUM_PARAMS` for flexibility. +/// Number of RPC parameters that LiteBox defined and reported to the normal-world +/// Linux kernel driver during `EXCHANGE_CAPABILITIES`. The Linux kernel driver is +/// expected to allocate a shared buffer for this number of parameters. pub const NUM_RPC_PARAMS: usize = 4; #[expect( @@ -1253,6 +1254,48 @@ impl TryFrom for UteeEntryFunc { } } +// RPC command IDs from `optee_os/core/include/optee_msg.h`. +// These occupy the `cmd` field of the RPC `optee_msg_arg`, which is a separate namespace +// from `OPTEE_MSG_CMD_*` used for main messaging. +const OPTEE_MSG_RPC_CMD_LOAD_TA: u32 = 0; +const OPTEE_MSG_RPC_CMD_RPMB: u32 = 1; +const OPTEE_MSG_RPC_CMD_FS: u32 = 2; +const OPTEE_MSG_RPC_CMD_GET_TIME: u32 = 3; +const OPTEE_MSG_RPC_CMD_WAIT_QUEUE: u32 = 4; +const OPTEE_MSG_RPC_CMD_SUSPEND: u32 = 5; +const OPTEE_MSG_RPC_CMD_SHM_ALLOC: u32 = 28; +const OPTEE_MSG_RPC_CMD_SHM_FREE: u32 = 29; +const OPTEE_MSG_RPC_CMD_NOTIFICATION: u32 = 30; + +/// `OPTEE_MSG_RPC_CMD_*` from `optee_os/core/include/optee_msg.h` +/// +/// These are the command IDs used in the `cmd` field of the RPC `optee_msg_arg`. +/// They live in a separate namespace from [`OpteeMessageCommand`] (which is for main +/// messaging between the normal-world driver and OP-TEE OS). +#[non_exhaustive] +#[derive(Clone, Copy, Debug, PartialEq, TryFromPrimitive)] +#[repr(u32)] +pub enum OpteeRpcCommand { + /// Load a Trusted Application binary. + LoadTa = OPTEE_MSG_RPC_CMD_LOAD_TA, + /// Replay Protected Memory Block (RPMB) access. + Rpmb = OPTEE_MSG_RPC_CMD_RPMB, + /// REE file-system access. + Fs = OPTEE_MSG_RPC_CMD_FS, + /// Get REE time. + GetTime = OPTEE_MSG_RPC_CMD_GET_TIME, + /// Wait-queue sleep/wake. + WaitQueue = OPTEE_MSG_RPC_CMD_WAIT_QUEUE, + /// Suspend execution. + Suspend = OPTEE_MSG_RPC_CMD_SUSPEND, + /// Allocate shared memory for RPC output. + ShmAlloc = OPTEE_MSG_RPC_CMD_SHM_ALLOC, + /// Free previously allocated shared memory. + ShmFree = OPTEE_MSG_RPC_CMD_SHM_FREE, + /// Asynchronous notification. + Notification = OPTEE_MSG_RPC_CMD_NOTIFICATION, +} + /// Temporary memory reference parameter /// /// `optee_msg_param_tmem` from `optee_os/core/include/optee_msg.h` @@ -1373,17 +1416,11 @@ pub struct OpteeMsgParam { impl OpteeMsgParam { /// Deserialize an `OpteeMsgParam` from a byte slice of exactly `size_of::()` bytes. - /// - /// # Safety rationale - /// - /// `OpteeMsgParam` is `#[repr(C)]` and every bit pattern is valid (the attr is a bitfield - /// and the union's `octets` variant accepts any bytes), so `read_unaligned` is safe here. - /// Alignment is not required because we use `read_unaligned`. pub fn from_bytes(bytes: &[u8]) -> Option { if bytes.len() < size_of::() { return None; } - // SAFETY: OpteeMsgParam is repr(C), 32 bytes, and any bit pattern is valid. + // SAFETY: OpteeMsgParam is repr(C) and 32 bytes, and we've checked the input slice is large enough. Some(unsafe { core::ptr::read_unaligned(bytes.as_ptr().cast::()) }) } @@ -1449,23 +1486,23 @@ impl OpteeMsgParam { /// Compute the total byte size of an `optee_msg_arg` with `num_params` parameters. /// Equivalent to the C macro `OPTEE_MSG_GET_ARG_SIZE(num_params)`. /// +/// Returns `size_of::() + num_params * size_of::()` +/// (i.e. the 32-byte header plus N × 32-byte parameter slots). +/// /// `num_params` is the total count of entries in `params[]`, which includes both meta /// parameters and client parameters. For example, `OpenSession` uses `TEE_NUM_PARAMS + 2` -/// (4 client + 2 meta), `InvokeCommand` uses up to `TEE_NUM_PARAMS` (4 client), and -/// RPC args use the count negotiated during `EXCHANGE_CAPABILITIES`. +/// (4 client + 2 meta) and `InvokeCommand` uses up to `TEE_NUM_PARAMS` (4 client). /// /// # Safety invariant /// -/// Callers must ensure `num_params` has been validated against `OpteeMsgArgs::MAX_PARAMS` -/// before calling this function. An unvalidated `num_params` from normal world memory -/// could produce an oversized result, leading to out-of-bounds access on fixed-size arrays. +/// Callers must ensure `num_params` has been validated against `OpteeMsgArgs::MAX_PARAMS`. +/// An unvalidated `num_params` from normal world memory could produce an oversized result, +/// leading to out-of-bounds access on fixed-size arrays. /// See CVE-2022-46152 (OP-TEE OOB via unvalidated `num_params`). -pub const fn optee_msg_get_arg_size(num_params: u32) -> usize { - // Defense-in-depth: catch unvalidated num_params during development/testing. - // This cannot be a runtime check in a const fn, but will fire in non-const calls. +pub const fn optee_msg_arg_total_size(num_params: u32) -> usize { debug_assert!( num_params as usize <= OpteeMsgArgs::MAX_PARAMS, - "optee_msg_get_arg_size: num_params exceeds MAX_PARAMS" + "optee_msg_arg_total_size: num_params exceeds MAX_PARAMS" ); core::mem::size_of::() + core::mem::size_of::() * num_params as usize @@ -1475,8 +1512,6 @@ pub const fn optee_msg_get_arg_size(num_params: u32) -> usize { /// /// This struct represents the fixed 32-byte header of the C `struct optee_msg_arg`. /// Unlike `OpteeMsgArgs`, all fields are plain `u32` so it can derive `FromBytes`/`IntoBytes`. -/// Used as phase 1 of the two-phase read: read this header to learn `num_params`, -/// then compute the total size and read the full blob. /// /// A single `optee_msg_arg` on the wire: /// @@ -1513,7 +1548,7 @@ pub struct OpteeMsgArgsHeader { /// `optee_msg_arg` from `optee_os/core/include/optee_msg.h` /// OP-TEE message argument structure that the normal world (or VTL0) OP-TEE driver and OP-TEE OS use to -/// exchange messages. +/// exchange messages. This data structure is used for carrying RPC information as well. #[derive(Clone, Copy)] #[repr(C)] pub struct OpteeMsgArgs { @@ -1731,6 +1766,160 @@ impl OpteeMsgArgs { } } +/// OP-TEE RPC argument structure. +/// +/// This is the RPC counterpart of [`OpteeMsgArgs`]. On the wire it shares the same +/// 32-byte header layout (`optee_msg_arg`), but only a subset of the header fields +/// are meaningful for RPC: +/// +/// | Field | RPC meaning | +/// |----------------------|------------------------------------------------------------| +/// | `cmd` | RPC command ([`OpteeRpcCommand`]) | +/// | `ret` / `ret_origin` | Return value written back by the normal-world RPC handler. | +/// | `num_params` | Number of RPC parameters (`EXCHANGE_CAPABILITIES`). | +/// | `params[]` | RPC payload (e.g. TA load request). | +/// +/// The remaining header fields (`func`, `session`, `cancel_id`) are **unused** for RPC +/// and always zero on the wire. They are not exposed in this struct. +#[derive(Clone, Copy)] +#[repr(C)] +pub struct OpteeRpcArgs { + /// RPC command ID. Unlike main args, this uses [`OpteeRpcCommand`] (e.g. `LoadTa`, + /// `ShmAlloc`) rather than [`OpteeMessageCommand`]. + pub cmd: OpteeRpcCommand, + /// unused for RPC. Corresponds to `func` in the main `optee_msg_arg`. + _reserved_func: u32, + /// unused for RPC. Corresponds to `session`. + _reserved_session: u32, + /// unused for RPC. Corresponds to `cancel_id`. + _reserved_cancel_id: u32, + /// Padding (matches `pad` in the wire format). + _pad: u32, + /// Return value from the normal-world RPC handler. + pub ret: TeeResult, + /// Origin of the return value. + pub ret_origin: TeeOrigin, + /// Number of parameters in `params`, negotiated during `EXCHANGE_CAPABILITIES`. + pub num_params: u32, + /// RPC parameters. Fixed to [`NUM_RPC_PARAMS`] entries. + pub params: [OpteeMsgParam; Self::MAX_PARAMS], +} + +impl OpteeRpcArgs { + /// Maximum number of RPC parameters this struct can hold. + /// + /// This is [`NUM_RPC_PARAMS`], the count negotiated during `EXCHANGE_CAPABILITIES`. + pub const MAX_PARAMS: usize = NUM_RPC_PARAMS; + + /// Construct an `OpteeRpcArgs` from a zerocopy header and a raw parameter byte slice. + /// + /// The `cmd` field is parsed as [`OpteeRpcCommand`]. Unlike + /// [`OpteeMsgArgs::from_header_and_raw_params`], `func`, `session`, and `cancel_id` + /// are stored as reserved zeros — they carry no meaning for RPC. + pub fn from_header_and_raw_params( + header: &OpteeMsgArgsHeader, + raw_params: &[u8], + ) -> Result { + let num = header.num_params as usize; + if num > Self::MAX_PARAMS { + return Err(OpteeSmcReturnCode::EBadCmd); + } + let needed = num * size_of::(); + if raw_params.len() < needed { + return Err(OpteeSmcReturnCode::EBadAddr); + } + + let cmd = OpteeRpcCommand::try_from(header.cmd).map_err(|_| OpteeSmcReturnCode::EBadCmd)?; + + let ret = TeeResult::try_from(header.ret).map_err(|_| OpteeSmcReturnCode::EBadCmd)?; + + let ret_origin = TeeOrigin::read_from_bytes(header.ret_origin.as_bytes()) + .map_err(|_| OpteeSmcReturnCode::EBadCmd)?; + + let mut params = [OpteeMsgParam { + attr: OpteeMsgAttr::default(), + u: OpteeMsgParamUnion { octets: [0u8; 24] }, + }; Self::MAX_PARAMS]; + + for (i, param) in params.iter_mut().enumerate().take(num) { + let offset = i * size_of::(); + let param_bytes = &raw_params[offset..offset + size_of::()]; + *param = OpteeMsgParam::from_bytes(param_bytes).ok_or(OpteeSmcReturnCode::EBadAddr)?; + } + + Ok(Self { + cmd, + _reserved_func: 0, + _reserved_session: 0, + _reserved_cancel_id: 0, + _pad: 0, + ret, + ret_origin, + num_params: header.num_params, + params, + }) + } + + /// Convert back to an [`OpteeMsgArgsHeader`] for wire serialization. + /// + /// The reserved fields (`func`, `session`, `cancel_id`) are written as 0. + pub fn to_header(&self) -> OpteeMsgArgsHeader { + OpteeMsgArgsHeader { + cmd: self.cmd as u32, + func: 0, + session: 0, + cancel_id: 0, + pad: 0, + ret: self.ret as u32, + ret_origin: *self.ret_origin.value(), + num_params: self.num_params, + } + } + + /// Serialize the params portion (up to `num_params`) as raw bytes into `buf`. + pub fn write_raw_params(&self, buf: &mut [u8]) -> Result { + let num = self.num_params as usize; + if num > Self::MAX_PARAMS { + return Err(OpteeSmcReturnCode::EBadCmd); + } + let needed = num * size_of::(); + if buf.len() < needed { + return Err(OpteeSmcReturnCode::EBadAddr); + } + for i in 0..num { + let offset = i * size_of::(); + buf[offset..offset + size_of::()] + .copy_from_slice(self.params[i].as_bytes()); + } + Ok(needed) + } + + /// Access a parameter by index with bounds checking against `num_params`. + pub fn get_param_value(&self, index: usize) -> Result { + if index >= self.num_params as usize { + Err(OpteeSmcReturnCode::ENotAvail) + } else { + self.params[index] + .get_param_value() + .ok_or(OpteeSmcReturnCode::EBadCmd) + } + } + + /// Access a tmem parameter by index with bounds checking against `num_params`. + pub fn get_param_tmem(&self, index: usize) -> Result { + if index >= self.num_params as usize { + Err(OpteeSmcReturnCode::ENotAvail) + } else { + self.params[index] + .get_param_tmem() + .ok_or(OpteeSmcReturnCode::EBadCmd) + } + } + // Note: RPC does not use rmem params. Rmem requires pre-registered shared memory + // references from the normal-world driver, which is a main-messaging-path concept. + // RPC uses tmem for buffer references since OP-TEE provides physical addresses directly. +} + /// A memory page to exchange OP-TEE SMC call arguments. /// OP-TEE assumes that the underlying architecture is Arm with TrustZone and /// thus it uses Secure Monitor Call (SMC) calling convention (SMCCC). @@ -1859,7 +2048,7 @@ pub enum OpteeSmcResult<'a> { }, CallWithArg { msg_args: Box, - rpc_args: Option>, + rpc_args: Option>, }, } @@ -1952,7 +2141,7 @@ const OPTEE_SMC_RETURN_ENOTAVAIL: usize = 0x7; const OPTEE_SMC_RETURN_UNKNOWN_FUNCTION: usize = 0xffff_ffff; #[non_exhaustive] -#[derive(Copy, Clone, PartialEq, TryFromPrimitive)] +#[derive(Copy, Clone, Debug, PartialEq, TryFromPrimitive)] #[repr(usize)] pub enum OpteeSmcReturnCode { Ok = OPTEE_SMC_RETURN_OK, @@ -2001,7 +2190,7 @@ impl From for litebox_common_linux::errno::Errno { /// * `elf_data` - Raw bytes of the ELF binary pub fn parse_ta_head(elf_data: &[u8]) -> Option { use core::mem::size_of; - use elf::{endian::AnyEndian, ElfBytes}; + use elf::{ElfBytes, endian::AnyEndian}; let elf = ElfBytes::::minimal_parse(elf_data).ok()?; let (shdrs, strtab) = elf.section_headers_with_strtab().ok()?; @@ -2194,4 +2383,86 @@ mod tests { assert_eq!(written, 6 * size_of::()); assert_eq!(params_out, params); } + + #[test] + fn test_optee_rpc_args_roundtrip() { + use alloc::vec; + + // ShmAlloc = 28 + let header = OpteeMsgArgsHeader { + cmd: 28, + func: 0, + session: 0, + cancel_id: 0, + pad: 0, + ret: 0, + ret_origin: 0, + num_params: 2, + }; + let mut params_in = vec![0u8; 2 * size_of::()]; + for (i, byte) in params_in.iter_mut().enumerate() { + *byte = (i & 0xFF) as u8; + } + + let rpc_args = OpteeRpcArgs::from_header_and_raw_params(&header, ¶ms_in) + .expect("should parse RPC args"); + assert_eq!(rpc_args.cmd, OpteeRpcCommand::ShmAlloc); + assert_eq!(rpc_args.num_params, 2); + + let header_out = rpc_args.to_header(); + assert_eq!(header_out.cmd, 28); + assert_eq!(header_out.func, 0); + assert_eq!(header_out.session, 0); + assert_eq!(header_out.cancel_id, 0); + assert_eq!(header_out.num_params, 2); + + let mut params_out = vec![0u8; 2 * size_of::()]; + let written = rpc_args + .write_raw_params(&mut params_out) + .expect("should serialize"); + assert_eq!(written, 2 * size_of::()); + assert_eq!(¶ms_out[..written], ¶ms_in[..written]); + } + + #[test] + fn test_rpc_args_rejects_main_cmd() { + // OpenSession = 0 is a main command, but also happens to be LoadTa = 0 for RPC. + // InvokeCommand = 1 maps to Rpmb = 1 in RPC. So to test rejection we need a + // cmd value that's valid for main but not for RPC. RegisterShm = 4 has no + // RPC counterpart. + let header = OpteeMsgArgsHeader { + cmd: 4, // RegisterShm — not a valid OpteeRpcCommand + func: 0, + session: 0, + cancel_id: 0, + pad: 0, + ret: 0, + ret_origin: 0, + num_params: 0, + }; + assert!(OpteeRpcArgs::from_header_and_raw_params(&header, &[]).is_err()); + } + + #[test] + fn test_rpc_args_reserved_fields_zeroed() { + let header = OpteeMsgArgsHeader { + cmd: 29, // ShmFree + func: 0xDEAD, + session: 0xBEEF, + cancel_id: 0xCAFE, + pad: 0, + ret: 0, + ret_origin: 0, + num_params: 0, + }; + let rpc_args = + OpteeRpcArgs::from_header_and_raw_params(&header, &[]).expect("should parse"); + assert_eq!(rpc_args.cmd, OpteeRpcCommand::ShmFree); + + // Reserved fields should be zeroed regardless of header input + let hdr_out = rpc_args.to_header(); + assert_eq!(hdr_out.func, 0); + assert_eq!(hdr_out.session, 0); + assert_eq!(hdr_out.cancel_id, 0); + } } diff --git a/litebox_runner_lvbs/src/lib.rs b/litebox_runner_lvbs/src/lib.rs index 4b5041712..58f99cd64 100644 --- a/litebox_runner_lvbs/src/lib.rs +++ b/litebox_runner_lvbs/src/lib.rs @@ -7,6 +7,7 @@ extern crate alloc; use alloc::boxed::Box; use alloc::sync::Arc; +use alloc::vec; use core::mem::size_of; use core::{ops::Neg, panic::PanicInfo}; use litebox::{ @@ -16,9 +17,9 @@ use litebox::{ }; use litebox_common_linux::errno::Errno; use litebox_common_optee::{ - optee_msg_get_arg_size, OpteeMessageCommand, OpteeMsgArgs, OpteeMsgArgsHeader, OpteeMsgParam, - OpteeSmcArgs, OpteeSmcResult, OpteeSmcReturnCode, TeeOrigin, TeeResult, UteeEntryFunc, - UteeParams, NUM_RPC_PARAMS, + OpteeMessageCommand, OpteeMsgArgs, OpteeMsgArgsHeader, OpteeRpcArgs, OpteeSmcArgs, + OpteeSmcResult, OpteeSmcReturnCode, TeeOrigin, TeeResult, UteeEntryFunc, UteeParams, + optee_msg_arg_total_size, }; use litebox_platform_lvbs::{ arch::{gdt, get_core_id, instrs::hlt_loop, interrupts}, @@ -26,15 +27,14 @@ use litebox_platform_lvbs::{ host::{bootparam::get_vtl1_memory_info, per_cpu_variables::allocate_per_cpu_variables}, mm::MemoryProvider, mshv::{ - hvcall, + NUM_VTLCALL_PARAMS, VsmFunction, hvcall, vsm::vsm_dispatch, vsm_intercept::raise_vtl0_gp_fault, + vtl_switch::{vtl_switch, vtl_switch_init}, vtl1_mem_layout::{ - get_heap_start_address, VTL1_INIT_HEAP_SIZE, VTL1_INIT_HEAP_START_PAGE, - VTL1_PML4E_PAGE, VTL1_PRE_POPULATED_MEMORY_SIZE, + VTL1_INIT_HEAP_SIZE, VTL1_INIT_HEAP_START_PAGE, VTL1_PML4E_PAGE, + VTL1_PRE_POPULATED_MEMORY_SIZE, get_heap_start_address, }, - vtl_switch::{vtl_switch, vtl_switch_init}, - VsmFunction, NUM_VTLCALL_PARAMS, }, serial_println, }; @@ -43,7 +43,7 @@ use litebox_shim_optee::msg_handler::{ decode_ta_request, handle_optee_msg_args, handle_optee_smc_args, update_optee_msg_args, }; use litebox_shim_optee::session::{ - allocate_session_id, SessionIdGuard, SessionManager, TaInstance, MAX_TA_INSTANCES, + MAX_TA_INSTANCES, SessionIdGuard, SessionManager, TaInstance, allocate_session_id, }; use litebox_shim_optee::{NormalWorldConstPtr, NormalWorldMutPtr, UserConstPtr}; use once_cell::race::OnceBox; @@ -1036,15 +1036,9 @@ fn handle_close_session( Ok(()) } -/// Maximum byte size of the serialized write-back stack buffer: main `optee_msg_arg` with -/// `MAX_PARAMS` parameters plus RPC `optee_msg_arg` with `NUM_RPC_PARAMS` parameters. -const MAX_WRITE_BLOB_SIZE: usize = (size_of::() - + size_of::() * OpteeMsgArgs::MAX_PARAMS) - + (size_of::() + size_of::() * NUM_RPC_PARAMS); - /// Update msg_args with return values and write back to normal world memory. /// -/// Serializes the main `OpteeMsgArgs` (and optional RPC args) into a contiguous byte +/// Serializes the main `OpteeMsgArgs` (and optional `OpteeRpcArgs`) into a contiguous byte /// blob and writes it to the VTL0 physical address. /// /// Per OP-TEE OS semantics: @@ -1083,7 +1077,7 @@ fn write_msg_args_to_normal_world( session_id: Option, ta_params: Option<&UteeParams>, ta_req_info: Option<&litebox_shim_optee::msg_handler::TaRequestInfo>, - rpc_args: Option<&OpteeMsgArgs>, + rpc_args: Option<&OpteeRpcArgs>, ) -> Result<(), OpteeSmcReturnCode> { // Ensure we're on a task page table, not the base page table. // Accessing TA userspace memory requires the TA's page table to be active. @@ -1109,50 +1103,32 @@ fn write_msg_args_to_normal_world( msg_args, )?; - // Compute variable-length sizes for the main args - let main_size = optee_msg_get_arg_size(msg_args.num_params); - - // Compute total size including optional RPC args - let (total_size, rpc_size) = if let Some(rpc) = rpc_args { - let rs = optee_msg_get_arg_size(rpc.num_params); - (main_size + rs, Some(rs)) + // Compute total size to write to the normal world memory including optional RPC args + let main_size = optee_msg_arg_total_size(msg_args.num_params); + let total_size = if let Some(rpc) = rpc_args { + main_size + optee_msg_arg_total_size(rpc.num_params) } else { - (main_size, None) + main_size }; - // Use a stack-allocated buffer: max size is 2 × optee_msg_get_arg_size(MAX_PARAMS) - // = 2 × (32 + 6×32) = 448 bytes, well within stack limits. - assert!(total_size <= MAX_WRITE_BLOB_SIZE); - let mut blob = [0u8; MAX_WRITE_BLOB_SIZE]; - let blob = &mut blob[..total_size]; - - // Serialize main args header - let header = msg_args.to_header(); - let header_bytes = header.as_bytes(); - blob[..size_of::()].copy_from_slice(header_bytes); + let mut blob = vec![0u8; total_size]; - // Serialize main args params + blob[..size_of::()].copy_from_slice(msg_args.to_header().as_bytes()); msg_args.write_raw_params(&mut blob[size_of::()..main_size])?; - // Serialize optional RPC args after the main args - if let Some((rpc, rpc_size)) = rpc_args.zip(rpc_size) { - let rpc_header = rpc.to_header(); - let rpc_header_bytes = rpc_header.as_bytes(); + if let Some(rpc) = rpc_args { blob[main_size..main_size + size_of::()] - .copy_from_slice(rpc_header_bytes); - rpc.write_raw_params( - &mut blob[main_size + size_of::()..main_size + rpc_size], - )?; + .copy_from_slice(rpc.to_header().as_bytes()); + rpc.write_raw_params(&mut blob[main_size + size_of::()..total_size])?; } - // Write the blob to normal world physical memory let mut ptr = NormalWorldMutPtr::::with_contiguous_pages( msg_args_phys_addr.truncate(), total_size, )?; // SAFETY: Writing msg_args back to normal world memory at a valid physical address. // The blob contains the serialized variable-length optee_msg_arg structure(s). - unsafe { ptr.write_slice_at_offset(0, blob) }?; + unsafe { ptr.write_slice_at_offset(0, &blob) }?; Ok(()) } diff --git a/litebox_shim_optee/src/msg_handler.rs b/litebox_shim_optee/src/msg_handler.rs index bfe067d80..7636be8ec 100644 --- a/litebox_shim_optee/src/msg_handler.rs +++ b/litebox_shim_optee/src/msg_handler.rs @@ -22,11 +22,11 @@ use hashbrown::HashMap; use litebox::{mm::linux::PAGE_SIZE, utils::TruncateExt}; use litebox_common_linux::vmap::{PhysPageAddr, PhysPointerError}; use litebox_common_optee::{ - optee_msg_get_arg_size, OpteeMessageCommand, OpteeMsgArgs, OpteeMsgArgsHeader, - OpteeMsgAttrType, OpteeMsgParam, OpteeMsgParamRmem, OpteeMsgParamTmem, OpteeMsgParamValue, + NUM_RPC_PARAMS, OpteeMessageCommand, OpteeMsgArgs, OpteeMsgArgsHeader, OpteeMsgAttrType, + OpteeMsgParamRmem, OpteeMsgParamTmem, OpteeMsgParamValue, OpteeRpcArgs, OpteeSecureWorldCapabilities, OpteeSmcArgs, OpteeSmcFunction, OpteeSmcResult, OpteeSmcReturnCode, TeeIdentity, TeeLogin, TeeOrigin, TeeParamType, TeeResult, TeeUuid, - UteeEntryFunc, UteeParamOwned, UteeParams, NUM_RPC_PARAMS, + UteeEntryFunc, UteeParamOwned, UteeParams, optee_msg_arg_total_size, }; use once_cell::race::OnceBox; use zerocopy::FromBytes; @@ -64,12 +64,6 @@ fn page_align_up(len: u64) -> u64 { len.next_multiple_of(PAGE_SIZE as u64) } -/// Maximum byte size of a single-copy read buffer: main `optee_msg_arg` with `MAX_PARAMS` -/// parameters plus RPC `optee_msg_arg` with `NUM_RPC_PARAMS` parameters. -const MAX_COPY_SIZE: usize = (size_of::() - + size_of::() * OpteeMsgArgs::MAX_PARAMS) - + (size_of::() + size_of::() * NUM_RPC_PARAMS); - /// Read `OpteeMsgArgs` from a VTL0 physical address using a single-copy approach. /// /// Copies the maximum possible size in one shot into a private VTL1 buffer, then @@ -78,13 +72,13 @@ const MAX_COPY_SIZE: usize = (size_of::() /// our private copy after the single read. /// /// The copy size is determined by known-good upper bounds, not by untrusted data: -/// - Main args: `optee_msg_get_arg_size(MAX_PARAMS)` = 224 bytes (the Linux driver -/// always allocates at least this much for the main arg). -/// - RPC args (when present): `optee_msg_get_arg_size(rpc_num_params)`, where +/// - Main args: `optee_msg_arg_total_size(MAX_PARAMS = 6)` = 224 bytes (the Linux +/// driver always allocates at least this much for the main arg). +/// - RPC args (when present): `optee_msg_arg_total_size(rpc_num_params)`, where /// `rpc_num_params` is our own negotiated value from `EXCHANGE_CAPABILITIES`. /// /// If `has_rpc_arg` is true, expects an appended RPC `optee_msg_arg` immediately after -/// the main one at offset `optee_msg_get_arg_size(num_params)` (the *actual* `num_params`, +/// the main one at offset `optee_msg_arg_total_size(num_params)` (the *actual* `num_params`, /// not `MAX_PARAMS`). This matches the Linux driver's layout. /// /// VTL0 physical memory layout at `phys_addr`: @@ -95,10 +89,10 @@ const MAX_COPY_SIZE: usize = (size_of::() /// v main_size main_max /// |<---------------------->| | /// +--------+------+--------+--------+------+-----------+ -/// | header |par[0]|par[N-1]| header |par[0]|par[R-1] | -/// | 32B | ... | 32B | 32B | ... | 32B | +/// | header |par[0]|par[N-1]| header |par[0]|par[R-1]|xx| +/// | 32B | ... | 32B | 32B | ... | 32B |xx| /// +--------+------+--------+--------+------+-----------+ -/// |<-- main_size -------->|<--- rpc_size -->| | +/// |<--- main_size -------->|<--- rpc_size --------->| | /// | ^ | /// | RPC starts here main_max + rpc_max /// |<----- copy_size = main_max + rpc_max ------------->| @@ -107,91 +101,61 @@ const MAX_COPY_SIZE: usize = (size_of::() /// R = rpc_num_params (negotiated during OPTEE_SMC_EXCHANGE_CAPABILITIES) /// /// We copy main_max + rpc_max bytes (the upper bound), which covers the -/// actual data (main_size + rpc_size) plus some unused padding between +/// actual data (main_size + rpc_size) plus some unused area between /// main_size and main_max. We parse using main_size from our private /// snapshot, so the RPC arg is found at its correct offset. /// ``` /// /// Returns `(main_args, Option)`. -/// -/// # Panics -/// -/// Panics if `copy_size` exceeds `MAX_COPY_SIZE` (448 bytes). This should never happen -/// since `rpc_num_params` is validated against `MAX_PARAMS` before computing `copy_size`. pub fn read_optee_msg_args_from_phys( phys_addr: usize, has_rpc_arg: bool, - rpc_num_params: usize, -) -> Result<(Box, Option>), OpteeSmcReturnCode> { - // Validate rpc_num_params against our array capacity (this is our own value, not - // from VTL0, but defense-in-depth). - if has_rpc_arg && rpc_num_params > OpteeMsgArgs::MAX_PARAMS { - return Err(OpteeSmcReturnCode::EBadCmd); - } - +) -> Result<(Box, Option>), OpteeSmcReturnCode> { // Compute copy size from known-good upper bounds — no untrusted data involved. - // The Linux driver always allocates optee_msg_get_arg_size(MAX_ARG_PARAM_COUNT=6) - // for the main arg, so we can safely read that much. The RPC arg in VTL0 sits at - // offset main_size (actual num_params), but we don't know num_params yet, so we - // copy main_max + rpc_max and index into the snapshot using validated num_params. - // Worst case we copy (MAX_PARAMS - actual_num_params) * 32 extra bytes of padding. - let main_max = optee_msg_get_arg_size(OpteeMsgArgs::MAX_PARAMS.truncate()); + let main_max = optee_msg_arg_total_size(OpteeMsgArgs::MAX_PARAMS.truncate()); let copy_size = if has_rpc_arg { - let rpc_size = optee_msg_get_arg_size(rpc_num_params.truncate()); - main_max - .checked_add(rpc_size) - .ok_or(OpteeSmcReturnCode::EBadAddr)? + main_max + optee_msg_arg_total_size(NUM_RPC_PARAMS.truncate()) } else { main_max }; - // Single copy from VTL0 into private VTL1 buffer. After this point we never - // touch VTL0 memory again — all parsing is from our snapshot. - assert!(copy_size <= MAX_COPY_SIZE); - let mut blob = [0u8; MAX_COPY_SIZE]; - let blob = &mut blob[..copy_size]; + let mut blob = alloc::vec![0u8; copy_size]; let mut blob_ptr = NormalWorldConstPtr::::with_contiguous_pages(phys_addr, copy_size) .map_err(|_| OpteeSmcReturnCode::EBadAddr)?; - unsafe { blob_ptr.read_slice_at_offset(0, blob) }.map_err(|_| OpteeSmcReturnCode::EBadAddr)?; + unsafe { blob_ptr.read_slice_at_offset(0, &mut blob) } + .map_err(|_| OpteeSmcReturnCode::EBadAddr)?; // Parse main header from the private buffer. - let header_end = size_of::(); - if blob.len() < header_end { - return Err(OpteeSmcReturnCode::EBadAddr); - } - let main_header = OpteeMsgArgsHeader::read_from_bytes(&blob[..header_end]) - .map_err(|_| OpteeSmcReturnCode::EBadAddr)?; + let main_header = OpteeMsgArgsHeader::read_from_prefix(&blob) + .map_err(|_| OpteeSmcReturnCode::EBadAddr)? + .0; - // Validate num_params from the snapshot. See CVE-2022-46152: OP-TEE OOB read via - // unvalidated num_params allowed normal world to read beyond stack-allocated param arrays. + // Validate num_params from the snapshot. if main_header.num_params as usize > OpteeMsgArgs::MAX_PARAMS { return Err(OpteeSmcReturnCode::EBadCmd); } - let main_size = optee_msg_get_arg_size(main_header.num_params); + let main_size = optee_msg_arg_total_size(main_header.num_params); let main_params = &blob[size_of::()..main_size]; let main_args = OpteeMsgArgs::from_header_and_raw_params(&main_header, main_params)?; - // Parse RPC args if present (from the same private buffer snapshot). - // The Linux driver places the RPC arg at offset main_size (based on the actual + // Parse RPC args if present. + // The Linux kernel driver places the RPC arg at offset main_size (based on the actual // num_params, not MAX_PARAMS). Since we copied main_max bytes which is >= main_size, // and main_size is computed from our own validated snapshot, this is safe. let rpc_args = if has_rpc_arg { let rpc_blob = &blob[main_size..]; - let rpc_header_end = size_of::(); - if rpc_blob.len() < rpc_header_end { - return Err(OpteeSmcReturnCode::EBadAddr); - } - let rpc_header = OpteeMsgArgsHeader::read_from_bytes(&rpc_blob[..rpc_header_end]) - .map_err(|_| OpteeSmcReturnCode::EBadAddr)?; + let rpc_header = OpteeMsgArgsHeader::read_from_prefix(rpc_blob) + .map_err(|_| OpteeSmcReturnCode::EBadAddr)? + .0; // Re-validate RPC num_params from the snapshot against our negotiated limit. if rpc_header.num_params as usize > NUM_RPC_PARAMS { return Err(OpteeSmcReturnCode::EBadCmd); } - let rpc_params = &rpc_blob[rpc_header_end..]; - let rpc = OpteeMsgArgs::from_header_and_raw_params(&rpc_header, rpc_params)?; + let rpc_params = &rpc_blob[size_of::()..]; + let rpc = OpteeRpcArgs::from_header_and_raw_params(&rpc_header, rpc_params)?; Some(Box::new(rpc)) } else { None @@ -218,7 +182,7 @@ pub fn handle_optee_smc_args( OpteeSmcFunction::CallWithArg => { let msg_args_addr = smc.optee_msg_args_phys_addr()?; let msg_args_addr: usize = msg_args_addr.truncate(); - let (msg_args, _) = read_optee_msg_args_from_phys(msg_args_addr, false, 0)?; + let (msg_args, _) = read_optee_msg_args_from_phys(msg_args_addr, false)?; Ok(OpteeSmcResult::CallWithArg { msg_args, rpc_args: None, @@ -227,8 +191,7 @@ pub fn handle_optee_smc_args( OpteeSmcFunction::CallWithRpcArg | OpteeSmcFunction::CallWithRegdArg => { let msg_args_addr = smc.optee_msg_args_phys_addr()?; let msg_args_addr: usize = msg_args_addr.truncate(); - let (msg_args, rpc_args) = - read_optee_msg_args_from_phys(msg_args_addr, true, NUM_RPC_PARAMS)?; + let (msg_args, rpc_args) = read_optee_msg_args_from_phys(msg_args_addr, true)?; Ok(OpteeSmcResult::CallWithArg { msg_args, rpc_args }) } OpteeSmcFunction::ExchangeCapabilities => { From 5cd5d8dc5afdee612bb1dbe9adda44bfcb12cd22 Mon Sep 17 00:00:00 2001 From: Sangho Lee Date: Fri, 13 Feb 2026 00:22:36 +0000 Subject: [PATCH 3/9] zerocopy to further remove unsafe --- Cargo.lock | 1 - litebox_common_optee/Cargo.toml | 1 - litebox_common_optee/src/lib.rs | 182 +++++++++++++++++++------------- 3 files changed, 106 insertions(+), 78 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bc1479fa3..e579f5a46 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -856,7 +856,6 @@ dependencies = [ "elf 0.8.0", "litebox", "litebox_common_linux", - "modular-bitfield", "num_enum", "thiserror", "zerocopy", diff --git a/litebox_common_optee/Cargo.toml b/litebox_common_optee/Cargo.toml index 8476afcd8..108280d16 100644 --- a/litebox_common_optee/Cargo.toml +++ b/litebox_common_optee/Cargo.toml @@ -8,7 +8,6 @@ bitflags = "2.9.0" elf = { version = "0.8.0", default-features = false } litebox = { path = "../litebox/", version = "0.1.0" } litebox_common_linux = { path = "../litebox_common_linux/", version = "0.1.0" } -modular-bitfield = { version = "0.12.0", default-features = false } num_enum = { version = "0.7.3", default-features = false } zerocopy = { version = "0.8", features = ["derive"] } thiserror = { version = "2.0.6", default-features = false } diff --git a/litebox_common_optee/src/lib.rs b/litebox_common_optee/src/lib.rs index 07a6802c0..34d09a91d 100644 --- a/litebox_common_optee/src/lib.rs +++ b/litebox_common_optee/src/lib.rs @@ -13,11 +13,9 @@ use core::mem::size_of; use litebox::platform::RawConstPointer as _; use litebox::utils::TruncateExt; use litebox_common_linux::{PtRegs, errno::Errno}; -use modular_bitfield::prelude::*; -use modular_bitfield::specifiers::{B8, B54}; use num_enum::TryFromPrimitive; use syscall_nr::{LdelfSyscallNr, TeeSyscallNr}; -use zerocopy::{FromBytes, Immutable, IntoBytes}; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; pub mod syscall_nr; @@ -411,27 +409,58 @@ const TEE_NUM_PARAMS: usize = 4; /// expected to allocate a shared buffer for this number of parameters. pub const NUM_RPC_PARAMS: usize = 4; -#[expect( - clippy::identity_op, - reason = "the macro auto-generates this, but some issue causes it to still bubble up; this suppresses it the hard way" -)] -mod workaround_identity_op_suppression { - use modular_bitfield::prelude::*; - use modular_bitfield::specifiers::{B4, B48}; - use zerocopy::{FromBytes, Immutable, IntoBytes}; - #[bitfield] - #[derive(Clone, Copy, Default, FromBytes, Immutable, IntoBytes)] - #[repr(C)] - pub struct UteeParamsTypes { - pub type_0: B4, - pub type_1: B4, - pub type_2: B4, - pub type_3: B4, - #[skip] - __: B48, - } -} -pub use workaround_identity_op_suppression::UteeParamsTypes; +/// Packed parameter types for [`UteeParams`]. +/// +/// Wire layout (little-endian u64): +/// - bits \[3:0\] – type_0 +/// - bits \[7:4\] – type_1 +/// - bits \[11:8\] – type_2 +/// - bits \[15:12\] – type_3 +/// - bits \[63:16\] – reserved (zero) +#[derive(Clone, Copy, Default, FromBytes, Immutable, IntoBytes, KnownLayout)] +#[repr(transparent)] +pub struct UteeParamsTypes(u64); + +impl UteeParamsTypes { + const NIBBLE_MASK: u64 = 0xF; + + /// Get the 4-bit type at the given `index` (0–3). + #[allow(clippy::cast_possible_truncation)] + fn get(self, index: usize) -> u8 { + ((self.0 >> (index * 4)) & Self::NIBBLE_MASK) as u8 + } + + /// Set the 4-bit type at the given `index` (0–3). + fn set(&mut self, index: usize, value: u8) { + let shift = index * 4; + self.0 = (self.0 & !(Self::NIBBLE_MASK << shift)) | (u64::from(value & 0xF) << shift); + } + + pub fn type_0(&self) -> u8 { + self.get(0) + } + pub fn type_1(&self) -> u8 { + self.get(1) + } + pub fn type_2(&self) -> u8 { + self.get(2) + } + pub fn type_3(&self) -> u8 { + self.get(3) + } + pub fn set_type_0(&mut self, v: u8) { + self.set(0, v); + } + pub fn set_type_1(&mut self, v: u8) { + self.set(1, v); + } + pub fn set_type_2(&mut self, v: u8) { + self.set(2, v); + } + pub fn set_type_3(&mut self, v: u8) { + self.set(3, v); + } +} const TEE_PARAM_TYPE_NONE: u8 = 0; const TEE_PARAM_TYPE_VALUE_INPUT: u8 = 1; @@ -1299,7 +1328,7 @@ pub enum OpteeRpcCommand { /// Temporary memory reference parameter /// /// `optee_msg_param_tmem` from `optee_os/core/include/optee_msg.h` -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, FromBytes, IntoBytes, Immutable, KnownLayout)] #[repr(C)] pub struct OpteeMsgParamTmem { /// Physical address of the buffer @@ -1313,7 +1342,7 @@ pub struct OpteeMsgParamTmem { /// Registered memory reference parameter /// /// `optee_msg_param_rmem` from `optee_os/core/include/optee_msg.h` -#[derive(Clone, Copy)] +#[derive(Clone, Copy, FromBytes, IntoBytes, Immutable, KnownLayout)] #[repr(C)] pub struct OpteeMsgParamRmem { /// Offset into shared memory reference @@ -1330,7 +1359,7 @@ pub struct OpteeMsgParamRmem { /// /// Note: LiteBox doesn't currently support FF-A shared memory, so this struct is /// provided for completeness but is not used. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, FromBytes, IntoBytes, Immutable, KnownLayout)] #[repr(C)] pub struct OpteeMsgParamFmem { /// Lower bits of offset into shared memory reference @@ -1347,7 +1376,7 @@ pub struct OpteeMsgParamFmem { /// Opaque value parameter /// Value parameters are passed unchecked between normal and secure world. -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, Immutable, KnownLayout)] #[repr(C)] pub struct OpteeMsgParamValue { pub a: u64, @@ -1355,16 +1384,12 @@ pub struct OpteeMsgParamValue { pub c: u64, } -/// Parameter used together with `OpteeMsgArgs` -#[derive(Clone, Copy)] -#[repr(C)] -pub union OpteeMsgParamUnion { - tmem: OpteeMsgParamTmem, - rmem: OpteeMsgParamRmem, - fmem: OpteeMsgParamFmem, - value: OpteeMsgParamValue, - octets: [u8; 24], -} +/// Parameter used together with `OpteeMsgArgs`. +/// +/// The 24-byte `data` field is the on-wire union of [`OpteeMsgParamTmem`], +/// [`OpteeMsgParamRmem`], [`OpteeMsgParamFmem`], and [`OpteeMsgParamValue`]. +/// Use the typed accessor methods to interpret it. +const OPTEE_MSG_PARAM_DATA_SIZE: usize = 24; const OPTEE_MSG_ATTR_TYPE_NONE: u8 = 0x0; const OPTEE_MSG_ATTR_TYPE_VALUE_INPUT: u8 = 0x1; @@ -1395,41 +1420,43 @@ pub enum OpteeMsgAttrType { TmemInout = OPTEE_MSG_ATTR_TYPE_TMEM_INOUT, } -#[non_exhaustive] -#[bitfield] -#[derive(Clone, Copy, Default)] -#[repr(C)] -pub struct OpteeMsgAttr { - pub typ: B8, - pub meta: bool, - pub noncontig: bool, - #[skip] - __: B54, +/// Attribute field of [`OpteeMsgParam`]. +/// +/// Wire layout (little-endian u64): +/// - bits \[7:0\] – type (`OPTEE_MSG_ATTR_TYPE_*`) +/// - bit 8 – meta +/// - bit 9 – noncontig +/// - bits \[63:10\] – reserved (zero) +#[derive(Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)] +#[repr(transparent)] +pub struct OpteeMsgAttr(u64); + +impl OpteeMsgAttr { + /// Returns the attribute type (bits 0–7). + #[allow(clippy::cast_possible_truncation)] + pub fn typ(&self) -> u8 { + self.0 as u8 + } + + /// Returns `true` when the meta bit (bit 8) is set. + pub fn meta(&self) -> bool { + self.0 & (1 << 8) != 0 + } + + /// Returns `true` when the noncontig bit (bit 9) is set. + pub fn noncontig(&self) -> bool { + self.0 & (1 << 9) != 0 + } } -#[derive(Clone, Copy)] +#[derive(Clone, Copy, FromBytes, IntoBytes, Immutable, KnownLayout)] #[repr(C)] pub struct OpteeMsgParam { attr: OpteeMsgAttr, - u: OpteeMsgParamUnion, + data: [u8; OPTEE_MSG_PARAM_DATA_SIZE], } impl OpteeMsgParam { - /// Deserialize an `OpteeMsgParam` from a byte slice of exactly `size_of::()` bytes. - pub fn from_bytes(bytes: &[u8]) -> Option { - if bytes.len() < size_of::() { - return None; - } - // SAFETY: OpteeMsgParam is repr(C) and 32 bytes, and we've checked the input slice is large enough. - Some(unsafe { core::ptr::read_unaligned(bytes.as_ptr().cast::()) }) - } - - /// Serialize this `OpteeMsgParam` as a byte slice of `size_of::()` bytes. - pub fn as_bytes(&self) -> &[u8; size_of::()] { - // SAFETY: OpteeMsgParam is repr(C) and 32 bytes. Reinterpreting as bytes is safe. - unsafe { &*(&raw const *self).cast::<[u8; size_of::()]>() } - } - pub fn attr_type(&self) -> OpteeMsgAttrType { OpteeMsgAttrType::try_from(self.attr.typ()).unwrap_or(OpteeMsgAttrType::None) } @@ -1440,7 +1467,7 @@ impl OpteeMsgParam { | OPTEE_MSG_ATTR_TYPE_TMEM_OUTPUT | OPTEE_MSG_ATTR_TYPE_TMEM_INOUT ) { - Some(unsafe { self.u.tmem }) + OpteeMsgParamTmem::read_from_bytes(&self.data).ok() } else { None } @@ -1452,7 +1479,7 @@ impl OpteeMsgParam { | OPTEE_MSG_ATTR_TYPE_RMEM_OUTPUT | OPTEE_MSG_ATTR_TYPE_RMEM_INOUT ) { - Some(unsafe { self.u.rmem }) + OpteeMsgParamRmem::read_from_bytes(&self.data).ok() } else { None } @@ -1464,7 +1491,7 @@ impl OpteeMsgParam { | OPTEE_MSG_ATTR_TYPE_RMEM_OUTPUT | OPTEE_MSG_ATTR_TYPE_RMEM_INOUT ) { - Some(unsafe { self.u.fmem }) + OpteeMsgParamFmem::read_from_bytes(&self.data).ok() } else { None } @@ -1476,7 +1503,7 @@ impl OpteeMsgParam { | OPTEE_MSG_ATTR_TYPE_VALUE_OUTPUT | OPTEE_MSG_ATTR_TYPE_VALUE_INOUT ) { - Some(unsafe { self.u.value }) + OpteeMsgParamValue::read_from_bytes(&self.data).ok() } else { None } @@ -1643,7 +1670,7 @@ impl OpteeMsgArgs { if index >= self.num_params as usize { Err(OpteeSmcReturnCode::ENotAvail) } else { - self.params[index].u.value = value; + self.params[index].data.copy_from_slice(value.as_bytes()); Ok(()) } } @@ -1658,8 +1685,9 @@ impl OpteeMsgArgs { if index >= self.num_params as usize { Err(OpteeSmcReturnCode::ENotAvail) } else { - // rmem.size and tmem.size are at the same offset as value.b in the union - self.params[index].u.rmem.size = size; + // rmem.size and tmem.size are at byte offset 8 in the 24-byte data, + // the same position as value.b in the original union. + self.params[index].data[8..16].copy_from_slice(&size.to_le_bytes()); Ok(()) } } @@ -1708,13 +1736,14 @@ impl OpteeMsgArgs { let mut params = [OpteeMsgParam { attr: OpteeMsgAttr::default(), - u: OpteeMsgParamUnion { octets: [0u8; 24] }, + data: [0u8; OPTEE_MSG_PARAM_DATA_SIZE], }; Self::MAX_PARAMS]; for (i, param) in params.iter_mut().enumerate().take(num) { let offset = i * size_of::(); let param_bytes = &raw_params[offset..offset + size_of::()]; - *param = OpteeMsgParam::from_bytes(param_bytes).ok_or(OpteeSmcReturnCode::EBadAddr)?; + *param = OpteeMsgParam::read_from_bytes(param_bytes) + .map_err(|_| OpteeSmcReturnCode::EBadAddr)?; } Ok(Self { @@ -1838,13 +1867,14 @@ impl OpteeRpcArgs { let mut params = [OpteeMsgParam { attr: OpteeMsgAttr::default(), - u: OpteeMsgParamUnion { octets: [0u8; 24] }, + data: [0u8; OPTEE_MSG_PARAM_DATA_SIZE], }; Self::MAX_PARAMS]; for (i, param) in params.iter_mut().enumerate().take(num) { let offset = i * size_of::(); let param_bytes = &raw_params[offset..offset + size_of::()]; - *param = OpteeMsgParam::from_bytes(param_bytes).ok_or(OpteeSmcReturnCode::EBadAddr)?; + *param = OpteeMsgParam::read_from_bytes(param_bytes) + .map_err(|_| OpteeSmcReturnCode::EBadAddr)?; } Ok(Self { From 2effbc3657419e9f762f6bb8a26f08cfaf201dff Mon Sep 17 00:00:00 2001 From: Sangho Lee Date: Fri, 13 Feb 2026 00:38:02 +0000 Subject: [PATCH 4/9] clippy --- litebox_common_optee/src/lib.rs | 40 ++++++++++++--------------------- 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/litebox_common_optee/src/lib.rs b/litebox_common_optee/src/lib.rs index 34d09a91d..e8bc50bcc 100644 --- a/litebox_common_optee/src/lib.rs +++ b/litebox_common_optee/src/lib.rs @@ -2328,13 +2328,11 @@ mod tests { }; let mut params_in = vec![0u8; 3 * size_of::()]; for (i, byte) in params_in.iter_mut().enumerate() { - *byte = (i & 0xFF) as u8; + *byte = u8::try_from(i % 256).unwrap(); } - let msg_args = match OpteeMsgArgs::from_header_and_raw_params(&header, ¶ms_in) { - Ok(m) => m, - Err(_) => panic!("expected Ok"), - }; + let msg_args = + OpteeMsgArgs::from_header_and_raw_params(&header, ¶ms_in).expect("expected Ok"); assert_eq!(msg_args.func, 0x1234); assert_eq!(msg_args.session, 0xABCD); assert_eq!(msg_args.num_params, 3); @@ -2346,10 +2344,9 @@ mod tests { assert_eq!(header_out.num_params, 3); let mut params_out = vec![0u8; 3 * size_of::()]; - let written = match msg_args.write_raw_params(&mut params_out) { - Ok(n) => n, - Err(_) => panic!("expected Ok"), - }; + let written = msg_args + .write_raw_params(&mut params_out) + .expect("expected Ok"); assert_eq!(written, 3 * size_of::()); assert_eq!(¶ms_out[..written], ¶ms_in[..written]); } @@ -2366,10 +2363,7 @@ mod tests { ret_origin: 0, num_params: 0, }; - let msg_args = match OpteeMsgArgs::from_header_and_raw_params(&header, &[]) { - Ok(m) => m, - Err(_) => panic!("expected Ok"), - }; + let msg_args = OpteeMsgArgs::from_header_and_raw_params(&header, &[]).expect("expected Ok"); assert_eq!(msg_args.num_params, 0); assert_eq!(msg_args.cancel_id, 42); @@ -2377,10 +2371,7 @@ mod tests { assert_eq!(header_out.num_params, 0); let mut buf = [0u8; 0]; - let written = match msg_args.write_raw_params(&mut buf) { - Ok(n) => n, - Err(_) => panic!("expected Ok"), - }; + let written = msg_args.write_raw_params(&mut buf).expect("expected Ok"); assert_eq!(written, 0); } @@ -2399,17 +2390,14 @@ mod tests { num_params: 6, // MAX_PARAMS }; let params = vec![0xABu8; 6 * size_of::()]; - let msg_args = match OpteeMsgArgs::from_header_and_raw_params(&header, ¶ms) { - Ok(m) => m, - Err(_) => panic!("expected Ok"), - }; + let msg_args = + OpteeMsgArgs::from_header_and_raw_params(&header, ¶ms).expect("expected Ok"); assert_eq!(msg_args.num_params, 6); let mut params_out = vec![0u8; 6 * size_of::()]; - let written = match msg_args.write_raw_params(&mut params_out) { - Ok(n) => n, - Err(_) => panic!("expected Ok"), - }; + let written = msg_args + .write_raw_params(&mut params_out) + .expect("expected Ok"); assert_eq!(written, 6 * size_of::()); assert_eq!(params_out, params); } @@ -2431,7 +2419,7 @@ mod tests { }; let mut params_in = vec![0u8; 2 * size_of::()]; for (i, byte) in params_in.iter_mut().enumerate() { - *byte = (i & 0xFF) as u8; + *byte = u8::try_from(i % 256).unwrap(); } let rpc_args = OpteeRpcArgs::from_header_and_raw_params(&header, ¶ms_in) From fe955fc363e413180a948a265ecb76920b10a1b2 Mon Sep 17 00:00:00 2001 From: Sangho Lee Date: Fri, 13 Feb 2026 00:53:26 +0000 Subject: [PATCH 5/9] refactoring --- litebox_common_optee/src/lib.rs | 108 +++----------------------------- 1 file changed, 8 insertions(+), 100 deletions(-) diff --git a/litebox_common_optee/src/lib.rs b/litebox_common_optee/src/lib.rs index e8bc50bcc..837d780fc 100644 --- a/litebox_common_optee/src/lib.rs +++ b/litebox_common_optee/src/lib.rs @@ -1575,7 +1575,7 @@ pub struct OpteeMsgArgsHeader { /// `optee_msg_arg` from `optee_os/core/include/optee_msg.h` /// OP-TEE message argument structure that the normal world (or VTL0) OP-TEE driver and OP-TEE OS use to -/// exchange messages. This data structure is used for carrying RPC information as well. +/// exchange messages. #[derive(Clone, Copy)] #[repr(C)] pub struct OpteeMsgArgs { @@ -1594,12 +1594,9 @@ pub struct OpteeMsgArgs { pub ret: TeeResult, /// Origin of the return value pub ret_origin: TeeOrigin, - /// Number of parameters contained in `params`. - /// - /// For main args: includes both meta parameters (e.g. TA UUID, client identity for - /// `OpenSession`) and client parameters. Typical values: 0 (close/cancel), + /// Number of parameters contained in `params`. It includes both meta parameters (e.g. TA UUID, + /// client identity for `OpenSession`) and client parameters. Typical values: 0 (close/cancel), /// `TEE_NUM_PARAMS` (invoke), `TEE_NUM_PARAMS + 2` (open_session). - /// For RPC args: the count negotiated during `EXCHANGE_CAPABILITIES`. pub num_params: u32, /// Parameters to be passed to/from the secure world. If `cmd == OpenSession`, the first /// two params are meta parameters (TA UUID and client identity, marked with @@ -1608,7 +1605,7 @@ pub struct OpteeMsgArgs { /// The C `struct optee_msg_arg` uses a flexible array member `params[]` whose length /// is determined by `num_params`. We fix it to `TEE_NUM_PARAMS + 2` (= `MAX_PARAMS`) /// to match the Linux driver's `MAX_ARG_PARAM_COUNT`. The variable-length wire format - /// is handled by the read/write proxy in `msg_handler.rs` and `write_msg_args_to_normal_world`. + /// is handled by the read/write proxy and `write_msg_args_to_normal_world`. pub params: [OpteeMsgParam; TEE_NUM_PARAMS + 2], } @@ -1695,24 +1692,12 @@ impl OpteeMsgArgs { /// Maximum number of parameters that `OpteeMsgArgs` can hold. /// /// This is `TEE_NUM_PARAMS + 2` = 6, matching the Linux driver's `MAX_ARG_PARAM_COUNT`. - /// The `+2` accounts for the meta parameters (TA UUID + client identity) that the - /// Linux driver prepends for `OpenSession` commands. For `InvokeCommand`, only - /// `TEE_NUM_PARAMS` (4) client parameters are used. For RPC args, the actual - /// `num_params` is negotiated during `EXCHANGE_CAPABILITIES` (typically 1-2 in - /// upstream OP-TEE), but we cap it to this same array size. pub const MAX_PARAMS: usize = TEE_NUM_PARAMS + 2; /// Construct an `OpteeMsgArgs` from a zerocopy header and a raw parameter byte slice. /// /// `raw_params` must contain at least `header.num_params * size_of::()` bytes. - /// `header.num_params` must not exceed `MAX_PARAMS` (6). Note that `num_params` counts - /// **all** entries in the `params[]` array, including meta parameters — e.g. for - /// `OpenSession`, the Linux driver sets `num_params = TEE_NUM_PARAMS + 2` (4 client - /// params + 2 meta params for TA UUID and client identity). For RPC args, `num_params` - /// is the count negotiated during `EXCHANGE_CAPABILITIES`. - /// - /// This bounds check is critical defense-in-depth against CVE-2022-46152-style attacks - /// where an unvalidated `num_params` from normal world causes OOB access on fixed arrays. + /// `header.num_params` must not exceed `MAX_PARAMS` (6). pub fn from_header_and_raw_params( header: &OpteeMsgArgsHeader, raw_params: &[u8], @@ -1776,7 +1761,6 @@ impl OpteeMsgArgs { /// Serialize the params portion (up to `num_params`) as raw bytes into `buf`. /// /// Re-validates `num_params <= MAX_PARAMS` before using as loop bound. - /// See CVE-2022-46152 for why this defense-in-depth matters. pub fn write_raw_params(&self, buf: &mut [u8]) -> Result { let num = self.num_params as usize; if num > Self::MAX_PARAMS { @@ -2351,57 +2335,6 @@ mod tests { assert_eq!(¶ms_out[..written], ¶ms_in[..written]); } - #[test] - fn test_zero_params() { - let header = OpteeMsgArgsHeader { - cmd: 3, // Cancel - func: 0, - session: 1, - cancel_id: 42, - pad: 0, - ret: 0, - ret_origin: 0, - num_params: 0, - }; - let msg_args = OpteeMsgArgs::from_header_and_raw_params(&header, &[]).expect("expected Ok"); - assert_eq!(msg_args.num_params, 0); - assert_eq!(msg_args.cancel_id, 42); - - let header_out = msg_args.to_header(); - assert_eq!(header_out.num_params, 0); - - let mut buf = [0u8; 0]; - let written = msg_args.write_raw_params(&mut buf).expect("expected Ok"); - assert_eq!(written, 0); - } - - #[test] - fn test_max_params() { - use alloc::vec; - - let header = OpteeMsgArgsHeader { - cmd: 0, // OpenSession - func: 0, - session: 0, - cancel_id: 0, - pad: 0, - ret: 0, - ret_origin: 0, - num_params: 6, // MAX_PARAMS - }; - let params = vec![0xABu8; 6 * size_of::()]; - let msg_args = - OpteeMsgArgs::from_header_and_raw_params(&header, ¶ms).expect("expected Ok"); - assert_eq!(msg_args.num_params, 6); - - let mut params_out = vec![0u8; 6 * size_of::()]; - let written = msg_args - .write_raw_params(&mut params_out) - .expect("expected Ok"); - assert_eq!(written, 6 * size_of::()); - assert_eq!(params_out, params); - } - #[test] fn test_optee_rpc_args_roundtrip() { use alloc::vec; @@ -2444,12 +2377,10 @@ mod tests { #[test] fn test_rpc_args_rejects_main_cmd() { - // OpenSession = 0 is a main command, but also happens to be LoadTa = 0 for RPC. - // InvokeCommand = 1 maps to Rpmb = 1 in RPC. So to test rejection we need a - // cmd value that's valid for main but not for RPC. RegisterShm = 4 has no - // RPC counterpart. + // Pick a cmd value that lies in the gap between Suspend (5) and ShmAlloc (28), + // so it is not a valid OpteeRpcCommand variant. let header = OpteeMsgArgsHeader { - cmd: 4, // RegisterShm — not a valid OpteeRpcCommand + cmd: 6, // not a valid OpteeRpcCommand func: 0, session: 0, cancel_id: 0, @@ -2460,27 +2391,4 @@ mod tests { }; assert!(OpteeRpcArgs::from_header_and_raw_params(&header, &[]).is_err()); } - - #[test] - fn test_rpc_args_reserved_fields_zeroed() { - let header = OpteeMsgArgsHeader { - cmd: 29, // ShmFree - func: 0xDEAD, - session: 0xBEEF, - cancel_id: 0xCAFE, - pad: 0, - ret: 0, - ret_origin: 0, - num_params: 0, - }; - let rpc_args = - OpteeRpcArgs::from_header_and_raw_params(&header, &[]).expect("should parse"); - assert_eq!(rpc_args.cmd, OpteeRpcCommand::ShmFree); - - // Reserved fields should be zeroed regardless of header input - let hdr_out = rpc_args.to_header(); - assert_eq!(hdr_out.func, 0); - assert_eq!(hdr_out.session, 0); - assert_eq!(hdr_out.cancel_id, 0); - } } From e2e91e27fd2d5fa9eba83843e26242e8c4776d27 Mon Sep 17 00:00:00 2001 From: Sangho Lee Date: Fri, 13 Feb 2026 02:58:50 +0000 Subject: [PATCH 6/9] Non TA MsgArgs --- Cargo.lock | 38 +-------- litebox_platform_lvbs/Cargo.toml | 4 +- litebox_runner_lvbs/Cargo.toml | 1 - litebox_runner_lvbs/src/lib.rs | 115 +++++++++++++++++++------- litebox_shim_optee/Cargo.toml | 8 -- litebox_shim_optee/src/msg_handler.rs | 21 +++-- 6 files changed, 100 insertions(+), 87 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e579f5a46..d338c4dc0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -445,17 +445,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "delegate" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "780eb241654bf097afb00fc5f054a09b687dad862e485fdcf8399bb056565370" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "der" version = "0.7.10" @@ -519,30 +508,12 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" -[[package]] -name = "elf" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b" - [[package]] name = "elf" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55dd888a213fc57e957abf2aa305ee3e8a28dbe05687a251f33b637cd46b0070" -[[package]] -name = "elf_loader" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a0da8db95cff71e500b3d7015c2441a4eb628e0df788b23d1b8d1243314342" -dependencies = [ - "bitflags 2.9.4", - "cfg-if", - "delegate", - "elf 0.7.4", -] - [[package]] name = "encode_unicode" version = "1.0.0" @@ -840,7 +811,7 @@ dependencies = [ "bitfield", "bitflags 2.9.4", "cfg-if", - "elf 0.8.0", + "elf", "int-enum", "litebox", "syscalls", @@ -853,7 +824,7 @@ name = "litebox_common_optee" version = "0.1.0" dependencies = [ "bitflags 2.9.4", - "elf 0.8.0", + "elf", "litebox", "litebox_common_linux", "num_enum", @@ -908,7 +879,7 @@ dependencies = [ "cms", "const-oid", "digest", - "elf 0.8.0", + "elf", "hashbrown", "libc", "litebox", @@ -1069,8 +1040,7 @@ dependencies = [ "bitvec", "cfg-if", "ctr", - "elf 0.8.0", - "elf_loader", + "elf", "hashbrown", "litebox", "litebox_common_linux", diff --git a/litebox_platform_lvbs/Cargo.toml b/litebox_platform_lvbs/Cargo.toml index 190f28b13..11d0cdb20 100644 --- a/litebox_platform_lvbs/Cargo.toml +++ b/litebox_platform_lvbs/Cargo.toml @@ -17,7 +17,6 @@ spin = { version = "0.10.0", default-features = false, features = [ "once", "rwlock", ] } -libc = "0.2.169" arrayvec = { version = "0.7.6", default-features = false } rangemap = { version = "1.5.1", features = ["const_fn"] } thiserror = { version = "2.0.6", default-features = false } @@ -42,6 +41,9 @@ zerocopy = { version = "0.8", default-features = false, features = ["derive"] } [target.'cfg(target_arch = "x86_64")'.dependencies] x86_64 = { version = "0.15.2", default-features = false, features = ["instructions"] } +[dev-dependencies] +libc = "0.2.177" + [features] default = ["optee_syscall"] optee_syscall = [] diff --git a/litebox_runner_lvbs/Cargo.toml b/litebox_runner_lvbs/Cargo.toml index 6d6dbedb4..b2f4cf88e 100644 --- a/litebox_runner_lvbs/Cargo.toml +++ b/litebox_runner_lvbs/Cargo.toml @@ -16,7 +16,6 @@ once_cell = { version = "1.21.3", default-features = false, features = ["race", num_enum = { version = "0.7.4", default-features = false } zerocopy = { version = "0.8", default-features = false } - [target.'cfg(target_arch = "x86_64")'.dependencies] x86_64 = { version = "0.15.2", default-features = false, features = ["instructions"] } diff --git a/litebox_runner_lvbs/src/lib.rs b/litebox_runner_lvbs/src/lib.rs index 58f99cd64..0fa01b317 100644 --- a/litebox_runner_lvbs/src/lib.rs +++ b/litebox_runner_lvbs/src/lib.rs @@ -315,17 +315,35 @@ fn optee_smc_handler(smc_args_addr: usize) -> OpteeSmcArgs { return *smc_args; }; match smc_result { - OpteeSmcResult::CallWithArg { - msg_args, - rpc_args: _rpc_args, - } => { + OpteeSmcResult::CallWithArg { msg_args, rpc_args } => { let mut msg_args = *msg_args; + let rpc_args = rpc_args.map(|b| *b); debug_serial_println!("OP-TEE SMC with MsgArgs Command: {:?}", msg_args.cmd); let result = match msg_args.cmd { - OpenSession => handle_open_session(&mut msg_args, msg_args_phys_addr), - InvokeCommand => handle_invoke_command(&mut msg_args, msg_args_phys_addr), - CloseSession => handle_close_session(&mut msg_args, msg_args_phys_addr), - _ => handle_optee_msg_args(&msg_args), + OpenSession => { + handle_open_session(&mut msg_args, msg_args_phys_addr, rpc_args.as_ref()) + } + InvokeCommand => { + handle_invoke_command(&mut msg_args, msg_args_phys_addr, rpc_args.as_ref()) + } + CloseSession => { + handle_close_session(&mut msg_args, msg_args_phys_addr, rpc_args.as_ref()) + } + _ => { + let r = handle_optee_msg_args(&msg_args); + if r.is_ok() { + msg_args.ret = TeeResult::Success; + } else { + msg_args.ret = TeeResult::BadParameters; + } + msg_args.ret_origin = TeeOrigin::Tee; + let _ = write_non_ta_msg_args_to_normal_world( + &msg_args, + msg_args_phys_addr, + rpc_args.as_ref(), + ); + r + } }; // Always switch back to base page table before returning to VTL0 @@ -354,6 +372,7 @@ fn optee_smc_handler(smc_args_addr: usize) -> OpteeSmcArgs { fn handle_open_session( msg_args: &mut OpteeMsgArgs, msg_args_phys_addr: u64, + rpc_args: Option<&OpteeRpcArgs>, ) -> Result<(), OpteeSmcReturnCode> { let ta_req_info = decode_ta_request(msg_args).map_err(|_| OpteeSmcReturnCode::EBadCmd)?; if ta_req_info.entry_func != UteeEntryFunc::OpenSession { @@ -374,6 +393,7 @@ fn handle_open_session( params, ta_uuid, &ta_req_info, + rpc_args, ) } else { open_session_new_instance( @@ -383,6 +403,7 @@ fn handle_open_session( ta_uuid, client_identity, &ta_req_info, + rpc_args, ) } } @@ -402,6 +423,7 @@ fn open_session_single_instance( params: &[litebox_common_optee::UteeParamOwned], ta_uuid: litebox_common_optee::TeeUuid, ta_req_info: &litebox_shim_optee::msg_handler::TaRequestInfo, + rpc_args: Option<&OpteeRpcArgs>, ) -> Result<(), OpteeSmcReturnCode> { // Use try_lock to avoid spinning - return EThreadLimit if TA is in use // The Linux driver will handle this by waiting and retrying @@ -500,7 +522,7 @@ fn open_session_single_instance( None, // No session ID on failure Some(&ta_params), Some(ta_req_info), - None, + rpc_args, )?; session_manager().remove_single_instance(&ta_uuid); @@ -526,7 +548,7 @@ fn open_session_single_instance( None, // No session ID on failure Some(&ta_params), Some(ta_req_info), - None, + rpc_args, )?; return Ok(()); @@ -544,7 +566,7 @@ fn open_session_single_instance( Some(runner_session_id), Some(&ta_params), Some(ta_req_info), - None, + rpc_args, )?; debug_serial_println!( @@ -566,6 +588,7 @@ fn open_session_new_instance( ta_uuid: litebox_common_optee::TeeUuid, client_identity: Option, ta_req_info: &litebox_shim_optee::msg_handler::TaRequestInfo, + rpc_args: Option<&OpteeRpcArgs>, ) -> Result<(), OpteeSmcReturnCode> { // Check TA instance limit // TODO: consider better resource management strategy @@ -650,7 +673,7 @@ fn open_session_new_instance( None, // No session ID on failure None, Some(ta_req_info), - None, + rpc_args, )?; return Ok(()); @@ -717,7 +740,7 @@ fn open_session_new_instance( None, // No session ID on failure Some(&ta_params), Some(ta_req_info), - None, + rpc_args, )?; // Tear down the page table - no session was registered @@ -751,7 +774,7 @@ fn open_session_new_instance( Some(runner_session_id), Some(&ta_params), Some(ta_req_info), - None, + rpc_args, )?; debug_serial_println!( @@ -773,6 +796,7 @@ fn open_session_new_instance( fn handle_invoke_command( msg_args: &mut OpteeMsgArgs, msg_args_phys_addr: u64, + rpc_args: Option<&OpteeRpcArgs>, ) -> Result<(), OpteeSmcReturnCode> { let ta_req_info = decode_ta_request(msg_args).map_err(|_| OpteeSmcReturnCode::EBadCmd)?; if ta_req_info.entry_func != UteeEntryFunc::InvokeCommand { @@ -869,7 +893,7 @@ fn handle_invoke_command( None, Some(&ta_params), Some(&ta_req_info), - None, + rpc_args, )?; if is_last_session { @@ -902,7 +926,7 @@ fn handle_invoke_command( None, Some(&ta_params), Some(&ta_req_info), - None, + rpc_args, )?; Ok(()) @@ -916,6 +940,7 @@ fn handle_invoke_command( fn handle_close_session( msg_args: &mut OpteeMsgArgs, msg_args_phys_addr: u64, + rpc_args: Option<&OpteeRpcArgs>, ) -> Result<(), OpteeSmcReturnCode> { let ta_req_info = decode_ta_request(msg_args).map_err(|_| OpteeSmcReturnCode::EBadCmd)?; if ta_req_info.entry_func != UteeEntryFunc::CloseSession { @@ -971,7 +996,7 @@ fn handle_close_session( None, None, None, - None, + rpc_args, )?; // Clone the instance Arc before dropping the lock for later cleanup check @@ -1045,21 +1070,6 @@ fn handle_close_session( /// - `TeeOrigin::Tee` is used when the error comes from TEE itself (panic/TARGET_DEAD) /// - `TeeOrigin::TrustedApp` is used when the error comes from the TA /// -/// Write-back layout at `msg_args_phys_addr`: -/// -/// ```text -/// blob[0] blob[main_size] -/// | | -/// v v -/// +--------+-----------+ +--------+-----------+ -/// | main | main | | rpc | rpc | -/// | header | params | | header | params | -/// | 32B | N*32B | | 32B | M*32B | -/// +--------+-----------+ +--------+-----------+ -/// |<--- main_size ---->| |<--- rpc_size ----->| -/// (only if rpc_args is Some) -/// ``` -/// /// # Security Note /// /// This function accesses TA userspace memory via `update_optee_msg_args` to copy out @@ -1132,6 +1142,47 @@ fn write_msg_args_to_normal_world( Ok(()) } +/// Write back `OpteeMsgArgs` (and optional `OpteeRpcArgs`) for non-TA commands +/// (e.g., RegisterShm, UnregisterShm) that don't require TA userspace memory access. +/// +/// Unlike [`write_msg_args_to_normal_world`], this function does not access TA userspace +/// memory and can be called from the base page table context. It simply serializes the +/// msg_args (which should already have `ret` / `ret_origin` set by the caller) and the +/// rpc_args (preserved as-is) back to the normal world physical address. +#[inline] +fn write_non_ta_msg_args_to_normal_world( + msg_args: &OpteeMsgArgs, + msg_args_phys_addr: u64, + rpc_args: Option<&OpteeRpcArgs>, +) -> Result<(), OpteeSmcReturnCode> { + let main_size = optee_msg_arg_total_size(msg_args.num_params); + let total_size = if let Some(rpc) = rpc_args { + main_size + optee_msg_arg_total_size(rpc.num_params) + } else { + main_size + }; + + let mut blob = vec![0u8; total_size]; + + blob[..size_of::()].copy_from_slice(msg_args.to_header().as_bytes()); + msg_args.write_raw_params(&mut blob[size_of::()..main_size])?; + + if let Some(rpc) = rpc_args { + blob[main_size..main_size + size_of::()] + .copy_from_slice(rpc.to_header().as_bytes()); + rpc.write_raw_params(&mut blob[main_size + size_of::()..total_size])?; + } + + let mut ptr = NormalWorldMutPtr::::with_contiguous_pages( + msg_args_phys_addr.truncate(), + total_size, + )?; + // SAFETY: Writing msg_args back to normal world memory at a valid physical address. + // The blob contains the serialized variable-length optee_msg_arg structure(s). + unsafe { ptr.write_slice_at_offset(0, &blob) }?; + Ok(()) +} + // use include_bytes! to include ldelf and (KMPP) TA binaries const LDELF_BINARY: &[u8] = &[0u8; 0]; const TA_BINARY: &[u8] = &[0u8; 0]; diff --git a/litebox_shim_optee/Cargo.toml b/litebox_shim_optee/Cargo.toml index 5ba2ff13c..69eefbdc0 100644 --- a/litebox_shim_optee/Cargo.toml +++ b/litebox_shim_optee/Cargo.toml @@ -22,14 +22,6 @@ spin = { version = "0.10.0", default-features = false, features = ["spin_mutex", thiserror = { version = "2.0.6", default-features = false } zerocopy = { version = "0.8", default-features = false } -[target.'cfg(target_arch = "x86")'.dependencies] -# ELF loader requires us to enable the `rel` dependency on 32-bit to work. -# Enabling it on 64-bit on the other hand is incorrect and would not work. Thus, -# we need to do it as a target-specific dependency. -# -# NOTE: We must keep this in sync with the version above. -elf_loader = { version = "0.12.0", default-features = false, features = ["rel"] } - [features] default = ["platform_lvbs"] platform_linux_userland = ["litebox_platform_multiplex/platform_linux_userland_with_optee_syscall"] diff --git a/litebox_shim_optee/src/msg_handler.rs b/litebox_shim_optee/src/msg_handler.rs index 7636be8ec..6b29838c0 100644 --- a/litebox_shim_optee/src/msg_handler.rs +++ b/litebox_shim_optee/src/msg_handler.rs @@ -64,7 +64,7 @@ fn page_align_up(len: u64) -> u64 { len.next_multiple_of(PAGE_SIZE as u64) } -/// Read `OpteeMsgArgs` from a VTL0 physical address using a single-copy approach. +/// Read `OpteeMsgArgs` (and optional `OpteeRpcArgs`) from a VTL0 physical address. /// /// Copies the maximum possible size in one shot into a private VTL1 buffer, then /// parses entirely from that buffer. This eliminates TOCTOU (double-fetch) issues: @@ -86,16 +86,15 @@ fn page_align_up(len: u64) -> u64 { /// ```text /// phys_addr /// | -/// v main_size main_max -/// |<---------------------->| | -/// +--------+------+--------+--------+------+-----------+ -/// | header |par[0]|par[N-1]| header |par[0]|par[R-1]|xx| -/// | 32B | ... | 32B | 32B | ... | 32B |xx| -/// +--------+------+--------+--------+------+-----------+ -/// |<--- main_size -------->|<--- rpc_size --------->| | -/// | ^ | -/// | RPC starts here main_max + rpc_max -/// |<----- copy_size = main_max + rpc_max ------------->| +/// v +/// +--------+------+--------+--------+------+----------~-+ +/// | header |par[0]|par[N-1]| header |par[0]|par[R-1]|xxx| +/// | 32B | ... | 32B | 32B | ... | 32B |xxx| +/// +--------+------+--------+--------+------+----------~-+ +/// |<--- main_size -------->|<--- rpc_size --------->| | +/// | ^ ^ | +/// | RPC starts here main_max | +/// |<----- copy_size = main_max + rpc_max -------------~>| /// /// N = actual num_params from header (validated <= MAX_PARAMS after copy) /// R = rpc_num_params (negotiated during OPTEE_SMC_EXCHANGE_CAPABILITIES) From 28d0a3603e957b94d121d150c55aa011c6bb2ceb Mon Sep 17 00:00:00 2001 From: Sangho Lee Date: Fri, 13 Feb 2026 23:11:10 +0000 Subject: [PATCH 7/9] addressed feedbacks --- litebox_common_optee/src/lib.rs | 75 +++++++++++++-------------- litebox_shim_optee/src/msg_handler.rs | 28 +++++----- 2 files changed, 49 insertions(+), 54 deletions(-) diff --git a/litebox_common_optee/src/lib.rs b/litebox_common_optee/src/lib.rs index 837d780fc..ffd545500 100644 --- a/litebox_common_optee/src/lib.rs +++ b/litebox_common_optee/src/lib.rs @@ -404,10 +404,10 @@ pub struct UteeParams { /// Number of TEE parameters to be passed to TAs. const TEE_NUM_PARAMS: usize = 4; -/// Number of RPC parameters that LiteBox defined and reported to the normal-world +/// Number of RPC parameters that the OP-TEE Shim defined and reported to the normal-world /// Linux kernel driver during `EXCHANGE_CAPABILITIES`. The Linux kernel driver is /// expected to allocate a shared buffer for this number of parameters. -pub const NUM_RPC_PARAMS: usize = 4; +const NUM_RPC_PARAMS: usize = 4; /// Packed parameter types for [`UteeParams`]. /// @@ -1283,9 +1283,6 @@ impl TryFrom for UteeEntryFunc { } } -// RPC command IDs from `optee_os/core/include/optee_msg.h`. -// These occupy the `cmd` field of the RPC `optee_msg_arg`, which is a separate namespace -// from `OPTEE_MSG_CMD_*` used for main messaging. const OPTEE_MSG_RPC_CMD_LOAD_TA: u32 = 0; const OPTEE_MSG_RPC_CMD_RPMB: u32 = 1; const OPTEE_MSG_RPC_CMD_FS: u32 = 2; @@ -1296,7 +1293,7 @@ const OPTEE_MSG_RPC_CMD_SHM_ALLOC: u32 = 28; const OPTEE_MSG_RPC_CMD_SHM_FREE: u32 = 29; const OPTEE_MSG_RPC_CMD_NOTIFICATION: u32 = 30; -/// `OPTEE_MSG_RPC_CMD_*` from `optee_os/core/include/optee_msg.h` +/// RPC command IDs from `optee_os/core/include/optee_msg.h` /// /// These are the command IDs used in the `cmd` field of the RPC `optee_msg_arg`. /// They live in a separate namespace from [`OpteeMessageCommand`] (which is for main @@ -1434,7 +1431,7 @@ pub struct OpteeMsgAttr(u64); impl OpteeMsgAttr { /// Returns the attribute type (bits 0–7). #[allow(clippy::cast_possible_truncation)] - pub fn typ(&self) -> u8 { + pub fn attr_type(&self) -> u8 { self.0 as u8 } @@ -1458,11 +1455,11 @@ pub struct OpteeMsgParam { impl OpteeMsgParam { pub fn attr_type(&self) -> OpteeMsgAttrType { - OpteeMsgAttrType::try_from(self.attr.typ()).unwrap_or(OpteeMsgAttrType::None) + OpteeMsgAttrType::try_from(self.attr.attr_type()).unwrap_or(OpteeMsgAttrType::None) } pub fn get_param_tmem(&self) -> Option { if matches!( - self.attr.typ(), + self.attr.attr_type(), OPTEE_MSG_ATTR_TYPE_TMEM_INPUT | OPTEE_MSG_ATTR_TYPE_TMEM_OUTPUT | OPTEE_MSG_ATTR_TYPE_TMEM_INOUT @@ -1474,7 +1471,7 @@ impl OpteeMsgParam { } pub fn get_param_rmem(&self) -> Option { if matches!( - self.attr.typ(), + self.attr.attr_type(), OPTEE_MSG_ATTR_TYPE_RMEM_INPUT | OPTEE_MSG_ATTR_TYPE_RMEM_OUTPUT | OPTEE_MSG_ATTR_TYPE_RMEM_INOUT @@ -1486,7 +1483,7 @@ impl OpteeMsgParam { } pub fn get_param_fmem(&self) -> Option { if matches!( - self.attr.typ(), + self.attr.attr_type(), OPTEE_MSG_ATTR_TYPE_RMEM_INPUT | OPTEE_MSG_ATTR_TYPE_RMEM_OUTPUT | OPTEE_MSG_ATTR_TYPE_RMEM_INOUT @@ -1498,7 +1495,7 @@ impl OpteeMsgParam { } pub fn get_param_value(&self) -> Option { if matches!( - self.attr.typ(), + self.attr.attr_type(), OPTEE_MSG_ATTR_TYPE_VALUE_INPUT | OPTEE_MSG_ATTR_TYPE_VALUE_OUTPUT | OPTEE_MSG_ATTR_TYPE_VALUE_INOUT @@ -1522,14 +1519,14 @@ impl OpteeMsgParam { /// /// # Safety invariant /// -/// Callers must ensure `num_params` has been validated against `OpteeMsgArgs::MAX_PARAMS`. +/// Callers must ensure `num_params` has been validated against `OpteeMsgArgs::MAX_ARG_PARAM_COUNT`. /// An unvalidated `num_params` from normal world memory could produce an oversized result, /// leading to out-of-bounds access on fixed-size arrays. /// See CVE-2022-46152 (OP-TEE OOB via unvalidated `num_params`). pub const fn optee_msg_arg_total_size(num_params: u32) -> usize { debug_assert!( - num_params as usize <= OpteeMsgArgs::MAX_PARAMS, - "optee_msg_arg_total_size: num_params exceeds MAX_PARAMS" + num_params as usize <= OpteeMsgArgs::MAX_ARG_PARAM_COUNT, + "optee_msg_arg_total_size: num_params exceeds MAX_ARG_PARAM_COUNT" ); core::mem::size_of::() + core::mem::size_of::() * num_params as usize @@ -1603,7 +1600,7 @@ pub struct OpteeMsgArgs { /// `OPTEE_MSG_ATTR_META`) and are not delivered to the TA. /// /// The C `struct optee_msg_arg` uses a flexible array member `params[]` whose length - /// is determined by `num_params`. We fix it to `TEE_NUM_PARAMS + 2` (= `MAX_PARAMS`) + /// is determined by `num_params`. We fix it to `TEE_NUM_PARAMS + 2` (= `MAX_ARG_PARAM_COUNT`) /// to match the Linux driver's `MAX_ARG_PARAM_COUNT`. The variable-length wire format /// is handled by the read/write proxy and `write_msg_args_to_normal_world`. pub params: [OpteeMsgParam; TEE_NUM_PARAMS + 2], @@ -1692,22 +1689,21 @@ impl OpteeMsgArgs { /// Maximum number of parameters that `OpteeMsgArgs` can hold. /// /// This is `TEE_NUM_PARAMS + 2` = 6, matching the Linux driver's `MAX_ARG_PARAM_COUNT`. - pub const MAX_PARAMS: usize = TEE_NUM_PARAMS + 2; + pub const MAX_ARG_PARAM_COUNT: usize = TEE_NUM_PARAMS + 2; /// Construct an `OpteeMsgArgs` from a zerocopy header and a raw parameter byte slice. /// /// `raw_params` must contain at least `header.num_params * size_of::()` bytes. - /// `header.num_params` must not exceed `MAX_PARAMS` (6). + /// `header.num_params` must not exceed `MAX_ARG_PARAM_COUNT` (6). pub fn from_header_and_raw_params( header: &OpteeMsgArgsHeader, raw_params: &[u8], ) -> Result { let num = header.num_params as usize; - if num > Self::MAX_PARAMS { + if num > Self::MAX_ARG_PARAM_COUNT { return Err(OpteeSmcReturnCode::EBadCmd); } - let needed = num * size_of::(); - if raw_params.len() < needed { + if raw_params.len() < num * size_of::() { return Err(OpteeSmcReturnCode::EBadAddr); } @@ -1722,7 +1718,7 @@ impl OpteeMsgArgs { let mut params = [OpteeMsgParam { attr: OpteeMsgAttr::default(), data: [0u8; OPTEE_MSG_PARAM_DATA_SIZE], - }; Self::MAX_PARAMS]; + }; Self::MAX_ARG_PARAM_COUNT]; for (i, param) in params.iter_mut().enumerate().take(num) { let offset = i * size_of::(); @@ -1760,14 +1756,14 @@ impl OpteeMsgArgs { /// Serialize the params portion (up to `num_params`) as raw bytes into `buf`. /// - /// Re-validates `num_params <= MAX_PARAMS` before using as loop bound. + /// Re-validates `num_params <= MAX_ARG_PARAM_COUNT` before using as loop bound. pub fn write_raw_params(&self, buf: &mut [u8]) -> Result { let num = self.num_params as usize; - if num > Self::MAX_PARAMS { + if num > Self::MAX_ARG_PARAM_COUNT { return Err(OpteeSmcReturnCode::EBadCmd); } - let needed = num * size_of::(); - if buf.len() < needed { + let out_buf_len = num * size_of::(); + if buf.len() < out_buf_len { return Err(OpteeSmcReturnCode::EBadAddr); } for i in 0..num { @@ -1775,7 +1771,7 @@ impl OpteeMsgArgs { buf[offset..offset + size_of::()] .copy_from_slice(self.params[i].as_bytes()); } - Ok(needed) + Ok(out_buf_len) } } @@ -1814,15 +1810,15 @@ pub struct OpteeRpcArgs { pub ret_origin: TeeOrigin, /// Number of parameters in `params`, negotiated during `EXCHANGE_CAPABILITIES`. pub num_params: u32, - /// RPC parameters. Fixed to [`NUM_RPC_PARAMS`] entries. - pub params: [OpteeMsgParam; Self::MAX_PARAMS], + /// RPC parameters. Fixed to `NUM_RPC_PARAMS` entries. + pub params: [OpteeMsgParam; Self::MAX_RPC_ARG_PARAM_COUNT], } impl OpteeRpcArgs { /// Maximum number of RPC parameters this struct can hold. /// - /// This is [`NUM_RPC_PARAMS`], the count negotiated during `EXCHANGE_CAPABILITIES`. - pub const MAX_PARAMS: usize = NUM_RPC_PARAMS; + /// This is `NUM_RPC_PARAMS`, the count negotiated during `EXCHANGE_CAPABILITIES`. + pub const MAX_RPC_ARG_PARAM_COUNT: usize = NUM_RPC_PARAMS; /// Construct an `OpteeRpcArgs` from a zerocopy header and a raw parameter byte slice. /// @@ -1834,11 +1830,10 @@ impl OpteeRpcArgs { raw_params: &[u8], ) -> Result { let num = header.num_params as usize; - if num > Self::MAX_PARAMS { + if num > Self::MAX_RPC_ARG_PARAM_COUNT { return Err(OpteeSmcReturnCode::EBadCmd); } - let needed = num * size_of::(); - if raw_params.len() < needed { + if raw_params.len() < num * size_of::() { return Err(OpteeSmcReturnCode::EBadAddr); } @@ -1852,7 +1847,7 @@ impl OpteeRpcArgs { let mut params = [OpteeMsgParam { attr: OpteeMsgAttr::default(), data: [0u8; OPTEE_MSG_PARAM_DATA_SIZE], - }; Self::MAX_PARAMS]; + }; Self::MAX_RPC_ARG_PARAM_COUNT]; for (i, param) in params.iter_mut().enumerate().take(num) { let offset = i * size_of::(); @@ -1893,11 +1888,11 @@ impl OpteeRpcArgs { /// Serialize the params portion (up to `num_params`) as raw bytes into `buf`. pub fn write_raw_params(&self, buf: &mut [u8]) -> Result { let num = self.num_params as usize; - if num > Self::MAX_PARAMS { + if num > Self::MAX_RPC_ARG_PARAM_COUNT { return Err(OpteeSmcReturnCode::EBadCmd); } - let needed = num * size_of::(); - if buf.len() < needed { + let out_buf_len = num * size_of::(); + if buf.len() < out_buf_len { return Err(OpteeSmcReturnCode::EBadAddr); } for i in 0..num { @@ -1905,7 +1900,7 @@ impl OpteeRpcArgs { buf[offset..offset + size_of::()] .copy_from_slice(self.params[i].as_bytes()); } - Ok(needed) + Ok(out_buf_len) } /// Access a parameter by index with bounds checking against `num_params`. @@ -2274,7 +2269,7 @@ mod tests { pad: 0, ret: 0, ret_origin: 0, - num_params: 7, // exceeds MAX_PARAMS = 6 + num_params: 7, // exceeds MAX_ARG_PARAM_COUNT = 6 }; let result = OpteeMsgArgs::from_header_and_raw_params(&header, &[0u8; 224]); assert!(result.is_err()); diff --git a/litebox_shim_optee/src/msg_handler.rs b/litebox_shim_optee/src/msg_handler.rs index 6b29838c0..24ce10314 100644 --- a/litebox_shim_optee/src/msg_handler.rs +++ b/litebox_shim_optee/src/msg_handler.rs @@ -22,11 +22,11 @@ use hashbrown::HashMap; use litebox::{mm::linux::PAGE_SIZE, utils::TruncateExt}; use litebox_common_linux::vmap::{PhysPageAddr, PhysPointerError}; use litebox_common_optee::{ - NUM_RPC_PARAMS, OpteeMessageCommand, OpteeMsgArgs, OpteeMsgArgsHeader, OpteeMsgAttrType, - OpteeMsgParamRmem, OpteeMsgParamTmem, OpteeMsgParamValue, OpteeRpcArgs, - OpteeSecureWorldCapabilities, OpteeSmcArgs, OpteeSmcFunction, OpteeSmcResult, - OpteeSmcReturnCode, TeeIdentity, TeeLogin, TeeOrigin, TeeParamType, TeeResult, TeeUuid, - UteeEntryFunc, UteeParamOwned, UteeParams, optee_msg_arg_total_size, + OpteeMessageCommand, OpteeMsgArgs, OpteeMsgArgsHeader, OpteeMsgAttrType, OpteeMsgParamRmem, + OpteeMsgParamTmem, OpteeMsgParamValue, OpteeRpcArgs, OpteeSecureWorldCapabilities, + OpteeSmcArgs, OpteeSmcFunction, OpteeSmcResult, OpteeSmcReturnCode, TeeIdentity, TeeLogin, + TeeOrigin, TeeParamType, TeeResult, TeeUuid, UteeEntryFunc, UteeParamOwned, UteeParams, + optee_msg_arg_total_size, }; use once_cell::race::OnceBox; use zerocopy::FromBytes; @@ -72,14 +72,14 @@ fn page_align_up(len: u64) -> u64 { /// our private copy after the single read. /// /// The copy size is determined by known-good upper bounds, not by untrusted data: -/// - Main args: `optee_msg_arg_total_size(MAX_PARAMS = 6)` = 224 bytes (the Linux +/// - Main args: `optee_msg_arg_total_size(MAX_ARG_PARAM_COUNT = 6)` = 224 bytes (the Linux /// driver always allocates at least this much for the main arg). /// - RPC args (when present): `optee_msg_arg_total_size(rpc_num_params)`, where /// `rpc_num_params` is our own negotiated value from `EXCHANGE_CAPABILITIES`. /// /// If `has_rpc_arg` is true, expects an appended RPC `optee_msg_arg` immediately after /// the main one at offset `optee_msg_arg_total_size(num_params)` (the *actual* `num_params`, -/// not `MAX_PARAMS`). This matches the Linux driver's layout. +/// not `MAX_ARG_PARAM_COUNT`). This matches the Linux driver's layout. /// /// VTL0 physical memory layout at `phys_addr`: /// @@ -96,7 +96,7 @@ fn page_align_up(len: u64) -> u64 { /// | RPC starts here main_max | /// |<----- copy_size = main_max + rpc_max -------------~>| /// -/// N = actual num_params from header (validated <= MAX_PARAMS after copy) +/// N = actual num_params from header (validated <= MAX_ARG_PARAM_COUNT after copy) /// R = rpc_num_params (negotiated during OPTEE_SMC_EXCHANGE_CAPABILITIES) /// /// We copy main_max + rpc_max bytes (the upper bound), which covers the @@ -111,9 +111,9 @@ pub fn read_optee_msg_args_from_phys( has_rpc_arg: bool, ) -> Result<(Box, Option>), OpteeSmcReturnCode> { // Compute copy size from known-good upper bounds — no untrusted data involved. - let main_max = optee_msg_arg_total_size(OpteeMsgArgs::MAX_PARAMS.truncate()); + let main_max = optee_msg_arg_total_size(OpteeMsgArgs::MAX_ARG_PARAM_COUNT.truncate()); let copy_size = if has_rpc_arg { - main_max + optee_msg_arg_total_size(NUM_RPC_PARAMS.truncate()) + main_max + optee_msg_arg_total_size(OpteeRpcArgs::MAX_RPC_ARG_PARAM_COUNT.truncate()) } else { main_max }; @@ -132,7 +132,7 @@ pub fn read_optee_msg_args_from_phys( .0; // Validate num_params from the snapshot. - if main_header.num_params as usize > OpteeMsgArgs::MAX_PARAMS { + if main_header.num_params as usize > OpteeMsgArgs::MAX_ARG_PARAM_COUNT { return Err(OpteeSmcReturnCode::EBadCmd); } @@ -142,7 +142,7 @@ pub fn read_optee_msg_args_from_phys( // Parse RPC args if present. // The Linux kernel driver places the RPC arg at offset main_size (based on the actual - // num_params, not MAX_PARAMS). Since we copied main_max bytes which is >= main_size, + // num_params, not MAX_ARG_PARAM_COUNT). Since we copied main_max bytes which is >= main_size, // and main_size is computed from our own validated snapshot, this is safe. let rpc_args = if has_rpc_arg { let rpc_blob = &blob[main_size..]; @@ -150,7 +150,7 @@ pub fn read_optee_msg_args_from_phys( .map_err(|_| OpteeSmcReturnCode::EBadAddr)? .0; // Re-validate RPC num_params from the snapshot against our negotiated limit. - if rpc_header.num_params as usize > NUM_RPC_PARAMS { + if rpc_header.num_params as usize > OpteeRpcArgs::MAX_RPC_ARG_PARAM_COUNT { return Err(OpteeSmcReturnCode::EBadCmd); } let rpc_params = &rpc_blob[size_of::()..]; @@ -202,7 +202,7 @@ pub fn handle_optee_smc_args( status: OpteeSmcReturnCode::Ok, capabilities: default_cap, max_notif_value: MAX_NOTIF_VALUE, - data: NUM_RPC_PARAMS, + data: OpteeRpcArgs::MAX_RPC_ARG_PARAM_COUNT, }) } OpteeSmcFunction::DisableShmCache => { From 8328c8fdb5a6ed854f7f0d8cf46d009803642c0b Mon Sep 17 00:00:00 2001 From: Sangho Lee Date: Sun, 15 Feb 2026 03:57:48 +0000 Subject: [PATCH 8/9] revise --- dev_tests/src/ratchet.rs | 1 - litebox_common_optee/src/lib.rs | 74 +++++++---- litebox_runner_lvbs/src/lib.rs | 173 +++++++++++--------------- litebox_shim_optee/Cargo.toml | 2 +- litebox_shim_optee/src/msg_handler.rs | 18 +-- litebox_shim_optee/src/ptr.rs | 54 ++++++-- 6 files changed, 184 insertions(+), 138 deletions(-) diff --git a/dev_tests/src/ratchet.rs b/dev_tests/src/ratchet.rs index 58ea1b154..bbc7c35b3 100644 --- a/dev_tests/src/ratchet.rs +++ b/dev_tests/src/ratchet.rs @@ -73,7 +73,6 @@ fn ratchet_maybe_uninit() -> Result<()> { ("litebox/", 1), ("litebox_platform_linux_userland/", 3), ("litebox_shim_linux/", 5), - ("litebox_shim_optee/", 1), ], |file| { Ok(file diff --git a/litebox_common_optee/src/lib.rs b/litebox_common_optee/src/lib.rs index ffd545500..740f328fe 100644 --- a/litebox_common_optee/src/lib.rs +++ b/litebox_common_optee/src/lib.rs @@ -1287,11 +1287,18 @@ const OPTEE_MSG_RPC_CMD_LOAD_TA: u32 = 0; const OPTEE_MSG_RPC_CMD_RPMB: u32 = 1; const OPTEE_MSG_RPC_CMD_FS: u32 = 2; const OPTEE_MSG_RPC_CMD_GET_TIME: u32 = 3; -const OPTEE_MSG_RPC_CMD_WAIT_QUEUE: u32 = 4; +const OPTEE_MSG_RPC_CMD_NOTIFICATION: u32 = 4; const OPTEE_MSG_RPC_CMD_SUSPEND: u32 = 5; -const OPTEE_MSG_RPC_CMD_SHM_ALLOC: u32 = 28; -const OPTEE_MSG_RPC_CMD_SHM_FREE: u32 = 29; -const OPTEE_MSG_RPC_CMD_NOTIFICATION: u32 = 30; +const OPTEE_MSG_RPC_CMD_SHM_ALLOC: u32 = 6; +const OPTEE_MSG_RPC_CMD_SHM_FREE: u32 = 7; +const OPTEE_MSG_RPC_CMD_GPROF: u32 = 9; +const OPTEE_MSG_RPC_CMD_SOCKET: u32 = 10; +const OPTEE_MSG_RPC_CMD_FTRACE: u32 = 11; +const OPTEE_MSG_RPC_CMD_PLUGIN: u32 = 12; +const OPTEE_MSG_RPC_CMD_I2C_TRANSFER: u32 = 21; +const OPTEE_MSG_RPC_CMD_RPMB_PROBE_RESET: u32 = 22; +const OPTEE_MSG_RPC_CMD_RPMB_PROBE_NEXT: u32 = 23; +const OPTEE_MSG_RPC_CMD_RPMB_PROBE_FRAMES: u32 = 24; /// RPC command IDs from `optee_os/core/include/optee_msg.h` /// @@ -1302,24 +1309,38 @@ const OPTEE_MSG_RPC_CMD_NOTIFICATION: u32 = 30; #[derive(Clone, Copy, Debug, PartialEq, TryFromPrimitive)] #[repr(u32)] pub enum OpteeRpcCommand { - /// Load a Trusted Application binary. + /// Load a TA into memory, defined in tee-supplicant. LoadTa = OPTEE_MSG_RPC_CMD_LOAD_TA, - /// Replay Protected Memory Block (RPMB) access. + /// Reserved Rpmb = OPTEE_MSG_RPC_CMD_RPMB, - /// REE file-system access. + /// REE file-system access, defined in tee-supplicant. Fs = OPTEE_MSG_RPC_CMD_FS, - /// Get REE time. + /// Get time. GetTime = OPTEE_MSG_RPC_CMD_GET_TIME, - /// Wait-queue sleep/wake. - WaitQueue = OPTEE_MSG_RPC_CMD_WAIT_QUEUE, + /// Notification from/to secure world. + Notification = OPTEE_MSG_RPC_CMD_NOTIFICATION, /// Suspend execution. Suspend = OPTEE_MSG_RPC_CMD_SUSPEND, - /// Allocate shared memory for RPC output. + /// Allocate a piece of shared memory. ShmAlloc = OPTEE_MSG_RPC_CMD_SHM_ALLOC, /// Free previously allocated shared memory. ShmFree = OPTEE_MSG_RPC_CMD_SHM_FREE, - /// Asynchronous notification. - Notification = OPTEE_MSG_RPC_CMD_NOTIFICATION, + /// GProf support management commands. + Gprof = OPTEE_MSG_RPC_CMD_GPROF, + /// Socket commands. + Socket = OPTEE_MSG_RPC_CMD_SOCKET, + /// Ftrace support management commands. + Ftrace = OPTEE_MSG_RPC_CMD_FTRACE, + /// Plugin commands. + Plugin = OPTEE_MSG_RPC_CMD_PLUGIN, + /// I2C transfer commands. + I2cTransfer = OPTEE_MSG_RPC_CMD_I2C_TRANSFER, + /// Reset RPMB probing + RpmbProbeReset = OPTEE_MSG_RPC_CMD_RPMB_PROBE_RESET, + /// Probe next RPMB device + RpmbProbeNext = OPTEE_MSG_RPC_CMD_RPMB_PROBE_NEXT, + /// RPBM access + RpmbProbeFrames = OPTEE_MSG_RPC_CMD_RPMB_PROBE_FRAMES, } /// Temporary memory reference parameter @@ -1523,10 +1544,11 @@ impl OpteeMsgParam { /// An unvalidated `num_params` from normal world memory could produce an oversized result, /// leading to out-of-bounds access on fixed-size arrays. /// See CVE-2022-46152 (OP-TEE OOB via unvalidated `num_params`). -pub const fn optee_msg_arg_total_size(num_params: u32) -> usize { +#[inline] +pub const fn optee_msg_args_total_size(num_params: u32) -> usize { debug_assert!( num_params as usize <= OpteeMsgArgs::MAX_ARG_PARAM_COUNT, - "optee_msg_arg_total_size: num_params exceeds MAX_ARG_PARAM_COUNT" + "optee_msg_args_total_size: num_params exceeds MAX_ARG_PARAM_COUNT" ); core::mem::size_of::() + core::mem::size_of::() * num_params as usize @@ -1960,7 +1982,7 @@ impl From<&OpteeSmcArgsPage> for OpteeSmcArgs { } /// OP-TEE SMC call arguments. -#[derive(Clone, Copy, Default)] +#[derive(Clone, Copy, Default, FromBytes)] pub struct OpteeSmcArgs { args: [usize; Self::NUM_OPTEE_SMC_ARGS], } @@ -2149,6 +2171,12 @@ const OPTEE_SMC_RETURN_ENOMEM: usize = 0x6; const OPTEE_SMC_RETURN_ENOTAVAIL: usize = 0x7; const OPTEE_SMC_RETURN_UNKNOWN_FUNCTION: usize = 0xffff_ffff; +const OPTEE_SMC_RETURN_RPC_PREFIX: usize = 0xffff_0000; +const OPTEE_SMC_RETURN_RPC_ALLOC: usize = OPTEE_SMC_RETURN_RPC_PREFIX; +const OPTEE_SMC_RETURN_RPC_FREE: usize = OPTEE_SMC_RETURN_RPC_PREFIX | 0x2; +const OPTEE_SMC_RETURN_RPC_FOREIGN_INTR: usize = OPTEE_SMC_RETURN_RPC_PREFIX | 0x4; +const OPTEE_SMC_RETURN_RPC_CMD: usize = OPTEE_SMC_RETURN_RPC_PREFIX | 0x5; + #[non_exhaustive] #[derive(Copy, Clone, Debug, PartialEq, TryFromPrimitive)] #[repr(usize)] @@ -2162,6 +2190,10 @@ pub enum OpteeSmcReturnCode { ENomem = OPTEE_SMC_RETURN_ENOMEM, ENotAvail = OPTEE_SMC_RETURN_ENOTAVAIL, UnknownFunction = OPTEE_SMC_RETURN_UNKNOWN_FUNCTION, + RpcAlloc = OPTEE_SMC_RETURN_RPC_ALLOC, + RpcFree = OPTEE_SMC_RETURN_RPC_FREE, + RpcForeignIntr = OPTEE_SMC_RETURN_RPC_FOREIGN_INTR, + RpcCmd = OPTEE_SMC_RETURN_RPC_CMD, } impl From for OpteeSmcReturnCode { @@ -2334,9 +2366,9 @@ mod tests { fn test_optee_rpc_args_roundtrip() { use alloc::vec; - // ShmAlloc = 28 + // ShmAlloc = 6 let header = OpteeMsgArgsHeader { - cmd: 28, + cmd: 6, func: 0, session: 0, cancel_id: 0, @@ -2356,7 +2388,7 @@ mod tests { assert_eq!(rpc_args.num_params, 2); let header_out = rpc_args.to_header(); - assert_eq!(header_out.cmd, 28); + assert_eq!(header_out.cmd, 6); assert_eq!(header_out.func, 0); assert_eq!(header_out.session, 0); assert_eq!(header_out.cancel_id, 0); @@ -2372,10 +2404,10 @@ mod tests { #[test] fn test_rpc_args_rejects_main_cmd() { - // Pick a cmd value that lies in the gap between Suspend (5) and ShmAlloc (28), + // Pick a cmd value that lies in the gap between Plugin (12) and I2C Transfer (21), // so it is not a valid OpteeRpcCommand variant. let header = OpteeMsgArgsHeader { - cmd: 6, // not a valid OpteeRpcCommand + cmd: 14, // not a valid OpteeRpcCommand func: 0, session: 0, cancel_id: 0, diff --git a/litebox_runner_lvbs/src/lib.rs b/litebox_runner_lvbs/src/lib.rs index 0fa01b317..06f4958af 100644 --- a/litebox_runner_lvbs/src/lib.rs +++ b/litebox_runner_lvbs/src/lib.rs @@ -19,7 +19,7 @@ use litebox_common_linux::errno::Errno; use litebox_common_optee::{ OpteeMessageCommand, OpteeMsgArgs, OpteeMsgArgsHeader, OpteeRpcArgs, OpteeSmcArgs, OpteeSmcResult, OpteeSmcReturnCode, TeeOrigin, TeeResult, UteeEntryFunc, UteeParams, - optee_msg_arg_total_size, + optee_msg_args_total_size, }; use litebox_platform_lvbs::{ arch::{gdt, get_core_id, instrs::hlt_loop, interrupts}, @@ -314,49 +314,42 @@ fn optee_smc_handler(smc_args_addr: usize) -> OpteeSmcArgs { smc_args.set_return_code(OpteeSmcReturnCode::EBadCmd); return *smc_args; }; - match smc_result { - OpteeSmcResult::CallWithArg { msg_args, rpc_args } => { - let mut msg_args = *msg_args; - let rpc_args = rpc_args.map(|b| *b); - debug_serial_println!("OP-TEE SMC with MsgArgs Command: {:?}", msg_args.cmd); - let result = match msg_args.cmd { - OpenSession => { - handle_open_session(&mut msg_args, msg_args_phys_addr, rpc_args.as_ref()) - } - InvokeCommand => { - handle_invoke_command(&mut msg_args, msg_args_phys_addr, rpc_args.as_ref()) - } - CloseSession => { - handle_close_session(&mut msg_args, msg_args_phys_addr, rpc_args.as_ref()) - } - _ => { - let r = handle_optee_msg_args(&msg_args); - if r.is_ok() { - msg_args.ret = TeeResult::Success; - } else { - msg_args.ret = TeeResult::BadParameters; - } - msg_args.ret_origin = TeeOrigin::Tee; - let _ = write_non_ta_msg_args_to_normal_world( - &msg_args, - msg_args_phys_addr, - rpc_args.as_ref(), - ); - r + if let OpteeSmcResult::CallWithArg { + msg_args, + rpc_args: _, + } = smc_result + { + let mut msg_args = *msg_args; + debug_serial_println!("OP-TEE SMC with MsgArgs Command: {:?}", msg_args.cmd); + let result = match msg_args.cmd { + OpenSession => handle_open_session(&mut msg_args, msg_args_phys_addr), + InvokeCommand => handle_invoke_command(&mut msg_args, msg_args_phys_addr), + CloseSession => handle_close_session(&mut msg_args, msg_args_phys_addr), + _ => { + let r = handle_optee_msg_args(&msg_args); + if r.is_ok() { + msg_args.ret = TeeResult::Success; + } else { + msg_args.ret = TeeResult::BadParameters; } - }; + msg_args.ret_origin = TeeOrigin::Tee; + let _ = write_non_ta_msg_args_to_normal_world(&msg_args, msg_args_phys_addr); + r + } + }; - // Always switch back to base page table before returning to VTL0 - // Safety: No user-space memory references are held after this point - unsafe { switch_to_base_page_table() }; + // Always switch back to base page table before returning to VTL0 + // Safety: No user-space memory references are held after this point + unsafe { switch_to_base_page_table() }; - match result { - Ok(()) => smc_args.set_return_code(OpteeSmcReturnCode::Ok), - Err(e) => smc_args.set_return_code(e), - } - *smc_args + if let Err(e) = result { + smc_args.set_return_code(e); + } else { + smc_args.set_return_code(OpteeSmcReturnCode::Ok); } - _ => smc_result.into(), + *smc_args + } else { + smc_result.into() } } @@ -372,7 +365,6 @@ fn optee_smc_handler(smc_args_addr: usize) -> OpteeSmcArgs { fn handle_open_session( msg_args: &mut OpteeMsgArgs, msg_args_phys_addr: u64, - rpc_args: Option<&OpteeRpcArgs>, ) -> Result<(), OpteeSmcReturnCode> { let ta_req_info = decode_ta_request(msg_args).map_err(|_| OpteeSmcReturnCode::EBadCmd)?; if ta_req_info.entry_func != UteeEntryFunc::OpenSession { @@ -393,7 +385,6 @@ fn handle_open_session( params, ta_uuid, &ta_req_info, - rpc_args, ) } else { open_session_new_instance( @@ -403,7 +394,6 @@ fn handle_open_session( ta_uuid, client_identity, &ta_req_info, - rpc_args, ) } } @@ -423,7 +413,6 @@ fn open_session_single_instance( params: &[litebox_common_optee::UteeParamOwned], ta_uuid: litebox_common_optee::TeeUuid, ta_req_info: &litebox_shim_optee::msg_handler::TaRequestInfo, - rpc_args: Option<&OpteeRpcArgs>, ) -> Result<(), OpteeSmcReturnCode> { // Use try_lock to avoid spinning - return EThreadLimit if TA is in use // The Linux driver will handle this by waiting and retrying @@ -522,7 +511,6 @@ fn open_session_single_instance( None, // No session ID on failure Some(&ta_params), Some(ta_req_info), - rpc_args, )?; session_manager().remove_single_instance(&ta_uuid); @@ -548,7 +536,6 @@ fn open_session_single_instance( None, // No session ID on failure Some(&ta_params), Some(ta_req_info), - rpc_args, )?; return Ok(()); @@ -566,7 +553,6 @@ fn open_session_single_instance( Some(runner_session_id), Some(&ta_params), Some(ta_req_info), - rpc_args, )?; debug_serial_println!( @@ -588,7 +574,6 @@ fn open_session_new_instance( ta_uuid: litebox_common_optee::TeeUuid, client_identity: Option, ta_req_info: &litebox_shim_optee::msg_handler::TaRequestInfo, - rpc_args: Option<&OpteeRpcArgs>, ) -> Result<(), OpteeSmcReturnCode> { // Check TA instance limit // TODO: consider better resource management strategy @@ -673,7 +658,6 @@ fn open_session_new_instance( None, // No session ID on failure None, Some(ta_req_info), - rpc_args, )?; return Ok(()); @@ -740,7 +724,6 @@ fn open_session_new_instance( None, // No session ID on failure Some(&ta_params), Some(ta_req_info), - rpc_args, )?; // Tear down the page table - no session was registered @@ -774,7 +757,6 @@ fn open_session_new_instance( Some(runner_session_id), Some(&ta_params), Some(ta_req_info), - rpc_args, )?; debug_serial_println!( @@ -796,7 +778,6 @@ fn open_session_new_instance( fn handle_invoke_command( msg_args: &mut OpteeMsgArgs, msg_args_phys_addr: u64, - rpc_args: Option<&OpteeRpcArgs>, ) -> Result<(), OpteeSmcReturnCode> { let ta_req_info = decode_ta_request(msg_args).map_err(|_| OpteeSmcReturnCode::EBadCmd)?; if ta_req_info.entry_func != UteeEntryFunc::InvokeCommand { @@ -893,7 +874,6 @@ fn handle_invoke_command( None, Some(&ta_params), Some(&ta_req_info), - rpc_args, )?; if is_last_session { @@ -926,7 +906,6 @@ fn handle_invoke_command( None, Some(&ta_params), Some(&ta_req_info), - rpc_args, )?; Ok(()) @@ -940,7 +919,6 @@ fn handle_invoke_command( fn handle_close_session( msg_args: &mut OpteeMsgArgs, msg_args_phys_addr: u64, - rpc_args: Option<&OpteeRpcArgs>, ) -> Result<(), OpteeSmcReturnCode> { let ta_req_info = decode_ta_request(msg_args).map_err(|_| OpteeSmcReturnCode::EBadCmd)?; if ta_req_info.entry_func != UteeEntryFunc::CloseSession { @@ -996,7 +974,6 @@ fn handle_close_session( None, None, None, - rpc_args, )?; // Clone the instance Arc before dropping the lock for later cleanup check @@ -1063,8 +1040,8 @@ fn handle_close_session( /// Update msg_args with return values and write back to normal world memory. /// -/// Serializes the main `OpteeMsgArgs` (and optional `OpteeRpcArgs`) into a contiguous byte -/// blob and writes it to the VTL0 physical address. +/// Serializes `OpteeMsgArgs` into a contiguous byte blob and writes it to +/// the VTL0 physical address. /// /// Per OP-TEE OS semantics: /// - `TeeOrigin::Tee` is used when the error comes from TEE itself (panic/TARGET_DEAD) @@ -1087,7 +1064,6 @@ fn write_msg_args_to_normal_world( session_id: Option, ta_params: Option<&UteeParams>, ta_req_info: Option<&litebox_shim_optee::msg_handler::TaRequestInfo>, - rpc_args: Option<&OpteeRpcArgs>, ) -> Result<(), OpteeSmcReturnCode> { // Ensure we're on a task page table, not the base page table. // Accessing TA userspace memory requires the TA's page table to be active. @@ -1113,28 +1089,15 @@ fn write_msg_args_to_normal_world( msg_args, )?; - // Compute total size to write to the normal world memory including optional RPC args - let main_size = optee_msg_arg_total_size(msg_args.num_params); - let total_size = if let Some(rpc) = rpc_args { - main_size + optee_msg_arg_total_size(rpc.num_params) - } else { - main_size - }; - - let mut blob = vec![0u8; total_size]; + let msg_args_size = optee_msg_args_total_size(msg_args.num_params); + let mut blob = vec![0u8; msg_args_size]; blob[..size_of::()].copy_from_slice(msg_args.to_header().as_bytes()); - msg_args.write_raw_params(&mut blob[size_of::()..main_size])?; - - if let Some(rpc) = rpc_args { - blob[main_size..main_size + size_of::()] - .copy_from_slice(rpc.to_header().as_bytes()); - rpc.write_raw_params(&mut blob[main_size + size_of::()..total_size])?; - } + msg_args.write_raw_params(&mut blob[size_of::()..msg_args_size])?; let mut ptr = NormalWorldMutPtr::::with_contiguous_pages( msg_args_phys_addr.truncate(), - total_size, + msg_args_size, )?; // SAFETY: Writing msg_args back to normal world memory at a valid physical address. // The blob contains the serialized variable-length optee_msg_arg structure(s). @@ -1142,40 +1105,27 @@ fn write_msg_args_to_normal_world( Ok(()) } -/// Write back `OpteeMsgArgs` (and optional `OpteeRpcArgs`) for non-TA commands -/// (e.g., RegisterShm, UnregisterShm) that don't require TA userspace memory access. +/// Write back `OpteeMsgArgs` for non-TA commands (e.g., RegisterShm, UnregisterShm) that +/// don't require TA userspace memory access. /// /// Unlike [`write_msg_args_to_normal_world`], this function does not access TA userspace /// memory and can be called from the base page table context. It simply serializes the -/// msg_args (which should already have `ret` / `ret_origin` set by the caller) and the -/// rpc_args (preserved as-is) back to the normal world physical address. +/// msg_args (which should already have `ret` / `ret_origin` set by the caller) back to +/// the normal world physical address. #[inline] fn write_non_ta_msg_args_to_normal_world( msg_args: &OpteeMsgArgs, msg_args_phys_addr: u64, - rpc_args: Option<&OpteeRpcArgs>, ) -> Result<(), OpteeSmcReturnCode> { - let main_size = optee_msg_arg_total_size(msg_args.num_params); - let total_size = if let Some(rpc) = rpc_args { - main_size + optee_msg_arg_total_size(rpc.num_params) - } else { - main_size - }; - - let mut blob = vec![0u8; total_size]; + let msg_args_size = optee_msg_args_total_size(msg_args.num_params); + let mut blob = vec![0u8; msg_args_size]; blob[..size_of::()].copy_from_slice(msg_args.to_header().as_bytes()); - msg_args.write_raw_params(&mut blob[size_of::()..main_size])?; - - if let Some(rpc) = rpc_args { - blob[main_size..main_size + size_of::()] - .copy_from_slice(rpc.to_header().as_bytes()); - rpc.write_raw_params(&mut blob[main_size + size_of::()..total_size])?; - } + msg_args.write_raw_params(&mut blob[size_of::()..msg_args_size])?; let mut ptr = NormalWorldMutPtr::::with_contiguous_pages( msg_args_phys_addr.truncate(), - total_size, + msg_args_size, )?; // SAFETY: Writing msg_args back to normal world memory at a valid physical address. // The blob contains the serialized variable-length optee_msg_arg structure(s). @@ -1183,6 +1133,35 @@ fn write_non_ta_msg_args_to_normal_world( Ok(()) } +/// Write `OpteeRpcArgs` to the normal world. Its write address is determined by +/// `msg_args_phys_addr` and the size of `OpteeMsgArgs`. +/// +/// Unlike [`write_msg_args_to_normal_world`], this function does not access TA userspace +/// memory and can be called from the base page table context. It simply serializes the +/// rpc_args and writes it to the normal world physical address. +#[expect(dead_code)] +#[inline] +fn write_rpc_args_to_normal_world( + msg_args: &OpteeMsgArgs, + msg_args_phys_addr: u64, + rpc_args: &OpteeRpcArgs, +) -> Result<(), OpteeSmcReturnCode> { + let msg_args_size = optee_msg_args_total_size(msg_args.num_params); + let rpc_args_size = optee_msg_args_total_size(rpc_args.num_params); + + let mut blob = vec![0u8; rpc_args_size]; + blob[..size_of::()].copy_from_slice(rpc_args.to_header().as_bytes()); + rpc_args.write_raw_params(&mut blob[size_of::()..rpc_args_size])?; + + let rpc_pa: usize = + >::truncate(msg_args_phys_addr) + msg_args_size; // RPC args are placed right after the main msg_args blob + let mut ptr = NormalWorldMutPtr::::with_contiguous_pages(rpc_pa, rpc_args_size)?; + // SAFETY: Writing rpc_args back to normal world memory at a valid physical address. + // The blob contains the serialized variable-length optee_msg_arg structure(s). + unsafe { ptr.write_slice_at_offset(0, &blob) }?; + Ok(()) +} + // use include_bytes! to include ldelf and (KMPP) TA binaries const LDELF_BINARY: &[u8] = &[0u8; 0]; const TA_BINARY: &[u8] = &[0u8; 0]; diff --git a/litebox_shim_optee/Cargo.toml b/litebox_shim_optee/Cargo.toml index 69eefbdc0..e277e60cc 100644 --- a/litebox_shim_optee/Cargo.toml +++ b/litebox_shim_optee/Cargo.toml @@ -20,7 +20,7 @@ num_enum = { version = "0.7.3", default-features = false } once_cell = { version = "1.20.2", default-features = false, features = ["alloc", "race"] } spin = { version = "0.10.0", default-features = false, features = ["spin_mutex", "once"] } thiserror = { version = "2.0.6", default-features = false } -zerocopy = { version = "0.8", default-features = false } +zerocopy = { version = "0.8", default-features = false, features = ["derive"] } [features] default = ["platform_lvbs"] diff --git a/litebox_shim_optee/src/msg_handler.rs b/litebox_shim_optee/src/msg_handler.rs index 24ce10314..f0c70acf7 100644 --- a/litebox_shim_optee/src/msg_handler.rs +++ b/litebox_shim_optee/src/msg_handler.rs @@ -26,10 +26,10 @@ use litebox_common_optee::{ OpteeMsgParamTmem, OpteeMsgParamValue, OpteeRpcArgs, OpteeSecureWorldCapabilities, OpteeSmcArgs, OpteeSmcFunction, OpteeSmcResult, OpteeSmcReturnCode, TeeIdentity, TeeLogin, TeeOrigin, TeeParamType, TeeResult, TeeUuid, UteeEntryFunc, UteeParamOwned, UteeParams, - optee_msg_arg_total_size, + optee_msg_args_total_size, }; use once_cell::race::OnceBox; -use zerocopy::FromBytes; +use zerocopy::{FromBytes, Immutable}; // OP-TEE version and build info (2.0) // TODO: Consider replacing it with our own version info @@ -72,13 +72,13 @@ fn page_align_up(len: u64) -> u64 { /// our private copy after the single read. /// /// The copy size is determined by known-good upper bounds, not by untrusted data: -/// - Main args: `optee_msg_arg_total_size(MAX_ARG_PARAM_COUNT = 6)` = 224 bytes (the Linux +/// - Main args: `optee_msg_args_total_size(MAX_ARG_PARAM_COUNT = 6)` = 224 bytes (the Linux /// driver always allocates at least this much for the main arg). -/// - RPC args (when present): `optee_msg_arg_total_size(rpc_num_params)`, where +/// - RPC args (when present): `optee_msg_args_total_size(rpc_num_params)`, where /// `rpc_num_params` is our own negotiated value from `EXCHANGE_CAPABILITIES`. /// /// If `has_rpc_arg` is true, expects an appended RPC `optee_msg_arg` immediately after -/// the main one at offset `optee_msg_arg_total_size(num_params)` (the *actual* `num_params`, +/// the main one at offset `optee_msg_args_total_size(num_params)` (the *actual* `num_params`, /// not `MAX_ARG_PARAM_COUNT`). This matches the Linux driver's layout. /// /// VTL0 physical memory layout at `phys_addr`: @@ -111,9 +111,9 @@ pub fn read_optee_msg_args_from_phys( has_rpc_arg: bool, ) -> Result<(Box, Option>), OpteeSmcReturnCode> { // Compute copy size from known-good upper bounds — no untrusted data involved. - let main_max = optee_msg_arg_total_size(OpteeMsgArgs::MAX_ARG_PARAM_COUNT.truncate()); + let main_max = optee_msg_args_total_size(OpteeMsgArgs::MAX_ARG_PARAM_COUNT.truncate()); let copy_size = if has_rpc_arg { - main_max + optee_msg_arg_total_size(OpteeRpcArgs::MAX_RPC_ARG_PARAM_COUNT.truncate()) + main_max + optee_msg_args_total_size(OpteeRpcArgs::MAX_RPC_ARG_PARAM_COUNT.truncate()) } else { main_max }; @@ -136,7 +136,7 @@ pub fn read_optee_msg_args_from_phys( return Err(OpteeSmcReturnCode::EBadCmd); } - let main_size = optee_msg_arg_total_size(main_header.num_params); + let main_size = optee_msg_args_total_size(main_header.num_params); let main_params = &blob[size_of::()..main_size]; let main_args = OpteeMsgArgs::from_header_and_raw_params(&main_header, main_params)?; @@ -535,7 +535,7 @@ pub fn update_optee_msg_args( /// virtually contiguous in the normal world. All these address values must be page aligned. /// /// `pages_data` from [Linux](https://elixir.bootlin.com/linux/v6.18.2/source/drivers/tee/optee/smc_abi.c#L409) -#[derive(Clone, Copy)] +#[derive(Clone, Copy, FromBytes, Immutable)] #[repr(C)] struct ShmRefPagesData { pub pages_list: [u64; Self::PAGELIST_ENTRIES_PER_PAGE], diff --git a/litebox_shim_optee/src/ptr.rs b/litebox_shim_optee/src/ptr.rs index a27105caa..2a54bac25 100644 --- a/litebox_shim_optee/src/ptr.rs +++ b/litebox_shim_optee/src/ptr.rs @@ -65,6 +65,29 @@ use litebox_common_linux::vmap::{ PhysPageAddr, PhysPageMapInfo, PhysPageMapPermissions, PhysPointerError, VmapManager, }; use litebox_platform_multiplex::platform; +use zerocopy::FromBytes; + +/// Allocate a zeroed `Box` on the heap. +/// +/// # Panics +/// +/// Panics if `T` is a zero-sized type, since `alloc_zeroed` with a zero-sized +/// layout is undefined behavior. +fn box_new_zeroed() -> alloc::boxed::Box { + assert!( + core::mem::size_of::() > 0, + "box_new_zeroed does not support zero-sized types" + ); + let layout = core::alloc::Layout::new::(); + // Safety: layout has a non-zero size and correct alignment for T. + let ptr = unsafe { alloc::alloc::alloc_zeroed(layout) }.cast::(); + if ptr.is_null() { + alloc::alloc::handle_alloc_error(layout); + } + // Safety: ptr is a valid, zeroed, properly aligned heap allocation for T. + // T: FromBytes guarantees all-zero is a valid bit pattern. + unsafe { alloc::boxed::Box::from_raw(ptr) } +} #[inline] fn align_down(address: usize, align: usize) -> usize { @@ -176,10 +199,14 @@ impl PhysMutPtr { /// The caller should be aware that the given physical address might be concurrently written by /// other entities (e.g., the normal world kernel) if there is no extra security mechanism /// in place (e.g., by the hypervisor or hardware). That is, it might read corrupt data. + /// `FromBytes` is required to ensure T is valid for any bit pattern from untrusted physical memory. pub unsafe fn read_at_offset( &mut self, count: usize, - ) -> Result, PhysPointerError> { + ) -> Result, PhysPointerError> + where + T: FromBytes, + { if count >= self.count { return Err(PhysPointerError::IndexOutOfBounds(count, self.count)); } @@ -190,19 +217,18 @@ impl PhysMutPtr { PhysPageMapPermissions::READ, )? }; - let mut buffer = core::mem::MaybeUninit::::uninit(); + let mut boxed = box_new_zeroed::(); // Fallible: another core may unmap this page concurrently. let result = unsafe { litebox::mm::exception_table::memcpy_fallible( - buffer.as_mut_ptr().cast::(), + core::ptr::from_mut::(boxed.as_mut()).cast::(), guard.ptr.cast::(), guard.size, ) }; debug_assert!(result.is_ok(), "fault reading from mapped physical page"); result.map_err(|_| PhysPointerError::CopyFailed)?; - // Safety: memcpy_fallible fully initialized the buffer on success. - Ok(alloc::boxed::Box::new(unsafe { buffer.assume_init() })) + Ok(boxed) } /// Read a slice of values at the given offset from the physical pointer. @@ -212,11 +238,15 @@ impl PhysMutPtr { /// The caller should be aware that the given physical address might be concurrently written by /// other entities (e.g., the normal world kernel) if there is no extra security mechanism /// in place (e.g., by the hypervisor or hardware). That is, it might read corrupt data. + /// `FromBytes` is required to ensure T is valid for any bit pattern from untrusted physical memory. pub unsafe fn read_slice_at_offset( &mut self, count: usize, values: &mut [T], - ) -> Result<(), PhysPointerError> { + ) -> Result<(), PhysPointerError> + where + T: FromBytes, + { if count .checked_add(values.len()) .is_none_or(|end| end > self.count) @@ -467,7 +497,7 @@ pub struct PhysConstPtr { inner: PhysMutPtr, } -impl PhysConstPtr { +impl PhysConstPtr { /// Create a new `PhysConstPtr` from the given physical page array and offset. /// /// All addresses in `pages` should be valid and aligned to `ALIGN`, and `offset` should be smaller @@ -513,7 +543,10 @@ impl PhysConstPtr { pub unsafe fn read_at_offset( &mut self, count: usize, - ) -> Result, PhysPointerError> { + ) -> Result, PhysPointerError> + where + T: FromBytes, + { unsafe { self.inner.read_at_offset(count) } } @@ -528,7 +561,10 @@ impl PhysConstPtr { &mut self, count: usize, values: &mut [T], - ) -> Result<(), PhysPointerError> { + ) -> Result<(), PhysPointerError> + where + T: FromBytes, + { unsafe { self.inner.read_slice_at_offset(count, values) } } } From bd04941016587c0a2d6c2184bfaa07bbf565a99a Mon Sep 17 00:00:00 2001 From: Sangho Lee Date: Tue, 17 Feb 2026 01:12:05 +0000 Subject: [PATCH 9/9] simplification --- litebox_common_optee/src/lib.rs | 187 ++++++++++++++++++-------------- litebox_runner_lvbs/src/lib.rs | 20 +--- 2 files changed, 113 insertions(+), 94 deletions(-) diff --git a/litebox_common_optee/src/lib.rs b/litebox_common_optee/src/lib.rs index 740f328fe..66a24be35 100644 --- a/litebox_common_optee/src/lib.rs +++ b/litebox_common_optee/src/lib.rs @@ -1592,6 +1592,38 @@ pub struct OpteeMsgArgsHeader { pub num_params: u32, } +/// Convert the header portion of this `OpteeMsgArgs` to an `OpteeMsgArgsHeader`. +impl From for OpteeMsgArgsHeader { + fn from(args: OpteeMsgArgs) -> Self { + Self { + cmd: args.cmd as u32, + func: args.func, + session: args.session, + cancel_id: args.cancel_id, + pad: 0, + ret: args.ret.into(), + ret_origin: *args.ret_origin.value(), + num_params: args.num_params, + } + } +} + +/// Convert the header portion of this `OpteeRpcArgs` to an `OpteeMsgArgsHeader`. +impl From for OpteeMsgArgsHeader { + fn from(args: OpteeRpcArgs) -> Self { + Self { + cmd: args.cmd as u32, + func: 0, + session: 0, + cancel_id: 0, + pad: 0, + ret: args.ret.into(), + ret_origin: *args.ret_origin.value(), + num_params: args.num_params, + } + } +} + /// `optee_msg_arg` from `optee_os/core/include/optee_msg.h` /// OP-TEE message argument structure that the normal world (or VTL0) OP-TEE driver and OP-TEE OS use to /// exchange messages. @@ -1713,7 +1745,7 @@ impl OpteeMsgArgs { /// This is `TEE_NUM_PARAMS + 2` = 6, matching the Linux driver's `MAX_ARG_PARAM_COUNT`. pub const MAX_ARG_PARAM_COUNT: usize = TEE_NUM_PARAMS + 2; - /// Construct an `OpteeMsgArgs` from a zerocopy header and a raw parameter byte slice. + /// Construct an `OpteeMsgArgs` from an `OpteeMsgArgsHeader` and a raw parameter byte slice. /// /// `raw_params` must contain at least `header.num_params * size_of::()` bytes. /// `header.num_params` must not exceed `MAX_ARG_PARAM_COUNT` (6). @@ -1754,7 +1786,7 @@ impl OpteeMsgArgs { func: header.func, session: header.session, cancel_id: header.cancel_id, - pad: header.pad, + pad: 0, ret, ret_origin, num_params: header.num_params, @@ -1762,38 +1794,24 @@ impl OpteeMsgArgs { }) } - /// Convert the header portion of this `OpteeMsgArgs` back to an `OpteeMsgArgsHeader`. - pub fn to_header(&self) -> OpteeMsgArgsHeader { - OpteeMsgArgsHeader { - cmd: self.cmd as u32, - func: self.func, - session: self.session, - cancel_id: self.cancel_id, - pad: self.pad, - ret: self.ret as u32, - ret_origin: *self.ret_origin.value(), - num_params: self.num_params, - } - } - - /// Serialize the params portion (up to `num_params`) as raw bytes into `buf`. + /// Serialize this `OpteeMsgArgs` into a raw byte buffer. /// - /// Re-validates `num_params <= MAX_ARG_PARAM_COUNT` before using as loop bound. - pub fn write_raw_params(&self, buf: &mut [u8]) -> Result { - let num = self.num_params as usize; - if num > Self::MAX_ARG_PARAM_COUNT { - return Err(OpteeSmcReturnCode::EBadCmd); - } - let out_buf_len = num * size_of::(); - if buf.len() < out_buf_len { + /// The buffer should be large enough to hold the header plus `num_params` parameters + /// (as returned by [`optee_msg_args_total_size`]). + pub fn serialize(&self, buf: &mut [u8]) -> Result<(), OpteeSmcReturnCode> { + if buf.len() < optee_msg_args_total_size(self.num_params) + || self.num_params as usize > Self::MAX_ARG_PARAM_COUNT + { return Err(OpteeSmcReturnCode::EBadAddr); } - for i in 0..num { - let offset = i * size_of::(); - buf[offset..offset + size_of::()] - .copy_from_slice(self.params[i].as_bytes()); - } - Ok(out_buf_len) + let header = OpteeMsgArgsHeader::from(*self); + let header_bytes = header.as_bytes(); + buf[..header_bytes.len()].copy_from_slice(header_bytes); + write_optee_msg_params_to_buf( + &self.params[..self.num_params as usize], + &mut buf[header_bytes.len()..], + ); + Ok(()) } } @@ -1842,11 +1860,10 @@ impl OpteeRpcArgs { /// This is `NUM_RPC_PARAMS`, the count negotiated during `EXCHANGE_CAPABILITIES`. pub const MAX_RPC_ARG_PARAM_COUNT: usize = NUM_RPC_PARAMS; - /// Construct an `OpteeRpcArgs` from a zerocopy header and a raw parameter byte slice. + /// Construct an `OpteeRpcArgs` from an `OpteeMsgArgsHeader` and a raw parameter byte slice. /// - /// The `cmd` field is parsed as [`OpteeRpcCommand`]. Unlike - /// [`OpteeMsgArgs::from_header_and_raw_params`], `func`, `session`, and `cancel_id` - /// are stored as reserved zeros — they carry no meaning for RPC. + /// Unlike [`OpteeMsgArgs::from_header_and_raw_params`], `cmd` is parsed as [`OpteeRpcCommand`] and + /// `func`, `session`, and `cancel_id` are stored as zeros — they carry no meaning for RPC. pub fn from_header_and_raw_params( header: &OpteeMsgArgsHeader, raw_params: &[u8], @@ -1891,38 +1908,24 @@ impl OpteeRpcArgs { }) } - /// Convert back to an [`OpteeMsgArgsHeader`] for wire serialization. + /// Serialize this `OpteeRpcArgs` into a raw byte buffer. /// - /// The reserved fields (`func`, `session`, `cancel_id`) are written as 0. - pub fn to_header(&self) -> OpteeMsgArgsHeader { - OpteeMsgArgsHeader { - cmd: self.cmd as u32, - func: 0, - session: 0, - cancel_id: 0, - pad: 0, - ret: self.ret as u32, - ret_origin: *self.ret_origin.value(), - num_params: self.num_params, - } - } - - /// Serialize the params portion (up to `num_params`) as raw bytes into `buf`. - pub fn write_raw_params(&self, buf: &mut [u8]) -> Result { - let num = self.num_params as usize; - if num > Self::MAX_RPC_ARG_PARAM_COUNT { - return Err(OpteeSmcReturnCode::EBadCmd); - } - let out_buf_len = num * size_of::(); - if buf.len() < out_buf_len { + /// The buffer should be large enough to hold the header plus `num_params` parameters + /// (as returned by [`optee_msg_args_total_size`]). + pub fn serialize(&self, buf: &mut [u8]) -> Result<(), OpteeSmcReturnCode> { + if buf.len() < optee_msg_args_total_size(self.num_params) + || self.num_params as usize > Self::MAX_RPC_ARG_PARAM_COUNT + { return Err(OpteeSmcReturnCode::EBadAddr); } - for i in 0..num { - let offset = i * size_of::(); - buf[offset..offset + size_of::()] - .copy_from_slice(self.params[i].as_bytes()); - } - Ok(out_buf_len) + let header = OpteeMsgArgsHeader::from(*self); + let header_bytes = header.as_bytes(); + buf[..header_bytes.len()].copy_from_slice(header_bytes); + write_optee_msg_params_to_buf( + &self.params[..self.num_params as usize], + &mut buf[header_bytes.len()..], + ); + Ok(()) } /// Access a parameter by index with bounds checking against `num_params`. @@ -1946,11 +1949,49 @@ impl OpteeRpcArgs { .ok_or(OpteeSmcReturnCode::EBadCmd) } } + + /// Set a value parameter by index with bounds checking against `num_params`. + pub fn set_param_value( + &mut self, + index: usize, + value: OpteeMsgParamValue, + ) -> Result<(), OpteeSmcReturnCode> { + if index >= self.num_params as usize { + Err(OpteeSmcReturnCode::ENotAvail) + } else { + self.params[index].data.copy_from_slice(value.as_bytes()); + Ok(()) + } + } + + /// Set a tmem parameter by index with bounds checking against `num_params`. + pub fn set_param_tmem( + &mut self, + index: usize, + tmem: OpteeMsgParamTmem, + ) -> Result<(), OpteeSmcReturnCode> { + if index >= self.num_params as usize { + Err(OpteeSmcReturnCode::ENotAvail) + } else { + self.params[index].data.copy_from_slice(tmem.as_bytes()); + Ok(()) + } + } + // Note: RPC does not use rmem params. Rmem requires pre-registered shared memory // references from the normal-world driver, which is a main-messaging-path concept. // RPC uses tmem for buffer references since OP-TEE provides physical addresses directly. } +/// Serialize the params portion as raw bytes into `buf`. +#[inline] +fn write_optee_msg_params_to_buf(params: &[OpteeMsgParam], buf: &mut [u8]) { + for (i, param) in params.iter().enumerate() { + let offset = i * size_of::(); + buf[offset..offset + size_of::()].copy_from_slice(param.as_bytes()); + } +} + /// A memory page to exchange OP-TEE SMC call arguments. /// OP-TEE assumes that the underlying architecture is Arm with TrustZone and /// thus it uses Secure Monitor Call (SMC) calling convention (SMCCC). @@ -2348,18 +2389,11 @@ mod tests { assert_eq!(msg_args.session, 0xABCD); assert_eq!(msg_args.num_params, 3); - let header_out = msg_args.to_header(); + let header_out = OpteeMsgArgsHeader::from(msg_args); assert_eq!(header_out.cmd, 1); assert_eq!(header_out.func, 0x1234); assert_eq!(header_out.session, 0xABCD); assert_eq!(header_out.num_params, 3); - - let mut params_out = vec![0u8; 3 * size_of::()]; - let written = msg_args - .write_raw_params(&mut params_out) - .expect("expected Ok"); - assert_eq!(written, 3 * size_of::()); - assert_eq!(¶ms_out[..written], ¶ms_in[..written]); } #[test] @@ -2387,19 +2421,12 @@ mod tests { assert_eq!(rpc_args.cmd, OpteeRpcCommand::ShmAlloc); assert_eq!(rpc_args.num_params, 2); - let header_out = rpc_args.to_header(); + let header_out = OpteeMsgArgsHeader::from(rpc_args); assert_eq!(header_out.cmd, 6); assert_eq!(header_out.func, 0); assert_eq!(header_out.session, 0); assert_eq!(header_out.cancel_id, 0); assert_eq!(header_out.num_params, 2); - - let mut params_out = vec![0u8; 2 * size_of::()]; - let written = rpc_args - .write_raw_params(&mut params_out) - .expect("should serialize"); - assert_eq!(written, 2 * size_of::()); - assert_eq!(¶ms_out[..written], ¶ms_in[..written]); } #[test] diff --git a/litebox_runner_lvbs/src/lib.rs b/litebox_runner_lvbs/src/lib.rs index 06f4958af..d72d35506 100644 --- a/litebox_runner_lvbs/src/lib.rs +++ b/litebox_runner_lvbs/src/lib.rs @@ -8,7 +8,6 @@ extern crate alloc; use alloc::boxed::Box; use alloc::sync::Arc; use alloc::vec; -use core::mem::size_of; use core::{ops::Neg, panic::PanicInfo}; use litebox::{ mm::linux::PAGE_SIZE, @@ -17,9 +16,8 @@ use litebox::{ }; use litebox_common_linux::errno::Errno; use litebox_common_optee::{ - OpteeMessageCommand, OpteeMsgArgs, OpteeMsgArgsHeader, OpteeRpcArgs, OpteeSmcArgs, - OpteeSmcResult, OpteeSmcReturnCode, TeeOrigin, TeeResult, UteeEntryFunc, UteeParams, - optee_msg_args_total_size, + OpteeMessageCommand, OpteeMsgArgs, OpteeRpcArgs, OpteeSmcArgs, OpteeSmcResult, + OpteeSmcReturnCode, TeeOrigin, TeeResult, UteeEntryFunc, UteeParams, optee_msg_args_total_size, }; use litebox_platform_lvbs::{ arch::{gdt, get_core_id, instrs::hlt_loop, interrupts}, @@ -48,7 +46,6 @@ use litebox_shim_optee::session::{ use litebox_shim_optee::{NormalWorldConstPtr, NormalWorldMutPtr, UserConstPtr}; use once_cell::race::OnceBox; use spin::mutex::SpinMutex; -use zerocopy::IntoBytes; /// # Panics /// @@ -1090,10 +1087,8 @@ fn write_msg_args_to_normal_world( )?; let msg_args_size = optee_msg_args_total_size(msg_args.num_params); - let mut blob = vec![0u8; msg_args_size]; - blob[..size_of::()].copy_from_slice(msg_args.to_header().as_bytes()); - msg_args.write_raw_params(&mut blob[size_of::()..msg_args_size])?; + msg_args.serialize(&mut blob)?; let mut ptr = NormalWorldMutPtr::::with_contiguous_pages( msg_args_phys_addr.truncate(), @@ -1118,10 +1113,8 @@ fn write_non_ta_msg_args_to_normal_world( msg_args_phys_addr: u64, ) -> Result<(), OpteeSmcReturnCode> { let msg_args_size = optee_msg_args_total_size(msg_args.num_params); - let mut blob = vec![0u8; msg_args_size]; - blob[..size_of::()].copy_from_slice(msg_args.to_header().as_bytes()); - msg_args.write_raw_params(&mut blob[size_of::()..msg_args_size])?; + msg_args.serialize(&mut blob)?; let mut ptr = NormalWorldMutPtr::::with_contiguous_pages( msg_args_phys_addr.truncate(), @@ -1147,11 +1140,10 @@ fn write_rpc_args_to_normal_world( rpc_args: &OpteeRpcArgs, ) -> Result<(), OpteeSmcReturnCode> { let msg_args_size = optee_msg_args_total_size(msg_args.num_params); - let rpc_args_size = optee_msg_args_total_size(rpc_args.num_params); + let rpc_args_size = optee_msg_args_total_size(rpc_args.num_params); let mut blob = vec![0u8; rpc_args_size]; - blob[..size_of::()].copy_from_slice(rpc_args.to_header().as_bytes()); - rpc_args.write_raw_params(&mut blob[size_of::()..rpc_args_size])?; + rpc_args.serialize(&mut blob)?; let rpc_pa: usize = >::truncate(msg_args_phys_addr) + msg_args_size; // RPC args are placed right after the main msg_args blob