cypheron_core/platform/
linux.rs1use std::fs;
2use std::io::Error;
3
4pub mod platform {
5 use super::*;
6
7 pub fn secure_random_bytes(buffer: &mut [u8]) -> Result<(), Error> {
8 if try_getrandom(buffer).is_ok() {
9 return Ok(());
10 }
11
12 secure_random_bytes_dev_urandom(buffer)
13 }
14
15 pub fn secure_zero(buffer: &mut [u8]) {
16 unsafe {
17 if has_explicit_bzero() {
18 libc::explicit_bzero(buffer.as_mut_ptr() as *mut libc::c_void, buffer.len());
19 } else {
20 secure_zero_fallback(buffer);
21 }
22 }
23 }
24}
25
26fn try_getrandom(buffer: &mut [u8]) -> Result<(), Error> {
27 unsafe {
28 let result = libc::syscall(libc::SYS_getrandom, buffer.as_mut_ptr(), buffer.len(), 0);
29
30 if result < 0 {
31 return Err(Error::other("getrandom syscall failed"));
32 }
33
34 if result as usize != buffer.len() {
35 return Err(Error::other("getrandom returned insufficient bytes"));
36 }
37 }
38
39 Ok(())
40}
41
42fn secure_random_bytes_dev_urandom(buffer: &mut [u8]) -> Result<(), Error> {
43 use std::fs::File;
44 use std::io::Read;
45
46 let mut file = File::open("/dev/urandom")
47 .map_err(|e| Error::other(format!("Failed to open /dev/urandom: {}", e)))?;
48
49 file.read_exact(buffer)
50 .map_err(|e| Error::other(format!("Failed to read from /dev/urandom: {}", e)))?;
51
52 Ok(())
53}
54
55fn has_explicit_bzero() -> bool {
56 true
57}
58
59fn secure_zero_fallback(buffer: &mut [u8]) {
60 use zeroize::Zeroize;
61 buffer.zeroize();
62}
63
64pub fn protect_memory(buffer: &mut [u8], protect: bool) -> Result<(), Error> {
65 use libc::{mprotect, PROT_NONE, PROT_READ, PROT_WRITE};
66
67 let protection = if protect {
68 PROT_NONE
69 } else {
70 PROT_READ | PROT_WRITE
71 };
72
73 let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize;
74 let addr = buffer.as_mut_ptr() as usize;
75 let aligned_addr = addr & !(page_size - 1);
76 let aligned_len = ((addr + buffer.len() + page_size - 1) & !(page_size - 1)) - aligned_addr;
77
78 unsafe {
79 if mprotect(aligned_addr as *mut libc::c_void, aligned_len, protection) != 0 {
80 return Err(Error::other("Failed to protect memory"));
81 }
82 }
83
84 Ok(())
85}
86
87pub fn get_linux_distro() -> String {
88 if let Ok(content) = fs::read_to_string("/etc/os-release") {
89 for line in content.lines() {
90 if line.starts_with("PRETTY_NAME=") {
91 let name = line.strip_prefix("PRETTY_NAME=").unwrap_or("");
92 return name.trim_matches('"').to_string();
93 }
94 }
95 }
96
97 if let Ok(content) = fs::read_to_string("/etc/lsb-release") {
98 for line in content.lines() {
99 if line.starts_with("DISTRIB_DESCRIPTION=") {
100 let name = line.strip_prefix("DISTRIB_DESCRIPTION=").unwrap_or("");
101 return name.trim_matches('"').to_string();
102 }
103 }
104 }
105
106 "Linux (distribution unknown)".to_string()
107}
108
109pub fn get_kernel_version() -> String {
110 if let Ok(version) = fs::read_to_string("/proc/version") {
111 if let Some(end) = version.find(' ') {
112 return version[..end].to_string();
113 }
114 }
115
116 "Unknown kernel".to_string()
117}
118
119#[derive(Debug, Clone, Default)]
120pub struct CpuInfo {
121 pub model_name: String,
122 pub cores: u32,
123 pub has_aes: bool,
124 pub has_avx2: bool,
125 pub has_rdrand: bool,
126 pub has_rdseed: bool,
127}
128
129pub fn get_cpu_info() -> CpuInfo {
130 use std::collections::HashMap;
131
132 let mut info = CpuInfo::default();
133
134 if let Ok(content) = fs::read_to_string("/proc/cpuinfo") {
135 let mut properties = HashMap::new();
136
137 for line in content.lines() {
138 if let Some(pos) = line.find(':') {
139 let key = line[..pos].trim();
140 let value = line[pos + 1..].trim();
141 properties.insert(key.to_string(), value.to_string());
142 }
143 }
144
145 if let Some(model) = properties.get("model name") {
146 info.model_name = model.clone();
147 }
148
149 if let Some(flags) = properties.get("flags") {
150 info.has_aes = flags.contains("aes");
151 info.has_avx2 = flags.contains("avx2");
152 info.has_rdrand = flags.contains("rdrand");
153 info.has_rdseed = flags.contains("rdseed");
154 }
155
156 if let Some(cores) = properties.get("cpu cores") {
157 info.cores = cores.parse().unwrap_or(1);
158 }
159 }
160
161 info
162}
163
164pub fn optimize_for_crypto() -> Result<(), Error> {
165 set_cpu_affinity()?;
166
167 unsafe {
168 if libc::setpriority(libc::PRIO_PROCESS, 0, -5) != 0 {
169 eprintln!("Warning: Could not set process priority (requires privileges)");
170 }
171 }
172
173 Ok(())
174}
175
176fn set_cpu_affinity() -> Result<(), Error> {
177 let cpu_count = num_cpus::get();
178
179 unsafe {
180 let mut cpu_set: libc::cpu_set_t = std::mem::zeroed();
181 libc::CPU_ZERO(&mut cpu_set);
182
183 for i in 0..cpu_count {
184 libc::CPU_SET(i, &mut cpu_set);
185 }
186
187 if libc::sched_setaffinity(0, std::mem::size_of::<libc::cpu_set_t>(), &cpu_set) != 0 {
188 return Err(Error::other("Failed to set CPU affinity"));
189 }
190 }
191
192 Ok(())
193}
194
195#[derive(Debug, Clone)]
196pub struct SecurityFeatures {
197 pub has_hardware_rng: bool,
198 pub has_aes_ni: bool,
199 pub has_avx2: bool,
200 pub has_secure_boot: bool,
201 pub has_tpm: bool,
202}
203
204pub fn check_security_features() -> SecurityFeatures {
205 let cpu_info = get_cpu_info();
206
207 SecurityFeatures {
208 has_hardware_rng: cpu_info.has_rdrand || cpu_info.has_rdseed,
209 has_aes_ni: cpu_info.has_aes,
210 has_avx2: cpu_info.has_avx2,
211 has_secure_boot: check_secure_boot(),
212 has_tpm: check_tpm(),
213 }
214}
215
216fn check_secure_boot() -> bool {
217 if let Ok(content) = fs::read_to_string(
218 "/sys/firmware/efi/efivars/SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c",
219 ) {
220 content.len() > 4 && content.as_bytes()[4] == 1
221 } else {
222 false
223 }
224}
225
226fn check_tpm() -> bool {
227 fs::metadata("/dev/tpm0").is_ok() || fs::metadata("/dev/tpmrm0").is_ok()
228}
229
230#[cfg(feature = "seccomp-bpf")]
231pub mod sandbox {
232 use std::io::Error;
233
234 pub fn enable_crypto_sandbox() -> Result<(), Error> {
235 use libseccomp::*;
236
237 let mut ctx = match ScmpFilterContext::new_filter(ScmpAction::KillProcess) {
238 Ok(ctx) => ctx,
239 Err(e) => {
240 return Err(Error::other(format!(
241 "Failed to create seccomp context: {}",
242 e
243 )))
244 }
245 };
246
247 let allowed_syscalls = [
248 "read",
249 "write",
250 "getrandom",
251 "mmap",
252 "munmap",
253 "mprotect",
254 "mlock",
255 "munlock",
256 "brk",
257 "clock_gettime",
258 "gettimeofday",
259 "exit_group",
260 "exit",
261 "futex",
262 "sched_yield",
263 "rt_sigreturn",
264 "sigaltstack",
265 ];
266
267 for syscall_name in &allowed_syscalls {
268 let syscall = ScmpSyscall::from_name(syscall_name).map_err(|e| {
269 Error::other(format!("Failed to resolve syscall {}: {}", syscall_name, e))
270 })?;
271
272 ctx.add_rule(ScmpAction::Allow, syscall).map_err(|e| {
273 Error::other(format!(
274 "Failed to add seccomp rule for {}: {}",
275 syscall_name, e
276 ))
277 })?;
278 }
279
280 ctx.load()
281 .map_err(|e| Error::other(format!("Failed to load seccomp filter: {}", e)))?;
282
283 Ok(())
284 }
285
286 pub fn enable_production_hardening() -> Result<(), Error> {
287 unsafe {
288 if libc::prctl(libc::PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) != 0 {
289 return Err(Error::other("Failed to set NO_NEW_PRIVS"));
290 }
291 }
292
293 enable_crypto_sandbox()?;
294
295 Ok(())
296 }
297}
298
299#[cfg(feature = "seccomp-bpf")]
300pub use sandbox::enable_production_hardening as enable_production_security;
301
302#[cfg(not(feature = "seccomp-bpf"))]
303pub fn enable_production_security() -> Result<(), Error> {
304 eprintln!("Warning: seccomp-bpf feature not enabled - sandbox not active");
305 Ok(())
306}
307
308#[cfg(test)]
309mod tests {
310 use super::*;
311
312 #[test]
313 fn test_secure_random_bytes() {
314 let mut buffer = [0u8; 32];
315 platform::secure_random_bytes(&mut buffer).expect("Failed to generate random bytes");
316
317 assert!(buffer.iter().any(|&b| b != 0));
318 }
319
320 #[test]
321 fn test_secure_zero() {
322 let mut buffer = [0xAA; 32];
323 platform::secure_zero(&mut buffer);
324
325 assert!(buffer.iter().all(|&b| b == 0));
326 }
327
328 #[test]
329 fn test_cpu_info() {
330 let info = get_cpu_info();
331 println!("CPU: {}, cores: {}", info.model_name, info.cores);
332 }
333
334 #[test]
335 fn test_security_features() {
336 let features = check_security_features();
337 println!("Security features: {:?}", features);
338 }
339
340 #[cfg(feature = "seccomp-bpf")]
341 #[test]
342 fn test_seccomp_allows_getrandom() {
343 let mut buffer = [0u8; 16];
344 let result = try_getrandom(&mut buffer);
345 assert!(result.is_ok());
346 }
347}