diff --git a/litebox_common_optee/src/lib.rs b/litebox_common_optee/src/lib.rs index bcff985e1..f0942b4d9 100644 --- a/litebox_common_optee/src/lib.rs +++ b/litebox_common_optee/src/lib.rs @@ -612,6 +612,17 @@ pub struct TeeUuid { } impl TeeUuid { + /// The nil UUID (all zeros, RFC 4122 S4.1.7). + /// + /// Used for anonymous clients (e.g. ,`TeeLogin::Public`) that carry no + /// REE-derived identity. + pub const NIL: Self = Self { + time_low: 0, + time_mid: 0, + time_hi_and_version: 0, + clock_seq_and_node: [0; 8], + }; + /// Converts a UUID from a 16-byte array in RFC 4122 format (big-endian for numeric fields). /// /// The byte layout is: @@ -820,8 +831,13 @@ const TEE_LOGIN_APPLICATION: u32 = 0x4; const TEE_LOGIN_APPLICATION_USER: u32 = 0x5; const TEE_LOGIN_APPLICATION_GROUP: u32 = 0x6; const TEE_LOGIN_TRUSTED_APP: u32 = 0xf000_0000; +// Private OP-TEE login for in-kernel REE clients (from +// `tee_api_defines_extensions.h`). Userspace cannot send it. It uses +// the nil client UUID. +const TEE_LOGIN_REE_KERNEL: u32 = 0x8000_0000; /// `TEE Login type` from `optee_os/lib/libutee/include/tee_api_defines.h` +/// (plus the `TEE_LOGIN_REE_KERNEL` extension). #[derive(Clone, Copy, PartialEq, TryFromPrimitive, Immutable, IntoBytes)] #[repr(u32)] pub enum TeeLogin { @@ -831,6 +847,7 @@ pub enum TeeLogin { Application = TEE_LOGIN_APPLICATION, ApplicationUser = TEE_LOGIN_APPLICATION_USER, ApplicationGroup = TEE_LOGIN_APPLICATION_GROUP, + ReeKernel = TEE_LOGIN_REE_KERNEL, TrustedApp = TEE_LOGIN_TRUSTED_APP, } @@ -1457,6 +1474,11 @@ const OPTEE_MSG_ATTR_TYPE_TMEM_INOUT: u8 = 0xb; // Note: `OPTEE_MSG_ATTR_TYPE_FMEM_*` are aliases of `OPTEE_MSG_ATTR_TYPE_RMEM_*`. // Whether it is RMEM of FMEM depends on the conduit. +/// Meta-parameter marker (bit 8) of the attribute word. Set on the `OpenSession` +/// TA-UUID and client-identity params, which are consumed by the secure world +/// and not delivered to the TA. +const OPTEE_MSG_ATTR_META: u64 = 1 << 8; + #[non_exhaustive] #[derive(Debug, PartialEq, TryFromPrimitive)] #[repr(u8)] @@ -1480,11 +1502,17 @@ pub enum OpteeMsgAttrType { /// - bit 8 – meta /// - bit 9 – noncontig /// - bits \[63:10\] – reserved (zero) -#[derive(Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)] +#[derive(Clone, Copy, Default, PartialEq, Eq, FromBytes, IntoBytes, Immutable, KnownLayout)] #[repr(transparent)] pub struct OpteeMsgAttr(u64); impl OpteeMsgAttr { + /// The exact attribute word an `OpenSession` meta value parameter must carry + /// (`OPTEE_MSG_ATTR_META | OPTEE_MSG_ATTR_TYPE_VALUE_INPUT`, all other bits + /// zero). See [`OpteeMsgArgs::get_meta_param_value`]. + pub const META_VALUE_INPUT: Self = + Self(OPTEE_MSG_ATTR_META | OPTEE_MSG_ATTR_TYPE_VALUE_INPUT as u64); + /// Returns the attribute type (bits 0–7). #[allow(clippy::cast_possible_truncation)] pub fn attr_type(&self) -> u8 { @@ -1745,6 +1773,28 @@ impl OpteeMsgArgs { .ok_or(OpteeSmcReturnCode::EBadCmd)?) } } + + /// Read a value parameter that must be tagged as an `OpenSession` meta parameter. + /// + /// `OpenSession` conveys the TA UUID and client identity in the first two + /// params, each marked exactly [`OpteeMsgAttr::META_VALUE_INPUT`]. This + /// mirrors OP-TEE OS `get_open_session_meta()`, which rejects the request + /// unless the full attr word of both meta params equals that value. Plain + /// `get_param_value` ignores these bits, so it must not be used for this. + pub fn get_meta_param_value( + &self, + index: usize, + ) -> Result { + if index >= self.num_params as usize { + return Err(OpteeSmcReturnCode::ENotAvail); + } + let param = &self.params[index]; + if param.attr != OpteeMsgAttr::META_VALUE_INPUT { + return Err(OpteeSmcReturnCode::EBadCmd); + } + param.get_param_value().ok_or(OpteeSmcReturnCode::EBadCmd) + } + pub fn set_param_value( &mut self, index: usize, diff --git a/litebox_runner_optee_on_linux_userland/src/tests.rs b/litebox_runner_optee_on_linux_userland/src/tests.rs index 4bfe1f3da..38b5d530e 100644 --- a/litebox_runner_optee_on_linux_userland/src/tests.rs +++ b/litebox_runner_optee_on_linux_userland/src/tests.rs @@ -7,7 +7,9 @@ use litebox::platform::RawConstPointer; use litebox::utils::TruncateExt; -use litebox_common_optee::{TeeParamType, UteeEntryFunc, UteeParamOwned, UteeParams}; +use litebox_common_optee::{ + TeeIdentity, TeeLogin, TeeParamType, TeeUuid, UteeEntryFunc, UteeParamOwned, UteeParams, +}; use litebox_shim_optee::session::allocate_session_id; use litebox_shim_optee::{LoadedProgram, UserConstPtr}; use serde::Deserialize; @@ -49,12 +51,22 @@ pub fn run_ta_with_test_commands( if func_id == UteeEntryFunc::OpenSession { let ta_head = litebox_common_optee::parse_ta_head(ta_bin) .expect("Failed to parse TA header from ta_bin"); + // Emulate the client identity a real REE client would present. The + // secure world normally derives this from the OpenSession meta + // params; here the test file states it, defaulting to a `user` login. + let client_identity = cmd.client_identity.as_ref().map_or( + TeeIdentity { + login: TeeLogin::User, + uuid: TeeUuid::NIL, + }, + ClientIdentityJson::to_tee_identity, + ); let loaded = shim .load_ldelf( ldelf_bin, ta_head.uuid, Some(ta_bin), - None, + Some(client_identity), allocate_session_id().unwrap(), ) .map_err(|_| { @@ -173,6 +185,80 @@ pub struct TaCommandBase64 { cmd_id: u32, #[serde(default)] args: Vec, + /// Client identity presented for an `OpenSession`, mirroring the + /// `TEE_Identity` a real REE client would supply (the secure world derives + /// this from the message meta params; this userland harness has no such + /// client, so the test states it here). Ignored for other commands. When + /// absent it defaults to a `user` login with the nil UUID, which is what a + /// typical REE user client presents. + #[serde(default)] + client_identity: Option, +} + +/// Client identity for an `OpenSession`, parsed from the test JSON. +#[derive(Debug, Deserialize)] +struct ClientIdentityJson { + #[serde(default)] + login: ClientLoginJson, + /// RFC 4122 UUID string (hyphens optional). Defaults to the nil UUID. + #[serde(default)] + uuid: Option, +} + +/// JSON mirror of [`TeeLogin`], so test files can name logins (e.g. `"user"`) +/// instead of raw `TEE_LOGIN_*` constants. +#[derive(Debug, Default, Clone, Copy, Deserialize)] +#[serde(rename_all = "snake_case")] +enum ClientLoginJson { + Public, + #[default] + User, + Group, + Application, + ApplicationUser, + ApplicationGroup, + ReeKernel, + TrustedApp, +} + +impl From for TeeLogin { + fn from(login: ClientLoginJson) -> Self { + match login { + ClientLoginJson::Public => TeeLogin::Public, + ClientLoginJson::User => TeeLogin::User, + ClientLoginJson::Group => TeeLogin::Group, + ClientLoginJson::Application => TeeLogin::Application, + ClientLoginJson::ApplicationUser => TeeLogin::ApplicationUser, + ClientLoginJson::ApplicationGroup => TeeLogin::ApplicationGroup, + ClientLoginJson::ReeKernel => TeeLogin::ReeKernel, + ClientLoginJson::TrustedApp => TeeLogin::TrustedApp, + } + } +} + +impl ClientIdentityJson { + fn to_tee_identity(&self) -> TeeIdentity { + let uuid = self + .uuid + .as_deref() + .map_or(TeeUuid::NIL, parse_uuid_or_panic); + TeeIdentity { + login: self.login.into(), + uuid, + } + } +} + +/// Parse an RFC 4122 UUID string (hyphens optional) into a [`TeeUuid`]. +fn parse_uuid_or_panic(s: &str) -> TeeUuid { + let hex: String = s.chars().filter(|&c| c != '-').collect(); + assert_eq!(hex.len(), 32, "client uuid must be 32 hex digits: {s:?}"); + let mut bytes = [0u8; 16]; + for (i, byte) in bytes.iter_mut().enumerate() { + *byte = u8::from_str_radix(&hex[i * 2..i * 2 + 2], 16) + .unwrap_or_else(|_| panic!("invalid hex in client uuid: {s:?}")); + } + TeeUuid::from_bytes(bytes) } #[derive(Debug, Deserialize)] diff --git a/litebox_runner_optee_on_linux_userland/tests/aes-ta-cmds.json b/litebox_runner_optee_on_linux_userland/tests/aes-ta-cmds.json index 8bc266ac0..a80a3bcd0 100644 --- a/litebox_runner_optee_on_linux_userland/tests/aes-ta-cmds.json +++ b/litebox_runner_optee_on_linux_userland/tests/aes-ta-cmds.json @@ -1,6 +1,9 @@ [ { - "func_id": "open_session" + "func_id": "open_session", + "client_identity": { + "login": "user" + } }, { "func_id": "invoke_command", diff --git a/litebox_runner_optee_on_linux_userland/tests/hello-ta-cmds.json b/litebox_runner_optee_on_linux_userland/tests/hello-ta-cmds.json index a55e89770..9d87bf108 100644 --- a/litebox_runner_optee_on_linux_userland/tests/hello-ta-cmds.json +++ b/litebox_runner_optee_on_linux_userland/tests/hello-ta-cmds.json @@ -1,6 +1,9 @@ [ { - "func_id": "open_session" + "func_id": "open_session", + "client_identity": { + "login": "user" + } }, { "func_id": "invoke_command", diff --git a/litebox_runner_optee_on_linux_userland/tests/kmpp-ta-cmds.json b/litebox_runner_optee_on_linux_userland/tests/kmpp-ta-cmds.json index 9eb2e0cee..88d5e4187 100644 --- a/litebox_runner_optee_on_linux_userland/tests/kmpp-ta-cmds.json +++ b/litebox_runner_optee_on_linux_userland/tests/kmpp-ta-cmds.json @@ -1,6 +1,9 @@ [ { "func_id": "open_session", + "client_identity": { + "login": "user" + }, "args": [ { "param_type": "value_input", @@ -31,6 +34,9 @@ }, { "func_id": "open_session", + "client_identity": { + "login": "user" + }, "args": [ { "param_type": "value_input", diff --git a/litebox_runner_optee_on_linux_userland/tests/random-ta-cmds.json b/litebox_runner_optee_on_linux_userland/tests/random-ta-cmds.json index 166b1d718..8df2c67dc 100644 --- a/litebox_runner_optee_on_linux_userland/tests/random-ta-cmds.json +++ b/litebox_runner_optee_on_linux_userland/tests/random-ta-cmds.json @@ -1,6 +1,9 @@ [ { - "func_id": "open_session" + "func_id": "open_session", + "client_identity": { + "login": "user" + } }, { "func_id": "invoke_command", diff --git a/litebox_shim_optee/src/lib.rs b/litebox_shim_optee/src/lib.rs index a91fe4a6e..2644b1d7b 100644 --- a/litebox_shim_optee/src/lib.rs +++ b/litebox_shim_optee/src/lib.rs @@ -237,9 +237,11 @@ impl OpteeShim { thread: ThreadState::new(), session_id, ta_app_id: ta_uuid, + // Fall back to the anonymous public identity when no client is + // supplied (matches OP-TEE OS / the Linux driver). client_identity: client.unwrap_or(TeeIdentity { - login: TeeLogin::User, - uuid: TeeUuid::default(), + login: TeeLogin::Public, + uuid: TeeUuid::NIL, }), tee_cryp_state_map: TeeCrypStateMap::new(), tee_obj_map: TeeObjMap::new(), @@ -1428,8 +1430,8 @@ mod test_utils { session_id: SessionIdPool::allocate().unwrap(), ta_app_id: TeeUuid::default(), client_identity: TeeIdentity { - login: TeeLogin::User, - uuid: TeeUuid::default(), + login: TeeLogin::Public, + uuid: TeeUuid::NIL, }, tee_cryp_state_map: TeeCrypStateMap::new(), tee_obj_map: TeeObjMap::new(), diff --git a/litebox_shim_optee/src/msg_handler.rs b/litebox_shim_optee/src/msg_handler.rs index b1c125101..831e0edf8 100644 --- a/litebox_shim_optee/src/msg_handler.rs +++ b/litebox_shim_optee/src/msg_handler.rs @@ -408,25 +408,43 @@ pub fn decode_ta_request( let (ta_uuid, client_identity, skip): (Option, Option, usize) = if ta_entry_func == UteeEntryFunc::OpenSession { - // If it is an OpenSession request, extract UUIDs and login from params[0] and params[1] - // Based on observed Linux kernel behavior: + // If it is an OpenSession request, extract the TA UUID, client UUID, + // and login from the two meta params. Wire layout (per the Linux + // OP-TEE driver): // - params[0].a/b = TA UUID (two little-endian u64 values) - // - params[1].a/b = client UUID (two little-endian u64 values) + // - params[1].a/b = client UUID, but only meaningful for the + // user/group/application logins; PUBLIC/REE_KERNEL ignore it and + // report the nil UUID (see the match below). // - params[1].c = client login type (TEE_LOGIN_*) - let param0 = msg_args.get_param_value(0)?; + let param0 = msg_args.get_meta_param_value(0)?; let ta_data = [param0.a, param0.b]; - let param1 = msg_args.get_param_value(1)?; - let client_data = [param1.a, param1.b]; + let param1 = msg_args.get_meta_param_value(1)?; let login: u32 = param1.c.trunc(); - let login = TeeLogin::try_from(login).unwrap_or(TeeLogin::Public); + // Reject unknown login methods + let login = TeeLogin::try_from(login).map_err(|_| OpteeSmcReturnCode::EBadCmd)?; + + // Only the REE-derived user/group/application logins carry a + // meaningful client UUID. PUBLIC and REE_KERNEL clients are anonymous + // and must report the nil UUID (OP-TEE OS memsets it to zero). + // TRUSTED_APP identifies a TA-to-TA caller and is established + // internally, never from a normal-world message. + let client_uuid = match login { + TeeLogin::Public | TeeLogin::ReeKernel => TeeUuid::NIL, + TeeLogin::User + | TeeLogin::Group + | TeeLogin::Application + | TeeLogin::ApplicationUser + | TeeLogin::ApplicationGroup => TeeUuid::from_u64_array([param1.a, param1.b]), + TeeLogin::TrustedApp => return Err(OpteeSmcReturnCode::EBadCmd), + }; // Skip the first two parameters as they convey TA and client UUIDs ( Some(TeeUuid::from_u64_array(ta_data)), Some(TeeIdentity { login, - uuid: TeeUuid::from_u64_array(client_data), + uuid: client_uuid, }), 2, )