Skip to main content

strat9_kernel/
capability.rs

1//! Capability-based Security System
2//!
3//! See also: [Architecture Overview](https://strat9-os.org/strat9-os-docs/architecture.html)
4//! for the security model and data flow diagrams.
5//!
6//! Implements a capability-based security model for Strat9-OS.
7//! All kernel resources are accessed through unforgeable tokens (capabilities).
8
9use crate::{
10    ipc::{self, MultiHandleResource},
11    process::TaskId,
12    sync::SpinLock,
13    vfs,
14};
15use alloc::{collections::BTreeMap, vec::Vec};
16use core::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
17
18/// Unique identifier for a capability
19#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
20pub struct CapId(u64);
21
22/// Key for per-resource refcounting: (resource_type, resource_ptr)
23#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
24struct ResourceKey(ResourceType, usize);
25
26impl CapId {
27    /// Generate a new unique capability ID
28    pub fn new() -> Self {
29        static NEXT_ID: AtomicU64 = AtomicU64::new(0);
30        CapId(NEXT_ID.fetch_add(1, Ordering::SeqCst))
31    }
32
33    /// Convert a raw u64 into a CapId (used for syscall handles).
34    pub fn from_raw(raw: u64) -> Self {
35        CapId(raw)
36    }
37
38    /// Get the raw u64 value (for syscall return values).
39    pub fn as_u64(self) -> u64 {
40        self.0
41    }
42}
43
44/// Types of kernel resources that can be accessed via capabilities
45#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
46pub enum ResourceType {
47    MemoryRegion,
48    IoPortRange,
49    InterruptLine,
50    IpcPort,
51    /// A typed MPMC sync-channel (SyncChan), accessed via SYS_CHAN_* syscalls.
52    Channel,
53    /// Shared-memory ring buffer for bulk IPC (SYS_IPC_RING_*).
54    SharedRing,
55    /// POSIX-like counting semaphore (SYS_SEM_*).
56    Semaphore,
57    Device,
58    AddressSpace,
59    Silo,
60    Module,
61    File,
62    Nic,
63    FileSystem,
64    Console,
65    Keyboard,
66    Volume,
67    Namespace,
68    /// IPC transport endpoint (N1/N2/N3).
69    IpcTransport,
70}
71
72/// Permissions associated with a capability
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub struct CapPermissions {
75    pub read: bool,
76    pub write: bool,
77    pub execute: bool,
78    /// Allow granting this capability to other processes
79    pub grant: bool,
80    /// Allow revoking this capability
81    pub revoke: bool,
82}
83
84impl CapPermissions {
85    /// Create permissions with all rights disabled
86    pub const fn none() -> Self {
87        CapPermissions {
88            read: false,
89            write: false,
90            execute: false,
91            grant: false,
92            revoke: false,
93        }
94    }
95
96    /// Create permissions with read and write rights
97    pub const fn read_write() -> Self {
98        CapPermissions {
99            read: true,
100            write: true,
101            execute: false,
102            grant: false,
103            revoke: false,
104        }
105    }
106
107    /// Create permissions with all rights enabled
108    pub const fn all() -> Self {
109        CapPermissions {
110            read: true,
111            write: true,
112            execute: true,
113            grant: true,
114            revoke: true,
115        }
116    }
117}
118
119/// A capability token that grants access to a kernel resource
120#[derive(Debug, Clone)]
121pub struct Capability {
122    /// Unique identifier for this capability
123    pub id: CapId,
124    /// Type of resource this capability grants access to
125    pub resource_type: ResourceType,
126    /// Permissions associated with this capability
127    pub permissions: CapPermissions,
128    /// Reference to the actual resource (opaque to prevent direct access)
129    pub resource: usize, // Actually a pointer to the resource, cast to usize
130    /// Opaque badge injected by the kernel at send time.
131    ///
132    /// For channel capabilities this is the identity presented to receivers
133    /// in `IpcMessage::sender`.  The sender cannot forge it: the kernel
134    /// overwrites `sender` with `cap.badge` on every `SYS_CHAN_SEND`.
135    ///
136    /// Defaults to `id.as_u64()`.  When a capability is granted (delegated)
137    /// via `SYS_CHAN_GRANT`, the granter may supply a custom badge so the
138    /// receiver can distinguish individual clients without learning their
139    /// global task IDs.
140    pub badge: u64,
141}
142
143/// Table of capabilities for a process
144pub struct CapabilityTable {
145    /// Mapping from capability ID to capability
146    capabilities: BTreeMap<CapId, Capability>,
147}
148
149impl Clone for CapabilityTable {
150    /// Performs the clone operation.
151    fn clone(&self) -> Self {
152        Self {
153            capabilities: self.capabilities.clone(),
154        }
155    }
156}
157
158impl CapabilityTable {
159    /// Create a new empty capability table
160    pub fn new() -> Self {
161        CapabilityTable {
162            capabilities: BTreeMap::new(),
163        }
164    }
165
166    /// Insert a capability into the table
167    pub fn insert(&mut self, cap: Capability) -> CapId {
168        let id = cap.id;
169        get_capability_manager().register_capability(cap.clone());
170        self.capabilities.insert(id, cap);
171        id
172    }
173
174    /// Remove a capability from the table
175    pub fn remove(&mut self, id: CapId) -> Option<Capability> {
176        let removed = self.capabilities.remove(&id);
177        if removed.is_some() {
178            let _ = get_capability_manager().revoke_capability(id);
179        }
180        removed
181    }
182
183    /// Get a reference to a capability (no permission check).
184    pub fn get(&self, id: CapId) -> Option<&Capability> {
185        self.capabilities.get(&id)
186    }
187
188    /// Revoke all capabilities in this table and clear it.
189    /// Does not allocate memory.
190    pub fn revoke_all(&mut self) {
191        let capabilities = self.take_all();
192        for capability in &capabilities {
193            release_capability(capability, None);
194        }
195    }
196
197    /// Removes and returns every capability in this table.
198    pub fn take_all(&mut self) -> Vec<Capability> {
199        core::mem::take(&mut self.capabilities)
200            .into_values()
201            .collect()
202    }
203
204    /// Check whether any capability of the given resource type has required permissions.
205    pub fn has_resource_type_with_permissions(
206        &self,
207        resource_type: ResourceType,
208        required: CapPermissions,
209    ) -> bool {
210        self.capabilities.values().any(|cap| {
211            cap.resource_type == resource_type
212                && (!required.read || cap.permissions.read)
213                && (!required.write || cap.permissions.write)
214                && (!required.execute || cap.permissions.execute)
215                && (!required.grant || cap.permissions.grant)
216                && (!required.revoke || cap.permissions.revoke)
217        })
218    }
219
220    /// Check whether a specific resource has required permissions.
221    pub fn has_resource_with_permissions(
222        &self,
223        resource_type: ResourceType,
224        resource: usize,
225        required: CapPermissions,
226    ) -> bool {
227        self.capabilities.values().any(|cap| {
228            cap.resource_type == resource_type
229                && cap.resource == resource
230                && (!required.read || cap.permissions.read)
231                && (!required.write || cap.permissions.write)
232                && (!required.execute || cap.permissions.execute)
233                && (!required.grant || cap.permissions.grant)
234                && (!required.revoke || cap.permissions.revoke)
235        })
236    }
237
238    /// Get a reference to a capability if it exists and has the required permissions
239    pub fn get_with_permissions(&self, id: CapId, required: CapPermissions) -> Option<&Capability> {
240        self.capabilities.get(&id).filter(|cap| {
241            // Check if the capability has all required permissions
242            (!required.read || cap.permissions.read)
243                && (!required.write || cap.permissions.write)
244                && (!required.execute || cap.permissions.execute)
245                && (!required.grant || cap.permissions.grant)
246                && (!required.revoke || cap.permissions.revoke)
247        })
248    }
249
250    /// Get a mutable reference to a capability if it exists and has the required permissions
251    pub fn get_mut_with_permissions(
252        &mut self,
253        id: CapId,
254        required: CapPermissions,
255    ) -> Option<&mut Capability> {
256        if let Some(cap) = self.capabilities.get_mut(&id) {
257            // Check if the capability has all required permissions
258            if (!required.read || cap.permissions.read)
259                && (!required.write || cap.permissions.write)
260                && (!required.execute || cap.permissions.execute)
261                && (!required.grant || cap.permissions.grant)
262                && (!required.revoke || cap.permissions.revoke)
263            {
264                Some(cap)
265            } else {
266                None
267            }
268        } else {
269            None
270        }
271    }
272
273    /// Duplicate a capability (grant permission required).
274    ///
275    /// The new capability inherits the same badge as the original.
276    /// Use [`duplicate_with_badge`] when delegating to assign a custom badge
277    /// that identifies the delegation chain to the receiver.
278    pub fn duplicate(&mut self, id: CapId) -> Option<Capability> {
279        if let Some(cap) = self.capabilities.get(&id) {
280            if cap.permissions.grant {
281                Some(Capability {
282                    id: CapId::new(),
283                    resource_type: cap.resource_type,
284                    permissions: cap.permissions,
285                    resource: cap.resource,
286                    badge: cap.badge,
287                })
288            } else {
289                None
290            }
291        } else {
292            None
293        }
294    }
295
296    /// Duplicate a capability with a custom badge (grant permission required).
297    ///
298    /// Used by `SYS_CHAN_GRANT` to delegate a channel endpoint while giving
299    /// the receiver a badge the receiver can use to distinguish senders.
300    /// The `new_badge` value is injected by the kernel : the sender cannot
301    /// override it at send time.
302    pub fn duplicate_with_badge(&mut self, id: CapId, new_badge: u64) -> Option<Capability> {
303        if let Some(cap) = self.capabilities.get(&id) {
304            if cap.permissions.grant {
305                Some(Capability {
306                    id: CapId::new(),
307                    resource_type: cap.resource_type,
308                    permissions: cap.permissions,
309                    resource: cap.resource,
310                    badge: new_badge,
311                })
312            } else {
313                None
314            }
315        } else {
316            None
317        }
318    }
319}
320
321/// Global capability manager
322pub struct CapabilityManager {
323    /// All capabilities in the system
324    all_capabilities: SpinLock<BTreeMap<CapId, Capability>>,
325    /// Per-resource refcounts for O(1) capability counting (no full-table scan).
326    ///
327    /// Each `register_capability` increments the counter; each `revoke_capability`
328    /// decrements it. `resource_capability_count` is O(1) : it reads the atomic
329    /// directly instead of scanning `all_capabilities`.  The lock is only held
330    /// for the brief insert/remove + atomic update, never for a full-map scan.
331    resource_refcounts: SpinLock<BTreeMap<ResourceKey, AtomicUsize>>,
332}
333
334impl CapabilityManager {
335    /// Create a new capability manager
336    pub fn new() -> Self {
337        CapabilityManager {
338            all_capabilities: SpinLock::new(BTreeMap::new()),
339            resource_refcounts: SpinLock::new(BTreeMap::new()),
340        }
341    }
342
343    /// Register a new resource and return a capability to access it
344    pub fn create_capability(
345        &self,
346        resource_type: ResourceType,
347        resource: usize,
348        permissions: CapPermissions,
349    ) -> Capability {
350        let id = CapId::new();
351        let cap = Capability {
352            badge: id.as_u64(),
353            id,
354            resource_type,
355            permissions,
356            resource,
357        };
358
359        self.all_capabilities.lock().insert(cap.id, cap.clone());
360        self.increment_resource_refcount(resource_type, resource);
361        cap
362    }
363
364    /// Register an already-created capability in the global table.
365    pub fn register_capability(&self, cap: Capability) {
366        let key = ResourceKey(cap.resource_type, cap.resource);
367        self.all_capabilities.lock().insert(cap.id, cap);
368        self.increment_resource_refcount_key(&key);
369    }
370
371    /// Revoke a capability (removes it from the global table)
372    pub fn revoke_capability(&self, id: CapId) -> Option<Capability> {
373        let removed = {
374            let mut caps = self.all_capabilities.lock();
375            caps.remove(&id)
376        };
377        if let Some(ref cap) = removed {
378            let key = ResourceKey(cap.resource_type, cap.resource);
379            self.decrement_resource_refcount(&key);
380        }
381        removed
382    }
383
384    /// Count remaining capabilities that still reference the same resource.
385    /// O(1) lookup via per-resource refcount : no full-table scan.
386    pub fn resource_capability_count(&self, resource_type: ResourceType, resource: usize) -> usize {
387        let key = ResourceKey(resource_type, resource);
388        self.resource_refcounts
389            .lock()
390            .get(&key)
391            .map(|rc| rc.load(Ordering::Relaxed))
392            .unwrap_or(0)
393    }
394
395    // -- Internal refcount helpers --
396
397    fn increment_resource_refcount(&self, resource_type: ResourceType, resource: usize) {
398        let key = ResourceKey(resource_type, resource);
399        self.increment_resource_refcount_key(&key);
400    }
401
402    fn increment_resource_refcount_key(&self, key: &ResourceKey) {
403        let mut refcounts = self.resource_refcounts.lock();
404        let entry = refcounts.entry(*key).or_insert_with(|| AtomicUsize::new(0));
405        entry.fetch_add(1, Ordering::Relaxed);
406    }
407
408    fn decrement_resource_refcount(&self, key: &ResourceKey) {
409        let mut refcounts = self.resource_refcounts.lock();
410        // Check and remove under the same lock acquisition to avoid the TOCTOU
411        // window where another thread could increment the refcount between the
412        // `drop` and the second `lock().remove()`.
413        let should_remove = if let Some(rc) = refcounts.get(key) {
414            rc.fetch_sub(1, Ordering::Relaxed) == 1
415        } else {
416            false
417        };
418        if should_remove {
419            refcounts.remove(key);
420        }
421    }
422}
423
424use spin::Once;
425
426static CAPABILITY_MANAGER: Once<CapabilityManager> = Once::new();
427
428/// Get a reference to the global capability manager
429pub fn get_capability_manager() -> &'static CapabilityManager {
430    CAPABILITY_MANAGER.call_once(CapabilityManager::new)
431}
432
433/// Releases a capability and cleans up the underlying resource.
434pub fn release_capability(cap: &Capability, owner_task: Option<TaskId>) {
435    let _ = get_capability_manager().revoke_capability(cap.id);
436    let remaining_caps =
437        get_capability_manager().resource_capability_count(cap.resource_type, cap.resource);
438
439    let shared_resource = match cap.resource_type {
440        ResourceType::SharedRing => Some(MultiHandleResource::SharedRing(ipc::RingId::from_u64(
441            cap.resource as u64,
442        ))),
443        ResourceType::Semaphore => Some(MultiHandleResource::Semaphore(ipc::SemId::from_u64(
444            cap.resource as u64,
445        ))),
446        ResourceType::Channel => Some(MultiHandleResource::Channel(ipc::ChanId::from_u64(
447            cap.resource as u64,
448        ))),
449        ResourceType::IpcPort => Some(MultiHandleResource::IpcPort {
450            id: ipc::PortId::from_u64(cap.resource as u64),
451            owner: owner_task,
452        }),
453        _ => None,
454    };
455
456    if let Some(resource) = shared_resource {
457        if remaining_caps == 0 {
458            let _ = resource.destroy();
459        }
460        return;
461    }
462
463    match cap.resource_type {
464        ResourceType::File => {
465            if let Ok(fd) = u32::try_from(cap.resource) {
466                let _ = vfs::close(fd);
467            }
468        }
469        ResourceType::MemoryRegion => {
470            let _ =
471                crate::memory::memory_region_registry().release_handle(cap.resource as u64, cap.id);
472        }
473        ResourceType::IpcTransport => {
474            if remaining_caps == 0 {
475                let tid = crate::ipc::transport::TransportId::from_u64(cap.resource as u64);
476                let _ = crate::syscall::transport::TRANSPORT_MANAGER.close(tid);
477            }
478        }
479        _ => {}
480    }
481}