Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions capi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,23 @@ rules-profiling = ["yara-x/rules-profiling"]
# This feature is disabled by default.
magic-module = ["yara-x/magic-module"]

# Enables internal logging and automatically writes log messages to stderr.
#
# When this feature is enabled, log output can be configured via the `YRX_LOG`
# environment variable set to any of the log levels: `error`, `warn`, `info`,
# `debug`, or `trace`.
#
# You can also specify different log levels for specific targets/modules using
# comma-separated `target=level` directives:
#
# YRX_LOG=info
# YRX_LOG=yara_x=debug
# YRX_LOG=warn,yara_x::compiler=trace
#
# This feature is disabled by default.
stderr-logs = ["yara-x/stderr-logs"]


[lib]
name = "yara_x_capi"
crate-type = ["staticlib", "cdylib"]
Expand Down
7 changes: 7 additions & 0 deletions capi/include/yara_x.h
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,13 @@ enum YRX_RESULT yrx_scanner_set_timeout(struct YRX_SCANNER *scanner,
enum YRX_RESULT yrx_scanner_fast_scan(struct YRX_SCANNER *scanner,
bool yes);

// Sets the maximum number of matches per pattern.
//
// When a pattern reaches the maximum number of matches it won't produce more
// matches.
enum YRX_RESULT yrx_scanner_max_matches_per_pattern(struct YRX_SCANNER *scanner,
size_t n);

// Scans a data buffer.
//
// `data` can be null as long as `len` is 0. In such cases its handled as
Expand Down
32 changes: 32 additions & 0 deletions capi/src/scanner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,19 @@ impl<'r> InnerScanner<'r> {
self
}

fn max_matches_per_pattern(&mut self, n: usize) -> &mut Self {
match self {
InnerScanner::SingleBlock(s) => {
s.max_matches_per_pattern(n);
}
InnerScanner::MultiBlock(s) => {
s.max_matches_per_pattern(n);
}
InnerScanner::None => unreachable!(),
}
self
}

fn make_multi_block(&mut self) -> &mut yara_x::blocks::Scanner<'r> {
// Already a multi-block scanner, nothing else to do.
if let Self::MultiBlock(s) = self {
Expand Down Expand Up @@ -193,6 +206,25 @@ pub unsafe extern "C" fn yrx_scanner_fast_scan(
YRX_RESULT::YRX_SUCCESS
}

/// Sets the maximum number of matches per pattern.
///
/// When a pattern reaches the maximum number of matches it won't produce more
/// matches.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn yrx_scanner_max_matches_per_pattern(
scanner: *mut YRX_SCANNER,
n: usize,
) -> YRX_RESULT {
let scanner = match scanner.as_mut() {
Some(s) => s,
None => return YRX_RESULT::YRX_INVALID_ARGUMENT,
};

scanner.inner.max_matches_per_pattern(n);

YRX_RESULT::YRX_SUCCESS
}

/// Scans a data buffer.
///
/// `data` can be null as long as `len` is 0. In such cases its handled as
Expand Down
52 changes: 45 additions & 7 deletions capi/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ use crate::{
yrx_rules_destroy, yrx_rules_iter, yrx_rules_iter_imports,
yrx_rules_serialize, yrx_scanner_clear_profiling_data, yrx_scanner_create,
yrx_scanner_destroy, yrx_scanner_fast_scan, yrx_scanner_finish,
yrx_scanner_iter_slowest_rules, yrx_scanner_on_console_log,
yrx_scanner_on_matching_rule, yrx_scanner_scan, yrx_scanner_scan_block,
yrx_scanner_scan_file, yrx_scanner_set_global_bool,
yrx_scanner_set_global_float, yrx_scanner_set_global_int,
yrx_scanner_set_global_json, yrx_scanner_set_global_str,
yrx_scanner_set_module_data, yrx_scanner_set_module_output,
yrx_scanner_set_timeout,
yrx_scanner_iter_slowest_rules, yrx_scanner_max_matches_per_pattern,
yrx_scanner_on_console_log, yrx_scanner_on_matching_rule,
yrx_scanner_scan, yrx_scanner_scan_block, yrx_scanner_scan_file,
yrx_scanner_set_global_bool, yrx_scanner_set_global_float,
yrx_scanner_set_global_int, yrx_scanner_set_global_json,
yrx_scanner_set_global_str, yrx_scanner_set_module_data,
yrx_scanner_set_module_output, yrx_scanner_set_timeout,
};

use std::ffi::{CStr, c_char, c_void};
Expand Down Expand Up @@ -571,6 +571,40 @@ fn capi_fast_scan() {
}
}

