From 496a7a4c72a3207215b43f542afb0e960971efd8 Mon Sep 17 00:00:00 2001 From: nevaforget Date: Sat, 28 Mar 2026 10:13:18 +0100 Subject: [PATCH] =?UTF-8?q?fix:=20address=20audit=20findings=20=E2=80=94?= =?UTF-8?q?=20security,=20performance,=20and=20correctness?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Use absolute paths for all binaries in power.rs to prevent PATH hijacking - Implement POWER_TIMEOUT via try_wait() polling (was declared but unused) - Fix potential panic in load_background_texture when GResource path fails to_str() — now falls back to known wallpaper resource path - Compress wallpaper.jpg in GResource bundle (saves ~374 KB in binary) - Merge double idle_add_local_once into single cycle for faster focus - Centralize GRESOURCE_PREFIX as pub(crate) const in main.rs - Fix fallback user UID from 0 (root) to u32::MAX - Fix CSS comment: "square card" → "circular card" (border-radius: 50%) --- resources/resources.gresource.xml | 2 +- resources/style.css | 2 +- src/config.rs | 4 +- src/main.rs | 4 +- src/panel.rs | 19 ++++----- src/power.rs | 67 +++++++++++++++++++------------ src/users.rs | 4 +- 7 files changed, 61 insertions(+), 41 deletions(-) diff --git a/resources/resources.gresource.xml b/resources/resources.gresource.xml index c379862..35a93a7 100644 --- a/resources/resources.gresource.xml +++ b/resources/resources.gresource.xml @@ -2,7 +2,7 @@ style.css - wallpaper.jpg + wallpaper.jpg default-avatar.svg diff --git a/resources/style.css b/resources/style.css index fd9283b..574e4d7 100644 --- a/resources/style.css +++ b/resources/style.css @@ -31,7 +31,7 @@ window.wallpaper { margin-bottom: 40px; } -/* Action button — square card */ +/* Action button — circular card */ .action-button { min-width: 120px; min-height: 120px; diff --git a/src/config.rs b/src/config.rs index 87b0732..d3138a2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -6,7 +6,6 @@ use std::fs; use std::path::{Path, PathBuf}; const MOONARCH_WALLPAPER: &str = "/usr/share/moonarch/wallpaper.jpg"; -const GRESOURCE_PREFIX: &str = "/dev/moonarch/moonset"; /// Default config search paths: system-wide, then user-specific. fn default_config_paths() -> Vec { @@ -65,7 +64,8 @@ pub fn resolve_background_path_with(config: &Config, moonarch_wallpaper: &Path) } // GResource fallback path (loaded from compiled resources at runtime) - PathBuf::from(format!("{GRESOURCE_PREFIX}/wallpaper.jpg")) + let prefix = crate::GRESOURCE_PREFIX; + PathBuf::from(format!("{prefix}/wallpaper.jpg")) } #[cfg(test)] diff --git a/src/main.rs b/src/main.rs index e12217a..9a972a8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,9 +12,11 @@ use gtk4::prelude::*; use gtk4::{self as gtk, gio}; use gtk4_layer_shell::LayerShell; +pub(crate) const GRESOURCE_PREFIX: &str = "/dev/moonarch/moonset"; + fn load_css(display: &gdk::Display) { let css_provider = gtk::CssProvider::new(); - css_provider.load_from_resource("/dev/moonarch/moonset/style.css"); + css_provider.load_from_resource(&format!("{GRESOURCE_PREFIX}/style.css")); gtk::style_context_add_provider_for_display( display, &css_provider, diff --git a/src/panel.rs b/src/panel.rs index 3d262fa..0510689 100644 --- a/src/panel.rs +++ b/src/panel.rs @@ -81,12 +81,15 @@ pub fn action_definitions() -> Vec { /// Load the wallpaper as a texture once, for sharing across all windows. pub fn load_background_texture(bg_path: &Path) -> gdk::Texture { - if bg_path.starts_with("/dev/moonarch/moonset") { - gdk::Texture::from_resource(bg_path.to_str().unwrap_or("")) + let fallback = format!("{}/wallpaper.jpg", crate::GRESOURCE_PREFIX); + + if bg_path.starts_with(crate::GRESOURCE_PREFIX) { + let resource_path = bg_path.to_str().unwrap_or(&fallback); + gdk::Texture::from_resource(resource_path) } else { let file = gio::File::for_path(bg_path); gdk::Texture::from_file(&file).unwrap_or_else(|_| { - gdk::Texture::from_resource("/dev/moonarch/moonset/wallpaper.jpg") + gdk::Texture::from_resource(&fallback) }) } } @@ -127,7 +130,7 @@ pub fn create_panel_window(texture: &gdk::Texture, app: >k::Application) -> gt username: "user".to_string(), display_name: "User".to_string(), home: dirs::home_dir().unwrap_or_default(), - uid: 0, + uid: u32::MAX, }); // State for confirm box @@ -233,11 +236,9 @@ pub fn create_panel_window(texture: &gdk::Texture, app: >k::Application) -> gt let bb = button_box_clone.clone(); glib::idle_add_local_once(move || { w.add_css_class("visible"); - glib::idle_add_local_once(move || { - if let Some(first) = bb.first_child() { - first.grab_focus(); - } - }); + if let Some(first) = bb.first_child() { + first.grab_focus(); + } }); }); diff --git a/src/power.rs b/src/power.rs index f147959..a50bff6 100644 --- a/src/power.rs +++ b/src/power.rs @@ -2,16 +2,15 @@ // ABOUTME: Wrappers around system commands for the session power menu. use std::fmt; -use std::process::Command; -use std::time::Duration; +use std::io::Read; +use std::process::{Command, Stdio}; +use std::time::{Duration, Instant}; -#[allow(dead_code)] const POWER_TIMEOUT: Duration = Duration::from_secs(30); #[derive(Debug)] pub enum PowerError { CommandFailed { action: &'static str, message: String }, - #[allow(dead_code)] Timeout { action: &'static str }, } @@ -32,55 +31,73 @@ impl std::error::Error for PowerError {} /// Run a command with timeout and return a PowerError on failure. fn run_command(action: &'static str, program: &str, args: &[&str]) -> Result<(), PowerError> { - let child = Command::new(program) + let mut child = Command::new(program) .args(args) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) .spawn() .map_err(|e| PowerError::CommandFailed { action, message: e.to_string(), })?; - let output = child - .wait_with_output() - .map_err(|e| PowerError::CommandFailed { - action, - message: e.to_string(), - })?; - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - return Err(PowerError::CommandFailed { - action, - message: format!("exit code {}: {}", output.status, stderr.trim()), - }); + let deadline = Instant::now() + POWER_TIMEOUT; + loop { + match child.try_wait() { + Ok(Some(status)) => { + if !status.success() { + let mut stderr_buf = String::new(); + if let Some(mut stderr) = child.stderr.take() { + let _ = stderr.read_to_string(&mut stderr_buf); + } + return Err(PowerError::CommandFailed { + action, + message: format!("exit code {}: {}", status, stderr_buf.trim()), + }); + } + return Ok(()); + } + Ok(None) => { + if Instant::now() >= deadline { + let _ = child.kill(); + let _ = child.wait(); + return Err(PowerError::Timeout { action }); + } + std::thread::sleep(Duration::from_millis(100)); + } + Err(e) => { + return Err(PowerError::CommandFailed { + action, + message: e.to_string(), + }); + } + } } - - Ok(()) } /// Lock the current session by launching moonlock. pub fn lock() -> Result<(), PowerError> { - run_command("lock", "moonlock", &[]) + run_command("lock", "/usr/bin/moonlock", &[]) } /// Quit the Niri compositor (logout). pub fn logout() -> Result<(), PowerError> { - run_command("logout", "niri", &["msg", "action", "quit"]) + run_command("logout", "/usr/bin/niri", &["msg", "action", "quit"]) } /// Hibernate the system via systemctl. pub fn hibernate() -> Result<(), PowerError> { - run_command("hibernate", "systemctl", &["hibernate"]) + run_command("hibernate", "/usr/bin/systemctl", &["hibernate"]) } /// Reboot the system via loginctl. pub fn reboot() -> Result<(), PowerError> { - run_command("reboot", "loginctl", &["reboot"]) + run_command("reboot", "/usr/bin/loginctl", &["reboot"]) } /// Shut down the system via loginctl. pub fn shutdown() -> Result<(), PowerError> { - run_command("shutdown", "loginctl", &["poweroff"]) + run_command("shutdown", "/usr/bin/loginctl", &["poweroff"]) } #[cfg(test)] diff --git a/src/users.rs b/src/users.rs index 7451907..f853ced 100644 --- a/src/users.rs +++ b/src/users.rs @@ -5,7 +5,6 @@ use nix::unistd::{getuid, User as NixUser}; use std::path::{Path, PathBuf}; const DEFAULT_ACCOUNTSSERVICE_DIR: &str = "/var/lib/AccountsService/icons"; -const GRESOURCE_PREFIX: &str = "/dev/moonarch/moonset"; /// Represents the current user for the power menu. #[derive(Debug, Clone)] @@ -74,7 +73,8 @@ pub fn get_avatar_path_with( /// Return the GResource path to the default avatar SVG. pub fn get_default_avatar_path() -> String { - format!("{GRESOURCE_PREFIX}/default-avatar.svg") + let prefix = crate::GRESOURCE_PREFIX; + format!("{prefix}/default-avatar.svg") } #[cfg(test)]