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:
+42
-19
@@ -177,12 +177,24 @@ impl FingerprintListener {
|
||||
|
||||
/// Resume fingerprint verification after a transient interruption (e.g. failed
|
||||
/// PAM account check). Reuses previously stored callbacks. Re-claims the device
|
||||
/// and restarts verification from scratch.
|
||||
/// and restarts verification from scratch. Awaits any in-flight VerifyStop +
|
||||
/// Release before re-claiming the device so fprintd does not reject the Claim
|
||||
/// while the previous session is still being torn down.
|
||||
pub async fn resume_async(
|
||||
listener: &Rc<RefCell<FingerprintListener>>,
|
||||
username: &str,
|
||||
) {
|
||||
listener.borrow_mut().failed_attempts = 0;
|
||||
// Drain in-flight cleanup so the device is actually released before Claim.
|
||||
// Without this, a fast resume after on_verify_status's fire-and-forget
|
||||
// cleanup races the Release call and fprintd returns "already claimed".
|
||||
let proxy = listener.borrow_mut().take_cleanup_proxy();
|
||||
if let Some(proxy) = proxy {
|
||||
Self::perform_dbus_cleanup(proxy).await;
|
||||
}
|
||||
// Deliberately do NOT reset failed_attempts here. An attacker with sensor
|
||||
// control could otherwise cycle verify-match → check_account fail → resume,
|
||||
// and the 10-attempt cap would never trigger. The counter decays only via
|
||||
// a fresh lock session (listener construction).
|
||||
Self::begin_verification(listener, username).await;
|
||||
}
|
||||
|
||||
@@ -352,26 +364,37 @@ impl FingerprintListener {
|
||||
}
|
||||
}
|
||||
|
||||
/// Disconnect the signal handler and send VerifyStop + Release to fprintd.
|
||||
/// Signal disconnect is synchronous to prevent further callbacks.
|
||||
/// D-Bus cleanup is fire-and-forget to avoid blocking the UI.
|
||||
fn cleanup_dbus(&mut self) {
|
||||
/// Disconnect the signal handler and clear running flags. Returns the proxy
|
||||
/// the caller should use for the async D-Bus cleanup (VerifyStop + Release).
|
||||
///
|
||||
/// Split into a sync part (signal disconnect, flags) and an async part
|
||||
/// (`perform_dbus_cleanup`) so callers can either spawn the async work
|
||||
/// fire-and-forget (via `cleanup_dbus`) or await it to serialize with a
|
||||
/// subsequent Claim (via `resume_async`).
|
||||
fn take_cleanup_proxy(&mut self) -> Option<gio::DBusProxy> {
|
||||
self.running = false;
|
||||
self.running_flag.set(false);
|
||||
|
||||
if let Some(ref proxy) = self.device_proxy {
|
||||
if let Some(id) = self.signal_id.take() {
|
||||
proxy.disconnect(id);
|
||||
}
|
||||
let proxy = proxy.clone();
|
||||
glib::spawn_future_local(async move {
|
||||
let _ = proxy
|
||||
.call_future("VerifyStop", None, gio::DBusCallFlags::NONE, DBUS_TIMEOUT_MS)
|
||||
.await;
|
||||
let _ = proxy
|
||||
.call_future("Release", None, gio::DBusCallFlags::NONE, DBUS_TIMEOUT_MS)
|
||||
.await;
|
||||
});
|
||||
let proxy = self.device_proxy.clone()?;
|
||||
if let Some(id) = self.signal_id.take() {
|
||||
proxy.disconnect(id);
|
||||
}
|
||||
Some(proxy)
|
||||
}
|
||||
|
||||
async fn perform_dbus_cleanup(proxy: gio::DBusProxy) {
|
||||
let _ = proxy
|
||||
.call_future("VerifyStop", None, gio::DBusCallFlags::NONE, DBUS_TIMEOUT_MS)
|
||||
.await;
|
||||
let _ = proxy
|
||||
.call_future("Release", None, gio::DBusCallFlags::NONE, DBUS_TIMEOUT_MS)
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Fire-and-forget cleanup for code paths that cannot await (e.g. drop, stop).
|
||||
fn cleanup_dbus(&mut self) {
|
||||
if let Some(proxy) = self.take_cleanup_proxy() {
|
||||
glib::spawn_future_local(Self::perform_dbus_cleanup(proxy));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user