secgate/
lib.rs

1#![feature(fn_traits)]
2#![feature(unboxed_closures)]
3#![feature(tuple_trait)]
4#![feature(auto_traits)]
5#![feature(negative_impls)]
6#![feature(linkage)]
7#![feature(maybe_uninit_as_bytes)]
8#![feature(thread_local)]
9
10use core::ffi::{c_char, CStr};
11use std::{
12    cell::{RefCell, UnsafeCell},
13    fmt::Debug,
14    marker::{PhantomData, Tuple},
15    mem::MaybeUninit,
16    sync::OnceLock,
17};
18
19pub use secgate_macros::*;
20use twizzler_abi::object::ObjID;
21pub use twizzler_rt_abi::error::{ResourceError, TwzError};
22
23pub mod util;
24
25/// A struct of information about a secure gate. These are auto-generated by the
26/// [crate::entry] macro, and stored in a special ELF section (.twz_secgate_info) as an array.
27/// The dynamic linker and monitor can then use this to easily enumerate gates.
28#[repr(C)]
29pub struct SecGateInfo<F> {
30    /// A pointer to the implementation entry function. This must be a pointer, and we statically
31    /// check that is has the same size as usize (sorry cheri, we'll fix this another time)
32    pub imp: F,
33    /// The name of this secure gate. This must be a pointer to a null-terminated C string.
34    name: *const c_char,
35}
36
37impl<F> core::fmt::Debug for SecGateInfo<F> {
38    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39        write!(f, "SecGateInfo({:p})", self.name)
40    }
41}
42
43impl<F> SecGateInfo<F> {
44    pub const fn new(imp: F, name: &'static CStr) -> Self {
45        Self {
46            imp,
47            name: name.as_ptr(),
48        }
49    }
50
51    pub fn name(&self) -> &CStr {
52        // Safety: we only ever construct self from a static CStr.
53        unsafe { CStr::from_ptr(self.name) }
54    }
55}
56
57// Safety: If F is Send, we are too because the name field points to a static C string that cannot
58// be written to.
59unsafe impl<F: Send> Send for SecGateInfo<F> {}
60// Safety: If F is Sync, we are too because the name field points to a static C string that cannot
61// be written to.
62unsafe impl<F: Sync> Sync for SecGateInfo<F> {}
63
64/// Minimum alignment of secure trampolines.
65pub const SECGATE_TRAMPOLINE_ALIGN: usize = 0x10;
66
67/// Non-generic and non-pointer-based SecGateInfo, for use during dynamic linking.
68pub type RawSecGateInfo = SecGateInfo<usize>;
69// Ensure that these are the same size because the dynamic linker uses the raw variant.
70static_assertions::assert_eq_size!(RawSecGateInfo, SecGateInfo<&fn()>);
71
72/// Arguments that will be passed to the secure call. Concrete versions of this are generated by the
73/// macro.
74#[derive(Clone, Copy)]
75#[repr(C)]
76pub struct Arguments<Args: Tuple + Crossing + Copy> {
77    args: Args,
78}
79
80impl<Args: Tuple + Crossing + Copy> Arguments<Args> {
81    pub fn with_alloca<F, R>(args: Args, f: F) -> R
82    where
83        F: FnOnce(&mut Self) -> R,
84    {
85        alloca::alloca(|stack_space| {
86            stack_space.write(Self { args });
87            // Safety: we init the MaybeUninit just above.
88            f(unsafe { stack_space.assume_init_mut() })
89        })
90    }
91
92    pub fn into_inner(self) -> Args {
93        self.args
94    }
95}
96
97/// Return value to be filled by the secure call. Concrete versions of this are generated by the
98/// macro.
99#[derive(Copy)]
100#[repr(C)]
101pub struct Return<T: Crossing + Copy> {
102    isset: bool,
103    ret: MaybeUninit<T>,
104}
105
106impl<T: Copy + Crossing> Clone for Return<T> {
107    fn clone(&self) -> Self {
108        *self
109    }
110}
111
112impl<T: Crossing + Copy> Return<T> {
113    pub fn with_alloca<F, R>(f: F) -> R
114    where
115        F: FnOnce(&mut Self) -> R,
116    {
117        alloca::alloca(|stack_space| {
118            stack_space.write(Self {
119                isset: false,
120                ret: MaybeUninit::uninit(),
121            });
122            // Safety: we init the MaybeUninit just above.
123            f(unsafe { stack_space.assume_init_mut() })
124        })
125    }
126
127    /// If a previous call to set is made, or this was constructed by new(), then into_inner
128    /// returns the inner value. Otherwise, returns None.
129    pub fn into_inner(self) -> Option<T> {
130        if self.isset {
131            Some(unsafe { self.ret.assume_init() })
132        } else {
133            None
134        }
135    }
136
137    /// Construct a new, uninitialized Self.
138    pub fn new_uninit() -> Self {
139        Self {
140            isset: false,
141            ret: MaybeUninit::uninit(),
142        }
143    }
144
145    /// Set the inner value. Future call to into_inner will return Some(val).
146    pub fn set(&mut self, val: T) {
147        self.ret.write(val);
148        self.isset = true;
149    }
150}
151
152/// An auto trait that limits the types that can be send across to another compartment. These are:
153/// 1. Types other than references, UnsafeCell, raw pointers, slices.
154/// 2. #[repr(C)] structs and enums made from Crossing types.
155///
156/// # Safety
157/// The type must meet the above requirements.
158pub unsafe auto trait Crossing {}
159
160impl<T> !Crossing for &T {}
161impl<T> !Crossing for &mut T {}
162impl<T: ?Sized> !Crossing for UnsafeCell<T> {}
163impl<T> !Crossing for *const T {}
164impl<T> !Crossing for *mut T {}
165impl<T> !Crossing for &[T] {}
166impl<T> !Crossing for &mut [T] {}
167
168unsafe impl<T: Crossing + Copy> Crossing for Result<T, TwzError> {}
169
170/// Required to put in your source if you call any secure gates.
171// TODO: this isn't ideal, but it's the only solution I have at the moment. For some reason,
172// the linker doesn't even bother linking the libcalloca.a library that alloca creates. This forces
173// that to happen.
174#[macro_export]
175macro_rules! secgate_prelude {
176    () => {
177        #[link(name = "calloca", kind = "static")]
178        extern "C" {
179            pub fn c_with_alloca();
180        }
181    };
182}
183
184#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Ord, Eq, Hash)]
185#[repr(C)]
186pub struct GateCallInfo {
187    thread_id: ObjID,
188    src_ctx: ObjID,
189}
190
191impl GateCallInfo {
192    /// Allocate a new GateCallInfo on the stack for the closure.
193    pub fn with_alloca<F, R>(thread_id: ObjID, src_ctx: ObjID, f: F) -> R
194    where
195        F: FnOnce(&mut Self) -> R,
196    {
197        alloca::alloca(|stack_space| {
198            stack_space.write(Self { thread_id, src_ctx });
199            // Safety: we init the MaybeUninit just above.
200            f(unsafe { stack_space.assume_init_mut() })
201        })
202    }
203
204    /// Get the ID of the source context, or None if the call was not cross-context.
205    pub fn source_context(&self) -> Option<ObjID> {
206        if self.src_ctx.raw() == 0 {
207            None
208        } else {
209            Some(self.src_ctx)
210        }
211    }
212
213    /// Get the ID of the calling thread.
214    pub fn thread_id(&self) -> ObjID {
215        if self.thread_id.raw() == 0 {
216            twizzler_abi::syscall::sys_thread_self_id()
217        } else {
218            self.thread_id
219        }
220    }
221
222    /// Ensures that the data is filled out (may read thread ID from kernel if necessary).
223    pub fn canonicalize(self) -> Self {
224        Self {
225            thread_id: self.thread_id(),
226            src_ctx: self.src_ctx,
227        }
228    }
229}
230
231fn get_tp() -> usize {
232    let mut val: usize;
233    unsafe {
234        #[cfg(target_arch = "x86_64")]
235        core::arch::asm!("rdfsbase {}", out(reg) val);
236        #[cfg(not(target_arch = "x86_64"))]
237        core::arch::asm!("mrs {}, tpidr_el0", out(reg) val);
238    }
239    val
240}
241
242/// Get the thread ID of the caller.
243pub fn get_thread_id() -> ObjID {
244    if !unsafe { __is_monitor_ready() } {
245        return twizzler_abi::syscall::sys_thread_self_id();
246    }
247    #[thread_local]
248    static ONCE_ID: OnceLock<ObjID> = OnceLock::new();
249    if get_tp() != 0 {
250        *ONCE_ID.get_or_init(|| twizzler_abi::syscall::sys_thread_self_id())
251    } else {
252        twizzler_abi::syscall::sys_thread_self_id()
253    }
254}
255
256/// Get the thread ID of the caller.
257pub fn get_sctx_id() -> ObjID {
258    if !unsafe { __is_monitor_ready() } {
259        return twizzler_abi::syscall::sys_thread_active_sctx_id();
260    }
261    #[thread_local]
262    static ONCE_ID: OnceLock<ObjID> = OnceLock::new();
263    if get_tp() != 0 {
264        *ONCE_ID.get_or_init(|| twizzler_abi::syscall::sys_thread_active_sctx_id())
265    } else {
266        twizzler_abi::syscall::sys_thread_active_sctx_id()
267    }
268}
269
270pub fn runtime_preentry(info: &GateCallInfo) -> Result<(), TwzError> {
271    twizzler_rt_abi::core::twz_rt_cross_compartment_entry()?;
272    set_caller(info.clone());
273    Ok(())
274}
275
276pub struct SecFrame {
277    tp: usize,
278    sctx: ObjID,
279}
280
281pub fn frame() -> SecFrame {
282    let tp = get_tp();
283    // TODO: do this without calling the kernel.
284    let sctx = twizzler_abi::syscall::sys_thread_active_sctx_id();
285    SecFrame { tp, sctx }
286}
287
288pub fn restore_frame(frame: SecFrame) {
289    if frame.tp != 0 {
290        twizzler_abi::syscall::sys_thread_settls(frame.tp as u64);
291    }
292    twizzler_abi::syscall::sys_thread_set_active_sctx_id(frame.sctx).unwrap();
293}
294
295#[derive(Clone, Copy)]
296pub struct DynamicSecGate<'comp, A, R> {
297    address: usize,
298    _pd: PhantomData<&'comp (A, R)>,
299}
300
301impl<'a, A: Tuple + Crossing + Copy, R: Crossing + Copy> Fn<A> for DynamicSecGate<'a, A, R> {
302    extern "rust-call" fn call(&self, args: A) -> Self::Output {
303        unsafe { dynamic_gate_call(*self, args) }
304    }
305}
306
307impl<'a, A: Tuple + Crossing + Copy, R: Crossing + Copy> FnMut<A> for DynamicSecGate<'a, A, R> {
308    extern "rust-call" fn call_mut(&mut self, args: A) -> Self::Output {
309        unsafe { dynamic_gate_call(*self, args) }
310    }
311}
312
313impl<'a, A: Tuple + Crossing + Copy, R: Crossing + Copy> FnOnce<A> for DynamicSecGate<'a, A, R> {
314    type Output = Result<R, TwzError>;
315
316    extern "rust-call" fn call_once(self, args: A) -> Self::Output {
317        unsafe { dynamic_gate_call(self, args) }
318    }
319}
320
321impl<'a, A, R> Debug for DynamicSecGate<'a, A, R> {
322    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
323        write!(
324            f,
325            "DynamicSecGate [{} -> {}] {{ address: {:x} }}",
326            std::any::type_name::<A>(),
327            std::any::type_name::<R>(),
328            self.address
329        )
330    }
331}
332
333impl<'comp, A, R> DynamicSecGate<'comp, A, R> {
334    pub unsafe fn new(address: usize) -> Self {
335        Self {
336            address,
337            _pd: PhantomData,
338        }
339    }
340}
341
342pub unsafe fn dynamic_gate_call<A: Tuple + Crossing + Copy, R: Crossing + Copy>(
343    target: DynamicSecGate<A, R>,
344    args: A,
345) -> Result<R, TwzError> {
346    let frame = frame();
347    // Allocate stack space for args + ret. Args::with_alloca also inits the memory.
348    let ret = GateCallInfo::with_alloca(get_thread_id(), get_sctx_id(), |info| {
349        Arguments::<A>::with_alloca(args, |args| {
350            Return::<Result<R, TwzError>>::with_alloca(|ret| {
351                // Call the trampoline in the mod.
352                unsafe {
353                        //#mod_name::#trampoline_name_without_prefix(info as *const _, args as *const _, ret as *mut _);
354                        #[cfg(target_arch = "x86_64")]
355                        core::arch::asm!("call {target}", target = in(reg) target.address, in("rdi") info as *const _, in("rsi") args as *const _, in("rdx") ret as *mut _, clobber_abi("C"));
356                        #[cfg(not(target_arch = "x86_64"))]
357                        todo!()
358                    }
359                ret.into_inner()
360            })
361        })
362    });
363    restore_frame(frame);
364    ret.ok_or(ResourceError::Unavailable)?
365}
366
367#[thread_local]
368static CALLER_INFO: RefCell<Option<GateCallInfo>> = RefCell::new(None);
369
370unsafe extern "C" {
371    fn __is_monitor_ready() -> bool;
372}
373
374pub fn set_caller(info: GateCallInfo) {
375    if unsafe { __is_monitor_ready() } {
376        CALLER_INFO.borrow_mut().replace(info);
377    }
378}
379
380fn _reset_caller() {
381    if unsafe { __is_monitor_ready() } {
382        CALLER_INFO.borrow_mut().take();
383    }
384}
385
386pub fn get_caller() -> Option<GateCallInfo> {
387    if !unsafe { __is_monitor_ready() } {
388        return None;
389    }
390    if CALLER_INFO.borrow().is_none() {
391        panic!("..")
392    }
393    CALLER_INFO.borrow().clone()
394}