sandbox_core/
capabilities.rs1use std::path::Path;
7
8#[derive(Debug, Clone)]
10pub struct SystemCapabilities {
11 pub has_root: bool,
13 pub has_user_namespaces: bool,
15 pub has_seccomp: bool,
17 pub has_landlock: bool,
19 pub has_cgroup_v2: bool,
21 pub has_cgroup_delegation: bool,
23}
24
25impl SystemCapabilities {
26 pub fn detect() -> Self {
28 Self {
29 has_root: detect_root(),
30 has_user_namespaces: detect_user_namespaces(),
31 has_seccomp: detect_seccomp(),
32 has_landlock: detect_landlock(),
33 has_cgroup_v2: detect_cgroup_v2(),
34 has_cgroup_delegation: detect_cgroup_delegation(),
35 }
36 }
37
38 pub fn can_sandbox_unprivileged(&self) -> bool {
40 self.has_seccomp
43 }
44
45 pub fn can_sandbox_privileged(&self) -> bool {
47 self.has_root && self.has_cgroup_v2
48 }
49
50 pub fn summary(&self) -> String {
52 let mut lines = Vec::new();
53 let check = |available: bool| if available { "[ok]" } else { "[--]" };
54
55 lines.push(format!("{} Root privileges", check(self.has_root)));
56 lines.push(format!(
57 "{} User namespaces",
58 check(self.has_user_namespaces)
59 ));
60 lines.push(format!("{} Seccomp BPF", check(self.has_seccomp)));
61 lines.push(format!("{} Landlock LSM", check(self.has_landlock)));
62 lines.push(format!("{} Cgroup v2", check(self.has_cgroup_v2)));
63 lines.push(format!(
64 "{} Cgroup delegation",
65 check(self.has_cgroup_delegation)
66 ));
67
68 lines.join("\n")
69 }
70}
71
72fn detect_root() -> bool {
73 unsafe { libc::geteuid() == 0 }
74}
75
76fn detect_user_namespaces() -> bool {
77 if let Ok(content) = std::fs::read_to_string("/proc/sys/kernel/unprivileged_userns_clone")
79 && content.trim() == "0"
80 {
81 return false;
82 }
83
84 if let Ok(content) = std::fs::read_to_string("/proc/sys/user/max_user_namespaces")
86 && let Ok(max) = content.trim().parse::<u64>()
87 {
88 return max > 0;
89 }
90
91 true
93}
94
95fn detect_seccomp() -> bool {
96 let ret = unsafe { libc::prctl(libc::PR_GET_SECCOMP, 0, 0, 0, 0) };
98 ret >= 0
101}
102
103fn detect_landlock() -> bool {
104 let ret = unsafe {
108 libc::syscall(
109 libc::SYS_landlock_create_ruleset,
110 std::ptr::null::<libc::c_void>(),
111 0usize,
112 1u32, )
114 };
115
116 if ret >= 0 {
117 return true;
119 }
120
121 let errno = std::io::Error::last_os_error().raw_os_error().unwrap_or(0);
123 errno != libc::ENOSYS
124}
125
126fn detect_cgroup_v2() -> bool {
127 Path::new("/sys/fs/cgroup/cgroup.controllers").exists()
128}
129
130fn detect_cgroup_delegation() -> bool {
131 let uid = unsafe { libc::geteuid() };
133 if uid == 0 {
134 return true; }
136
137 let user_slice = format!("/sys/fs/cgroup/user.slice/user-{}.slice", uid);
138 let path = Path::new(&user_slice);
139
140 if !path.exists() {
141 return false;
142 }
143
144 let test_path = path.join("sandbox-test-probe");
146 match std::fs::create_dir(&test_path) {
147 Ok(()) => {
148 let _ = std::fs::remove_dir(&test_path);
149 true
150 }
151 Err(_) => false,
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158
159 #[test]
160 fn detect_returns_valid_capabilities() {
161 let caps = SystemCapabilities::detect();
162 let _ = caps.has_root;
164 let _ = caps.has_seccomp;
165 let _ = caps.has_user_namespaces;
166 let _ = caps.has_landlock;
167 let _ = caps.has_cgroup_v2;
168 let _ = caps.has_cgroup_delegation;
169 }
170
171 #[test]
172 fn summary_produces_output() {
173 let caps = SystemCapabilities::detect();
174 let summary = caps.summary();
175 assert!(!summary.is_empty());
176 assert!(summary.contains("Root privileges"));
177 assert!(summary.contains("Seccomp BPF"));
178 }
179
180 #[test]
181 fn seccomp_detection_works() {
182 let has = detect_seccomp();
184 let _ = has;
186 }
187
188 #[test]
189 fn root_detection_matches_euid() {
190 let detected = detect_root();
191 let actual = unsafe { libc::geteuid() == 0 };
192 assert_eq!(detected, actual);
193 }
194}