fix: audit fixes — fprintd path validation, progressive faillock warning (v0.6.7)
All checks were successful
Update PKGBUILD version / update-pkgver (push) Successful in 1s
All checks were successful
Update PKGBUILD version / update-pkgver (push) Successful in 1s
- Validate fprintd device path prefix (/net/reactivated/Fprint/Device/) before creating D-Bus proxy (prevents use of unexpected object paths) - faillock_warning now warns at remaining <= 2 attempts (not just == 1), improving UX for higher max_attempts configurations
This commit is contained in:
parent
af5b7c8912
commit
59c509dcbb
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -575,7 +575,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "moonlock"
|
name = "moonlock"
|
||||||
version = "0.6.5"
|
version = "0.6.7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"gdk-pixbuf",
|
"gdk-pixbuf",
|
||||||
"gdk4",
|
"gdk4",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "moonlock"
|
name = "moonlock"
|
||||||
version = "0.6.6"
|
version = "0.6.7"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "A secure Wayland lockscreen with GTK4, PAM and fingerprint support"
|
description = "A secure Wayland lockscreen with GTK4, PAM and fingerprint support"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|||||||
@ -13,6 +13,7 @@ const FPRINTD_DEVICE_IFACE: &str = "net.reactivated.Fprint.Device";
|
|||||||
|
|
||||||
const MAX_FP_ATTEMPTS: u32 = 10;
|
const MAX_FP_ATTEMPTS: u32 = 10;
|
||||||
const DBUS_TIMEOUT_MS: i32 = 3000;
|
const DBUS_TIMEOUT_MS: i32 = 3000;
|
||||||
|
const FPRINTD_DEVICE_PREFIX: &str = "/net/reactivated/Fprint/Device/";
|
||||||
|
|
||||||
/// Retry-able statuses — finger not read properly, try again.
|
/// Retry-able statuses — finger not read properly, try again.
|
||||||
const RETRY_STATUSES: &[&str] = &[
|
const RETRY_STATUSES: &[&str] = &[
|
||||||
@ -99,6 +100,10 @@ impl FingerprintListener {
|
|||||||
if device_path.is_empty() {
|
if device_path.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if !device_path.starts_with(FPRINTD_DEVICE_PREFIX) {
|
||||||
|
log::warn!("Unexpected fprintd device path: {device_path}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
match gio::DBusProxy::for_bus_future(
|
match gio::DBusProxy::for_bus_future(
|
||||||
gio::BusType::System,
|
gio::BusType::System,
|
||||||
|
|||||||
24
src/i18n.rs
24
src/i18n.rs
@ -100,11 +100,13 @@ pub fn load_strings(locale: Option<&str>) -> &'static Strings {
|
|||||||
match locale { "de" => &STRINGS_DE, _ => &STRINGS_EN }
|
match locale { "de" => &STRINGS_DE, _ => &STRINGS_EN }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a warning when the user is one attempt away from lockout.
|
/// Returns a warning when the user is close to lockout (2 or fewer attempts remaining).
|
||||||
/// Caller is responsible for handling the locked state (count >= max_attempts).
|
/// Caller is responsible for handling the locked state (count >= max_attempts).
|
||||||
pub fn faillock_warning(attempt_count: u32, max_attempts: u32, strings: &Strings) -> Option<String> {
|
pub fn faillock_warning(attempt_count: u32, max_attempts: u32, strings: &Strings) -> Option<String> {
|
||||||
let remaining = max_attempts.saturating_sub(attempt_count);
|
let remaining = max_attempts.saturating_sub(attempt_count);
|
||||||
if remaining == 1 { return Some(strings.faillock_attempts_remaining.replace("{n}", &remaining.to_string())); }
|
if remaining > 0 && remaining <= 2 {
|
||||||
|
return Some(strings.faillock_attempts_remaining.replace("{n}", &remaining.to_string()));
|
||||||
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,7 +140,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test] fn faillock_zero() { assert!(faillock_warning(0, 3, load_strings(Some("en"))).is_none()); }
|
#[test] fn faillock_zero() { assert!(faillock_warning(0, 3, load_strings(Some("en"))).is_none()); }
|
||||||
#[test] fn faillock_one() { assert!(faillock_warning(1, 3, load_strings(Some("en"))).is_none()); }
|
#[test] fn faillock_one() { assert!(faillock_warning(1, 3, load_strings(Some("en"))).is_some()); }
|
||||||
#[test] fn faillock_two() { assert!(faillock_warning(2, 3, load_strings(Some("en"))).is_some()); }
|
#[test] fn faillock_two() { assert!(faillock_warning(2, 3, load_strings(Some("en"))).is_some()); }
|
||||||
#[test] fn faillock_three() { assert!(faillock_warning(3, 3, load_strings(Some("en"))).is_none()); }
|
#[test] fn faillock_three() { assert!(faillock_warning(3, 3, load_strings(Some("en"))).is_none()); }
|
||||||
|
|
||||||
@ -150,11 +152,23 @@ mod tests {
|
|||||||
let strings = load_strings(Some("en"));
|
let strings = load_strings(Some("en"));
|
||||||
for count in 0..max {
|
for count in 0..max {
|
||||||
let result = faillock_warning(count, max, strings);
|
let result = faillock_warning(count, max, strings);
|
||||||
if max - count == 1 {
|
let remaining = max - count;
|
||||||
assert!(result.is_some(), "should warn at count={count}");
|
if remaining <= 2 {
|
||||||
|
assert!(result.is_some(), "should warn at count={count} (remaining={remaining})");
|
||||||
} else {
|
} else {
|
||||||
assert!(result.is_none(), "should not warn at count={count}");
|
assert!(result.is_none(), "should not warn at count={count}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn faillock_warns_progressively_with_higher_max() {
|
||||||
|
let strings = load_strings(Some("en"));
|
||||||
|
// With max=5: warn at count 3 (rem=2) and count 4 (rem=1), not at 0-2
|
||||||
|
assert!(faillock_warning(0, 5, strings).is_none());
|
||||||
|
assert!(faillock_warning(2, 5, strings).is_none());
|
||||||
|
assert!(faillock_warning(3, 5, strings).is_some());
|
||||||
|
assert!(faillock_warning(4, 5, strings).is_some());
|
||||||
|
assert!(faillock_warning(5, 5, strings).is_none()); // at max, caller handles lockout
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user