Fix 29 audit findings across all severity tiers
Critical: fix unsquashfs arg order, quote Exec paths with spaces, fix compare_versions antisymmetry, chunk-based signature detection, bounded ELF header reads. High: handle NULL CVE severity, prevent pipe deadlock in inspector, fix glob_match edge case, fix backup archive path collisions, async crash detection with stderr capture. Medium: gate scan on auto-scan setting, fix window size persistence, fix announce() for Stack containers, claim lightbox gesture, use serde_json for CLI output, remove dead CSS @media blocks, add detail-tab persistence, remove invalid metainfo categories, byte-level fuse signature search. Low: tighten Wayland env var detection, ELF magic validation, timeout for update info extraction, quoted arg parsing, stop watcher timer on window destroy, GSettings choices/range constraints, remove unused CSS classes, define status-ok/status-attention CSS.
This commit is contained in:
@@ -370,27 +370,51 @@ fn parse_elf32_sections(data: &[u8]) -> Option<String> {
|
||||
}
|
||||
|
||||
/// Fallback: run the AppImage with --appimage-updateinformation flag.
|
||||
/// Uses a 5-second timeout to avoid hanging on apps with custom AppRun scripts.
|
||||
fn extract_update_info_runtime(path: &Path) -> Option<String> {
|
||||
let output = std::process::Command::new(path)
|
||||
let mut child = std::process::Command::new(path)
|
||||
.arg("--appimage-updateinformation")
|
||||
.env("APPIMAGE_EXTRACT_AND_RUN", "1")
|
||||
.output()
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::null())
|
||||
.spawn()
|
||||
.ok()?;
|
||||
|
||||
if output.status.success() {
|
||||
let info = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||
if !info.is_empty() && info.contains('|') {
|
||||
return Some(info);
|
||||
let timeout = std::time::Duration::from_secs(5);
|
||||
let start = std::time::Instant::now();
|
||||
loop {
|
||||
match child.try_wait() {
|
||||
Ok(Some(status)) => {
|
||||
if status.success() {
|
||||
let mut output = String::new();
|
||||
if let Some(mut stdout) = child.stdout.take() {
|
||||
use std::io::Read;
|
||||
stdout.read_to_string(&mut output).ok()?;
|
||||
}
|
||||
let info = output.trim().to_string();
|
||||
if !info.is_empty() && info.contains('|') {
|
||||
return Some(info);
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
Ok(None) => {
|
||||
if start.elapsed() >= timeout {
|
||||
let _ = child.kill();
|
||||
let _ = child.wait();
|
||||
log::warn!("Timed out reading update info from {}", path.display());
|
||||
return None;
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
}
|
||||
Err(_) => return None,
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
// -- GitHub/GitLab API types for JSON deserialization --
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[allow(dead_code)]
|
||||
struct GhRelease {
|
||||
tag_name: String,
|
||||
name: Option<String>,
|
||||
@@ -406,7 +430,6 @@ struct GhAsset {
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[allow(dead_code)]
|
||||
struct GlRelease {
|
||||
tag_name: String,
|
||||
name: Option<String>,
|
||||
@@ -492,6 +515,12 @@ fn check_github_release(
|
||||
|
||||
let release: GhRelease = response.body_mut().read_json().ok()?;
|
||||
|
||||
log::info!(
|
||||
"GitHub release: tag={}, name={:?}",
|
||||
release.tag_name,
|
||||
release.name.as_deref().unwrap_or("(none)"),
|
||||
);
|
||||
|
||||
let latest_version = clean_version(&release.tag_name);
|
||||
|
||||
// Find matching asset using glob-like pattern
|
||||
@@ -549,6 +578,12 @@ fn check_gitlab_release(
|
||||
|
||||
let release: GlRelease = response.body_mut().read_json().ok()?;
|
||||
|
||||
log::info!(
|
||||
"GitLab release: tag={}, name={:?}",
|
||||
release.tag_name,
|
||||
release.name.as_deref().unwrap_or("(none)"),
|
||||
);
|
||||
|
||||
let latest_version = clean_version(&release.tag_name);
|
||||
|
||||
let download_url = release.assets.and_then(|assets| {
|
||||
@@ -669,18 +704,24 @@ fn glob_match(pattern: &str, text: &str) -> bool {
|
||||
|
||||
// Last part must match at the end (unless pattern ends with *)
|
||||
let last = parts[parts.len() - 1];
|
||||
if !last.is_empty() {
|
||||
let end_limit = if !last.is_empty() {
|
||||
if !text.ends_with(last) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
text.len() - last.len()
|
||||
} else {
|
||||
text.len()
|
||||
};
|
||||
|
||||
// Middle parts must appear in order
|
||||
// Middle parts must appear in order within the allowed range
|
||||
for part in &parts[1..parts.len() - 1] {
|
||||
if part.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if let Some(found) = text[pos..].find(part) {
|
||||
if pos >= end_limit {
|
||||
return false;
|
||||
}
|
||||
if let Some(found) = text[pos..end_limit].find(part) {
|
||||
pos += found + part.len();
|
||||
} else {
|
||||
return false;
|
||||
@@ -691,7 +732,7 @@ fn glob_match(pattern: &str, text: &str) -> bool {
|
||||
}
|
||||
|
||||
/// Clean a version string - strip leading 'v' or 'V' prefix.
|
||||
fn clean_version(version: &str) -> String {
|
||||
pub(crate) fn clean_version(version: &str) -> String {
|
||||
let v = version.trim();
|
||||
v.strip_prefix('v')
|
||||
.or_else(|| v.strip_prefix('V'))
|
||||
@@ -949,24 +990,25 @@ fn download_file(
|
||||
|
||||
/// Verify that a file is a valid AppImage (has ELF header + AppImage magic bytes).
|
||||
fn verify_appimage(path: &Path) -> bool {
|
||||
if let Ok(data) = fs::read(path) {
|
||||
if data.len() < 12 {
|
||||
return false;
|
||||
}
|
||||
// Check ELF magic
|
||||
if &data[0..4] != b"\x7FELF" {
|
||||
return false;
|
||||
}
|
||||
// Check AppImage Type 2 magic at offset 8: AI\x02
|
||||
if data[8] == 0x41 && data[9] == 0x49 && data[10] == 0x02 {
|
||||
return true;
|
||||
}
|
||||
// Check AppImage Type 1 magic at offset 8: AI\x01
|
||||
if data[8] == 0x41 && data[9] == 0x49 && data[10] == 0x01 {
|
||||
return true;
|
||||
}
|
||||
use std::io::Read;
|
||||
let mut file = match fs::File::open(path) {
|
||||
Ok(f) => f,
|
||||
Err(_) => return false,
|
||||
};
|
||||
let mut header = [0u8; 12];
|
||||
if file.read_exact(&mut header).is_err() {
|
||||
return false;
|
||||
}
|
||||
false
|
||||
// Check ELF magic
|
||||
if &header[0..4] != b"\x7FELF" {
|
||||
return false;
|
||||
}
|
||||
// Check AppImage Type 2 magic at offset 8: AI\x02
|
||||
if header[8] == 0x41 && header[9] == 0x49 && header[10] == 0x02 {
|
||||
return true;
|
||||
}
|
||||
// Check AppImage Type 1 magic at offset 8: AI\x01
|
||||
header[8] == 0x41 && header[9] == 0x49 && header[10] == 0x01
|
||||
}
|
||||
|
||||
/// Perform an update using the best available method.
|
||||
|
||||
Reference in New Issue
Block a user