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}
131
132/// Table of capabilities for a process
133pub struct CapabilityTable {
134    /// Mapping from capability ID to capability
135    capabilities: BTreeMap<CapId, Capability>,
136}
137
138impl Clone for CapabilityTable {
139    /// Performs the clone operation.
140    fn clone(&self) -> Self {
141        Self {
142            capabilities: self.capabilities.clone(),
143        }
144    }
145}
146
147impl CapabilityTable {
148    /// Create a new empty capability table
149    pub fn new() -> Self {
150        CapabilityTable {
151            capabilities: BTreeMap::new(),
152        }
153    }
154
155    /// Insert a capability into the table
156    pub fn insert(&mut self, cap: Capability) -> CapId {
157        let id = cap.id;
158        get_capability_manager().register_capability(cap.clone());
159        self.capabilities.insert(id, cap);
160        id
161    }
162
163    /// Remove a capability from the table
164    pub fn remove(&mut self, id: CapId) -> Option<Capability> {
165        let removed = self.capabilities.remove(&id);
166        if removed.is_some() {
167            let _ = get_capability_manager().revoke_capability(id);
168        }
169        removed
170    }
171
172    /// Get a reference to a capability (no permission check).
173    pub fn get(&self, id: CapId) -> Option<&Capability> {
174        self.capabilities.get(&id)
175    }
176
177    /// Revoke all capabilities in this table and clear it.
178    /// Does not allocate memory.
179    pub fn revoke_all(&mut self) {
180        let capabilities = self.take_all();
181        for capability in &capabilities {
182            release_capability(capability, None);
183        }
184    }
185
186    /// Removes and returns every capability in this table.
187    pub fn take_all(&mut self) -> Vec<Capability> {
188        core::mem::take(&mut self.capabilities)
189            .into_values()
190            .collect()
191    }
192
193    /// Check whether any capability of the given resource type has required permissions.
194    pub fn has_resource_type_with_permissions(
195        &self,
196        resource_type: ResourceType,
197        required: CapPermissions,
198    ) -> bool {
199        self.capabilities.values().any(|cap| {
200            cap.resource_type == resource_type
201                && (!required.read || cap.permissions.read)
202                && (!required.write || cap.permissions.write)
203                && (!required.execute || cap.permissions.execute)
204                && (!required.grant || cap.permissions.grant)
205                && (!required.revoke || cap.permissions.revoke)
206        })
207    }
208
209    /// Check whether a specific resource has required permissions.
210    pub fn has_resource_with_permissions(
211        &self,
212        resource_type: ResourceType,
213        resource: usize,
214        required: CapPermissions,
215    ) -> bool {
216        self.capabilities.values().any(|cap| {
217            cap.resource_type == resource_type
218                && cap.resource == resource
219                && (!required.read || cap.permissions.read)
220                && (!required.write || cap.permissions.write)
221                && (!required.execute || cap.permissions.execute)
222                && (!required.grant || cap.permissions.grant)
223                && (!required.revoke || cap.permissions.revoke)
224        })
225    }
226
227    /// Get a reference to a capability if it exists and has the required permissions
228    pub fn get_with_permissions(&self, id: CapId, required: CapPermissions) -> Option<&Capability> {
229        self.capabilities.get(&id).filter(|cap| {
230            // Check if the capability has all required permissions
231            (!required.read || cap.permissions.read)
232                && (!required.write || cap.permissions.write)
233                && (!required.execute || cap.permissions.execute)
234                && (!required.grant || cap.permissions.grant)
235                && (!required.revoke || cap.permissions.revoke)
236        })
237    }
238
239    /// Get a mutable reference to a capability if it exists and has the required permissions
240    pub fn get_mut_with_permissions(
241        &mut self,
242        id: CapId,
243        required: CapPermissions,
244    ) -> Option<&mut Capability> {
245        if let Some(cap) = self.capabilities.get_mut(&id) {
246            // Check if the capability has all required permissions
247            if (!required.read || cap.permissions.read)
248                && (!required.write || cap.permissions.write)
249                && (!required.execute || cap.permissions.execute)
250                && (!required.grant || cap.permissions.grant)
251                && (!required.revoke || cap.permissions.revoke)
252            {
253                Some(cap)
254            } else {
255                None
256            }
257        } else {
258            None
259        }
260    }
261
262    /// Duplicate a capability (grant permission required)
263    pub fn duplicate(&mut self, id: CapId) -> Option<Capability> {
264        if let Some(cap) = self.capabilities.get(&id) {
265            if cap.permissions.grant {
266                // Create a new capability with the same properties
267                Some(Capability {
268                    id: CapId::new(),
269                    resource_type: cap.resource_type,
270                    permissions: cap.permissions,
271                    resource: cap.resource,
272                })
273            } else {
274                None
275            }
276        } else {
277            None
278        }
279    }
280}
281
282/// Global capability manager
283pub struct CapabilityManager {
284    /// All capabilities in the system
285    all_capabilities: SpinLock<BTreeMap<CapId, Capability>>,
286    /// Per-resource refcounts for O(1) capability counting (no full-table scan).
287    ///
288    /// Each `register_capability` increments the counter; each `revoke_capability`
289    /// decrements it. `resource_capability_count` is O(1) : it reads the atomic
290    /// directly instead of scanning `all_capabilities`.  The lock is only held
291    /// for the brief insert/remove + atomic update, never for a full-map scan.
292    resource_refcounts: SpinLock<BTreeMap<ResourceKey, AtomicUsize>>,
293}
294
295impl CapabilityManager {
296    /// Create a new capability manager
297    pub fn new() -> Self {
298        CapabilityManager {
299            all_capabilities: SpinLock::new(BTreeMap::new()),
300            resource_refcounts: SpinLock::new(BTreeMap::new()),
301        }
302    }
303
304    /// Register a new resource and return a capability to access it
305    pub fn create_capability(
306        &self,
307        resource_type: ResourceType,
308        resource: usize,
309        permissions: CapPermissions,
310    ) -> Capability {
311        let cap = Capability {
312            id: CapId::new(),
313            resource_type,
314            permissions,
315            resource,
316        };
317
318        self.all_capabilities.lock().insert(cap.id, cap.clone());
319        self.increment_resource_refcount(resource_type, resource);
320        cap
321    }
322
323    /// Register an already-created capability in the global table.
324    pub fn register_capability(&self, cap: Capability) {
325        let key = ResourceKey(cap.resource_type, cap.resource);
326        self.all_capabilities.lock().insert(cap.id, cap);
327        self.increment_resource_refcount_key(&key);
328    }
329
330    /// Revoke a capability (removes it from the global table)
331    pub fn revoke_capability(&self, id: CapId) -> Option<Capability> {
332        let removed = {
333            let mut caps = self.all_capabilities.lock();
334            caps.remove(&id)
335        };
336        if let Some(ref cap) = removed {
337            let key = ResourceKey(cap.resource_type, cap.resource);
338            self.decrement_resource_refcount(&key);
339        }
340        removed
341    }
342
343    /// Count remaining capabilities that still reference the same resource.
344    /// O(1) lookup via per-resource refcount : no full-table scan.
345    pub fn resource_capability_count(&self, resource_type: ResourceType, resource: usize) -> usize {
346        let key = ResourceKey(resource_type, resource);
347        self.resource_refcounts
348            .lock()
349            .get(&key)
350            .map(|rc| rc.load(Ordering::Relaxed))
351            .unwrap_or(0)
352    }
353
354    // -- Internal refcount helpers --
355
356    fn increment_resource_refcount(&self, resource_type: ResourceType, resource: usize) {
357        let key = ResourceKey(resource_type, resource);
358        self.increment_resource_refcount_key(&key);
359    }
360
361    fn increment_resource_refcount_key(&self, key: &ResourceKey) {
362        let mut refcounts = self.resource_refcounts.lock();
363        let entry = refcounts.entry(*key).or_insert_with(|| AtomicUsize::new(0));
364        entry.fetch_add(1, Ordering::Relaxed);
365    }
366
367    fn decrement_resource_refcount(&self, key: &ResourceKey) {
368        let mut refcounts = self.resource_refcounts.lock();
369        // Check and remove under the same lock acquisition to avoid the TOCTOU
370        // window where another thread could increment the refcount between the
371        // `drop` and the second `lock().remove()`.
372        let should_remove = if let Some(rc) = refcounts.get(key) {
373            rc.fetch_sub(1, Ordering::Relaxed) == 1
374        } else {
375            false
376        };
377        if should_remove {
378            refcounts.remove(key);
379        }
380    }
381}
382
383use spin::Once;
384
385static CAPABILITY_MANAGER: Once<CapabilityManager> = Once::new();
386
387/// Get a reference to the global capability manager
388pub fn get_capability_manager() -> &'static CapabilityManager {
389    CAPABILITY_MANAGER.call_once(CapabilityManager::new)
390}
391
392/// Releases a capability and cleans up the underlying resource.
393pub fn release_capability(cap: &Capability, owner_task: Option<TaskId>) {
394    let _ = get_capability_manager().revoke_capability(cap.id);
395    let remaining_caps =
396        get_capability_manager().resource_capability_count(cap.resource_type, cap.resource);
397
398    let shared_resource = match cap.resource_type {
399        ResourceType::SharedRing => Some(MultiHandleResource::SharedRing(ipc::RingId::from_u64(
400            cap.resource as u64,
401        ))),
402        ResourceType::Semaphore => Some(MultiHandleResource::Semaphore(ipc::SemId::from_u64(
403            cap.resource as u64,
404        ))),
405        ResourceType::Channel => Some(MultiHandleResource::Channel(ipc::ChanId::from_u64(
406            cap.resource as u64,
407        ))),
408        ResourceType::IpcPort => Some(MultiHandleResource::IpcPort {
409            id: ipc::PortId::from_u64(cap.resource as u64),
410            owner: owner_task,
411        }),
412        _ => None,
413    };
414
415    if let Some(resource) = shared_resource {
416        if remaining_caps == 0 {
417            let _ = resource.destroy();
418        }
419        return;
420    }
421
422    match cap.resource_type {
423        ResourceType::File => {
424            if let Ok(fd) = u32::try_from(cap.resource) {
425                let _ = vfs::close(fd);
426            }
427        }
428        ResourceType::MemoryRegion => {
429            let _ =
430                crate::memory::memory_region_registry().release_handle(cap.resource as u64, cap.id);
431        }
432        ResourceType::IpcTransport => {
433            if remaining_caps == 0 {
434                let tid =
435                    crate::ipc::transport::TransportId::from_u64(cap.resource as u64);
436                let _ = crate::syscall::transport::TRANSPORT_MANAGER.close(tid);
437            }
438        }
439        _ => {}
440    }
441}