1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
|
//! A small process monitor example using netlink connector messages.
//!
//! You must run this example with root privileges, in the root pid namespace
//!
//! See this blog post for more details:
//! https://nick-black.com/dankwiki/index.php/The_Proc_Connector_and_Socket_Filters
use neli::{
connector::{CnMsg, ProcEvent, ProcEventHeader},
consts::{
connector::{CnMsgIdx, CnMsgVal, ProcCnMcastOp},
nl::{NlmF, Nlmsg},
socket::NlFamily,
},
nl::{NlPayload, NlmsghdrBuilder},
socket::synchronous::NlSocketHandle,
utils::Groups,
};
use std::fs;
fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();
let pid = std::process::id();
let socket = NlSocketHandle::connect(
NlFamily::Connector,
Some(pid),
Groups::new_bitmask(CnMsgIdx::Proc.into()),
)?;
let subscribe = NlmsghdrBuilder::default()
.nl_type(Nlmsg::Done)
.nl_flags(NlmF::empty())
.nl_pid(pid)
.nl_payload(NlPayload::Payload(
neli::connector::CnMsgBuilder::default()
.idx(CnMsgIdx::Proc)
.val(CnMsgVal::Proc)
.payload(ProcCnMcastOp::Listen)
.build()?,
))
.build()?;
socket.send(&subscribe)?;
loop {
for event in socket.recv::<Nlmsg, CnMsg<ProcEventHeader>>()?.0 {
let ProcEvent::Exec { process_pid, .. } = event?
.get_payload()
.ok_or("Failed to extract payload")?
.payload()
.event
else {
continue;
};
let exe = fs::read_link(format!("/proc/{process_pid}/exe"))
.map(|p| p.display().to_string())
.unwrap_or_else(|_| "unknown".to_string());
let cmdline = cmdline_to_string(process_pid).unwrap_or_else(|_| "unknown".to_string());
println!("Process created: PID: {process_pid}, Exe: {exe}, Cmdline: {cmdline}");
}
}
}
fn cmdline_to_string(pid: i32) -> std::io::Result<String> {
// 1) Read the entire file into a byte‐buffer in one go
let mut data = fs::read(format!("/proc/{pid}/cmdline"))?;
// 2) In‐place map all remaining NULs to spaces
for b in &mut data {
if *b == 0 {
*b = b' ';
}
}
// 3) SAFELY convert bytes → String without re‐checking UTF‐8
//
// We can do this because /proc/cmdline is guaranteed to be valid UTF-8
// on Linux (arguments must be passed as UTF-8), so we skip the cost
// of a UTF-8 validation pass.
let s = unsafe { String::from_utf8_unchecked(data) };
Ok(s)
}
|