Skip to content
Closed
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
3 changes: 3 additions & 0 deletions library/alloc/src/cheri/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

#[cfg(all(target_family = "cheriot", target_os = "cheriotrtos"))]
pub mod seal;
141 changes: 141 additions & 0 deletions library/alloc/src/cheri/seal.rs
Original file line number Diff line number Diff line change
@@ -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<T>(*mut T);

impl<T> Clone for SealedCapability<T> {
fn clone(&self) -> Self {
Self(self.0)
}
}

impl<T> core::fmt::Debug for SealedCapability<T> {
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<Self> {
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<T> {
/// The sealed capability this seal created.
sealed: SealedCapability<T>,
/// The reference to the unsealed value.
unsealed: *mut T,
}

impl<T> Seal<T> {
/// Create a new [`Seal`].
#[inline(always)]
pub fn try_new<G: Fn() -> T>(key: SealingKey, generator: G) -> Option<Self> {
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<G: Fn() -> T>(
key: SealingKey,
generator: G,
timeout: Timeout,
) -> Option<Self> {
let mut unsealed: *mut T = core::ptr::null_mut();
let sz = size_of::<T>();
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::<T>(sealed as *mut T);
Some(Self { sealed, unsealed })
}
}

/// Get the sealed pointer from the seal.
pub fn sealed(&self) -> SealedCapability<T> {
self.sealed.clone()
}

/// Get the unsealed pointer from the seal.
pub fn unsealed(&self) -> *mut T {
self.unsealed
}
}
14 changes: 14 additions & 0 deletions library/alloc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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;
2 changes: 1 addition & 1 deletion library/core/src/cheri/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
150 changes: 137 additions & 13 deletions library/core/src/cheri/seal.rs
Original file line number Diff line number Diff line change
@@ -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: crate::marker::PointerLike>(T);
use crate::cheri::timeout::Timeout;
use crate::ffi::c_void;

impl<T: core::marker::PointerLike> SealedCapability<T> {
/// Unseal the capability.
pub unsafe fn unseal<K: core::marker::PointerLike>(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<T>(*mut T);

impl<T> Clone for SealedCapability<T> {
fn clone(&self) -> Self {
Self(self.0)
}
}

impl<T: core::marker::PointerLike + 'static> crate::fmt::Debug for SealedCapability<T> {
impl<T> crate::fmt::Debug for SealedCapability<T> {
fn fmt(&self, f: &mut crate::fmt::Formatter<'_>) -> crate::fmt::Result {
write!(f, "*sealed {:?}", crate::any::TypeId::of::<T>())
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<Self> {
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<T> {
/// The sealed capability this seal created.
sealed: SealedCapability<T>,
/// The reference to the unsealed value.
unsealed: *mut T,
}

// [todo](xdoardo): Move this to `alloc::cheri::cheriot`.
impl<T> Seal<T> {

pub type DefaultSealingType = SealingKey<T>;

/// Create a new [`Seal`].
#[inline(always)]
pub fn try_new<G: Fn() -> T>(key: SealingKey, generator: G) -> Option<Self> {
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<G: Fn() -> T>(
key: SealingKey,
generator: G,
timeout: Timeout,
) -> Option<Self> {
let mut unsealed: *mut T = crate::ptr::null_mut();
let sz = size_of::<T>();
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::<T>(sealed as *mut T);
Some(Self { sealed, unsealed })
}
}

/// Get the sealed pointer from the seal.
pub fn sealed(&self) -> SealedCapability<T> {
self.sealed.clone()
}

/// Get the unsealed pointer from the seal.
pub fn unsealed(&self) -> *mut T {
self.unsealed
}
}
32 changes: 32 additions & 0 deletions library/core/src/cheri/timeout.rs
Original file line number Diff line number Diff line change
@@ -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<Ticks> for Timeout {
fn from(remaining: Ticks) -> Self {
Self { elapsed: 0, remaining }
}
}
2 changes: 2 additions & 0 deletions library/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
Loading