@@ -26,7 +26,6 @@ use std::{
2626use once_cell:: sync:: Lazy ;
2727use serde:: { Deserialize , Serialize } ;
2828use serde_json:: { Value , json} ;
29- use smda:: FileArchitecture ;
3029use yaml_rust:: Yaml ;
3130
3231// 0.4.2: regexes used in tag-string parsing — compiled once per
@@ -43,16 +42,16 @@ static PARTS_ID_RE: Lazy<regex::Regex> = Lazy::new(|| {
4342 . expect ( "compile-time regex literal — pattern is valid" )
4443} ) ;
4544
46- use consts:: FileFormat ;
47- // 0.4.0: `Os` is referenced only by the properties-gated
48- // `FileCapabilities::get_os` — gating the import avoids the
49- // `--no-default-features` unused-import warning.
50- #[ cfg( feature = "properties" ) ]
51- use consts:: Os ;
52- use sede:: { from_hex, to_hex} ;
53-
45+ // 0.5.3 (#22): types that appear in public fields (`Properties::format`,
46+ // `Properties::os`, `Properties::arch`, `FileCapabilities::security_checks`)
47+ // are re-exported from the crate root — previously downstream could not
48+ // name them (their modules are private), making the fields unusable for
49+ // anything but Debug-printing.
50+ pub use crate :: consts:: { FileFormat , Os } ;
5451pub use crate :: error:: Error ;
55- use crate :: security:: options:: status:: SecurityCheckStatus ;
52+ pub use crate :: security:: options:: status:: SecurityCheckStatus ;
53+ use sede:: { from_hex, to_hex} ;
54+ pub use smda:: FileArchitecture ;
5655
5756pub ( crate ) mod consts;
5857mod error;
@@ -148,22 +147,37 @@ impl LibCSpec {
148147
149148// Used for options for binary security checks.
150149impl From < String > for LibCSpec {
150+ /// Lenient conversion kept for API compatibility: unknown versions
151+ /// fall back to the newest spec. Prefer `LibCSpec::from_str`
152+ /// (0.5.3, #22), which rejects unknown versions.
151153 fn from ( value : String ) -> Self {
152- match value. as_str ( ) {
153- "1.0.0" => LibCSpec :: LSB1 ,
154- "1.1.0" => LibCSpec :: LSB1dot1 ,
155- "1.2.0" => LibCSpec :: LSB1dot2 ,
156- "1.3.0" => LibCSpec :: LSB1dot3 ,
157- "2.0.0" => LibCSpec :: LSB2 ,
158- "2.0.1" => LibCSpec :: LSB2dot0dot1 ,
159- "2.1.0" => LibCSpec :: LSB2dot1 ,
160- "3.0.0" => LibCSpec :: LSB3 ,
161- "3.1.0" => LibCSpec :: LSB3dot1 ,
162- "3.2.0" => LibCSpec :: LSB3dot2 ,
163- "4.0.0" => LibCSpec :: LSB4 ,
164- "4.1.0" => LibCSpec :: LSB4dot1 ,
165- "5.0.0" => LibCSpec :: LSB5 ,
166- _ => LibCSpec :: LSB5 ,
154+ value. parse ( ) . unwrap_or ( LibCSpec :: LSB5 )
155+ }
156+ }
157+
158+ impl std:: str:: FromStr for LibCSpec {
159+ type Err = Error ;
160+
161+ /// Strict version parse — unknown versions are an error.
162+ /// 0.5.3 (#22): previously the only way in was `From<String>`,
163+ /// which silently mapped any typo (e.g. `4.0.1`) to `LSB5` and
164+ /// changed fortify-check semantics without a word.
165+ fn from_str ( s : & str ) -> Result < Self > {
166+ match s {
167+ "1.0.0" => Ok ( LibCSpec :: LSB1 ) ,
168+ "1.1.0" => Ok ( LibCSpec :: LSB1dot1 ) ,
169+ "1.2.0" => Ok ( LibCSpec :: LSB1dot2 ) ,
170+ "1.3.0" => Ok ( LibCSpec :: LSB1dot3 ) ,
171+ "2.0.0" => Ok ( LibCSpec :: LSB2 ) ,
172+ "2.0.1" => Ok ( LibCSpec :: LSB2dot0dot1 ) ,
173+ "2.1.0" => Ok ( LibCSpec :: LSB2dot1 ) ,
174+ "3.0.0" => Ok ( LibCSpec :: LSB3 ) ,
175+ "3.1.0" => Ok ( LibCSpec :: LSB3dot1 ) ,
176+ "3.2.0" => Ok ( LibCSpec :: LSB3dot2 ) ,
177+ "4.0.0" => Ok ( LibCSpec :: LSB4 ) ,
178+ "4.1.0" => Ok ( LibCSpec :: LSB4dot1 ) ,
179+ "5.0.0" => Ok ( LibCSpec :: LSB5 ) ,
180+ _ => Err ( Error :: InvalidLibCSpec ( s. to_string ( ) ) ) ,
167181 }
168182 }
169183}
@@ -206,6 +220,15 @@ impl BinarySecurityCheckOptions {
206220 input_file : PathBuf :: new ( ) ,
207221 }
208222 }
223+
224+ /// Assume that input files do not use any C runtime libraries
225+ /// (disables the libc-dependent ELF checks such as FORTIFY-SOURCE).
226+ /// 0.5.3 (#22): the option existed but was unreachable — the field
227+ /// is crate-private and `new()` hard-coded it to `false`.
228+ pub fn no_libc ( mut self , no_libc : bool ) -> Self {
229+ self . no_libc = no_libc;
230+ self
231+ }
209232}
210233
211234impl Default for BinarySecurityCheckOptions {
@@ -450,9 +473,17 @@ impl<'a> AnalyzeBuilder<'a> {
450473 /// Terminal — analyse a binary on disk. Routes through capa-rs's
451474 /// magic-byte format detection (PE → dnfile-then-smda, ELF →
452475 /// smda, Mach-O → smda) and runs the binary security checklist.
453- pub fn from_file ( self , file_name : impl AsRef < str > ) -> Result < FileCapabilities > {
476+ ///
477+ /// Accepts any `AsRef<Path>` (0.5.3 — was `AsRef<str>`); non-UTF-8
478+ /// paths are converted with `to_string_lossy`.
479+ pub fn from_file ( self , file_name : impl AsRef < std:: path:: Path > ) -> Result < FileCapabilities > {
454480 let rule_path = self . rules . ok_or ( Error :: BuilderMissingRules ) ?;
455- let f = file_name. as_ref ( ) . to_string ( ) ;
481+ let f = file_name. as_ref ( ) . to_string_lossy ( ) . into_owned ( ) ;
482+ // Spawn the rules load FIRST so it overlaps with format
483+ // detection and (eager) disassembly below — pre-#22 the thread
484+ // was spawned after the extractor was built, so `join` blocked
485+ // immediately and ~1000 rule files loaded strictly serially.
486+ let rules_thread_handle = spawn ( move || rules:: RuleSet :: new ( & rule_path) ) ;
456487 let ( format, buffer) = get_format ( & f) ?;
457488 let extractor = get_file_extractors (
458489 & f,
@@ -461,10 +492,14 @@ impl<'a> AnalyzeBuilder<'a> {
461492 self . high_accuracy ,
462493 self . resolve_tailcalls ,
463494 ) ?;
464- let rules_thread_handle = spawn ( move || rules:: RuleSet :: new ( & rule_path) ) ;
465495 let rules = match rules_thread_handle. join ( ) {
466496 Ok ( Ok ( rules) ) => rules,
467- Ok ( Err ( _) ) | Err ( _) => return Err ( Error :: DescriptionEvaluationError ) ,
497+ // Propagate the real RuleSet error (bad YAML, missing
498+ // dependency, …) — pre-#22 every failure was misreported
499+ // as DescriptionEvaluationError. A loader panic is a bug;
500+ // keep it a panic rather than mislabeling it.
501+ Ok ( Err ( e) ) => return Err ( e) ,
502+ Err ( panic) => std:: panic:: resume_unwind ( panic) ,
468503 } ;
469504
470505 // Security checks — defaults if caller didn't override.
@@ -553,6 +588,7 @@ impl<'a> AnalyzeBuilder<'a> {
553588 /// (pass `0` if the caller has no preference).
554589 pub fn from_buffer ( self , raw : & [ u8 ] , base_addr : u64 , bitness : u32 ) -> Result < FileCapabilities > {
555590 let rule_path = self . rules . ok_or ( Error :: BuilderMissingRules ) ?;
591+ let rules_thread_handle = spawn ( move || rules:: RuleSet :: new ( & rule_path) ) ;
556592 // Construct the extractor directly via smda's parse_buffer —
557593 // get_file_extractors routes on PE/ELF/Mach-O magic, which
558594 // a raw buffer doesn't have.
@@ -565,10 +601,12 @@ impl<'a> AnalyzeBuilder<'a> {
565601 self . resolve_tailcalls ,
566602 ) ?) ;
567603
568- let rules_thread_handle = spawn ( move || rules:: RuleSet :: new ( & rule_path) ) ;
569604 let rules = match rules_thread_handle. join ( ) {
570605 Ok ( Ok ( rules) ) => rules,
571- Ok ( Err ( _) ) | Err ( _) => return Err ( Error :: DescriptionEvaluationError ) ,
606+ // See `from_file`: real error propagates, a loader panic
607+ // stays a panic (#22).
608+ Ok ( Err ( e) ) => return Err ( e) ,
609+ Err ( panic) => std:: panic:: resume_unwind ( panic) ,
572610 } ;
573611
574612 // 0.4.3: FLIRT setup — see `from_file` for rationale.
@@ -1391,6 +1429,23 @@ pub struct FunctionCapabilities {
13911429 capabilities : Vec < String > ,
13921430}
13931431
1432+ impl FunctionCapabilities {
1433+ /// Address of the analysed function.
1434+ pub fn address ( & self ) -> usize {
1435+ self . address
1436+ }
1437+
1438+ /// Number of features extracted from the function.
1439+ pub fn features ( & self ) -> usize {
1440+ self . features
1441+ }
1442+
1443+ /// Names of the rules that matched inside the function.
1444+ pub fn capabilities ( & self ) -> & [ String ] {
1445+ & self . capabilities
1446+ }
1447+ }
1448+
13941449fn parse_parts_id ( s : & str ) -> Result < ( Vec < String > , String ) > {
13951450 // 0.4.2: cached at module scope (PARTS_ID_RE); was compiled per call.
13961451 let re = & * PARTS_ID_RE ;
0 commit comments