1use std::fmt;
18use std::sync::atomic::{AtomicU64, Ordering};
19use std::sync::Arc;
20
21#[derive(Clone, Copy, PartialEq, Eq, Hash)]
26pub struct CapId(u128);
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum CapIdError {
31 Entropy,
33}
34
35impl CapId {
36 pub const NONE: CapId = CapId(0);
38
39 pub fn random() -> Result<Self, CapIdError> {
41 for _ in 0..8 {
42 let mut bytes = [0u8; 16];
43 if getrandom::getrandom(&mut bytes).is_err() {
44 return Err(CapIdError::Entropy);
45 }
46 let raw = u128::from_le_bytes(bytes);
47 if raw != 0 {
48 return Ok(CapId(raw));
49 }
50 }
51 Err(CapIdError::Entropy)
52 }
53
54 #[inline]
55 pub const fn is_none(self) -> bool {
56 self.0 == 0
57 }
58
59 #[inline]
60 pub const fn as_u128(self) -> u128 {
61 self.0
62 }
63
64 #[inline]
66 pub(crate) const fn from_raw(raw: u128) -> Self {
67 CapId(raw)
68 }
69}
70
71impl fmt::Debug for CapId {
72 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73 write!(f, "CapId({:032x})", self.0)
74 }
75}
76
77impl fmt::Display for CapId {
78 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79 if self.is_none() {
80 f.write_str("cap#none")
81 } else {
82 write!(f, "cap#{:032x}", self.0)
83 }
84 }
85}
86
87impl std::error::Error for CapIdError {}
88
89impl fmt::Display for CapIdError {
90 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91 match self {
92 CapIdError::Entropy => f.write_str("capability CSPRNG unavailable"),
93 }
94 }
95}
96
97pub type NativeIdx = u32;
99
100#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
106pub struct CapRights(u32);
107
108impl CapRights {
109 pub const NONE: CapRights = CapRights(0);
110 pub const SEND: CapRights = CapRights(1 << 0);
111 pub const ASK: CapRights = CapRights(1 << 1);
112 pub const RECV: CapRights = CapRights(1 << 2);
113 pub const SPAWN: CapRights = CapRights(1 << 3);
114 pub const LINK: CapRights = CapRights(1 << 4);
115 pub const MONITOR: CapRights = CapRights(1 << 5);
116 pub const ADMIN: CapRights = CapRights(1 << 6);
117 pub const NATIVE: CapRights = CapRights(1 << 7);
118
119 pub const ADDRESSING: CapRights = CapRights(
121 Self::SEND.0 | Self::ASK.0 | Self::LINK.0 | Self::MONITOR.0,
122 );
123
124 pub const FLOW: CapRights = CapRights(
129 Self::SEND.0
130 | Self::ASK.0
131 | Self::RECV.0
132 | Self::SPAWN.0
133 | Self::LINK.0
134 | Self::MONITOR.0
135 | Self::NATIVE.0,
136 );
137
138 pub const ROOT: CapRights = Self::FLOW;
141
142 pub const SEND_ASK: CapRights = CapRights(Self::SEND.0 | Self::ASK.0);
144
145 #[inline]
146 pub const fn empty() -> Self {
147 CapRights(0)
148 }
149
150 #[inline]
151 pub const fn union(self, other: CapRights) -> CapRights {
152 CapRights(self.0 | other.0)
153 }
154
155 #[inline]
156 pub const fn intersect(self, other: CapRights) -> CapRights {
157 CapRights(self.0 & other.0)
158 }
159
160 #[inline]
161 pub const fn contains(self, other: CapRights) -> bool {
162 self.0 & other.0 == other.0
163 }
164
165 #[inline]
166 pub const fn is_subset_of(self, other: CapRights) -> bool {
167 self.0 & !other.0 == 0
168 }
169
170 #[inline]
171 pub const fn bits(self) -> u32 {
172 self.0
173 }
174
175 #[inline]
177 pub const fn bits_u8(self) -> u8 {
178 self.0 as u8
179 }
180
181 #[inline]
182 pub const fn from_bits(bits: u32) -> Self {
183 CapRights(bits)
184 }
185
186 #[inline]
187 pub const fn from_u8(bits: u8) -> Self {
188 CapRights(bits as u32)
189 }
190}
191
192#[derive(Clone, Debug, PartialEq, Eq)]
196pub struct NativeMask(Arc<[u64]>);
197
198impl NativeMask {
199 pub fn empty(native_count: usize) -> Self {
200 let words = native_count.div_ceil(64);
201 Self(Arc::from(vec![0u64; words].into_boxed_slice()))
202 }
203
204 pub fn full(native_count: usize) -> Self {
205 let words = native_count.div_ceil(64);
206 let mut v = vec![u64::MAX; words];
207 let rem = native_count % 64;
210 if rem != 0 {
211 if let Some(last) = v.last_mut() {
212 *last &= (1u64 << rem) - 1;
213 }
214 }
215 Self(Arc::from(v.into_boxed_slice()))
216 }
217
218 pub fn from_indices(native_count: usize, idxs: &[NativeIdx]) -> Self {
219 let words = native_count.div_ceil(64);
220 let mut v = vec![0u64; words];
221 for &i in idxs {
222 let (w, b) = (i as usize / 64, i as usize % 64);
223 if w < v.len() && (i as usize) < native_count {
224 v[w] |= 1u64 << b;
225 }
226 }
227 Self(Arc::from(v.into_boxed_slice()))
228 }
229
230 pub fn word_len(&self) -> usize {
231 self.0.len()
232 }
233
234 #[inline]
235 pub fn allows(&self, idx: NativeIdx) -> bool {
236 let (w, b) = (idx as usize / 64, idx as usize % 64);
237 match self.0.get(w) {
238 Some(word) => word & (1u64 << b) != 0,
239 None => false,
240 }
241 }
242
243 pub fn intersect(&self, requested: &NativeMask) -> NativeMask {
246 let n = self.0.len().min(requested.0.len());
247 let mut words = vec![0u64; n];
248 for i in 0..n {
249 let a = match self.0.get(i) {
250 Some(w) => *w,
251 None => 0,
252 };
253 let b = match requested.0.get(i) {
254 Some(w) => *w,
255 None => 0,
256 };
257 words[i] = a & b;
258 }
259 NativeMask(Arc::from(words.into_boxed_slice()))
260 }
261}
262
263#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
266pub enum CapTarget {
267 Flow(u64),
269 NativeTable,
272 Scheduler,
274}
275
276impl CapTarget {
277 pub fn flow_id(self) -> Option<u64> {
278 match self {
279 CapTarget::Flow(id) => Some(id),
280 CapTarget::NativeTable | CapTarget::Scheduler => None,
281 }
282 }
283}
284
285#[derive(Clone, Debug, PartialEq, Eq)]
287pub struct Cap {
288 pub target: CapTarget,
289 pub rights: CapRights,
290 pub native_mask: Option<NativeMask>,
292 epoch: u64,
295}
296
297#[derive(Debug)]
300pub struct RevocationCell(AtomicU64);
301
302impl RevocationCell {
303 pub fn new() -> Self {
304 Self(AtomicU64::new(0))
305 }
306
307 pub fn epoch(&self) -> u64 {
308 self.0.load(Ordering::Acquire)
309 }
310
311 pub fn revoke(&self) {
312 self.0.fetch_add(1, Ordering::AcqRel);
313 }
314}
315
316impl Default for RevocationCell {
317 fn default() -> Self {
318 Self::new()
319 }
320}
321
322impl Cap {
323 pub fn root(
325 target: CapTarget,
326 rights: CapRights,
327 native_mask: Option<NativeMask>,
328 cell: &RevocationCell,
329 ) -> Self {
330 Self {
331 target,
332 rights,
333 native_mask,
334 epoch: cell.epoch(),
335 }
336 }
337
338 pub fn epoch(&self) -> u64 {
339 self.epoch
340 }
341
342 pub fn is_valid(&self, cell: &RevocationCell) -> bool {
343 self.epoch == cell.epoch()
344 }
345
346 pub fn attenuate(&self, want_rights: CapRights, want_native: Option<&NativeMask>) -> Cap {
350 let rights = self.rights.intersect(want_rights);
351 let native_mask = match (&self.native_mask, want_native) {
352 (Some(cur), Some(req)) => Some(cur.intersect(req)),
353 (Some(cur), None) => Some(cur.clone()),
354 (None, _) => None,
355 };
356 Cap {
357 target: self.target,
358 rights,
359 native_mask,
360 epoch: self.epoch,
361 }
362 }
363}
364
365#[cfg(test)]
366mod tests {
367 use super::*;
368
369 #[test]
370 fn none_is_zero_and_random_is_not() -> Result<(), CapIdError> {
371 assert!(CapId::NONE.is_none());
372 let a = CapId::random()?;
373 let b = CapId::random()?;
374 assert!(!a.is_none());
375 assert!(!b.is_none());
376 assert_ne!(a, b);
377 Ok(())
378 }
379
380 #[test]
381 fn from_raw_is_crate_internal() {
382 assert_eq!(CapId::from_raw(0), CapId::NONE);
383 assert!(!CapId::from_raw(1).is_none());
384 }
385
386 #[test]
387 fn send_ask_bits_stay_compatible() {
388 assert_eq!(CapRights::SEND.bits(), 0b01);
389 assert_eq!(CapRights::ASK.bits(), 0b10);
390 assert_eq!(CapRights::SEND_ASK.bits(), 0b11);
391 assert!(CapRights::ADMIN.bits() > 0b11);
392 }
393
394 #[test]
395 fn attenuation_never_escalates() {
396 let cell = RevocationCell::new();
397 let nm = NativeMask::from_indices(128, &[3, 5, 9]);
398 let parent = Cap::root(
399 CapTarget::Flow(1),
400 CapRights::SEND.union(CapRights::NATIVE),
401 Some(nm),
402 &cell,
403 );
404
405 let child = parent.attenuate(CapRights::SEND.union(CapRights::ADMIN), None);
406 assert!(!child.rights.contains(CapRights::ADMIN));
407 assert!(child.rights.contains(CapRights::SEND));
408
409 let wide = NativeMask::full(128);
410 let child2 = parent.attenuate(CapRights::NATIVE, Some(&wide));
411 let child_mask = child2.native_mask.as_ref();
412 assert!(child_mask.is_some_and(|m| m.allows(3)));
413 let parent_mask = parent.native_mask.as_ref();
414 assert!(parent_mask.is_some_and(|m| !m.allows(7)));
415 }
416
417 #[test]
418 fn attenuation_is_subset_for_all_byte_masks() {
419 let cell = RevocationCell::new();
420 for src in 0u32..=255 {
421 for want in 0u32..=255 {
422 let parent = Cap::root(
423 CapTarget::Flow(1),
424 CapRights::from_bits(src),
425 None,
426 &cell,
427 );
428 let child = parent.attenuate(CapRights::from_bits(want), None);
429 assert!(
430 child.rights.is_subset_of(parent.rights),
431 "escalation src={src:#x} want={want:#x} got={:#x}",
432 child.rights.bits()
433 );
434 assert_eq!(child.rights.bits(), src & want);
435 }
436 }
437 }
438
439 #[test]
440 fn chained_delegate_never_gains_bits() {
441 let cell = RevocationCell::new();
442 let mut cap = Cap::root(
443 CapTarget::Flow(1),
444 CapRights::FLOW,
445 Some(NativeMask::from_indices(32, &[0, 1, 2])),
446 &cell,
447 );
448 let adversarial = [
449 CapRights::ADMIN,
450 CapRights::NATIVE.union(CapRights::ADMIN),
451 CapRights::from_bits(u32::MAX),
452 CapRights::NONE,
453 CapRights::FLOW,
454 ];
455 for want in adversarial {
456 cap = cap.attenuate(want, Some(&NativeMask::full(32)));
457 assert!(cap.rights.is_subset_of(CapRights::FLOW));
458 assert!(!cap.rights.contains(CapRights::ADMIN));
459 if let Some(mask) = &cap.native_mask {
460 assert!(!mask.allows(7));
461 }
462 }
463 }
464
465 #[test]
466 fn revocation_kills_all_derived_caps() {
467 let cell = RevocationCell::new();
468 let root = Cap::root(CapTarget::Flow(1), CapRights::SEND, None, &cell);
469 let child = root.attenuate(CapRights::SEND, None);
470 assert!(child.is_valid(&cell));
471 cell.revoke();
472 assert!(!child.is_valid(&cell));
473 assert!(!root.is_valid(&cell));
474 }
475}