use assert_cmd::{Command, cargo_bin};
use assert_fs::TempDir;
use assert_fs::prelude::*;
use predicates::prelude::*;
use serde_json;
#[test]
fn always_true() {
Command::new(cargo_bin!("yr"))
.arg("scan")
.arg("src/tests/testdata/true.yar")
.arg("src/tests/testdata/dummy.file")
.assert()
.success()
.stderr(predicate::str::contains(
"warning[invariant_expr]: invariant boolean expression",
))
.stdout(predicate::str::contains(
"always_true src/tests/testdata/dummy.file",
));
}
#[test]
fn negate() {
Command::new(cargo_bin!("yr"))
.arg("scan")
.arg("--negate")
.arg("src/tests/testdata/true.yar")
.arg("src/tests/testdata/dummy.file")
.assert()
.success()
.stdout("");
}
#[test]
fn scan_list_from_stdin() {
Command::new(cargo_bin!("yr"))
.arg("scan")
.arg("--count")
.arg("--scan-list")
.arg("--threads=1")
.arg("src/tests/testdata/foo.yar")
.arg("-")
.write_stdin("src/tests/testdata/dummy.file\n")
.assert()
.success()
.stdout("src/tests/testdata/dummy.file: 1\n");
}
#[test]
fn filter_by_tag() {
Command::new(cargo_bin!("yr"))
.arg("scan")
.arg("--tag=foo")
.arg("src/tests/testdata/foo.yar")
.arg("src/tests/testdata/dummy.file")
.assert()
.success()
.stdout("");
Command::new(cargo_bin!("yr"))
.arg("scan")
.arg("--tag=bar")
.arg("src/tests/testdata/foo.yar")
.arg("src/tests/testdata/dummy.file")
.assert()
.success()
.stdout(predicate::str::contains("foo src/tests/testdata/dummy.file"));
let temp_dir = TempDir::new().unwrap();
let rules_file = temp_dir.child("rules.yar");
rules_file
.write_str(
r#"
rule rule_a : tag_a { condition: true }
rule rule_b : tag_b { condition: true }
"#,
)
.unwrap();
Command::new(cargo_bin!("yr"))
.arg("scan")
.arg("--output-format=ndjson")
.arg("--tag=tag_a")
.arg(rules_file.path())
.arg("src/tests/testdata/dummy.file")
.assert()
.success()
.stdout("{\"path\":\"src/tests/testdata/dummy.file\",\"rules\":[{\"identifier\":\"rule_a\"}]}\n");
Command::new(cargo_bin!("yr"))
.arg("scan")
.arg("--output-format=ndjson")
.arg("--tag=tag_c")
.arg(rules_file.path())
.arg("src/tests/testdata/dummy.file")
.assert()
.success()
.stdout("");
let output = Command::new(cargo_bin!("yr"))
.arg("scan")
.arg("--output-format=json")
.arg("--tag=tag_a")
.arg(rules_file.path())
.arg("src/tests/testdata/dummy.file")
.assert()
.success()
.get_output()
.stdout
.clone();
let json: serde_json::Value =
serde_json::from_slice(&output).expect("valid JSON output");
let matches = json["matches"].as_array().unwrap();
assert_eq!(matches.len(), 1);
assert_eq!(matches[0]["rule"], "rule_a");
let output = Command::new(cargo_bin!("yr"))
.arg("scan")
.arg("--output-format=json")
.arg("--tag=tag_c")
.arg(rules_file.path())
.arg("src/tests/testdata/dummy.file")
.assert()
.success()
.get_output()
.stdout
.clone();
let json: serde_json::Value =
serde_json::from_slice(&output).expect("valid JSON output");
let matches = json["matches"].as_array().unwrap();
assert!(matches.is_empty());
}
#[test]
fn disable_warning() {
Command::new(cargo_bin!("yr"))
.arg("scan")
.arg("--disable-warnings=invariant_expr")
.arg("src/tests/testdata/true.yar")
.arg("src/tests/testdata/dummy.file")
.assert()
.success()
.stderr(
predicate::str::contains(
"warning[invariant_expr]: invariant boolean expression",
)
.not(),
)
.stdout(predicate::str::contains(
"always_true src/tests/testdata/dummy.file",
));
}
#[test]
fn disable_warning_config_file() {
let temp_dir = TempDir::new().unwrap();
let config_file = temp_dir.child("config.toml");
config_file
.write_str(
r#"
[warnings]
invariant_expr = { disabled = true }
"#,
)
.unwrap();
Command::new(cargo_bin!("yr"))
.arg("--config")
.arg(config_file.path())
.arg("scan")
.arg("src/tests/testdata/true.yar")
.arg("src/tests/testdata/dummy.file")
.assert()
.success()
.stderr(
predicate::str::contains(
"warning[invariant_expr]: invariant boolean expression",
)
.not(),
)
.stdout(predicate::str::contains(
"always_true src/tests/testdata/dummy.file",
));
}
#[test]
fn print_strings() {
Command::new(cargo_bin!("yr"))
.arg("scan")
.arg("--print-strings")
.arg("src/tests/testdata/foo.yar")
.arg("src/tests/testdata/dummy.file")
.assert()
.success()
.stdout(
"foo src/tests/testdata/dummy.file
0x0:3:$foo: foo
0x0:3:$foo_hex: 66 6f 6f
",
);
}
#[test]
fn print_strings_n() {
Command::new(cargo_bin!("yr"))
.arg("scan")
.arg("--print-strings=2")
.arg("src/tests/testdata/foo.yar")
.arg("src/tests/testdata/dummy.file")
.assert()
.success()
.stdout(
"foo src/tests/testdata/dummy.file
0x0:3:$foo: fo ... 1 more bytes
0x0:3:$foo_hex: 66 6f ... 1 more bytes
",
);
}
#[test]
fn print_namespace() {
Command::new(cargo_bin!("yr"))
.arg("scan")
.arg("--print-namespace")
.arg("src/tests/testdata/foo.yar")
.arg("src/tests/testdata/dummy.file")
.assert()
.success()
.stdout("default:foo src/tests/testdata/dummy.file\n");
}
#[test]
fn print_meta() {
Command::new(cargo_bin!("yr"))
.arg("scan")
.arg("--print-meta")
.arg("src/tests/testdata/foo.yar")
.arg("src/tests/testdata/dummy.file")
.assert()
.success()
.stdout("foo [string=\"foo\",bool=true,int=1,float=3.14,regexp=\"foo\"] src/tests/testdata/dummy.file\n");
}
#[test]
fn print_tags() {
Command::new(cargo_bin!("yr"))
.arg("scan")
.arg("--print-tags")
.arg("src/tests/testdata/foo.yar")
.arg("src/tests/testdata/dummy.file")
.assert()
.success()
.stdout("foo [bar,baz] src/tests/testdata/dummy.file\n");
}
#[test]
fn path_as_namespace() {
Command::new(cargo_bin!("yr"))
.arg("scan")
.arg("--print-namespace")
.arg("--path-as-namespace")
.arg("src/tests/testdata/foo.yar")
.arg("src/tests/testdata/dummy.file")
.assert()
.success()
.stdout(
"src/tests/testdata/foo.yar:foo src/tests/testdata/dummy.file\n",
);
}
#[test]
fn format_ndjson() {
Command::new(cargo_bin!("yr"))
.arg("scan")
.arg("--output-format=ndjson")
.arg("src/tests/testdata/foo.yar")
.arg("src/tests/testdata/dummy.file")
.assert()
.success()
.stdout("{\"path\":\"src/tests/testdata/dummy.file\",\"rules\":[{\"identifier\":\"foo\"}]}\n");
}
#[test]
fn define() {
Command::new(cargo_bin!("yr"))
.arg("scan")
.arg("--define=float=3.14")
.arg("--define=int=1")
.arg("--define=bool=true")
.arg("src/tests/testdata/variables.yar")
.arg("src/tests/testdata/dummy.file")
.assert()
.success()
.stdout("test src/tests/testdata/dummy.file\n");
}
#[test]
fn console() {
Command::new(cargo_bin!("yr"))
.arg("scan")
.arg("src/tests/testdata/console.yar")
.arg("src/tests/testdata/dummy.file")
.assert()
.success()
.stderr("src/tests/testdata/dummy.file: hello\n");
Command::new(cargo_bin!("yr"))
.arg("scan")
.arg("--disable-console-logs")
.arg("src/tests/testdata/console.yar")
.arg("src/tests/testdata/dummy.file")
.assert()
.success()
.stderr("");
}
#[test]
fn ignore_module() {
Command::new(cargo_bin!("yr"))
.arg("scan")
.arg("--ignore-module=unknown")
.arg("src/tests/testdata/unknown_module.yar")
.arg("src/tests/testdata/dummy.file")
.assert()
.success()
.stderr(
r#"warning[unsupported_module]: module `unknown` is not supported
--> src/tests/testdata/unknown_module.yar:1:1
|
1 | import "unknown"
| ---------------- module `unknown` used here
warning[unsupported_module]: module `unknown` is not supported
--> src/tests/testdata/unknown_module.yar:5:6
|
5 | unknown.foo()
| ------- module `unknown` used here
|
= note: the whole rule `test` will be ignored
"#,
);
}
#[test]
fn recursive() {
Command::new(cargo_bin!("yr"))
.arg("scan")
.arg("--recursive")
.arg("src/tests/testdata/foo.yar")
.arg("src/tests/testdata/dummy.file")
.assert()
.failure()
.code(1)
.stderr(
"error: can\'t use \'--recursive\' when <TARGET_PATH> is a file\n",
);
}
#[test]
fn compiled_rules() {
Command::new(cargo_bin!("yr"))
.arg("scan")
.arg("--compiled-rules")
.arg("src/tests/testdata/foo.yar")
.arg("src/tests/testdata/foo.yar")
.arg("src/tests/testdata/dummy.file")
.assert()
.failure()
.code(1)
.stderr("error: can\'t use \'--compiled-rules\' with more than one RULES_PATH\n");
Command::new(cargo_bin!("yr"))
.arg("scan")
.arg("--compiled-rules")
.arg("namespace:src/tests/testdata/foo.yar")
.arg("src/tests/testdata/dummy.file")
.assert()
.failure()
.code(1)
.stderr("error: can\'t use namespace with \'--compiled-rules\'\n");
let temp_dir = TempDir::new().unwrap();
let input_file = temp_dir.child("rule.yar");
input_file.write_str("rule test { condition: true }").unwrap();
Command::new(cargo_bin!("yr"))
.arg("compile")
.arg("-o")
.arg(input_file.with_extension("yarc"))
.arg(input_file.path())
.assert()
.success();
Command::new(cargo_bin!("yr"))
.arg("scan")
.arg("--compiled-rules")
.arg(input_file.with_extension("yarc"))
.arg("src/tests/testdata/dummy.file")
.assert()
.success();
}
#[test]
fn issue_280() {
Command::new(cargo_bin!("yr"))
.arg("scan")
.arg("src/tests/testdata/foo.yar")
.arg("./src/tests/testdata/")
.assert()
.success();
Command::new(cargo_bin!("yr"))
.arg("scan")
.arg("src/tests/testdata/foo.yar")
.arg(".")
.assert()
.success();
Command::new(cargo_bin!("yr"))
.arg("scan")
.arg("src/tests/testdata/foo.yar")
.arg("./")
.assert()
.success();
#[cfg(target_os = "windows")]
Command::new(cargo_bin!("yr"))
.arg("scan")
.arg("src/tests/testdata/foo.yar")
.arg(r#".\"#)
.assert()
.success();
}
#[test]
fn json_output_duplicate_meta_keys() {
let output = Command::new(cargo_bin!("yr"))
.arg("scan")
.arg("--output-format=json")
.arg("--print-meta")
.arg("src/tests/testdata/duplicate_meta.yar")
.arg("src/tests/testdata/dummy.file")
.assert()
.success()
.get_output()
.stdout
.clone();
let json: serde_json::Value =
serde_json::from_slice(&output).expect("valid JSON output");
let meta = &json["matches"][0]["meta"];
assert_eq!(meta["author"], "Test Author");
assert_eq!(meta["description"], "Rule with duplicate metadata keys");
let hash = &meta["hash"];
assert!(hash.is_array(), "hash should be an array");
let hash_array = hash.as_array().unwrap();
assert_eq!(hash_array.len(), 3);
assert!(hash_array.contains(&serde_json::json!("aaa111")));
assert!(hash_array.contains(&serde_json::json!("bbb222")));
assert!(hash_array.contains(&serde_json::json!("ccc333")));
}
#[test]
fn json_output_single_meta_not_array() {
let output = Command::new(cargo_bin!("yr"))
.arg("scan")
.arg("--output-format=json")
.arg("--print-meta")
.arg("src/tests/testdata/foo.yar")
.arg("src/tests/testdata/dummy.file")
.assert()
.success()
.get_output()
.stdout
.clone();
let json: serde_json::Value =
serde_json::from_slice(&output).expect("valid JSON output");
let meta = &json["matches"][0]["meta"];
assert!(meta["string"].is_string());
assert!(meta["bool"].is_boolean());
assert!(meta["int"].is_i64());
assert!(meta["float"].is_f64());
}
#[test]
fn fast_scan() {
Command::new(cargo_bin!("yr"))
.arg("scan")
.arg("--fast-scan")
.arg("src/tests/testdata/foo.yar")
.arg("src/tests/testdata/dummy.file")
.assert()
.success()
.stdout(predicate::str::contains("foo src/tests/testdata/dummy.file"));
Command::new(cargo_bin!("yr"))
.arg("scan")
.arg("-f")
.arg("src/tests/testdata/foo.yar")
.arg("src/tests/testdata/dummy.file")
.assert()
.success()
.stdout(predicate::str::contains("foo src/tests/testdata/dummy.file"));
}
#[test]
fn cpu_limit() {
Command::new(cargo_bin!("yr"))
.arg("scan")
.arg("--cpu-limit=50")
.arg("src/tests/testdata/foo.yar")
.arg("src/tests/testdata/dummy.file")
.assert()
.success();
}
#[test]
fn ignore_invalid_rules() {
let temp_dir = TempDir::new().unwrap();
let yar_file = temp_dir.child("test.yar");
yar_file
.write_str(
r#"
rule valid_rule {
condition: true
}
rule invalid_rule {
condition: undefined_var == 1
}
"#,
)
.unwrap();
Command::new(cargo_bin!("yr"))
.arg("scan")
.arg(yar_file.path())
.arg("src/tests/testdata/dummy.file")
.assert()
.failure()
.stderr(predicate::str::contains("1 error(s) found"));
Command::new(cargo_bin!("yr"))
.arg("scan")
.arg("--ignore-invalid-rules")
.arg(yar_file.path())
.arg("src/tests/testdata/dummy.file")
.assert()
.success()
.stdout(predicate::str::contains("valid_rule"));
}
#[test]
fn format_ndjson_omits_non_matching_files() {
let temp_dir = TempDir::new().unwrap();
let matching_file = temp_dir.child("matching.file");
let non_matching_file = temp_dir.child("non_matching.file");
matching_file.write_str("foo").unwrap();
non_matching_file.write_str("bar").unwrap();
let expected_path = matching_file.path().to_str().unwrap();
Command::new(cargo_bin!("yr"))
.arg("scan")
.arg("--output-format=ndjson")
.arg("src/tests/testdata/foo.yar")
.arg(temp_dir.path())
.assert()
.success()
.stdout(format!(
"{{\"path\":{},\"rules\":[{{\"identifier\":\"foo\"}}]}}\n",
serde_json::to_string(expected_path).unwrap()
));
Command::new(cargo_bin!("yr"))
.arg("scan")
.arg("--output-format=ndjson")
.arg("--tag=non_existent_tag")
.arg("src/tests/testdata/foo.yar")
.arg(temp_dir.path())
.assert()
.success()
.stdout("");
Command::new(cargo_bin!("yr"))
.arg("scan")
.arg("--output-format=ndjson")
.arg("--count")
.arg("src/tests/testdata/foo.yar")
.arg(temp_dir.path())
.assert()
.success()
.stdout(format!(
"{{\"path\":{},\"count\":1}}\n",
serde_json::to_string(expected_path).unwrap()
));
Command::new(cargo_bin!("yr"))
.arg("scan")
.arg("--output-format=ndjson")
.arg("--count")
.arg("--tag=non_existent_tag")
.arg("src/tests/testdata/foo.yar")
.arg(temp_dir.path())
.assert()
.success()
.stdout("");
}
#[test]
fn count_omits_non_matching_files() {
let temp_dir = TempDir::new().unwrap();
let matching_file = temp_dir.child("matching.file");
let non_matching_file = temp_dir.child("non_matching.file");
matching_file.write_str("foo").unwrap();
non_matching_file.write_str("bar").unwrap();
Command::new(cargo_bin!("yr"))
.arg("scan")
.arg("--count")
.arg("src/tests/testdata/foo.yar")
.arg(temp_dir.path())
.assert()
.success()
.stdout(format!("{}: 1\n", matching_file.path().display()));
Command::new(cargo_bin!("yr"))
.arg("scan")
.arg("--count")
.arg("--tag=non_existent_tag")
.arg("src/tests/testdata/foo.yar")
.arg(temp_dir.path())
.assert()
.success()
.stdout("");
}
#[cfg(unix)]
#[test]
fn issue_785() {
use std::fs::File;
use std::io::Read;
use std::os::fd::FromRawFd;
use std::process::Stdio;
use std::ptr;
#[repr(C)]
struct Winsize {
ws_row: u16,
ws_col: u16,
ws_xpixel: u16,
ws_ypixel: u16,
}
unsafe extern "C" {
fn openpty(
amaster: *mut std::ffi::c_int,
aslave: *mut std::ffi::c_int,
name: *mut std::ffi::c_char,
termp: *const std::ffi::c_void,
winp: *const Winsize,
) -> std::ffi::c_int;
}
let mut master_fd = -1;
let mut slave_fd = -1;
let ws = Winsize { ws_row: 24, ws_col: 80, ws_xpixel: 0, ws_ypixel: 0 };
let ret = unsafe {
openpty(
&mut master_fd,
&mut slave_fd,
ptr::null_mut(),
ptr::null(),
&ws,
)
};
assert_eq!(ret, 0);
let mut master = unsafe { File::from_raw_fd(master_fd) };
let slave = unsafe { File::from_raw_fd(slave_fd) };
let mut cmd = std::process::Command::new(cargo_bin!("yr"));
cmd.arg("scan")
.arg("--print-strings")
.arg("src/tests/testdata/foo.yar")
.arg("src/tests/testdata/dummy.file")
.env("TERM", "xterm-256color")
.stdin(Stdio::from(slave.try_clone().unwrap()))
.stdout(Stdio::from(slave.try_clone().unwrap()))
.stderr(Stdio::from(slave));
let mut child = cmd.spawn().unwrap();
drop(cmd);
let mut output = Vec::new();
let mut buf = [0u8; 1024];
loop {
match master.read(&mut buf) {
Ok(0) | Err(_) => break,
Ok(n) => output.extend_from_slice(&buf[..n]),
}
}
assert!(child.wait().unwrap().success());
let output = String::from_utf8(output).unwrap();
assert!(
output.contains("0x0:3:$foo: foo\r\n0x0:3:$foo_hex: 66 6f 6f\r\n"),
"unexpected terminal output: {output:?}"
);
}