From 2c5ed778da1608a3c4bed43caedb3e20370a488c Mon Sep 17 00:00:00 2001 From: "Victor M. Alvarez" Date: Tue, 21 May 2024 17:52:02 +0200 Subject: [PATCH 01/32] feat: implement the `fix encoding` command. YARA-X is stricter that YARA with respect to invalid UTF-8 characters in source code. This command allows to convert your YARA source files to UTF-8 encoding if they are not. --- Cargo.lock | 13 ++++ cli/Cargo.toml | 2 + cli/src/commands/fix.rs | 168 ++++++++++++++++++++++++++++++++++++++++ cli/src/commands/mod.rs | 3 + cli/src/help.rs | 9 +++ cli/src/main.rs | 1 + 6 files changed, 196 insertions(+) create mode 100644 cli/src/commands/fix.rs diff --git a/Cargo.lock b/Cargo.lock index 7d635d9d7..84f603ec4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -511,6 +511,17 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chardetng" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14b8f0b65b7b08ae3c8187e8d77174de20cb6777864c6b832d8ad365999cf1ea" +dependencies = [ + "cfg-if", + "encoding_rs", + "memchr", +] + [[package]] name = "chrono" version = "0.4.35" @@ -4840,12 +4851,14 @@ version = "0.3.0" dependencies = [ "anyhow", "ascii_tree", + "chardetng", "clap 4.5.3", "clap_complete", "colored_json", "crossbeam", "crossterm 0.27.0", "enable-ansi-support", + "encoding_rs", "env_logger", "globwalk", "log", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 563147619..7071a163d 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -53,9 +53,11 @@ yara-x-parser = { workspace = true, features = ["ascii-tree"] } yara-x-proto-yaml = { workspace = true } yara-x-fmt = { workspace = true } +chardetng = "0.1.17" colored_json = "4.0.0" crossbeam = "0.8.2" crossterm = "0.27.0" +encoding_rs = "0.8.33" pprof = { version = "0.13.0", features = ["flamegraph"], optional = true } strum_macros = "0.25" superconsole = "0.2.0" diff --git a/cli/src/commands/fix.rs b/cli/src/commands/fix.rs new file mode 100644 index 000000000..615771627 --- /dev/null +++ b/cli/src/commands/fix.rs @@ -0,0 +1,168 @@ +use anyhow::Context; +use clap::{arg, value_parser, ArgAction, ArgMatches, Command}; +use std::borrow::Cow; +use std::fs; +use std::path::PathBuf; +use std::sync::atomic::{AtomicUsize, Ordering}; +use superconsole::{Component, Line, Lines, Span}; +use yansi::Color::{Green, Red, Yellow}; +use yansi::Paint; + +use crate::walk::Message; +use crate::{help, walk}; + +pub fn fix() -> Command { + super::command("fix") + .about("Utilities for fixing source code") + // The `fix` command is still in beta. + .hide(true) + .arg_required_else_help(true) + .subcommand(fix_encoding()) +} + +pub fn fix_encoding() -> Command { + super::command("encoding") + .about("Convert source files to UTF-8") + .long_about(help::FIX_ENCODING_HELP) + .arg( + arg!() + .help("Path to YARA source file or directory") + .value_parser(value_parser!(PathBuf)), + ) + .arg(arg!(-d - -"dry-run").help("Don't modify source files")) + .arg( + arg!(-d --"max-depth" ) + .help("Walk directories recursively up to a given depth") + .long_help(help::DEPTH_LONG_HELP) + .value_parser(value_parser!(u16)), + ) + .arg( + arg!(-f --filter ) + .help("Check files that match the given pattern only") + .long_help(help::FILTER_LONG_HELP) + .action(ArgAction::Append), + ) + .arg( + arg!(-p --"threads" ) + .help("Use the given number of threads") + .long_help(help::THREADS_LONG_HELP) + .required(false) + .value_parser(value_parser!(u8).range(1..)), + ) +} + +pub fn exec_fix(args: &ArgMatches) -> anyhow::Result<()> { + match args.subcommand() { + Some(("encoding", args)) => exec_fix_encoding(args), + _ => unreachable!(), + } +} + +pub fn exec_fix_encoding(args: &ArgMatches) -> anyhow::Result<()> { + let rules_path = args.get_one::("RULES_PATH").unwrap(); + let filters = args.get_many::("filter"); + let dry_run = args.get_flag("dry-run"); + let max_depth = args.get_one::("max-depth"); + let num_threads = args.get_one::("threads"); + + let mut w = walk::ParWalker::path(rules_path); + + if let Some(max_depth) = max_depth { + w.max_depth(*max_depth as usize); + } + + if let Some(num_threads) = num_threads { + w.num_threads(*num_threads); + } + + if let Some(filters) = filters { + for filter in filters { + w.filter(filter); + } + } else { + // Default filters are `**/*.yar` and `**/*.yara`. + w.filter("**/*.yar").filter("**/*.yara"); + } + + w.walk( + FixEncodingState::new(), + |_, _| {}, + |state, output, file_path, _| { + let src = fs::read(&file_path).with_context(|| { + format!("can not read `{}`", file_path.display()) + })?; + + // Detect the original encoding. + let mut detector = chardetng::EncodingDetector::new(); + detector.feed(src.as_slice(), true); + + // Decode the source file as UTF-8. `invalid_chars` will be true + // if some character could not be encoded as UTF-8 and was replaced + // by the replacement character. + let (src_utf8, encoding, invalid_chars) = + detector.guess(None, true).decode(src.as_slice()); + + // Re-write the source as UTF-8, except if --dry-run was used or + // the original source was not modified at all. + if !dry_run && matches!(src_utf8, Cow::Owned(_)) { + fs::write(&file_path, src_utf8.as_bytes())?; + state.files_modified.fetch_add(1, Ordering::Relaxed); + } + + output.send(Message::Info(format!( + "{:>14} {}", + encoding + .name() + .paint(if invalid_chars { Yellow } else { Green }) + .bold(), + file_path.display() + )))?; + + Ok(()) + }, + |err, output| { + let _ = output.send(Message::Error(format!( + "{} {}", + "error:".paint(Red).bold(), + err + ))); + + Ok(()) + }, + ) + .unwrap(); + + Ok(()) +} + +struct FixEncodingState { + files_modified: AtomicUsize, +} + +impl FixEncodingState { + fn new() -> Self { + Self { files_modified: AtomicUsize::new(0) } + } +} + +impl Component for FixEncodingState { + fn draw_unchecked( + &self, + _dimensions: superconsole::Dimensions, + mode: superconsole::DrawMode, + ) -> anyhow::Result { + let res = match mode { + superconsole::DrawMode::Normal | superconsole::DrawMode::Final => { + let modified = format!( + "{} file(s) modified.", + self.files_modified.load(Ordering::Relaxed) + ); + + Line::from_iter([Span::new_unstyled( + modified.paint(Green).bold(), + )?]) + } + }; + Ok(Lines(vec![res])) + } +} diff --git a/cli/src/commands/mod.rs b/cli/src/commands/mod.rs index 2fdae9489..0b4c07ef9 100644 --- a/cli/src/commands/mod.rs +++ b/cli/src/commands/mod.rs @@ -3,6 +3,7 @@ mod compile; mod completion; mod debug; mod dump; +mod fix; mod fmt; mod scan; @@ -11,6 +12,7 @@ pub use compile::*; pub use completion::*; pub use debug::*; pub use dump::*; +pub use fix::*; pub use fmt::*; pub use scan::*; @@ -55,6 +57,7 @@ pub fn cli() -> Command { commands::debug(), commands::dump(), commands::fmt(), + commands::fix(), commands::completion(), ]) } diff --git a/cli/src/help.rs b/cli/src/help.rs index ce203e3c7..130ef582e 100644 --- a/cli/src/help.rs +++ b/cli/src/help.rs @@ -91,3 +91,12 @@ pub const SCAN_LIST_HELP: &str = r#"Indicate that TARGET_PATH is a file containi must be a text file containing one path per line. The paths must be either absolute paths, or relative to the current directory."#; + +pub const FIX_ENCODING_HELP: &str = r#"Convert source files to UTF-8 + +YARA-X is stricter that YARA with respect to invalid UTF-8 characters in source code. This +command allows to convert your YARA source files to UTF-8 encoding if they are not. + +If is a directory, all files with extensions `.yar` and `.yara` will be converted. +This behavior can be changed by using the `--filter` option. +"#; diff --git a/cli/src/main.rs b/cli/src/main.rs index a4fb193ad..c017f85f4 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -58,6 +58,7 @@ fn main() -> anyhow::Result<()> { let result = match args.subcommand() { Some(("debug", args)) => commands::exec_debug(args), Some(("check", args)) => commands::exec_check(args), + Some(("fix", args)) => commands::exec_fix(args), Some(("fmt", args)) => commands::exec_fmt(args), Some(("scan", args)) => commands::exec_scan(args), Some(("dump", args)) => commands::exec_dump(args), From 33eba846811d231278f9b7c1df6bcc666f3602fa Mon Sep 17 00:00:00 2001 From: Jacob Latonis Date: Tue, 21 May 2024 11:43:40 -0600 Subject: [PATCH 02/32] feat: implement array entitlement parsing for complete entitlement parsing (#116) --- lib/src/modules/macho/parser.rs | 29 ++++++++++++++----- .../modules/macho/tests/testdata/chess.out | 1 + 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/lib/src/modules/macho/parser.rs b/lib/src/modules/macho/parser.rs index f18385e96..1c247c89a 100644 --- a/lib/src/modules/macho/parser.rs +++ b/lib/src/modules/macho/parser.rs @@ -1,5 +1,6 @@ use crate::modules::protos; use bstr::{BStr, ByteSlice}; +use itertools::Itertools; #[cfg(feature = "logging")] use log::error; use nom::bytes::complete::take; @@ -799,8 +800,8 @@ impl<'a> MachOFile<'a> { if blob.magic == CS_MAGIC_EMBEDDED_ENTITLEMENTS { let xml_data = &super_data [offset + size_of_blob..offset + length]; - let xml_string = std::str::from_utf8(xml_data) - .unwrap_or_default(); + let xml_string = + std::str::from_utf8(xml_data).unwrap_or_default(); let opt = roxmltree::ParsingOptions { allow_dtd: true, @@ -812,13 +813,25 @@ impl<'a> MachOFile<'a> { xml_string, opt, ) { - for node in parsed_xml - .descendants() - .filter(|n| n.has_tag_name("key")) - { + for node in parsed_xml.descendants().filter(|n| { + n.has_tag_name("key") + || n.has_tag_name("array") + }) { if let Some(entitlement) = node.text() { - self.entitlements - .push(entitlement.to_string()); + if node.has_tag_name("array") { + node.descendants() + .filter_map(|n| n.text()) + .filter(|t| !t.trim().is_empty()) + .unique() + .map(|t| t.to_string()) + .for_each(|array_entitlement| { + self.entitlements + .push(array_entitlement) + }); + } else { + self.entitlements + .push(entitlement.to_string()); + } } } } diff --git a/lib/src/modules/macho/tests/testdata/chess.out b/lib/src/modules/macho/tests/testdata/chess.out index 34cf6f9e9..40333ad8c 100644 --- a/lib/src/modules/macho/tests/testdata/chess.out +++ b/lib/src/modules/macho/tests/testdata/chess.out @@ -817,6 +817,7 @@ dyld_info: entitlements: - "com.apple.developer.game-center" - "com.apple.private.tcc.allow" + - "kTCCServiceMicrophone" - "com.apple.security.app-sandbox" - "com.apple.security.device.microphone" - "com.apple.security.files.user-selected.read-write" From 1e816a70471a32313d7194ebc199aff3abab87c6 Mon Sep 17 00:00:00 2001 From: "Victor M. Alvarez" Date: Wed, 22 May 2024 11:15:52 +0200 Subject: [PATCH 03/32] feat: expose rule metadata in the Rust API --- lib/src/compiler/mod.rs | 29 ++++++++- lib/src/compiler/rules.rs | 12 ++++ lib/src/lib.rs | 2 + lib/src/scanner/mod.rs | 74 +++++++++++++++++++++- lib/src/scanner/tests.rs | 50 +++++++++++++-- parser/src/ast/mod.rs | 4 +- parser/src/parser/cst2ast.rs | 15 ++++- parser/src/parser/tests/testdata/meta.yaml | 2 +- 8 files changed, 178 insertions(+), 10 deletions(-) diff --git a/lib/src/compiler/mod.rs b/lib/src/compiler/mod.rs index 9a1d4adb2..932e2dfab 100644 --- a/lib/src/compiler/mod.rs +++ b/lib/src/compiler/mod.rs @@ -722,13 +722,39 @@ impl<'a> Compiler<'a> { // compiling the current rule this snapshot allows restoring the // compiler to the state it had before starting compiling the rule. // This way we don't leave too much junk, like atoms, or sub-patterns - // corresponding to failed rules. + // corresponding to failed rules. However, there is some junk left + // behind in `ident_pool` and `lit_pool`, because once a string is + // added to one of these pools it can't be removed. let snapshot = self.take_snapshot(); // The RuleId for the new rule is current length of `self.rules`. The // first rule has RuleId = 0. let rule_id = RuleId(self.rules.len() as i32); + // Build a vector of pairs (IdentId, MetaValue) for every meta defined + // in the rule. + let meta = rule + .meta + .iter() + .flatten() + .map(|m| { + ( + self.ident_pool.get_or_intern(m.identifier.name), + match &m.value { + ast::MetaValue::Integer(i) => MetaValue::Integer(*i), + ast::MetaValue::Float(f) => MetaValue::Float(*f), + ast::MetaValue::Bool(b) => MetaValue::Bool(*b), + ast::MetaValue::String(s) => { + MetaValue::String(self.lit_pool.get_or_intern(s)) + } + ast::MetaValue::Bytes(s) => { + MetaValue::Bytes(self.lit_pool.get_or_intern(s)) + } + }, + ) + }) + .collect(); + // Add the new rule to `self.rules`. The only information about the // rule that we don't have right now is the PatternId corresponding to // each pattern, that's why the `pattern` fields is initialized as @@ -745,6 +771,7 @@ impl<'a> Compiler<'a> { patterns: vec![], is_global: rule.flags.contains(RuleFlag::Global), is_private: rule.flags.contains(RuleFlag::Private), + metadata: meta, }); let mut rule_patterns = Vec::new(); diff --git a/lib/src/compiler/rules.rs b/lib/src/compiler/rules.rs index eb29f652d..84969bddb 100644 --- a/lib/src/compiler/rules.rs +++ b/lib/src/compiler/rules.rs @@ -453,6 +453,16 @@ impl fmt::Debug for Rules { } } +/// Metadata values. +#[derive(Serialize, Deserialize)] +pub(crate) enum MetaValue { + Bool(bool), + Integer(i64), + Float(f64), + String(LiteralId), + Bytes(LiteralId), +} + /// Information about each of the individual rules included in [`Rules`]. #[derive(Serialize, Deserialize)] pub(crate) struct RuleInfo { @@ -467,6 +477,8 @@ pub(crate) struct RuleInfo { /// compilation phase, but not during the scan phase. #[serde(skip)] pub(crate) ident_span: Span, + /// Metadata associated to the rule. + pub(crate) metadata: Vec<(IdentId, MetaValue)>, /// Vector with all the patterns defined by this rule. pub(crate) patterns: Vec<(IdentId, PatternId)>, /// True if the rule is global. diff --git a/lib/src/lib.rs b/lib/src/lib.rs index d13650d3a..b21a53f5c 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -53,6 +53,8 @@ pub use compiler::SerializationError; pub use scanner::Match; pub use scanner::Matches; pub use scanner::MatchingRules; +pub use scanner::MetaValue; +pub use scanner::Metadata; pub use scanner::ModuleOutputs; pub use scanner::NonMatchingRules; pub use scanner::Pattern; diff --git a/lib/src/scanner/mod.rs b/lib/src/scanner/mod.rs index 6a0ed1793..dce481ed9 100644 --- a/lib/src/scanner/mod.rs +++ b/lib/src/scanner/mod.rs @@ -18,6 +18,7 @@ use std::time::Duration; use std::{cmp, fs, thread}; use bitvec::prelude::*; +use bstr::{BStr, ByteSlice}; use fmmap::{MmapFile, MmapFileExt}; use indexmap::IndexMap; use protobuf::{CodedInputStream, MessageDyn}; @@ -33,7 +34,7 @@ use crate::modules::{Module, BUILTIN_MODULES}; use crate::types::{Struct, TypeValue}; use crate::variables::VariableError; use crate::wasm::{ENGINE, MATCHING_RULES_BITMAP_BASE}; -use crate::{modules, wasm, Variable}; +use crate::{compiler, modules, wasm, Variable}; pub(crate) use crate::scanner::context::*; use crate::scanner::matches::PatternMatches; @@ -947,6 +948,15 @@ impl<'a, 'r> Rule<'a, 'r> { self.rules.ident_pool().get(self.rule_info.namespace_ident_id).unwrap() } + /// Returns the metadata associated to this rule. + pub fn metadata(&self) -> Metadata<'a, 'r> { + Metadata { + ctx: self.ctx, + iterator: self.rule_info.metadata.iter(), + len: self.rule_info.metadata.len(), + } + } + /// Returns the patterns defined by this rule. pub fn patterns(&self) -> Patterns<'a, 'r> { Patterns { @@ -958,6 +968,68 @@ impl<'a, 'r> Rule<'a, 'r> { } } +/// Iterator that returns the metadata associated to a rule. +/// +/// The iterator returns (&str, [`MetaValue`]) pairs, where the first item +/// is the identifier, and the second one the metadata value. +pub struct Metadata<'a, 'r> { + ctx: &'a ScanContext<'r>, + iterator: Iter<'a, (IdentId, compiler::MetaValue)>, + len: usize, +} + +/// A metadata value. +#[derive(Debug, PartialEq)] +pub enum MetaValue<'r> { + /// Integer value. + Integer(i64), + /// Float value. + Float(f64), + /// Bool value. + Bool(bool), + /// A valid UTF-8 string. + String(&'r str), + /// An arbitrary string. Used when the value contains invalid UTF-8 + /// characters. + Bytes(&'r BStr), +} + +impl<'a, 'r> Iterator for Metadata<'a, 'r> { + type Item = (&'r str, MetaValue<'r>); + + fn next(&mut self) -> Option { + let (ident_id, value) = self.iterator.next()?; + + let ident = + self.ctx.compiled_rules.ident_pool().get(*ident_id).unwrap(); + + let value = match value { + compiler::MetaValue::Bool(b) => MetaValue::Bool(*b), + compiler::MetaValue::Integer(i) => MetaValue::Integer(*i), + compiler::MetaValue::Float(f) => MetaValue::Float(*f), + compiler::MetaValue::String(id) => { + let s = self.ctx.compiled_rules.lit_pool().get(*id).unwrap(); + // We can be sure that s is a valid UTF-8 string, because + // the type of meta is MetaValue::String. + let s = unsafe { s.to_str_unchecked() }; + MetaValue::String(s) + } + compiler::MetaValue::Bytes(id) => MetaValue::Bytes( + self.ctx.compiled_rules.lit_pool().get(*id).unwrap(), + ), + }; + + Some((ident, value)) + } +} + +impl<'a, 'r> ExactSizeIterator for Metadata<'a, 'r> { + #[inline] + fn len(&self) -> usize { + self.len + } +} + /// An iterator that returns the patterns defined by a rule. pub struct Patterns<'a, 'r> { ctx: &'a ScanContext<'r>, diff --git a/lib/src/scanner/tests.rs b/lib/src/scanner/tests.rs index 1c0e8af58..ee74ae05f 100644 --- a/lib/src/scanner/tests.rs +++ b/lib/src/scanner/tests.rs @@ -3,7 +3,7 @@ use protobuf::MessageDyn; use protobuf::{Message, MessageFull}; use crate::mods; -use crate::scanner::Scanner; +use crate::scanner::{MetaValue, Scanner}; use crate::variables::VariableError; #[test] @@ -60,8 +60,8 @@ fn matches() { let mut scanner = Scanner::new(&rules); let results = scanner.scan(b"foobar").expect("scan should not fail"); - for matching_rules in results.matching_rules() { - for pattern in matching_rules.patterns() { + for matching_rule in results.matching_rules() { + for pattern in matching_rule.patterns() { matches.extend( pattern .matches() @@ -76,6 +76,46 @@ fn matches() { ) } +#[test] +fn metadata() { + let rules = crate::compile( + r#" + rule test { + meta: + foo = 1 + bar = 2.0 + baz = true + qux = "qux" + quux = "qu\x00x" + condition: + true + } + "#, + ) + .unwrap(); + + let mut metas = vec![]; + let mut scanner = Scanner::new(&rules); + let results = scanner.scan(b"").expect("scan should not fail"); + + for matching_rule in results.matching_rules() { + for meta in matching_rule.metadata() { + metas.push(meta) + } + } + + assert_eq!( + metas, + [ + ("foo", MetaValue::Integer(1)), + ("bar", MetaValue::Float(2.0)), + ("baz", MetaValue::Bool(true)), + ("qux", MetaValue::String("qux")), + ("quux", MetaValue::Bytes(b"qu\0x".into())), + ] + ) +} + #[test] fn xor_matches() { let rules = crate::compile( @@ -92,12 +132,12 @@ fn xor_matches() { let mut matches = vec![]; - for matching_rules in Scanner::new(&rules) + for matching_rule in Scanner::new(&rules) .scan(b"lhrrhrrhqqh") .expect("scan should not fail") .matching_rules() { - for pattern in matching_rules.patterns() { + for pattern in matching_rule.patterns() { matches.extend( pattern .matches() diff --git a/parser/src/ast/mod.rs b/parser/src/ast/mod.rs index 39e60366f..e753f6f6a 100644 --- a/parser/src/ast/mod.rs +++ b/parser/src/ast/mod.rs @@ -30,7 +30,7 @@ use std::fmt::{Debug, Display, Formatter}; use std::slice::Iter; use bitmask::bitmask; -use bstr::BStr; +use bstr::{BStr, BString}; use yara_x_macros::*; pub use crate::ast::span::*; @@ -117,6 +117,7 @@ pub enum MetaValue<'src> { Integer(i64), Float(f64), String(&'src str), + Bytes(BString), } impl<'src> Display for MetaValue<'src> { @@ -126,6 +127,7 @@ impl<'src> Display for MetaValue<'src> { Self::Integer(v) => write!(f, "{}", v), Self::Float(v) => write!(f, "{:.1}", v), Self::String(v) => write!(f, "{}", v), + Self::Bytes(v) => write!(f, "{}", v), } } } diff --git a/parser/src/parser/cst2ast.rs b/parser/src/parser/cst2ast.rs index cc84251f7..eb98496d3 100644 --- a/parser/src/parser/cst2ast.rs +++ b/parser/src/parser/cst2ast.rs @@ -867,7 +867,20 @@ fn meta_from_cst<'src>( GrammarRule::float_lit => { MetaValue::Float(float_lit_from_cst(ctx, value_node)?) } - GrammarRule::string_lit => MetaValue::String(value_node.as_str()), + GrammarRule::string_lit => { + match string_lit_from_cst(ctx, value_node, true)? { + // If the result is a string borrowed directly from the + // source code, we can be sure that it's a valid UTF-8 + // string. + Cow::Borrowed(s) => { + MetaValue::String(unsafe { s.to_str_unchecked() }) + } + // If the result is an owned string is because it contains + // some escaped character, this string is not guaranteed + // to be a valid UTF-8 string. + Cow::Owned(s) => MetaValue::Bytes(s), + } + } rule => unreachable!("{:?}", rule), }; diff --git a/parser/src/parser/tests/testdata/meta.yaml b/parser/src/parser/tests/testdata/meta.yaml index b1041a1b7..de056cb20 100644 --- a/parser/src/parser/tests/testdata/meta.yaml +++ b/parser/src/parser/tests/testdata/meta.yaml @@ -20,7 +20,7 @@ │ ├─ some_float = 2.0 │ ├─ some_bool = true │ ├─ some_bool = false - │ └─ some_string = "foo" + │ └─ some_string = foo └─ condition └─ true From 3508d53cccbdad329c3e7ea51e7956108ad4da61 Mon Sep 17 00:00:00 2001 From: "Victor M. Alvarez" Date: Wed, 22 May 2024 12:31:36 +0200 Subject: [PATCH 04/32] feat: expose rule metadata in Python API --- py/src/lib.rs | 31 ++++++++++++++++++++++++++++++- py/tests/test_api.py | 25 +++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/py/src/lib.rs b/py/src/lib.rs index 702a39af3..ed928a29b 100644 --- a/py/src/lib.rs +++ b/py/src/lib.rs @@ -307,7 +307,7 @@ impl ScanResults { /// Rules that matched during the scan. fn matching_rules(&self) -> Py { Python::with_gil(|py| { - PyTuple::new_bound(py, &self.matching_rules).into() + PyTuple::new_bound(py, &self.matching_rules).unbind() }) } @@ -323,6 +323,7 @@ impl ScanResults { struct Rule { identifier: String, namespace: String, + metadata: Vec>, patterns: Vec>, } @@ -340,6 +341,12 @@ impl Rule { self.namespace.as_str() } + /// Metadata associated to the rule. + #[getter] + fn metadata(&self) -> Py { + Python::with_gil(|py| PyTuple::new_bound(py, &self.metadata).into()) + } + /// Patterns defined by the rule. #[getter] fn patterns(&self) -> Py { @@ -498,6 +505,10 @@ fn rule_to_py(py: Python, rule: yrx::Rule) -> PyResult> { Rule { identifier: rule.identifier().to_string(), namespace: rule.namespace().to_string(), + metadata: rule + .metadata() + .map(|(ident, value)| metadata_to_py(py, ident, value)) + .collect::, _>>()?, patterns: rule .patterns() .map(|pattern| pattern_to_py(py, pattern)) @@ -506,6 +517,24 @@ fn rule_to_py(py: Python, rule: yrx::Rule) -> PyResult> { ) } +fn metadata_to_py( + py: Python, + ident: &str, + metadata: yrx::MetaValue, +) -> PyResult> { + let value = match metadata { + yrx::MetaValue::Integer(v) => v.to_object(py), + yrx::MetaValue::Float(v) => v.to_object(py), + yrx::MetaValue::Bool(v) => v.to_object(py), + yrx::MetaValue::String(v) => v.to_object(py), + yrx::MetaValue::Bytes(v) => v.to_object(py), + }; + + let tuple = PyTuple::new_bound(py, [ident.to_object(py), value]); + + Ok(tuple.unbind()) +} + fn pattern_to_py(py: Python, pattern: yrx::Pattern) -> PyResult> { Py::new( py, diff --git a/py/tests/test_api.py b/py/tests/test_api.py index 23568d8c0..60f20c06b 100644 --- a/py/tests/test_api.py +++ b/py/tests/test_api.py @@ -110,6 +110,31 @@ def test_namespaces(): assert matching_rules[1].patterns[0].matches[0].xor_key is None +def test_metadata(): + rules = yara_x.compile(''' + rule test { + meta: + foo = 1 + bar = 2.0 + baz = true + qux = "qux" + quux = "qu\x00x" + condition: + true + } + ''') + + matching_rules = rules.scan(b'').matching_rules + + assert matching_rules[0].metadata == ( + ("foo", 1), + ("bar", 2.0), + ("baz", True), + ("qux", "qux"), + ("quux", "qu\0x") + ) + + def test_compile_and_scan(): rules = yara_x.compile('rule foo {strings: $a = "foo" condition: $a}') matching_rules = rules.scan(b'foobar').matching_rules From cdd84bd726896e41f52d03d9af73a754057c9169 Mon Sep 17 00:00:00 2001 From: "Victor M. Alvarez" Date: Wed, 22 May 2024 13:00:12 +0200 Subject: [PATCH 05/32] perf(py): avoid creating new tuples when not necessary. Before this change, every time you called a getter that returns a tuple (like Pattern.matches, Rule.patterns, Rule.metadata) the tuple was built again from scratch. This is not necessary, as those tuples are immutable. Now the tuples are created once, and a reference to them is returned every time you need to get them. --- py/src/lib.rs | 62 +++++++++++++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/py/src/lib.rs b/py/src/lib.rs index ed928a29b..982cd013a 100644 --- a/py/src/lib.rs +++ b/py/src/lib.rs @@ -295,7 +295,7 @@ impl Scanner { #[pyclass] struct ScanResults { /// Vector that contains all the rules that matched during the scan. - matching_rules: Vec>, + matching_rules: Py, /// Dictionary where keys are module names and values are other /// dictionaries with the information produced by the corresponding module. module_outputs: Py, @@ -306,9 +306,7 @@ impl ScanResults { #[getter] /// Rules that matched during the scan. fn matching_rules(&self) -> Py { - Python::with_gil(|py| { - PyTuple::new_bound(py, &self.matching_rules).unbind() - }) + Python::with_gil(|py| self.matching_rules.clone_ref(py)) } #[getter] @@ -323,8 +321,8 @@ impl ScanResults { struct Rule { identifier: String, namespace: String, - metadata: Vec>, - patterns: Vec>, + metadata: Py, + patterns: Py, } #[pymethods] @@ -344,13 +342,13 @@ impl Rule { /// Metadata associated to the rule. #[getter] fn metadata(&self) -> Py { - Python::with_gil(|py| PyTuple::new_bound(py, &self.metadata).into()) + Python::with_gil(|py| self.metadata.clone_ref(py)) } /// Patterns defined by the rule. #[getter] fn patterns(&self) -> Py { - Python::with_gil(|py| PyTuple::new_bound(py, &self.patterns).into()) + Python::with_gil(|py| self.patterns.clone_ref(py)) } } @@ -358,7 +356,7 @@ impl Rule { #[pyclass] struct Pattern { identifier: String, - matches: Vec>, + matches: Py, } #[pymethods] @@ -372,7 +370,7 @@ impl Pattern { /// Matches found for this pattern. #[getter] fn matches(&self) -> Py { - Python::with_gil(|py| PyTuple::new_bound(py, &self.matches).into()) + Python::with_gil(|py| self.matches.clone_ref(py)) } } @@ -495,7 +493,10 @@ fn scan_results_to_py( Py::new( py, - ScanResults { matching_rules, module_outputs: module_outputs.into() }, + ScanResults { + matching_rules: PyTuple::new_bound(py, matching_rules).unbind(), + module_outputs: module_outputs.into(), + }, ) } @@ -505,14 +506,19 @@ fn rule_to_py(py: Python, rule: yrx::Rule) -> PyResult> { Rule { identifier: rule.identifier().to_string(), namespace: rule.namespace().to_string(), - metadata: rule - .metadata() - .map(|(ident, value)| metadata_to_py(py, ident, value)) - .collect::, _>>()?, - patterns: rule - .patterns() - .map(|pattern| pattern_to_py(py, pattern)) - .collect::, _>>()?, + metadata: PyTuple::new_bound( + py, + rule.metadata() + .map(|(ident, value)| metadata_to_py(py, ident, value)), + ) + .unbind(), + patterns: PyTuple::new_bound( + py, + rule.patterns() + .map(|pattern| pattern_to_py(py, pattern)) + .collect::, _>>()?, + ) + .unbind(), }, ) } @@ -521,7 +527,7 @@ fn metadata_to_py( py: Python, ident: &str, metadata: yrx::MetaValue, -) -> PyResult> { +) -> Py { let value = match metadata { yrx::MetaValue::Integer(v) => v.to_object(py), yrx::MetaValue::Float(v) => v.to_object(py), @@ -530,9 +536,7 @@ fn metadata_to_py( yrx::MetaValue::Bytes(v) => v.to_object(py), }; - let tuple = PyTuple::new_bound(py, [ident.to_object(py), value]); - - Ok(tuple.unbind()) + PyTuple::new_bound(py, [ident.to_object(py), value]).unbind() } fn pattern_to_py(py: Python, pattern: yrx::Pattern) -> PyResult> { @@ -540,10 +544,14 @@ fn pattern_to_py(py: Python, pattern: yrx::Pattern) -> PyResult> { py, Pattern { identifier: pattern.identifier().to_string(), - matches: pattern - .matches() - .map(|match_| match_to_py(py, match_)) - .collect::, _>>()?, + matches: PyTuple::new_bound( + py, + pattern + .matches() + .map(|match_| match_to_py(py, match_)) + .collect::, _>>()?, + ) + .unbind(), }, ) } From b88f4d9a01f49bb6be2338136bdab1defaf12595 Mon Sep 17 00:00:00 2001 From: "Victor M. Alvarez" Date: Wed, 22 May 2024 13:04:32 +0200 Subject: [PATCH 06/32] docs(py): document the `Rule.metadata` field. --- py/src/lib.rs | 3 ++- site/content/docs/api/python.md | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/py/src/lib.rs b/py/src/lib.rs index 982cd013a..6e179e009 100644 --- a/py/src/lib.rs +++ b/py/src/lib.rs @@ -339,7 +339,8 @@ impl Rule { self.namespace.as_str() } - /// Metadata associated to the rule. + /// A tuple of pairs `(identifier, value)` with the metadata associated to + /// the rule. #[getter] fn metadata(&self) -> Py { Python::with_gil(|py| self.metadata.clone_ref(py)) diff --git a/site/content/docs/api/python.md b/site/content/docs/api/python.md index d2ddc8e43..debc40fcd 100644 --- a/site/content/docs/api/python.md +++ b/site/content/docs/api/python.md @@ -323,6 +323,11 @@ A tuple of [Pattern](#pattern) with every pattern defined by the rule, matching or not. Each pattern contains information about the matches that were found during the scan, if any. +#### .metadata + +A tuple of pairs `(identifier, value)` with the metadata associated to the +rule. + --------- ### Pattern From e48592c81e39f38db72923946189db6e9d593a06 Mon Sep 17 00:00:00 2001 From: "Victor M. Alvarez" Date: Wed, 22 May 2024 15:59:26 +0200 Subject: [PATCH 07/32] tests: add tests covering regexp repetition operators with spaces. --- lib/src/tests/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/src/tests/mod.rs b/lib/src/tests/mod.rs index 295fc6c48..e8156a2a0 100644 --- a/lib/src/tests/mod.rs +++ b/lib/src/tests/mod.rs @@ -1025,6 +1025,9 @@ fn regexp_patterns_1() { pattern_false!(r#"/ab.{1,2}cdef/"#, b"abcdef"); pattern_match!(r#"/abcd.{1,2}ef/"#, b"abcdxef", b"abcdxef"); pattern_match!(r#"/ab.{1,2}cdef/"#, b"abxcdef", b"abxcdef"); + pattern_match!(r#"/ab.{1, 2}cdef/"#, b"abxcdef", b"abxcdef"); + pattern_match!(r#"/ab.{1 ,2}cdef/"#, b"abxcdef", b"abxcdef"); + pattern_match!(r#"/ab.{1 , 2}cdef/"#, b"abxcdef", b"abxcdef"); pattern_match!(r#"/a(.*)*/"#, b"a", b"a"); pattern_match!(r#"/a(.*){2}/"#, b"a", b"a"); pattern_match!(r#"/a(.*){2,4}/"#, b"a", b"a"); From 964a2969ae24c24eb82fed344b0b33faad7fcf33 Mon Sep 17 00:00:00 2001 From: "Victor M. Alvarez" Date: Wed, 22 May 2024 16:21:02 +0200 Subject: [PATCH 08/32] chore: show warning when `build.rs` can not re-generate `src/modules/modules.rs` Instead of panicking when `build.rs` is unable to re-generate the file, show a warning and return without error. --- lib/build.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/build.rs b/lib/build.rs index 8edc7d6a7..551791494 100644 --- a/lib/build.rs +++ b/lib/build.rs @@ -47,7 +47,16 @@ fn generate_module_files(proto_files: Vec) { // #[cfg(feature = "foo_module")] // pub mod foo; // - let mut modules_rs = File::create("src/modules/modules.rs").unwrap(); + let mut modules_rs = match File::create("src/modules/modules.rs") { + Ok(modules_rs) => modules_rs, + Err(err) => { + println!("cargo:warning=`build.rs` was unable to re-generate `lib/src/modules/modules.rs`"); + println!("cargo:warning=due to the following error: {}", err); + println!("cargo:warning=ignore this warning unless you are trying to add new YARA-X modules"); + println!("cargo:warning=to disable the warning set the environment variable YRX_REGENERATE_MODULES_RS=false"); + return; + } + }; write!( modules_rs, From b0b4065e54233f77d2f155ade0fef13db236ec34 Mon Sep 17 00:00:00 2001 From: "Victor M. Alvarez" Date: Wed, 22 May 2024 18:13:28 +0200 Subject: [PATCH 09/32] chore: upgrade `rayon` to version 1.10.0 and `zip` to version 1.3.1 --- Cargo.lock | 69 +++++++++++++++++++++++++++--------------------------- Cargo.toml | 4 ++-- 2 files changed, 37 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 84f603ec4..25a3e4681 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -387,9 +387,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.15.4" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytecount" @@ -619,15 +619,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" -[[package]] -name = "cmake" -version = "0.1.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" -dependencies = [ - "cc", -] - [[package]] name = "colorchoice" version = "1.0.0" @@ -1400,7 +1391,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" dependencies = [ "crc32fast", - "libz-ng-sys", "miniz_oxide", ] @@ -1936,16 +1926,6 @@ dependencies = [ "redox_syscall", ] -[[package]] -name = "libz-ng-sys" -version = "1.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6409efc61b12687963e602df8ecf70e8ddacf95bc6576bcf16e3ac6328083c5" -dependencies = [ - "cmake", - "libc", -] - [[package]] name = "lingua" version = "1.6.2" @@ -2052,6 +2032,12 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "lockfree-object-pool" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" + [[package]] name = "log" version = "0.4.21" @@ -2940,6 +2926,7 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ + "libc", "rand_chacha", "rand_core", ] @@ -3808,12 +3795,6 @@ dependencies = [ "winnow", ] -[[package]] -name = "typed-arena" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" - [[package]] name = "typenum" version = "1.17.0" @@ -4981,41 +4962,61 @@ name = "zeroize" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.55", +] [[package]] name = "zip" -version = "1.1.2" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a23468b4a7a4e9e1e62812f8d1dd614f67f148e2b3faa72f4843bf00b573fd7" +checksum = "1b7a5a9285bd4ee13bdeb3f8a4917eb46557e53f270c783849db8bef37b0ad00" dependencies = [ "aes", "arbitrary", - "byteorder", "bzip2", "constant_time_eq", "crc32fast", "crossbeam-utils", "deflate64", + "displaydoc", "flate2", "hmac", + "indexmap 2.2.6", "lzma-rs", "pbkdf2", + "rand", "sha1", + "thiserror", "time", + "zeroize", "zopfli", "zstd", ] [[package]] name = "zopfli" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c1f48f3508a3a3f2faee01629564400bc12260f6214a056d06a3aaaa6ef0736" +checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" dependencies = [ + "bumpalo", "crc32fast", + "lockfree-object-pool", "log", + "once_cell", "simd-adler32", - "typed-arena", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 451f491cb..a99accdfe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -82,7 +82,7 @@ protobuf-codegen = "3.4.0" protobuf-json-mapping = "3.4.0" protobuf-parse = "3.4.0" protobuf-support = "3.4.0" -rayon = "1.5.3" +rayon = "1.10.0" regex-syntax = "0.8.3" regex-automata = "0.4.6" roxmltree = "0.19.0" @@ -109,7 +109,7 @@ yara-x-macros = { path = "macros", version = "0.3.0" } yara-x-parser = { path = "parser", version = "0.3.0" } yara-x-proto = { path = "proto", version = "0.3.0" } yara-x-proto-yaml = { path = "proto-yaml", version = "0.3.0" } -zip = "1.1.2" +zip = "1.3.1" # Special profile that builds a release binary with link-time optimization. # Compiling with this profile takes a while, but the resulting binary is From eec301a41c49d813d3719a5e786795900ccb5329 Mon Sep 17 00:00:00 2001 From: Jacob Latonis Date: Thu, 23 May 2024 02:06:57 -0600 Subject: [PATCH 10/32] feat: implement dylib and entitlement hashing for macho (#93) Implemented a Mach-O similarity function `dylib_hash()` and `entitlement_hash()` which is similar to imphash or any other attribute hashing mechanism. This will hash dylib entries as defined in: https://github.com/g-les/macho_similarity/blob/main/implementation.md#dylib-hashing. --- Cargo.lock | 4 +- lib/src/modules/add_modules.rs | 28 ++++++------- lib/src/modules/macho/mod.rs | 67 ++++++++++++++++++++++++++++++ lib/src/modules/macho/tests/mod.rs | 55 ++++++++++++++++++++++++ lib/src/modules/modules.rs | 30 ++++++------- lib/src/modules/protos/macho.proto | 8 ++-- 6 files changed, 157 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 25a3e4681..703067f1b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2534,9 +2534,9 @@ dependencies = [ [[package]] name = "pest" -version = "2.7.10" +version = "2.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8" +checksum = "56f8023d0fb78c8e03784ea1c7f3fa36e68a723138990b8d5a47d916b651e7a8" dependencies = [ "memchr", "thiserror", diff --git a/lib/src/modules/add_modules.rs b/lib/src/modules/add_modules.rs index ca9e29331..6b3424ea9 100644 --- a/lib/src/modules/add_modules.rs +++ b/lib/src/modules/add_modules.rs @@ -1,31 +1,31 @@ // File generated automatically by build.rs. Do not edit. { -#[cfg(feature = "string-module")] -add_module!(modules, "string", string, "string.String", Some("string"), Some(string::__main__ as MainFn)); +#[cfg(feature = "test_proto2-module")] +add_module!(modules, "test_proto2", test_proto2, "test_proto2.TestProto2", Some("test_proto2"), Some(test_proto2::__main__ as MainFn)); #[cfg(feature = "macho-module")] add_module!(modules, "macho", macho, "macho.Macho", Some("macho"), Some(macho::__main__ as MainFn)); -#[cfg(feature = "pe-module")] -add_module!(modules, "pe", pe, "pe.PE", Some("pe"), Some(pe::__main__ as MainFn)); -#[cfg(feature = "elf-module")] -add_module!(modules, "elf", elf, "elf.ELF", Some("elf"), Some(elf::__main__ as MainFn)); -#[cfg(feature = "text-module")] -add_module!(modules, "text", text, "text.Text", Some("text"), Some(text::__main__ as MainFn)); -#[cfg(feature = "dotnet-module")] -add_module!(modules, "dotnet", dotnet, "dotnet.Dotnet", Some("dotnet"), Some(dotnet::__main__ as MainFn)); #[cfg(feature = "lnk-module")] add_module!(modules, "lnk", lnk, "lnk.Lnk", Some("lnk"), Some(lnk::__main__ as MainFn)); #[cfg(feature = "hash-module")] add_module!(modules, "hash", hash, "hash.Hash", Some("hash"), Some(hash::__main__ as MainFn)); #[cfg(feature = "magic-module")] add_module!(modules, "magic", magic, "magic.Magic", Some("magic"), Some(magic::__main__ as MainFn)); -#[cfg(feature = "math-module")] -add_module!(modules, "math", math, "math.Math", Some("math"), Some(math::__main__ as MainFn)); -#[cfg(feature = "test_proto2-module")] -add_module!(modules, "test_proto2", test_proto2, "test_proto2.TestProto2", Some("test_proto2"), Some(test_proto2::__main__ as MainFn)); +#[cfg(feature = "text-module")] +add_module!(modules, "text", text, "text.Text", Some("text"), Some(text::__main__ as MainFn)); #[cfg(feature = "time-module")] add_module!(modules, "time", time, "time.Time", Some("time"), Some(time::__main__ as MainFn)); +#[cfg(feature = "dotnet-module")] +add_module!(modules, "dotnet", dotnet, "dotnet.Dotnet", Some("dotnet"), Some(dotnet::__main__ as MainFn)); #[cfg(feature = "test_proto3-module")] add_module!(modules, "test_proto3", test_proto3, "test_proto3.TestProto3", Some("test_proto3"), Some(test_proto3::__main__ as MainFn)); +#[cfg(feature = "pe-module")] +add_module!(modules, "pe", pe, "pe.PE", Some("pe"), Some(pe::__main__ as MainFn)); +#[cfg(feature = "string-module")] +add_module!(modules, "string", string, "string.String", Some("string"), Some(string::__main__ as MainFn)); #[cfg(feature = "console-module")] add_module!(modules, "console", console, "console.Console", Some("console"), Some(console::__main__ as MainFn)); +#[cfg(feature = "elf-module")] +add_module!(modules, "elf", elf, "elf.ELF", Some("elf"), Some(elf::__main__ as MainFn)); +#[cfg(feature = "math-module")] +add_module!(modules, "math", math, "math.Math", Some("math"), Some(math::__main__ as MainFn)); } \ No newline at end of file diff --git a/lib/src/modules/macho/mod.rs b/lib/src/modules/macho/mod.rs index 99b1ae7bf..36c14843c 100644 --- a/lib/src/modules/macho/mod.rs +++ b/lib/src/modules/macho/mod.rs @@ -7,6 +7,8 @@ use crate::modules::prelude::*; use crate::modules::protos::macho::*; +use itertools::Itertools; +use md5::{Digest, Md5}; mod parser; #[cfg(test)] @@ -264,6 +266,71 @@ fn has_rpath(ctx: &ScanContext, rpath: RuntimeString) -> Option { Some(false) } +/// Returns an md5 hash of the dylibs designated in the mach-o binary +#[module_export] +fn dylib_hash(ctx: &mut ScanContext) -> Option { + let macho = ctx.module_output::()?; + let mut md5_hash = Md5::new(); + let mut dylibs_to_hash = &macho.dylibs; + + // if there are not any dylibs in the main Macho, the dylibs of the nested file should be hashed + if dylibs_to_hash.is_empty() && !macho.file.is_empty() { + dylibs_to_hash = &macho.file[0].dylibs; + } + + // we need to check again as the nested file dylibs could be empty too + if dylibs_to_hash.is_empty() { + return None; + } + + let dylibs_to_hash: String = dylibs_to_hash + .iter() + .map(|d| { + std::string::String::from_utf8(d.name.clone().unwrap()) + .unwrap() + .trim() + .to_lowercase() + }) + .unique() + .sorted() + .join(","); + + md5_hash.update(dylibs_to_hash.as_bytes()); + + let digest = format!("{:x}", md5_hash.finalize()); + Some(RuntimeString::new(digest)) +} + +/// Returns an md5 hash of the entitlements designated in the mach-o binary +#[module_export] +fn entitlement_hash(ctx: &mut ScanContext) -> Option { + let macho = ctx.module_output::()?; + let mut md5_hash = Md5::new(); + let mut entitlements_to_hash = &macho.entitlements; + + // if there are not any entitlements in the main Macho, the dylibs of the nested file should be hashed + if entitlements_to_hash.is_empty() && !macho.file.is_empty() { + entitlements_to_hash = &macho.file[0].entitlements; + } + + // we need to check again as the nested file dylibs could be empty too + if entitlements_to_hash.is_empty() { + return None; + } + + let entitlements_str: String = entitlements_to_hash + .iter() + .map(|e| e.trim().to_lowercase()) + .unique() + .sorted() + .join(","); + + md5_hash.update(entitlements_str.as_bytes()); + + let digest = format!("{:x}", md5_hash.finalize()); + Some(RuntimeString::new(digest)) +} + #[module_main] fn main(input: &[u8]) -> Macho { match parser::MachO::parse(input) { diff --git a/lib/src/modules/macho/tests/mod.rs b/lib/src/modules/macho/tests/mod.rs index cbbcb1887..646dc260f 100644 --- a/lib/src/modules/macho/tests/mod.rs +++ b/lib/src/modules/macho/tests/mod.rs @@ -311,4 +311,59 @@ fn test_macho_module() { "#, &chess_macho_data ); + + rule_true!( + r#" + import "macho" + rule macho_test { + condition: + macho.dylib_hash() == "6813ec6aceb392c8a9abe9db8e25d847" + } + "#, + &chess_macho_data + ); + + rule_true!( + r#" + import "macho" + rule macho_test { + condition: + macho.dylib_hash() == "c92070ad210458d5b3e8f048b1578e6d" + } + "#, + &tiny_universal_macho_data + ); + + rule_true!( + r#" + import "macho" + rule macho_test { + condition: + not defined macho.dylib_hash() + } + "#, + &[] + ); + + rule_true!( + r#" + import "macho" + rule macho_test { + condition: + macho.entitlement_hash() == "cc9486efb0ce73ba411715273658da80" + } + "#, + &chess_macho_data + ); + + rule_true!( + r#" + import "macho" + rule macho_test { + condition: + not defined macho.entitlement_hash() + } + "#, + &[] + ); } diff --git a/lib/src/modules/modules.rs b/lib/src/modules/modules.rs index 7ad936dbd..016edda99 100644 --- a/lib/src/modules/modules.rs +++ b/lib/src/modules/modules.rs @@ -1,29 +1,29 @@ // File generated automatically by build.rs. Do not edit. -#[cfg(feature = "string-module")] -mod string; +#[cfg(feature = "test_proto2-module")] +mod test_proto2; #[cfg(feature = "macho-module")] mod macho; -#[cfg(feature = "pe-module")] -mod pe; -#[cfg(feature = "elf-module")] -mod elf; -#[cfg(feature = "text-module")] -mod text; -#[cfg(feature = "dotnet-module")] -mod dotnet; #[cfg(feature = "lnk-module")] mod lnk; #[cfg(feature = "hash-module")] mod hash; #[cfg(feature = "magic-module")] mod magic; -#[cfg(feature = "math-module")] -mod math; -#[cfg(feature = "test_proto2-module")] -mod test_proto2; +#[cfg(feature = "text-module")] +mod text; #[cfg(feature = "time-module")] mod time; +#[cfg(feature = "dotnet-module")] +mod dotnet; #[cfg(feature = "test_proto3-module")] mod test_proto3; +#[cfg(feature = "pe-module")] +mod pe; +#[cfg(feature = "string-module")] +mod string; #[cfg(feature = "console-module")] -mod console; \ No newline at end of file +mod console; +#[cfg(feature = "elf-module")] +mod elf; +#[cfg(feature = "math-module")] +mod math; \ No newline at end of file diff --git a/lib/src/modules/protos/macho.proto b/lib/src/modules/protos/macho.proto index e1bf29739..dadc49a93 100644 --- a/lib/src/modules/protos/macho.proto +++ b/lib/src/modules/protos/macho.proto @@ -41,10 +41,10 @@ message Certificates { } message Dylib { - optional bytes name = 1; - optional uint32 timestamp = 2 [(yaml.field).fmt = "t"]; - optional string compatibility_version = 3; - optional string current_version = 4; + required bytes name = 1; + required uint32 timestamp = 2 [(yaml.field).fmt = "t"]; + required string compatibility_version = 3; + required string current_version = 4; } message DyldInfo { From 5ac725f05fdc3c256ee4557af6e2088cd4d399ad Mon Sep 17 00:00:00 2001 From: "Victor M. Alvarez" Date: Thu, 23 May 2024 10:13:43 +0200 Subject: [PATCH 11/32] refactor: make `dylib_hash` panic-free. --- lib/src/modules/add_modules.rs | 28 ++++++++++++++-------------- lib/src/modules/macho/mod.rs | 18 +++++++++++------- lib/src/modules/modules.rs | 30 +++++++++++++++--------------- 3 files changed, 40 insertions(+), 36 deletions(-) diff --git a/lib/src/modules/add_modules.rs b/lib/src/modules/add_modules.rs index 6b3424ea9..ca9e29331 100644 --- a/lib/src/modules/add_modules.rs +++ b/lib/src/modules/add_modules.rs @@ -1,31 +1,31 @@ // File generated automatically by build.rs. Do not edit. { -#[cfg(feature = "test_proto2-module")] -add_module!(modules, "test_proto2", test_proto2, "test_proto2.TestProto2", Some("test_proto2"), Some(test_proto2::__main__ as MainFn)); +#[cfg(feature = "string-module")] +add_module!(modules, "string", string, "string.String", Some("string"), Some(string::__main__ as MainFn)); #[cfg(feature = "macho-module")] add_module!(modules, "macho", macho, "macho.Macho", Some("macho"), Some(macho::__main__ as MainFn)); +#[cfg(feature = "pe-module")] +add_module!(modules, "pe", pe, "pe.PE", Some("pe"), Some(pe::__main__ as MainFn)); +#[cfg(feature = "elf-module")] +add_module!(modules, "elf", elf, "elf.ELF", Some("elf"), Some(elf::__main__ as MainFn)); +#[cfg(feature = "text-module")] +add_module!(modules, "text", text, "text.Text", Some("text"), Some(text::__main__ as MainFn)); +#[cfg(feature = "dotnet-module")] +add_module!(modules, "dotnet", dotnet, "dotnet.Dotnet", Some("dotnet"), Some(dotnet::__main__ as MainFn)); #[cfg(feature = "lnk-module")] add_module!(modules, "lnk", lnk, "lnk.Lnk", Some("lnk"), Some(lnk::__main__ as MainFn)); #[cfg(feature = "hash-module")] add_module!(modules, "hash", hash, "hash.Hash", Some("hash"), Some(hash::__main__ as MainFn)); #[cfg(feature = "magic-module")] add_module!(modules, "magic", magic, "magic.Magic", Some("magic"), Some(magic::__main__ as MainFn)); -#[cfg(feature = "text-module")] -add_module!(modules, "text", text, "text.Text", Some("text"), Some(text::__main__ as MainFn)); +#[cfg(feature = "math-module")] +add_module!(modules, "math", math, "math.Math", Some("math"), Some(math::__main__ as MainFn)); +#[cfg(feature = "test_proto2-module")] +add_module!(modules, "test_proto2", test_proto2, "test_proto2.TestProto2", Some("test_proto2"), Some(test_proto2::__main__ as MainFn)); #[cfg(feature = "time-module")] add_module!(modules, "time", time, "time.Time", Some("time"), Some(time::__main__ as MainFn)); -#[cfg(feature = "dotnet-module")] -add_module!(modules, "dotnet", dotnet, "dotnet.Dotnet", Some("dotnet"), Some(dotnet::__main__ as MainFn)); #[cfg(feature = "test_proto3-module")] add_module!(modules, "test_proto3", test_proto3, "test_proto3.TestProto3", Some("test_proto3"), Some(test_proto3::__main__ as MainFn)); -#[cfg(feature = "pe-module")] -add_module!(modules, "pe", pe, "pe.PE", Some("pe"), Some(pe::__main__ as MainFn)); -#[cfg(feature = "string-module")] -add_module!(modules, "string", string, "string.String", Some("string"), Some(string::__main__ as MainFn)); #[cfg(feature = "console-module")] add_module!(modules, "console", console, "console.Console", Some("console"), Some(console::__main__ as MainFn)); -#[cfg(feature = "elf-module")] -add_module!(modules, "elf", elf, "elf.ELF", Some("elf"), Some(elf::__main__ as MainFn)); -#[cfg(feature = "math-module")] -add_module!(modules, "math", math, "math.Math", Some("math"), Some(math::__main__ as MainFn)); } \ No newline at end of file diff --git a/lib/src/modules/macho/mod.rs b/lib/src/modules/macho/mod.rs index 36c14843c..93d4e8414 100644 --- a/lib/src/modules/macho/mod.rs +++ b/lib/src/modules/macho/mod.rs @@ -273,7 +273,8 @@ fn dylib_hash(ctx: &mut ScanContext) -> Option { let mut md5_hash = Md5::new(); let mut dylibs_to_hash = &macho.dylibs; - // if there are not any dylibs in the main Macho, the dylibs of the nested file should be hashed + // if there are not any dylibs in the main Macho, the dylibs of the nested + // file should be hashed if dylibs_to_hash.is_empty() && !macho.file.is_empty() { dylibs_to_hash = &macho.file[0].dylibs; } @@ -285,11 +286,13 @@ fn dylib_hash(ctx: &mut ScanContext) -> Option { let dylibs_to_hash: String = dylibs_to_hash .iter() - .map(|d| { - std::string::String::from_utf8(d.name.clone().unwrap()) - .unwrap() - .trim() - .to_lowercase() + .filter_map(|d| { + Some( + String::from_utf8(d.name.clone()?) + .unwrap() + .trim() + .to_lowercase(), + ) }) .unique() .sorted() @@ -308,7 +311,8 @@ fn entitlement_hash(ctx: &mut ScanContext) -> Option { let mut md5_hash = Md5::new(); let mut entitlements_to_hash = &macho.entitlements; - // if there are not any entitlements in the main Macho, the dylibs of the nested file should be hashed + // if there are not any entitlements in the main Macho, the dylibs of the + // nested file should be hashed if entitlements_to_hash.is_empty() && !macho.file.is_empty() { entitlements_to_hash = &macho.file[0].entitlements; } diff --git a/lib/src/modules/modules.rs b/lib/src/modules/modules.rs index 016edda99..7ad936dbd 100644 --- a/lib/src/modules/modules.rs +++ b/lib/src/modules/modules.rs @@ -1,29 +1,29 @@ // File generated automatically by build.rs. Do not edit. -#[cfg(feature = "test_proto2-module")] -mod test_proto2; +#[cfg(feature = "string-module")] +mod string; #[cfg(feature = "macho-module")] mod macho; +#[cfg(feature = "pe-module")] +mod pe; +#[cfg(feature = "elf-module")] +mod elf; +#[cfg(feature = "text-module")] +mod text; +#[cfg(feature = "dotnet-module")] +mod dotnet; #[cfg(feature = "lnk-module")] mod lnk; #[cfg(feature = "hash-module")] mod hash; #[cfg(feature = "magic-module")] mod magic; -#[cfg(feature = "text-module")] -mod text; +#[cfg(feature = "math-module")] +mod math; +#[cfg(feature = "test_proto2-module")] +mod test_proto2; #[cfg(feature = "time-module")] mod time; -#[cfg(feature = "dotnet-module")] -mod dotnet; #[cfg(feature = "test_proto3-module")] mod test_proto3; -#[cfg(feature = "pe-module")] -mod pe; -#[cfg(feature = "string-module")] -mod string; #[cfg(feature = "console-module")] -mod console; -#[cfg(feature = "elf-module")] -mod elf; -#[cfg(feature = "math-module")] -mod math; \ No newline at end of file +mod console; \ No newline at end of file From e69ff75780090c6d006522dea1c44aa045558e9e Mon Sep 17 00:00:00 2001 From: "Victor M. Alvarez" Date: Thu, 23 May 2024 10:41:49 +0200 Subject: [PATCH 12/32] fix: don't allow empty DLL names --- lib/src/modules/pe/parser.rs | 4 ++++ ...735bfcf2125be8eb1f3dd468eeb56a1c615c34f95bf38cb58b7d3a.out | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/src/modules/pe/parser.rs b/lib/src/modules/pe/parser.rs index 83b1f4a40..096ba20dc 100644 --- a/lib/src/modules/pe/parser.rs +++ b/lib/src/modules/pe/parser.rs @@ -2172,6 +2172,10 @@ impl<'a> PE<'a> { // limit of 256 bytes, though. let dll_name = self.str_at_rva(rva)?; + if dll_name.len() == 0 { + return None; + } + for c in dll_name.chars() { if c.is_ascii_control() { return None; diff --git a/lib/src/modules/pe/tests/testdata/c7dce6c950735bfcf2125be8eb1f3dd468eeb56a1c615c34f95bf38cb58b7d3a.out b/lib/src/modules/pe/tests/testdata/c7dce6c950735bfcf2125be8eb1f3dd468eeb56a1c615c34f95bf38cb58b7d3a.out index ea2d5cea0..2b9279fe3 100644 --- a/lib/src/modules/pe/tests/testdata/c7dce6c950735bfcf2125be8eb1f3dd468eeb56a1c615c34f95bf38cb58b7d3a.out +++ b/lib/src/modules/pe/tests/testdata/c7dce6c950735bfcf2125be8eb1f3dd468eeb56a1c615c34f95bf38cb58b7d3a.out @@ -22,7 +22,6 @@ checksum: 174636 base_of_code: 4096 entry_point: 35760 entry_point_raw: 38832 -dll_name: "" export_timestamp: 4294967295 # 2106-02-07 06:28:15 UTC section_alignment: 4096 file_alignment: 512 From 8d160a5a66a25938141656d986b538e1cb758cc0 Mon Sep 17 00:00:00 2001 From: "Victor M. Alvarez" Date: Thu, 23 May 2024 10:50:44 +0200 Subject: [PATCH 13/32] style: minor style change --- lib/src/modules/pe/parser.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/modules/pe/parser.rs b/lib/src/modules/pe/parser.rs index 096ba20dc..db5f454ba 100644 --- a/lib/src/modules/pe/parser.rs +++ b/lib/src/modules/pe/parser.rs @@ -2172,7 +2172,7 @@ impl<'a> PE<'a> { // limit of 256 bytes, though. let dll_name = self.str_at_rva(rva)?; - if dll_name.len() == 0 { + if dll_name.is_empty() { return None; } From 4f105425cdc7e81b660257a54e3933b6671176c6 Mon Sep 17 00:00:00 2001 From: "Victor M. Alvarez" Date: Thu, 23 May 2024 11:02:30 +0200 Subject: [PATCH 14/32] fix(capi): avoid panic when `yrx_rule_patterns` receives invalid pointer. --- capi/include/yara_x.h | 2 ++ capi/src/lib.rs | 9 ++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/capi/include/yara_x.h b/capi/include/yara_x.h index b328e1721..12779344b 100644 --- a/capi/include/yara_x.h +++ b/capi/include/yara_x.h @@ -179,6 +179,8 @@ enum YRX_RESULT yrx_rule_namespace(const struct YRX_RULE *rule, // in the data it matched. The patterns are represented by a [`YRX_PATTERNS`] // object that must be destroyed with [`yrx_patterns_destroy`] when not needed // anymore. +// +// This function returns a null pointer when `rule` is null. struct YRX_PATTERNS *yrx_rule_patterns(const struct YRX_RULE *rule); // Destroys a [`YRX_PATTERNS`] object. diff --git a/capi/src/lib.rs b/capi/src/lib.rs index 7ed61133d..d6419cad5 100644 --- a/capi/src/lib.rs +++ b/capi/src/lib.rs @@ -358,11 +358,18 @@ pub unsafe extern "C" fn yrx_rule_namespace( /// in the data it matched. The patterns are represented by a [`YRX_PATTERNS`] /// object that must be destroyed with [`yrx_patterns_destroy`] when not needed /// anymore. +/// +/// This function returns a null pointer when `rule` is null. #[no_mangle] pub unsafe extern "C" fn yrx_rule_patterns( rule: *const YRX_RULE, ) -> *mut YRX_PATTERNS { - let patterns_iter = rule.as_ref().unwrap().0.patterns(); + let patterns_iter = if let Some(rule) = rule.as_ref() { + rule.0.patterns() + } else { + return std::ptr::null_mut(); + }; + let mut patterns = Vec::with_capacity(patterns_iter.len()); for pattern in patterns_iter { From 710ed69eca5b030f4c4351f8e97c0ce4cf0f97e7 Mon Sep 17 00:00:00 2001 From: "Victor M. Alvarez" Date: Thu, 23 May 2024 13:23:23 +0200 Subject: [PATCH 15/32] feat: implement the`Metadata.into_json` API This function returns the metadata associated to a rule as a `serde_json::Value`. --- lib/src/scanner/mod.rs | 77 ++++++++++++++++++++++++++++++++++------ lib/src/scanner/tests.rs | 21 ++++++++--- 2 files changed, 83 insertions(+), 15 deletions(-) diff --git a/lib/src/scanner/mod.rs b/lib/src/scanner/mod.rs index dce481ed9..9aa569574 100644 --- a/lib/src/scanner/mod.rs +++ b/lib/src/scanner/mod.rs @@ -23,6 +23,7 @@ use fmmap::{MmapFile, MmapFileExt}; use indexmap::IndexMap; use protobuf::{CodedInputStream, MessageDyn}; use rustc_hash::{FxHashMap, FxHashSet}; +use serde::Serialize; use thiserror::Error; use wasmtime::{ AsContext, AsContextMut, Global, GlobalType, MemoryType, Mutability, @@ -968,18 +969,9 @@ impl<'a, 'r> Rule<'a, 'r> { } } -/// Iterator that returns the metadata associated to a rule. -/// -/// The iterator returns (&str, [`MetaValue`]) pairs, where the first item -/// is the identifier, and the second one the metadata value. -pub struct Metadata<'a, 'r> { - ctx: &'a ScanContext<'r>, - iterator: Iter<'a, (IdentId, compiler::MetaValue)>, - len: usize, -} - /// A metadata value. -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Serialize)] +#[serde(untagged)] pub enum MetaValue<'r> { /// Integer value. Integer(i64), @@ -994,6 +986,69 @@ pub enum MetaValue<'r> { Bytes(&'r BStr), } +/// Iterator that returns the metadata associated to a rule. +/// +/// The iterator returns (`&str`, [`MetaValue`]) pairs, where the first item +/// is the identifier, and the second one the metadata value. +pub struct Metadata<'a, 'r> { + ctx: &'a ScanContext<'r>, + iterator: Iter<'a, (IdentId, compiler::MetaValue)>, + len: usize, +} + +impl<'a, 'r> Metadata<'a, 'r> { + /// Returns the metadata as a [`serde_json::Value`]. + /// + /// The returned value is an array of tuples `(ident, value)` with all + /// the metadata associated to the rule. + /// + /// ```rust + /// # use yara_x; + /// let rules = yara_x::compile(r#" + /// rule test { + /// meta: + /// some_int = 1 + /// some_bool = true + /// some_str = "foo" + /// some_bytes = "\x01\x02\x03" + /// condition: + /// true + /// } + /// "#).unwrap(); + /// + /// let mut scanner = yara_x::Scanner::new(&rules); + /// + /// let scan_results = scanner + /// .scan(&[]) + /// .unwrap(); + /// + /// let matching_rule = scan_results + /// .matching_rules() + /// .next() + /// .unwrap(); + /// + /// assert_eq!( + /// matching_rule.metadata().into_json(), + /// serde_json::json!([ + /// ("some_int", 1), + /// ("some_bool", true), + /// ("some_str", "foo"), + /// ("some_bytes", [0x01, 0x02, 0x03]), + /// ]) + /// ); + /// ``` + pub fn into_json(self) -> serde_json::Value { + let v: Vec<(&'r str, MetaValue<'r>)> = self.collect(); + serde_json::value::to_value(v).unwrap() + } + + /// Returns `true` if the rule doesn't have any metadata. + #[inline] + pub fn is_empty(&self) -> bool { + self.iterator.len() == 0 + } +} + impl<'a, 'r> Iterator for Metadata<'a, 'r> { type Item = (&'r str, MetaValue<'r>); diff --git a/lib/src/scanner/tests.rs b/lib/src/scanner/tests.rs index ee74ae05f..ad32f030f 100644 --- a/lib/src/scanner/tests.rs +++ b/lib/src/scanner/tests.rs @@ -1,6 +1,7 @@ use pretty_assertions::assert_eq; use protobuf::MessageDyn; use protobuf::{Message, MessageFull}; +use serde_json::json; use crate::mods; use crate::scanner::{MetaValue, Scanner}; @@ -97,11 +98,10 @@ fn metadata() { let mut metas = vec![]; let mut scanner = Scanner::new(&rules); let results = scanner.scan(b"").expect("scan should not fail"); + let matching_rule = results.matching_rules().next().unwrap(); - for matching_rule in results.matching_rules() { - for meta in matching_rule.metadata() { - metas.push(meta) - } + for meta in matching_rule.metadata() { + metas.push(meta) } assert_eq!( @@ -113,6 +113,19 @@ fn metadata() { ("qux", MetaValue::String("qux")), ("quux", MetaValue::Bytes(b"qu\0x".into())), ] + ); + + let meta_json = matching_rule.metadata().into_json(); + + assert_eq!( + meta_json, + json!([ + ("foo", 1), + ("bar", 2.0), + ("baz", true), + ("qux", "qux"), + ("quux", [113, 117, 0, 120]) + ]) ) } From 9e489bd9f5d870d47c02ec269096c17ce2df8bbe Mon Sep 17 00:00:00 2001 From: "Victor M. Alvarez" Date: Thu, 23 May 2024 13:30:03 +0200 Subject: [PATCH 16/32] feat(capi): implement the `yrx_rule_metadata_as_json` function. --- capi/include/yara_x.h | 18 +++++++++++++++++- capi/src/lib.rs | 38 +++++++++++++++++++++++++++++++++++++- capi/src/tests.rs | 26 ++++++++++++++++++++------ 3 files changed, 74 insertions(+), 8 deletions(-) diff --git a/capi/include/yara_x.h b/capi/include/yara_x.h index 12779344b..d5c7fcd9c 100644 --- a/capi/include/yara_x.h +++ b/capi/include/yara_x.h @@ -55,6 +55,8 @@ typedef enum YRX_RESULT { INVALID_UTF8, // An error occurred while serializing/deserializing YARA rules. SERIALIZATION_ERROR, + // An error returned when a rule doesn't have any metadata. + NO_METADATA, } YRX_RESULT; // A compiler that takes YARA source code and produces compiled rules. @@ -135,7 +137,7 @@ enum YRX_RESULT yrx_compile(const char *src, // that contains the serialized rules. This structure has a pointer to the // data itself, and its length. // -// This [`YRX_BUFFER`] must be destroyed with [`yrx_buffer_destroy`]. +// The [`YRX_BUFFER`] must be destroyed with [`yrx_buffer_destroy`]. enum YRX_RESULT yrx_rules_serialize(struct YRX_RULES *rules, struct YRX_BUFFER **buf); @@ -173,6 +175,20 @@ enum YRX_RESULT yrx_rule_namespace(const struct YRX_RULE *rule, const uint8_t **ns, size_t *len); +// Returns the rule metadata encoded as JSON. +// +// In the address indicated by the `buf` pointer, the function will copy a +// `YRX_BUFFER*` pointer. The `YRX_BUFFER` structure represents a buffer +// that contains the metadata encoded as JSON. This structure has a pointer +// to the data itself, and its length. +// +// The [`YRX_BUFFER`] must be destroyed with [`yrx_buffer_destroy`]. +// +// If the rule doesn't have any metadata, this function returns +// [`YRX_RESULT::NO_METADATA`]. +enum YRX_RESULT yrx_rule_metadata_as_json(const struct YRX_RULE *rule, + struct YRX_BUFFER **buf); + // Returns all the patterns defined by a rule. // // Each pattern contains information about whether it matched or not, and where diff --git a/capi/src/lib.rs b/capi/src/lib.rs index d6419cad5..8a3de5b12 100644 --- a/capi/src/lib.rs +++ b/capi/src/lib.rs @@ -134,6 +134,8 @@ pub enum YRX_RESULT { INVALID_UTF8, /// An error occurred while serializing/deserializing YARA rules. SERIALIZATION_ERROR, + /// An error returned when a rule doesn't have any metadata. + NO_METADATA, } /// A set of compiled YARA rules. @@ -248,7 +250,7 @@ pub unsafe extern "C" fn yrx_compile( /// that contains the serialized rules. This structure has a pointer to the /// data itself, and its length. /// -/// This [`YRX_BUFFER`] must be destroyed with [`yrx_buffer_destroy`]. +/// The [`YRX_BUFFER`] must be destroyed with [`yrx_buffer_destroy`]. #[no_mangle] pub unsafe extern "C" fn yrx_rules_serialize( rules: *mut YRX_RULES, @@ -352,6 +354,40 @@ pub unsafe extern "C" fn yrx_rule_namespace( } } +/// Returns the rule metadata encoded as JSON. +/// +/// In the address indicated by the `buf` pointer, the function will copy a +/// `YRX_BUFFER*` pointer. The `YRX_BUFFER` structure represents a buffer +/// that contains the metadata encoded as JSON. This structure has a pointer +/// to the data itself, and its length. +/// +/// The [`YRX_BUFFER`] must be destroyed with [`yrx_buffer_destroy`]. +/// +/// If the rule doesn't have any metadata, this function returns +/// [`YRX_RESULT::NO_METADATA`]. +#[no_mangle] +pub unsafe extern "C" fn yrx_rule_metadata_as_json( + rule: *const YRX_RULE, + buf: &mut *mut YRX_BUFFER, +) -> YRX_RESULT { + if let Some(rule) = rule.as_ref() { + let metadata = rule.0.metadata(); + if metadata.is_empty() { + return YRX_RESULT::NO_METADATA; + } + let json = metadata.into_json().to_string(); + let mut json = ManuallyDrop::new(json); + *buf = Box::into_raw(Box::new(YRX_BUFFER { + data: json.as_mut_ptr(), + length: json.len(), + })); + LAST_ERROR.set(None); + YRX_RESULT::SUCCESS + } else { + YRX_RESULT::INVALID_ARGUMENT + } +} + /// Returns all the patterns defined by a rule. /// /// Each pattern contains information about whether it matched or not, and where diff --git a/capi/src/tests.rs b/capi/src/tests.rs index a4ecbe532..4bafd319c 100644 --- a/capi/src/tests.rs +++ b/capi/src/tests.rs @@ -6,14 +6,16 @@ use crate::compiler::{ }; use crate::{ yrx_buffer_destroy, yrx_last_error, yrx_patterns_destroy, - yrx_rule_identifier, yrx_rule_namespace, yrx_rule_patterns, - yrx_rules_deserialize, yrx_rules_destroy, yrx_rules_serialize, - yrx_scanner_create, yrx_scanner_destroy, yrx_scanner_on_matching_rule, - yrx_scanner_scan, yrx_scanner_set_global_bool, - yrx_scanner_set_global_float, yrx_scanner_set_global_int, - yrx_scanner_set_global_str, yrx_scanner_set_timeout, YRX_BUFFER, YRX_RULE, + yrx_rule_identifier, yrx_rule_metadata_as_json, yrx_rule_namespace, + yrx_rule_patterns, yrx_rules_deserialize, yrx_rules_destroy, + yrx_rules_serialize, yrx_scanner_create, yrx_scanner_destroy, + yrx_scanner_on_matching_rule, yrx_scanner_scan, + yrx_scanner_set_global_bool, yrx_scanner_set_global_float, + yrx_scanner_set_global_int, yrx_scanner_set_global_str, + yrx_scanner_set_timeout, YRX_BUFFER, YRX_RULE, }; use std::ffi::{c_void, CString}; +use std::slice; extern "C" fn callback(rule: *const YRX_RULE, user_data: *mut c_void) { let mut ptr = std::ptr::null(); @@ -23,6 +25,16 @@ extern "C" fn callback(rule: *const YRX_RULE, user_data: *mut c_void) { yrx_rule_namespace(rule, &mut ptr, &mut len); yrx_rule_identifier(rule, &mut ptr, &mut len); + let mut metadata: *mut YRX_BUFFER = std::ptr::null_mut(); + yrx_rule_metadata_as_json(rule, &mut metadata); + + let json = slice::from_raw_parts((*metadata).data, (*metadata).length); + let json_str = String::from_utf8(json.to_vec()).unwrap(); + + assert_eq!(json_str.as_str(), r#"[["some_int",1]]"#); + + yrx_buffer_destroy(metadata); + let patterns = yrx_rule_patterns(rule); assert_eq!((*patterns).num_patterns, 1); yrx_patterns_destroy(patterns); @@ -41,6 +53,8 @@ fn capi() { let src = CString::new( b"rule test {\ + meta: \ + some_int = 1 \ strings: \ $foo = \"foo\" \ condition: \ From 9f90eaab37a764cc90c40881fc60e448483bb7bf Mon Sep 17 00:00:00 2001 From: "Victor M. Alvarez" Date: Thu, 23 May 2024 20:08:57 +0200 Subject: [PATCH 17/32] feat(capi): implement APIs for obtaining rule's metadata. Remove `yrx_rule_metadata_as_json`. --- capi/include/yara_x.h | 59 ++++++++++--- capi/src/lib.rs | 189 ++++++++++++++++++++++++++++++++++++------ capi/src/tests.rs | 28 +++---- 3 files changed, 220 insertions(+), 56 deletions(-) diff --git a/capi/include/yara_x.h b/capi/include/yara_x.h index d5c7fcd9c..fab44375f 100644 --- a/capi/include/yara_x.h +++ b/capi/include/yara_x.h @@ -33,6 +33,15 @@ // constructs that YARA-X doesn't accept by default. #define YRX_RELAXED_RE_SYNTAX 2 +// Metadata value types. +typedef enum YRX_METADATA_VALUE_TYPE { + INTEGER, + FLOAT, + BOOLEAN, + STRING, + BYTES, +} YRX_METADATA_VALUE_TYPE; + // Error codes returned by functions in this API. typedef enum YRX_RESULT { // Everything was OK. @@ -79,6 +88,41 @@ typedef struct YRX_BUFFER { size_t length; } YRX_BUFFER; +// Represents a metadata value that contains raw bytes. +typedef struct YRX_METADATA_BYTES { + // Number of bytes. + size_t length; + // Pointer to the bytes. + uint8_t *data; +} YRX_METADATA_BYTES; + +// Metadata value. +typedef union YRX_METADATA_VALUE { + int64_t integer; + double float; + bool boolean; + char *string; + struct YRX_METADATA_BYTES bytes; +} YRX_METADATA_VALUE; + +// A metadata entry. +typedef struct YRX_METADATA_ENTRY { + // Metadata identifier. + char *identifier; + enum YRX_METADATA_VALUE_TYPE value_type; + union YRX_METADATA_VALUE value; +} YRX_METADATA_ENTRY; + +// Represents the metadata associated to a rule. +typedef struct YRX_METADATA { + // Number of metadata entries. + size_t num_entries; + // Pointer to an array of YRX_METADATA_ENTRY structures. The array has + // num_entries items. If num_entries is zero this pointer is invalid + // and should not be de-referenced. + struct YRX_METADATA_ENTRY *entries; +} YRX_METADATA; + // Contains information about a pattern match. typedef struct YRX_MATCH { // Offset within the data where the match occurred. @@ -175,19 +219,8 @@ enum YRX_RESULT yrx_rule_namespace(const struct YRX_RULE *rule, const uint8_t **ns, size_t *len); -// Returns the rule metadata encoded as JSON. -// -// In the address indicated by the `buf` pointer, the function will copy a -// `YRX_BUFFER*` pointer. The `YRX_BUFFER` structure represents a buffer -// that contains the metadata encoded as JSON. This structure has a pointer -// to the data itself, and its length. -// -// The [`YRX_BUFFER`] must be destroyed with [`yrx_buffer_destroy`]. -// -// If the rule doesn't have any metadata, this function returns -// [`YRX_RESULT::NO_METADATA`]. -enum YRX_RESULT yrx_rule_metadata_as_json(const struct YRX_RULE *rule, - struct YRX_BUFFER **buf); +// Destroys a [`YRX_METADATA`] object. +void yrx_metadata_destroy(struct YRX_METADATA *metadata); // Returns all the patterns defined by a rule. // diff --git a/capi/src/lib.rs b/capi/src/lib.rs index 8a3de5b12..21a2d5f6e 100644 --- a/capi/src/lib.rs +++ b/capi/src/lib.rs @@ -144,6 +144,88 @@ pub struct YRX_RULES(yara_x::Rules); /// A single YARA rule. pub struct YRX_RULE<'a, 'r>(yara_x::Rule<'a, 'r>); +/// Represents the metadata associated to a rule. +#[repr(C)] +pub struct YRX_METADATA { + /// Number of metadata entries. + num_entries: usize, + /// Pointer to an array of YRX_METADATA_ENTRY structures. The array has + /// num_entries items. If num_entries is zero this pointer is invalid + /// and should not be de-referenced. + entries: *mut YRX_METADATA_ENTRY, +} + +impl Drop for YRX_METADATA { + fn drop(&mut self) { + unsafe { + drop(Box::from_raw(slice_from_raw_parts_mut( + self.entries, + self.num_entries, + ))); + } + } +} + +/// Metadata value types. +#[repr(C)] +#[allow(missing_docs)] +pub enum YRX_METADATA_VALUE_TYPE { + INTEGER, + FLOAT, + BOOLEAN, + STRING, + BYTES, +} + +/// Represents a metadata value that contains raw bytes. +#[derive(Copy, Clone)] +#[repr(C)] +pub struct YRX_METADATA_BYTES { + /// Number of bytes. + length: usize, + /// Pointer to the bytes. + data: *mut u8, +} + +/// Metadata value. +#[repr(C)] +union YRX_METADATA_VALUE { + integer: i64, + float: f64, + boolean: bool, + string: *mut c_char, + bytes: YRX_METADATA_BYTES, +} + +/// A metadata entry. +#[repr(C)] +pub struct YRX_METADATA_ENTRY { + /// Metadata identifier. + identifier: *mut c_char, + value_type: YRX_METADATA_VALUE_TYPE, + value: YRX_METADATA_VALUE, +} + +impl Drop for YRX_METADATA_ENTRY { + fn drop(&mut self) { + unsafe { + drop(CString::from_raw(self.identifier)); + match self.value_type { + YRX_METADATA_VALUE_TYPE::STRING => { + drop(CString::from_raw(self.value.string)); + } + YRX_METADATA_VALUE_TYPE::BYTES => { + drop(Box::from_raw(slice_from_raw_parts_mut( + self.value.bytes.data, + self.value.bytes.length, + ))); + } + _ => {} + } + } + } +} + /// A set of patterns declared in a YARA rule. #[repr(C)] pub struct YRX_PATTERNS { @@ -354,38 +436,91 @@ pub unsafe extern "C" fn yrx_rule_namespace( } } -/// Returns the rule metadata encoded as JSON. -/// -/// In the address indicated by the `buf` pointer, the function will copy a -/// `YRX_BUFFER*` pointer. The `YRX_BUFFER` structure represents a buffer -/// that contains the metadata encoded as JSON. This structure has a pointer -/// to the data itself, and its length. +/// Returns the metadata associated to a rule. /// -/// The [`YRX_BUFFER`] must be destroyed with [`yrx_buffer_destroy`]. +/// The metadata is represented by a [`YRX_METADATA`] object that must be +/// destroyed with [`yrx_metadata_destroy`] when not needed anymore. /// -/// If the rule doesn't have any metadata, this function returns -/// [`YRX_RESULT::NO_METADATA`]. -#[no_mangle] -pub unsafe extern "C" fn yrx_rule_metadata_as_json( +/// This function returns a null pointer when `rule` is null or the +/// rule doesn't have any metadata. +pub unsafe extern "C" fn yrx_rule_metadata( rule: *const YRX_RULE, - buf: &mut *mut YRX_BUFFER, -) -> YRX_RESULT { - if let Some(rule) = rule.as_ref() { - let metadata = rule.0.metadata(); - if metadata.is_empty() { - return YRX_RESULT::NO_METADATA; - } - let json = metadata.into_json().to_string(); - let mut json = ManuallyDrop::new(json); - *buf = Box::into_raw(Box::new(YRX_BUFFER { - data: json.as_mut_ptr(), - length: json.len(), - })); - LAST_ERROR.set(None); - YRX_RESULT::SUCCESS +) -> *mut YRX_METADATA { + let metadata = if let Some(rule) = rule.as_ref() { + rule.0.metadata() } else { - YRX_RESULT::INVALID_ARGUMENT + return std::ptr::null_mut(); + }; + + if metadata.is_empty() { + return std::ptr::null_mut(); + } + + let mut entries = Vec::with_capacity(metadata.len()); + + for (identifier, value) in metadata { + let identifier = CString::new(identifier).unwrap().into_raw(); + + match value { + yara_x::MetaValue::Integer(v) => { + entries.push(YRX_METADATA_ENTRY { + identifier, + value_type: YRX_METADATA_VALUE_TYPE::INTEGER, + value: YRX_METADATA_VALUE { integer: v }, + }); + } + yara_x::MetaValue::Float(v) => { + entries.push(YRX_METADATA_ENTRY { + identifier, + value_type: YRX_METADATA_VALUE_TYPE::FLOAT, + value: YRX_METADATA_VALUE { float: v }, + }); + } + yara_x::MetaValue::Bool(v) => { + entries.push(YRX_METADATA_ENTRY { + identifier, + value_type: YRX_METADATA_VALUE_TYPE::BOOLEAN, + value: YRX_METADATA_VALUE { boolean: v }, + }); + } + yara_x::MetaValue::String(v) => { + entries.push(YRX_METADATA_ENTRY { + identifier, + value_type: YRX_METADATA_VALUE_TYPE::STRING, + value: YRX_METADATA_VALUE { + string: CString::new(v).unwrap().into_raw(), + }, + }); + } + yara_x::MetaValue::Bytes(v) => { + let v = v.to_vec().into_boxed_slice(); + let mut v = ManuallyDrop::new(v); + entries.push(YRX_METADATA_ENTRY { + identifier, + value_type: YRX_METADATA_VALUE_TYPE::BYTES, + value: YRX_METADATA_VALUE { + bytes: YRX_METADATA_BYTES { + data: v.as_mut_ptr(), + length: v.len(), + }, + }, + }); + } + }; } + + let mut entries = ManuallyDrop::new(entries); + + Box::into_raw(Box::new(YRX_METADATA { + num_entries: entries.len(), + entries: entries.as_mut_ptr(), + })) +} + +/// Destroys a [`YRX_METADATA`] object. +#[no_mangle] +pub unsafe extern "C" fn yrx_metadata_destroy(metadata: *mut YRX_METADATA) { + drop(Box::from_raw(metadata)); } /// Returns all the patterns defined by a rule. diff --git a/capi/src/tests.rs b/capi/src/tests.rs index 4bafd319c..c020b3035 100644 --- a/capi/src/tests.rs +++ b/capi/src/tests.rs @@ -5,17 +5,16 @@ use crate::compiler::{ yrx_compiler_destroy, yrx_compiler_new_namespace, }; use crate::{ - yrx_buffer_destroy, yrx_last_error, yrx_patterns_destroy, - yrx_rule_identifier, yrx_rule_metadata_as_json, yrx_rule_namespace, - yrx_rule_patterns, yrx_rules_deserialize, yrx_rules_destroy, - yrx_rules_serialize, yrx_scanner_create, yrx_scanner_destroy, - yrx_scanner_on_matching_rule, yrx_scanner_scan, + yrx_buffer_destroy, yrx_last_error, yrx_metadata_destroy, + yrx_patterns_destroy, yrx_rule_identifier, yrx_rule_metadata, + yrx_rule_namespace, yrx_rule_patterns, yrx_rules_deserialize, + yrx_rules_destroy, yrx_rules_serialize, yrx_scanner_create, + yrx_scanner_destroy, yrx_scanner_on_matching_rule, yrx_scanner_scan, yrx_scanner_set_global_bool, yrx_scanner_set_global_float, yrx_scanner_set_global_int, yrx_scanner_set_global_str, yrx_scanner_set_timeout, YRX_BUFFER, YRX_RULE, }; use std::ffi::{c_void, CString}; -use std::slice; extern "C" fn callback(rule: *const YRX_RULE, user_data: *mut c_void) { let mut ptr = std::ptr::null(); @@ -25,18 +24,13 @@ extern "C" fn callback(rule: *const YRX_RULE, user_data: *mut c_void) { yrx_rule_namespace(rule, &mut ptr, &mut len); yrx_rule_identifier(rule, &mut ptr, &mut len); - let mut metadata: *mut YRX_BUFFER = std::ptr::null_mut(); - yrx_rule_metadata_as_json(rule, &mut metadata); - - let json = slice::from_raw_parts((*metadata).data, (*metadata).length); - let json_str = String::from_utf8(json.to_vec()).unwrap(); - - assert_eq!(json_str.as_str(), r#"[["some_int",1]]"#); - - yrx_buffer_destroy(metadata); - + let metadata = yrx_rule_metadata(rule); let patterns = yrx_rule_patterns(rule); + assert_eq!((*patterns).num_patterns, 1); + assert_eq!((*metadata).num_entries, 3); + + yrx_metadata_destroy(metadata); yrx_patterns_destroy(patterns); } @@ -55,6 +49,8 @@ fn capi() { b"rule test {\ meta: \ some_int = 1 \ + some_string = \"foo\" \ + some_bytes = \"\\x01\\x00\\x02\" \ strings: \ $foo = \"foo\" \ condition: \ From 88d30e0f3bfbdb117852940162615b7a9429e8fa Mon Sep 17 00:00:00 2001 From: "Victor M. Alvarez" Date: Thu, 23 May 2024 20:13:51 +0200 Subject: [PATCH 18/32] docs: add comments for `YRX_METADATA_ENTRY` fields --- capi/include/yara_x.h | 3 +++ capi/src/lib.rs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/capi/include/yara_x.h b/capi/include/yara_x.h index fab44375f..d290947cb 100644 --- a/capi/include/yara_x.h +++ b/capi/include/yara_x.h @@ -109,7 +109,10 @@ typedef union YRX_METADATA_VALUE { typedef struct YRX_METADATA_ENTRY { // Metadata identifier. char *identifier; + // Type of value. enum YRX_METADATA_VALUE_TYPE value_type; + // The value itself. This is a union, use the member that matches the + // value type. union YRX_METADATA_VALUE value; } YRX_METADATA_ENTRY; diff --git a/capi/src/lib.rs b/capi/src/lib.rs index 21a2d5f6e..b84ada189 100644 --- a/capi/src/lib.rs +++ b/capi/src/lib.rs @@ -202,7 +202,10 @@ union YRX_METADATA_VALUE { pub struct YRX_METADATA_ENTRY { /// Metadata identifier. identifier: *mut c_char, + /// Type of value. value_type: YRX_METADATA_VALUE_TYPE, + /// The value itself. This is a union, use the member that matches the + /// value type. value: YRX_METADATA_VALUE, } From 497c678cb288dbfdfc12415741e301d724735c97 Mon Sep 17 00:00:00 2001 From: "Victor M. Alvarez" Date: Fri, 24 May 2024 09:47:10 +0200 Subject: [PATCH 19/32] fix: rename fields in `YRX_METADATA_VALUE` Avoid using C/C++ keywords as field identifiers. --- capi/include/yara_x.h | 8 ++++---- capi/src/lib.rs | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/capi/include/yara_x.h b/capi/include/yara_x.h index d290947cb..99f5337d4 100644 --- a/capi/include/yara_x.h +++ b/capi/include/yara_x.h @@ -35,8 +35,8 @@ // Metadata value types. typedef enum YRX_METADATA_VALUE_TYPE { - INTEGER, - FLOAT, + I64, + F64, BOOLEAN, STRING, BYTES, @@ -98,8 +98,8 @@ typedef struct YRX_METADATA_BYTES { // Metadata value. typedef union YRX_METADATA_VALUE { - int64_t integer; - double float; + int64_t i64; + double f64; bool boolean; char *string; struct YRX_METADATA_BYTES bytes; diff --git a/capi/src/lib.rs b/capi/src/lib.rs index b84ada189..99361318a 100644 --- a/capi/src/lib.rs +++ b/capi/src/lib.rs @@ -170,8 +170,8 @@ impl Drop for YRX_METADATA { #[repr(C)] #[allow(missing_docs)] pub enum YRX_METADATA_VALUE_TYPE { - INTEGER, - FLOAT, + I64, + F64, BOOLEAN, STRING, BYTES, @@ -190,8 +190,8 @@ pub struct YRX_METADATA_BYTES { /// Metadata value. #[repr(C)] union YRX_METADATA_VALUE { - integer: i64, - float: f64, + r#i64: i64, + r#f64: f64, boolean: bool, string: *mut c_char, bytes: YRX_METADATA_BYTES, @@ -468,15 +468,15 @@ pub unsafe extern "C" fn yrx_rule_metadata( yara_x::MetaValue::Integer(v) => { entries.push(YRX_METADATA_ENTRY { identifier, - value_type: YRX_METADATA_VALUE_TYPE::INTEGER, - value: YRX_METADATA_VALUE { integer: v }, + value_type: YRX_METADATA_VALUE_TYPE::I64, + value: YRX_METADATA_VALUE { r#i64: v }, }); } yara_x::MetaValue::Float(v) => { entries.push(YRX_METADATA_ENTRY { identifier, - value_type: YRX_METADATA_VALUE_TYPE::FLOAT, - value: YRX_METADATA_VALUE { float: v }, + value_type: YRX_METADATA_VALUE_TYPE::F64, + value: YRX_METADATA_VALUE { r#f64: v }, }); } yara_x::MetaValue::Bool(v) => { From caccbce482399785c52d07180fcaf911e0d49ce5 Mon Sep 17 00:00:00 2001 From: "Victor M. Alvarez" Date: Fri, 24 May 2024 09:54:01 +0200 Subject: [PATCH 20/32] fix: add missing `#[no_mangle]` decorator to `yrx_rule_metadata` --- capi/include/yara_x.h | 9 +++++++++ capi/src/lib.rs | 1 + site/content/docs/api/c.md | 9 +++++++++ 3 files changed, 19 insertions(+) diff --git a/capi/include/yara_x.h b/capi/include/yara_x.h index 99f5337d4..0c781902f 100644 --- a/capi/include/yara_x.h +++ b/capi/include/yara_x.h @@ -222,6 +222,15 @@ enum YRX_RESULT yrx_rule_namespace(const struct YRX_RULE *rule, const uint8_t **ns, size_t *len); +// Returns the metadata associated to a rule. +// +// The metadata is represented by a [`YRX_METADATA`] object that must be +// destroyed with [`yrx_metadata_destroy`] when not needed anymore. +// +// This function returns a null pointer when `rule` is null or the +// rule doesn't have any metadata. +struct YRX_METADATA *yrx_rule_metadata(const struct YRX_RULE *rule); + // Destroys a [`YRX_METADATA`] object. void yrx_metadata_destroy(struct YRX_METADATA *metadata); diff --git a/capi/src/lib.rs b/capi/src/lib.rs index 99361318a..5d972b247 100644 --- a/capi/src/lib.rs +++ b/capi/src/lib.rs @@ -446,6 +446,7 @@ pub unsafe extern "C" fn yrx_rule_namespace( /// /// This function returns a null pointer when `rule` is null or the /// rule doesn't have any metadata. +#[no_mangle] pub unsafe extern "C" fn yrx_rule_metadata( rule: *const YRX_RULE, ) -> *mut YRX_METADATA { diff --git a/site/content/docs/api/c.md b/site/content/docs/api/c.md index da3dc277f..0be17c801 100644 --- a/site/content/docs/api/c.md +++ b/site/content/docs/api/c.md @@ -435,6 +435,15 @@ The `*ns` pointer will be valid as long as the [YRX_RULES](#yrx_rules) object that contains the rule is not destroyed. The namespace is guaranteed to be a valid UTF-8 string. +#### yrx_rule_metadata + +```c +enum YRX_RESULT yrx_rule_namespace( + const struct YRX_RULE *rule, + const uint8_t **ns, + size_t *len); +``` + #### yrx_rule_patterns ```c From bfa57afde38bae9c9dae6828b5865f21b70948d1 Mon Sep 17 00:00:00 2001 From: "Victor M. Alvarez" Date: Fri, 24 May 2024 10:13:43 +0200 Subject: [PATCH 21/32] docs(capi): document the API for accessing rule's metadata --- site/content/docs/api/c.md | 115 ++++++++++++++++++- site/hugo_stats.json | 218 +++++++++++++++++++++++++++++++------ 2 files changed, 295 insertions(+), 38 deletions(-) diff --git a/site/content/docs/api/c.md b/site/content/docs/api/c.md index 0be17c801..914c790d7 100644 --- a/site/content/docs/api/c.md +++ b/site/content/docs/api/c.md @@ -438,12 +438,15 @@ valid UTF-8 string. #### yrx_rule_metadata ```c -enum YRX_RESULT yrx_rule_namespace( - const struct YRX_RULE *rule, - const uint8_t **ns, - size_t *len); +struct YRX_METADATA *yrx_rule_metadata(const struct YRX_RULE *rule); ``` +Returns an array with all the metadata values associated to the rule. + +The metadata is represented by a [YRX_METADATA](#yrx_metadata) object that must +be destroyed with [yrx_metadata_destroy](#yrx_metadata_destroy) when not needed +anymore. + #### yrx_rule_patterns ```c @@ -477,6 +480,8 @@ typedef struct YRX_PATTERNS { } YRX_PATTERNS; ``` +------ + #### yrx_patterns_destroy ```c @@ -485,7 +490,6 @@ void yrx_patterns_destroy(struct YRX_PATTERNS *patterns); Destroys the [YRX_PATTERNS](#yrx_patterns) object. - ------ ### YRX_PATTERN @@ -522,6 +526,107 @@ typedef struct YRX_MATCH { ------ +### YRX_METADATA + +Contains the metadata values associated to a rule. You will get a pointer to +one of these structures when calling [yrx_rule_metadata](#yrx_rule_metadata), +you are responsible for calling [yrx_metadata_destroy](#yrx_metadata_destroy) +when not using the structure anymore. + +```c +typedef struct YRX_METADATA { + // Number of metadata entries. + size_t num_entries; + // Pointer to an array of YRX_METADATA_ENTRY structures. The array has + // num_entries items. If num_entries is zero this pointer is invalid + // and should not be de-referenced. + struct YRX_METADATA_ENTRY *entries; +} YRX_METADATA; + +``` + +------ + +#### yrx_metadata_destroy + +```c +void yrx_metadata_destroy(struct YRX_METADATA *metadata); +``` + +Destroys the [YRX_METADATA](#yrx_metadata) object. + +------ + +### YRX_METADATA_ENTRY + +An individual metadata entry. The [YRX_METADATA](#yrx_metadata) +object has a pointer to an array of these structures. The structure +contains information about the metadata identifier, its type, and +its value. The `value` field is a union with multiple alternatives, +you must use the type indicated in the `value_type` for deciding +which alternative to use while accessing the metadata value. + +```c +typedef struct YRX_METADATA_ENTRY { + // Metadata identifier. + char *identifier; + // Type of value. + enum YRX_METADATA_VALUE_TYPE value_type; + // The value itself. This is a union, use the member that matches the + // value type. + union YRX_METADATA_VALUE value; +} YRX_METADATA_ENTRY; +``` + +### YRX_METADATA_VALUE_TYPE + +Each of the possible types of a metadata entry. + +------ + +```c +typedef enum YRX_METADATA_VALUE_TYPE { + I64, + F64, + BOOLEAN, + STRING, + BYTES, +} YRX_METADATA_VALUE_TYPE; +``` + +------ + +### YRX_METADATA_VALUE + +Union that represents a metadata value. + +```c +typedef union YRX_METADATA_VALUE { + int64_t i64; + double f64; + bool boolean; + char *string; + struct YRX_METADATA_BYTES bytes; +} YRX_METADATA_VALUE; +``` + +------ + +### YRX_METADATA_BYTES + +Structure that represents a metadata value with an arbitrary sequence of bytes. + +```c +typedef struct YRX_METADATA_BYTES { + // Number of bytes. + size_t length; + // Pointer to the bytes. + uint8_t *data; +} YRX_METADATA_BYTES; +``` + +------ + ### YRX_RESULT Error codes returned by multiple functions in this API. diff --git a/site/hugo_stats.json b/site/hugo_stats.json index 2b7175954..73b35530d 100644 --- a/site/hugo_stats.json +++ b/site/hugo_stats.json @@ -13,10 +13,12 @@ "defs", "details", "div", + "em", "figcaption", "figure", "footer", "form", + "format", "g", "h1", "h2", @@ -25,7 +27,9 @@ "h5", "head", "header", + "hr", "html", + "img", "input", "kbd", "label", @@ -35,14 +39,15 @@ "link", "main", "meta", + "module", "nav", "noscript", - "ol", "p", "path", "pre", "rect", "script", + "seconds", "section", "small", "span", @@ -84,6 +89,7 @@ "card", "card-body", "card-list", + "card-terms", "categories", "chroma", "col-lg-10", @@ -119,7 +125,6 @@ "doks-sidebar", "error404", "expressive-code", - "fade", "flex-column", "flex-grow-1", "flex-lg-row", @@ -200,7 +205,6 @@ "nav", "nav-item", "nav-link", - "nav-tabs", "navbar", "navbar-brand", "navbar-expand-lg", @@ -244,17 +248,15 @@ "section-features", "section-md", "section-nav", - "show", "single", "social-link", "status", "sticky-top", "stretched-link", "submitted", - "tab-content", - "tab-pane", "tags", "taxonomy", + "term", "text-body", "text-body-secondary", "text-center", @@ -275,6 +277,23 @@ "wrap" ], "ids": [ + "--compiled-rules--c", + "--define--d-varvalue", + "--disable-console-logs", + "--module--m-module", + "--negate--n", + "--no-colors", + "--output-format--o-format", + "--path-as-namespace", + "--path-as-namespace-1", + "--print-namespace--e", + "--print-strings--s", + "--relaxed-re-syntax", + "--relaxed-re-syntax-1", + "--scan-list", + "--skip-larger-file_size", + "--threads--p-num_threads", + "--timeout--a-seconds", "100", "102", "103", @@ -289,66 +308,136 @@ "98", "99", "TableOfContents", + "__init__relaxed_re_syntaxfalse", "absint", + "add_sourcestring", "alphabets-for-base64-modifiers", "alternatives", "anonymous-patterns", + "api-overview", + "api-reference", "applying-the-same-condition-to-many-patterns", "assembly", "assemblyref", "base64-modifier", - "but-why-a-rewrite", + "better-error-reporting", + "build", + "building-the-c-library", + "building-the-go-library", + "buildtool", + "buildversion", "buttonColorMode", + "calculate_checksum", "certificate", + "certificates", "characteristics", "checksum32offset-size", "checksum32string", "class", "comments", + "compile", + "compileerror", + "compiler", "countbyte-offset-size", "countersignature", "counting-pattern-occurrences", + "cpu_arm_64_subtype", + "cpu_arm_subtype", + "cpu_i386_subtype", + "cpu_i386_type", + "cpu_intel_pentium_subtype", + "cpu_intel_subtype", + "cpu_mc_subtype", + "cpu_powerpc_subtype", + "cpu_sparc_subtype", + "cpu_type", + "cpu_x86_subtype", "crc32offset-size", "crc32string", + "define_globalidentifier-value", "deviationoffset-size-mean", "deviationstring-mean", + "device_type", "differences-in-base64-patterns", "directoryentry", "direntry", "dllcharacteristics", "doks-docs-nav", - "dotnet", - "dotnetproto", "drivetype", + "dump", "duplicate-rule-modifiers", + "dyldinfo", + "dylib", "dyn", "dyntype", - "elf", - "elfproto", + "dysymtab", "entropyoffset-size", "entropystring", + "example", + "example-1", + "example-2", + "example-3", + "example-4", + "example-5", + "example-6", + "example-7", + "example-8", + "examples", "export", + "exports_indexfn_name", + "exports_indexfn_regex", + "exports_indexordinal", + "exportsfn_name", + "exportsfn_regex", + "exportsordinal", + "fat_header", + "fatarch", + "file", "file-size", + "file_flag", + "file_type", "fileattributes", "finding-patterns-at-specific-offsets", "fullword-modifier", "function", "functions", "further-reading", + "global-rules", + "global-rules-cant-depend-on-non-global-rules", + "go-api", + "h-rh-i-0", + "h-rh-i-1", + "header", + "heading", "hexinteger", "hexmessage-integer", + "higher-overall-performance", + "identifier", + "identifier-1", + "imphash", "import", + "import_md5", "importflags", + "importsdll_name", + "importsdll_name-fn_name", + "importsdll_name-ordinal", + "importsdll_regex-fn_regex", + "importstype-dll_name-fn_name", "in_rangetest-lower-upper", + "incompatible-apis", + "installation", "installing-with-cargo", "is-yara-really-dead", + "is_32bit", + "is_64bit", + "is_dll", "iterating-over-pattern-occurrences", "jump-bounds-in-hex-patterns", "jumps", "keyvalue", + "length", "lengthstring", - "lnk", - "lnkproto", + "linkeditdata", "logfloat", "loginteger", "logmessage-float", @@ -356,7 +445,11 @@ "logmessage-string", "logstring", "machine", + "mask_64bit", + "match", "match-lengths", + "matches", + "matching_rules", "maxint-int", "md5offset-size", "md5string", @@ -365,14 +458,19 @@ "metadata", "method", "minint-int", + "minversion", "modeoffset-size", "module-structure", + "module_outputs", "monte_carlo_pioffset-size", "monte_carlo_pistring", - "nav-tab", - "nav-tabContent", + "more-powerful-cli", + "namespace", "negation", "negative-numbers-as-array-indexes", + "new_namespacestring", + "no-include-statements", + "no-process-scanning", "nocase-modifier", "now", "of-statement-accepts-tuples-of-boolean-expressions", @@ -380,24 +478,35 @@ "offcanvasNavMainLabel", "offcanvasNavSection", "offcanvasNavSectionLabel", + "offset", "operators", "optionalmagic", "overlay", "param", - "pe", - "peproto", + "parser-re-usability", + "pattern", + "patterns", "percentagebyte-offset-size", - "peversioninfoentry", - "peversioninfoentry-1", - "pre-requisites", - "protocol-documentation", + "private-rules", "query", "reading-data-at-a-given-offset", + "referencing-other-rules", "resource", "resourcetype", + "rich_signaturetoolidtoolid-version", + "rich_signatureversionversion-toolid", "richsignature", "richtool", - "scalar-value-types", + "rule", + "rules", + "rva_to_offsetrva", + "scan", + "scan_filepath", + "scanbytes", + "scanbytes-1", + "scanerror", + "scanner", + "scanresults", "search-form", "searchModal", "searchModalLabel", @@ -405,13 +514,20 @@ "searchToggleDesktop", "searchToggleMobile", "section", + "section_attributes", + "section_indexname", + "section_indexoffset", + "section_type", "sectioncharacteristics", "sectiontype", "segment", + "segment_flag", "segmentflags", "segmenttype", "serial_correlationoffset-size", "serial_correlationstring", + "set_globalidentifier-value", + "set_timeoutseconds", "sets-of-patterns", "sha1offset-size", "sha1string", @@ -426,32 +542,68 @@ "subsystem", "sym", "symbind", + "symtab", "symtype", "symvisibility", - "table-of-contents", - "tabs-install-openssl-0", - "tabs-install-openssl-0-tab", - "tabs-install-openssl-1", - "tabs-install-openssl-1-tab", - "tabs-install-openssl-2", - "tabs-install-openssl-2-tab", - "to_intinteger-base", + "tags", + "telfhash", + "the--character-must-be-escaped-in-regular-expressions", + "the-bad-things", + "the-good-things", + "timeouterror", "to_intstring", "to_intstring-base", "to_numberbool", "to_stringint", "to_stringint-base", - "to_stringinteger-base", "toc", "trackerdata", "type", "using-xor-and-fullword-together", "version", - "versioninfo", "versioninfoentry", + "whats-next", + "whats-the-current-state-of-yara-x", + "why-a-rewrite", "wide-modifier", "wildcards", - "xor-modifier" + "xor-modifier", + "xor_key", + "yrx_compile", + "yrx_compiler", + "yrx_compiler_add_source", + "yrx_compiler_build", + "yrx_compiler_create", + "yrx_compiler_define_global_xxxx", + "yrx_compiler_destroy", + "yrx_compiler_new_namespace", + "yrx_last_error", + "yrx_match", + "yrx_metadata", + "yrx_metadata_bytes", + "yrx_metadata_destroy", + "yrx_metadata_entry", + "yrx_metadata_value", + "yrx_metadata_value_type", + "yrx_on_matching_rule", + "yrx_pattern", + "yrx_patterns", + "yrx_patterns_destroy", + "yrx_result", + "yrx_rule", + "yrx_rule_identifier", + "yrx_rule_metadata", + "yrx_rule_namespace", + "yrx_rule_patterns", + "yrx_rules", + "yrx_rules_destroy", + "yrx_scanner", + "yrx_scanner_create", + "yrx_scanner_destroy", + "yrx_scanner_on_matching_rule", + "yrx_scanner_scan", + "yrx_scanner_set_global_xxxx", + "yrx_scanner_set_timeout" ] } } From b84c1e96c85f1db788b66a1ad8a14e7928bb9e6f Mon Sep 17 00:00:00 2001 From: "Victor M. Alvarez" Date: Fri, 24 May 2024 10:15:09 +0200 Subject: [PATCH 22/32] docs: publish blog post "YARA-X as a file inspection tool" --- .../yara-x-as-a-file-parsing-tool/index.md | 70 ++++++++++++++++++ .../screenshot.png | Bin 0 -> 177522 bytes site/hugo_stats.json | 1 - 3 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 site/content/blog/yara-x-as-a-file-parsing-tool/index.md create mode 100644 site/content/blog/yara-x-as-a-file-parsing-tool/screenshot.png diff --git a/site/content/blog/yara-x-as-a-file-parsing-tool/index.md b/site/content/blog/yara-x-as-a-file-parsing-tool/index.md new file mode 100644 index 000000000..71882ef57 --- /dev/null +++ b/site/content/blog/yara-x-as-a-file-parsing-tool/index.md @@ -0,0 +1,70 @@ +--- +title: "YARA-X as a file inspection tool" +description: "How YARA-X can be used for extracting information from multiple file formats" +summary: "" +date: 2024-05-24T00:00:00+01:00 +lastmod: 2024-05-24T00:00:00+01:00 +draft: false +weight: 50 +categories: [ ] +tags: [ ] +contributors: [ "Victor M. Alvarez" ] +pinned: false +homepage: false +seo: + title: "YARA-X as file inspection tool" + description: "How YARA-X can be used for extracting information from multiple file formats" + canonical: "" # custom canonical URL (optional) + noindex: false # false (default) or true +--- + +As you may already know, YARA-X has modules that can parse certain file formats +and produce a data structure containing information extracted from the parsed +files. This information can be leveraged to create more precise and powerful +rules tailored to each file format's unique characteristics. + +YARA-X supports the parsing of several file formats, including: + +* [PE](https://en.wikipedia.org/wiki/Portable_Executable) (Portable Executable) +* [.NET](https://en.wikipedia.org/wiki/.NET_Framework) (PE file for the .NET + framework) +* [ELF](https://en.wikipedia.org/wiki/Executable_and_Linkable_Format) ( + Executable and Linkable Format) +* [Mach-O](https://en.wikipedia.org/wiki/Mach-O) (Mach Object file format) +* [LNK](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-shllink/16cb4ca1-9339-4d0c-a68d-bf1d6cc0f943) ( + Windows Shortcut) + +For each of these formats, YARA-X constructs a comprehensive data structure +that describes the file in detail. + +While the legacy YARA also had the capability to parse these file formats, +YARA-X brings a significant improvement by making the inspection of these data +structures much more straightforward. This is accomplished through the dump +command: + +``` +yr dump [FILE] +``` + +By executing this command, YARA-X parses the specified file using all the +relevant modules and outputs the generated data structures. The output is +provided in YAML format by default, chosen for its readability and ease of use +by both humans and machines. But JSON format is also available. + +Previously, YARA offered the `--print-module-data` option (or `-D`), which +provided similar functionality. However, it required users to pass a YARA source +file that imported the desired modules, and the output was often cumbersome and +difficult to interpret. This option was primarily intended as a debugging tool +but gained unexpected popularity among users for extracting information about +certain file formats. + +With the introduction of the `dump` command in YARA-X, this process is greatly +simplified, providing users with a more efficient and user-friendly way to +access detailed information about various file formats. See how the output +of this command looks like: + +![screenshot.png](screenshot.png) + +If you want to learn more about the `dump` command, see its [documentation]({{< +ref "cli.md" >}}#dump) + diff --git a/site/content/blog/yara-x-as-a-file-parsing-tool/screenshot.png b/site/content/blog/yara-x-as-a-file-parsing-tool/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..d36f3dce0fd517c7a6aeb211f90579dfd50a43f7 GIT binary patch literal 177522 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
;`xFY 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
qlBkTLbc}TQAW{$Vtof8X! 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 PrO~Qr^g@5hhx!pjsN0PY*gBoz0)Zy zb56IQfa4#!KA9)v3BA+e&Ug@yIQruUQgyxUjN07-)_0BLMhTM4njtkzmcLcsTo0pA zGHG<)D@^qxB0b+lv07XL5b?xEoU?eTc}?kS1+O2b9&lH<5I;Oq9kUJxXj+YZ-C#ic zid131;^_J$rnW-ZUNf}%Y8~QcXb{6WhR1MXYrg=~{6NM(6Y_GHVx(4)SkDg71fShD6IyHAq^#~6gIGw#)&t3`20fP zIR<%7#jz{NvKrIF&Bjcp@1v7B5!X|`u*e1HmTJYdX#3gUK|$-O``I0v0_wDN<7(Qb zm8kP$>tUMP5&QTLDunT?W;Wa-`F&+{GvjPU%lLOuj)Yu}D2OM@H1rB!7vimE)WigS zs!o5cUmMRR2_&$i(AQ+v^j>Wih%rWR2KM_HQw_OuddidS zaI)>-!}as0Q91KLf|qt<2ey*8ruy__C5T(*U+5|+??Q|A7S#p=h+npv#=*bt30D_f zi0Zb*3fcoIQO>FAKz#`AJ#L(M63S0kuOBDLcNWpIr4L>dLiT|a%l%F7RDonQFB>kx z2w*BBpTUXNiKi$JE&4hHVU1~h$@s8Q=49}Fp4<}K1zAu!SIq_KY$*1ytNKA99E$30 z7G`8V�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 literal 0 HcmV?d00001 diff --git a/site/hugo_stats.json b/site/hugo_stats.json index 73b35530d..87a15701c 100644 --- a/site/hugo_stats.json +++ b/site/hugo_stats.json @@ -408,7 +408,6 @@ "h-rh-i-0", "h-rh-i-1", "header", - "heading", "hexinteger", "hexmessage-integer", "higher-overall-performance", From 58e14c385707f2f282847b93253b58819b4fc82c Mon Sep 17 00:00:00 2001 From: "Victor M. Alvarez" Date: Fri, 24 May 2024 10:53:38 +0200 Subject: [PATCH 23/32] fix: rename function`invoke_mod` to `invoke` in fuzzers --- lib/fuzz/fuzz_targets/dotnet_parser.rs | 2 +- lib/fuzz/fuzz_targets/elf_parser.rs | 2 +- lib/fuzz/fuzz_targets/lnk_parser.rs | 2 +- lib/fuzz/fuzz_targets/macho_parser.rs | 2 +- lib/fuzz/fuzz_targets/pe_parser.rs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/fuzz/fuzz_targets/dotnet_parser.rs b/lib/fuzz/fuzz_targets/dotnet_parser.rs index a783d17c6..178efa707 100644 --- a/lib/fuzz/fuzz_targets/dotnet_parser.rs +++ b/lib/fuzz/fuzz_targets/dotnet_parser.rs @@ -2,5 +2,5 @@ use libfuzzer_sys::fuzz_target; fuzz_target!(|data: &[u8]| { - let _ = yara_x::mods::invoke_mod::(data); + let _ = yara_x::mods::invoke::(data); }); diff --git a/lib/fuzz/fuzz_targets/elf_parser.rs b/lib/fuzz/fuzz_targets/elf_parser.rs index c26fc6ee1..6b4ad5e6a 100644 --- a/lib/fuzz/fuzz_targets/elf_parser.rs +++ b/lib/fuzz/fuzz_targets/elf_parser.rs @@ -2,5 +2,5 @@ use libfuzzer_sys::fuzz_target; fuzz_target!(|data: &[u8]| { - let _ = yara_x::mods::invoke_mod::(data); + let _ = yara_x::mods::invoke::(data); }); diff --git a/lib/fuzz/fuzz_targets/lnk_parser.rs b/lib/fuzz/fuzz_targets/lnk_parser.rs index a83dc5a19..0af3f8f88 100644 --- a/lib/fuzz/fuzz_targets/lnk_parser.rs +++ b/lib/fuzz/fuzz_targets/lnk_parser.rs @@ -2,5 +2,5 @@ use libfuzzer_sys::fuzz_target; fuzz_target!(|data: &[u8]| { - let _ = yara_x::mods::invoke_mod::(data); + let _ = yara_x::mods::invoke::(data); }); diff --git a/lib/fuzz/fuzz_targets/macho_parser.rs b/lib/fuzz/fuzz_targets/macho_parser.rs index d2311d386..6f23b6e01 100644 --- a/lib/fuzz/fuzz_targets/macho_parser.rs +++ b/lib/fuzz/fuzz_targets/macho_parser.rs @@ -2,5 +2,5 @@ use libfuzzer_sys::fuzz_target; fuzz_target!(|data: &[u8]| { - let _ = yara_x::mods::invoke_mod::(data); + let _ = yara_x::mods::invoke::(data); }); diff --git a/lib/fuzz/fuzz_targets/pe_parser.rs b/lib/fuzz/fuzz_targets/pe_parser.rs index 4c880999c..64f78b7d7 100644 --- a/lib/fuzz/fuzz_targets/pe_parser.rs +++ b/lib/fuzz/fuzz_targets/pe_parser.rs @@ -2,5 +2,5 @@ use libfuzzer_sys::fuzz_target; fuzz_target!(|data: &[u8]| { - let _ = yara_x::mods::invoke_mod::(data); + let _ = yara_x::mods::invoke::(data); }); From 547cb8ef09874999ac34b4a4b68ef51aa502c7d2 Mon Sep 17 00:00:00 2001 From: "Victor M. Alvarez" Date: Fri, 24 May 2024 19:09:03 +0200 Subject: [PATCH 24/32] fix: stop parsing import descriptor when finding one where name is zero. This is the behaviour exhibited by YARA. --- lib/src/modules/pe/parser.rs | 4 +++- lib/src/modules/pe/rva2off.rs | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/src/modules/pe/parser.rs b/lib/src/modules/pe/parser.rs index db5f454ba..e74d432ae 100644 --- a/lib/src/modules/pe/parser.rs +++ b/lib/src/modules/pe/parser.rs @@ -1777,7 +1777,9 @@ impl<'a> PE<'a> { let mut import_descriptors = iterator( input, verify(descriptor_parser, |d| { - d.import_address_table != 0 || d.import_name_table != 0 + d.name != 0 + && (d.import_address_table != 0 + || d.import_name_table != 0) }), ); diff --git a/lib/src/modules/pe/rva2off.rs b/lib/src/modules/pe/rva2off.rs index ba825233d..dbe8b5d64 100644 --- a/lib/src/modules/pe/rva2off.rs +++ b/lib/src/modules/pe/rva2off.rs @@ -13,9 +13,9 @@ pub(crate) trait Section { /// Convert a relative virtual address (RVA) to a file offset. /// -/// A RVA is an offset relative to the base address of the executable +/// An RVA is an offset relative to the base address of the executable /// program. The PE format uses RVAs in multiple places and sometimes -/// is necessary to covert the RVA to a file offset. +/// is necessary to convert the RVA to a file offset. pub(crate) fn rva_to_offset( rva: u32, sections: &[impl Section], From 9ddbbbe4e4eb8ee32e887ed2cffe6132a6401af9 Mon Sep 17 00:00:00 2001 From: Michael Pivonka Date: Sat, 25 May 2024 10:26:21 -0500 Subject: [PATCH 25/32] feat(go): expose rule's metadata in Go API (#123) * Exposed rule's metadata in golang package * feat(go) Implemented APIs for obtaining rule's metadata. * refactor: access metadata values in a safer manner. * chore: remove unnecessary import statement --------- Co-authored-by: Victor M. Alvarez --- go/main.go | 72 +++++++++++++++++++++++++++++++++++++++++++++- go/scanner_test.go | 38 ++++++++++++++++++++---- 2 files changed, 104 insertions(+), 6 deletions(-) diff --git a/go/main.go b/go/main.go index 0ca73e422..56f950b9b 100644 --- a/go/main.go +++ b/go/main.go @@ -4,6 +4,26 @@ package yara_x // #cgo !static_link pkg-config: yara_x_capi // #cgo static_link pkg-config: --static yara_x_capi // #include +// +// uint64_t meta_i64(void* value) { +// return ((YRX_METADATA_VALUE*) value)->i64; +// } +// +// double meta_f64(void* value) { +// return ((YRX_METADATA_VALUE*) value)->f64; +// } +// +// bool meta_bool(void* value) { +// return ((YRX_METADATA_VALUE*) value)->boolean; +// } +// +// char* meta_str(void* value) { +// return ((YRX_METADATA_VALUE*) value)->string; +// } +// +// YRX_METADATA_BYTES* meta_bytes(void* value) { +// return &(((YRX_METADATA_VALUE*) value)->bytes); +// } import "C" import ( "errors" @@ -11,7 +31,6 @@ import ( "unsafe" ) - // Compile receives YARA source code and returns compiled [Rules] that can be // used for scanning data. func Compile(src string, opts ...CompileOption) (*Rules, error) { @@ -88,6 +107,8 @@ type Rule struct { identifier string cPatterns *C.YRX_PATTERNS patterns []Pattern + cMetadata *C.YRX_METADATA + metadata []Metadata } // Creates a new Rule from it's C counterpart. @@ -112,6 +133,8 @@ func newRule(cRule *C.YRX_RULE) *Rule { identifier, C.yrx_rule_patterns(cRule), nil, + C.yrx_rule_metadata(cRule), + nil, } runtime.SetFinalizer(rule, (*Rule).destroy) @@ -120,6 +143,9 @@ func newRule(cRule *C.YRX_RULE) *Rule { func (r *Rule) destroy() { C.yrx_patterns_destroy(r.cPatterns) + if r.cMetadata != nil { + C.yrx_metadata_destroy(r.cMetadata) + } runtime.SetFinalizer(r, nil) } @@ -133,6 +159,50 @@ func (r *Rule) Namespace() string { return r.namespace } +// Metadata returns the rule's metadata +func (r *Rule) Metadata() []Metadata { + // if this method was called before, return the metadata already cached. + if r.metadata != nil { + return r.metadata + } + + numMetadata := int(r.cMetadata.num_entries) + cMetadata := unsafe.Slice(r.cMetadata.entries, numMetadata) + r.metadata = make([]Metadata, numMetadata) + + for i, metadata := range cMetadata { + r.metadata[i].Identifier = C.GoString(metadata.identifier) + switch metadata.value_type { + case C.I64: + r.metadata[i].Value = int64( + C.meta_i64(unsafe.Pointer(&metadata.value))) + case C.F64: + r.metadata[i].Value = float64( + C.meta_f64(unsafe.Pointer(&metadata.value))) + case C.BOOLEAN: + r.metadata[i].Value = bool( + C.meta_bool(unsafe.Pointer(&metadata.value))) + case C.STRING: + r.metadata[i].Value = C.GoString( + C.meta_str(unsafe.Pointer(&metadata.value))) + case C.BYTES: + bytes := C.meta_bytes(unsafe.Pointer(&metadata.value)) + r.metadata[i].Value = C.GoBytes( + unsafe.Pointer(bytes.data), + C.int(bytes.length), + ) + } + } + + return r.metadata +} + +// Metadata represents a metadata in a Rule. +type Metadata struct { + Identifier string + Value interface{} +} + // Patterns returns the patterns defined by this rule. func (r *Rule) Patterns() []Pattern { // If this method was called before, return the patterns already cached. diff --git a/go/scanner_test.go b/go/scanner_test.go index 17f74355d..c2d2b58f2 100644 --- a/go/scanner_test.go +++ b/go/scanner_test.go @@ -5,13 +5,14 @@ import ( "runtime" "testing" "time" + + "github.com/stretchr/testify/assert" ) -import "github.com/stretchr/testify/assert" func TestScanner1(t *testing.T) { r, _ := Compile("rule t { condition: true }") s := NewScanner(r) - matchingRules, _:= s.Scan([]byte{}) + matchingRules, _ := s.Scan([]byte{}) assert.Len(t, matchingRules, 1) assert.Equal(t, "t", matchingRules[0].Identifier()) @@ -76,7 +77,34 @@ func TestScanner4(t *testing.T) { func TestScannerTimeout(t *testing.T) { r, _ := Compile("rule t { strings: $a = /a(.*)*a/ condition: $a }") s := NewScanner(r) - s.SetTimeout(1*time.Second) - _, err := s.Scan(bytes.Repeat([]byte("a"), 9000)) + s.SetTimeout(1 * time.Second) + _, err := s.Scan(bytes.Repeat([]byte("a"), 10000)) assert.ErrorIs(t, err, ErrTimeout) -} \ No newline at end of file +} + +func TestScannerMetadata(t *testing.T) { + r, _ := Compile(`rule t { + meta: + some_int = 1 + some_float = 2.3034 + some_bool = true + some_string = "hello" + some_bytes = "\x00\x01\x02" + condition: + true + }`) + s := NewScanner(r) + matchingRules, _ := s.Scan([]byte{}) + + assert.Len(t, matchingRules, 1) + assert.Equal(t, "some_int", matchingRules[0].Metadata()[0].Identifier) + assert.Equal(t, int64(1), matchingRules[0].Metadata()[0].Value) + assert.Equal(t, "some_float", matchingRules[0].Metadata()[1].Identifier) + assert.Equal(t, float64(2.3034), matchingRules[0].Metadata()[1].Value) + assert.Equal(t, "some_bool", matchingRules[0].Metadata()[2].Identifier) + assert.Equal(t, true, matchingRules[0].Metadata()[2].Value) + assert.Equal(t, "some_string", matchingRules[0].Metadata()[3].Identifier) + assert.Equal(t, "hello", matchingRules[0].Metadata()[3].Value) + assert.Equal(t, "some_bytes", matchingRules[0].Metadata()[4].Identifier) + assert.Equal(t, []byte{0, 1, 2}, matchingRules[0].Metadata()[4].Value) +} From 5a70903f2d3423f7d8d138e71bf57cf8f045adab Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Sat, 25 May 2024 20:25:40 +0200 Subject: [PATCH 26/32] docs: fix minor typo (#124) Signed-off-by: Philippe Ombredanne --- site/content/docs/intro/yara_vs_yara-x.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/content/docs/intro/yara_vs_yara-x.md b/site/content/docs/intro/yara_vs_yara-x.md index d9d9ce3a3..cbc02c69d 100644 --- a/site/content/docs/intro/yara_vs_yara-x.md +++ b/site/content/docs/intro/yara_vs_yara-x.md @@ -118,7 +118,7 @@ But maintaining those parser in-sync with the official one is hard, and very often they lag behind. YARA-X, in contrast, has a more modular design, where the parser is decoupled -from the rule compilation logic, allowing the re-use of the parser of other +from the rule compilation logic, allowing the re-use of the parser for other purposes. ## The bad things @@ -164,4 +164,4 @@ a directory, you don't need to use or maintain a `index.yar` file that contains Process scanning is not implemented in YARA-X yet. This may change in the future if this feature has enough demand. For the time being it's a low priority -feature, but let us know if you really want process scanning implemented. \ No newline at end of file +feature, but let us know if you really want process scanning implemented. From c7759f89b3fb80dc53a9314081fcf45329929e77 Mon Sep 17 00:00:00 2001 From: "Victor M. Alvarez" Date: Mon, 27 May 2024 10:14:32 +0200 Subject: [PATCH 27/32] refactor(cli): change the way in which the output path is passed to the `compile` command The output path was passed as the last positional argument for the command, but this have an inconvenience. If the user pass multiple input files and forgets to specify an output path, the last input path is used as output and the input file is overwritten. In order to prevent this, the output path is now passed with the `--output` option. If the option is missing, the default path `output.yarc` is used. Closes #126. --- cli/src/commands/compile.rs | 25 +++++++++++++------------ site/content/docs/intro/cli.md | 8 +++++--- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/cli/src/commands/compile.rs b/cli/src/commands/compile.rs index cf1e8da6b..e9dfcbf1a 100644 --- a/cli/src/commands/compile.rs +++ b/cli/src/commands/compile.rs @@ -17,17 +17,10 @@ pub fn compile() -> Command { .action(ArgAction::Append), ) .arg( - arg!() - .help("Path to file with compiled results") - .value_parser(value_parser!(PathBuf)), - ) - .arg( - arg!(--"path-as-namespace") - .help("Use file path as rule namespace"), - ) - .arg( - arg!(--"relaxed-re-syntax") - .help("Use a more relaxed syntax check while parsing regular expressions"), + arg!(-o --"output" ) + .help("Output file with compiled results") + .default_value("output.yarc") + .value_parser(value_parser!(PathBuf)) ) .arg( Arg::new("define") @@ -40,12 +33,20 @@ pub fn compile() -> Command { .value_parser(external_var_parser) .action(ArgAction::Append), ) + .arg( + arg!(--"path-as-namespace") + .help("Use file path as rule namespace"), + ) + .arg( + arg!(--"relaxed-re-syntax") + .help("Use a more relaxed syntax check while parsing regular expressions"), + ) } pub fn exec_compile(args: &ArgMatches) -> anyhow::Result<()> { let rules_path = args.get_many::("RULES_PATH").unwrap(); - let output_path = args.get_one::("OUTPUT_PATH").unwrap(); let path_as_namespace = args.get_flag("path-as-namespace"); + let output_path = args.get_one::("output").unwrap(); let external_vars: Option> = args .get_many::<(String, serde_json::Value)>("define") diff --git a/site/content/docs/intro/cli.md b/site/content/docs/intro/cli.md index 799de49f1..ba2466948 100644 --- a/site/content/docs/intro/cli.md +++ b/site/content/docs/intro/cli.md @@ -160,15 +160,17 @@ re-used for multiple scan operations. The syntax for this command is: ``` -yr compile [OPTIONS] ... +yr compile [OPTIONS] ... ``` Each `` is the path of YARA source file or a directory containing source files. When`` is a directory YARA-X iterates the directory recursively looking for any `*.yar` or `*.yara` files. -The `` is the path of the output binary file. The supported -options are: +### --output, -o + +Specify the path for the output binary file containing the compiled rules. By +default, is `output.yarc`. ### --relaxed-re-syntax From 865db1d1585a476188d71ad871312bc17d2423a7 Mon Sep 17 00:00:00 2001 From: "Victor M. Alvarez" Date: Mon, 27 May 2024 15:32:14 +0200 Subject: [PATCH 28/32] refactor: allow calling non-global rules from global ones. --- lib/src/compiler/context.rs | 26 +- lib/src/compiler/emit.rs | 53 +--- lib/src/compiler/errors.rs | 24 -- lib/src/compiler/ir/ast2ir.rs | 29 +- lib/src/compiler/mod.rs | 11 +- lib/src/compiler/tests/testdata/errors/36.in | 9 - lib/src/compiler/tests/testdata/errors/36.out | 14 - lib/src/scanner/context.rs | 41 +-- lib/src/scanner/mod.rs | 8 +- lib/src/scanner/tests.rs | 7 +- lib/src/wasm/builder.rs | 293 ++++++++---------- .../content/docs/writing_rules/differences.md | 19 -- 12 files changed, 173 insertions(+), 361 deletions(-) delete mode 100644 lib/src/compiler/tests/testdata/errors/36.in delete mode 100644 lib/src/compiler/tests/testdata/errors/36.out diff --git a/lib/src/compiler/context.rs b/lib/src/compiler/context.rs index 655ebbee1..3f7ff13f4 100644 --- a/lib/src/compiler/context.rs +++ b/lib/src/compiler/context.rs @@ -5,8 +5,7 @@ use std::rc::Rc; use yara_x_parser::report::ReportBuilder; use crate::compiler::ir::PatternIdx; -use crate::compiler::{ir, IdentId, RuleId, RuleInfo, Warnings}; -use crate::string_pool::StringPool; +use crate::compiler::{ir, Warnings}; use crate::symbols::{StackedSymbolTable, SymbolLookup}; use crate::types::Type; use crate::wasm; @@ -26,9 +25,6 @@ pub(in crate::compiler) struct CompileContext<'a, 'src, 'sym> { /// (i.e: `symbol_table`) is ignored. pub current_symbol_table: Option>, - /// Information about the rules compiled so far. - pub rules: &'a Vec, - /// Reference to a vector that contains the IR for the patterns declared /// in the current rule. pub current_rule_patterns: &'a mut Vec>, @@ -36,9 +32,6 @@ pub(in crate::compiler) struct CompileContext<'a, 'src, 'sym> { /// Warnings generated during the compilation. pub warnings: &'a mut Warnings, - /// Pool with identifiers used in the rules. - pub ident_pool: &'a mut StringPool, - /// Stack of variables. These are local variables used during the /// evaluation of rule conditions, for example for storing loop variables. pub vars: VarStack, @@ -48,23 +41,6 @@ pub(in crate::compiler) struct CompileContext<'a, 'src, 'sym> { } impl<'a, 'src, 'sym> CompileContext<'a, 'src, 'sym> { - /// Returns a [`RuleInfo`] given its [`RuleId`]. - /// - /// # Panics - /// - /// If no rule with such [`RuleId`] exists. - #[inline] - pub fn get_rule(&self, rule_id: RuleId) -> &RuleInfo { - self.rules.get(rule_id.0 as usize).unwrap() - } - - /// Returns the [`RuleInfo`] structure corresponding to the rule currently - /// being compiled. - #[inline] - pub fn get_current_rule(&self) -> &RuleInfo { - self.rules.last().unwrap() - } - /// Given a pattern identifier (e.g. `$a`, `#a`, `@a`) search for it in /// the current rule and return its position. /// diff --git a/lib/src/compiler/emit.rs b/lib/src/compiler/emit.rs index aeff95d51..175951ae9 100644 --- a/lib/src/compiler/emit.rs +++ b/lib/src/compiler/emit.rs @@ -246,21 +246,7 @@ pub(super) fn emit_rule_condition( rule_id: RuleId, condition: &mut Expr, ) { - // Global and non-global rules are put into two independent instruction - // sequences. The global rules are put into the instruction sequence that - // gets executed first, which means that global rules will be executed - // before any non-global rule, regardless of their order in the source - // code. Within the same group (global and non-global) rules maintain their - // relative order, though. - // - // Global rules can not invoke non-global rule. As global rules will always - // run before non-global ones, the former can't rely on the result of the - // latter. - let mut instr = if ctx.current_rule.is_global { - builder.new_global_rule() - } else { - builder.new_rule() - }; + let mut instr = builder.start_rule(rule_id, ctx.current_rule.is_global); // When the "logging" feature is enabled, print a log before the starting // evaluating the rule's condition. In case of error during the evaluation @@ -286,42 +272,7 @@ pub(super) fn emit_rule_condition( }, ); - // Check if the result from the condition is zero (false). - instr.unop(UnaryOp::I32Eqz); - instr.if_else( - None, - |then_| { - // The condition is false. For normal rules we don't do anything, - // but for global rules we must call `global_rule_no_match` and - // return 1. - // - // By returning 1 the function that contains the logic for this - // rule exits immediately, preventing any other rule (both global - // and non-global) in the same namespace is executed, and therefore - // they will remain false. - // - // This guarantees that any global rule that returns false, forces - // the non-global rules in the same namespace to be false. There - // may be some global rules that matched before, though. The - // purpose of `global_rule_no_match` is reverting those previous - // matches. - if ctx.current_rule.is_global { - // Call `global_rule_no_match`. - then_.i32_const(rule_id.0); - then_.call(ctx.function_id( - wasm::export__global_rule_no_match.mangled_name, - )); - // Return 1. - then_.i32_const(1); - then_.return_(); - } - }, - |else_| { - // The condition is true, call `rule_match`. - else_.i32_const(rule_id.0); - else_.call(ctx.function_id(wasm::export__rule_match.mangled_name)); - }, - ); + builder.finish_rule(); } /// Emits WASM code for `expr` into the instruction sequence `instr`. diff --git a/lib/src/compiler/errors.rs b/lib/src/compiler/errors.rs index 87fe99a86..164a30140 100644 --- a/lib/src/compiler/errors.rs +++ b/lib/src/compiler/errors.rs @@ -155,30 +155,6 @@ pub enum CompileError { ident_span: Span, }, - #[error("global rule `{global_rule}` depends on non-global rule `{non_global_rule}`")] - #[label( - "`{non_global_rule}` is used in the condition of `{global_rule}`", - non_global_rule_usage_span - )] - #[label( - "non-global rule `{non_global_rule}` declared here", - non_global_rule_span, - style = "note" - )] - #[label( - "global rule `{global_rule}` declared here", - global_rule_span, - style = "note" - )] - WrongRuleDependency { - detailed_report: String, - global_rule: String, - non_global_rule: String, - global_rule_span: Span, - non_global_rule_span: Span, - non_global_rule_usage_span: Span, - }, - #[error("invalid regular expression")] #[label("{error}", span)] #[note(note)] diff --git a/lib/src/compiler/ir/ast2ir.rs b/lib/src/compiler/ir/ast2ir.rs index 387036917..3b8cb316c 100644 --- a/lib/src/compiler/ir/ast2ir.rs +++ b/lib/src/compiler/ir/ast2ir.rs @@ -385,34 +385,7 @@ pub(in crate::compiler) fn expr_from_ast( } let symbol = symbol.unwrap(); - - // Return error if a global rule depends on a non-global rule. This - // is an error because global rules are evaluated before non-global - // rules, even if the global rule appears after the non-global one - // in the source code. This means that by the time the global rule - // is being evaluated we can't know if the non-global rule matched - // or not. - // A global rule can depend on another global rule. And non-global - // rules can depend both on global rules and non-global ones. - if let SymbolKind::Rule(rule_id) = symbol.kind() { - let current_rule = ctx.get_current_rule(); - let used_rule = ctx.get_rule(*rule_id); - if current_rule.is_global && !used_rule.is_global { - return Err(Box::new(CompileError::wrong_rule_dependency( - ctx.report_builder, - ctx.ident_pool - .get(current_rule.ident_id) - .unwrap() - .to_string(), - ident.name.to_string(), - current_rule.ident_span, - used_rule.ident_span, - ident.span, - ), - )); - } - } - + #[cfg(feature = "constant-folding")] { let type_value = symbol.type_value(); diff --git a/lib/src/compiler/mod.rs b/lib/src/compiler/mod.rs index 932e2dfab..7476f8c54 100644 --- a/lib/src/compiler/mod.rs +++ b/lib/src/compiler/mod.rs @@ -780,9 +780,7 @@ impl<'a> Compiler<'a> { relaxed_re_syntax: self.relaxed_re_syntax, current_symbol_table: None, symbol_table: &mut self.symbol_table, - ident_pool: &mut self.ident_pool, report_builder: &self.report_builder, - rules: &self.rules, current_rule_patterns: &mut rule_patterns, warnings: &mut self.warnings, vars: VarStack::new(), @@ -1722,7 +1720,7 @@ impl From for u64 { pub(crate) struct NamespaceId(i32); /// ID associated to each rule. -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, Default)] pub(crate) struct RuleId(i32); impl From for RuleId { @@ -1746,6 +1744,13 @@ impl From for usize { } } +impl From for i32 { + #[inline] + fn from(value: RuleId) -> Self { + value.0 + } +} + /// ID associated to each regexp used in a rule condition. #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub(crate) struct RegexpId(i32); diff --git a/lib/src/compiler/tests/testdata/errors/36.in b/lib/src/compiler/tests/testdata/errors/36.in deleted file mode 100644 index 1ea9baa1f..000000000 --- a/lib/src/compiler/tests/testdata/errors/36.in +++ /dev/null @@ -1,9 +0,0 @@ -rule test_1 { - condition: - true -} - -global rule test_2 { - condition: - test_1 -} \ No newline at end of file diff --git a/lib/src/compiler/tests/testdata/errors/36.out b/lib/src/compiler/tests/testdata/errors/36.out deleted file mode 100644 index 60a800c62..000000000 --- a/lib/src/compiler/tests/testdata/errors/36.out +++ /dev/null @@ -1,14 +0,0 @@ -error: global rule `test_2` depends on non-global rule `test_1` - --> line:8:5 - | -1 | rule test_1 { - | ------ note: non-global rule `test_1` declared here -2 | condition: -... -5 | -6 | global rule test_2 { - | ------ note: global rule `test_2` declared here -7 | condition: -8 | test_1 - | ^^^^^^ `test_1` is used in the condition of `test_2` - | \ No newline at end of file diff --git a/lib/src/scanner/context.rs b/lib/src/scanner/context.rs index 357441cd7..02632e5bb 100644 --- a/lib/src/scanner/context.rs +++ b/lib/src/scanner/context.rs @@ -49,16 +49,17 @@ pub(crate) struct ScanContext<'r> { /// Length of data being scanned. pub scanned_data_len: usize, /// Vector containing the IDs of the non-private rules that matched, - /// including both global and non-global ones. Global rules are initially - /// added to `global_matching_rules`, and once all the rules in the - /// namespace are evaluated, the global rules that matched are moved - /// to this vector. + /// including both global and non-global ones. The rules are added first + /// to the `matching_rules` map, and then moved to this vector once the + /// scan finishes. pub non_private_matching_rules: Vec, /// Vector containing the IDs of the private rules that matched, including - /// both global and non-global ones. + /// both global and non-global ones. The rules are added first to the + /// `matching_rules` map, and then moved to this vector once the scan + /// finishes. pub private_matching_rules: Vec, - /// Map containing the IDs of the global rules that matched. - pub global_matching_rules: FxHashMap>, + /// Map containing the IDs of rules that matched. + pub matching_rules: FxHashMap>, /// Compiled rules for this scan. pub compiled_rules: &'r Rules, /// Structure that contains top-level symbols, like module names @@ -246,21 +247,19 @@ impl ScanContext<'_> { /// Called during the scan process when a global rule didn't match. /// - /// When this happens any other global rule in the same namespace that - /// matched previously is reset to a non-matching state. + /// When this happens any other rule in the same namespace that matched + /// previously is reset to a non-matching state. pub(crate) fn track_global_rule_no_match(&mut self, rule_id: RuleId) { let rule = self.compiled_rules.get(rule_id); // This function must be called only for global rules. debug_assert!(rule.is_global); - // All the global rules that matched previously, and are in the same + // All the rules that matched previously, and are in the same // namespace as the non-matching rule, must be removed from the - // `global_matching_rules` map. Also, their corresponding bits in + // `matching_rules` map. Also, their corresponding bits in // the matching rules bitmap must be cleared. - if let Some(rules) = - self.global_matching_rules.get_mut(&rule.namespace_id) - { + if let Some(rules) = self.matching_rules.get_mut(&rule.namespace_id) { let wasm_store = unsafe { self.wasm_store.as_mut() }; let main_mem = self.main_memory.unwrap().data_mut(wasm_store); @@ -293,16 +292,10 @@ impl ScanContext<'_> { rule_id, ); - if rule.is_global { - self.global_matching_rules - .entry(rule.namespace_id) - .or_default() - .push(rule_id); - } else if rule.is_private { - self.private_matching_rules.push(rule_id); - } else { - self.non_private_matching_rules.push(rule_id); - } + self.matching_rules + .entry(rule.namespace_id) + .or_default() + .push(rule_id); let wasm_store = unsafe { self.wasm_store.as_mut() }; let mem = self.main_memory.unwrap().data_mut(wasm_store); diff --git a/lib/src/scanner/mod.rs b/lib/src/scanner/mod.rs index 9aa569574..7ba2232df 100644 --- a/lib/src/scanner/mod.rs +++ b/lib/src/scanner/mod.rs @@ -151,7 +151,7 @@ impl<'r> Scanner<'r> { scanned_data_len: 0, private_matching_rules: Vec::new(), non_private_matching_rules: Vec::new(), - global_matching_rules: FxHashMap::default(), + matching_rules: FxHashMap::default(), main_memory: None, module_outputs: FxHashMap::default(), user_provided_module_outputs: FxHashMap::default(), @@ -671,10 +671,10 @@ impl<'r> Scanner<'r> { // to some struct. ctx.current_struct = None; - // Move all the in `global_matching_rules` to `private_matching_rules` - // and `non_private_matching_rules`, leaving `global_matching_rules` + // Move all the in `matching_rules` to `private_matching_rules` + // and `non_private_matching_rules`, leaving `matching_rules` // empty. - for rules in ctx.global_matching_rules.values_mut() { + for rules in ctx.matching_rules.values_mut() { for rule_id in rules.drain(0..) { if ctx.compiled_rules.get(rule_id).is_private { ctx.private_matching_rules.push(rule_id); diff --git a/lib/src/scanner/tests.rs b/lib/src/scanner/tests.rs index ad32f030f..a495cd42d 100644 --- a/lib/src/scanner/tests.rs +++ b/lib/src/scanner/tests.rs @@ -401,10 +401,15 @@ fn global_rules() { compiler .add_source( r#" + // This rule is always true. + private rule const_true { + condition: + true + } // This global rule doesn't affect the results because it's true. global rule global_true { condition: - true + const_true } // Even if the condition is true, this rule doesn't match because of // the false global rule that follows. diff --git a/lib/src/wasm/builder.rs b/lib/src/wasm/builder.rs index 13c1610af..9b7af9dec 100644 --- a/lib/src/wasm/builder.rs +++ b/lib/src/wasm/builder.rs @@ -1,6 +1,8 @@ +use crate::compiler::RuleId; +use crate::wasm; use rustc_hash::FxHashMap; use std::mem; -use walrus::ir::{Block, InstrSeqId}; +use walrus::ir::{Block, InstrSeqId, UnaryOp}; use walrus::ValType::{F64, I32, I64}; use walrus::{FunctionBuilder, FunctionId, InstrSeqBuilder}; @@ -29,32 +31,27 @@ macro_rules! global_const { /// /// ```text /// func main { +/// ... initialization of some global variables. +/// /// call namespaces_0 /// ... +/// call namespaces_1 +/// ... /// call namespaces_N +/// +/// ... returns 0 if everything went ok or 1 if a timeout occurred. /// } /// ``` /// -/// Each of the `namespaces_X` function contains a block per YARA namespace, -/// and each of these blocks contains two inner blocks, for global and -/// non-global rules respectively. For example: +/// Each of the `namespaces_X` function contains a block per YARA namespace: +/// /// ```text /// func namespaces_0 { /// block { ;; block for namespace 0 -/// block { ;; block for global rules -/// ... -/// } -/// block { ;; block for non-global rules -/// ... -/// } +/// ... /// } /// block { ;; block for namespace 1 -/// block { ;; block for global rules -/// ... -/// } -/// block { ;; block for non-global rules -/// ... -/// } +/// ... /// } /// ... more blocks /// } @@ -73,23 +70,16 @@ macro_rules! global_const { /// In turn, each of the namespace blocks calls one or more rules functions /// which contains the logic for multiple YARA rules. This is how one of the /// namespace blocks looks in details: +/// /// ```text -/// block outer { ;; block for namespace 1 -/// block { ;; block for global rules -/// call global_rules_0 ;; calls a function that contains the logic -/// ;; for one or more global rules -/// br_if outer ;; exit the outer block if result is 1 -/// ... -/// call global_rules_n -/// br_if outer -/// } -/// block { ;; block for non-global rules -/// call rules_0 ;; calls a function that contains the logic for one -/// ;; or more non-global rules -/// ... -/// call rules_n -/// } -/// } +/// block namespaces_n { ;; block for namespace N +/// call rules_0 ;; calls a function that contains the logic +/// ;; for one or more rules +/// br_if namespaces_n ;; exit the namespace block if result is 1 +/// ... +/// call rules_n +/// br_if namespaces_n +/// } /// ``` /// /// Each of the rules function contains the code for multiple YARA rules. The @@ -97,26 +87,22 @@ macro_rules! global_const { /// rules per function. As in the case of namespaces, this has an impact in /// compilation time. This is how these functions look like: /// ```text -/// func global_rules_0 { -/// ... code for global rule 1. -/// ... -/// ... code for global rule N -/// return 0 ;; when global rules matched, the result is 0. -/// } -/// /// func rules_0 { -/// ... code for non-global rule 1 -/// ... -/// ... code for non-global rule 2 +/// ... code for rule 1 +/// ... code for rule 2 +/// ... code for global rule 1 +/// +/// ... +/// return 0 /// } /// ``` /// -/// Each of the functions containing global rules (i.e: `global_rules_N`) return -/// one of the following values: +/// Each of the functions containing rules (i.e: `rules_N`) return one of the +/// following values: /// -/// 0 - When all global rules matches +/// 0 - When all global rules matched /// 1 - When some global rule didn't match -/// 2 - If a timeout occurs +/// pub(crate) struct WasmModuleBuilder { module: walrus::Module, wasm_symbols: WasmSymbols, @@ -124,20 +110,17 @@ pub(crate) struct WasmModuleBuilder { main_func: FunctionBuilder, namespace_func: FunctionBuilder, rules_func: FunctionBuilder, - global_rules_func: FunctionBuilder, namespace_block: InstrSeqId, - global_rules_block: InstrSeqId, - rules_block: InstrSeqId, + rule_id: RuleId, num_rules: usize, - num_global_rules: usize, num_namespaces: usize, namespaces_per_func: usize, rules_per_func: usize, + global_rule: bool, } impl WasmModuleBuilder { - const GLOBAL_RULES_FUNC_RET: [walrus::ValType; 1] = [I32; 1]; - const RULES_FUNC_RET: [walrus::ValType; 0] = []; + const RULES_FUNC_RET: [walrus::ValType; 1] = [I32; 1]; /// Creates a new module builder. pub fn new() -> Self { @@ -178,12 +161,6 @@ impl WasmModuleBuilder { f64_tmp: module.locals.add(F64), }; - let global_rules_func = FunctionBuilder::new( - &mut module.types, - &[], - &Self::GLOBAL_RULES_FUNC_RET, - ); - let mut namespace_func = FunctionBuilder::new(&mut module.types, &[], &[]); @@ -205,25 +182,21 @@ impl WasmModuleBuilder { main_func.func_body().global_set(timeout_occurred); let namespace_block = namespace_func.dangling_instr_seq(None).id(); - let global_rules_block = namespace_func.dangling_instr_seq(None).id(); - let rules_block = namespace_func.dangling_instr_seq(None).id(); Self { module, wasm_symbols, wasm_exports, main_func, - global_rules_func, namespace_func, rules_func, namespace_block, - global_rules_block, - rules_block, + rule_id: RuleId::default(), num_rules: 0, - num_global_rules: 0, num_namespaces: 0, namespaces_per_func: 10, rules_per_func: 10, + global_rule: false, } } @@ -252,31 +225,75 @@ impl WasmModuleBuilder { self } - /// Returns a instruction sequence builder that can be used for emitting - /// code for a global YARA rule. The code emitted for a global rule must - /// return early with `return 1` if the rule didn't match. - pub fn new_global_rule(&mut self) -> InstrSeqBuilder { - if self.num_global_rules == self.rules_per_func { - self.finish_global_rule_func(); - self.num_global_rules = 0; - } - self.num_global_rules += 1; - self.global_rules_func.func_body() - } - /// Returns an instruction sequence builder that can be used for emitting - /// code for a non-global YARA rule. - pub fn new_rule(&mut self) -> InstrSeqBuilder { + /// code for a YARA rule. + /// + /// The code emitted for the rule must leave an i32 in the stack with value + /// 1 or 0 indicating whether the rule matched or not. + pub fn start_rule( + &mut self, + rule_id: RuleId, + global: bool, + ) -> InstrSeqBuilder { if self.num_rules == self.rules_per_func { self.finish_rule_func(); self.num_rules = 0; } self.num_rules += 1; + self.rule_id = rule_id; + self.global_rule = global; + self.rules_func.func_body() } + /// This finishes the code for a rule. + /// + /// Each call to [`WasmModuleBuilder::start_rule`] must be followed by a + /// call to this function once the code for rule has been emitted. + pub fn finish_rule(&mut self) { + let global_rule_no_match = + self.function_id(wasm::export__global_rule_no_match.mangled_name); + + let export_rule = + self.function_id(wasm::export__rule_match.mangled_name); + + let mut instr = self.rules_func.func_body(); + + // Check if the result from the condition is zero (false). + instr.unop(UnaryOp::I32Eqz).if_else( + None, + |then_| { + // The condition is false. For normal rules we don't do anything, + // but for global rules we must call `global_rule_no_match`, and + // return 1. + // + // By returning 1 the function that contains the logic for this + // rule exits immediately, preventing that any other rule in the + // same namespace is executed. + // + // This guarantees that any global rule that returns false, forces + // any other rule in the same namespace to be false. The purpose of + // calling `global_rule_no_match` is reverting any previous matches + // that occurred in the same namespace. + if self.global_rule { + then_ + // Call `global_rule_no_match`. + .i32_const(self.rule_id.into()) + .call(global_rule_no_match) + // Return 1. + .i32_const(1) + .return_(); + } + }, + |else_| { + // The condition is true, call `rule_match`. + else_.i32_const(self.rule_id.into()).call(export_rule); + }, + ); + } + + /// Starts a new namespace. pub fn new_namespace(&mut self) { - self.finish_global_rule_func(); self.finish_rule_func(); self.finish_namespace_block(); if self.num_namespaces == self.namespaces_per_func { @@ -285,12 +302,10 @@ impl WasmModuleBuilder { } self.num_namespaces += 1; self.num_rules = 0; - self.num_global_rules = 0; } /// Builds the WASM module and consumes the builder. pub fn build(mut self) -> walrus::Module { - self.finish_global_rule_func(); self.finish_rule_func(); self.finish_namespace_block(); self.finish_namespace_func(); @@ -311,52 +326,33 @@ impl WasmModuleBuilder { } impl WasmModuleBuilder { - fn finish_namespace_block(&mut self) { - let global_rules = !self - .namespace_func - .instr_seq(self.global_rules_block) - .instrs() - .is_empty(); + /// Given a function mangled name returns its id. + /// + /// # Panics + /// + /// If a no function with the given name exists. + pub fn function_id(&self, fn_mangled_name: &str) -> FunctionId { + *self.wasm_exports.get(fn_mangled_name).unwrap_or_else(|| { + panic!("can't find function `{}`", fn_mangled_name) + }) + } - let rules = !self + fn finish_namespace_block(&mut self) { + if !self .namespace_func - .instr_seq(self.rules_block) + .instr_seq(self.namespace_block) .instrs() - .is_empty(); - - if global_rules { + .is_empty() + { + // Add the current block to the namespace function and create a + // new block. self.namespace_func - .instr_seq(self.namespace_block) - .instr(Block { seq: self.global_rules_block }); - } - - if rules { - self.namespace_func - .instr_seq(self.namespace_block) - .instr(Block { seq: self.rules_block }); - } + .func_body() + .instr(Block { seq: self.namespace_block }); - match (global_rules, rules) { - (true, true) | (true, false) => { - self.namespace_func - .func_body() - .instr(Block { seq: self.namespace_block }); - } - (false, true) => { - self.namespace_func - .func_body() - .instr(Block { seq: self.rules_block }); - } - (false, false) => {} + self.namespace_block = + self.namespace_func.dangling_instr_seq(None).id(); } - - self.namespace_block = - self.namespace_func.dangling_instr_seq(None).id(); - - self.global_rules_block = - self.namespace_func.dangling_instr_seq(None).id(); - - self.rules_block = self.namespace_func.dangling_instr_seq(None).id(); } fn finish_namespace_func(&mut self) { @@ -368,46 +364,11 @@ impl WasmModuleBuilder { self.namespace_block = self.namespace_func.dangling_instr_seq(None).id(); - self.global_rules_block = - self.namespace_func.dangling_instr_seq(None).id(); - - self.rules_block = self.namespace_func.dangling_instr_seq(None).id(); - self.main_func.func_body().call( self.module.funcs.add_local(namespace_func.local_func(Vec::new())), ); } - fn finish_global_rule_func(&mut self) { - let mut global_rules_func = mem::replace( - &mut self.global_rules_func, - FunctionBuilder::new( - &mut self.module.types, - &[], - &Self::GLOBAL_RULES_FUNC_RET, - ), - ); - - if !global_rules_func.func_body().instrs().is_empty() { - // The last instruction in a global rules function leaves a - // 0 in the stack as its return value. This is reached only - // when all global rules match. If any global rules doesn't - // match, the function exits early with a return value of 1. - global_rules_func.func_body().i32_const(0); - - let mut block = - self.namespace_func.instr_seq(self.global_rules_block); - - block.call( - self.module - .funcs - .add_local(global_rules_func.local_func(Vec::new())), - ); - - block.br_if(self.namespace_block); - } - } - fn finish_rule_func(&mut self) { let mut rule_func = mem::replace( &mut self.rules_func, @@ -419,12 +380,26 @@ impl WasmModuleBuilder { ); if !rule_func.func_body().instrs().is_empty() { - let mut rules_block = - self.namespace_func.instr_seq(self.rules_block); + // The last instruction in a rules function leaves a 0 in the + // stack as its return value. This is reached only when all + // global rules match. If any global rules doesn't match, the + // function exits early with a return value of 1. + rule_func.func_body().i32_const(0); - rules_block.call( + let mut namespace_block = + self.namespace_func.instr_seq(self.namespace_block); + + namespace_block.call( self.module.funcs.add_local(rule_func.local_func(Vec::new())), ); + + let namespace_block_id = namespace_block.id(); + + // If the rules function returned 1 is because some global rule + // didn't match, in this case we exit early from the namespace + // block, preventing any other rule in the namespace from being + // executed. + namespace_block.br_if(namespace_block_id); } } } diff --git a/site/content/docs/writing_rules/differences.md b/site/content/docs/writing_rules/differences.md index f98936b95..99609e063 100644 --- a/site/content/docs/writing_rules/differences.md +++ b/site/content/docs/writing_rules/differences.md @@ -158,25 +158,6 @@ In YARA-X you can specify different alphabets for `base64` and `base64wide` in the same pattern. In the example above, `base64` will use the default alphabet as always, while `base64wide` will use the custom alphabet. -## Global rules can't depend on non-global rules - -In YARA 4.x a global rule can depend on a non-global rule, as long as the -non-global rule is declared first. For instance, this is valid in YARA 4.x: - -``` -rule my_non_global_rule { - condition: - .... -} - -global rule my_global_rule { - condition: - my_non_global_rule -} -``` - -In YARA-X this is forbidden, global rules can only depend on other global rules. - ## "of" statement accepts tuples of boolean expressions In YARA 4.x the `of` statement accepts a tuple of pattern or rule identifiers. From ae2ea2d0f06a5ab1858e060d25a124d622aacdfc Mon Sep 17 00:00:00 2001 From: "Victor M. Alvarez" Date: Mon, 27 May 2024 15:40:40 +0200 Subject: [PATCH 29/32] fix: prevent `rustfmt` error `rustfmt` was failing with: `error[internal]: left behind trailing whitespace`. --- lib/src/compiler/ir/ast2ir.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/compiler/ir/ast2ir.rs b/lib/src/compiler/ir/ast2ir.rs index 3b8cb316c..4354bf971 100644 --- a/lib/src/compiler/ir/ast2ir.rs +++ b/lib/src/compiler/ir/ast2ir.rs @@ -385,7 +385,6 @@ pub(in crate::compiler) fn expr_from_ast( } let symbol = symbol.unwrap(); - #[cfg(feature = "constant-folding")] { let type_value = symbol.type_value(); From 164b085598348c449e880099dee0e8c473143b2a Mon Sep 17 00:00:00 2001 From: "Victor M. Alvarez" Date: Mon, 27 May 2024 18:35:54 +0200 Subject: [PATCH 30/32] chore: add assertion --- lib/src/scanner/mod.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/src/scanner/mod.rs b/lib/src/scanner/mod.rs index 7ba2232df..3d34a6700 100644 --- a/lib/src/scanner/mod.rs +++ b/lib/src/scanner/mod.rs @@ -671,9 +671,15 @@ impl<'r> Scanner<'r> { // to some struct. ctx.current_struct = None; - // Move all the in `matching_rules` to `private_matching_rules` - // and `non_private_matching_rules`, leaving `matching_rules` - // empty. + // Both `private_matching_rules` and `non_private_matching_rules` are + // empty at this point. Matching rules were being tracked by the + // `matching_rules` map, but we are about to move them to these two + // vectors while leaving the map empty. + assert!(ctx.private_matching_rules.is_empty()); + assert!(ctx.non_private_matching_rules.is_empty()); + + // Move the matching rules the vectors, leaving the `matching_rules` + // map empty. for rules in ctx.matching_rules.values_mut() { for rule_id in rules.drain(0..) { if ctx.compiled_rules.get(rule_id).is_private { From e8dedd73ccaa325730381024df51982b37cf451d Mon Sep 17 00:00:00 2001 From: "Victor M. Alvarez" Date: Tue, 28 May 2024 10:45:12 +0200 Subject: [PATCH 31/32] fix: use `MAX_ATOMS_PER_REGEXP` constant instead of literal number. --- lib/src/re/thompson/compiler.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/re/thompson/compiler.rs b/lib/src/re/thompson/compiler.rs index bbfd0df0a..8a70ad608 100644 --- a/lib/src/re/thompson/compiler.rs +++ b/lib/src/re/thompson/compiler.rs @@ -148,7 +148,7 @@ impl Compiler { // users can increase the number if they start getting warnings due // to 0-length atoms, but for the time being let's use a number that // seems to work fine in most cases. - lit_extractor.limit_total(2048); + lit_extractor.limit_total(MAX_ATOMS_PER_REGEXP); lit_extractor.limit_literal_len(DESIRED_ATOM_SIZE); lit_extractor.limit_repeat(DESIRED_ATOM_SIZE); From 8c96849e7bead6c4515a46a62db3d532764c5b7f Mon Sep 17 00:00:00 2001 From: "Victor M. Alvarez" Date: Tue, 28 May 2024 18:57:54 +0200 Subject: [PATCH 32/32] feat: implement API for treating slow patterns as error instead of warnings (#131) Slow patterns are those that have atoms with less than 2 bytes. Such patterns can slow down scanning and the compiler raises a warning when it finds one of those patterns. The new APIs allow telling the compiler to raise an error instead of a warning. --- capi/include/yara_x.h | 4 ++++ capi/src/compiler.rs | 7 +++++++ go/compiler.go | 14 ++++++++++++++ go/compiler_test.go | 8 ++++++++ lib/src/compiler/errors.rs | 4 ++++ lib/src/compiler/mod.rs | 24 ++++++++++++++++++++++-- py/src/lib.rs | 27 ++++++++++++++++++++++----- py/tests/test_api.py | 6 ++++++ 8 files changed, 87 insertions(+), 7 deletions(-) diff --git a/capi/include/yara_x.h b/capi/include/yara_x.h index 0c781902f..deeb91118 100644 --- a/capi/include/yara_x.h +++ b/capi/include/yara_x.h @@ -33,6 +33,10 @@ // constructs that YARA-X doesn't accept by default. #define YRX_RELAXED_RE_SYNTAX 2 +// Flag passed to [`yrx_compiler_create`] for treating slow patterns as +// errors instead of warnings. +#define YRX_ERROR_ON_SLOW_PATTERN 4 + // Metadata value types. typedef enum YRX_METADATA_VALUE_TYPE { I64, diff --git a/capi/src/compiler.rs b/capi/src/compiler.rs index 4b61b5500..b62110895 100644 --- a/capi/src/compiler.rs +++ b/capi/src/compiler.rs @@ -27,6 +27,10 @@ pub const YRX_COLORIZE_ERRORS: u32 = 1; /// constructs that YARA-X doesn't accept by default. pub const YRX_RELAXED_RE_SYNTAX: u32 = 2; +/// Flag passed to [`yrx_compiler_create`] for treating slow patterns as +/// errors instead of warnings. +pub const YRX_ERROR_ON_SLOW_PATTERN: u32 = 4; + fn _yrx_compiler_create<'a>(flags: u32) -> yara_x::Compiler<'a> { let mut compiler = yara_x::Compiler::new(); if flags & YRX_RELAXED_RE_SYNTAX != 0 { @@ -35,6 +39,9 @@ fn _yrx_compiler_create<'a>(flags: u32) -> yara_x::Compiler<'a> { if flags & YRX_COLORIZE_ERRORS != 0 { compiler.colorize_errors(true); } + if flags & YRX_ERROR_ON_SLOW_PATTERN != 0 { + compiler.error_on_slow_pattern(true); + } compiler } diff --git a/go/compiler.go b/go/compiler.go index 5200e7c07..2e7e9ee6e 100644 --- a/go/compiler.go +++ b/go/compiler.go @@ -71,10 +71,20 @@ func RelaxedReSyntax(yes bool) CompileOption { } } +// ErrorOnSlowPattern is an option for [NewCompiler] and [Compile] that +// tells the compiler to treat slow patterns as errors instead of warnings. +func ErrorOnSlowPattern(yes bool) CompileOption { + return func(c *Compiler) error { + c.errorOnSlowPattern = yes + return nil + } +} + // Compiler represent a YARA compiler. type Compiler struct { cCompiler *C.YRX_COMPILER relaxedReSyntax bool + errorOnSlowPattern bool ignoredModules map[string]bool vars map[string]interface{} } @@ -97,6 +107,10 @@ func NewCompiler(opts... CompileOption) (*Compiler, error) { flags |= C.YRX_RELAXED_RE_SYNTAX } + if c.errorOnSlowPattern { + flags |= C.YRX_ERROR_ON_SLOW_PATTERN + } + C.yrx_compiler_create(flags, &c.cCompiler) if err := c.initialize(); err != nil { diff --git a/go/compiler_test.go b/go/compiler_test.go index 7f00b2fbf..daaea261f 100644 --- a/go/compiler_test.go +++ b/go/compiler_test.go @@ -39,6 +39,14 @@ func TestRelaxedReSyntax(t *testing.T) { assert.Len(t, matchingRules, 1) } + +func TestErrorOnSlowPattern(t *testing.T) { + _, err := Compile(` + rule test { strings: $a = /a.*b/ condition: $a }`, + ErrorOnSlowPattern(true)) + assert.Error(t, err) +} + func TestSerialization(t *testing.T) { r, err := Compile("rule test { condition: true }") assert.NoError(t, err) diff --git a/lib/src/compiler/errors.rs b/lib/src/compiler/errors.rs index 164a30140..7ca624f78 100644 --- a/lib/src/compiler/errors.rs +++ b/lib/src/compiler/errors.rs @@ -193,4 +193,8 @@ pub enum CompileError { span: Span, note: Option, }, + + #[error("slow pattern")] + #[label("this pattern may slow down the scan", span)] + SlowPattern { detailed_report: String, span: Span }, } diff --git a/lib/src/compiler/mod.rs b/lib/src/compiler/mod.rs index 7476f8c54..35d2b4be5 100644 --- a/lib/src/compiler/mod.rs +++ b/lib/src/compiler/mod.rs @@ -138,6 +138,10 @@ pub struct Compiler<'a> { /// escape sequences. relaxed_re_syntax: bool, + /// If true, slow patterns produce an error instead of a warning. A slow + /// pattern is one with atoms shorter than 2 bytes. + error_on_slow_pattern: bool, + /// Used for generating error and warning reports. report_builder: ReportBuilder, @@ -310,6 +314,7 @@ impl<'a> Compiler<'a> { wasm_symbols, wasm_exports, relaxed_re_syntax: false, + error_on_slow_pattern: false, next_pattern_id: PatternId(0), current_pattern_id: PatternId(0), current_namespace: default_namespace, @@ -590,6 +595,14 @@ impl<'a> Compiler<'a> { self } + /// When enabled, slow patterns produce an error instead of a warning. + /// + /// This is disabled by default. + pub fn error_on_slow_pattern(&mut self, yes: bool) -> &mut Self { + self.error_on_slow_pattern = yes; + self + } + /// Returns the warnings emitted by the compiler. #[inline] pub fn warnings(&self) -> &[Warning] { @@ -1597,8 +1610,15 @@ impl<'a> Compiler<'a> { } if slow_pattern { - self.warnings - .add(|| Warning::slow_pattern(&self.report_builder, span)); + if self.error_on_slow_pattern { + return Err(Box::new(CompileError::slow_pattern( + &self.report_builder, + span, + ))); + } else { + self.warnings + .add(|| Warning::slow_pattern(&self.report_builder, span)); + } } Ok((atoms, is_fast_regexp)) diff --git a/py/src/lib.rs b/py/src/lib.rs index 6e179e009..8c0dde123 100644 --- a/py/src/lib.rs +++ b/py/src/lib.rs @@ -50,14 +50,21 @@ fn compile(src: &str) -> PyResult { struct Compiler { inner: yrx::Compiler<'static>, relaxed_re_syntax: bool, + error_on_slow_pattern: bool, } impl Compiler { - fn new_inner(relaxed_re_syntax: bool) -> yrx::Compiler<'static> { + fn new_inner( + relaxed_re_syntax: bool, + error_on_slow_pattern: bool, + ) -> yrx::Compiler<'static> { let mut compiler = yrx::Compiler::new(); if relaxed_re_syntax { compiler.relaxed_re_syntax(true); } + if error_on_slow_pattern { + compiler.error_on_slow_pattern(true); + } compiler } } @@ -77,10 +84,17 @@ impl Compiler { /// their meaning from the context (e.g., `{` and `}` in `/foo{}bar/` are /// literal, but in `/foo{0,1}bar/` they form the repetition operator /// `{0,1}`). + /// + /// The `error_on_slow_pattern` argument tells the compiler to treat slow + /// patterns as errors, instead of warnings. #[new] - #[pyo3(signature = (*, relaxed_re_syntax=false))] - fn new(relaxed_re_syntax: bool) -> Self { - Self { inner: Self::new_inner(relaxed_re_syntax), relaxed_re_syntax } + #[pyo3(signature = (*, relaxed_re_syntax=false, error_on_slow_pattern=false))] + fn new(relaxed_re_syntax: bool, error_on_slow_pattern: bool) -> Self { + Self { + inner: Self::new_inner(relaxed_re_syntax, error_on_slow_pattern), + relaxed_re_syntax, + error_on_slow_pattern, + } } /// Adds a YARA source code to be compiled. @@ -160,7 +174,10 @@ impl Compiler { fn build(&mut self) -> Rules { let compiler = mem::replace( &mut self.inner, - Self::new_inner(self.relaxed_re_syntax), + Self::new_inner( + self.relaxed_re_syntax, + self.error_on_slow_pattern, + ), ); Rules::new(compiler.build()) } diff --git a/py/tests/test_api.py b/py/tests/test_api.py index 60f20c06b..3aa7b5de9 100644 --- a/py/tests/test_api.py +++ b/py/tests/test_api.py @@ -23,6 +23,12 @@ def test_relaxed_re_syntax(): assert len(matching_rules) == 1 +def test_error_on_slow_pattern(): + compiler = yara_x.Compiler(error_on_slow_pattern=True) + with pytest.raises(yara_x.CompileError): + compiler.add_source(r'rule test {strings: $a = /a.*b/ condition: $a}') + + def test_int_globals(): compiler = yara_x.Compiler() compiler.define_global('some_int', 1)