Skip to content
Draft
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
89 changes: 41 additions & 48 deletions litebox_shim_optee/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ impl OpteeShim {
ta_stack_base_addr: Cell::new(0),
ta_prepared: Cell::new(false),
#[cfg(target_arch = "x86_64")]
tls_base_addr: Cell::new(0),
stack_guard_page_addr: Cell::new(0),
},
};
if let Some(ta_bin) = ta_bin
Expand Down Expand Up @@ -783,14 +783,11 @@ 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.allocate_stack_guard_page()?;
self.ta_prepared.set(true);
}

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

let mut ta_stack =
crate::loader::ta_stack::allocate_stack(self, self.get_ta_stack_base_addr()).ok_or(
Expand Down Expand Up @@ -839,51 +836,47 @@ impl Task {
}
}

/// Allocate the guest TLS for an OP-TEE TA.
/// Allocate and initialize the page backing the x86-64 stack-guard slot.
///
/// 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();
/// The x86-64 toolchain emits stack-protector accesses to `%fs:0x28`.
/// Normally glibc or musl initializes that ABI slot before application code
/// runs. OP-TEE TAs use neither runtime, so the shim must provide and

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OP-TEE TAs use neither runtime

Is this specific to TAs running in LiteBox?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a stale/draft PR.

The main issue is that libutee we are using is unaware of x86-gcc/libc's convention, such that it does not prepare FSBase as gcc expects. Revising the devkit (or its build configuration) would be a fundamental solution.

/// initialize the slot before entering a protected TA.
fn allocate_stack_guard_page(&self) -> Result<(), ElfLoaderError> {
use litebox::platform::CrngProvider as _;

let page = self
.sys_mmap(
0,
PAGE_SIZE,
ProtFlags::PROT_READ_WRITE,
MapFlags::MAP_PRIVATE | MapFlags::MAP_ANONYMOUS | MapFlags::MAP_POPULATE,
-1,
0,
)
.map_err(|_| {
ElfLoaderError::MappingError(litebox::mm::linux::MappingError::OutOfMemory)
})?;
let mut guard = [0u8; core::mem::size_of::<usize>()];
self.global.platform.fill_bytes_crng(&mut guard);
// Terminator-canary convention (matches glibc `_dl_setup_stack_chk_guard`):
// zero the lowest-addressed byte of the guard to stop the overflow by C string func.
guard[0] = 0;
page.copy_from_slice(loader::ta_stack::ABI_STACK_GUARD_FS_OFFSET, &guard)
.ok_or(ElfLoaderError::InvalidStackAddr)?;
self.sys_mprotect(page, PAGE_SIZE, ProtFlags::PROT_READ)
.map_err(ElfLoaderError::ProtectError)?;
self.stack_guard_page_addr.set(page.as_usize());
Ok(())
}

/// Restore the guest TLS (FS base) before entering the TA.
///
/// FS base is cleared across VTL switches, so we must restore it before
/// every TA entry.
#[cfg(target_arch = "x86_64")]
fn restore_guest_tls(&self) {
/// Restore the guest FS base so `%fs:0x28` reads the stack guard page.
fn restore_stack_guard_fs_base(&self) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: As you are setting FSBase register value here, restore in the function name is a bit confusing.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using restore here because user-mode LiteBox and guests share FSBase.

use litebox::platform::ArchSpecificProvider as _;
let addr = self.tls_base_addr.get();
if addr == 0 {
return; // TLS not allocated yet
}
let fs_base = self.stack_guard_page_addr.get();
debug_assert_ne!(fs_base, 0);
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");
}

Expand Down Expand Up @@ -1363,9 +1356,9 @@ 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)
/// Base address of the read-only page containing the stack guard.
#[cfg(target_arch = "x86_64")]
tls_base_addr: Cell<usize>,
stack_guard_page_addr: Cell<usize>,
// TODO: OP-TEE supports global, persistent objects across sessions. Add these maps if needed.
}

Expand Down Expand Up @@ -1531,7 +1524,7 @@ mod test_utils {
ta_stack_base_addr: Cell::new(0),
ta_prepared: Cell::new(false),
#[cfg(target_arch = "x86_64")]
tls_base_addr: Cell::new(0),
stack_guard_page_addr: Cell::new(0),
}
}
}
Expand Down
17 changes: 17 additions & 0 deletions litebox_shim_optee/src/loader/ta_stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,23 @@ 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 provides a dedicated stack-guard
/// page whose guard word is stored at this offset.
#[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