Skip to main content

nucleus/security/
capabilities.rs

1use crate::error::{NucleusError, Result};
2use caps::{CapSet, Capability, CapsHashSet};
3use tracing::{debug, info};
4
5/// Security context that tracks capability state
6pub struct CapabilityManager {
7    dropped: bool,
8}
9
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct CapabilitySets {
12    pub bounding: Vec<Capability>,
13    pub permitted: Vec<Capability>,
14    pub effective: Vec<Capability>,
15    pub inheritable: Vec<Capability>,
16    pub ambient: Vec<Capability>,
17}
18
19impl CapabilityManager {
20    pub fn new() -> Self {
21        Self { dropped: false }
22    }
23
24    /// Drop all capabilities
25    ///
26    /// This implements the transition: Privileged -> CapabilitiesDropped
27    /// in the security state machine (Nucleus_Security_SecurityEnforcement.tla)
28    pub fn drop_all(&mut self) -> Result<()> {
29        if self.dropped {
30            debug!("Capabilities already dropped, skipping");
31            return Ok(());
32        }
33
34        info!("Dropping all capabilities");
35
36        // Clear all capability sets
37        caps::clear(None, CapSet::Permitted).map_err(|e| {
38            NucleusError::CapabilityError(format!("Failed to clear permitted caps: {}", e))
39        })?;
40
41        caps::clear(None, CapSet::Effective).map_err(|e| {
42            NucleusError::CapabilityError(format!("Failed to clear effective caps: {}", e))
43        })?;
44
45        caps::clear(None, CapSet::Inheritable).map_err(|e| {
46            NucleusError::CapabilityError(format!("Failed to clear inheritable caps: {}", e))
47        })?;
48
49        caps::clear(None, CapSet::Ambient).map_err(|e| {
50            NucleusError::CapabilityError(format!("Failed to clear ambient caps: {}", e))
51        })?;
52
53        // Clear bounding set: prevents regaining capabilities through exec of setuid binaries
54        for cap in caps::all() {
55            if let Err(e) = caps::drop(None, CapSet::Bounding, cap) {
56                // Some capabilities may not be in the bounding set; log and continue
57                debug!(
58                    "Failed to drop bounding cap {:?}: {} (may not be present)",
59                    cap, e
60                );
61            }
62        }
63
64        self.dropped = true;
65        info!("Successfully dropped all capabilities (including bounding set)");
66
67        Ok(())
68    }
69
70    /// Drop all capabilities except the specified ones
71    ///
72    /// For most use cases, we drop ALL capabilities. This method is provided
73    /// for special cases where specific capabilities are needed.
74    pub fn drop_except(&mut self, keep: &[Capability]) -> Result<()> {
75        if self.dropped {
76            debug!("Capabilities already dropped, skipping");
77            return Ok(());
78        }
79
80        info!("Dropping capabilities except: {:?}", keep);
81
82        // Get all capabilities
83        let all_caps = caps::all();
84
85        // Drop each capability that's not in the keep list
86        for cap in all_caps {
87            if !keep.contains(&cap) {
88                caps::drop(None, CapSet::Permitted, cap).map_err(|e| {
89                    NucleusError::CapabilityError(format!("Failed to drop {cap:?}: {e}"))
90                })?;
91
92                caps::drop(None, CapSet::Effective, cap).map_err(|e| {
93                    NucleusError::CapabilityError(format!("Failed to drop {cap:?}: {e}"))
94                })?;
95
96                caps::drop(None, CapSet::Inheritable, cap).map_err(|e| {
97                    NucleusError::CapabilityError(format!("Failed to drop {cap:?}: {e}"))
98                })?;
99
100                if let Err(e) = caps::drop(None, CapSet::Bounding, cap) {
101                    debug!(
102                        "Failed to drop bounding cap {:?}: {} (may not be present)",
103                        cap, e
104                    );
105                }
106            }
107        }
108
109        // Always clear ambient capabilities
110        caps::clear(None, CapSet::Ambient).map_err(|e| {
111            NucleusError::CapabilityError(format!("Failed to clear ambient caps: {}", e))
112        })?;
113
114        self.dropped = true;
115        info!("Successfully dropped capabilities");
116
117        Ok(())
118    }
119
120    /// Apply explicit capability sets.
121    ///
122    /// Bounding is handled as a drop-only upper bound; the remaining sets are
123    /// set exactly to the provided values.
124    pub fn apply_sets(&mut self, sets: &CapabilitySets) -> Result<()> {
125        if self.dropped {
126            debug!("Capabilities already dropped, skipping");
127            return Ok(());
128        }
129
130        info!("Applying explicit capability sets");
131
132        for cap in caps::all() {
133            if !sets.bounding.contains(&cap) {
134                if let Err(e) = caps::drop(None, CapSet::Bounding, cap) {
135                    debug!(
136                        "Failed to drop bounding cap {:?}: {} (may not be present)",
137                        cap, e
138                    );
139                }
140            }
141        }
142
143        caps::set(None, CapSet::Permitted, &to_caps_hash_set(&sets.permitted)).map_err(|e| {
144            NucleusError::CapabilityError(format!("Failed to set permitted caps: {}", e))
145        })?;
146        caps::set(
147            None,
148            CapSet::Inheritable,
149            &to_caps_hash_set(&sets.inheritable),
150        )
151        .map_err(|e| {
152            NucleusError::CapabilityError(format!("Failed to set inheritable caps: {}", e))
153        })?;
154        caps::set(None, CapSet::Ambient, &to_caps_hash_set(&sets.ambient)).map_err(|e| {
155            NucleusError::CapabilityError(format!("Failed to set ambient caps: {}", e))
156        })?;
157        caps::set(None, CapSet::Effective, &to_caps_hash_set(&sets.effective)).map_err(|e| {
158            NucleusError::CapabilityError(format!("Failed to set effective caps: {}", e))
159        })?;
160
161        self.dropped = true;
162        info!("Successfully applied capability sets");
163        Ok(())
164    }
165
166    /// Check if capabilities have been dropped
167    pub fn is_dropped(&self) -> bool {
168        self.dropped
169    }
170}
171
172impl Default for CapabilityManager {
173    fn default() -> Self {
174        Self::new()
175    }
176}
177
178fn to_caps_hash_set(caps_list: &[Capability]) -> CapsHashSet {
179    caps_list.iter().copied().collect()
180}
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185
186    #[test]
187    fn test_capability_manager_initial_state() {
188        let mgr = CapabilityManager::new();
189        assert!(!mgr.is_dropped());
190    }
191
192    #[test]
193    fn test_drop_idempotent() {
194        let mut mgr = CapabilityManager::new();
195        // First drop should succeed
196        let _ = mgr.drop_all();
197        assert!(mgr.is_dropped());
198
199        // Second drop should also succeed (idempotent)
200        let result = mgr.drop_all();
201        assert!(result.is_ok());
202        assert!(mgr.is_dropped());
203    }
204}