fix: audit fixes — D-Bus sender validation, fp lifecycle, multi-monitor caching (v0.6.0)
Close the only exploitable auth bypass: validate VerifyStatus signal sender against fprintd's unique bus name. Fix fingerprint D-Bus lifecycle so devices are properly released on verify-match and async restarts check the running flag between awaits. Security: num_msg guard in PAM callback, symlink rejection for background_path, peek icon disabled, TOML parse errors logged, panic hook before logging. Performance: blur and avatar textures cached across monitors, release profile with LTO/strip.
This commit is contained in:
+50
-12
@@ -41,11 +41,15 @@ struct LockscreenState {
|
||||
|
||||
/// Create a lockscreen window for a single monitor.
|
||||
/// Fingerprint is not initialized here — use `wire_fingerprint()` after async init.
|
||||
/// The `blur_cache` and `avatar_cache` are shared across monitors for multi-monitor
|
||||
/// setups, avoiding redundant GPU renders and SVG rasterizations.
|
||||
pub fn create_lockscreen_window(
|
||||
bg_texture: &gdk::Texture,
|
||||
config: &Config,
|
||||
app: >k::Application,
|
||||
unlock_callback: Rc<dyn Fn()>,
|
||||
blur_cache: &Rc<RefCell<Option<gdk::Texture>>>,
|
||||
avatar_cache: &Rc<RefCell<Option<gdk::Texture>>>,
|
||||
) -> LockscreenHandles {
|
||||
let window = gtk::ApplicationWindow::builder()
|
||||
.application(app)
|
||||
@@ -83,7 +87,7 @@ pub fn create_lockscreen_window(
|
||||
window.set_child(Some(&overlay));
|
||||
|
||||
// Background wallpaper
|
||||
let background = create_background_picture(bg_texture, config.background_blur);
|
||||
let background = create_background_picture(bg_texture, config.background_blur, blur_cache);
|
||||
overlay.set_child(Some(&background));
|
||||
|
||||
// Centered vertical box
|
||||
@@ -109,12 +113,17 @@ pub fn create_lockscreen_window(
|
||||
avatar_frame.append(&avatar_image);
|
||||
login_box.append(&avatar_frame);
|
||||
|
||||
// Load avatar
|
||||
let avatar_path = users::get_avatar_path(&user.home, &user.username);
|
||||
if let Some(path) = avatar_path {
|
||||
set_avatar_from_file(&avatar_image, &path);
|
||||
// Load avatar — use shared cache to avoid redundant loading on multi-monitor setups.
|
||||
// The cache is populated by the first monitor and reused by subsequent ones.
|
||||
if let Some(ref cached) = *avatar_cache.borrow() {
|
||||
avatar_image.set_paintable(Some(cached));
|
||||
} else {
|
||||
set_default_avatar(&avatar_image, &window);
|
||||
let avatar_path = users::get_avatar_path(&user.home, &user.username);
|
||||
if let Some(path) = avatar_path {
|
||||
set_avatar_from_file(&avatar_image, &path, avatar_cache);
|
||||
} else {
|
||||
set_default_avatar(&avatar_image, &window, avatar_cache);
|
||||
}
|
||||
}
|
||||
|
||||
// Username label
|
||||
@@ -125,7 +134,7 @@ pub fn create_lockscreen_window(
|
||||
// Password entry
|
||||
let password_entry = gtk::PasswordEntry::builder()
|
||||
.placeholder_text(strings.password_placeholder)
|
||||
.show_peek_icon(true)
|
||||
.show_peek_icon(false)
|
||||
.hexpand(true)
|
||||
.build();
|
||||
password_entry.add_css_class("password-entry");
|
||||
@@ -361,12 +370,18 @@ pub fn start_fingerprint(
|
||||
let fp_label_fail = handles.fp_label.clone();
|
||||
let unlock_cb_fp = handles.unlock_callback.clone();
|
||||
|
||||
let fp_rc_success = fp_rc.clone();
|
||||
let on_success = move || {
|
||||
let label = fp_label_success.clone();
|
||||
let cb = unlock_cb_fp.clone();
|
||||
let fp = fp_rc_success.clone();
|
||||
glib::idle_add_local_once(move || {
|
||||
label.set_text(load_strings(None).fingerprint_success);
|
||||
let strings = load_strings(None);
|
||||
label.set_text(strings.fingerprint_success);
|
||||
label.add_css_class("success");
|
||||
// stop() is idempotent — cleanup_dbus() already ran inside on_verify_status,
|
||||
// but this mirrors the PAM success path for defense-in-depth.
|
||||
fp.borrow_mut().stop();
|
||||
cb();
|
||||
});
|
||||
};
|
||||
@@ -434,7 +449,13 @@ pub fn load_background_texture(bg_path: &Path) -> gdk::Texture {
|
||||
/// When `blur_radius` is `Some(sigma)` with sigma > 0, blur is applied via GPU
|
||||
/// (GskBlurNode). The blur is rendered to a concrete texture on `realize` (when
|
||||
/// the GPU renderer is available), avoiding lazy-render artifacts.
|
||||
fn create_background_picture(texture: &gdk::Texture, blur_radius: Option<f32>) -> gtk::Picture {
|
||||
/// The `blur_cache` is shared across monitors — the first to realize renders the
|
||||
/// blur, subsequent monitors reuse the cached texture.
|
||||
fn create_background_picture(
|
||||
texture: &gdk::Texture,
|
||||
blur_radius: Option<f32>,
|
||||
blur_cache: &Rc<RefCell<Option<gdk::Texture>>>,
|
||||
) -> gtk::Picture {
|
||||
let background = gtk::Picture::for_paintable(texture);
|
||||
background.set_content_fit(gtk::ContentFit::Cover);
|
||||
background.set_hexpand(true);
|
||||
@@ -443,9 +464,15 @@ fn create_background_picture(texture: &gdk::Texture, blur_radius: Option<f32>) -
|
||||
if let Some(sigma) = blur_radius {
|
||||
if sigma > 0.0 {
|
||||
let texture = texture.clone();
|
||||
let cache = blur_cache.clone();
|
||||
background.connect_realize(move |picture| {
|
||||
if let Some(ref cached) = *cache.borrow() {
|
||||
picture.set_paintable(Some(cached));
|
||||
return;
|
||||
}
|
||||
if let Some(blurred) = render_blurred_texture(picture, &texture, sigma) {
|
||||
picture.set_paintable(Some(&blurred));
|
||||
*cache.borrow_mut() = Some(blurred);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -477,12 +504,17 @@ fn render_blurred_texture(
|
||||
Some(renderer.render_texture(&node, None))
|
||||
}
|
||||
|
||||
/// Load an image file and set it as the avatar.
|
||||
fn set_avatar_from_file(image: >k::Image, path: &Path) {
|
||||
/// Load an image file and set it as the avatar. Stores the texture in the cache.
|
||||
fn set_avatar_from_file(
|
||||
image: >k::Image,
|
||||
path: &Path,
|
||||
cache: &Rc<RefCell<Option<gdk::Texture>>>,
|
||||
) {
|
||||
match Pixbuf::from_file_at_scale(path.to_str().unwrap_or(""), AVATAR_SIZE, AVATAR_SIZE, true) {
|
||||
Ok(pixbuf) => {
|
||||
let texture = gdk::Texture::for_pixbuf(&pixbuf);
|
||||
image.set_paintable(Some(&texture));
|
||||
*cache.borrow_mut() = Some(texture);
|
||||
}
|
||||
Err(_) => {
|
||||
image.set_icon_name(Some("avatar-default-symbolic"));
|
||||
@@ -491,7 +523,12 @@ fn set_avatar_from_file(image: >k::Image, path: &Path) {
|
||||
}
|
||||
|
||||
/// Load the default avatar SVG from GResources, tinted with the foreground color.
|
||||
fn set_default_avatar(image: >k::Image, window: >k::ApplicationWindow) {
|
||||
/// Stores the texture in the cache for reuse on additional monitors.
|
||||
fn set_default_avatar(
|
||||
image: >k::Image,
|
||||
window: >k::ApplicationWindow,
|
||||
cache: &Rc<RefCell<Option<gdk::Texture>>>,
|
||||
) {
|
||||
let resource_path = users::get_default_avatar_path();
|
||||
if let Ok(bytes) =
|
||||
gio::resources_lookup_data(&resource_path, gio::ResourceLookupFlags::NONE)
|
||||
@@ -513,6 +550,7 @@ fn set_default_avatar(image: >k::Image, window: >k::ApplicationWindow) {
|
||||
if let Some(pixbuf) = loader.pixbuf() {
|
||||
let texture = gdk::Texture::for_pixbuf(&pixbuf);
|
||||
image.set_paintable(Some(&texture));
|
||||
*cache.borrow_mut() = Some(texture);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user