Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 32 additions & 54 deletions litebox_shim_optee/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use litebox::{
shim::ContinueOperation,
utils::TruncateExt,
};
use litebox_common_linux::{MapFlags, ProtFlags, errno::Errno};
use litebox_common_linux::errno::Errno;
use litebox_common_optee::{
LdelfArg, LdelfSyscallRequest, SyscallRequest, TaFlags, TeeAlgorithm, TeeAlgorithmClass,
TeeAttributeType, TeeCrypStateHandle, TeeHandleFlag, TeeIdentity, TeeLogin, TeeObjHandle,
Expand Down Expand Up @@ -270,8 +270,6 @@ impl OpteeShim {
ta_entry_point: Cell::new(0),
ta_stack_base_addr: Cell::new(0),
ta_prepared: Cell::new(false),
#[cfg(target_arch = "x86_64")]
tls_base_addr: Cell::new(0),
},
};
if let Some(ta_bin) = ta_bin
Expand Down Expand Up @@ -784,15 +782,9 @@ impl Task {
let ta_entry_point = self.get_ta_entry_point();
let mut elf_loader = loader::elf::ElfLoader::new(self, &ta_bin, false)?;
elf_loader.load_ta_trampoline(ta_entry_point)?;
self.allocate_guest_tls(None).map_err(|_| {
ElfLoaderError::MappingError(litebox::mm::linux::MappingError::OutOfMemory)
})?;
self.ta_prepared.set(true);
}

#[cfg(target_arch = "x86_64")]
self.restore_guest_tls();

