Fix 29 audit findings across all severity tiers

This commit is contained in:
2026-02-27 22:08:53 +02:00
parent ce11431cdf
commit 804ba35a70
25 changed files with 475 additions and 250 deletions

View File

@@ -42,7 +42,6 @@ pub enum LaunchMethod {
/// Extract-and-run fallback (APPIMAGE_EXTRACT_AND_RUN=1)
ExtractAndRun,
/// Via firejail sandbox
#[allow(dead_code)]
Sandboxed,
}
@@ -68,7 +67,6 @@ pub enum LaunchResult {
Crashed {
exit_code: Option<i32>,
stderr: String,
#[allow(dead_code)]
method: LaunchMethod,
},
/// Failed to launch.
@@ -99,6 +97,22 @@ pub fn launch_appimage(
}
};
// Override with sandboxed launch if the user enabled firejail for this app
let method = if has_firejail() {
let sandbox = db
.get_appimage_by_id(record_id)
.ok()
.flatten()
.and_then(|r| r.sandbox_mode);
if sandbox.as_deref() == Some("firejail") {
LaunchMethod::Sandboxed
} else {
method
}
} else {
method
};
let result = execute_appimage(appimage_path, &method, extra_args, extra_env);
// Record the launch event regardless of success
@@ -163,42 +177,38 @@ fn execute_appimage(
cmd.env(key, value);
}
// Capture stderr to detect crash messages, stdin detached
// Detach stdin, pipe stderr so we can capture crash messages
cmd.stdin(Stdio::null());
cmd.stderr(Stdio::piped());
match cmd.spawn() {
Ok(mut child) => {
// Brief wait to detect immediate crashes (e.g. missing Qt plugins)
std::thread::sleep(std::time::Duration::from_millis(1500));
// Give the process a brief moment to fail on immediate errors
// (missing libs, exec format errors, Qt plugin failures, etc.)
std::thread::sleep(std::time::Duration::from_millis(150));
match child.try_wait() {
Ok(Some(status)) => {
// Process already exited - it crashed
let stderr = child
.stderr
.take()
.and_then(|mut err| {
let mut buf = String::new();
use std::io::Read;
err.read_to_string(&mut buf).ok()?;
Some(buf)
})
.unwrap_or_default();
// Already exited - immediate crash. Read stderr for details.
let stderr_text = child.stderr.take().map(|mut pipe| {
let mut buf = String::new();
use std::io::Read;
// Read with a size cap to avoid huge allocations
let mut limited = (&mut pipe).take(64 * 1024);
let _ = limited.read_to_string(&mut buf);
buf
}).unwrap_or_default();
LaunchResult::Crashed {
exit_code: status.code(),
stderr,
stderr: stderr_text,
method: method.clone(),
}
}
Ok(None) => {
// Still running - success
LaunchResult::Started {
child,
method: method.clone(),
}
}
Err(_) => {
// Can't check status, assume it's running
_ => {
// Still running after 150ms - drop the stderr pipe so the
// child process won't block if it fills the pipe buffer.
drop(child.stderr.take());
LaunchResult::Started {
child,
method: method.clone(),
@@ -211,11 +221,38 @@ fn execute_appimage(
}
/// Parse a launch_args string from the database into a Vec of individual arguments.
/// Splits on whitespace; returns an empty Vec if the input is None or empty.
#[allow(dead_code)]
/// Parse launch arguments with basic quote support.
/// Splits on whitespace, respecting double-quoted strings.
/// Returns an empty Vec if the input is None or empty.
pub fn parse_launch_args(args: Option<&str>) -> Vec<String> {
args.map(|s| s.split_whitespace().map(String::from).collect())
.unwrap_or_default()
let Some(s) = args else {
return Vec::new();
};
let s = s.trim();
if s.is_empty() {
return Vec::new();
}
let mut result = Vec::new();
let mut current = String::new();
let mut in_quotes = false;
for c in s.chars() {
match c {
'"' => in_quotes = !in_quotes,
' ' | '\t' if !in_quotes => {
if !current.is_empty() {
result.push(std::mem::take(&mut current));
}
}
_ => current.push(c),
}
}
if !current.is_empty() {
result.push(current);
}
result
}
/// Check if firejail is available for sandboxed launches.