#[test]
fn capi_max_matches_per_pattern() {
unsafe {
let mut compiler = std::ptr::null_mut();
yrx_compiler_create(0, &mut compiler);

let src = c"rule test { strings: $a = \"foo\" condition: #a == 1 }";
yrx_compiler_add_source(compiler, src.as_ptr());

let rules = yrx_compiler_build(compiler);
yrx_compiler_destroy(compiler);

let mut scanner = std::ptr::null_mut();
yrx_scanner_create(rules, &mut scanner);

yrx_scanner_max_matches_per_pattern(scanner, 1);

let mut matches = 0;
yrx_scanner_on_matching_rule(
scanner,
on_rule_match_increase_counter,
&mut matches as *mut i32 as *mut c_void,
);

let data = b"foofoofoo";
yrx_scanner_scan(scanner, data.as_ptr(), data.len());

assert_eq!(matches, 1);

yrx_scanner_destroy(scanner);
yrx_rules_destroy(rules);
}
}

#[test]
fn capi_null_args() {
unsafe {
Expand Down Expand Up @@ -775,6 +809,10 @@ fn capi_null_args() {
yrx_scanner_fast_scan(std::ptr::null_mut(), true),
YRX_RESULT::YRX_INVALID_ARGUMENT
);
assert_eq!(
yrx_scanner_max_matches_per_pattern(std::ptr::null_mut(), 10),
YRX_RESULT::YRX_INVALID_ARGUMENT
);
assert_eq!(
yrx_scanner_scan(std::ptr::null_mut(), std::ptr::null(), 0),
YRX_RESULT::YRX_INVALID_ARGUMENT
Expand Down
15 changes: 9 additions & 6 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,22 @@ test = false
# Enable the "debug" command for developers.
debug-cmd = []

# When this feature is enabled the CLI program prints debug logs if
# the RUST_LOG environment variable is set to any of the debug levels:
# When this feature is enabled the CLI program prints debug logs to stderr if
# the YRX_LOG environment variable is set to any of the log levels:
#
# error
# warn
# info
# debug
# trace
#
# Example: RUST_LOG=info ./yr scan some_rule.yar some_file
logging = ["dep:log", "dep:env_logger"]
# You can also specify different log levels for specific targets/modules using
# comma-separated `target=level` directives:
#
# YRX_LOG=info ./yr scan some_rule.yar some_file
# YRX_LOG=yara_x=debug ./yr scan some_rule.yar some_file
# YRX_LOG=warn,yara_x::compiler=trace ./yr scan some_rule.yar some_file
stderr-logs = ["yara-x/stderr-logs"]

# Enables rules profiling. Notice that profiling has an impact on scan
# performance.
Expand All @@ -58,8 +63,6 @@ figment = { workspace = true, features = ["toml"] }
globwalk = { workspace = true }
home = { workspace = true }
itertools = { workspace = true }
env_logger = { workspace = true, optional = true, features = ["auto-color"] }
log = { workspace = true, optional = true }
protobuf = { workspace = true }
regex = { workspace = true }
serde_json = { workspace = true, features = ["preserve_order"] }
Expand Down
3 changes: 0 additions & 3 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,6 @@ fn main() -> anyhow::Result<()> {
println!("could not enable ANSI support: {err}")
}

#[cfg(feature = "logging")]
env_logger::init();

// If stdout is not a tty (for example, because it was redirected to a
// file) turn off colors. This way you can redirect the output to a file
// without ANSI escape codes messing up the file content.
Expand Down
8 changes: 4 additions & 4 deletions cli/src/walk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -539,10 +539,10 @@ impl<'a> ParWalker<'a> {
}
}));

