fix: address audit findings — security, performance, and correctness
- 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%)
This commit is contained in:
parent
2d1d364270
commit
496a7a4c72
@ -2,7 +2,7 @@
|
||||
<gresources>
|
||||
<gresource prefix="/dev/moonarch/moonset">
|
||||
<file>style.css</file>
|
||||
<file>wallpaper.jpg</file>
|
||||
<file compressed="true">wallpaper.jpg</file>
|
||||
<file>default-avatar.svg</file>
|
||||
</gresource>
|
||||
</gresources>
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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<PathBuf> {
|
||||
@ -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)]
|
||||
|
||||
@ -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,
|
||||
|
||||
19
src/panel.rs
19
src/panel.rs
@ -81,12 +81,15 @@ pub fn action_definitions() -> Vec<ActionDef> {
|
||||
|
||||
/// 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();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
67
src/power.rs
67
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)]
|
||||
|
||||
@ -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)]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user