diff --git a/library/alloc/src/cheri/mod.rs b/library/alloc/src/cheri/mod.rs new file mode 100644 index 0000000000000..3a681b4381230 --- /dev/null +++ b/library/alloc/src/cheri/mod.rs @@ -0,0 +1,3 @@ + +#[cfg(all(target_family = "cheriot", target_os = "cheriotrtos"))] +pub mod seal; diff --git a/library/alloc/src/cheri/seal.rs b/library/alloc/src/cheri/seal.rs new file mode 100644 index 0000000000000..f4f59cb95b694 --- /dev/null +++ b/library/alloc/src/cheri/seal.rs @@ -0,0 +1,141 @@ +//! Software sealing of pointers in the CHERIoT-RTOS. + +use core::cheri::timeout::Timeout; +use core::ffi::c_void; + +unsafe extern "chericcallcc" { + /// Dynamically create a new token. + #[cheriot_compartment = "allocator"] + #[link_name = "_Z13token_key_newv"] + fn token_key_new() -> *const c_void; + + /// Dynamically create a new sealed value. + /// The return value is the sealed capability, while `unsealed` will contain a pointer to the + /// allocated memory region. + #[cheriot_compartment = "allocator"] + #[link_name = "_Z27token_sealed_unsealed_allocP7TimeoutU19__sealed_capabilityP24AllocatorCapabilityStateP10SKeyStructjPPv"] + fn token_sealed_unsealed_alloc( + timeout: &core::cheri::timeout::Timeout, + heap_capability: *const c_void, + key: *const c_void, + sz: usize, + unsealed: *mut *mut c_void, + ) -> *mut c_void; +} + +unsafe extern "C" { + #[cheriot_static_sealed_value] + static __default_malloc_capability: *const c_void; +} + +/// A sealed capability. +pub struct SealedCapability(*mut T); + +impl Clone for SealedCapability { + fn clone(&self) -> Self { + Self(self.0) + } +} + +impl core::fmt::Debug for SealedCapability { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "SealedCapability<{}>(opaque)", core::any::type_name_of_val(&self.0)) + } +} + +/// A sealing key. +/// +/// This kind of key can be used for software sealing only: passing this to +/// [`core::intrinsics::cheri::cheri_unseal`] will generate an invalid sealed pointer. +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct SealingKey(pub(self) *const ()); + +impl core::fmt::Debug for SealingKey { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "sealing_key({:p})", self.0) + } +} + +impl SealingKey { + /// Create a new sealing key. + /// + /// This function is guaranteed to complete unless the allocator has exhausted + /// the total number of sealing keys possible (2^32 - 2^24). After this point, + /// it will never succeed. A compartment that is granted access to this entry + /// point is trusted not to exhaust this resource. If you wish to allow a + /// compartment to seal objects, but do not wish to allow it to allocate new + /// sealing keys, then you should insert a proxy compartment that guarantees + /// that it will call this API once and return a single key to the caller. + /// + /// The return value from this is a capability with the permit-seal and + /// permit-unseal permissions. Callers may remove one or both of these + /// permissions and delegate the resulting capability to allow other + /// compartments to either seal or unseal the capabilities with this key. + /// + /// If the sealing keys have been exhausted then this will return + /// [`Option::None`]. This API is guaranteed never to block. + #[inline(always)] + pub fn try_new() -> Option { + let res = unsafe { token_key_new() }; + if res.is_null() { None } else { Some(SealingKey(res as *const ())) } + } +} + +/// Represents the result of software sealing. +#[derive(Debug)] +pub struct Seal { + /// The sealed capability this seal created. + sealed: SealedCapability, + /// The reference to the unsealed value. + unsealed: *mut T, +} + +impl Seal { + /// Create a new [`Seal`]. + #[inline(always)] + pub fn try_new T>(key: SealingKey, generator: G) -> Option { + Self::try_new_with_timeout(key, generator, 0u32.into()) + } + + /// Create a new [`Self`] with the given `timeout`. + #[inline(always)] + pub fn try_new_with_timeout T>( + key: SealingKey, + generator: G, + timeout: Timeout, + ) -> Option { + let mut unsealed: *mut T = core::ptr::null_mut(); + let sz = size_of::(); + let sealed = unsafe { + token_sealed_unsealed_alloc( + &timeout, + __default_malloc_capability, + key.0 as *const c_void, + sz, + &mut unsealed as *mut *mut T as *mut *mut c_void, + ) + }; + + if unsealed.is_null() || sealed.is_null() { + None + } else { + let value = generator(); + unsafe { + core::ptr::copy_nonoverlapping(&value as *const T, unsealed, sz); + } + let sealed = SealedCapability::(sealed as *mut T); + Some(Self { sealed, unsealed }) + } + } + + /// Get the sealed pointer from the seal. + pub fn sealed(&self) -> SealedCapability { + self.sealed.clone() + } + + /// Get the unsealed pointer from the seal. + pub fn unsealed(&self) -> *mut T { + self.unsealed + } +} diff --git a/library/alloc/src/lib.rs b/library/alloc/src/lib.rs index 33e806c888e0d..a00593730d230 100644 --- a/library/alloc/src/lib.rs +++ b/library/alloc/src/lib.rs @@ -199,7 +199,12 @@ // that the feature-gate isn't enabled. Ideally, it wouldn't check for the feature gate for docs // from other crates, but since this can only appear for lang items, it doesn't seem worth fixing. #![feature(intra_doc_pointers)] + +/* CHERI-specific features. */ #![cfg_attr(not(bootstrap), cfg_attr(target_family = "cheri", feature(non_null_from_ref)))] +#![cfg_attr(not(bootstrap), cfg_attr(target_family = "cheri", feature(cheri)))] +#![cfg_attr(not(bootstrap), cfg_attr(target_family = "cheriot", feature(cheriot_compartment)))] +#![cfg_attr(not(bootstrap), cfg_attr(target_family = "cheriot", feature(abi_chericc)))] // Module with internal macros used by other modules (needs to be included before other modules). #[macro_use] @@ -240,3 +245,12 @@ pub mod __export { pub use core::format_args; pub use core::hint::must_use; } + +#[cfg(not(bootstrap))] +#[cfg(target_family = "cheri")] +#[unstable( + feature = "cheri", + issue = "none", + reason = "support for CHERI has open design concerns" +)] +pub mod cheri; diff --git a/library/core/src/cheri/mod.rs b/library/core/src/cheri/mod.rs index 9ee97db93122f..a61ed981da6bf 100644 --- a/library/core/src/cheri/mod.rs +++ b/library/core/src/cheri/mod.rs @@ -22,7 +22,7 @@ pub mod permissions; use permissions::PermissionSet; #[cfg(all(target_family = "cheriot", target_os = "cheriotrtos"))] -pub mod seal; +pub mod timeout; /// A capability is a concept specific to CHERI systems. /// In summary, it is a well-defined non-integral pointer. diff --git a/library/core/src/cheri/seal.rs b/library/core/src/cheri/seal.rs index 0020c0baeaa48..5cc8f4b06abd7 100644 --- a/library/core/src/cheri/seal.rs +++ b/library/core/src/cheri/seal.rs @@ -1,21 +1,145 @@ -//! Sealing pointers. +//! Software sealing of pointers in the CHERIoT-RTOS. -/// A sealed pointer. -#[lang = "cheri_sealed_capability"] -#[repr(transparent)] -pub struct SealedCapability(T); +use crate::cheri::timeout::Timeout; +use crate::ffi::c_void; -impl SealedCapability { - /// Unseal the capability. - pub unsafe fn unseal(self, key: K) -> T { - unsafe { - crate::intrinsics::cheri::cheri_unseal(self, key) - } +unsafe extern "chericcallcc" { + /// Dynamically create a new token. + #[cheriot_compartment = "allocator"] + fn _Z13token_key_newv() -> *const c_void; + + // [todo](xdoardo): Move this to `alloc::cheri::cheriot`. + /// Dynamically create a new sealed value. + /// The return value is the sealed capability, while `unsealed` will contain a pointer to the + /// allocated memory region. + #[cheriot_compartment = "allocator"] + fn _Z27token_sealed_unsealed_allocP7TimeoutU19__sealed_capabilityP24AllocatorCapabilityStateP10SKeyStructjPPv( + timeout: &crate::cheri::timeout::Timeout, + heap_capability: *const c_void, + key: *const c_void, + sz: usize, + unsealed: *mut *mut c_void, + ) -> *mut c_void; +} + +unsafe extern "C" { + // [todo](xdoardo): Move this to `alloc::cheri::cheriot`. + #[cheriot_static_sealed_value] + static __default_malloc_capability: *const c_void; +} + +/// A sealed capability. +pub struct SealedCapability(*mut T); + +impl Clone for SealedCapability { + fn clone(&self) -> Self { + Self(self.0) } } -impl crate::fmt::Debug for SealedCapability { +impl crate::fmt::Debug for SealedCapability { fn fmt(&self, f: &mut crate::fmt::Formatter<'_>) -> crate::fmt::Result { - write!(f, "*sealed {:?}", crate::any::TypeId::of::()) + write!(f, "SealedCapability<{}>(opaque)", crate::any::type_name_of_val(&self.0)) + } +} + +/// A sealing key. +/// +/// This kind of key can be used for software sealing only: passing this to +/// [`crate::intrinsics::cheri::cheri_unseal`] will generate an invalid sealed pointer. +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct SealingKey(pub(self) *const ()); + +impl crate::fmt::Debug for SealingKey { + fn fmt(&self, f: &mut crate::fmt::Formatter<'_>) -> crate::fmt::Result { + write!(f, "sealing_key({:p})", self.0) + } +} + +impl SealingKey { + /// Create a new sealing key. + /// + /// This function is guaranteed to complete unless the allocator has exhausted + /// the total number of sealing keys possible (2^32 - 2^24). After this point, + /// it will never succeed. A compartment that is granted access to this entry + /// point is trusted not to exhaust this resource. If you wish to allow a + /// compartment to seal objects, but do not wish to allow it to allocate new + /// sealing keys, then you should insert a proxy compartment that guarantees + /// that it will call this API once and return a single key to the caller. + /// + /// The return value from this is a capability with the permit-seal and + /// permit-unseal permissions. Callers may remove one or both of these + /// permissions and delegate the resulting capability to allow other + /// compartments to either seal or unseal the capabilities with this key. + /// + /// If the sealing keys have been exhausted then this will return + /// [`Option::None`]. This API is guaranteed never to block. + #[inline(always)] + pub fn try_new() -> Option { + let res = unsafe { _Z13token_key_newv() }; + if res.is_null() { None } else { Some(SealingKey(res as *const ())) } + } +} + +/// Represents the result of software sealing. +#[derive(Debug)] +pub struct Seal { + /// The sealed capability this seal created. + sealed: SealedCapability, + /// The reference to the unsealed value. + unsealed: *mut T, +} + +// [todo](xdoardo): Move this to `alloc::cheri::cheriot`. +impl Seal { + + pub type DefaultSealingType = SealingKey; + + /// Create a new [`Seal`]. + #[inline(always)] + pub fn try_new T>(key: SealingKey, generator: G) -> Option { + Self::try_new_with_timeout(key, generator, 0u32.into()) + } + + /// Create a new [`Self`] with the given `timeout`. + #[inline(always)] + pub fn try_new_with_timeout T>( + key: SealingKey, + generator: G, + timeout: Timeout, + ) -> Option { + let mut unsealed: *mut T = crate::ptr::null_mut(); + let sz = size_of::(); + let sealed = unsafe { + _Z27token_sealed_unsealed_allocP7TimeoutU19__sealed_capabilityP24AllocatorCapabilityStateP10SKeyStructjPPv( + &timeout, + __default_malloc_capability, + key.0 as *const c_void, + sz, + &mut unsealed as *mut *mut T as *mut *mut c_void, + ) + }; + + if unsealed.is_null() || sealed.is_null() { + None + } else { + let value = generator(); + unsafe { + crate::ptr::copy_nonoverlapping(&value as *const T, unsealed, sz); + } + let sealed = SealedCapability::(sealed as *mut T); + Some(Self { sealed, unsealed }) + } + } + + /// Get the sealed pointer from the seal. + pub fn sealed(&self) -> SealedCapability { + self.sealed.clone() + } + + /// Get the unsealed pointer from the seal. + pub fn unsealed(&self) -> *mut T { + self.unsealed } } diff --git a/library/core/src/cheri/timeout.rs b/library/core/src/cheri/timeout.rs new file mode 100644 index 0000000000000..169b02d4dafc6 --- /dev/null +++ b/library/core/src/cheri/timeout.rs @@ -0,0 +1,32 @@ +//! Description of timeouts in the CHERIoT RTOS. + +/// Quantity used for measuring time for timeouts. The unit is scheduler ticks. +pub type Ticks = u32; + +/// Structure representing a timeout. This is intended to allow a single +/// instance to be chained across blocking calls. +/// +/// Timeouts *may not be stored in the heap*. Handling timeout structures that +/// may disappear between sleeping and waking is very complicated and would +/// impact a lot of fast paths. Instead, most functions that take a timeout +/// will simply fail if the timeout is on the heap. +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct Timeout { + /// The time that has elapsed during blocking operations for this timeout + /// structure. This should be initialised to 0. It may exceed the initial + /// value of `remaining` if a higher-priority thread preempts the blocking + /// thread. + elapsed: u32, + + /// The remaining time. This is clamped at 0 on subtraction. A special + /// value of `UnlimitedTimeout` can be set to represent an unlimited + /// timeout. + remaining: u32, +} + +impl From for Timeout { + fn from(remaining: Ticks) -> Self { + Self { elapsed: 0, remaining } + } +} diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index 29454c7d90d54..027fc948d0f8f 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -205,6 +205,8 @@ #![feature(wasm_target_feature)] #![feature(x86_amx_intrinsics)] // tidy-alphabetical-end +#![cfg_attr(not(bootstrap), cfg_attr(target_family = "cheriot", feature(cheriot_compartment)))] +#![cfg_attr(not(bootstrap), cfg_attr(target_family = "cheriot", feature(abi_chericc)))] // allow using `core::` in intra-doc links #[allow(unused_extern_crates)]