// `multi_progress` will be `None` if the `logging` feature is
// enabled or if either stdout or stderr is not a tty (for example
// when any of them are redirected to a file).
let multi_progress = if cfg!(feature = "logging") {
// `multi_progress` will be `None` if the `stderr-logs` feature
// is enabled or if either stdout or stderr is not a tty (for
// example when any of them are redirected to a file).
let multi_progress = if cfg!(feature = "stderr-logs") {
None
} else if io::stdout().is_tty() {
Some(MultiProgress::new())
Expand Down
6 changes: 6 additions & 0 deletions go/scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,12 @@ func (s *Scanner) FastScan(yes bool) {
runtime.KeepAlive(s)
}

// MaxMatchesPerPattern sets the maximum number of matches per pattern.
func (s *Scanner) MaxMatchesPerPattern(n int) {
C.yrx_scanner_max_matches_per_pattern(s.cScanner, C.size_t(n))
runtime.KeepAlive(s)
}

var ErrTimeout = errors.New("timeout")

// SetGlobal sets the value of a global variable.
Expand Down
16 changes: 16 additions & 0 deletions go/scanner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,3 +178,19 @@ func TestScannerFastScan(t *testing.T) {
assert.Len(t, matchingRules[0].Patterns(), 1)
assert.Len(t, matchingRules[0].Patterns()[0].Matches(), 1)
}

func TestScannerMaxMatchesPerPattern(t *testing.T) {
r, _ := Compile(`
rule t {
strings:
$a = "foo"
condition:
#a == 1
}`)
s := NewScanner(r)
s.MaxMatchesPerPattern(1)
scanResults, _ := s.Scan([]byte("foofoofoo"))
matchingRules := scanResults.MatchingRules()

assert.Len(t, matchingRules, 1)
}
33 changes: 30 additions & 3 deletions lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,35 @@ rustdoc-args = ["--cfg", "docsrs"]
# actually computes the expression.
constant-folding = []

# Enables internal log statements inside YARA-X via the standard `log` crate
# facade.
#
# When this feature is enabled, YARA-X emits internal log messages (using
# `log::info!`,`log::error!`, etc.). However, it does not initialize any
# concrete logger implementation. The host application remains responsible
# for configuring and initializing its own logger (e.g., `env_logger`,
# `tracing-subscriber`, or a custom logger).
logging = ["dep:log", "dep:quanta"]

# Enables internal logging and automatically logs messages to stderr.
#
# Unlike `logging` (which only emits log events and relies on the host
# application to set up a logger), `stderr-logs` actually writes log messages
# to stderr. This is particularly useful when YARA-X is embedded in non-Rust
# host binaries, allowing log output to be configured via the `YRX_LOG`
# environment variable.
#
# Log output can be configured by setting `YRX_LOG` to any of the log levels:
# `error`, `warn`, `info`, `debug`, or `trace`.
#
# You can also specify different log levels for specific targets/modules using
# comma-separated `target=level` directives:
#
# YRX_LOG=info # Global level set to info
# YRX_LOG=yara_x=debug # Debug level for the yara_x crate
# YRX_LOG=warn,yara_x::compiler=trace # Global warn, trace for yara_x::compiler
stderr-logs = ["dep:env_logger", "logging"]

# Enables the use of exact atoms for speeding up matches. Exact atoms are those
# that don't require further verification, the sole presence of the atom
# indicates that the pattern containing the atom matches. For instance, in
Expand Down Expand Up @@ -74,9 +103,6 @@ generate-module-docs = ["protoc"]
# plugin. Follow the instructions in: https://lib.rs/crates/protobuf-codegen3
protoc = []

# Enables debug logs.
logging = ["dep:log", "dep:quanta"]

# When enabled, the serialization of compiled rules include native code for
# the platform in which the rules where compiled. This reduces the load time,
# as the native code is already included in the serialized rules and doesn't
Expand Down Expand Up @@ -310,6 +336,7 @@ digest = { workspace = true, optional = true }
dsa = { workspace = true, optional = true }
ecdsa = { workspace = true, optional = true }
encoding_rs = { workspace = true, optional = true }
env_logger = { workspace = true, optional = true }
flate2 = { workspace = true, optional = true }
memmap2 = { workspace = true }
indexmap = { workspace = true, features = ["serde"] }
Expand Down
2 changes: 2 additions & 0 deletions lib/src/compiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,8 @@ pub struct Compiler<'a> {
impl<'a> Compiler<'a> {
/// Creates a new YARA compiler.
pub fn new() -> Self {
crate::init_logger();

let mut ident_pool = StringPool::new();
let mut symbol_table = StackedSymbolTable::new();

Expand Down
30 changes: 19 additions & 11 deletions lib/src/compiler/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,8 @@ impl Rules {
});
}

crate::init_logger();

#[cfg(feature = "logging")]
let start = Instant::now();

Expand Down Expand Up @@ -526,13 +528,21 @@ impl Rules {
pub(crate) fn get_rule_and_pattern_by_sub_pattern_id(
&self,
sub_pattern_id: SubPatternId,
) -> Option<(RuleId, IdentId)> {
let (target_pattern_id, _) = self.get_sub_pattern(sub_pattern_id);
for (rule_id, rule) in self.rules.iter().enumerate() {
) -> Option<(&RuleInfo, &PatternInfo)> {
let (pattern_id, _) = self.get_sub_pattern(sub_pattern_id);
self.get_rule_and_pattern_by_pattern_id(*pattern_id)
}

#[cfg(feature = "logging")]
pub(crate) fn get_rule_and_pattern_by_pattern_id(
&self,
pattern_id: PatternId,
) -> Option<(&RuleInfo, &PatternInfo)> {
for rule in &self.rules {
for p in &rule.patterns {
if p.pattern_id == *target_pattern_id {
return Some((rule_id.into(), p.ident_id));
};
if p.pattern_id == pattern_id {
return Some((rule, p));
}
}
}
None
Expand Down Expand Up @@ -591,15 +601,13 @@ impl Rules {
}

if x.atom.len() < 2 {
let (rule_id, pattern_ident_id) = self
let (rule, pattern) = self
.get_rule_and_pattern_by_sub_pattern_id(x.sub_pattern_id)
.unwrap();

let rule = self.get(rule_id);

info!(
warn!(
"Very short atom in pattern `{}` in rule `{}:{}` (length: {})",
self.ident_pool.get(pattern_ident_id).unwrap(),
self.ident_pool.get(pattern.ident_id).unwrap(),
self.ident_pool.get(rule.namespace_ident_id).unwrap(),
self.ident_pool.get(rule.ident_id).unwrap(),
x.atom.len()
Expand Down
Loading
Loading