use std::collections::HashMap;
use std::ffi::CString;
use std::ffi::{CStr, c_char, c_void};
use std::mem;
use std::time::Duration;
#[cfg(feature = "rules-profiling")]
use yara_x::ProfilingData;
use yara_x::ScanOptions;
use yara_x::errors::ScanError;
use crate::{
_yrx_set_last_error, YRX_RESULT, YRX_RULE, YRX_RULE_CALLBACK, YRX_RULES,
};
enum InnerScanner<'r> {
None,
SingleBlock(yara_x::Scanner<'r>),
MultiBlock(yara_x::blocks::Scanner<'r>),
}
impl<'r> InnerScanner<'r> {
fn set_timeout(&mut self, duration: Duration) -> &mut Self {
match self {
InnerScanner::SingleBlock(s) => {
s.set_timeout(duration);
}
InnerScanner::MultiBlock(s) => {
s.set_timeout(duration);
}
InnerScanner::None => unreachable!(),
}
self
}
fn fast_scan(&mut self, yes: bool) -> &mut Self {
match self {
InnerScanner::SingleBlock(s) => {
s.fast_scan(yes);
}
InnerScanner::MultiBlock(s) => {
s.fast_scan(yes);
}
InnerScanner::None => unreachable!(),
}
self
}
fn make_multi_block(&mut self) -> &mut yara_x::blocks::Scanner<'r> {
if let Self::MultiBlock(s) = self {
return s;
}
if let Self::SingleBlock(s) = mem::replace(self, InnerScanner::None) {
*self = InnerScanner::MultiBlock(s.into());
}
match self {
InnerScanner::MultiBlock(s) => s,
_ => unreachable!(),
}
}
fn set_global<T>(
&mut self,
ident: &str,
value: T,
) -> Result<&mut Self, yara_x::errors::VariableError>
where
T: TryInto<yara_x::Variable, Error = yara_x::errors::VariableError>,
{
match self {
InnerScanner::SingleBlock(s) => {
s.set_global(ident, value)?;
}
InnerScanner::MultiBlock(s) => {
s.set_global(ident, value)?;
}
InnerScanner::None => unreachable!(),
}
Ok(self)
}
#[cfg(feature = "rules-profiling")]
fn slowest_rules(&self, n: usize) -> Vec<ProfilingData<'_>> {
match self {
InnerScanner::SingleBlock(s) => s.slowest_rules(n),
InnerScanner::MultiBlock(s) => s.slowest_rules(n),
InnerScanner::None => unreachable!(),
}
}
#[cfg(feature = "rules-profiling")]
fn clear_profiling_data(&mut self) {
match self {
InnerScanner::SingleBlock(s) => s.clear_profiling_data(),
InnerScanner::MultiBlock(s) => s.clear_profiling_data(),
InnerScanner::None => unreachable!(),
}
}
}
pub struct YRX_SCANNER<'r, 'm> {
inner: InnerScanner<'r>,
on_matching_rule: Option<(YRX_RULE_CALLBACK, *mut c_void)>,
module_data: HashMap<&'m str, &'m [u8]>,
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn yrx_scanner_create(
rules: *const YRX_RULES,
scanner: &mut *mut YRX_SCANNER,
) -> YRX_RESULT {
let rules = if let Some(rules) = rules.as_ref() {
rules
} else {
return YRX_RESULT::YRX_INVALID_ARGUMENT;
};
*scanner = Box::into_raw(Box::new(YRX_SCANNER {
inner: InnerScanner::SingleBlock(yara_x::Scanner::new(rules.inner())),
on_matching_rule: None,
module_data: HashMap::new(),
}));
YRX_RESULT::YRX_SUCCESS
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn yrx_scanner_destroy(scanner: *mut YRX_SCANNER) {
drop(Box::from_raw(scanner))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn yrx_scanner_set_timeout(
scanner: *mut YRX_SCANNER,
timeout: u64,
) -> YRX_RESULT {
let scanner = match scanner.as_mut() {
Some(s) => s,
None => return YRX_RESULT::YRX_INVALID_ARGUMENT,
};
scanner.inner.set_timeout(Duration::from_secs(timeout));
YRX_RESULT::YRX_SUCCESS
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn yrx_scanner_fast_scan(
scanner: *mut YRX_SCANNER,
yes: bool,
) -> YRX_RESULT {
let scanner = match scanner.as_mut() {
Some(s) => s,
None => return YRX_RESULT::YRX_INVALID_ARGUMENT,
};
scanner.inner.fast_scan(yes);
YRX_RESULT::YRX_SUCCESS
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn yrx_scanner_scan(
scanner: *mut YRX_SCANNER,
data: *const u8,
len: usize,
) -> YRX_RESULT {
_yrx_set_last_error::<ScanError>(None);
let scanner = match scanner.as_mut() {
Some(s) => s,
None => return YRX_RESULT::YRX_INVALID_ARGUMENT,
};
let data = match slice_from_ptr_and_len(data, len) {
Some(data) => data,
None => return YRX_RESULT::YRX_INVALID_ARGUMENT,
};
let options = scanner
.module_data
.drain()
.fold(ScanOptions::new(), |acc, (module_name, meta)| {
acc.set_module_metadata(module_name, meta)
});
let scan_results = match &mut scanner.inner {
InnerScanner::SingleBlock(s) => s.scan_with_options(data, options),
InnerScanner::MultiBlock(_) => return YRX_RESULT::YRX_INVALID_STATE,
InnerScanner::None => unreachable!(),
};
match scan_results {
Ok(results) => {
if let Some((callback, user_data)) = scanner.on_matching_rule {
for r in results.matching_rules() {
callback(&YRX_RULE::new(r), user_data);
}
}
YRX_RESULT::YRX_SUCCESS
}
Err(ScanError::Timeout) => {
_yrx_set_last_error(Some(ScanError::Timeout));
YRX_RESULT::YRX_SCAN_TIMEOUT
}
Err(err) => {
_yrx_set_last_error(Some(err));
YRX_RESULT::YRX_SCAN_ERROR
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn yrx_scanner_scan_file(
scanner: *mut YRX_SCANNER,
path: *const c_char,
) -> YRX_RESULT {
_yrx_set_last_error::<ScanError>(None);
let scanner = match scanner.as_mut() {
Some(s) => s,
None => return YRX_RESULT::YRX_INVALID_ARGUMENT,
};
let path = match str_from_ptr(path) {
Ok(path) => path,
Err(err) => return err,
};
let options = scanner
.module_data
.drain()
.fold(ScanOptions::new(), |acc, (module_name, meta)| {
acc.set_module_metadata(module_name, meta)
});
let scan_results = match &mut scanner.inner {
InnerScanner::SingleBlock(s) => {
s.scan_file_with_options(path, options)
}
InnerScanner::MultiBlock(_) => return YRX_RESULT::YRX_INVALID_STATE,
InnerScanner::None => unreachable!(),
};
match scan_results {
Ok(results) => {
if let Some((callback, user_data)) = scanner.on_matching_rule {
for r in results.matching_rules() {
callback(&YRX_RULE::new(r), user_data);
}
}
YRX_RESULT::YRX_SUCCESS
}
Err(ScanError::Timeout) => {
_yrx_set_last_error(Some(ScanError::Timeout));
YRX_RESULT::YRX_SCAN_TIMEOUT
}
Err(err) => {
_yrx_set_last_error(Some(err));
YRX_RESULT::YRX_SCAN_ERROR
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn yrx_scanner_scan_block(
scanner: *mut YRX_SCANNER,
base: usize,
data: *const u8,
len: usize,
) -> YRX_RESULT {
_yrx_set_last_error::<ScanError>(None);
let scanner = match scanner.as_mut() {
Some(s) => s,
None => return YRX_RESULT::YRX_INVALID_ARGUMENT,
};
let data = match slice_from_ptr_and_len(data, len) {
Some(data) => data,
None => return YRX_RESULT::YRX_INVALID_ARGUMENT,
};
match scanner.inner.make_multi_block().scan(base, data) {
Ok(_) => YRX_RESULT::YRX_SUCCESS,
Err(ScanError::Timeout) => {
_yrx_set_last_error(Some(ScanError::Timeout));
YRX_RESULT::YRX_SCAN_TIMEOUT
}
Err(err) => {
_yrx_set_last_error(Some(err));
YRX_RESULT::YRX_SCAN_ERROR
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn yrx_scanner_finish(
scanner: *mut YRX_SCANNER,
) -> YRX_RESULT {
_yrx_set_last_error::<ScanError>(None);
let scanner = match scanner.as_mut() {
Some(s) => s,
None => return YRX_RESULT::YRX_INVALID_ARGUMENT,
};
match scanner.inner.make_multi_block().finish() {
Ok(results) => {
if let Some((callback, user_data)) = scanner.on_matching_rule {
for r in results.matching_rules() {
callback(&YRX_RULE::new(r), user_data);
}
}
YRX_RESULT::YRX_SUCCESS
}
Err(ScanError::Timeout) => {
_yrx_set_last_error(Some(ScanError::Timeout));
YRX_RESULT::YRX_SCAN_TIMEOUT
}
Err(err) => {
_yrx_set_last_error(Some(err));
YRX_RESULT::YRX_SCAN_ERROR
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn yrx_scanner_on_matching_rule(
scanner: *mut YRX_SCANNER,
callback: YRX_RULE_CALLBACK,
user_data: *mut std::ffi::c_void,
) -> YRX_RESULT {
if let Some(scanner) = scanner.as_mut() {
scanner.on_matching_rule = Some((callback, user_data));
YRX_RESULT::YRX_SUCCESS
} else {
YRX_RESULT::YRX_INVALID_ARGUMENT
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn yrx_scanner_set_module_output(
scanner: *mut YRX_SCANNER,
name: *const c_char,
data: *const u8,
len: usize,
) -> YRX_RESULT {
let scanner = match scanner.as_mut() {
Some(s) => s,
None => return YRX_RESULT::YRX_INVALID_ARGUMENT,
};
let module_name = match str_from_ptr(name) {
Ok(module_name) => module_name,
Err(err) => return err,
};
let data = match slice_from_ptr_and_len(data, len) {
Some(data) => data,
None => return YRX_RESULT::YRX_INVALID_ARGUMENT,
};
match &mut scanner.inner {
InnerScanner::SingleBlock(scanner) => {
match scanner.set_module_output_raw(module_name, data) {
Ok(_) => {
_yrx_set_last_error::<ScanError>(None);
YRX_RESULT::YRX_SUCCESS
}
Err(err) => {
_yrx_set_last_error(Some(err));
YRX_RESULT::YRX_SCAN_ERROR
}
}
}
InnerScanner::MultiBlock(_) => YRX_RESULT::YRX_INVALID_STATE,
InnerScanner::None => unreachable!(),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn yrx_scanner_set_module_data(
scanner: *mut YRX_SCANNER,
name: *const c_char,
data: *const u8,
len: usize,
) -> YRX_RESULT {
let scanner = match scanner.as_mut() {
Some(s) => s,
None => return YRX_RESULT::YRX_INVALID_ARGUMENT,
};
let name = match str_from_ptr(name) {
Ok(name) => name,
Err(err) => return err,
};
let data = match slice_from_ptr_and_len(data, len) {
Some(data) => data,
None => return YRX_RESULT::YRX_INVALID_ARGUMENT,
};
if matches!(scanner.inner, InnerScanner::MultiBlock(_)) {
return YRX_RESULT::YRX_INVALID_STATE;
}
scanner.module_data.insert(name, data);
YRX_RESULT::YRX_SUCCESS
}
unsafe extern "C" fn yrx_scanner_set_global<
T: TryInto<yara_x::Variable, Error = yara_x::errors::VariableError>,
>(
scanner: *mut YRX_SCANNER,
ident: *const c_char,
value: T,
) -> YRX_RESULT {
let scanner = match scanner.as_mut() {
Some(s) => s,
None => return YRX_RESULT::YRX_INVALID_ARGUMENT,
};
let ident = match str_from_ptr(ident) {
Ok(ident) => ident,
Err(err) => return err,
};
match scanner.inner.set_global(ident, value) {
Ok(_) => {
_yrx_set_last_error::<ScanError>(None);
YRX_RESULT::YRX_SUCCESS
}
Err(err) => {
_yrx_set_last_error(Some(err));
YRX_RESULT::YRX_VARIABLE_ERROR
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn yrx_scanner_set_global_str(
scanner: *mut YRX_SCANNER,
ident: *const c_char,
value: *const c_char,
) -> YRX_RESULT {
match str_from_ptr(value) {
Ok(value) => yrx_scanner_set_global(scanner, ident, value),
Err(err) => err,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn yrx_scanner_set_global_bool(
scanner: *mut YRX_SCANNER,
ident: *const c_char,
value: bool,
) -> YRX_RESULT {
yrx_scanner_set_global(scanner, ident, value)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn yrx_scanner_set_global_int(
scanner: *mut YRX_SCANNER,
ident: *const c_char,
value: i64,
) -> YRX_RESULT {
yrx_scanner_set_global(scanner, ident, value)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn yrx_scanner_set_global_float(
scanner: *mut YRX_SCANNER,
ident: *const c_char,
value: f64,
) -> YRX_RESULT {
yrx_scanner_set_global(scanner, ident, value)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn yrx_scanner_set_global_json(
scanner: *mut YRX_SCANNER,
ident: *const c_char,
value: *const c_char,
) -> YRX_RESULT {
let value = match str_from_ptr(value) {
Ok(value) => value,
Err(err) => return err,
};
let value: serde_json::Value = match serde_json::from_str(value) {
Ok(json_value) => json_value,
Err(_) => return YRX_RESULT::YRX_INVALID_ARGUMENT,
};
yrx_scanner_set_global(scanner, ident, value)
}
pub type YRX_CONSOLE_CALLBACK = extern "C" fn(message: *const c_char) -> ();
#[unsafe(no_mangle)]
pub unsafe extern "C" fn yrx_scanner_on_console_log(
scanner: *mut YRX_SCANNER,
callback: YRX_CONSOLE_CALLBACK,
) -> YRX_RESULT {
let scanner = match scanner.as_mut() {
Some(s) => s,
None => return YRX_RESULT::YRX_INVALID_ARGUMENT,
};
let wrapper = move |message: String| {
let msg = CString::new(message).unwrap();
callback(msg.as_ptr());
};
match &mut scanner.inner {
InnerScanner::SingleBlock(s) => {
s.console_log(wrapper);
}
InnerScanner::MultiBlock(s) => {
s.console_log(wrapper);
}
InnerScanner::None => unreachable!(),
}
YRX_RESULT::YRX_SUCCESS
}
pub type YRX_SLOWEST_RULES_CALLBACK = extern "C" fn(
namespace_: *const c_char,
rule: *const c_char,
pattern_matching_time: f64,
condition_exec_time: f64,
user_data: *mut c_void,
) -> ();
#[unsafe(no_mangle)]
#[allow(unused_variables)]
pub unsafe extern "C" fn yrx_scanner_iter_slowest_rules(
scanner: *mut YRX_SCANNER,
n: usize,
callback: YRX_SLOWEST_RULES_CALLBACK,
user_data: *mut c_void,
) -> YRX_RESULT {
#[cfg(not(feature = "rules-profiling"))]
return YRX_RESULT::YRX_NOT_SUPPORTED;
#[cfg(feature = "rules-profiling")]
{
let scanner = match scanner.as_ref() {
Some(s) => s,
None => return YRX_RESULT::YRX_INVALID_ARGUMENT,
};
for profiling_info in scanner.inner.slowest_rules(n) {
let namespace = CString::new(profiling_info.namespace).unwrap();
let rule = CString::new(profiling_info.rule).unwrap();
callback(
namespace.as_ptr(),
rule.as_ptr(),
profiling_info.pattern_matching_time.as_secs_f64(),
profiling_info.condition_exec_time.as_secs_f64(),
user_data,
);
}
YRX_RESULT::YRX_SUCCESS
}
}
#[unsafe(no_mangle)]
#[allow(unused_variables)]
pub unsafe extern "C" fn yrx_scanner_clear_profiling_data(
scanner: *mut YRX_SCANNER,
) -> YRX_RESULT {
#[cfg(not(feature = "rules-profiling"))]
return YRX_RESULT::YRX_NOT_SUPPORTED;
#[cfg(feature = "rules-profiling")]
{
match scanner.as_mut() {
Some(s) => s.inner.clear_profiling_data(),
None => return YRX_RESULT::YRX_INVALID_ARGUMENT,
};
YRX_RESULT::YRX_SUCCESS
}
}
unsafe fn slice_from_ptr_and_len<'a>(
data: *const u8,
len: usize,
) -> Option<&'a [u8]> {
if data.is_null() && len > 0 {
return None;
}
let data = if data.is_null() || len == 0 {
&[]
} else {
std::slice::from_raw_parts(data, len)
};
Some(data)
}
unsafe fn str_from_ptr<'a>(s: *const c_char) -> Result<&'a str, YRX_RESULT> {
match CStr::from_ptr(s).to_str() {
Ok(s) => Ok(s),
Err(err) => {
_yrx_set_last_error(Some(err));
Err(YRX_RESULT::YRX_INVALID_UTF8)
}
}
}