1use super::{Check, Status};
4
5pub fn checks() -> Vec<Check> {
6 let mut results = Vec::new();
7 let pids = daemon_pids();
8
9 let all_filtered = !pids.is_empty()
11 && pids
12 .iter()
13 .all(|pid| read_proc_field(*pid, "Seccomp").as_deref() == Some("2"));
14 results.push(Check {
15 id: "sandbox.seccomp".into(),
16 category: "sandbox",
17 status: if all_filtered {
18 Status::Pass
19 } else {
20 Status::Fail
21 },
22 value: if all_filtered {
23 "filter mode (all daemons)".into()
24 } else {
25 "not all daemons have seccomp filter".into()
26 },
27 description: "Seccomp BPF restricts available syscalls per daemon".into(),
28 });
29
30 let all_nnp = !pids.is_empty()
32 && pids
33 .iter()
34 .all(|pid| read_proc_field(*pid, "NoNewPrivs").as_deref() == Some("1"));
35 results.push(Check {
36 id: "sandbox.no_new_privs".into(),
37 category: "sandbox",
38 status: if all_nnp { Status::Pass } else { Status::Warn },
39 value: if all_nnp {
40 "set (all daemons)".into()
41 } else {
42 "not set on all daemons".into()
43 },
44 description: "NoNewPrivileges prevents privilege escalation via setuid/capabilities".into(),
45 });
46
47 results
48}
49
50fn read_proc_field(pid: u32, field: &str) -> Option<String> {
51 let content = std::fs::read_to_string(format!("/proc/{pid}/status")).ok()?;
52 for line in content.lines() {
53 if let Some(rest) = line.strip_prefix(field) {
54 return Some(rest.trim_start_matches(':').trim().to_string());
55 }
56 }
57 None
58}
59
60fn daemon_pids() -> Vec<u32> {
61 let binaries = [
62 "daemon-profile",
63 "daemon-secrets",
64 "daemon-wm",
65 "daemon-launcher",
66 "daemon-clipboard",
67 "daemon-input",
68 "daemon-snippets",
69 ];
70 binaries
71 .iter()
72 .filter_map(|name| {
73 let output = std::process::Command::new("pidof")
74 .arg(name)
75 .output()
76 .ok()?;
77 if output.status.success() {
78 String::from_utf8_lossy(&output.stdout)
79 .split_whitespace()
80 .next()?
81 .parse()
82 .ok()
83 } else {
84 None
85 }
86 })
87 .collect()
88}