fix: audit MEDIUM fixes — D-Bus race, TOCTOU, FP reset, entry clear (v0.6.11)

- fingerprint: split cleanup_dbus into a sync take_cleanup_proxy() + async
  perform_dbus_cleanup(). resume_async now awaits VerifyStop+Release before
  re-claiming, so fprintd cannot reject the Claim on a slow bus. stop()
  still spawns the cleanup fire-and-forget.
- fingerprint: remove failed_attempts = 0 from resume_async. An attacker
  with sensor control could otherwise cycle verify-match → account-fail →
  resume and never trip the 10-attempt cap.
- lockscreen: open the wallpaper with O_NOFOLLOW and build the texture
  from bytes, closing the TOCTOU between the symlink check and Texture::
  from_file.
- lockscreen: clear password_entry immediately after extracting the
  Zeroizing<String>, shortening the window the GLib GString copy stays in
  libc-malloc'd memory.
This commit is contained in:
2026-04-24 13:21:19 +02:00
parent 39d9cbb624
commit 9dfd1829e9
5 changed files with 81 additions and 24 deletions
+7
View File
@@ -2,6 +2,13 @@
Architectural and design decisions for Moonlock, in reverse chronological order.
## 2026-04-24 Audit MEDIUM fixes: D-Bus cleanup race, TOCTOU open, FP reset, GTK entry clear (v0.6.11)
- **Who**: ClaudeCode, Dom
- **Why**: Second round after the HIGH fixes, addressing the four MEDIUM findings. (1) `cleanup_dbus` spawned VerifyStop + Release as fire-and-forget, then `resume_async` called Claim after only a 2 s timeout — shorter than the 3 s D-Bus timeout, so on a slow bus the Claim could race the Release and fprintd would reject it, leaving the FP listener permanently dead. (2) `load_background_texture` relied on the caller's `symlink_metadata` check, re-opening the path via `gdk::Texture::from_file` — a classic TOCTOU window. (3) `resume_async` unconditionally reset `failed_attempts`, allowing an attacker with sensor control to evade the 10-attempt cap by cycling verify-match → `check_account` fail → resume. (4) The GTK `PasswordEntry` buffer was only cleared on timeout or auth failure, leaving the password in GLib malloc'd memory longer than necessary.
- **Tradeoffs**: The D-Bus cleanup is now split into a synchronous helper (`take_cleanup_proxy` — signal disconnect + flag clear) and an async helper (`perform_dbus_cleanup` — VerifyStop + Release), so `resume_async` can await the release while `stop()` stays fire-and-forget. Dropping the `failed_attempts` reset means a flaky sensor could reach 10 failures faster, but the correct remedy is a new lock session (construction) rather than a reset that also helps attackers.
- **How**: (1) Split `cleanup_dbus` into `take_cleanup_proxy()` (sync) + `perform_dbus_cleanup(proxy)` (async). `resume_async` now awaits `perform_dbus_cleanup` before `begin_verification`. `stop()` still spawns the cleanup fire-and-forget. (2) `load_background_texture` opens with `O_NOFOLLOW` via `std::fs::OpenOptions::custom_flags`, reads to bytes, and builds the texture via `gdk::Texture::from_bytes`. (3) Removed `listener.borrow_mut().failed_attempts = 0` from `resume_async`. (4) `password_entry.set_text("")` now fires right after the `Zeroizing::new(entry.text().to_string())` extraction, shortening the GTK-side window.
## 2026-04-24 Audit fixes: RefCell borrow across await, async avatar decode
- **Who**: ClaudeCode, Dom