Skip to main content

threatflux_binary_analysis/
error.rs

1//! Error types for binary analysis operations
2
3use thiserror::Error;
4
5/// Result type for binary analysis operations
6pub type Result<T> = std::result::Result<T, BinaryError>;
7
8/// Errors that can occur during binary analysis
9#[derive(Error, Debug)]
10#[non_exhaustive]
11pub enum BinaryError {
12    /// Failed to parse binary format
13    #[error("Failed to parse binary format: {0}")]
14    ParseError(String),
15
16    /// Unsupported binary format
17    #[error("Unsupported binary format: {0}")]
18    UnsupportedFormat(String),
19
20    /// Unsupported architecture
21    #[error("Unsupported architecture: {0}")]
22    UnsupportedArchitecture(String),
23
24    /// Invalid binary data
25    #[error("Invalid binary data: {0}")]
26    InvalidData(String),
27
28    /// Input exceeds the configured analysis limit
29    #[error("Input size {actual} bytes exceeds the configured limit of {limit} bytes")]
30    InputTooLarge {
31        /// Size of the supplied input, in bytes
32        actual: usize,
33        /// Configured maximum input size, in bytes
34        limit: usize,
35    },
36
37    /// Disassembly error
38    #[error("Disassembly failed: {0}")]
39    DisassemblyError(String),
40
41    /// Control flow analysis error
42    #[error("Control flow analysis failed: {0}")]
43    ControlFlowError(String),
44
45    /// Symbol resolution error
46    #[error("Symbol resolution failed: {0}")]
47    SymbolError(String),
48
49    /// Entropy analysis error
50    #[error("Entropy analysis failed: {0}")]
51    EntropyError(String),
52
53    /// I/O error
54    #[error("I/O error: {0}")]
55    IoError(#[from] std::io::Error),
56
57    /// Memory mapping error
58    #[error("Memory mapping error: {0}")]
59    MemoryMapError(String),
60
61    /// Configuration error
62    #[error("Configuration error: {0}")]
63    ConfigError(String),
64
65    /// Feature not available
66    #[error("Feature not available: {0} (try enabling the corresponding feature flag)")]
67    FeatureNotAvailable(String),
68
69    /// Internal error
70    #[error("Internal error: {0}")]
71    Internal(String),
72}
73
74#[cfg(any(feature = "elf", feature = "macho", feature = "pe"))]
75impl From<goblin::error::Error> for BinaryError {
76    fn from(err: goblin::error::Error) -> Self {
77        BinaryError::ParseError(err.to_string())
78    }
79}
80
81#[cfg(feature = "disasm-capstone")]
82impl From<capstone::Error> for BinaryError {
83    fn from(err: capstone::Error) -> Self {
84        BinaryError::DisassemblyError(err.to_string())
85    }
86}
87
88#[cfg(feature = "wasm")]
89impl From<wasmparser::BinaryReaderError> for BinaryError {
90    fn from(err: wasmparser::BinaryReaderError) -> Self {
91        BinaryError::ParseError(format!("WASM parse error: {}", err))
92    }
93}
94
95impl BinaryError {
96    /// Create a new parse error
97    pub fn parse<S: Into<String>>(msg: S) -> Self {
98        Self::ParseError(msg.into())
99    }
100
101    /// Create a new unsupported format error
102    pub fn unsupported_format<S: Into<String>>(format: S) -> Self {
103        Self::UnsupportedFormat(format.into())
104    }
105
106    /// Create a new unsupported architecture error
107    pub fn unsupported_arch<S: Into<String>>(arch: S) -> Self {
108        Self::UnsupportedArchitecture(arch.into())
109    }
110
111    /// Create a new invalid data error
112    pub fn invalid_data<S: Into<String>>(msg: S) -> Self {
113        Self::InvalidData(msg.into())
114    }
115
116    /// Create a new input-size limit error
117    pub fn input_too_large(actual: usize, limit: usize) -> Self {
118        Self::InputTooLarge { actual, limit }
119    }
120
121    /// Create a new disassembly error
122    pub fn disassembly<S: Into<String>>(msg: S) -> Self {
123        Self::DisassemblyError(msg.into())
124    }
125
126    /// Create a new control flow error
127    pub fn control_flow<S: Into<String>>(msg: S) -> Self {
128        Self::ControlFlowError(msg.into())
129    }
130
131    /// Create a new symbol error
132    pub fn symbol<S: Into<String>>(msg: S) -> Self {
133        Self::SymbolError(msg.into())
134    }
135
136    /// Create a new entropy error
137    pub fn entropy<S: Into<String>>(msg: S) -> Self {
138        Self::EntropyError(msg.into())
139    }
140
141    /// Create a new memory map error
142    pub fn memory_map<S: Into<String>>(msg: S) -> Self {
143        Self::MemoryMapError(msg.into())
144    }
145
146    /// Create a new configuration error
147    pub fn config<S: Into<String>>(msg: S) -> Self {
148        Self::ConfigError(msg.into())
149    }
150
151    /// Create a new feature not available error
152    pub fn feature_not_available<S: Into<String>>(feature: S) -> Self {
153        Self::FeatureNotAvailable(feature.into())
154    }
155
156    /// Create a new internal error
157    pub fn internal<S: Into<String>>(msg: S) -> Self {
158        Self::Internal(msg.into())
159    }
160}
161
162#[cfg(test)]
163mod tests {
164    use super::*;
165    use std::error::Error;
166    use std::io;
167
168    #[test]
169    fn test_binary_error_parse_creation() {
170        let error = BinaryError::parse("test message");
171        assert!(matches!(error, BinaryError::ParseError(_)));
172        assert_eq!(
173            error.to_string(),
174            "Failed to parse binary format: test message"
175        );
176    }
177
178    #[test]
179    fn test_binary_error_parse_creation_string() {
180        let msg = String::from("dynamic string");
181        let error = BinaryError::parse(msg);
182        assert!(matches!(error, BinaryError::ParseError(_)));
183        assert_eq!(
184            error.to_string(),
185            "Failed to parse binary format: dynamic string"
186        );
187    }
188
189    #[test]
190    fn test_binary_error_unsupported_format_creation() {
191        let error = BinaryError::unsupported_format("CUSTOM");
192        assert!(matches!(error, BinaryError::UnsupportedFormat(_)));
193        assert_eq!(error.to_string(), "Unsupported binary format: CUSTOM");
194    }
195
196    #[test]
197    fn test_binary_error_unsupported_arch_creation() {
198        let error = BinaryError::unsupported_arch("ARM32");
199        assert!(matches!(error, BinaryError::UnsupportedArchitecture(_)));
200        assert_eq!(error.to_string(), "Unsupported architecture: ARM32");
201    }
202
203    #[test]
204    fn test_binary_error_invalid_data_creation() {
205        let error = BinaryError::invalid_data("corrupt header");
206        assert!(matches!(error, BinaryError::InvalidData(_)));
207        assert_eq!(error.to_string(), "Invalid binary data: corrupt header");
208    }
209
210    #[test]
211    fn test_binary_error_disassembly_creation() {
212        let error = BinaryError::disassembly("unable to decode instruction");
213        assert!(matches!(error, BinaryError::DisassemblyError(_)));
214        assert_eq!(
215            error.to_string(),
216            "Disassembly failed: unable to decode instruction"
217        );
218    }
219
220    #[test]
221    fn test_binary_error_control_flow_creation() {
222        let error = BinaryError::control_flow("circular dependency detected");
223        assert!(matches!(error, BinaryError::ControlFlowError(_)));
224        assert_eq!(
225            error.to_string(),
226            "Control flow analysis failed: circular dependency detected"
227        );
228    }
229
230    #[test]
231    fn test_binary_error_symbol_creation() {
232        let error = BinaryError::symbol("undefined reference");
233        assert!(matches!(error, BinaryError::SymbolError(_)));
234        assert_eq!(
235            error.to_string(),
236            "Symbol resolution failed: undefined reference"
237        );
238    }
239
240    #[test]
241    fn test_binary_error_entropy_creation() {
242        let error = BinaryError::entropy("insufficient data");
243        assert!(matches!(error, BinaryError::EntropyError(_)));
244        assert_eq!(
245            error.to_string(),
246            "Entropy analysis failed: insufficient data"
247        );
248    }
249
250    #[test]
251    fn test_binary_error_memory_map_creation() {
252        let error = BinaryError::memory_map("permission denied");
253        assert!(matches!(error, BinaryError::MemoryMapError(_)));
254        assert_eq!(error.to_string(), "Memory mapping error: permission denied");
255    }
256
257    #[test]
258    fn test_binary_error_config_creation() {
259        let error = BinaryError::config("invalid configuration value");
260        assert!(matches!(error, BinaryError::ConfigError(_)));
261        assert_eq!(
262            error.to_string(),
263            "Configuration error: invalid configuration value"
264        );
265    }
266
267    #[test]
268    fn test_binary_error_feature_not_available_creation() {
269        let error = BinaryError::feature_not_available("disasm-capstone");
270        assert!(matches!(error, BinaryError::FeatureNotAvailable(_)));
271        assert_eq!(
272            error.to_string(),
273            "Feature not available: disasm-capstone (try enabling the corresponding feature flag)"
274        );
275    }
276
277    #[test]
278    fn test_binary_error_internal_creation() {
279        let error = BinaryError::internal("unexpected state");
280        assert!(matches!(error, BinaryError::Internal(_)));
281        assert_eq!(error.to_string(), "Internal error: unexpected state");
282    }
283
284    #[test]
285    fn test_binary_error_display_all_variants() {
286        let test_cases = vec![
287            (
288                BinaryError::ParseError("parse issue".to_string()),
289                "Failed to parse binary format: parse issue",
290            ),
291            (
292                BinaryError::UnsupportedFormat("UNKNOWN".to_string()),
293                "Unsupported binary format: UNKNOWN",
294            ),
295            (
296                BinaryError::UnsupportedArchitecture("SPARC".to_string()),
297                "Unsupported architecture: SPARC",
298            ),
299            (
300                BinaryError::InvalidData("malformed".to_string()),
301                "Invalid binary data: malformed",
302            ),
303            (
304                BinaryError::DisassemblyError("decode failed".to_string()),
305                "Disassembly failed: decode failed",
306            ),
307            (
308                BinaryError::ControlFlowError("analysis failed".to_string()),
309                "Control flow analysis failed: analysis failed",
310            ),
311            (
312                BinaryError::SymbolError("not found".to_string()),
313                "Symbol resolution failed: not found",
314            ),
315            (
316                BinaryError::EntropyError("low entropy".to_string()),
317                "Entropy analysis failed: low entropy",
318            ),
319            (
320                BinaryError::MemoryMapError("map failed".to_string()),
321                "Memory mapping error: map failed",
322            ),
323            (
324                BinaryError::ConfigError("bad config".to_string()),
325                "Configuration error: bad config",
326            ),
327            (
328                BinaryError::FeatureNotAvailable("test-feature".to_string()),
329                "Feature not available: test-feature (try enabling the corresponding feature flag)",
330            ),
331            (
332                BinaryError::Internal("bug detected".to_string()),
333                "Internal error: bug detected",
334            ),
335        ];
336
337        for (error, expected) in test_cases {
338            assert_eq!(error.to_string(), expected);
339        }
340    }
341
342    #[test]
343    fn test_binary_error_debug_formatting() {
344        let error = BinaryError::ParseError("test".to_string());
345        let debug_str = format!("{:?}", error);
346        assert!(debug_str.contains("ParseError"));
347        assert!(debug_str.contains("test"));
348    }
349
350    #[test]
351    fn test_from_io_error() {
352        let io_err = io::Error::new(io::ErrorKind::NotFound, "file not found");
353        let binary_err: BinaryError = io_err.into();
354
355        assert!(matches!(binary_err, BinaryError::IoError(_)));
356        assert!(binary_err.to_string().contains("I/O error"));
357        assert!(binary_err.to_string().contains("file not found"));
358    }
359
360    #[cfg(any(feature = "elf", feature = "macho", feature = "pe"))]
361    #[test]
362    fn test_from_goblin_error() {
363        let goblin_err = goblin::error::Error::Malformed("invalid header".to_string());
364        let binary_err: BinaryError = goblin_err.into();
365
366        assert!(matches!(binary_err, BinaryError::ParseError(_)));
367        assert!(
368            binary_err
369                .to_string()
370                .contains("Failed to parse binary format")
371        );
372        assert!(binary_err.to_string().contains("invalid header"));
373    }
374
375    #[test]
376    fn test_error_source_chain_io() {
377        let io_err = io::Error::new(io::ErrorKind::PermissionDenied, "access denied");
378        let binary_err: BinaryError = io_err.into();
379
380        // Test that the error source chain is properly maintained
381        assert!(binary_err.source().is_some());
382        let source = binary_err.source().unwrap();
383        assert!(source.to_string().contains("access denied"));
384    }
385
386    #[test]
387    fn test_error_source_chain_root() {
388        let parse_err = BinaryError::ParseError("test".to_string());
389        assert!(parse_err.source().is_none());
390    }
391
392    #[test]
393    fn test_result_type_alias() {
394        fn test_function() -> Result<i32> {
395            Ok(42)
396        }
397
398        let result = test_function();
399        assert!(result.is_ok());
400        assert_eq!(result.unwrap(), 42);
401    }
402
403    #[test]
404    fn test_result_type_alias_error() {
405        fn test_function() -> Result<i32> {
406            Err(BinaryError::parse("test error"))
407        }
408
409        let result = test_function();
410        assert!(result.is_err());
411        let error = result.unwrap_err();
412        assert!(matches!(error, BinaryError::ParseError(_)));
413    }
414
415    // Test conditional compilation features
416    #[cfg(feature = "disasm-capstone")]
417    #[test]
418    fn test_from_capstone_error() {
419        use capstone::Error as CapstoneError;
420
421        let capstone_err = CapstoneError::InvalidHandle;
422        let binary_err: BinaryError = capstone_err.into();
423
424        assert!(matches!(binary_err, BinaryError::DisassemblyError(_)));
425        assert!(binary_err.to_string().contains("Disassembly failed"));
426    }
427
428    #[cfg(feature = "wasm")]
429    #[test]
430    fn test_from_wasmparser_error() {
431        use wasmparser::BinaryReader;
432
433        // Create an invalid WASM binary to trigger a parse error
434        let invalid_wasm = vec![0x00, 0x61, 0x73]; // Truncated WASM data
435        let mut reader = BinaryReader::new(&invalid_wasm, 0);
436
437        // Try to read a u32 from truncated data to trigger an error
438        let wasm_result = reader.read_u32();
439        assert!(wasm_result.is_err());
440
441        let wasm_err = wasm_result.unwrap_err();
442        let binary_err: BinaryError = wasm_err.into();
443
444        assert!(matches!(binary_err, BinaryError::ParseError(_)));
445        assert!(
446            binary_err
447                .to_string()
448                .contains("Failed to parse binary format")
449        );
450        assert!(binary_err.to_string().contains("WASM parse error"));
451    }
452
453    // Test error propagation in realistic scenarios
454    #[test]
455    fn test_error_propagation_chain() {
456        fn inner_function() -> std::io::Result<()> {
457            Err(io::Error::new(
458                io::ErrorKind::UnexpectedEof,
459                "truncated file",
460            ))
461        }
462
463        fn outer_function() -> Result<()> {
464            inner_function()?;
465            Ok(())
466        }
467
468        let result = outer_function();
469        assert!(result.is_err());
470        let error = result.unwrap_err();
471        assert!(matches!(error, BinaryError::IoError(_)));
472        assert!(error.source().is_some());
473    }
474
475    #[test]
476    fn test_error_equality_and_comparison() {
477        let err1 = BinaryError::ParseError("test".to_string());
478        let err2 = BinaryError::ParseError("test".to_string());
479        let err3 = BinaryError::ParseError("different".to_string());
480        let err4 = BinaryError::InvalidData("test".to_string());
481
482        // Note: BinaryError doesn't implement PartialEq, so we test the string representations
483        assert_eq!(err1.to_string(), err2.to_string());
484        assert_ne!(err1.to_string(), err3.to_string());
485        assert_ne!(err1.to_string(), err4.to_string());
486    }
487
488    #[test]
489    fn test_error_send_sync() {
490        fn assert_send<T: Send>() {}
491        fn assert_sync<T: Sync>() {}
492
493        assert_send::<BinaryError>();
494        assert_sync::<BinaryError>();
495    }
496
497    #[test]
498    fn test_comprehensive_constructor_parameters() {
499        // Test that constructors accept both &str and String
500        let _e1 = BinaryError::parse("str literal");
501        let _e2 = BinaryError::parse(String::from("owned string"));
502        let _e3 = BinaryError::parse("str reference".to_string());
503
504        // Test empty strings
505        let _e4 = BinaryError::internal("");
506        assert_eq!(_e4.to_string(), "Internal error: ");
507
508        // Test unicode strings
509        let _e5 = BinaryError::config("配置错误");
510        assert!(_e5.to_string().contains("配置错误"));
511    }
512
513    #[test]
514    fn test_error_in_threaded_context() {
515        use std::thread;
516
517        let handle = thread::spawn(|| {
518            let error = BinaryError::parse("thread error");
519            error.to_string()
520        });
521
522        let result = handle.join().unwrap();
523        assert_eq!(result, "Failed to parse binary format: thread error");
524    }
525}