From 641b13704b83e0c99f434544fba5d6be8e3de310 Mon Sep 17 00:00:00 2001 From: Robert M1 <50460704+githubrobbi@users.noreply.github.com> Date: Fri, 19 Jun 2026 07:57:07 -0700 Subject: [PATCH] fix(client): don't refuse the daemon when its launch path != resolved image path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `uffs ` failed with "Daemon identity verification failed … the process on the IPC endpoint does not match the exe hash recorded when the daemon started" on WinGet installs — blocking every search. Root cause: the identity check compares FNV-1a hashes of two path STRINGS produced by different APIs: - daemon records `std::env::current_exe()` (the LAUNCH path) - client reads `QueryFullProcessImageNameW(pid)` (the resolved IMAGE path) For a daemon launched through a symlink/shim — e.g. WinGet's `…\Local\Microsoft\WinGet\Links\uffsd.exe` — those two strings differ for the SAME binary, so the hashes mismatch and the strict check hard-refused the connection. It's a false positive, not impersonation. Fix: treat the path-string hash as a fast-path, not the sole authority. On mismatch, defer to `verify_daemon_identity(pid)` — the peer must be a `uffsd` binary (by name) and, when signed, pass Authenticode. A genuine impostor fails that exactly as it failed the hash, so security is preserved; this just mirrors the existing hash==0 fallback. The two strict-verify tests still pass (their non-`uffsd` test process is refused by the name check). Verified: 204 uffs-client tests green, clippy clean. Co-Authored-By: Claude Opus 4.8 --- crates/uffs-client/src/verify.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/crates/uffs-client/src/verify.rs b/crates/uffs-client/src/verify.rs index 4a8300ef4..fe3a4fa32 100644 --- a/crates/uffs-client/src/verify.rs +++ b/crates/uffs-client/src/verify.rs @@ -126,14 +126,25 @@ pub(crate) fn verify_daemon_pid_file(pid_path: &std::path::Path) -> bool { }; if actual_hash != expected_hash { + // A path-STRING hash mismatch is NOT proof of impersonation. The daemon + // records `current_exe()` (its launch path), while this side reads + // `QueryFullProcessImageNameW` (the resolved image path). For a daemon + // launched through a symlink/shim — e.g. a WinGet `…\Links\uffsd.exe` + // entry, or any relaunch — those two path strings differ for the SAME + // binary, so the hashes differ even though nothing is wrong. Rather than + // refuse a legitimate daemon, defer to the authoritative identity check: + // the peer must be a `uffsd` binary (by name) and, when signed, pass + // Authenticode — which a real impostor fails just as it fails the hash. + // (This mirrors the hash==0 path, which already falls back here.) tracing::warn!( pid, exe = %exe_path.display(), expected_hash, actual_hash, - "Daemon exe_path_hash mismatch — possible impersonation" + "Daemon exe_path_hash mismatch (launch path vs resolved image path?) \ + — deferring to name + signature identity check" ); - return false; + return verify_daemon_identity(pid); } true