blob: 55a4a1ba9dd8fa8f7cac0d4eb32d81680a8e56d9 [file] [log] [blame]
// Copyright Microsoft and CHERIoT Contributors.
// SPDX-License-Identifier: MIT
#pragma once
/**
* C++ helpers for operating on capabilities.
*/
#include <cheri.h>
#include <cstddef>
#include <cstdint>
#include <initializer_list>
#include <magic_enum/magic_enum.hpp>
namespace CHERI
{
/**
* The complete set of architectural permissions.
*/
enum class Permission : uint32_t
{
/**
* Capability refers to global memory (this capability may be stored
* anywhere).
*/
Global = 0,
/**
* Global capabilities can be loaded through this capability. Without
* this permission, any capability loaded via this capability will
* have `Global` and `LoadGlobal` removed.
*/
LoadGlobal = 1,
/**
* Capability may be used to store. Any store via a capability without
* this permission will trap.
*/
Store = 2,
/**
* Capabilities with store permission may be loaded through this
* capability. Without this, any loaded capability will have
* `LoadMutable` and `Store` removed.
*/
LoadMutable = 3,
/**
* This capability may be used to store capabilities that do not have
* `Global` permission.
*/
StoreLocal = 4,
/**
* This capability can be used to load.
*/
Load = 5,
/**
* Any load and store permissions on this capability convey the right to
* load or store capabilities in addition to data.
*/
LoadStoreCapability = 6,
/**
* If installed as the program counter capability, running code may
* access privileged system registers.
*/
AccessSystemRegisters = 7,
/**
* This capability may be used as a jump target and used to execute
* instructions.
*/
Execute = 8,
/**
* This capability may be used to unseal other capabilities. The
* 'address' range is in the sealing type namespace and not in the
* memory namespace.
*/
Unseal = 9,
/**
* This capability may be used to seal other capabilities. The
* 'address' range is in the sealing type namespace and not in the
* memory namespace.
*/
Seal = 10,
/**
* Software defined permission bit, no architectural meaning.
*/
User0 = 11
};
/**
* Class encapsulating a set of permissions.
*/
class PermissionSet
{
/**
* Helper that returns the bit associated with a given permission.
*/
static constexpr uint32_t permission_bit(Permission p)
{
return 1 << static_cast<uint32_t>(p);
}
/**
* Helper for building permissions, adds a permission to the raw
* bitfield.
*/
__always_inline constexpr void add_permission(Permission p)
{
rawPermissions |= permission_bit(p);
}
/**
* Private constructor for creating a permission set from a raw bitmask.
* This should never be used accidentally and so is hidden behind a
* factory method with an explicit name. Callers should use
* `PermissionSet::from_raw`.
*/
constexpr PermissionSet(uint32_t rawPermissions)
: rawPermissions(rawPermissions)
{
}
public:
/**
* Computes (at compile time) a bitmask containing the set of valid
* permission bits.
*
* FIXME would ideally make this private and expose a public static
* constexpr field but this seems to trigger a compiler bug when trying
* to initialise said field using this function.
*/
static constexpr uint32_t valid_permissions_mask()
{
uint32_t mask = 0;
for (auto permission : magic_enum::enum_values<Permission>())
{
mask |= 1 << static_cast<uint32_t>(permission);
}
return mask;
}
private:
/**
* Permissions iterator. Stores the permissions and iterates over them
* one bit at a time.
*/
class Iterator
{
/// `PermissionSet` may construct this.
friend class PermissionSet;
/// The raw permissions bitmap.
uint32_t permissions;
/// Constructor, take a raw permissions bitmap.
constexpr Iterator(uint32_t rawPermissions)
: permissions(rawPermissions)
{
}
public:
/**
* Dereference, returns the lowest-numbered permission.
*/
constexpr Permission operator*()
{
return Permission(__builtin_ffs(permissions) - 1);
}
/**
* Preincrement, drops the lowest-numbered permission.
*/
constexpr Iterator &operator++()
{
permissions &= ~(1 << (__builtin_ffs(permissions) - 1));
return *this;
}
/**
* Returns true if the other iterator has a different set of
* permissions.
*/
constexpr bool operator!=(const Iterator Other)
{
return permissions != Other.permissions;
}
};
public:
/**
* The raw bitmap of permissions. This is public so that this class
* meets the requirements of a structural type and can therefore be
* used as a template parameter. This field should never be directly
* modified.
*/
uint32_t rawPermissions = 0;
/**
* Constructs a permission set from a raw permission mask.
*/
static constexpr PermissionSet from_raw(uint32_t raw)
{
raw &= valid_permissions_mask();
return {raw};
}
/**
* Constructs a permission set from a single permission.
*/
constexpr PermissionSet(Permission p)
{
add_permission(p);
}
/**
* Construct a permission set from a list of permissions.
*/
__always_inline constexpr PermissionSet(
std::initializer_list<Permission> permissions)
{
for (auto p : permissions)
{
add_permission(p);
}
}
/**
* Copy constructor.
*/
constexpr PermissionSet(const PermissionSet &other)
= default;
/**
* Returns a permission set representing all permissions.
*/
constexpr static PermissionSet omnipotent()
{
return PermissionSet{valid_permissions_mask()};
}
/**
* And-permissions operation, creates a new permission set containing
* only permissions present in both this set and the argument.
*/
constexpr PermissionSet operator&(PermissionSet p)
{
return PermissionSet{rawPermissions & p.rawPermissions};
}
/**
* And-permissions operation, removes all permissions that are not
* present in both permission sets.
*/
constexpr PermissionSet &operator&=(PermissionSet p)
{
rawPermissions &= p.rawPermissions;
return *this;
}
/**
* Constructs a new permission set without the specified permission.
*/
[[nodiscard]] constexpr PermissionSet without(Permission p) const
{
return {rawPermissions & ~permission_bit(p)};
}
/**
* Constructs a new permission set without the specified permissions.
*/
template<std::same_as<Permission>... Permissions>
[[nodiscard]] constexpr PermissionSet without(Permission p,
Permissions... ps) const
{
return this->without(p).without(ps...);
}
/**
* Returns true if, and only if, this permission set can be derived
* from the argument set.
*/
[[nodiscard]] constexpr bool can_derive_from(PermissionSet other) const
{
return (rawPermissions & other.rawPermissions) == rawPermissions;
}
/**
* Returns true if this permission set contains the specified
* permission.
*/
[[nodiscard]] constexpr bool contains(Permission permission) const
{
return (permission_bit(permission) & rawPermissions) ==
permission_bit(permission);
}
/**
* Returns true if this permission set contains the specified
* permissions.
*/
template<std::same_as<Permission>... Permissions>
[[nodiscard]] constexpr bool contains(Permission p,
Permissions... ps) const
{
return this->contains(p) && this->contains(ps...);
}
/**
* Returns the raw permission mask as an integer containing a bitfield
* of permissions.
*/
[[nodiscard]] constexpr uint32_t as_raw() const
{
return rawPermissions;
}
/**
* Returns an iterator over the permissions starting at the
* lowest-numbered permission.
*/
[[nodiscard]] constexpr Iterator begin() const
{
return {rawPermissions};
}
/**
* Returns an end iterator.
*/
[[nodiscard]] constexpr Iterator end() const
{
// Each increment of an iterator will drop one permission and so an
// iterator will compare equal to {0} once all permissions have
// been dropped.
return {0};
}
/**
* Three-way comparison. Treats a superset as greater-than, identical
* permissions as equivalent, and sets that don't have a superset
* releationship as unordered.
*/
constexpr auto operator<=>(const PermissionSet Other) const
{
if (rawPermissions == Other.rawPermissions)
{
return std::partial_ordering::equivalent;
}
if (can_derive_from(Other))
{
return std::partial_ordering::less;
}
if (Other.can_derive_from(*this))
{
return std::partial_ordering::greater;
}
return std::partial_ordering::unordered;
}
/**
* Equality operator, wraps the three-way compare operator.
*/
constexpr bool operator==(PermissionSet other) const
{
// Clang-tidy spuriously suggests that this 0 should be nullptr.
return (*this <=> other) == 0; // NOLINT(modernize-use-nullptr)
}
};
/**
* Rounds `len` up to a CHERI representable length for the current
* architecture.
*/
__always_inline inline size_t representable_length(size_t length)
{
return __builtin_cheri_round_representable_length(length);
}
/**
* Returns the alignment mask required for a given length.
*/
__always_inline inline size_t representable_alignment_mask(size_t length)
{
return __builtin_cheri_representable_alignment_mask(length);
}
/// Can the range [base, base + size) be precisely covered by a capability?
inline bool is_precise_range(ptraddr_t base, size_t size)
{
return (base & ~representable_alignment_mask(size)) == 0 &&
representable_length(size) == size;
}
/**
* Helper class for accessing capability properties on pointers.
*/
template<typename T>
class Capability
{
protected:
/// The capability that this class wraps.
T *ptr;
private:
/**
* Constructs a PermissionSet with the permissions of the given pointer.
*/
static PermissionSet permission_set_from_pointer(const void *p)
{
auto perms = __builtin_cheri_perms_get(p);
auto mask = PermissionSet::valid_permissions_mask();
/* FIXME teach the compiler that the builtin always returns a value
* that is a subset of the mask, otherwise it unnecessarily
* constructs and applies the mask in from_raw */
__builtin_assume((perms & ~mask) == 0);
return PermissionSet::from_raw(perms);
}
/**
* Base class for the proxies that accessors in this class return.
*/
class PropertyProxyBase
{
protected:
/**
* The capability that this proxy refers to.
*/
Capability &cap;
/**
* Replaces the underlying capability
*/
template<typename U>
void set(U *newPtr)
{
cap.ptr = static_cast<T *>(newPtr);
}
/**
* Returns the capability's pointer.
*/
[[nodiscard]] T *ptr() const
{
return cap.ptr;
}
public:
/// Constructor, takes the capability whose property this class is
/// proxying.
PropertyProxyBase(Capability &c) : cap(c) {}
};
/**
* Proxy for accessing a capability's address.
*/
struct AddressProxy : public PropertyProxyBase
{
/// Inherit the constructor from the base class.
using PropertyProxyBase::PropertyProxyBase;
/// Inherit the pointer accesors
/// @{
using PropertyProxyBase::ptr;
using PropertyProxyBase::set;
/// @}
/**
* Implicit casts can convert this to an address.
*/
operator ptraddr_t() const
{
return __builtin_cheri_address_get(ptr());
}
/**
* Set the address in the underlying capability.
*/
AddressProxy &operator=(ptraddr_t addr)
{
set(__builtin_cheri_address_set(ptr(), addr));
return *this;
}
/**
* Set the address in the underlying capability given another
* address proxy.
*/
AddressProxy &operator=(AddressProxy addr)
{
set(__builtin_cheri_address_set(ptr(), addr));
return *this;
}
/**
* Add a displacement to the capability's address.
*/
AddressProxy &operator+=(ptrdiff_t displacement)
{
set(__builtin_cheri_offset_increment(ptr(), displacement));
return *this;
}
/**
* Subtract a displacement from the capability's address.
*/
AddressProxy &operator-=(ptrdiff_t displacement)
{
set(__builtin_cheri_offset_increment(ptr(), -displacement));
return *this;
}
};
/**
* Proxy for accessing an object's bounds.
*/
struct BoundsProxy : public PropertyProxyBase
{
/// Inherit the constructor from the base class.
using PropertyProxyBase::PropertyProxyBase;
/// Inherit the pointer accesors
/// @{
using PropertyProxyBase::ptr;
using PropertyProxyBase::set;
/// @}
/**
* Return the object's bounds (displacement from the address to the
* end).
*/
operator ptrdiff_t() const
{
return __builtin_cheri_length_get(ptr()) -
(__builtin_cheri_address_get(ptr()) -
__builtin_cheri_base_get(ptr()));
}
/**
* Set the capability's bounds, giving an invalid capability if this
* cannot be represented exactly.
*/
BoundsProxy &operator=(size_t bounds)
{
set(__builtin_cheri_bounds_set_exact(ptr(), bounds));
return *this;
}
/**
* Set the bounds, adding some padding (up to the bounds of the
* original capability) if necessary for alignment.
*/
BoundsProxy &set_inexact(size_t bounds)
{
set(__builtin_cheri_bounds_set(ptr(), bounds));
return *this;
}
private:
BoundsProxy &set_inexact_at_most_slow(size_t bounds)
{
ptraddr_t newBaseAddress = this->cap.address();
// The number of bits in CHERIoT's capability encoding's
// mantissa. This is part of the capability encoding and
// so, ideally, wouldn't be hard coded here.
static constexpr size_t MantissaBits = 9;
// The maximum possible representable length given the new
// base is a full mantissa width of 1s followed by 0s with
// its least significant 1 aligned to the least significant
// 1 in the base address.
size_t maximumLength = ((1 << MantissaBits) - 1)
<< __builtin_ctz(newBaseAddress);
// Ensure that the requested length is representable by
// making sure that it fits within a mantissa width,
// rounding down by dropping any lower bits. This might be
// excessive by up to one bit position, because the
// representable alignment mask is designed to work with the
// rounding-up inexact bounds setting instruction. As a result,
// we might not return the largest possible representable
// length, but we won't return a wildly too small one, either.
size_t alignedLength =
bounds & representable_alignment_mask(bounds);
// Select the smaller of those two lengths.
bounds = std::min<size_t>(alignedLength, maximumLength);
*this = bounds;
return *this;
}
public:
/**
* Set the bounds to `length` if `length` is representable with the
* current alignment of `buffer`. If not, then select a smaller
* `length` that is representable. Unlike set_inexact(), the
* resulting base will always be the current address; that is, there
* will be no padding below the current address.
*
* The caller must call .length() on the resulting capability to
* determine the imposed bounds.
*
* See is_precise_range().
*/
__always_inline BoundsProxy &set_inexact_at_most(size_t bounds)
{
// Just try to set the requested bounds, first. If that works,
// there's no need for bit-twiddling at all.
Capability p = ptr();
p.bounds() = bounds;
if (p.is_valid())
{
set(static_cast<T *>(p));
return *this;
}
return set_inexact_at_most_slow(bounds);
}
};
/**
* Proxy for accessing a capability's permissions
*/
struct PermissionsProxy : public PropertyProxyBase
{
/// Inherit the constructor from the base class.
using PropertyProxyBase::PropertyProxyBase;
/// Inherit the pointer accesors
/// @{
using PropertyProxyBase::ptr;
using PropertyProxyBase::set;
/// @}
/**
* Implicitly convert to a permission set.
*/
operator PermissionSet() const
{
return permission_set_from_pointer(ptr());
}
/**
* And-permissions operation, removes all permissions that are not
* present in both permission sets from the capability.
*/
PermissionsProxy &operator&=(PermissionSet permissions)
{
set(__builtin_cheri_perms_and(ptr(), permissions.as_raw()));
return *this;
}
/**
* Returns a permission set containing only the permissions held by
* the capability and the argument.
*/
constexpr PermissionSet operator&(PermissionSet p)
{
return static_cast<PermissionSet>(*this) & p;
}
/**
* Constructs a new permission set without the specified
* permissions.
*/
template<std::same_as<Permission>... Permissions>
constexpr PermissionSet without(Permissions... ps) const
{
return static_cast<PermissionSet>(*this).without(ps...);
}
/**
* Returns true if, and only if, this permission set can be derived
* from the argument set.
*/
[[nodiscard]] constexpr bool
can_derive_from(PermissionSet other) const
{
return static_cast<PermissionSet>(*this).can_derive_from(other);
}
/**
* Returns true if this permission set contains the specified
* permissions.
*/
template<std::same_as<Permission>... Permissions>
constexpr bool contains(Permissions... permissions) const
{
return static_cast<PermissionSet>(*this).contains(
permissions...);
}
/**
* Returns the raw permission mask as an integer containing a
* bitfield of permissions.
*/
[[nodiscard]] constexpr uint32_t as_raw() const
{
return static_cast<PermissionSet>(*this).as_raw();
}
/**
* Returns an iterator over the permissions starting at the
* lowest-numbered permission.
*/
auto begin()
{
return static_cast<PermissionSet>(*this).begin();
}
/**
* Returns an end iterator.
*/
auto end()
{
return static_cast<PermissionSet>(*this).end();
}
/**
* Comparison operator.
*/
constexpr std::partial_ordering
operator<=>(const PermissionSet Other) const
{
return static_cast<PermissionSet>(*this) <=> Other;
}
/**
* Equality operator, wraps the three-way compare operator.
*/
constexpr bool operator==(const PermissionSet Other) const
{
return (*this <=> Other) == 0;
}
};
/// The property proxy base is allowed to directly access the pointer
/// that this class wraps.
friend class PropertyProxyBase;
public:
/// Constructor from a null pointer.
constexpr Capability(std::nullptr_t) : ptr(nullptr) {}
/// Default constructor, initialises with a null pointer.
constexpr Capability() : ptr(nullptr) {}
/// Constructor, takes an existing pointer to wrap
constexpr Capability(T *p) : ptr(p) {}
/// Copy constructor, aliases the object that is pointed to by `ptr`.
constexpr Capability(const Capability &other) : ptr(other.ptr) {}
/// Move constructor.
constexpr Capability(Capability &&other) : ptr(other.ptr)
{
other.ptr = nullptr;
}
/**
* Replace the pointer that this capability wraps with another.
*/
Capability &operator=(const Capability &other)
{
ptr = other.ptr;
return *this;
}
/**
* Transfer the pointer that this capability wraps from .
*/
Capability &operator=(Capability &&other)
{
ptr = other.ptr;
other.ptr = nullptr;
return *this;
}
/**
* Access the address of the capability.
*/
AddressProxy address() [[clang::lifetimebound]]
{
return {*this};
}
/**
* Return the address as an integer from a `const` capability.
*/
[[nodiscard]] ptraddr_t address() const
{
return __builtin_cheri_address_get(ptr);
}
/**
* Access (read, set) the capability's bounds.
*/
BoundsProxy bounds() [[clang::lifetimebound]]
{
return {*this};
}
/**
* Return the bounds as an integer.
*/
[[nodiscard]] ptrdiff_t bounds() const
{
return __builtin_cheri_length_get(ptr) -
(__builtin_cheri_address_get(ptr()) -
__builtin_cheri_base_get(ptr()));
}
/**
* Access the permissions of this capability.
*/
PermissionsProxy permissions() [[clang::lifetimebound]]
{
return {*this};
}
/**
* Get a copy of the permissions from a `const` capability.
*/
[[nodiscard]] PermissionSet permissions() const
{
return permission_set_from_pointer(ptr);
}
/**
* Pointer subtraction.
*/
Capability operator-(ptrdiff_t diff)
{
return {ptr - diff};
}
/**
* Pointer subtraction.
*/
Capability &operator-=(ptrdiff_t diff)
{
ptr -= diff;
return *this;
}
/**
* Pointer addition.
*/
Capability operator+(ptrdiff_t diff)
{
return {ptr + diff};
}
/**
* Pointer addition.
*/
Capability &operator+=(ptrdiff_t diff)
{
ptr += diff;
return *this;
}
/**
* Returns the tag bit indicating whether this is a valid capability.
*/
[[nodiscard]] bool is_valid() const
{
// The clang static analyser doesn't yet know that null is untagged
// and so warns of possible null dereferences after this method
// returns true. Explicitly assume that a tagged thing is non-null
// to fix this.
if (__builtin_cheri_tag_get(ptr))
{
__builtin_assume(ptr != nullptr);
return true;
}
return false;
}
/**
* Return whether this is a sealed capability.
*/
[[nodiscard]] bool is_sealed() const
{
return __builtin_cheri_type_get(ptr) != 0;
}
/**
* Returns the type of this capability, 0 if this is not a sealed
* capability.
*/
[[nodiscard]] uint32_t type() const
{
return __builtin_cheri_type_get(ptr);
}
/**
* Returns the base address of this capability.
*/
[[nodiscard]] ptraddr_t base() const
{
return __builtin_cheri_base_get(ptr);
}
/**
* Returns the length of this capability.
*/
[[nodiscard]] size_t length() const
{
return __builtin_cheri_length_get(ptr);
}
/**
* Returns the address of the top of this capability.
*/
[[nodiscard]] ptraddr_t top() const
{
return base() + length();
}
/**
* Capability comparison. Defines ordered comparison for capabilities
* with the same bounds and permissions. All other capabilities are
* either equivalent (identical bit pattern, including the tag bit) or
* unordered.
*/
constexpr std::partial_ordering operator<=>(T *other) const
{
return (*this <=> Capability<T>{other}) == 0;
}
/**
* Comparison against null pointer.
*
* Returns equivalent if this is a canonical null pointer, returns
* unordered for any other (tagged or untagged) value. Callers may
* often want `is_valid` instead of this.
*/
constexpr std::partial_ordering operator<=>(std::nullptr_t) const
{
if (__builtin_cheri_equal_exact(ptr, nullptr))
{
return std::partial_ordering::equivalent;
}
return std::partial_ordering::unordered;
}
constexpr bool operator==(const Capability Other) const
{
return __builtin_cheri_equal_exact(ptr, Other.ptr);
}
/**
* Capability comparison. Defines ordered comparison for capabilities
* with the same bounds and permissions. All other capabilities are
* either equivalent (identical bit pattern, including the tag bit) or
* unordered.
*
* Callers may want to compare addresses, rather than capabilities, if
* they want a defined comparison that is stable between two objects.
*/
constexpr std::partial_ordering
operator<=>(const Capability Other) const
{
if (__builtin_cheri_equal_exact(ptr, Other.ptr))
{
return std::partial_ordering::equivalent;
}
// If neither capability is sealed, check if everything except the
// address is the same and define ordered comparison on pointers to
// the same object.
if (!(is_sealed() || Other.is_sealed()) &&
__builtin_cheri_equal_exact(__builtin_address_set(
ptr, __builtin_address_get(Other), Other)))
{
return static_cast<ptraddr_t>(ptr) <=>
static_cast<ptraddr_t>(Other);
}
// Comparison of pointers to different objects is ub, you probably
// want address comparison:
return std::partial_ordering::unordered;
}
/**
* Equality operator, wraps the three-way compare operator.
*/
constexpr bool operator==(std::nullptr_t) const
{
return (*this <=> nullptr) == 0;
}
/**
* Implicit cast to the raw pointer type.
*/
template<typename U = T>
requires(!std::same_as<U, void>) operator U *()
{
return ptr;
}
/**
* Implicit cast to a raw pointer type.
*/
operator void *()
{
return ptr;
}
/**
* Access fields of the target as if this were a raw pointer.
*/
T *operator->()
{
return ptr;
}
/**
* Explicitly get the raw pointer.
*/
T *get()
{
return ptr;
}
/**
* Dereference operator.
*/
template<typename U = T>
requires(!std::same_as<U, void>) U &operator*()
{
return *ptr;
}
/**
* Cast this capability to some other type.
*/
template<typename U>
Capability<U> cast()
{
return {static_cast<U *>(ptr)};
}
/**
* Returns true if the tags of `this` and `other` match and if `this`
* conveys no rights that are not present in `other`. Returns false in
* all other cases.
*/
template<typename U>
bool is_subset_of(Capability<U> other)
{
return __builtin_cheri_subset_test(other.ptr, ptr);
}
/**
* Seal this capability with the given key.
*/
Capability<T> &seal(void *key)
{
ptr = static_cast<T *>(__builtin_cheri_seal(ptr, key));
return *this;
}
/**
* Unseal this capability with the given key.
*/
Capability<T> &unseal(void *key)
{
#ifdef FLUTE
// Flute still throws exceptions on invalid use. As a temporary
// work-around, add a quick check that this thing has the sealing
// type and don't unseal if it hasn't. This isn't a complete test,
// it's just sufficient to get the tests passing on Flute.
if (type() != __builtin_cheri_address_get(key))
{
ptr = nullptr;
}
else
#endif
{
ptr = static_cast<T *>(__builtin_cheri_unseal(ptr, key));
}
return *this;
}
/**
* Subscript operator.
*/
template<typename U = T>
requires(!std::same_as<U, void>) U &operator[](size_t index)
{
return ptr[index];
}
/**
* Returns true if the capability is `align`-byte aligned, false
* otherwise.
*/
bool is_aligned(size_t align)
{
return __builtin_is_aligned(ptr, align);
}
/**
* Aligns the capability down to the nearest `align`-byte boundary.
*/
Capability &align_down(size_t align)
{
ptr = __builtin_align_down(ptr, align);
return *this;
}
/**
* Aligns the capability up to the nearest `align`-byte boundary.
*/
Capability &align_up(size_t align)
{
ptr = __builtin_align_up(ptr, align);
return *this;
}
};
/**
* Concept that matches pointers.
*/
template<typename T>
concept IsPointer = std::is_pointer_v<T>;
/**
* Concept that matches smart pointers, i.e., classes which implements
* a `get` method returning a pointer, and supports `operator=` with
* the return value of `get`. This will match `Capability`, standard
* library smart pointers, etc.
*/
template<typename T>
concept IsSmartPointerLike = requires(T b)
{
{
b.get()
} -> IsPointer;
}
&&requires(T b)
{
b = b.get();
};
/**
* Checks that `ptr` is valid, unsealed, has at least `Permissions`,
* and has at least `Space` bytes after the current offset.
*
* `ptr` can be a pointer, or a smart pointer, i.e., any class that
* supports a `get` method returning a pointer, and `operator=`. This
* includes `Capability` and standard library smart pointers.
*
* If the permissions do not include Global, then this will also check
* that the capability does not point to the current thread's stack.
* This behaviour can be disabled (for example, for use in a shared
* library) by passing `false` for `CheckStack`.
*
* If `EnforceStrictPermissions` is set to `true`, this will also set
* the permissions of passed capability reference to `Permissions`, and
* its bounds to `space`. This is useful for detecting cases where
* compartments ask for less permissions than they actually require.
*
* This function is provided as a wrapper for the `::check_pointer` C
* API. It is always inlined. For each call site, it materialises the
* constants needed before performing an indirect call to
* `::check_pointer`.
*/
template<PermissionSet Permissions = PermissionSet{Permission::Load},
bool CheckStack = true,
bool EnforceStrictPermissions = false>
__always_inline inline bool check_pointer(
auto &ptr,
size_t space = sizeof(
std::remove_pointer<
decltype(ptr)>)) requires(std::
is_pointer_v<
std::remove_cvref_t<decltype(ptr)>> ||
IsSmartPointerLike<
std::remove_cvref_t<decltype(ptr)>>)
{
// We can skip a stack check if we've asked for Global because the
// stack does not have this permission.
constexpr bool StackCheckNeeded =
CheckStack && !Permissions.contains(Permission::Global);
constexpr bool IsRawPointer =
std::is_pointer_v<std::remove_cvref_t<decltype(ptr)>>;
bool isValid;
if constexpr (IsRawPointer)
{
// If passed `ptr` as a raw capability (e.g., `void*`),
// pass it as-is to ::check_pointer.
isValid = ::check_pointer(
ptr, space, Permissions.as_raw(), StackCheckNeeded);
}
else
{
// Otherwise, call `get` on `ptr` to retrieve a raw
// capability.
isValid = ::check_pointer(
ptr.get(), space, Permissions.as_raw(), StackCheckNeeded);
}
// If passed `EnforceStrictPermissions`, set the permissions
// of `ptr` to `Permissions`, and its bounds to `space`
if constexpr (EnforceStrictPermissions)
{
if (isValid)
{
if constexpr (IsRawPointer)
{
Capability cap{ptr};
cap.permissions() &= Permissions;
cap.bounds() = space;
ptr = cap.get();
}
else
{
Capability cap{ptr.get()};
cap.permissions() &= Permissions;
cap.bounds() = space;
ptr = cap.get();
}
}
}
return isValid;
}
/**
* Invokes the passed callable object with interrupts disabled.
*/
template<typename T>
[[cheri::interrupt_state(disabled)]] auto with_interrupts_disabled(T &&fn)
{
return fn();
}
/**
* The codes used in the cause field of the mtval CSR when the processor
* takes a CHERI exception.
*/
enum class CauseCode
{
/**
* No exception. This value is passed to the error handler after a
* forced unwind in a called compartment.
*/
None = 0,
/**
* Attempted to use a capability outside its bounds.
*/
BoundsViolation = 1,
/**
* Attempted to use an untagged capability to authorize something.
*/
TagViolation = 2,
/**
* Attempted to use a sealed capability to authorize something.
*/
SealViolation = 3,
/**
* Attempted to jump to a capability without `Permission::Execute`.
*/
PermitExecuteViolation = 0x11,
/**
* Attempted to load via a capability without `Permission::Load`.
*/
PermitLoadViolation = 0x12,
/**
* Attempted to store via a capability without `Permission::Store`.
*/
PermitStoreViolation = 0x13,
/**
* Attempted to store a tagged capability via a capability without
* `Permission::LoadStoreCapability`.
*/
PermitStoreCapabilityViolation = 0x15,
/**
* Attempted to store a tagged capability without `Permission::Global`
* via capability without `Permission::StoreLocal`.
*/
PermitStoreLocalCapabilityViolation = 0x16,
/**
* Attempted to access a restricted CSR or SCR with PCC without
* `Permission::AccessSystemRegisters`.
*/
PermitAccessSystemRegistersViolation = 0x18,
/**
* Used to represent a value that has no valid meaning in hardware.
*/
Invalid = -1
};
/**
* Register numbers as reported in cap idx field of `mtval` CSR when
* a CHERI exception is taken. Values less than 32 refer to general
* purpose registers and others to SCRs (of these, only PCC can actually
* cause an exception).
*/
enum class RegisterNumber
{
/**
* The zero register, which always contains the `NULL` capability.
*/
CZR = 0x0,
/**
* `$c1` / `$cra` used by the ABI as the return address.
* Not preserved across calls.
*/
CRA = 0x1,
/**
* `$c2` / `$csp` used by the ABI as the stack pointer.
* Preserved across calls.
*/
CSP = 0x2,
/**
* `$c3` / `$cgp` used by the ABI as the global pointer.
* Not allocatable by the compiler, set by the switcher on compartment
* entry.
*/
CGP = 0x3,
/**
* `$c4` / `$ctp` used by the ABI as the thread pointer.
* Currently unused by the compiler.
* Not preserved across compartment calls.
*/
CTP = 0x4,
/**
* `$c5` / `$ct0` used by the ABI as temporary register.
* Not preserved across calls.
*/
CT0 = 0x5,
/**
* `$c6` / `$ct1` used by the ABI as temporary register.
* Not preserved across calls.
*/
CT1 = 0x6,
/**
* `$c7` / `$ct2` used by the ABI as temporary register.
* Not preserved across calls.
*/
CT2 = 0x7,
/**
* `$c8` / `$cs0` used by the ABI as a callee-saved register.
* Preserved across calls.
*/
CS0 = 0x8,
/**
* `$c9` / `$cs1` used by the ABI as a callee-saved register.
* Preserved across calls.
*/
CS1 = 0x9,
/**
* `$c10` / `$ca0` used by the ABI as an argument register.
* Not preserved across calls.
*/
CA0 = 0xa,
/**
* `$c11` / `$ca1` used by the ABI as an argument register.
* Not preserved across calls.
*/
CA1 = 0xb,
/**
* `$c12` / `$ca2` used by the ABI as an argument register.
* Not preserved across calls.
*/
CA2 = 0xc,
/**
* `$c13` / `$ca3` used by the ABI as an argument register.
* Not preserved across calls.
*/
CA3 = 0xd,
/**
* `$c14` / `$ca4` used by the ABI as an argument register.
* Not preserved across calls.
*/
CA4 = 0xe,
/**
* `$c15` / `$ca5` used by the ABI as an argument register.
* Not preserved across calls.
*/
CA5 = 0xf,
/**
* The Program Counter Capability.
*
* Special capability register used to authorize instruction fetch. The
* address is that of the faulting instruction. Also used for accessing
* read-only globals.
*/
PCC = 0x20,
/**
* Machine-mode Trap Code Capability.
*
* Special capability register that
* is installed in PCC when the CPU takes a trap. The address has the
* same semantics as the RISC-V `mtvec` CSR. Only accessible when PCC
* has the AccessSystemRegisters permission.
*/
MTCC = 0x3c,
/**
* Machine-mode Tusted Data Capability.
*
* Special capability register that contains the memory root capability
* on boot. Only accessible when PCC has the AccessSystemRegisters
* permission. Use by the RTOS to store a capability to the trusted
* stack.
*/
MTDC = 0x3d,
/**
* Machine-mode Scratch Capability. Special capabiltiy register that
* contains the sealing root capability on boot. Only accessible when
* PCC has the AccessSystemRegisters permission.
*/
MScratchC = 0x3e,
/**
* Machine-mode Exception Program Counter Capability. Special capability
* register that contains the PCC of the faulting instruction on trap.
* The address has the same semantics as the RISC-V `mepc` CSR. Only
* accessible when PCC has the AccessSystemRegisters permission.
*/
MEPCC = 0x3f,
/**
* Indicates a value that is not used by the hardware to refer to a
* register.
*/
Invalid = -1
};
/**
* Decompose the value reported in the `mtval` CSR on CHERI exception
* into a pair of `CauseCode` and `RegisterNumber`.
*
* Will return `CauseCode::Invalid` if the code field is not one
* of the defined causes and `RegisterNumber::Invalid` if the register
* number is not a valid register number. Other bits of mtval are ignored.
*/
inline std::pair<CauseCode, RegisterNumber>
extract_cheri_mtval(uint32_t mtval)
{
auto causeCode = magic_enum::enum_cast<CauseCode>(mtval & 0x1f)
.value_or(CauseCode::Invalid);
auto registerNumber =
magic_enum::enum_cast<RegisterNumber>((mtval >> 5) & 0x3f)
.value_or(RegisterNumber::Invalid);
return {causeCode, registerNumber};
}
} // namespace CHERI