Skip to main content

strat9_kernel/
capability.rs

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