Skip to main content

sandbox_namespace/
config.rs

1//! Namespace management for sandbox isolation
2
3use nix::sched::CloneFlags;
4use nix::unistd::Pid;
5use sandbox_core::{Result, SandboxError};
6
7/// Namespace types that can be isolated
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum NamespaceType {
10    Pid,
11    Ipc,
12    Net,
13    Mount,
14    Uts,
15    User,
16}
17
18/// Configuration for namespace isolation
19#[derive(Debug, Clone, PartialEq)]
20pub struct NamespaceConfig {
21    pub pid: bool,
22    pub ipc: bool,
23    pub net: bool,
24    pub mount: bool,
25    pub uts: bool,
26    pub user: bool,
27}
28
29impl Default for NamespaceConfig {
30    fn default() -> Self {
31        // CHANGED: user namespace now enabled by default for unprivileged operation
32        Self {
33            pid: true,
34            ipc: true,
35            net: true,
36            mount: true,
37            uts: true,
38            user: true,
39        }
40    }
41}
42
43impl NamespaceConfig {
44    /// All namespaces enabled
45    pub fn all() -> Self {
46        Self {
47            pid: true,
48            ipc: true,
49            net: true,
50            mount: true,
51            uts: true,
52            user: true,
53        }
54    }
55
56    /// Minimal configuration (PID, IPC, NET, MOUNT)
57    pub fn minimal() -> Self {
58        Self {
59            pid: true,
60            ipc: true,
61            net: true,
62            mount: true,
63            uts: false,
64            user: false,
65        }
66    }
67
68    /// Unprivileged mode: user namespace enabled to allow other namespaces without root
69    pub fn unprivileged() -> Self {
70        Self {
71            pid: true,
72            ipc: true,
73            net: true,
74            mount: true,
75            uts: true,
76            user: true,
77        }
78    }
79
80    /// Privileged mode: no user namespace needed (running as root)
81    pub fn privileged() -> Self {
82        Self {
83            pid: true,
84            ipc: true,
85            net: true,
86            mount: true,
87            uts: true,
88            user: false,
89        }
90    }
91
92    /// Convert to clone flags
93    pub fn to_clone_flags(&self) -> CloneFlags {
94        let mut flags = CloneFlags::empty();
95        if self.pid {
96            flags |= CloneFlags::CLONE_NEWPID;
97        }
98        if self.ipc {
99            flags |= CloneFlags::CLONE_NEWIPC;
100        }
101        if self.net {
102            flags |= CloneFlags::CLONE_NEWNET;
103        }
104        if self.mount {
105            flags |= CloneFlags::CLONE_NEWNS;
106        }
107        if self.uts {
108            flags |= CloneFlags::CLONE_NEWUTS;
109        }
110        if self.user {
111            flags |= CloneFlags::CLONE_NEWUSER;
112        }
113        flags
114    }
115
116    pub fn all_enabled(&self) -> bool {
117        self.pid && self.ipc && self.net && self.mount && self.uts && self.user
118    }
119
120    pub fn enabled_count(&self) -> usize {
121        [
122            self.pid, self.ipc, self.net, self.mount, self.uts, self.user,
123        ]
124        .iter()
125        .filter(|&&x| x)
126        .count()
127    }
128}
129
130/// Information about a namespace
131#[derive(Debug, Clone)]
132pub struct NamespaceInfo {
133    pub ns_type: NamespaceType,
134    pub inode: u64,
135}
136
137/// Get namespace inode (for identification)
138pub fn get_namespace_inode(ns_type: &str) -> Result<u64> {
139    get_namespace_inode_for_pid(ns_type, None)
140}
141
142/// Get namespace inode for a specific process
143pub fn get_namespace_inode_for_pid(ns_type: &str, pid: Option<Pid>) -> Result<u64> {
144    let pid_str = match pid {
145        Some(p) => p.as_raw().to_string(),
146        None => "self".to_string(),
147    };
148    let path = format!("/proc/{}/ns/{}", pid_str, ns_type);
149    let stat = std::fs::metadata(&path).map_err(|e| {
150        SandboxError::Namespace(format!(
151            "Failed to get namespace info for pid={} ns={}: {}",
152            pid_str, ns_type, e
153        ))
154    })?;
155
156    #[cfg(unix)]
157    {
158        use std::os::unix::fs::MetadataExt;
159        Ok(stat.ino())
160    }
161
162    #[cfg(not(unix))]
163    {
164        let _ = stat;
165        Err(SandboxError::Namespace(
166            "Namespace info not available on this platform".to_string(),
167        ))
168    }
169}
170
171/// Check if two processes share a namespace
172pub fn shares_namespace(ns_type: &str, pid1: Option<Pid>, pid2: Option<Pid>) -> Result<bool> {
173    let inode1 = get_namespace_inode_for_pid(ns_type, pid1)?;
174    let inode2 = get_namespace_inode_for_pid(ns_type, pid2)?;
175    Ok(inode1 == inode2)
176}
177
178#[cfg(test)]
179mod tests {
180    use super::*;
181
182    #[test]
183    fn test_namespace_config_default_includes_user() {
184        let config = NamespaceConfig::default();
185        assert!(config.user, "Default should now include user namespace");
186        assert!(config.pid);
187        assert!(config.ipc);
188        assert!(config.net);
189        assert!(config.mount);
190        assert!(config.uts);
191    }
192
193    #[test]
194    fn test_namespace_config_all() {
195        let config = NamespaceConfig::all();
196        assert!(config.all_enabled());
197    }
198
199    #[test]
200    fn test_namespace_config_minimal() {
201        let config = NamespaceConfig::minimal();
202        assert!(config.pid);
203        assert!(!config.uts);
204        assert!(!config.user);
205    }
206
207    #[test]
208    fn test_unprivileged_enables_user_ns() {
209        let config = NamespaceConfig::unprivileged();
210        assert!(config.user);
211        assert_eq!(config.enabled_count(), 6);
212    }
213
214    #[test]
215    fn test_privileged_disables_user_ns() {
216        let config = NamespaceConfig::privileged();
217        assert!(!config.user);
218        assert_eq!(config.enabled_count(), 5);
219    }
220
221    #[test]
222    fn test_enabled_count() {
223        assert_eq!(NamespaceConfig::default().enabled_count(), 6);
224        assert_eq!(NamespaceConfig::all().enabled_count(), 6);
225        assert_eq!(NamespaceConfig::minimal().enabled_count(), 4);
226    }
227
228    #[test]
229    fn test_clone_flags_conversion() {
230        let config = NamespaceConfig::default();
231        let flags = config.to_clone_flags();
232        assert!(!flags.is_empty());
233        assert!(flags.contains(CloneFlags::CLONE_NEWPID));
234        assert!(flags.contains(CloneFlags::CLONE_NEWUSER));
235    }
236}