login authentication (PAM + fingerprint)
git clone https://git.lucas.co/cce-authenticator.git
fix: reap the helper on cancel — every cancelled prompt leaked a zombie
Found while verifying the retry path. The Cancel handler killed the helper and
took it out of the shared slot, which also stops the reader thread from waiting on
it — and Rust never reaps a Child on drop. So each cancelled authentication left a
zombie for the life of the daemon, which is the life of the session: four were
already parked under the agent from this session's testing. Reaped on a detached
thread rather than inline, because this process is the session's only polkit agent
and must never wedge on a wait.
The retry path itself verified clean, without risking pam_faillock (deny=3,
unlock_time=600): killing the helper produces the same non-zero exit a wrong
password does, and drives the same code. Three distinct helper pids, two restarts
logged with the countdown, then the bound holding — no fourth spawn, and the dialog
showing "Authentication failed - press Escape to cancel". Verified after this fix
that three cancelled prompts leave zero zombies, where they previously left three.
Co-Authored-By: Claude Fable 5 <[email protected]>
src/main.rs | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/src/main.rs b/src/main.rs
index 129412e..7934d40 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -460,6 +460,14 @@ impl Application for AuthenticatorApp {
if let Some(ref shared_child) = self.shared_child {
if let Some(mut child) = shared_child.lock().unwrap().take() {
let _ = child.kill();
+ // `kill` only signals — Rust never reaps on drop — and taking the
+ // child here means the reader thread won't wait() on it either, so
+ // without this every cancelled prompt left a zombie for the life of
+ // the session. Reaped off-thread because this daemon must never
+ // wedge on a wait: it is the session's only polkit agent.
+ std::thread::spawn(move || {
+ let _ = child.wait();
+ });
}
}
*exit = true;