diff --git a/crates/fbuild-core/src/process_identity.rs b/crates/fbuild-core/src/process_identity.rs index d63d0867..bc896e33 100644 --- a/crates/fbuild-core/src/process_identity.rs +++ b/crates/fbuild-core/src/process_identity.rs @@ -25,6 +25,12 @@ use crate::path::NormalizedPath; /// `STILL_ACTIVE`. #[cfg(unix)] pub fn pid_is_alive(pid: u32) -> bool { + // kill() with pid 0 targets the caller's own process GROUP, not a + // process, so probing 0 would always report "alive". No legitimate + // owner record ever holds pid 0 — treat it as dead. + if pid == 0 { + return false; + } // SAFETY: kill(pid, 0) is a well-defined liveness probe — no signal is // delivered, the syscall just returns 0 if the pid exists and the // caller has permission to signal it. @@ -234,6 +240,14 @@ mod tests { assert!(!pid_is_alive(dead)); } + #[test] + fn pid_zero_is_never_alive() { + // Unix kill(0, 0) probes the caller's own process group and + // succeeds, which read as "alive" before the explicit guard — + // wedging any lock whose owner record held pid 0 (#1213). + assert!(!pid_is_alive(0)); + } + #[test] fn exe_stem_fails_closed_for_wrong_stem() { // Our own PID is alive, but its exe stem is the test binary, not