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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions boreal-cli/src/args/callback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub struct CallbackOptions {
pub print_count: bool,
pub print_statistics: bool,
pub print_module_data: bool,
pub match_max_length: Option<usize>,
pub count_limit: Option<u64>,
pub identifier: Option<String>,
pub tag: Option<String>,
Expand All @@ -32,6 +33,7 @@ impl CallbackOptions {
print_count: args.get_flag("count"),
print_statistics: args.get_flag("print_scan_statistics"),
print_module_data: args.get_flag("print_module_data"),
match_max_length: args.get_one::<usize>("match_max_length").copied(),
count_limit: args.get_one::<u64>("count_limit").copied(),
identifier: args.get_one("identifier").cloned(),
tag: args.get_one("tag").cloned(),
Expand Down Expand Up @@ -108,6 +110,18 @@ pub fn add_callback_args(command: Command) -> Command {
.action(ArgAction::SetTrue)
.help("Print rule tags"),
)
.arg(
Arg::new("match_max_length")
.long("match-max-length")
.value_parser(value_parser!(usize))
.help("Maximum length of string matches")
.long_help(
"Maximum length of string matches.\n\n\
If string matches are printed, they will be truncated if \
they exceed this limit.\n\
The default value is 512."
)
)
.arg(
Arg::new("count")
.short('c')
Expand Down
53 changes: 53 additions & 0 deletions boreal-cli/src/args/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ pub struct CompilerOptions {
pub profile: Option<CompilerProfile>,
pub compute_statistics: bool,
pub max_strings_per_rule: Option<usize>,
pub max_condition_depth: Option<u32>,
pub parse_expr_recursion_limit: Option<u8>,
pub parse_string_recursion_limit: Option<u8>,
pub disable_includes: bool,
pub defines: Option<Values<(String, ExternalValue)>>,
pub rules_files: Vec<String>,
}
Expand All @@ -25,6 +29,10 @@ impl CompilerOptions {
profile: args.remove_one::<CompilerProfile>("profile"),
compute_statistics: args.get_flag("string_statistics"),
max_strings_per_rule: args.remove_one::<usize>("max_strings_per_rule"),
max_condition_depth: args.remove_one::<u32>("max_condition_depth"),
parse_expr_recursion_limit: args.remove_one::<u8>("parse_expr_recursion_limit"),
parse_string_recursion_limit: args.remove_one::<u8>("parse_string_recursion_limit"),
disable_includes: args.get_flag("disable_includes"),
defines: args.remove_many::<(String, ExternalValue)>("define"),
rules_files,
}
Expand Down Expand Up @@ -54,6 +62,51 @@ pub fn add_compiler_args(mut command: Command, in_yr_subcommand: bool) -> Comman
.value_parser(value_parser!(usize))
.help("Maximum number of strings in a single rule"),
)
.arg(
Arg::new("max_condition_depth")
.long("max-condition-depth")
.value_name("NUMBER")
.value_parser(value_parser!(u32))
.help("Maximum depth in a rule's condition AST")
.long_help(
"Maximum depth in a rule's condition AST.\n\n\
Defensive limit used to prevent stack overflow.\n\
Should this limit be reached when compiling rules, this parameter \
can be used to modify it.",
),
)
.arg(
Arg::new("parse_expr_recursion_limit")
.long("parse-expression-recursion-limit")
.value_name("NUMBER")
.value_parser(value_parser!(u8))
.help("Maximum recursion depth allowed when parsing an expression")
.long_help(
"Maximum recursion depth allowed when parsing an expression.\n\n\
Defensive limit used to prevent stack overflow.\n\
Should this limit be reached when compiling rules, this parameter \
can be used to modify it.",
),
)
.arg(
Arg::new("parse_string_recursion_limit")
.long("parse-string-recursion-limit")
.value_name("NUMBER")
.value_parser(value_parser!(u8))
.help("Maximum recursion depth allowed when parsing a regex or a hex-string")
.long_help(
"Maximum recursion depth allowed when parsing a regex or a hex-string.\n\n\
Defensive limit used to prevent stack overflow.\n\
Should this limit be reached when compiling rules, this parameter \
can be used to modify it.",
),
)
.arg(
Arg::new("disable_includes")
.long("disable-includes")
.action(ArgAction::SetTrue)
.help("Disable the possibility to include yara files"),
)
.arg(
Arg::new("define")
.short('d')
Expand Down
26 changes: 22 additions & 4 deletions boreal-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,10 @@ fn compile_rules(options: CompilerOptions, warning_mode: WarningMode) -> Option<
profile,
compute_statistics,
max_strings_per_rule,
max_condition_depth,
parse_expr_recursion_limit,
parse_string_recursion_limit,
disable_includes,
defines,
rules_files,
} = options;
Expand All @@ -244,10 +248,20 @@ fn compile_rules(options: CompilerOptions, warning_mode: WarningMode) -> Option<

let mut params = CompilerParams::default()
.fail_on_warnings(matches!(warning_mode, WarningMode::Fail))
.compute_statistics(compute_statistics);
.compute_statistics(compute_statistics)
.disable_includes(disable_includes);
if let Some(limit) = max_strings_per_rule {
params = params.max_strings_per_rule(limit);
}
if let Some(limit) = max_condition_depth {
params = params.max_condition_depth(limit);
}
if let Some(limit) = parse_expr_recursion_limit {
params = params.parse_expression_recursion_limit(limit);
}
if let Some(limit) = parse_string_recursion_limit {
params = params.parse_string_recursion_limit(limit);
}
compiler.set_params(params);

if let Some(defines) = defines {
Expand Down Expand Up @@ -433,10 +447,13 @@ fn update_scanner_params_from_callback_options(scanner: &mut Scanner, options: &
callback_events |= CallbackEvents::RULE_MATCH;
}

let mut params = scanner.scan_params().clone();
if let Some(max) = options.match_max_length {
params = params.match_max_length(max);
}

scanner.set_scan_params(
scanner
.scan_params()
.clone()
params
.compute_full_matches(options.print_strings_matches())
.compute_statistics(options.print_statistics)
.include_not_matched_rules(options.negate)
Expand Down Expand Up @@ -946,6 +963,7 @@ mod tests {
print_count: false,
print_statistics: false,
print_module_data: false,
match_max_length: None,
count_limit: None,
identifier: None,
tag: None,
Expand Down
151 changes: 151 additions & 0 deletions boreal-cli/tests/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1147,6 +1147,41 @@ rule my_rule {
);
}

#[test]
fn test_match_max_length() {
let rule_file = test_file(
br#"
rule my_rule {
strings:
$a = /<.*>/
condition:
any of them
}"#,
);

let input = test_file(b"<12345678> <123456> <1>");
let path = input.path().display();

// Test match data only
test_scan(
&["-s", "--match-max-length=5"],
&[rule_file.path()],
input.path(),
|cmd| {
cmd.assert()
.stdout(format!(
r#"my_rule {path}
0x0:$a: <1234
0xb:$a: <1234
0x14:$a: <1>
"#
))
.stderr("")
.success();
},
);
}

#[test]
fn test_print_string_xor_key() {
let rule_file = test_file(
Expand Down Expand Up @@ -1613,6 +1648,122 @@ rule a {
);
}

#[test]
fn test_max_condition_depth() {
let rule_file = test_file(
br#"
rule a {
condition:
5 + (4 - (3 + (2 * 1)))
}
"#,
);

let input = test_file(b"");
test_scan(
&["--max-condition-depth=2"],
&[rule_file.path()],
input.path(),
|cmd| {
cmd.assert()
.stdout("")
.stderr(
predicate::str::contains("error")
.and(predicate::str::contains("condition is too complex")),
)
.failure();
},
);
}

#[test]
fn test_parse_expression_recursion_limit() {
let rule_file = test_file(
br#"
rule a {
condition:
5 + (4 - (3 + (2 * 1)))
}
"#,
);

let input = test_file(b"");
test_scan(
&["--parse-expression-recursion-limit=2"],
&[rule_file.path()],
input.path(),
|cmd| {
cmd.assert()
.stdout("")
.stderr(
predicate::str::contains("error")
.and(predicate::str::contains("too many imbricated expressions")),
)
.failure();
},
);
}

#[test]
fn test_parse_string_recursion_limit() {
let rule_file = test_file(
br#"
rule a {
strings:
$ = { A0 (B0 (C0 (D0 (E0 | F0)))) }
condition:
true
}
"#,
);

let input = test_file(b"");
test_scan(
&["--parse-string-recursion-limit=2"],
&[rule_file.path()],
input.path(),
|cmd| {
cmd.assert()
.stdout("")
.stderr(
predicate::str::contains("error").and(predicate::str::contains(
"too many imbricated groups in the hex string",
)),
)
.failure();
},
);
}

#[test]
fn test_disable_includes() {
let rule_file = test_file(
br#"
include "other.yar"

rule a {
condition: true
}
"#,
);

let input = test_file(b"");
test_scan(
&["--disable-includes"],
&[rule_file.path()],
input.path(),
|cmd| {
cmd.assert()
.stdout("")
.stderr(
predicate::str::contains("error")
.and(predicate::str::contains("includes are not allowed")),
)
.failure();
},
);
}

#[test]
fn test_string_max_nb_matches() {
let rule_file = test_file(
Expand Down
27 changes: 25 additions & 2 deletions boreal-py/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,16 @@ fn boreal(m: &Bound<'_, PyModule>) -> PyResult<()> {
/// otherwise.
/// profile: Profile to use when compiling the rules. If not specified,
/// `CompilerProfile::Speed` is used.
/// max_strings_per_rule: Maximum number of strings allowed in a single rule.
/// If a rule has more strings than this limit, its compilation will fail.
/// max_strings_per_rule: Maximum number of strings allowed in a single rule.
/// If a rule has more strings than this limit, its compilation will fail.
/// max_condition_depth: Maximum depth in a rule's condition AST.
/// Defensive limit used to prevent stack overflow.
/// parse_expression_recursion_limit: Maximum recursion depth allowed when
/// parsing an expression.
/// Defensive limit used to prevent stack overflow.
/// parse_string_recursion_limit: Maximum recursion depth allowed when
/// parsing a regex or a hex-string.
/// Defensive limit used to prevent stack overflow.
///
/// Returns:
/// a `Scanner` object that holds the compiled rules.
Expand All @@ -158,6 +166,9 @@ fn boreal(m: &Bound<'_, PyModule>) -> PyResult<()> {
strict_escape=None,
profile=None,
max_strings_per_rule=None,
max_condition_depth=None,
parse_expression_recursion_limit=None,
parse_string_recursion_limit=None,
))]
#[allow(clippy::too_many_arguments)]
fn compile(
Expand All @@ -173,6 +184,9 @@ fn compile(
strict_escape: Option<bool>,
profile: Option<&CompilerProfile>,
max_strings_per_rule: Option<usize>,
max_condition_depth: Option<u32>,
parse_expression_recursion_limit: Option<u8>,
parse_string_recursion_limit: Option<u8>,
) -> PyResult<scanner::Scanner> {
let mut compiler = build_compiler(profile);

Expand All @@ -192,6 +206,15 @@ fn compile(
if let Some(value) = max_strings_per_rule {
params = params.max_strings_per_rule(value);
}
if let Some(limit) = max_condition_depth {
params = params.max_condition_depth(limit);
}
if let Some(limit) = parse_expression_recursion_limit {
params = params.parse_expression_recursion_limit(limit);
}
if let Some(limit) = parse_string_recursion_limit {
params = params.parse_string_recursion_limit(limit);
}

compiler.set_params(params);

Expand Down
Loading