fix: audit findings — wallpaper safety, log filtering, error truncation (v0.4.1)
- Rework load_background_texture(): use resources_lookup_data()/from_bytes() for GResource path (no abort on missing resource), add 50 MB file size limit, handle non-UTF-8 paths gracefully - Filter error details to debug level only — warn! logs without internal details to prevent system info leaking into journal - Make debug logging opt-in via MOONGREET_DEBUG env var (default: Info) - Truncate greetd error description in stale-session retry path using MAX_GREETD_ERROR_LENGTH (matching show_greetd_error()) - Add 3 unit tests for load_background_texture edge cases
This commit is contained in:
+90
-18
@@ -22,6 +22,7 @@ use crate::users::{self, User};
|
||||
|
||||
const AVATAR_SIZE: i32 = 128;
|
||||
const MAX_AVATAR_FILE_SIZE: u64 = 10 * 1024 * 1024;
|
||||
const MAX_WALLPAPER_FILE_SIZE: u64 = 50 * 1024 * 1024;
|
||||
const LAST_USER_PATH: &str = "/var/cache/moongreet/last-user";
|
||||
const LAST_SESSION_DIR: &str = "/var/cache/moongreet/last-session";
|
||||
const MAX_USERNAME_LENGTH: usize = 256;
|
||||
@@ -94,9 +95,49 @@ fn is_valid_username(name: &str) -> bool {
|
||||
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '.' || c == '-')
|
||||
}
|
||||
|
||||
/// Load the background image as a shared texture (decode once, reuse everywhere).
|
||||
pub fn load_background_texture(bg_path: &Path) -> Option<gdk::Texture> {
|
||||
let path_str = bg_path.to_str()?;
|
||||
if bg_path.starts_with("/dev/moonarch/moongreet") {
|
||||
match gio::resources_lookup_data(path_str, gio::ResourceLookupFlags::NONE) {
|
||||
Ok(bytes) => match gdk::Texture::from_bytes(&bytes) {
|
||||
Ok(texture) => Some(texture),
|
||||
Err(e) => {
|
||||
log::debug!("GResource texture decode error: {e}");
|
||||
log::warn!("Failed to decode background texture from GResource {path_str}");
|
||||
None
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
log::debug!("GResource lookup error: {e}");
|
||||
log::warn!("Failed to load background texture from GResource {path_str}");
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
match gdk::Texture::from_filename(bg_path) {
|
||||
Ok(texture) => Some(texture),
|
||||
Err(e) => {
|
||||
log::debug!("Wallpaper load error: {e}");
|
||||
log::warn!("Failed to load background texture from {}", bg_path.display());
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a wallpaper-only window for secondary monitors.
|
||||
pub fn create_wallpaper_window(
|
||||
bg_path: &Path,
|
||||
texture: &gdk::Texture,
|
||||
app: >k::Application,
|
||||
) -> gtk::ApplicationWindow {
|
||||
let window = gtk::ApplicationWindow::builder()
|
||||
@@ -104,19 +145,15 @@ pub fn create_wallpaper_window(
|
||||
.build();
|
||||
window.add_css_class("wallpaper");
|
||||
|
||||
let background = create_background_picture(bg_path);
|
||||
let background = create_background_picture(texture);
|
||||
window.set_child(Some(&background));
|
||||
|
||||
window
|
||||
}
|
||||
|
||||
/// Create a Picture widget for the wallpaper background.
|
||||
fn create_background_picture(bg_path: &Path) -> gtk::Picture {
|
||||
let background = if bg_path.starts_with("/dev/moonarch/moongreet") {
|
||||
gtk::Picture::for_resource(bg_path.to_str().unwrap_or(""))
|
||||
} else {
|
||||
gtk::Picture::for_filename(bg_path.to_str().unwrap_or(""))
|
||||
};
|
||||
/// Create a Picture widget for the wallpaper background from a pre-loaded texture.
|
||||
fn create_background_picture(texture: &gdk::Texture) -> gtk::Picture {
|
||||
let background = gtk::Picture::for_paintable(texture);
|
||||
background.set_content_fit(gtk::ContentFit::Cover);
|
||||
background.set_hexpand(true);
|
||||
background.set_vexpand(true);
|
||||
@@ -135,7 +172,7 @@ struct GreeterState {
|
||||
|
||||
/// Create the main greeter window with login UI.
|
||||
pub fn create_greeter_window(
|
||||
bg_path: &Path,
|
||||
texture: Option<&gdk::Texture>,
|
||||
config: &Config,
|
||||
app: >k::Application,
|
||||
) -> gtk::ApplicationWindow {
|
||||
@@ -182,7 +219,9 @@ pub fn create_greeter_window(
|
||||
window.set_child(Some(&overlay));
|
||||
|
||||
// Background wallpaper
|
||||
overlay.set_child(Some(&create_background_picture(bg_path)));
|
||||
if let Some(texture) = texture {
|
||||
overlay.set_child(Some(&create_background_picture(texture)));
|
||||
}
|
||||
|
||||
// Main layout: 3 rows (top spacer, center login, bottom bar)
|
||||
let main_box = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
||||
@@ -934,13 +973,16 @@ fn login_worker(
|
||||
return Ok(LoginResult::Cancelled);
|
||||
}
|
||||
if response.get("type").and_then(|v| v.as_str()) == Some("error") {
|
||||
return Ok(LoginResult::Error {
|
||||
message: response
|
||||
.get("description")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or(strings.auth_failed)
|
||||
.to_string(),
|
||||
});
|
||||
let description = response
|
||||
.get("description")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("");
|
||||
let message = if !description.is_empty() && description.len() <= MAX_GREETD_ERROR_LENGTH {
|
||||
description.to_string()
|
||||
} else {
|
||||
strings.auth_failed.to_string()
|
||||
};
|
||||
return Ok(LoginResult::Error { message });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1581,4 +1623,34 @@ mod tests {
|
||||
assert!(matches!(result, LoginResult::Success { .. }));
|
||||
handle.join().unwrap();
|
||||
}
|
||||
|
||||
// -- load_background_texture tests --
|
||||
|
||||
#[test]
|
||||
fn load_background_texture_missing_file_returns_none() {
|
||||
let result = load_background_texture(Path::new("/nonexistent/wallpaper.jpg"));
|
||||
assert!(result.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_background_texture_oversized_file_returns_none() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let path = dir.path().join("huge.jpg");
|
||||
// Create a sparse file that exceeds MAX_WALLPAPER_FILE_SIZE
|
||||
let f = std::fs::File::create(&path).unwrap();
|
||||
f.set_len(MAX_WALLPAPER_FILE_SIZE + 1).unwrap();
|
||||
let result = load_background_texture(&path);
|
||||
assert!(result.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_background_texture_non_utf8_path_returns_none() {
|
||||
use std::ffi::OsStr;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
// 0xFF is not valid UTF-8
|
||||
let non_utf8 = OsStr::from_bytes(&[0xff, 0xfe, 0xfd]);
|
||||
let path = Path::new(non_utf8);
|
||||
let result = load_background_texture(path);
|
||||
assert!(result.is_none());
|
||||
}
|
||||
}
|
||||
|
||||
+17
-4
@@ -54,18 +54,26 @@ fn activate(app: >k::Application) {
|
||||
let bg_path = config::resolve_background_path(&config);
|
||||
log::debug!("Background path: {}", bg_path.display());
|
||||
|
||||
// Load background texture once — shared across all windows
|
||||
let bg_texture = greeter::load_background_texture(&bg_path);
|
||||
if bg_texture.is_none() {
|
||||
log::error!("Failed to load background texture — greeter will start without wallpaper");
|
||||
}
|
||||
|
||||
let use_layer_shell = std::env::var("MOONGREET_NO_LAYER_SHELL").is_err();
|
||||
log::debug!("Layer shell: {use_layer_shell}");
|
||||
|
||||
// Main greeter window (login UI) — compositor picks focused monitor
|
||||
let greeter_window = greeter::create_greeter_window(&bg_path, &config, app);
|
||||
let greeter_window = greeter::create_greeter_window(bg_texture.as_ref(), &config, app);
|
||||
if use_layer_shell {
|
||||
setup_layer_shell(&greeter_window, true, gtk4_layer_shell::Layer::Top);
|
||||
}
|
||||
greeter_window.present();
|
||||
|
||||
// Wallpaper-only windows on all monitors (only with layer shell)
|
||||
if use_layer_shell {
|
||||
if use_layer_shell
|
||||
&& let Some(ref texture) = bg_texture
|
||||
{
|
||||
let monitors = display.monitors();
|
||||
log::debug!("Monitor count: {}", monitors.n_items());
|
||||
for i in 0..monitors.n_items() {
|
||||
@@ -73,7 +81,7 @@ fn activate(app: >k::Application) {
|
||||
.item(i)
|
||||
.and_then(|obj| obj.downcast::<gdk::Monitor>().ok())
|
||||
{
|
||||
let wallpaper = greeter::create_wallpaper_window(&bg_path, app);
|
||||
let wallpaper = greeter::create_wallpaper_window(texture, app);
|
||||
setup_layer_shell(&wallpaper, false, gtk4_layer_shell::Layer::Bottom);
|
||||
wallpaper.set_monitor(Some(&monitor));
|
||||
wallpaper.present();
|
||||
@@ -87,7 +95,12 @@ fn setup_logging() {
|
||||
.unwrap()
|
||||
.install()
|
||||
.unwrap();
|
||||
log::set_max_level(log::LevelFilter::Debug);
|
||||
let level = if std::env::var("MOONGREET_DEBUG").is_ok() {
|
||||
log::LevelFilter::Debug
|
||||
} else {
|
||||
log::LevelFilter::Info
|
||||
};
|
||||
log::set_max_level(level);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
||||
Reference in New Issue
Block a user