Skip to content

Commit 86e26df

Browse files
sangho2Sangho Lee
andauthored
Add support for running multiple TA instances with page table isolation (#632)
This PR adds support for running **multiple TAs** concurrently with **per-instance page table isolation** on LVBS. **Page Table Management** - `PageTableManager` manages base page table and task-specific page tables - Each TA instance gets its own task page table - Base page table contains only VTL1 kernel mappings **Session/Instance Management** - New `session.rs` module manages TA session/instance - Each instance tracks its page table ID for memory isolation - Sessions can share the same instance with a try-lock - Proper cleanup of page tables when sessions/instances are closed or TA panics **OP-TEE Shim Updates** - Update message handler and syscalls to work with per-instance page tables - Refactor pointer validation (`ptr.rs`) for the new isolation model - Enhance ELF loader integration with session-aware memory management **Runner Updates** - Significant refactoring to support multi-TA dispatch --------- Co-authored-by: Sangho Lee <sanghle@microsoft.com>
1 parent 90c7ed9 commit 86e26df

28 files changed

Lines changed: 2516 additions & 1040 deletions

File tree

Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dev_tests/src/ratchet.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ fn ratchet_globals() -> Result<()> {
4444
("litebox_runner_lvbs/", 4),
4545
("litebox_runner_snp/", 1),
4646
("litebox_shim_linux/", 1),
47-
("litebox_shim_optee/", 2),
47+
("litebox_shim_optee/", 3),
4848
],
4949
|file| {
5050
Ok(file

litebox_common_optee/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ edition = "2024"
55

66
[dependencies]
77
bitflags = "2.9.0"
8+
elf = { version = "0.8.0", default-features = false }
89
litebox = { path = "../litebox/", version = "0.1.0" }
910
litebox_common_linux = { path = "../litebox_common_linux/", version = "0.1.0" }
1011
modular-bitfield = { version = "0.12.0", default-features = false }

litebox_common_optee/src/lib.rs

Lines changed: 166 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@ extern crate alloc;
1010

1111
use alloc::boxed::Box;
1212
use litebox::platform::RawConstPointer as _;
13+
use litebox::utils::TruncateExt;
1314
use litebox_common_linux::{PtRegs, errno::Errno};
1415
use modular_bitfield::prelude::*;
1516
use modular_bitfield::specifiers::{B8, B54};
1617
use num_enum::TryFromPrimitive;
1718
use syscall_nr::{LdelfSyscallNr, TeeSyscallNr};
18-
use zerocopy::{FromBytes, IntoBytes};
19+
use zerocopy::{FromBytes, Immutable, IntoBytes};
1920

2021
pub mod syscall_nr;
2122

@@ -394,7 +395,7 @@ impl CommandId {
394395
/// `utee_params` from `optee_os/lib/libutee/include/utee_types.h`
395396
/// It contains up to 4 parameters where each of them is a collection of
396397
/// type (4 bits) and two 8-byte data (values or addresses).
397-
#[derive(Clone, Copy, Default, FromBytes, IntoBytes)]
398+
#[derive(Clone, Copy, Default, FromBytes, Immutable, IntoBytes)]
398399
#[repr(C)]
399400
pub struct UteeParams {
400401
pub types: UteeParamsTypes,
@@ -409,9 +410,9 @@ const TEE_NUM_PARAMS: usize = 4;
409410
mod workaround_identity_op_suppression {
410411
use modular_bitfield::prelude::*;
411412
use modular_bitfield::specifiers::{B4, B48};
412-
use zerocopy::{FromBytes, IntoBytes};
413+
use zerocopy::{FromBytes, Immutable, IntoBytes};
413414
#[bitfield]
414-
#[derive(Clone, Copy, Default, FromBytes, IntoBytes)]
415+
#[derive(Clone, Copy, Default, FromBytes, Immutable, IntoBytes)]
415416
#[repr(C)]
416417
pub struct UteeParamsTypes {
417418
pub type_0: B4,
@@ -539,7 +540,7 @@ open_enum! {
539540

540541
/// `TEE_UUID` from `optee_os/lib/libutee/include/tee_api_types.h`. It uniquely identifies
541542
/// TAs, cryptographic keys, and more.
542-
#[derive(Clone, Copy, PartialEq, Eq, Hash, Default, Debug, FromBytes, IntoBytes)]
543+
#[derive(Clone, Copy, PartialEq, Eq, Hash, Default, Debug, FromBytes, Immutable, IntoBytes)]
543544
#[repr(C)]
544545
pub struct TeeUuid {
545546
pub time_low: u32,
@@ -571,35 +572,101 @@ impl TeeUuid {
571572
}
572573
}
573574

574-
/// Converts a UUID from OP-TEE's u32 array representation.
575+
/// Converts a UUID from OP-TEE's u64 array representation (Linux kernel format).
575576
///
576-
/// OP-TEE passes UUIDs in SMC calls as 4 u32 values where:
577-
/// - `data[0]` = `time_low` (u32)
578-
/// - `data[1]` = `(time_mid << 16) | time_hi_and_version`
579-
/// - `data[2..3]` = `clock_seq_and_node` (8 bytes as 2 u32s, big-endian byte order)
580-
///
581-
/// For example, UUID `384fb3e0-e7f8-11e3-af63-0002a5d5c51b` is represented as:
582-
/// `[0x384fb3e0, 0xe7f811e3, 0xaf630002, 0xa5d5c51b]`
583-
pub fn from_u32_array(data: [u32; 4]) -> Self {
577+
/// The Linux kernel packs UUIDs as two little-endian u64 values via `export_uuid()`:
578+
/// ```c
579+
/// *a = get_unaligned_le64(p); // bytes[0..8] as little-endian u64
580+
/// *b = get_unaligned_le64(p + 8); // bytes[8..16] as little-endian u64
581+
/// ```
582+
pub fn from_u64_array(data: [u64; 2]) -> Self {
584583
let mut bytes = [0u8; 16];
585-
bytes[0..4].copy_from_slice(&data[0].to_be_bytes());
586-
bytes[4..8].copy_from_slice(&data[1].to_be_bytes());
587-
bytes[8..12].copy_from_slice(&data[2].to_be_bytes());
588-
bytes[12..16].copy_from_slice(&data[3].to_be_bytes());
584+
bytes[0..8].copy_from_slice(&data[0].to_le_bytes());
585+
bytes[8..16].copy_from_slice(&data[1].to_le_bytes());
589586
Self::from_bytes(bytes)
590587
}
588+
}
591589

592-
/// Converts a UUID from OP-TEE's u64 array representation.
593-
pub fn from_u64_array(data: [u64; 2]) -> Self {
594-
let mut bytes = [0u8; 16];
595-
bytes[0..8].copy_from_slice(&data[0].to_be_bytes());
596-
bytes[8..16].copy_from_slice(&data[1].to_be_bytes());
597-
Self::from_bytes(bytes)
590+
/// TA flags from `optee_os/lib/libutee/include/user_ta_header.h`.
591+
#[derive(Clone, Copy, PartialEq, Eq, Default, Debug, FromBytes, IntoBytes)]
592+
#[repr(transparent)]
593+
pub struct TaFlags(u32);
594+
595+
bitflags::bitflags! {
596+
impl TaFlags: u32 {
597+
/// TA has only one instance (deprecated flag, was USER_MODE)
598+
const USER_MODE = 0;
599+
/// TA executes from DDR (deprecated flag)
600+
const EXEC_DDR = 0;
601+
/// Only one TA instance exists at a time
602+
const SINGLE_INSTANCE = 0x0000_0004;
603+
/// Multiple sessions can share the instance
604+
const MULTI_SESSION = 0x0000_0008;
605+
/// Instance remains after last session closes
606+
const INSTANCE_KEEP_ALIVE = 0x0000_0010;
607+
/// TA accesses SDP memory
608+
const SECURE_DATA_PATH = 0x0000_0020;
609+
/// TA uses cache flush syscall
610+
const CACHE_MAINTENANCE = 0x0000_0080;
611+
/// TA can execute multiple sessions concurrently (pseudo-TAs only)
612+
const CONCURRENT = 0x0000_0100;
613+
/// Device enumeration at stage 1 (kernel driver init)
614+
const DEVICE_ENUM = 0x0000_0200;
615+
/// Device enumeration at stage 3 (with tee-supplicant)
616+
const DEVICE_ENUM_SUPP = 0x0000_0400;
617+
/// Don't close handle on corrupt object
618+
const DONT_CLOSE_HANDLE_ON_CORRUPT_OBJECT = 0x0000_0800;
619+
/// Device enumeration when TEE_STORAGE_PRIVATE is available
620+
const DEVICE_ENUM_TEE_STORAGE_PRIVATE = 0x0000_1000;
621+
/// Don't restart keep-alive TA if it crashed
622+
const INSTANCE_KEEP_CRASHED = 0x0000_2000;
623+
}
624+
}
625+
626+
impl TaFlags {
627+
/// Returns true if this TA should only have one instance.
628+
pub fn is_single_instance(&self) -> bool {
629+
self.contains(TaFlags::SINGLE_INSTANCE)
630+
}
631+
632+
/// Returns true if multiple sessions can share the TA instance.
633+
///
634+
/// Note: This flag is only meaningful when `SINGLE_INSTANCE` is also set.
635+
/// For non-single-instance TAs, each session gets its own instance anyway.
636+
pub fn is_multi_session(&self) -> bool {
637+
self.contains(TaFlags::MULTI_SESSION)
638+
}
639+
640+
/// Returns true if the TA instance should persist after all sessions close.
641+
///
642+
/// Note: This flag is only meaningful when `SINGLE_INSTANCE` is also set.
643+
/// For non-single-instance TAs, instances are always destroyed when their session closes.
644+
pub fn is_keep_alive(&self) -> bool {
645+
self.contains(TaFlags::INSTANCE_KEEP_ALIVE)
598646
}
599647
}
600648

649+
/// TA header structure from `optee_os/lib/libutee/include/user_ta_header.h`.
650+
///
651+
/// This structure is placed at the beginning of the `.ta_head` section in TA ELF binaries.
652+
#[derive(Clone, Copy, Debug, FromBytes, IntoBytes)]
653+
#[repr(C)]
654+
pub struct TaHead {
655+
/// TA UUID
656+
pub uuid: TeeUuid,
657+
/// Stack size in bytes
658+
pub stack_size: u32,
659+
/// TA flags (see `TaFlags`)
660+
pub flags: TaFlags,
661+
/// Deprecated entry point field
662+
pub depr_entry: u64,
663+
}
664+
665+
/// Name of the ELF section containing the TA header.
666+
pub const TA_HEAD_SECTION_NAME: &str = ".ta_head";
667+
601668
/// `TEE_Identity` from `optee_os/lib/libutee/include/tee_api_types.h`.
602-
#[derive(Clone, Copy, PartialEq)]
669+
#[derive(Clone, Copy, PartialEq, Immutable, IntoBytes)]
603670
#[repr(C)]
604671
pub struct TeeIdentity {
605672
pub login: TeeLogin,
@@ -683,7 +750,7 @@ const TEE_LOGIN_APPLICATION_GROUP: u32 = 0x6;
683750
const TEE_LOGIN_TRUSTED_APP: u32 = 0xf000_0000;
684751

685752
/// `TEE Login type` from `optee_os/lib/libutee/include/tee_api_defines.h`
686-
#[derive(Clone, Copy, PartialEq)]
753+
#[derive(Clone, Copy, PartialEq, TryFromPrimitive, Immutable, IntoBytes)]
687754
#[repr(u32)]
688755
pub enum TeeLogin {
689756
Public = TEE_LOGIN_PUBLIC,
@@ -727,10 +794,10 @@ impl TeeOperationMode {
727794
open_enum! {
728795
/// Origin code constants from `optee_os/lib/libutee/include/tee_api_defines.h`
729796
pub enum TeeOrigin: u32 {
730-
Api = 0,
731-
Comms = 1,
732-
Tee = 2,
733-
TrustedApp = 3,
797+
Api = 1,
798+
Comms = 2,
799+
Tee = 3,
800+
TrustedApp = 4,
734801
}
735802
}
736803

@@ -897,7 +964,7 @@ const TEE_ERROR_TIME_NOT_SET: u32 = 0xffff_5000;
897964
const TEE_ERROR_TIME_NEEDS_RESET: u32 = 0xffff_5001;
898965

899966
/// `TEE_Result` (API error codes) from `optee_os/lib/libutee/include/tee_api_defines.h`
900-
#[derive(Clone, Copy, TryFromPrimitive)]
967+
#[derive(Clone, Copy, TryFromPrimitive, PartialEq, Debug)]
901968
#[repr(u32)]
902969
pub enum TeeResult {
903970
Success = TEE_SUCCESS,
@@ -931,7 +998,6 @@ pub enum TeeResult {
931998
SignatureInvalid = TEE_ERROR_SIGNATURE_INVALID,
932999
TimeNotSet = TEE_ERROR_TIME_NOT_SET,
9331000
TimeNeedsReset = TEE_ERROR_TIME_NEEDS_RESET,
934-
Unknown = 0xffff_ffff,
9351001
}
9361002

9371003
impl From<TeeResult> for u32 {
@@ -1121,7 +1187,7 @@ bitflags::bitflags! {
11211187
}
11221188

11231189
/// `ldef_arg` from `optee_os/ldelf/include/ldelf.h`
1124-
#[derive(Clone, Copy, Default, FromBytes, IntoBytes)]
1190+
#[derive(Clone, Copy, Default, FromBytes, Immutable, IntoBytes)]
11251191
#[repr(C)]
11261192
pub struct LdelfArg {
11271193
pub uuid: TeeUuid,
@@ -1220,7 +1286,7 @@ pub struct OpteeMsgParamFmem {
12201286
/// Lower bits of offset into shared memory reference
12211287
pub offs_low: u32,
12221288
/// Higher bits of offset into shared memory reference
1223-
pub offs_high: u32,
1289+
pub offs_high: u16,
12241290
/// Internal offset into the first page of shared memory reference
12251291
pub internal_offs: u16,
12261292
/// Size of the buffer
@@ -1370,7 +1436,7 @@ pub struct OpteeMsgArgs {
13701436
pub cancel_id: u32,
13711437
pad: u32,
13721438
/// Return value from the secure world
1373-
pub ret: u32,
1439+
pub ret: TeeResult,
13741440
/// Origin of the return value
13751441
pub ret_origin: TeeOrigin,
13761442
/// Number of parameters contained in `params`
@@ -1379,6 +1445,9 @@ pub struct OpteeMsgArgs {
13791445
/// a TA UUID and they are not delivered to the TA.
13801446
/// Note that, originally, the length of this array is variable. We fix it to `TEE_NUM_PARAMS + 2` to
13811447
/// simplify the implementation (our OP-TEE Shim supports up to four parameters as well).
1448+
///
1449+
/// TODO: To support OP-TEE RPC, we should make this array length dynamic. Consider to use
1450+
/// a trailing unsized slice (DST) or other mechanisms.
13821451
pub params: [OpteeMsgParam; TEE_NUM_PARAMS + 2],
13831452
}
13841453

@@ -1444,6 +1513,22 @@ impl OpteeMsgArgs {
14441513
Ok(())
14451514
}
14461515
}
1516+
1517+
/// Set the size field for a memref parameter (rmem or tmem).
1518+
/// This updates `rmem.size` or `tmem.size` which share the same offset as `value.b` in the union.
1519+
pub fn set_param_memref_size(
1520+
&mut self,
1521+
index: usize,
1522+
size: u64,
1523+
) -> Result<(), OpteeSmcReturnCode> {
1524+
if index >= self.num_params as usize {
1525+
Err(OpteeSmcReturnCode::ENotAvail)
1526+
} else {
1527+
// rmem.size and tmem.size are at the same offset as value.b in the union
1528+
self.params[index].u.rmem.size = size;
1529+
Ok(())
1530+
}
1531+
}
14471532
}
14481533

14491534
/// A memory page to exchange OP-TEE SMC call arguments.
@@ -1493,7 +1578,7 @@ impl OpteeSmcArgs {
14931578

14941579
/// Get the physical address of `OpteeMsgArgs`. The secure world is expected to map and copy
14951580
/// this structure.
1496-
pub fn optee_msg_arg_phys_addr(&self) -> Result<u64, OpteeSmcReturnCode> {
1581+
pub fn optee_msg_args_phys_addr(&self) -> Result<u64, OpteeSmcReturnCode> {
14971582
// To avoid potential sign extension and overflow issues, OP-TEE stores the low and
14981583
// high 32 bits of a 64-bit address in `args[2]` and `args[1]`, respectively.
14991584
if self.args[1] & 0xffff_ffff_0000_0000 == 0 && self.args[2] & 0xffff_ffff_0000_0000 == 0 {
@@ -1503,6 +1588,11 @@ impl OpteeSmcArgs {
15031588
Err(OpteeSmcReturnCode::EBadAddr)
15041589
}
15051590
}
1591+
1592+
/// Set the return code of an OP-TEE SMC call
1593+
pub fn set_return_code(&mut self, code: OpteeSmcReturnCode) {
1594+
self.args[0] = code as usize;
1595+
}
15061596
}
15071597

15081598
/// `OPTEE_SMC_FUNCID_*` from `core/arch/arm/include/sm/optee_smc.h`
@@ -1568,7 +1658,7 @@ pub enum OpteeSmcResult<'a> {
15681658
shm_lower32: usize,
15691659
},
15701660
CallWithArg {
1571-
msg_arg: Box<OpteeMsgArgs>,
1661+
msg_args: Box<OpteeMsgArgs>,
15721662
},
15731663
}
15741664

@@ -1701,19 +1791,51 @@ impl From<OpteeSmcReturnCode> for litebox_common_linux::errno::Errno {
17011791
}
17021792
}
17031793

1794+
/// Parse the `.ta_head` section from a raw ELF binary.
1795+
///
1796+
/// This function searches for the `.ta_head` section in the ELF and parses the `TaHead`
1797+
/// structure from it. Returns `None` if the section is not found or cannot be parsed.
1798+
///
1799+
/// # Arguments
1800+
/// * `elf_data` - Raw bytes of the ELF binary
1801+
pub fn parse_ta_head(elf_data: &[u8]) -> Option<TaHead> {
1802+
use core::mem::size_of;
1803+
use elf::{ElfBytes, endian::AnyEndian};
1804+
1805+
let elf = ElfBytes::<AnyEndian>::minimal_parse(elf_data).ok()?;
1806+
let (shdrs, strtab) = elf.section_headers_with_strtab().ok()?;
1807+
let shdrs = shdrs?;
1808+
let strtab = strtab?;
1809+
1810+
for shdr in shdrs {
1811+
let name = strtab.get(shdr.sh_name as usize).ok()?;
1812+
if name == TA_HEAD_SECTION_NAME {
1813+
let offset: usize = shdr.sh_offset.truncate();
1814+
let size: usize = shdr.sh_size.truncate();
1815+
1816+
if size < size_of::<TaHead>() {
1817+
return None;
1818+
}
1819+
1820+
return TaHead::read_from_bytes(&elf_data[offset..offset + size_of::<TaHead>()]).ok();
1821+
}
1822+
}
1823+
None
1824+
}
1825+
17041826
#[cfg(test)]
17051827
mod tests {
17061828
use super::*;
17071829

17081830
#[test]
1709-
fn test_tee_uuid_from_u32_array() {
1831+
fn test_tee_uuid_from_u64_array() {
17101832
// Test with OP-TEE's well-known UUID: 384fb3e0-e7f8-11e3-af63-0002a5d5c51b
1711-
// As documented in optee_msg.h:
1712-
// OPTEE_MSG_UID_0 = 0x384fb3e0
1713-
// OPTEE_MSG_UID_1 = 0xe7f811e3
1714-
// OPTEE_MSG_UID_2 = 0xaf630002
1715-
// OPTEE_MSG_UID_3 = 0xa5d5c51b
1716-
let uuid = TeeUuid::from_u32_array([0x384fb3e0, 0xe7f811e3, 0xaf630002, 0xa5d5c51b]);
1833+
// UUID bytes (big-endian for time fields):
1834+
// [0x38, 0x4f, 0xb3, 0xe0, 0xe7, 0xf8, 0x11, 0xe3, 0xaf, 0x63, 0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b]
1835+
// When read as two little-endian u64 values:
1836+
// data[0] = bytes[0..8] as LE u64 = 0xe311f8e7_e0b34f38
1837+
// data[1] = bytes[8..16] as LE u64 = 0x1bc5d5a5_020063af
1838+
let uuid = TeeUuid::from_u64_array([0xe311f8e7_e0b34f38, 0x1bc5d5a5_020063af]);
17171839

17181840
assert_eq!(uuid.time_low, 0x384fb3e0);
17191841
assert_eq!(uuid.time_mid, 0xe7f8);

0 commit comments

Comments
 (0)