sandbox_rs/isolation/
namespace.rs1use crate::errors::{Result, SandboxError};
4use nix::sched::CloneFlags;
5use nix::unistd::Pid;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum NamespaceType {
10 Pid,
12 Ipc,
14 Net,
16 Mount,
18 Uts,
20 User,
22}
23
24#[derive(Debug, Clone, PartialEq)]
26pub struct NamespaceConfig {
27 pub pid: bool,
29 pub ipc: bool,
31 pub net: bool,
33 pub mount: bool,
35 pub uts: bool,
37 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 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 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 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 pub fn all_enabled(&self) -> bool {
107 self.pid && self.ipc && self.net && self.mount && self.uts && self.user
108 }
109
110 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#[derive(Debug, Clone)]
123pub struct NamespaceInfo {
124 pub ns_type: NamespaceType,
125 pub inode: u64,
126}
127
128pub fn get_namespace_inode(ns_type: &str) -> Result<u64> {
130 get_namespace_inode_for_pid(ns_type, None)
131}
132
133pub 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 #[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
162pub 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); let config = NamespaceConfig::all();
205 assert_eq!(config.enabled_count(), 6);
206
207 let config = NamespaceConfig::minimal();
208 assert_eq!(config.enabled_count(), 4); }
210
211 #[test]
212 fn test_clone_flags_conversion() {
213 let config = NamespaceConfig::default();
214 let flags = config.to_clone_flags();
215
216 assert!(!flags.is_empty());
218
219 assert!(flags.contains(CloneFlags::CLONE_NEWPID));
221 }
222
223 #[test]
224 fn test_get_namespace_inode() {
225 let result = get_namespace_inode("pid");
227 match result {
228 Ok(inode) => {
229 assert!(inode > 0);
230 }
231 Err(e) => {
232 eprintln!("Warning: namespace inode check failed: {}", e);
234 }
235 }
236 }
237
238 #[test]
239 fn test_shares_namespace_with_self() {
240 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 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 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}