From fcd465c2b037716f27e34367b97a43b5a2d7faee Mon Sep 17 00:00:00 2001 From: Khoa Vo Date: Thu, 2 Jul 2026 12:12:32 +0700 Subject: [PATCH] fix: exclude daemon's own sudo ancestor from password detection When the daemon is started via `sudo vietc-daemon` from a terminal, is_sudo_process would see sudo( in the terminal's process tree and disable the engine, making all keystrokes pass through untransformed. Now is_sudo_process builds the daemon's ancestor PID chain and skips any sudo process that is an ancestor of the daemon itself. --- daemon/src/app_state.rs | 56 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 5 deletions(-) diff --git a/daemon/src/app_state.rs b/daemon/src/app_state.rs index b8852e2..a6b9a61 100644 --- a/daemon/src/app_state.rs +++ b/daemon/src/app_state.rs @@ -831,18 +831,64 @@ fn log_password_detection(method: &str, context: &str) { eprintln!("[vietc] Password field detected via {}: {}", method, context); } -/// Check if the given PID or any of its descendants is running sudo/passwd. -/// Uses pstree to scan the full process tree (handles any depth): -/// terminal → shell → sudo → passwd -/// gnome-terminal-server → gnome-terminal → bash → sudo +/// Walk up the process tree from `pid` to PID 1 and return all ancestor PIDs. +fn get_ancestor_pids(pid: u32) -> Vec { + let mut ancestors = Vec::new(); + let mut current = pid; + loop { + ancestors.push(current); + let path = format!("/proc/{}/status", current); + let content = match std::fs::read_to_string(&path) { + Ok(c) => c, + Err(_) => break, + }; + let ppid = match content + .lines() + .find(|l| l.starts_with("PPid:")) + .and_then(|l| l.split_whitespace().nth(1)) + .and_then(|s| s.parse::().ok()) + { + Some(0) | None => break, + Some(p) => p, + }; + if ppid == current { + break; + } + current = ppid; + } + ancestors +} + +/// Check if the given PID or any of its descendants is running a password +/// prompt (sudo, passwd, pkexec). Excludes the daemon's own ancestor chain +/// so that running `sudo vietc-daemon` from the same terminal doesn't cause +/// a false positive. fn is_sudo_process(pid: u32) -> bool { + // Build the daemon's ancestor PID chain to exclude self from detection + let daemon_pid = unsafe { libc::getpid() as u32 }; + let daemon_ancestors = get_ancestor_pids(daemon_pid); + if let Ok(output) = Command::new("pstree") .args(["-p", &pid.to_string()]) .output() { let tree = String::from_utf8_lossy(&output.stdout); for target in &["sudo(", "passwd(", "pkexec("] { - if tree.contains(target) { + let mut start = 0; + while let Some(pos) = tree[start..].find(target) { + let abs_pos = start + pos; + let after = &tree[abs_pos + target.len()..]; + let found_pid = after + .split(')') + .next() + .and_then(|s| s.parse::().ok()); + // Skip if this PID is the daemon's own ancestor (e.g. `sudo vietc-daemon`) + if let Some(p) = found_pid { + if target == &"sudo(" && daemon_ancestors.contains(&p) { + start = abs_pos + 1; + continue; + } + } return true; } }