kernel/utilities/capability_ptr.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
// Licensed under the Apache License, Version 2.0 or the MIT License.
// SPDX-License-Identifier: Apache-2.0 OR MIT
// Copyright Google LLC 2024.
//! Defines the CapabilityPtr type
use core::fmt::{Formatter, LowerHex, UpperHex};
use core::ops::AddAssign;
use core::ptr::null;
use crate::cheri::{cheri_perms, cptr, CPtrOps};
use crate::config::{CfgMatch, CONFIG};
use crate::TIfCfg;
use super::machine_register::MachineRegister;
// The inner type for CapabilityPtr, which it abstracts with a consistent interface
type InnerType = TIfCfg!(is_cheri, cptr, *const ());
/// A pointer to userspace memory with implied authority.
///
/// A [`CapabilityPtr`] points to memory a userspace process may be permitted to
/// read, write, or execute. It is sized exactly to a CPU register that can pass
/// values between userspace and the kernel [^note1]. Operations on the pointer
/// may affect permissions, e.g. offsetting the pointer beyond the bounds of the
/// memory object may invalidate it.
///
/// [`CapabilityPtr`] should be used to store or pass a value between the
/// kernel and userspace that may represent a valid userspace reference,
/// when one party intends the other to access it.
///
/// [^note1]: Depending on the architecture, the size of a
/// [`CapabilityPtr`] may be a word size or larger, e.g., if registers
/// can store metadata such as access permissions.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[repr(transparent)]
pub struct CapabilityPtr {
ptr: InnerType,
}
/// Permission sets a [`CapabilityPtr`] may grant.
/// These may not be enforced or exist on a given platform.
#[derive(Copy, Clone, PartialEq)]
pub enum CapabilityPtrPermissions {
None,
Read,
Write,
ReadWrite,
Execute,
}
impl Default for CapabilityPtr {
/// Returns a null CapabilityPtr.
fn default() -> Self {
Self {
ptr: InnerType::new(crate::cheri::null(), null()),
}
}
}
impl From<usize> for CapabilityPtr {
/// Constructs a [`CapabilityPtr`] with a given address but no authority or
/// provenance.
#[inline]
fn from(from: usize) -> Self {
Self {
ptr: if CONFIG.is_cheri {
// On non-cheri this is a useless convervstion
#[allow(clippy::useless_conversion)]
InnerType::new_true(from.into())
} else {
// Ideally this would be core::ptr::without_provenance(from), but
// the CHERI toolchain is too old for without_provenance. This is
// equivalent.
InnerType::new_false(null::<()>().with_addr(from))
},
}
}
}
// In addition to its publicly-documented capabilities, CapabilityPtr's
// implementation can also store integers. MachineRegister uses that ability to
// simplify its implementation. No other user of CapabilityPtr should rely on
// that ability.
impl From<usize> for MachineRegister {
fn from(from: usize) -> Self {
Self::from(CapabilityPtr::from(from))
}
}
impl UpperHex for CapabilityPtr {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self.ptr.get_match() {
CfgMatch::True(cheri_ptr) => UpperHex::fmt(cheri_ptr, f),
CfgMatch::False(ptr) => UpperHex::fmt(&ptr.addr(), f),
}
}
}
impl LowerHex for CapabilityPtr {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self.ptr.get_match() {
CfgMatch::True(cheri_ptr) => LowerHex::fmt(cheri_ptr, f),
CfgMatch::False(ptr) => LowerHex::fmt(&ptr.addr(), f),
}
}
}
impl AddAssign<usize> for CapabilityPtr {
/// Increments the address of a [`CapabilityPtr`]. If the pointer is offset
/// past its bounds, its authority may be invalidated.
#[inline]
fn add_assign(&mut self, rhs: usize) {
self.ptr.map_mut(
|cheri_ptr| cheri_ptr.add_assign(rhs),
|ptr| *ptr = ptr.wrapping_byte_add(rhs),
)
}
}
/// Helper to convert abstract permissions to the bitmap that CHERI uses
fn cheri_perms_for(perms: CapabilityPtrPermissions) -> usize {
match perms {
CapabilityPtrPermissions::None => 0,
CapabilityPtrPermissions::Read => cheri_perms::DEFAULT_R,
CapabilityPtrPermissions::Write => cheri_perms::STORE,
CapabilityPtrPermissions::ReadWrite => cheri_perms::DEFAULT_RW,
CapabilityPtrPermissions::Execute => cheri_perms::EXECUTE,
}
}
impl CapabilityPtr {
/// Returns the address of this pointer. Does not expose provenance.
pub fn addr(self) -> usize {
self.as_ptr::<()>().addr()
}
/// Checks whether any that metadata that exists would allow this operation.
///
/// A [`CapabilityPtr`] constructed with new_with_authority would return true for this method
/// if specifying the same length (or shorter) with the same permissions (or fewer).
///
/// This is over-approximate as a lack of any such metadata would result in returning true.
///
/// This likely does not meet the requirements rust would have to covert to a reference and
/// further justification should be present elsewhere if doing so in the kernel.
///
/// If this function returns false, then it should likely not be allowed.
pub fn is_valid_for_operation(&self, length: usize, perms: CapabilityPtrPermissions) -> bool {
match self.ptr.get_match() {
CfgMatch::True(cheri_ptr) => {
// CHERI can distinguish between a valid and invalid capability with zero length.
// Tock, on the other hand, allows length zero allocations at any address.
// This is especially important as NULL and zero are often used with allow
// syscalls, which will reject invalid capabilities.
// We special case length 0 here. This is important for users as they cannot rely
// on the sanctity of zero length allocations to use as tokens. They are likely
// already using length 1 anyway so as not to be confused between adjacent objects.
(length == 0) || cheri_ptr.is_valid_for_operation(length, cheri_perms_for(perms))
}
CfgMatch::False(_) => true,
}
}
/// Returns the pointer component of a [`CapabilityPtr`] but without any of the authority.
pub fn as_ptr<T>(&self) -> *const T {
match self.ptr.get_match() {
CfgMatch::True(cheri_ptr) => cheri_ptr.as_ptr(),
CfgMatch::False(ptr) => *ptr,
}
.cast()
}
/// Returns the pointer component of a [`CapabilityPtr`] but without any of the authority only
/// if valid for an operation of length with perms.
///
/// See is_valid_for_operation.
pub fn as_ptr_checked<T>(
&self,
length: usize,
perms: CapabilityPtrPermissions,
) -> Result<*const T, ()> {
if self.is_valid_for_operation(length, perms) {
Ok(self.as_ptr())
} else {
Err(())
}
}
/// Construct a [`CapabilityPtr`] from a raw pointer, with authority ranging over
/// [`base`, `base + length`) and permissions `perms`.
///
/// Provenance note: may derive from a pointer other than the input to provide something with
/// valid provenance to justify the other arguments.
///
/// ## Safety
///
/// Constructing a [`CapabilityPtr`] with metadata may convey authority to
/// dereference this pointer, such as in userspace. When these pointers
/// serve as the only memory isolation primitive in the system, this method
/// can thus break Tock's isolation model. As semi-trusted kernel code can
/// name this type and method, it is thus marked as `unsafe`.
#[inline]
pub unsafe fn new_with_authority(
ptr: *const (),
base: usize,
length: usize,
perms: CapabilityPtrPermissions,
) -> Self {
Self {
ptr: if CONFIG.is_cheri {
let mut result = cptr::default();
if perms == CapabilityPtrPermissions::Execute {
result.set_addr_from_pcc_restricted(ptr as usize, base, length);
} else {
result.set_addr_from_ddc_restricted(
ptr as usize,
base,
length,
cheri_perms_for(perms),
);
}
InnerType::new_true(result)
} else {
InnerType::new_false(ptr)
},
}
}
/// If the [`CapabilityPtr`] is null returns `default`, otherwise applies `f` to `self`.
#[inline]
pub fn map_or<U, F>(&self, default: U, f: F) -> U
where
F: FnOnce(&Self) -> U,
{
if self.as_ptr::<()>().is_null() {
default
} else {
f(self)
}
}
/// If the [`CapabilityPtr`] is null returns `default`, otherwise applies `f` to `self`.
/// default is only evaluated if `self` is not null.
#[inline]
pub fn map_or_else<U, D, F>(&self, default: D, f: F) -> U
where
D: FnOnce() -> U,
F: FnOnce(&Self) -> U,
{
if self.as_ptr::<()>().is_null() {
default()
} else {
f(self)
}
}
}