memf_linux/
proc_cmdline.rs1#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
9pub struct ProcessCmdline {
10 pub pid: u32,
12 pub comm: String,
14 pub exe: String,
16 pub args: Vec<String>,
18 pub cmdline_raw: String,
20}
21
22pub fn parse_proc_cmdline(pid: u32, comm: &str, bytes: &[u8]) -> ProcessCmdline {
28 let fields: Vec<String> = bytes
29 .split(|&b| b == 0)
30 .filter_map(|chunk| {
31 if chunk.is_empty() {
32 None
33 } else {
34 Some(String::from_utf8_lossy(chunk).into_owned())
35 }
36 })
37 .collect();
38
39 let exe = fields.first().cloned().unwrap_or_default();
40 let args = fields.get(1..).unwrap_or(&[]).to_vec();
41 let cmdline_raw = fields.join(" ");
42
43 ProcessCmdline {
44 pid,
45 comm: comm.to_string(),
46 exe,
47 args,
48 cmdline_raw,
49 }
50}
51
52pub fn is_ssh_tunnel_cmdline(cmdline: &ProcessCmdline) -> bool {
57 if !cmdline.exe.contains("ssh") {
58 return false;
59 }
60 cmdline
61 .args
62 .iter()
63 .any(|a| matches!(a.as_str(), "-L" | "-R" | "-D"))
64}
65
66pub fn is_miner_cmdline(cmdline: &ProcessCmdline) -> bool {
71 if cmdline.exe.contains("xmrig") {
72 return true;
73 }
74 cmdline
75 .args
76 .iter()
77 .any(|a| a.contains("stratum+") || a == "--pool")
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83
84 #[test]
87 fn parse_ssh_tunnel_cmdline() {
88 let cmdline = parse_proc_cmdline(941, "ssh", b"ssh\0-L\x003333:pool:3333\0");
89 assert_eq!(cmdline.pid, 941);
90 assert_eq!(cmdline.comm, "ssh");
91 assert_eq!(cmdline.exe, "ssh");
92 assert_eq!(cmdline.args, vec!["-L", "3333:pool:3333"]);
93 }
94
95 #[test]
96 fn parse_xmrig_cmdline() {
97 let cmdline = parse_proc_cmdline(
98 100,
99 "xmrig",
100 b"xmrig\0--pool\0stratum+tcp://pool:3333\0-u\0user\0",
101 );
102 assert_eq!(cmdline.exe, "xmrig");
103 assert!(cmdline.args.contains(&"--pool".to_string()));
104 assert!(cmdline.args.iter().any(|a| a.starts_with("stratum+")));
105 }
106
107 #[test]
108 fn parse_empty_bytes_produces_empty_exe() {
109 let cmdline = parse_proc_cmdline(1, "init", b"");
110 assert_eq!(cmdline.exe, "");
111 assert!(cmdline.args.is_empty());
112 assert_eq!(cmdline.cmdline_raw, "");
113 }
114
115 #[test]
116 fn parse_single_exe_no_args() {
117 let cmdline = parse_proc_cmdline(2, "bash", b"/bin/bash\0");
118 assert_eq!(cmdline.exe, "/bin/bash");
119 assert!(cmdline.args.is_empty());
120 assert_eq!(cmdline.cmdline_raw, "/bin/bash");
121 }
122
123 #[test]
124 fn parse_cmdline_raw_space_joins_all_fields() {
125 let cmdline = parse_proc_cmdline(3, "python3", b"python3\0-m\0http.server\x008080\0");
126 assert_eq!(cmdline.cmdline_raw, "python3 -m http.server 8080");
127 }
128
129 #[test]
132 fn ssh_tunnel_forward_l_is_tunnel() {
133 let cmdline = parse_proc_cmdline(941, "ssh", b"ssh\0-L\x003333:pool:3333\0");
134 assert!(is_ssh_tunnel_cmdline(&cmdline));
135 }
136
137 #[test]
138 fn ssh_tunnel_forward_r_is_tunnel() {
139 let cmdline = parse_proc_cmdline(942, "ssh", b"ssh\0-R\x008080:localhost:80\0user@host\0");
140 assert!(is_ssh_tunnel_cmdline(&cmdline));
141 }
142
143 #[test]
144 fn ssh_tunnel_socks_d_is_tunnel() {
145 let cmdline = parse_proc_cmdline(943, "ssh", b"ssh\0-D\x001080\0user@host\0");
146 assert!(is_ssh_tunnel_cmdline(&cmdline));
147 }
148
149 #[test]
150 fn bash_is_not_ssh_tunnel() {
151 let cmdline = parse_proc_cmdline(1, "bash", b"/bin/bash\0-c\0true\0");
152 assert!(!is_ssh_tunnel_cmdline(&cmdline));
153 }
154
155 #[test]
156 fn ssh_without_tunnel_flags_is_not_tunnel() {
157 let cmdline = parse_proc_cmdline(944, "ssh", b"ssh\0user@host\0");
159 assert!(!is_ssh_tunnel_cmdline(&cmdline));
160 }
161
162 #[test]
165 fn xmrig_exe_is_miner() {
166 let cmdline = parse_proc_cmdline(200, "xmrig", b"xmrig\0--pool\0pool.minexmr.com:443\0");
167 assert!(is_miner_cmdline(&cmdline));
168 }
169
170 #[test]
171 fn stratum_arg_is_miner() {
172 let cmdline = parse_proc_cmdline(201, "miner", b"./miner\0stratum+tcp://pool:3333\0");
173 assert!(is_miner_cmdline(&cmdline));
174 }
175
176 #[test]
177 fn pool_flag_is_miner() {
178 let cmdline = parse_proc_cmdline(202, "miner2", b"miner2\0--pool\0pool.example.com\0");
179 assert!(is_miner_cmdline(&cmdline));
180 }
181
182 #[test]
183 fn bash_is_not_miner() {
184 let cmdline = parse_proc_cmdline(1, "bash", b"/bin/bash\0-c\0echo hello\0");
185 assert!(!is_miner_cmdline(&cmdline));
186 }
187
188 #[test]
191 fn process_cmdline_clone_and_debug() {
192 let cmdline = parse_proc_cmdline(5, "sh", b"sh\0-c\0true\0");
193 let cloned = cmdline.clone();
194 let dbg = format!("{cloned:?}");
195 assert!(dbg.contains("ProcessCmdline"));
196 }
197
198 #[test]
199 fn process_cmdline_serializes_to_json() {
200 let cmdline = parse_proc_cmdline(6, "nginx", b"nginx\0-g\0daemon off;\0");
201 let json = serde_json::to_string(&cmdline).unwrap();
202 assert!(json.contains("\"pid\":6"));
203 assert!(json.contains("\"exe\":\"nginx\""));
204 }
205}