fix: audit fixes — password zeroize, blur downscale, symlink hardening, error filtering (v0.7.0)
Update PKGBUILD version / update-pkgver (push) Successful in 2s

- Add zeroize dependency, wrap password in Zeroizing<String> from entry extraction
  through to login_worker (prevents heap-resident plaintext)
- Add MAX_BLUR_DIMENSION (1920px) downscale before GPU blur to reduce 4K workload
- Wallpaper: use symlink_metadata + is_symlink rejection in greeter.rs and config.rs
- Avatar: add is_file() check, swap lookup order to ~/.face first (consistent with
  moonlock/moonset)
- greetd errors: show generic fallback in UI, log raw PAM details at debug level only
- fprintd: validate device path prefix before creating D-Bus proxy
- Locale: cache detected locale via OnceLock (avoid repeated env/file reads)
This commit is contained in:
2026-03-30 16:03:04 +02:00
parent a2dc89854d
commit 1d557ea135
7 changed files with 102 additions and 49 deletions
+57 -23
View File
@@ -12,6 +12,7 @@ use std::os::unix::net::UnixStream;
use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::sync::{Arc, Mutex};
use zeroize::Zeroizing;
use crate::config::Config;
use crate::i18n::{faillock_warning, load_strings, Strings};
@@ -107,14 +108,18 @@ fn is_valid_gtk_theme(name: &str) -> bool {
/// Load background texture from filesystem.
pub fn load_background_texture(bg_path: &Path) -> Option<gdk::Texture> {
if let Ok(meta) = std::fs::metadata(bg_path)
&& meta.len() > MAX_WALLPAPER_FILE_SIZE
{
log::warn!(
"Wallpaper file too large ({} bytes), skipping: {}",
meta.len(), bg_path.display()
);
return None;
if let Ok(meta) = std::fs::symlink_metadata(bg_path) {
if meta.file_type().is_symlink() {
log::warn!("Rejecting symlink wallpaper: {}", bg_path.display());
return None;
}
if meta.len() > MAX_WALLPAPER_FILE_SIZE {
log::warn!(
"Wallpaper file too large ({} bytes), skipping: {}",
meta.len(), bg_path.display()
);
return None;
}
}
match gdk::Texture::from_filename(bg_path) {
Ok(texture) => Some(texture),
@@ -128,11 +133,18 @@ pub fn load_background_texture(bg_path: &Path) -> Option<gdk::Texture> {
// -- GPU blur via GskBlurNode -------------------------------------------------
/// Maximum texture dimension before downscaling for blur.
/// Keeps GPU work reasonable on 4K+ displays.
const MAX_BLUR_DIMENSION: f32 = 1920.0;
/// Render a blurred texture using the GPU via GskBlurNode.
///
/// To avoid edge darkening (blur samples transparent pixels outside bounds),
/// the texture is rendered with padding equal to 3x the blur sigma. The blur
/// is applied to the padded area, then cropped back to the original size.
///
/// Large textures (> MAX_BLUR_DIMENSION) are downscaled before blurring to
/// reduce GPU work. The sigma is scaled proportionally.
fn render_blurred_texture(
widget: &impl IsA<gtk::Widget>,
texture: &gdk::Texture,
@@ -141,16 +153,28 @@ fn render_blurred_texture(
let native = widget.native()?;
let renderer = native.renderer()?;
let w = texture.width() as f32;
let h = texture.height() as f32;
let orig_w = texture.width() as f32;
let orig_h = texture.height() as f32;
// Downscale large textures to reduce GPU blur work
let max_dim = orig_w.max(orig_h);
let scale = if max_dim > MAX_BLUR_DIMENSION {
MAX_BLUR_DIMENSION / max_dim
} else {
1.0
};
let w = (orig_w * scale).round();
let h = (orig_h * scale).round();
let scaled_sigma = sigma * scale;
// Padding must cover the blur kernel radius (typically ~3x sigma)
let pad = (sigma * 3.0).ceil();
let pad = (scaled_sigma * 3.0).ceil();
let snapshot = gtk::Snapshot::new();
// Clip output to original texture size
// Clip output to scaled texture size
snapshot.push_clip(&graphene_rs::Rect::new(pad, pad, w, h));
snapshot.push_blur(sigma as f64);
// Render texture at native size, shifted so edge pixels fill the padding area
snapshot.push_blur(scaled_sigma as f64);
// Render texture with padding on all sides (edges repeat via oversized bounds)
snapshot.append_texture(texture, &graphene_rs::Rect::new(-pad, -pad, w + 2.0 * pad, h + 2.0 * pad));
snapshot.pop(); // blur
snapshot.pop(); // clip
@@ -477,7 +501,7 @@ pub fn create_greeter_window(
};
let Some(user) = user else { return };
let password = entry.text().to_string();
let password = Zeroizing::new(entry.text().to_string());
let session = get_selected_session(&session_dropdown, &sessions_rc);
let Some(session) = session else {
@@ -884,15 +908,19 @@ fn extract_greetd_description<'a>(response: &'a serde_json::Value, fallback: &'a
.unwrap_or(fallback)
}
/// Display a greetd error, using a fallback for missing or oversized descriptions.
/// Display a greetd error. Logs raw PAM details at debug level,
/// shows only the generic fallback in the UI to avoid leaking system info.
fn show_greetd_error(
error_label: &gtk::Label,
password_entry: &gtk::PasswordEntry,
response: &serde_json::Value,
fallback: &str,
) {
let message = extract_greetd_description(response, fallback);
show_error(error_label, password_entry, message);
let raw = extract_greetd_description(response, fallback);
if raw != fallback {
log::debug!("greetd error detail: {raw}");
}
show_error(error_label, password_entry, fallback);
}
/// Cancel any in-progress greetd session.
@@ -961,7 +989,7 @@ fn attempt_login(
set_login_sensitive(password_entry, session_dropdown, false);
let username = user.username.clone();
let password = password.to_string();
let password = Zeroizing::new(password.to_string());
let exec_cmd = session.exec_cmd.clone();
let session_name = session.name.clone();
let greetd_sock = state.borrow().greetd_sock.clone();
@@ -1105,8 +1133,11 @@ fn login_worker(
return Ok(LoginResult::Cancelled);
}
if response.get("type").and_then(|v| v.as_str()) == Some("error") {
let message = extract_greetd_description(&response, strings.auth_failed).to_string();
return Ok(LoginResult::Error { message });
let raw = extract_greetd_description(&response, strings.auth_failed);
if raw != strings.auth_failed {
log::debug!("greetd error detail: {raw}");
}
return Ok(LoginResult::Error { message: strings.auth_failed.to_string() });
}
}
@@ -1194,9 +1225,12 @@ fn login_worker(
username: username.to_string(),
});
} else {
let raw = extract_greetd_description(&response, strings.session_start_failed);
if raw != strings.session_start_failed {
log::debug!("greetd error detail: {raw}");
}
return Ok(LoginResult::Error {
message: extract_greetd_description(&response, strings.session_start_failed)
.to_string(),
message: strings.session_start_failed.to_string(),
});
}
}