From eb5b1701c229d78473480692215979647b5d43ec Mon Sep 17 00:00:00 2001 From: pwnpope Date: Sat, 21 Feb 2026 14:01:45 -0500 Subject: [PATCH] Add fallback extractor support for external tools SquashFS extraction silently fails on systems without sasquatch installed, even when the standard unsquashfs tool is available. This adds a general-purpose fallback mechanism to the Extractor struct and uses it to fall back to unsquashfs when sasquatch is not found. Also fixes a pre-existing bug where carved files were not cleaned up when an external extractor command failed to launch. --- src/extractors/common.rs | 101 ++++++++++++++++++++++++++++--------- src/extractors/linux.rs | 1 + src/extractors/squashfs.rs | 11 +++- src/extractors/uefi.rs | 1 + 4 files changed, 88 insertions(+), 26 deletions(-) diff --git a/src/extractors/common.rs b/src/extractors/common.rs index e9fd85ac7..ad0e2a8a7 100644 --- a/src/extractors/common.rs +++ b/src/extractors/common.rs @@ -48,6 +48,8 @@ pub struct Extractor { pub exit_codes: Vec, /// Set to true to disable recursion into this extractor's extracted files pub do_not_recurse: bool, + /// Optional fallback extractor to try if the primary extractor fails + pub fallback: Option>, } /// Stores information about a completed extraction @@ -841,33 +843,31 @@ pub fn execute( result.extractor = format!("{}_built_in", signature.name); } - ExtractorType::External(cmd) => { - // Spawn the external extractor command - match spawn( - file_data, - file_path, - &output_directory, - signature, - extractor_definition.clone(), - ) { - Err(e) => { - error!( - "Failed to spawn external extractor for '{}' signature: {}", - signature.name, e - ); + ExtractorType::External(_) => { + // Try the primary extractor, then any fallbacks if it fails + let mut current_ext = Some(&extractor_definition); + + while let Some(ext) = current_ext { + result = try_external_extract( + file_data, + file_path, + &output_directory, + signature, + ext, + ); + + if result.success { + break; } - Ok(proc_info) => { - // Wait for the external process to exit - match proc_wait(proc_info) { - Err(_) => { - warn!("External extractor failed!"); - } - Ok(ext_result) => { - result = ext_result; - // Set the extractor name to the name of the extraction utility - result.extractor = cmd.to_string(); - } + // Try fallback extractor if available + current_ext = ext.fallback.as_deref(); + if let Some(fallback) = current_ext { + if let ExtractorType::External(fallback_cmd) = &fallback.utility { + info!( + "Trying fallback extractor '{}' for '{}' signature", + fallback_cmd, signature.name + ); } } } @@ -900,6 +900,50 @@ pub fn execute( result } +/// Attempt to run an external extractor, returning the extraction result. +fn try_external_extract( + file_data: &[u8], + file_path: &String, + output_directory: &String, + signature: &SignatureResult, + extractor: &Extractor, +) -> ExtractionResult { + let mut result = ExtractionResult { + ..Default::default() + }; + + if let ExtractorType::External(cmd) = &extractor.utility { + match spawn( + file_data, + file_path, + output_directory, + signature, + extractor.clone(), + ) { + Err(e) => { + error!( + "Failed to spawn external extractor '{}' for '{}' signature: {}", + cmd, signature.name, e + ); + } + + Ok(proc_info) => { + match proc_wait(proc_info) { + Err(_) => { + warn!("External extractor '{}' failed!", cmd); + } + Ok(ext_result) => { + result = ext_result; + result.extractor = cmd.to_string(); + } + } + } + } + } + + result +} + /// Spawn an external extractor process. fn spawn( file_data: &[u8], @@ -971,6 +1015,13 @@ fn spawn( .spawn() { Err(e) => { + // Clean up the carved file since the process failed to start + if let Err(rm_err) = fs::remove_file(&carved_file) { + warn!( + "Failed to clean up carved file '{}': {}", + carved_file, rm_err + ); + } error!( "Failed to execute command {}{:?}: {}", command, extractor.arguments, e diff --git a/src/extractors/linux.rs b/src/extractors/linux.rs index c939fb6b0..ad54874dc 100644 --- a/src/extractors/linux.rs +++ b/src/extractors/linux.rs @@ -13,5 +13,6 @@ pub fn linux_kernel_extractor() -> extractors::common::Extractor { "linux_kernel.elf".to_string(), ], exit_codes: vec![0], + fallback: None, } } diff --git a/src/extractors/squashfs.rs b/src/extractors/squashfs.rs index 4f8c871a6..c65cb3907 100644 --- a/src/extractors/squashfs.rs +++ b/src/extractors/squashfs.rs @@ -1,6 +1,7 @@ use crate::extractors; -/// Describes how to run the sasquatch utility to extract SquashFS images +/// Describes how to run the sasquatch utility to extract SquashFS images, +/// with unsquashfs as a fallback for systems that only have the standard tool. pub fn squashfs_extractor() -> extractors::common::Extractor { extractors::common::Extractor { utility: extractors::common::ExtractorType::External("sasquatch".to_string()), @@ -8,6 +9,14 @@ pub fn squashfs_extractor() -> extractors::common::Extractor { arguments: vec![extractors::common::SOURCE_FILE_PLACEHOLDER.to_string()], // Exit code may be 0 or 2; 2 indicates running as not root, but otherwise extraction is ok exit_codes: vec![0, 2], + // Fall back to unsquashfs if sasquatch is not installed + fallback: Some(Box::new(extractors::common::Extractor { + utility: extractors::common::ExtractorType::External("unsquashfs".to_string()), + extension: "sqsh".to_string(), + arguments: vec![extractors::common::SOURCE_FILE_PLACEHOLDER.to_string()], + exit_codes: vec![0, 2], + ..Default::default() + })), ..Default::default() } } diff --git a/src/extractors/uefi.rs b/src/extractors/uefi.rs index 0c901a8e3..32146a184 100644 --- a/src/extractors/uefi.rs +++ b/src/extractors/uefi.rs @@ -17,5 +17,6 @@ pub fn uefi_extractor() -> extractors::common::Extractor { * Recursing into this data would result in double extractions for no good reason. */ do_not_recurse: true, + fallback: None, } }