diff --git a/capi/Cargo.toml b/capi/Cargo.toml index b130b6a4..afec7f4f 100644 --- a/capi/Cargo.toml +++ b/capi/Cargo.toml @@ -12,7 +12,7 @@ homepage.workspace = true [features] # The `capi` feature is required by `cargo-c`. -default = ["capi", "rules-profiling"] +default = ["capi"] capi = [] # When enabled, the serialization of compiled rules include native code for @@ -29,7 +29,7 @@ native-code-serialization = ["yara-x/native-code-serialization"] # Enables rules profiling. # -# This feature is enabled by default. +# This feature is disabled by default. rules-profiling = ["yara-x/rules-profiling"] diff --git a/capi/include/yara_x.h b/capi/include/yara_x.h index 65289970..91913788 100644 --- a/capi/include/yara_x.h +++ b/capi/include/yara_x.h @@ -80,6 +80,9 @@ typedef enum YRX_RESULT { SERIALIZATION_ERROR, // An error returned when a rule doesn't have any metadata. NO_METADATA, + // An error returned in cases where some API is not supported because the + // library was not built with the required features. + NOT_SUPPORTED, } YRX_RESULT; // A compiler that takes YARA source code and produces compiled rules. @@ -708,7 +711,7 @@ enum YRX_RESULT yrx_scanner_set_global_float(struct YRX_SCANNER *scanner, // Iterates over the top N most expensive rules, calling the callback for // each rule. // -// Requires the `rules-profiling` feature. +// Requires the `rules-profiling` feature, otherwise the // // See [`YRX_MOST_EXPENSIVE_RULES_CALLBACK`] for more details. enum YRX_RESULT yrx_scanner_iter_most_expensive_rules(struct YRX_SCANNER *scanner, diff --git a/capi/src/lib.rs b/capi/src/lib.rs index 1442820d..c52d28fe 100644 --- a/capi/src/lib.rs +++ b/capi/src/lib.rs @@ -152,6 +152,9 @@ pub enum YRX_RESULT { SERIALIZATION_ERROR, /// An error returned when a rule doesn't have any metadata. NO_METADATA, + /// An error returned in cases where some API is not supported because the + /// library was not built with the required features. + NOT_SUPPORTED, } /// Returns the error message for the most recent function in this API diff --git a/capi/src/scanner.rs b/capi/src/scanner.rs index f6aacedd..704c196a 100644 --- a/capi/src/scanner.rs +++ b/capi/src/scanner.rs @@ -61,11 +61,11 @@ pub unsafe extern "C" fn yrx_scanner_set_timeout( scanner: *mut YRX_SCANNER, timeout: u64, ) -> YRX_RESULT { - if scanner.is_null() { - return YRX_RESULT::INVALID_ARGUMENT; - } + let scanner = match scanner.as_mut() { + Some(s) => s, + None => return YRX_RESULT::INVALID_ARGUMENT, + }; - let scanner = scanner.as_mut().unwrap(); scanner.inner.set_timeout(Duration::from_secs(timeout)); YRX_RESULT::SUCCESS @@ -84,16 +84,16 @@ pub unsafe extern "C" fn yrx_scanner_scan( ) -> YRX_RESULT { _yrx_set_last_error::(None); - if scanner.is_null() { - return YRX_RESULT::INVALID_ARGUMENT; - } + let scanner = match scanner.as_mut() { + Some(s) => s, + None => return YRX_RESULT::INVALID_ARGUMENT, + }; let data = match slice_from_ptr_and_len(data, len) { Some(data) => data, None => return YRX_RESULT::INVALID_ARGUMENT, }; - let scanner = scanner.as_mut().unwrap(); let scan_results = scanner.inner.scan(data); if let Err(err) = scan_results { @@ -178,9 +178,10 @@ pub unsafe extern "C" fn yrx_scanner_set_module_output( data: *const u8, len: usize, ) -> YRX_RESULT { - if scanner.is_null() { - return YRX_RESULT::INVALID_ARGUMENT; - } + let scanner = match scanner.as_mut() { + Some(s) => s, + None => return YRX_RESULT::INVALID_ARGUMENT, + }; let module_name = match CStr::from_ptr(name).to_str() { Ok(name) => name, @@ -195,8 +196,6 @@ pub unsafe extern "C" fn yrx_scanner_set_module_output( None => return YRX_RESULT::INVALID_ARGUMENT, }; - let scanner = scanner.as_mut().unwrap(); - match scanner.inner.set_module_output_raw(module_name, data) { Ok(_) => { _yrx_set_last_error::(None); @@ -216,9 +215,10 @@ unsafe extern "C" fn yrx_scanner_set_global< ident: *const c_char, value: T, ) -> YRX_RESULT { - if scanner.is_null() { - return YRX_RESULT::INVALID_ARGUMENT; - } + let scanner = match scanner.as_mut() { + Some(s) => s, + None => return YRX_RESULT::INVALID_ARGUMENT, + }; let ident = match CStr::from_ptr(ident).to_str() { Ok(ident) => ident, @@ -228,8 +228,6 @@ unsafe extern "C" fn yrx_scanner_set_global< } }; - let scanner = scanner.as_mut().unwrap(); - match scanner.inner.set_global(ident, value) { Ok(_) => { _yrx_set_last_error::(None); @@ -327,35 +325,40 @@ pub type YRX_MOST_EXPENSIVE_RULES_CALLBACK = extern "C" fn( /// Iterates over the top N most expensive rules, calling the callback for /// each rule. /// -/// Requires the `rules-profiling` feature. +/// Requires the `rules-profiling` feature, otherwise the /// /// See [`YRX_MOST_EXPENSIVE_RULES_CALLBACK`] for more details. -#[cfg(feature = "rules-profiling")] #[no_mangle] +#[allow(unused_variables)] pub unsafe extern "C" fn yrx_scanner_iter_most_expensive_rules( scanner: *mut YRX_SCANNER, n: usize, callback: YRX_MOST_EXPENSIVE_RULES_CALLBACK, user_data: *mut c_void, ) -> YRX_RESULT { - if scanner.is_null() { - return YRX_RESULT::INVALID_ARGUMENT; - } - - let scanner = scanner.as_ref().unwrap(); + #[cfg(not(feature = "rules-profiling"))] + return YRX_RESULT::NOT_SUPPORTED; + + #[cfg(feature = "rules-profiling")] + { + let scanner = match scanner.as_ref() { + Some(s) => s, + None => return YRX_RESULT::INVALID_ARGUMENT, + }; - for profiling_info in scanner.inner.most_expensive_rules(n) { - let namespace = CString::new(profiling_info.namespace).unwrap(); - let rule = CString::new(profiling_info.rule).unwrap(); + for profiling_info in scanner.inner.most_expensive_rules(n) { + let namespace = CString::new(profiling_info.namespace).unwrap(); + let rule = CString::new(profiling_info.rule).unwrap(); + + callback( + namespace.as_ptr(), + rule.as_ptr(), + profiling_info.pattern_matching_time.as_secs_f64(), + profiling_info.condition_exec_time.as_secs_f64(), + user_data, + ); + } - callback( - namespace.as_ptr(), - rule.as_ptr(), - profiling_info.pattern_matching_time.as_secs_f64(), - profiling_info.condition_exec_time.as_secs_f64(), - user_data, - ); + YRX_RESULT::SUCCESS } - - YRX_RESULT::SUCCESS } diff --git a/go/scanner.go b/go/scanner.go index 60be3a79..e765012d 100644 --- a/go/scanner.go +++ b/go/scanner.go @@ -247,6 +247,13 @@ func (s *Scanner) Scan(buf []byte) (*ScanResults, error) { return scanResults, err } +// ProfilingInfo contains profiling information about a YARA rule. +// +// For each rule it contains: the rule's namespace, the rule's name, +// the time spent in matching patterns declared by the rule, and the time +// spent evaluating the rule's condition. +// +// See [Scanner.MostExpensiveRules]. type ProfilingInfo struct { Namespace string Rule string @@ -276,16 +283,28 @@ func mostExpensiveRulesCallback( }) } +// MostExpensiveRules returns information about the slowest rules and how much +// time they spent matching patterns and executing their conditions. +// +// In order to use this function the YARA-X C library must be built with +// support for rules profiling, which is done by enabling the `rules-profiling` +// feature. Otherwise, calling this function will cause a panic. func (s *Scanner) MostExpensiveRules(n int) []ProfilingInfo { profilingInfo := make([]ProfilingInfo, 0) mostExpensiveRules := cgo.NewHandle(&profilingInfo) defer mostExpensiveRules.Delete() - if C._yrx_scanner_iter_most_expensive_rules( + result := C._yrx_scanner_iter_most_expensive_rules( s.cScanner, C.size_t(n), C.YRX_MOST_EXPENSIVE_RULES_CALLBACK(C.mostExpensiveRulesCallback), - C.uintptr_t(mostExpensiveRules)) != C.SUCCESS { + C.uintptr_t(mostExpensiveRules)) + + if result == C.NOT_SUPPORTED { + panic("MostExpensiveRules requires that the YARA-X C library is built with the `rules-profiling` feature") + } + + if result != C.SUCCESS { panic("yrx_scanner_iter_most_expensive_rules failed") } diff --git a/go/scanner_test.go b/go/scanner_test.go index 25937a63..bd59e288 100644 --- a/go/scanner_test.go +++ b/go/scanner_test.go @@ -91,18 +91,6 @@ func TestScannerTimeout(t *testing.T) { assert.ErrorIs(t, err, ErrTimeout) } -func TestScannerMostExpensiveRules(t *testing.T) { - r, _ := Compile("rule t { strings: $a = /a(.*)*a/ condition: $a }") - s := NewScanner(r) - _, err := s.Scan(bytes.Repeat([]byte("a"), 5000)) - assert.NoError(t, err) - profilingInfo := s.MostExpensiveRules(1) - assert.Equal(t, "t", profilingInfo[0].Rule) - assert.Equal(t, "default", profilingInfo[0].Namespace) - assert.Greater(t, profilingInfo[0].PatternMatchingTime, time.Duration(0)) - assert.Greater(t, profilingInfo[0].ConditionExecTime, time.Duration(0)) -} - func TestScannerMetadata(t *testing.T) { r, _ := Compile(`rule t { meta: