1use std::sync::OnceLock;
32
33use rustix::system::uname;
34use thiserror::Error;
35
36use crate::landlock;
37use crate::seccomp;
38
39#[derive(Debug, Clone)]
41pub struct SystemInfo {
42 pub kernel_version: (u32, u32, u32),
43 pub landlock_abi: u32,
44 pub user_ns_enabled: bool,
45 pub seccomp_enabled: bool,
46}
47
48#[derive(Debug, Clone, Error)]
50pub enum CheckError {
51 #[error("kernel version {}.{}.{} is too old, need at least {}.{}.{}", .found.0, .found.1, .found.2, .required.0, .required.1, .required.2)]
52 KernelTooOld { required: (u32, u32, u32), found: (u32, u32, u32) },
53
54 #[error("landlock is not available")]
55 LandlockNotAvailable,
56
57 #[error("user namespaces are disabled")]
58 UserNamespacesDisabled,
59
60 #[error("seccomp is not available")]
61 SeccompNotAvailable,
62
63 #[error("failed to read kernel version")]
64 KernelVersionReadFailed,
65}
66
67const MIN_KERNEL_VERSION: (u32, u32, u32) = (5, 13, 0);
69
70static SYSTEM_INFO: OnceLock<Result<SystemInfo, CheckError>> = OnceLock::new();
71
72pub fn check() -> Result<&'static SystemInfo, &'static CheckError> {
77 SYSTEM_INFO.get_or_init(check_impl).as_ref()
78}
79
80fn check_impl() -> Result<SystemInfo, CheckError> {
81 let kernel_version = get_kernel_version()?;
82 if kernel_version < MIN_KERNEL_VERSION {
83 return Err(CheckError::KernelTooOld {
84 required: MIN_KERNEL_VERSION,
85 found: kernel_version,
86 });
87 }
88
89 let landlock_abi = landlock::landlock_abi_version().unwrap_or(0);
90 if landlock_abi == 0 {
91 return Err(CheckError::LandlockNotAvailable);
92 }
93
94 let user_ns_enabled = check_user_namespaces();
95 if !user_ns_enabled {
96 return Err(CheckError::UserNamespacesDisabled);
97 }
98
99 let seccomp_enabled = seccomp::seccomp_available();
100 if !seccomp_enabled {
101 return Err(CheckError::SeccompNotAvailable);
102 }
103
104 Ok(SystemInfo {
105 kernel_version,
106 landlock_abi,
107 user_ns_enabled,
108 seccomp_enabled,
109 })
110}
111
112fn get_kernel_version() -> Result<(u32, u32, u32), CheckError> {
113 let uts = uname();
114 let release = uts.release().to_str().map_err(|_| CheckError::KernelVersionReadFailed)?;
115 parse_kernel_version(release)
116}
117
118fn parse_kernel_version(release: &str) -> Result<(u32, u32, u32), CheckError> {
119 let parts: Vec<&str> = release.split('.').collect();
120 if parts.len() < 2 {
121 return Err(CheckError::KernelVersionReadFailed);
122 }
123
124 let major = parts[0]
125 .parse::<u32>()
126 .map_err(|_| CheckError::KernelVersionReadFailed)?;
127
128 let minor = parts[1]
129 .parse::<u32>()
130 .map_err(|_| CheckError::KernelVersionReadFailed)?;
131
132 let patch = parts
134 .get(2)
135 .and_then(|p| p.split('-').next())
136 .and_then(|p| p.parse::<u32>().ok())
137 .unwrap_or(0);
138
139 Ok((major, minor, patch))
140}
141
142fn check_user_namespaces() -> bool {
143 if let Ok(content) = std::fs::read_to_string("/proc/sys/kernel/unprivileged_userns_clone") {
145 return content.trim() == "1";
146 }
147
148 if let Ok(content) = std::fs::read_to_string("/proc/sys/user/max_user_namespaces")
150 && content.trim().parse::<u32>().unwrap_or(0) > 0
151 {
152 return true;
153 }
154
155 unsafe {
158 let pid = libc::fork();
159 if pid < 0 {
160 return false;
161 }
162 if pid == 0 {
163 let ret = libc::unshare(libc::CLONE_NEWUSER);
164 libc::_exit(if ret == 0 { 0 } else { 1 });
165 }
166 let mut status: i32 = 0;
167 libc::waitpid(pid, &mut status, 0);
168 libc::WIFEXITED(status) && libc::WEXITSTATUS(status) == 0
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 #[test]
177 fn test_parse_kernel_version() {
178 assert_eq!(parse_kernel_version("5.15.0").unwrap(), (5, 15, 0));
179 assert_eq!(parse_kernel_version("6.1.0-generic").unwrap(), (6, 1, 0));
180 assert_eq!(parse_kernel_version("5.4.0-150-generic").unwrap(), (5, 4, 0));
181 }
182
183 #[test]
184 fn test_check() {
185 match check() {
186 Ok(info) => {
187 println!("Kernel version: {:?}", info.kernel_version);
188 println!("Landlock ABI: {}", info.landlock_abi);
189 println!("User NS enabled: {}", info.user_ns_enabled);
190 println!("Seccomp enabled: {}", info.seccomp_enabled);
191 }
192 Err(e) => {
193 println!("System check failed: {}", e);
194 }
195 }
196 }
197}