sandbox_namespace/
config.rs1use nix::sched::CloneFlags;
4use nix::unistd::Pid;
5use sandbox_core::{Result, SandboxError};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum NamespaceType {
10 Pid,
11 Ipc,
12 Net,
13 Mount,
14 Uts,
15 User,
16}
17
18#[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 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 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 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 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 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 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#[derive(Debug, Clone)]
132pub struct NamespaceInfo {
133 pub ns_type: NamespaceType,
134 pub inode: u64,
135}
136
137pub fn get_namespace_inode(ns_type: &str) -> Result<u64> {
139 get_namespace_inode_for_pid(ns_type, None)
140}
141
142pub 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
171pub 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}