cypheron_core/platform/
linux.rs

1use 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}