Skip to main content

muster/adapter/
process_identity.rs

1use crate::domain::agent_session::{AgentProcessId, AgentProcessStartToken};
2
3/// Offset basis for a deterministic process-start-text fingerprint.
4#[cfg(all(unix, not(target_os = "linux")))]
5const START_TOKEN_HASH_OFFSET: u64 = 14_695_981_039_346_656_037;
6/// Multiplier for a deterministic process-start-text fingerprint.
7#[cfg(all(unix, not(target_os = "linux")))]
8const START_TOKEN_HASH_PRIME: u64 = 1_099_511_628_211;
9
10/// Queries the operating system for a process's non-reusable creation marker.
11pub struct LocalProcessIdentity;
12
13impl LocalProcessIdentity {
14    /// Returns the creation marker for `process_id` when this platform exposes one.
15    pub fn start_token(process_id: AgentProcessId) -> Option<AgentProcessStartToken> {
16        Self::platform_start_token(process_id)
17    }
18
19    #[cfg(target_os = "linux")]
20    fn platform_start_token(process_id: AgentProcessId) -> Option<AgentProcessStartToken> {
21        let stat =
22            std::fs::read_to_string(format!("/proc/{}/stat", process_id.into_inner())).ok()?;
23        let fields = stat
24            .rsplit_once(") ")?
25            .1
26            .split_whitespace()
27            .collect::<Vec<_>>();
28        fields
29            .get(19)?
30            .parse::<u64>()
31            .ok()
32            .and_then(|token| AgentProcessStartToken::try_new(token).ok())
33    }
34
35    #[cfg(windows)]
36    fn platform_start_token(process_id: AgentProcessId) -> Option<AgentProcessStartToken> {
37        use windows_sys::Win32::{
38            Foundation::{CloseHandle, FILETIME},
39            System::Threading::{GetProcessTimes, OpenProcess, PROCESS_QUERY_LIMITED_INFORMATION},
40        };
41
42        // SAFETY: the PID is an ordinary integer, the output FILETIMEs are valid
43        // initialized stack storage, and the opened handle is closed on every path.
44        unsafe {
45            let handle = OpenProcess(
46                PROCESS_QUERY_LIMITED_INFORMATION,
47                0,
48                process_id.into_inner(),
49            );
50            if handle.is_null() {
51                return None;
52            }
53            let mut created = std::mem::zeroed::<FILETIME>();
54            let mut exited = std::mem::zeroed::<FILETIME>();
55            let mut kernel = std::mem::zeroed::<FILETIME>();
56            let mut user = std::mem::zeroed::<FILETIME>();
57            let result = GetProcessTimes(handle, &mut created, &mut exited, &mut kernel, &mut user);
58            let _ = CloseHandle(handle);
59            (result != 0)
60                .then(|| {
61                    u64::from(created.dwLowDateTime) | (u64::from(created.dwHighDateTime) << 32)
62                })
63                .and_then(|token| AgentProcessStartToken::try_new(token).ok())
64        }
65    }
66
67    #[cfg(all(unix, not(target_os = "linux")))]
68    fn platform_start_token(process_id: AgentProcessId) -> Option<AgentProcessStartToken> {
69        use std::process::Command;
70
71        let output = Command::new("ps")
72            .args(["-o", "lstart=", "-p", &process_id.into_inner().to_string()])
73            .output()
74            .ok()?;
75        let started = String::from_utf8(output.stdout).ok()?;
76        let token = started
77            .trim()
78            .bytes()
79            .fold(START_TOKEN_HASH_OFFSET, |hash, byte| {
80                (hash ^ u64::from(byte)).wrapping_mul(START_TOKEN_HASH_PRIME)
81            });
82        AgentProcessStartToken::try_new(token).ok()
83    }
84
85    #[cfg(not(any(unix, windows)))]
86    fn platform_start_token(_process_id: AgentProcessId) -> Option<AgentProcessStartToken> {
87        None
88    }
89}