Skip to content

Commit 3522484

Browse files
committed
feat: allow setting callback for console module in scanner
1 parent 928e380 commit 3522484

File tree

3 files changed

+120
-10
lines changed

3 files changed

+120
-10
lines changed

boreal/src/module/console.rs

Lines changed: 70 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,37 @@ use std::{collections::HashMap, sync::Arc};
55
use super::{EvalContext, Module, ModuleData, ModuleDataMap, StaticValue, Type, Value};
66

77
/// `console` module.
8+
///
9+
/// To use the module, you must provide a callback which will be called when
10+
/// `console.log` is called.
11+
///
12+
/// The callback can also be overridden on every scan by specifying a `ConsoleData`
13+
/// in the scanner.
14+
///
15+
/// ```
16+
/// use boreal::module::{Console, ConsoleData};
17+
/// use boreal::compiler::CompilerBuilder;
18+
///
19+
/// let mut compiler = CompilerBuilder::new()
20+
/// // Do not log anything by default
21+
/// .add_module(Console::with_callback(|_log| {}))
22+
/// .build();
23+
/// compiler.add_rules_str(r#"
24+
/// import "console"
25+
///
26+
/// rule a {
27+
/// condition: console.log("one")
28+
/// }"#).unwrap();
29+
/// let mut scanner = compiler.into_scanner();
30+
///
31+
/// scanner.scan_mem(b""); // Will not log anything
32+
///
33+
/// let console_data = ConsoleData::new(|log| {
34+
/// println!("yara console log: {log}");
35+
/// });
36+
/// scanner.set_module_data::<Console>(console_data);
37+
/// scanner.scan_mem(b""); // Will log "yara console log: one"
38+
/// ```
839
pub struct Console {
940
callback: Arc<LogCallback>,
1041
}
@@ -47,19 +78,40 @@ impl Module for Console {
4778
}
4879

4980
fn setup_new_scan(&self, data_map: &mut ModuleDataMap) {
50-
data_map.insert::<Self>(Data {
81+
data_map.insert::<Self>(PrivateData {
5182
callback: Arc::clone(&self.callback),
5283
});
5384
}
5485
}
5586

56-
pub struct Data {
87+
pub struct PrivateData {
5788
callback: Arc<LogCallback>,
5889
}
5990

6091
impl ModuleData for Console {
61-
type PrivateData = Data;
62-
type UserData = ();
92+
type PrivateData = PrivateData;
93+
type UserData = ConsoleData;
94+
}
95+
96+
/// Data used by the console module.
97+
///
98+
/// This data can be provided by a call to
99+
/// [`Scanner::set_module_data`](crate::scanner::Scanner::set_module_data) to override
100+
/// the default callback that was specified when compiling rules.
101+
pub struct ConsoleData {
102+
callback: Box<LogCallback>,
103+
}
104+
105+
impl ConsoleData {
106+
/// Provide a callback called when console.log is evaluted in rules.
107+
pub fn new<T>(callback: T) -> Self
108+
where
109+
T: Fn(String) + Send + Sync + UnwindSafe + RefUnwindSafe + 'static,
110+
{
111+
Self {
112+
callback: Box::new(callback),
113+
}
114+
}
63115
}
64116

65117
impl Console {
@@ -85,8 +137,7 @@ impl Console {
85137
add_value(arg, &mut res)?;
86138
}
87139

88-
let data = ctx.module_data.get::<Console>()?;
89-
(data.callback)(res);
140+
call_callback(ctx, res)?;
90141

91142
Some(Value::Integer(1))
92143
}
@@ -104,13 +155,24 @@ impl Console {
104155
}
105156
};
106157

107-
let data = ctx.module_data.get::<Console>()?;
108-
(data.callback)(res);
158+
call_callback(ctx, res)?;
109159

110160
Some(Value::Integer(1))
111161
}
112162
}
113163

164+
fn call_callback(ctx: &EvalContext, log: String) -> Option<()> {
165+
// First, check if there is a callback specified for this scan.
166+
if let Some(data) = ctx.module_data.get_user_data::<Console>() {
167+
(data.callback)(log);
168+
} else {
169+
// Otherwise, use the callback specified when building the module.
170+
let data = ctx.module_data.get::<Console>()?;
171+
(data.callback)(log);
172+
}
173+
Some(())
174+
}
175+
114176
fn add_value(value: Value, out: &mut String) -> Option<()> {
115177
match value {
116178
Value::Integer(v) => write!(out, "{v}").ok(),

boreal/src/module/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ use crate::memory::{Memory, Region};
4545
use crate::regex::Regex;
4646

4747
mod console;
48-
pub use console::{Console, LogCallback};
48+
pub use console::{Console, ConsoleData, LogCallback};
4949

5050
mod time;
5151
pub use time::Time;

boreal/tests/it/modules.rs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::sync::Mutex;
22

33
use boreal::compiler::CompilerBuilder;
4+
use boreal::module::{Console, ConsoleData};
45

56
use crate::utils::{check, check_boreal, check_err, Compiler};
67

@@ -452,7 +453,7 @@ fn test_module_console() {
452453
let mut compiler = Compiler::new();
453454
// Replace boreal compiler with a new one to add the console module
454455
compiler.compiler = CompilerBuilder::new()
455-
.add_module(boreal::module::Console::with_callback(|log| {
456+
.add_module(Console::with_callback(|log| {
456457
LOGS.lock().unwrap().push(log);
457458
}))
458459
.build();
@@ -511,6 +512,53 @@ rule a {
511512
);
512513
}
513514

515+
// Check the ConsoleData object can be used to override the callback.
516+
#[test]
517+
fn test_module_console_data() {
518+
static LOGS: Mutex<Vec<String>> = Mutex::new(Vec::new());
519+
520+
// Replace boreal compiler with a new one to add the console module
521+
let mut compiler = CompilerBuilder::new()
522+
.add_module(Console::with_callback(|_log| {}))
523+
.build();
524+
525+
compiler
526+
.add_rules_str(
527+
r#"import "console"
528+
rule a {
529+
condition:
530+
console.log("value") and console.hex(12872)
531+
}"#,
532+
)
533+
.unwrap();
534+
535+
let scanner = compiler.into_scanner();
536+
537+
// Nothing emitted by default
538+
scanner.scan_mem(b"").unwrap();
539+
assert_eq!(LOGS.lock().unwrap().len(), 0);
540+
541+
// But overriding works
542+
{
543+
let mut scanner2 = scanner.clone();
544+
scanner2.set_module_data::<Console>(ConsoleData::new(|log| {
545+
LOGS.lock().unwrap().push(log);
546+
}));
547+
scanner2.scan_mem(b"").unwrap();
548+
assert_eq!(&*LOGS.lock().unwrap(), &["value", "0x3248",]);
549+
LOGS.lock().unwrap().clear();
550+
551+
// This data stays for the whole scanner lifetime
552+
scanner2.scan_mem(b"").unwrap();
553+
assert_eq!(&*LOGS.lock().unwrap(), &["value", "0x3248",]);
554+
LOGS.lock().unwrap().clear();
555+
}
556+
557+
// The original scanner was not modified
558+
scanner.scan_mem(b"").unwrap();
559+
assert_eq!(LOGS.lock().unwrap().len(), 0);
560+
}
561+
514562
#[test]
515563
fn test_module_iterable_imbricated() {
516564
check_ok(

0 commit comments

Comments
 (0)