sandbox_rs/isolation/
namespace.rs

1//! Namespace management for sandbox isolation
2
3use crate::errors::{Result, SandboxError};
4use nix::sched::CloneFlags;
5use nix::unistd::Pid;
6
7/// Namespace types that can be isolated
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum NamespaceType {
10    /// PID namespace - isolate process IDs
11    Pid,
12    /// IPC namespace - isolate System V IPC
13    Ipc,
14    /// Network namespace - isolate network
15    Net,
16    /// Mount namespace - isolate mounts
17    Mount,
18    /// UTS namespace - isolate hostname
19    Uts,
20    /// User namespace - isolate UIDs/GIDs
21    User,
22}
23
24/// Configuration for namespace isolation
25#[derive(Debug, Clone, PartialEq)]
26pub struct NamespaceConfig {
27    /// PID namespace enabled
28    pub pid: bool,
29    /// IPC namespace enabled
30    pub ipc: bool,
31    /// Network namespace enabled
32    pub net: bool,
33    /// Mount namespace enabled
34    pub mount: bool,
35    /// UTS namespace enabled
36    pub uts: bool,
37    /// User namespace enabled
38    pub user: bool,
39}
40
41impl Default for NamespaceConfig {
42    fn default() -> Self {
43        Self {
44            pid: true,
45            ipc: true,
46            net: true,
47            mount: true,
48            uts: true,
49            user: false,
50        }
51    }
52}
53
54impl NamespaceConfig {
55    /// Create a new configuration with all namespaces enabled
56    pub fn all() -> Self {
57        Self {
58            pid: true,
59            ipc: true,
60            net: true,
61            mount: true,
62            uts: true,
63            user: true,
64        }
65    }
66
67    /// Create a minimal configuration
68    pub fn minimal() -> Self {
69        Self {
70            pid: true,
71            ipc: true,
72            net: true,
73            mount: true,
74            uts: false,
75            user: false,
76        }
77    }
78
79    /// Convert to clone flags
80    pub fn to_clone_flags(&self) -> CloneFlags {
81        let mut flags = CloneFlags::empty();
82
83        if self.pid {
84            flags |= CloneFlags::CLONE_NEWPID;
85        }
86        if self.ipc {
87            flags |= CloneFlags::CLONE_NEWIPC;
88        }
89        if self.net {
90            flags |= CloneFlags::CLONE_NEWNET;
91        }
92        if self.mount {
93            flags |= CloneFlags::CLONE_NEWNS;
94        }
95        if self.uts {
96            flags |= CloneFlags::CLONE_NEWUTS;
97        }
98        if self.user {
99            flags |= CloneFlags::CLONE_NEWUSER;
100        }
101
102        flags
103    }
104
105    /// Check if all namespaces are enabled
106    pub fn all_enabled(&self) -> bool {
107        self.pid && self.ipc && self.net && self.mount && self.uts && self.user
108    }
109
110    /// Count enabled namespaces
111    pub fn enabled_count(&self) -> usize {
112        [
113            self.pid, self.ipc, self.net, self.mount, self.uts, self.user,
114        ]
115        .iter()
116        .filter(|&&x| x)
117        .count()
118    }
119}
120
121/// Information about a namespace
122#[derive(Debug, Clone)]
123pub struct NamespaceInfo {
124    pub ns_type: NamespaceType,
125    pub inode: u64,
126}
127
128/// Get namespace inode (for identification)
129pub fn get_namespace_inode(ns_type: &str) -> Result<u64> {
130    get_namespace_inode_for_pid(ns_type, None)
131}
132
133/// Get namespace inode for a specific process
134pub fn get_namespace_inode_for_pid(ns_type: &str, pid: Option<Pid>) -> Result<u64> {
135    let pid_str = match pid {
136        Some(p) => p.as_raw().to_string(),
137        None => "self".to_string(),
138    };
139    let path = format!("/proc/{}/ns/{}", pid_str, ns_type);
140    let stat = std::fs::metadata(&path).map_err(|e| {
141        SandboxError::Namespace(format!(
142            "Failed to get namespace info for pid={} ns={}: {}",
143            pid_str, ns_type, e
144        ))
145    })?;
146
147    // Get inode number
148    #[cfg(unix)]
149    {
150        use std::os::unix::fs::MetadataExt;
151        Ok(stat.ino())
152    }
153
154    #[cfg(not(unix))]
155    {
156        Err(SandboxError::Namespace(
157            "Namespace info not available on this platform".to_string(),
158        ))
159    }
160}
161
162/// Check if two processes share a namespace
163pub fn shares_namespace(ns_type: &str, pid1: Option<Pid>, pid2: Option<Pid>) -> Result<bool> {
164    let inode1 = get_namespace_inode_for_pid(ns_type, pid1)?;
165    let inode2 = get_namespace_inode_for_pid(ns_type, pid2)?;
166
167    Ok(inode1 == inode2)
168}
169
170#[cfg(test)]
171mod tests {
172    use super::*;
173
174    #[test]
175    fn test_namespace_config_default() {
176        let config = NamespaceConfig::default();
177        assert!(config.pid);
178        assert!(config.ipc);
179        assert!(config.net);
180        assert!(config.mount);
181        assert!(config.uts);
182        assert!(!config.user);
183    }
184
185    #[test]
186    fn test_namespace_config_all() {
187        let config = NamespaceConfig::all();
188        assert!(config.all_enabled());
189    }
190
191    #[test]
192    fn test_namespace_config_minimal() {
193        let config = NamespaceConfig::minimal();
194        assert!(config.pid);
195        assert!(!config.uts);
196        assert!(!config.user);
197    }
198
199    #[test]
200    fn test_enabled_count() {
201        let config = NamespaceConfig::default();
202        assert_eq!(config.enabled_count(), 5); // pid, ipc, net, mount, uts
203
204        let config = NamespaceConfig::all();
205        assert_eq!(config.enabled_count(), 6);
206
207        let config = NamespaceConfig::minimal();
208        assert_eq!(config.enabled_count(), 4); // pid, ipc, net, mount
209    }
210
211    #[test]
212    fn test_clone_flags_conversion() {
213        let config = NamespaceConfig::default();
214        let flags = config.to_clone_flags();
215
216        // Should not be empty
217        assert!(!flags.is_empty());
218
219        // Should have NEWPID, NEWIPC, NEWNET, NEWNS
220        assert!(flags.contains(CloneFlags::CLONE_NEWPID));
221    }
222
223    #[test]
224    fn test_get_namespace_inode() {
225        // Should be able to get inode for current process
226        let result = get_namespace_inode("pid");
227        match result {
228            Ok(inode) => {
229                assert!(inode > 0);
230            }
231            Err(e) => {
232                // May fail if /proc is not available in test environment
233                eprintln!("Warning: namespace inode check failed: {}", e);
234            }
235        }
236    }
237
238    #[test]
239    fn test_shares_namespace_with_self() {
240        // Current process should share namespace with itself
241        let result = shares_namespace("pid", None, None);
242        match result {
243            Ok(shares) => assert!(shares, "Process should share namespace with itself"),
244            Err(e) => eprintln!("Warning: namespace sharing check failed: {}", e),
245        }
246    }
247
248    #[test]
249    fn test_namespace_inode_for_self() {
250        // Should be able to get inode for current process
251        let result = get_namespace_inode_for_pid("pid", None);
252        match result {
253            Ok(inode) => {
254                assert!(inode > 0, "Namespace inode should be positive");
255            }
256            Err(e) => {
257                eprintln!("Warning: namespace inode check failed: {}", e);
258            }
259        }
260    }
261
262    #[test]
263    fn test_namespace_inode_consistency() {
264        // Getting inode twice should return same value
265        let inode1 = get_namespace_inode("pid");
266        let inode2 = get_namespace_inode("pid");
267
268        match (inode1, inode2) {
269            (Ok(i1), Ok(i2)) => {
270                assert_eq!(i1, i2, "Namespace inode should be consistent");
271            }
272            _ => {
273                eprintln!("Warning: namespace inode check failed");
274            }
275        }
276    }
277
278    #[test]
279    fn test_namespace_type_equality() {
280        assert_eq!(NamespaceType::Pid, NamespaceType::Pid);
281        assert_ne!(NamespaceType::Pid, NamespaceType::Net);
282    }
283}