nucleus/security/
capabilities.rs1use crate::error::{NucleusError, Result};
2use caps::{CapSet, Capability, CapsHashSet};
3use tracing::{debug, info};
4
5pub 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 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 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 for cap in caps::all() {
55 if let Err(e) = caps::drop(None, CapSet::Bounding, cap) {
56 debug!(
58 "Failed to drop bounding cap {:?}: {} (may not be present)",
59 cap, e
60 );
61 }
62 }
63
64 let bounding = caps::read(None, CapSet::Bounding).map_err(|e| {
66 NucleusError::CapabilityError(format!("Failed to read bounding set after drop: {}", e))
67 })?;
68 if !bounding.is_empty() {
69 let leaked: Vec<String> = bounding.iter().map(|c| format!("{:?}", c)).collect();
70 return Err(NucleusError::CapabilityError(format!(
71 "Bounding set still contains capabilities after drop_all: [{}]",
72 leaked.join(", ")
73 )));
74 }
75
76 self.dropped = true;
77 info!("Successfully dropped all capabilities (including bounding set)");
78
79 Ok(())
80 }
81
82 pub fn drop_except(&mut self, keep: &[Capability]) -> Result<()> {
87 if self.dropped {
88 debug!("Capabilities already dropped, skipping");
89 return Ok(());
90 }
91
92 info!("Dropping capabilities except: {:?}", keep);
93
94 let all_caps = caps::all();
96
97 for cap in all_caps {
99 if !keep.contains(&cap) {
100 caps::drop(None, CapSet::Permitted, cap).map_err(|e| {
101 NucleusError::CapabilityError(format!("Failed to drop {cap:?}: {e}"))
102 })?;
103
104 caps::drop(None, CapSet::Effective, cap).map_err(|e| {
105 NucleusError::CapabilityError(format!("Failed to drop {cap:?}: {e}"))
106 })?;
107
108 caps::drop(None, CapSet::Inheritable, cap).map_err(|e| {
109 NucleusError::CapabilityError(format!("Failed to drop {cap:?}: {e}"))
110 })?;
111
112 if let Err(e) = caps::drop(None, CapSet::Bounding, cap) {
113 debug!(
114 "Failed to drop bounding cap {:?}: {} (may not be present)",
115 cap, e
116 );
117 }
118 }
119 }
120
121 caps::clear(None, CapSet::Ambient).map_err(|e| {
123 NucleusError::CapabilityError(format!("Failed to clear ambient caps: {}", e))
124 })?;
125
126 self.dropped = true;
127 info!("Successfully dropped capabilities");
128
129 Ok(())
130 }
131
132 pub fn apply_sets(&mut self, sets: &CapabilitySets) -> Result<()> {
137 if self.dropped {
138 debug!("Capabilities already dropped, skipping");
139 return Ok(());
140 }
141
142 info!("Applying explicit capability sets");
143
144 for cap in caps::all() {
145 if !sets.bounding.contains(&cap) {
146 if let Err(e) = caps::drop(None, CapSet::Bounding, cap) {
147 debug!(
148 "Failed to drop bounding cap {:?}: {} (may not be present)",
149 cap, e
150 );
151 }
152 }
153 }
154
155 caps::set(None, CapSet::Permitted, &to_caps_hash_set(&sets.permitted)).map_err(|e| {
158 NucleusError::CapabilityError(format!("Failed to set permitted caps: {}", e))
159 })?;
160 caps::set(None, CapSet::Effective, &to_caps_hash_set(&sets.effective)).map_err(|e| {
161 NucleusError::CapabilityError(format!("Failed to set effective caps: {}", e))
162 })?;
163 caps::set(
164 None,
165 CapSet::Inheritable,
166 &to_caps_hash_set(&sets.inheritable),
167 )
168 .map_err(|e| {
169 NucleusError::CapabilityError(format!("Failed to set inheritable caps: {}", e))
170 })?;
171 caps::set(None, CapSet::Ambient, &to_caps_hash_set(&sets.ambient)).map_err(|e| {
172 NucleusError::CapabilityError(format!("Failed to set ambient caps: {}", e))
173 })?;
174
175 self.dropped = true;
176 info!("Successfully applied capability sets");
177 Ok(())
178 }
179
180 pub fn is_dropped(&self) -> bool {
182 self.dropped
183 }
184
185 pub fn verify_no_namespace_caps(production: bool) -> Result<()> {
192 use caps::Capability;
193 let ns_caps = [
194 Capability::CAP_SYS_ADMIN,
195 Capability::CAP_NET_ADMIN,
196 Capability::CAP_SYS_PTRACE,
197 ];
198 let effective = caps::read(None, CapSet::Effective).map_err(|e| {
199 NucleusError::CapabilityError(format!("Failed to read effective caps: {}", e))
200 })?;
201 let mut leaked = Vec::new();
202 for cap in &ns_caps {
203 if effective.contains(cap) {
204 leaked.push(format!("{:?}", cap));
205 }
206 }
207 if !leaked.is_empty() {
208 let msg = format!(
209 "SEC-CLONE3: namespace-creating capabilities still present after drop: [{}]. \
210 clone3 syscall is allowed without argument filtering — these caps \
211 must be absent to prevent namespace escape.",
212 leaked.join(", ")
213 );
214 if production {
215 return Err(NucleusError::CapabilityError(msg));
216 }
217 tracing::warn!("{}", msg);
218 }
219 Ok(())
220 }
221}
222
223impl Default for CapabilityManager {
224 fn default() -> Self {
225 Self::new()
226 }
227}
228
229fn to_caps_hash_set(caps_list: &[Capability]) -> CapsHashSet {
230 caps_list.iter().copied().collect()
231}
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236
237 #[test]
238 fn test_capability_manager_initial_state() {
239 let mgr = CapabilityManager::new();
240 assert!(!mgr.is_dropped());
241 }
242
243 #[test]
244 fn test_drop_idempotent() {
245 let mut mgr = CapabilityManager::new();
246 match mgr.drop_all() {
249 Ok(()) => {
250 assert!(mgr.is_dropped());
251 let result = mgr.drop_all();
253 assert!(result.is_ok());
254 assert!(mgr.is_dropped());
255 }
256 Err(_) => {
257 }
260 }
261 }
262}