let mut ta_stack =
crate::loader::ta_stack::allocate_stack(self, self.get_ta_stack_base_addr()).ok_or(
ElfLoaderError::MappingError(litebox::mm::linux::MappingError::OutOfMemory),
Expand All @@ -801,6 +793,18 @@ impl Task {
.init(self.global.platform, params)
.ok_or(ElfLoaderError::InvalidStackAddr)?;

// Point the FS base at the TA stack canary so the compiler's
// stack-guard reads resolve to it. Must run after
// `ta_stack.init` (which pushes the canary) and on every entry,
// because the guest FS base does not persist across shim<->guest
// transitions (see `set_guest_stack_guard_fs_base`).
#[cfg(target_arch = "x86_64")]
Self::set_guest_stack_guard_fs_base(
ta_stack
.canary_addr()
.ok_or(ElfLoaderError::InvalidStackAddr)?,
)?;

Ok(ThreadInitState::Ta {
cmd_id: cmd_id.unwrap_or(0) as usize,
params_address: ta_stack.get_params_address(),
Expand Down Expand Up @@ -840,52 +844,31 @@ impl Task {
}
}

/// Allocate the guest TLS for an OP-TEE TA.
/// Point the guest FS base at the TA stack canary so the compiler's stack
/// guard read resolves onto it.
///
/// This function is required to overcome the compatibility issue coming from
/// system and build toolchain differences. OP-TEE OS only supports a single thread and
/// thus does not explicitly set up the TLS area. In contrast, we do use an x86 toolchain to
/// compile OP-TEE TAs and this toolchain assumes there is a valid TLS areas for various purposes
/// including stack protection. To this end, the toolchain generates binaries using
/// the `FS` register for TLS access.
/// This function allocates a TLS area on behalf of the TA to satisfy the toolchain's assumption.
/// Instead of using this function, we could change the flags of the toolchain to not use TLS
/// (e.g., `-fno-stack-protector`), but this might be insecure. Also, the toolchain might have
/// other features relying on TLS.
#[cfg(target_arch = "x86_64")]
fn allocate_guest_tls(
&self,
tls_size: Option<usize>,
) -> Result<(), litebox_common_linux::errno::Errno> {
let tls_size = tls_size.unwrap_or(PAGE_SIZE).next_multiple_of(PAGE_SIZE);
let addr = self.sys_mmap(
0,
tls_size,
ProtFlags::PROT_READ | ProtFlags::PROT_WRITE,
MapFlags::MAP_PRIVATE | MapFlags::MAP_ANONYMOUS,
-1,
0,
)?;
// Store TLS address for later restoration
self.tls_base_addr.set(addr.as_usize());
self.restore_guest_tls();
Ok(())
}

/// Restore the guest TLS (FS base) before entering the TA.
/// OP-TEE TAs are single-threaded and have no TLS block, but the x86-64
/// toolchain still emits stack-protector reads of `%fs:0x28`. To satisfy
/// those reads without allocating a TLS area, we set the FS base to
/// `canary_addr - ABI_STACK_GUARD_FS_OFFSET` (shared by GCC, Clang, glibc,
/// and musl), so the compiler's guard read lands on the TA stack canary.
///
/// FS base is cleared across VTL switches, so we must restore it before
/// every TA entry.
/// This must be called on every TA entry: the guest FS base is not
/// guaranteed to persist across a shim<->guest transition. Re-establishing
/// it here keeps the shim platform-agnostic.
///
/// Returns [`ElfLoaderError::InvalidStackAddr`] if `canary_addr` is below
/// `ABI_STACK_GUARD_FS_OFFSET`.
#[cfg(target_arch = "x86_64")]
fn restore_guest_tls(&self) {
fn set_guest_stack_guard_fs_base(canary_addr: usize) -> Result<(), ElfLoaderError> {
use litebox::platform::ArchSpecificProvider as _;
let addr = self.tls_base_addr.get();
if addr == 0 {
return; // TLS not allocated yet
}
let fs_base = canary_addr
.checked_sub(crate::loader::ta_stack::ABI_STACK_GUARD_FS_OFFSET)
.ok_or(ElfLoaderError::InvalidStackAddr)?;
litebox_platform_multiplex::platform()
.set_arch_specific_register(&litebox::platform::ArchSpecificRegister::FsBase, addr)
.set_arch_specific_register(&litebox::platform::ArchSpecificRegister::FsBase, fs_base)
.expect("requires guaranteed platform support for FsBase");
Ok(())
}

/// Retrieve the result of the `ldelf` execution.
Expand Down Expand Up @@ -1364,9 +1347,6 @@ struct Task {
ta_stack_base_addr: Cell<usize>,
/// Whether the TA has been prepared
ta_prepared: Cell<bool>,
/// TLS base address for x86_64 (stored to restore FS before each TA entry)
#[cfg(target_arch = "x86_64")]
tls_base_addr: Cell<usize>,
// TODO: OP-TEE supports global, persistent objects across sessions. Add these maps if needed.
}

Expand Down Expand Up @@ -1519,8 +1499,6 @@ mod test_utils {
ta_entry_point: Cell::new(0),
ta_stack_base_addr: Cell::new(0),
ta_prepared: Cell::new(false),
#[cfg(target_arch = "x86_64")]
tls_base_addr: Cell::new(0),
}
}
}
Expand Down
41 changes: 40 additions & 1 deletion litebox_shim_optee/src/loader/ta_stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,24 @@ use zerocopy::IntoBytes;

use crate::{Platform, UserMutPtr};

/// Offset of the stack-protector guard from the x86-64 thread pointer (`%fs`).
///
/// This is a compiler + C-library convention: when using the default TLS-based
/// stack protector, both GCC and Clang emit the stack-guard access as
/// `%fs:0x28`, matching the `stack_guard` slot in the glibc/musl `tcbhead_t`.
/// The offset is nominally tunable via `-mstack-protector-guard-offset`, but
/// `0x28` is the fixed default across GCC, Clang, glibc, and musl on x86-64,
/// so we treat it as a constant here.
///
/// The guard itself is a single pointer-sized word, i.e., **8 bytes** on x86-64.
/// The read therefore spans `[%fs:0x28 .. %fs:0x30)`.
///
/// OP-TEE TAs have no TLS block, so the shim sets the guest FS base to
/// `canary_addr - ABI_STACK_GUARD_FS_OFFSET`, making that read resolve onto the
/// CRNG canary pushed by [`TaStack::init`].
#[cfg(target_arch = "x86_64")]
pub(crate) const ABI_STACK_GUARD_FS_OFFSET: usize = 0x28;

#[inline]
fn align_down(addr: usize, align: usize) -> usize {
debug_assert!(align.is_power_of_two());
Expand Down Expand Up @@ -62,6 +80,9 @@ pub struct TaStack {
num_params: usize,
/// Position where LdelfArg was pushed (if any)
ldelf_arg_pos: Option<usize>,
#[cfg(target_arch = "x86_64")]
/// Position where the TA stack canary was pushed (if any).
canary_pos: Option<usize>,
}

impl TaStack {
Expand All @@ -84,6 +105,7 @@ impl TaStack {
params: UteeParams::new(),
num_params: 0,
ldelf_arg_pos: None,
canary_pos: None,
})
}

Expand All @@ -101,6 +123,13 @@ impl TaStack {
self.stack_top.as_usize() + self.len - core::mem::size_of::<UteeParams>()
}

/// Get the address of the TA stack canary pushed by [`Self::init`].
/// Returns `None` if no canary has been pushed yet.
#[cfg(target_arch = "x86_64")]
pub(crate) fn canary_addr(&self) -> Option<usize> {
self.canary_pos.map(|pos| self.stack_top.as_usize() + pos)
}

/// Get the address of `LdelfArg` on the stack.
///
/// Returns the actual address where `LdelfArg` was pushed via `init_with_ldelf_arg`.
Expand All @@ -124,6 +153,16 @@ impl TaStack {
Some(())
}

/// Push the TA stack canary and record its address.
fn push_canary(&mut self, canary: &[u8; 16]) -> Option<()> {
self.push_bytes(canary)?;
#[cfg(target_arch = "x86_64")]
{
self.canary_pos = Some(self.pos);
}
Some(())
}

/// Zero the unused stack region before a new session writes its parameters,
/// to avoid leaking leftover data from a prior session whose stack region was recycled.
/// The trailing `UteeParams` slot is left untouched here because
Expand Down Expand Up @@ -262,7 +301,7 @@ impl TaStack {
// Random 16-byte stack canary
let mut canary = [0u8; 16];
<Platform as litebox::platform::CrngProvider>::fill_bytes_crng(platform, &mut canary);
self.push_bytes(&canary)?;
self.push_canary(&canary)?;

// `reenter_thread` *jumps* into the TA entry point (which is a function) rather than
// calls it. Adjust the stack pointer to ensure post-call stack alignment.
Expand Down