更新libclamav库1.0.0版本
This commit is contained in:
1477
clamav/libclamav_rust/src/cdiff.rs
Normal file
1477
clamav/libclamav_rust/src/cdiff.rs
Normal file
File diff suppressed because it is too large
Load Diff
275
clamav/libclamav_rust/src/evidence.rs
Normal file
275
clamav/libclamav_rust/src/evidence.rs
Normal file
@@ -0,0 +1,275 @@
|
||||
/*
|
||||
* Functions and structures for recording, reporting evidence towards a scan verdict.
|
||||
*
|
||||
* Copyright (C) 2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Authors: Micah Snyder
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
use std::{collections::HashMap, ffi::CStr, mem::ManuallyDrop, os::raw::c_char};
|
||||
|
||||
use log::{debug, error, warn};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{ffi_util::FFIError, rrf_call, sys, validate_str_param};
|
||||
|
||||
/// CdiffError enumerates all possible errors returned by this library.
|
||||
#[derive(Error, Debug)]
|
||||
pub enum EvidenceError {
|
||||
#[error("Invalid format")]
|
||||
Format,
|
||||
|
||||
#[error("Invalid parameter: {0}")]
|
||||
InvalidParameter(String),
|
||||
|
||||
#[error("{0} parmeter is NULL")]
|
||||
NullParam(&'static str),
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub enum IndicatorType {
|
||||
/// For hash-based indicators.
|
||||
Strong,
|
||||
/// For potentially unwanted applications/programs that are not malicious but may be used maliciously.
|
||||
PotentiallyUnwanted,
|
||||
|
||||
#[cfg(feature = "not_ready")]
|
||||
/// Weak indicators that together with other indicators can be used to form a stronger indicator.
|
||||
/// This type of indicator should NEVER alert the user on its own.
|
||||
Weak,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Evidence {
|
||||
strong: HashMap<String, Vec<IndicatorMeta>>,
|
||||
pua: HashMap<String, Vec<IndicatorMeta>>,
|
||||
#[cfg(feature = "not_ready")]
|
||||
weak: HashMap<String, Vec<IndicatorMeta>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct IndicatorMeta {
|
||||
/// The original string pointer for the "virname", to pass back.
|
||||
static_virname: *const c_char,
|
||||
}
|
||||
|
||||
/// Initialize a match vector
|
||||
#[no_mangle]
|
||||
pub extern "C" fn evidence_new() -> sys::evidence_t {
|
||||
Box::into_raw(Box::new(Evidence::default())) as sys::evidence_t
|
||||
}
|
||||
|
||||
/// Free the evidence
|
||||
#[no_mangle]
|
||||
pub extern "C" fn evidence_free(evidence: sys::evidence_t) {
|
||||
if evidence.is_null() {
|
||||
warn!("Attempted to free a NULL evidence pointer. Please report this at: https://github.com/Cisco-Talos/clamav/issues");
|
||||
} else {
|
||||
let _ = unsafe { Box::from_raw(evidence as *mut Evidence) };
|
||||
}
|
||||
}
|
||||
|
||||
/// C interface for Evidence::render_verdict().
|
||||
/// Handles all the unsafe ffi stuff.
|
||||
///
|
||||
/// Render a verdict based on the evidence, depending on the severity of the
|
||||
/// indicators found and the scan configuration.
|
||||
///
|
||||
/// The individual alerting-indicators would have already been printed at this point.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// No parameters may be NULL
|
||||
#[export_name = "evidence_render_verdict"]
|
||||
pub unsafe extern "C" fn _evidence_render_verdict(evidence: sys::evidence_t) -> bool {
|
||||
let evidence = ManuallyDrop::new(Box::from_raw(evidence as *mut Evidence));
|
||||
|
||||
evidence.render_verdict()
|
||||
}
|
||||
|
||||
/// C interface to get a string name for one of the alerts.
|
||||
/// Will first check for one from the strong indicators, then pua.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Returns a string that is either static, or allocated when reading the database.
|
||||
/// So the lifetime of the string is good at least until you reload or unload the databases.
|
||||
///
|
||||
/// No parameters may be NULL
|
||||
#[export_name = "evidence_get_last_alert"]
|
||||
pub unsafe extern "C" fn _evidence_get_last_alert(evidence: sys::evidence_t) -> *const c_char {
|
||||
let evidence = ManuallyDrop::new(Box::from_raw(evidence as *mut Evidence));
|
||||
|
||||
if let Some(meta) = evidence.strong.values().last() {
|
||||
meta.last().unwrap().static_virname as *const c_char
|
||||
} else if let Some(meta) = evidence.pua.values().last() {
|
||||
meta.last().unwrap().static_virname as *const c_char
|
||||
} else {
|
||||
// no alerts, return NULL
|
||||
std::ptr::null()
|
||||
}
|
||||
}
|
||||
|
||||
/// C interface to get a string name for one of the alerts.
|
||||
/// Will first check for one from the strong indicators, then pua.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Returns a string that is either static, or allocated when reading the database.
|
||||
/// So the lifetime of the string is good at least until you reload or unload the databases.
|
||||
///
|
||||
/// No parameters may be NULL
|
||||
#[export_name = "evidence_get_indicator"]
|
||||
pub unsafe extern "C" fn _evidence_get_indicator(
|
||||
evidence: sys::evidence_t,
|
||||
indicator_type: IndicatorType,
|
||||
index: usize,
|
||||
) -> *const c_char {
|
||||
let evidence = ManuallyDrop::new(Box::from_raw(evidence as *mut Evidence));
|
||||
|
||||
match indicator_type {
|
||||
IndicatorType::Strong => {
|
||||
if let Some(meta) = evidence.strong.values().nth(index) {
|
||||
return meta.last().unwrap().static_virname as *const c_char;
|
||||
} else {
|
||||
// no alert at that index. return NULL
|
||||
return std::ptr::null();
|
||||
}
|
||||
}
|
||||
IndicatorType::PotentiallyUnwanted => {
|
||||
if let Some(meta) = evidence.pua.values().nth(index) {
|
||||
return meta.last().unwrap().static_virname as *const c_char;
|
||||
} else {
|
||||
// no alert at that index. return NULL
|
||||
return std::ptr::null();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// C interface to check number of alerting indicators in evidence.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// No parameters may be NULL
|
||||
#[export_name = "evidence_num_alerts"]
|
||||
pub unsafe extern "C" fn _evidence_num_alerts(evidence: sys::evidence_t) -> usize {
|
||||
let evidence = ManuallyDrop::new(Box::from_raw(evidence as *mut Evidence));
|
||||
|
||||
evidence.strong.len() + evidence.pua.len()
|
||||
}
|
||||
|
||||
/// C interface to check number of indicators in evidence.
|
||||
/// Handles all the unsafe ffi stuff.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// No parameters may be NULL
|
||||
#[export_name = "evidence_num_indicators_type"]
|
||||
pub unsafe extern "C" fn _evidence_num_indicators_type(
|
||||
evidence: sys::evidence_t,
|
||||
indicator_type: IndicatorType,
|
||||
) -> usize {
|
||||
let evidence = ManuallyDrop::new(Box::from_raw(evidence as *mut Evidence));
|
||||
|
||||
match indicator_type {
|
||||
IndicatorType::Strong => evidence.strong.len(),
|
||||
IndicatorType::PotentiallyUnwanted => evidence.pua.len(),
|
||||
#[cfg(feature = "not_ready")]
|
||||
// TODO: Implement a way to record, report number of indicators in the tree (you know, after making this a tree).
|
||||
IndicatorType::Weak => evidence.weak.len(),
|
||||
}
|
||||
}
|
||||
|
||||
/// C interface for Evidence::add_indicator().
|
||||
/// Handles all the unsafe ffi stuff.
|
||||
///
|
||||
/// Add an indicator to the evidence.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `hexsig` and `err` must not be NULL
|
||||
#[export_name = "evidence_add_indicator"]
|
||||
pub unsafe extern "C" fn _evidence_add_indicator(
|
||||
evidence: sys::evidence_t,
|
||||
name: *const c_char,
|
||||
indicator_type: IndicatorType,
|
||||
err: *mut *mut FFIError,
|
||||
) -> bool {
|
||||
let name_str = validate_str_param!(name);
|
||||
|
||||
let mut evidence = ManuallyDrop::new(Box::from_raw(evidence as *mut Evidence));
|
||||
|
||||
rrf_call!(
|
||||
err = err,
|
||||
evidence.add_indicator(name_str, name, indicator_type)
|
||||
)
|
||||
}
|
||||
|
||||
impl Evidence {
|
||||
/// Check if we have any indicators that should alert the user.
|
||||
pub fn render_verdict(&self) -> bool {
|
||||
debug!("Checking verdict...");
|
||||
|
||||
let num_alerting_indicators = self.strong.len() + self.pua.len();
|
||||
|
||||
if num_alerting_indicators > 0 {
|
||||
debug!("Found {} alerting indicators", num_alerting_indicators);
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Add an indicator to the evidence.
|
||||
pub fn add_indicator(
|
||||
&mut self,
|
||||
name: &str,
|
||||
static_virname: *const c_char,
|
||||
indicator_type: IndicatorType,
|
||||
) -> Result<(), EvidenceError> {
|
||||
let meta: IndicatorMeta = IndicatorMeta { static_virname };
|
||||
|
||||
match indicator_type {
|
||||
IndicatorType::Strong => {
|
||||
self.strong
|
||||
.entry(name.to_string())
|
||||
.or_insert_with(Vec::new)
|
||||
.push(meta);
|
||||
}
|
||||
|
||||
IndicatorType::PotentiallyUnwanted => {
|
||||
self.pua
|
||||
.entry(name.to_string())
|
||||
.or_insert_with(Vec::new)
|
||||
.push(meta);
|
||||
}
|
||||
|
||||
#[cfg(feature = "not_ready")]
|
||||
// TODO: Implement a tree structure for recording weak indicators, to
|
||||
// match the archive/extraction level at which each was found.
|
||||
// This will be required for alerting signatures to depend on weak-indicators for embedded content.
|
||||
IndicatorType::Weak => {
|
||||
self.weak
|
||||
.entry(name.to_string())
|
||||
.or_insert_with(Vec::new)
|
||||
.push(meta);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
308
clamav/libclamav_rust/src/ffi_util.rs
Normal file
308
clamav/libclamav_rust/src/ffi_util.rs
Normal file
@@ -0,0 +1,308 @@
|
||||
/*
|
||||
* Various functions to ease working through FFI
|
||||
*
|
||||
* Copyright (C) 2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Authors: Scott Hutton
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
use std::{
|
||||
ffi::{CStr, CString},
|
||||
mem::ManuallyDrop,
|
||||
os::raw::c_char,
|
||||
};
|
||||
|
||||
/// Wraps a call to a "result-returning function", allowing it to specify an
|
||||
/// error receiver and (optionally) result receiver.
|
||||
///
|
||||
/// If the `out` parameter is omitted, the function's return value will be
|
||||
/// expected to be a pointer on success, or NULL if the function call returns an error.
|
||||
///
|
||||
/// If the `out` parameter is included, the function will return a `bool`
|
||||
/// indicating success, and *either* populate the `out` input variable with the
|
||||
/// result, or the `err` input variable with a pointer to an FFIError struct.
|
||||
///
|
||||
/// Example (on the Rust side)
|
||||
/// ```
|
||||
/// use frs_error::{frs_result, FFIError};
|
||||
/// use num_traits::CheckedDiv;
|
||||
///
|
||||
/// pub fn checked_div<T>(numerator: T, denominator: T) -> Result<T, MyError>
|
||||
/// where
|
||||
/// T: CheckedDiv,
|
||||
/// {
|
||||
/// numerator
|
||||
/// .checked_div(&denominator)
|
||||
/// .ok_or(MyError::DivByZero)
|
||||
/// }
|
||||
///
|
||||
/// #[no_mangle]
|
||||
/// pub unsafe extern "C" fn checked_div_i64(
|
||||
/// numerator: i64,
|
||||
/// denominator: i64,
|
||||
/// result: *mut i64,
|
||||
/// err: *mut *mut FFIError,
|
||||
/// ) -> bool {
|
||||
/// frs_call!(out = result, err = err, checked_div(numerator, denominator))
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// And from the C side:
|
||||
/// ``` c
|
||||
/// int64_t result;
|
||||
/// FFIError *err = NULL;
|
||||
///
|
||||
/// if (checked_div_i64(10, 0, &result, &err)) {
|
||||
/// return 0;
|
||||
/// } else {
|
||||
/// fprintf(stderr, "Error: %s\n", ffierror_fmt(err));
|
||||
/// ffierror_free(err);
|
||||
/// return 1;
|
||||
/// }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! rrf_call {
|
||||
(err = $err_out:ident, $($fn:ident).+( $($args:expr),* )) => {
|
||||
if $err_out.is_null() {
|
||||
panic!("{} is NULL", stringify!($err_out));
|
||||
} else {
|
||||
match $($fn).*( $($args),* ) {
|
||||
Ok(()) => {
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
*$err_out = Box::into_raw(Box::new(e.into()));
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
(out = $result_out:ident, err=$err_out:ident, $($fn:ident).+( $($args:expr),* )) => {
|
||||
if $err_out.is_null() {
|
||||
panic!("{} is NULL", stringify!($err_out));
|
||||
} else {
|
||||
match $($fn).*( $($args),* ) {
|
||||
Ok(result) => {
|
||||
*$result_out = result;
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
*$err_out = Box::into_raw(Box::new(e.into()));
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
($fn:ident( err=$err_out:ident $(, $args:expr)* )) => {
|
||||
if $err_out.is_null() {
|
||||
panic!("{} is NULL", stringify!($err_out));
|
||||
} else {
|
||||
match $fn( $($args),* ) {
|
||||
Ok(result) => {
|
||||
Box::into_raw(Box::new(result))
|
||||
// result as *const ::core::ffi::c_void
|
||||
}
|
||||
Err(e) => {
|
||||
*$err_out = Box::into_raw(Box::new(e.into()));
|
||||
std::ptr::null()
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Consume the specified `Result<T,E>`, update output variables, and return true
|
||||
/// (if Result::is_ok) or false (if Result::is_err).
|
||||
//
|
||||
/// The `Result`'s `E` must implement `std::error::Error`.
|
||||
///
|
||||
/// The `out` parameter is optional, and the Result's "ok" value will be ignored
|
||||
/// if `out` is omitted.
|
||||
///
|
||||
/// Or for returning errors more explicitly without a function call:
|
||||
/// ```
|
||||
/// #[no_mangle]
|
||||
/// pub unsafe extern "C" fn checked_div_i64(
|
||||
/// numerator: i64,
|
||||
/// denominator: i64,
|
||||
/// out: *mut i64,
|
||||
/// err: *mut *mut FFIError,
|
||||
/// ) -> bool {
|
||||
/// let div_result = checked_div(numerator, denominator);
|
||||
///
|
||||
/// // Do other things that examine `div_result`, but don't consume it
|
||||
/// // ...
|
||||
///
|
||||
/// // Finally return
|
||||
/// frs_result!(result_in = div_result, out = out, err = err)
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
#[macro_export]
|
||||
macro_rules! ffi_result {
|
||||
(result_in=$result_in:ident, err=$err_out:ident) => {
|
||||
if $err_out.is_null() {
|
||||
panic!("{} is NULL", stringify!($err_out));
|
||||
} else {
|
||||
match $result_in {
|
||||
Ok(_) => true,
|
||||
Err(e) => {
|
||||
*$err_out = Box::into_raw(Box::new(e.into()));
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
(result_in=$result_in:ident, out=$result_out:ident, err=$err:ident) => {
|
||||
if $err.is_null() {
|
||||
panic!("{} is NULL", stringify!($err));
|
||||
} else {
|
||||
match $result_in {
|
||||
Ok(result) => {
|
||||
*$result_out = result;
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
*$err = Box::into_raw(Box::new(e.into()));
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Consume the specified error and update an output variable, returning false.
|
||||
///
|
||||
/// The given error must implement `std::error::Error`.
|
||||
///
|
||||
/// This macro is best used when specifically returning an error encountered
|
||||
/// outside of wrapping a Result-returning function (such as input validation).
|
||||
#[macro_export]
|
||||
macro_rules! ffi_error {
|
||||
(err=$err_out:ident, $err:expr) => {
|
||||
if $err_out.is_null() {
|
||||
panic!("{} is NULL", stringify!($err));
|
||||
} else {
|
||||
*$err_out = Box::into_raw(Box::new($err.into()));
|
||||
false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// A generic container for any error that implements `Into<std::error::Error>`
|
||||
pub struct FFIError {
|
||||
/// The contained error
|
||||
error: Box<dyn std::error::Error>,
|
||||
/// Cached formatted version of the error
|
||||
c_string: Option<CString>,
|
||||
}
|
||||
|
||||
impl FFIError {
|
||||
pub(crate) fn get_cstring(&mut self) -> Result<&CStr, std::ffi::NulError> {
|
||||
if self.c_string.is_none() {
|
||||
self.c_string = Some(CString::new(format!("{}", self.error))?);
|
||||
}
|
||||
Ok(self.c_string.as_ref().unwrap().as_c_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static + std::error::Error> From<T> for FFIError {
|
||||
fn from(err: T) -> Self {
|
||||
FFIError {
|
||||
error: Box::new(err),
|
||||
c_string: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute (and cache) a formatted error string from the provided [`FFIError`] pointer.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `err` must not be NULL
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn ffierror_fmt(err: *mut FFIError) -> *const c_char {
|
||||
assert!(!err.is_null());
|
||||
let mut err: ManuallyDrop<Box<FFIError>> = ManuallyDrop::new(Box::from_raw(err));
|
||||
match err.get_cstring() {
|
||||
Ok(s) => s.as_ptr(),
|
||||
Err(_) => CStr::from_bytes_with_nul_unchecked(b"<error string contains NUL>\0").as_ptr(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Free a [`FFIError`] structure
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `err` must not be NULL
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn ffierror_free(err: *mut FFIError) {
|
||||
assert!(!err.is_null());
|
||||
let _: Box<FFIError> = Box::from_raw(err);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FFIError;
|
||||
|
||||
#[test]
|
||||
fn basic() {
|
||||
// Capture a typical error
|
||||
if let Err(e) = std::str::from_utf8(b"\x80") {
|
||||
let _: FFIError = e.into();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn size() {
|
||||
eprintln!("FFIError size = {}", std::mem::size_of::<FFIError>())
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify that the given parameter is not NULL, and valid UTF-8,
|
||||
/// returns a &str if successful else returns sys::cl_error_t_CL_EARG
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```edition2018
|
||||
/// use util::validate_str_param;
|
||||
///
|
||||
/// # pub extern "C" fn _my_c_interface(blah: *const c_char) -> sys::cl_error_t {
|
||||
/// let blah = validate_str_param!(blah);
|
||||
/// # }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! validate_str_param {
|
||||
($ptr:ident) => {
|
||||
if $ptr.is_null() {
|
||||
warn!("{} is NULL", stringify!($ptr));
|
||||
return false;
|
||||
} else {
|
||||
#[allow(unused_unsafe)]
|
||||
match unsafe { CStr::from_ptr($ptr) }.to_str() {
|
||||
Err(e) => {
|
||||
warn!("{} is not valid unicode: {}", stringify!($ptr), e);
|
||||
return false;
|
||||
}
|
||||
Ok(s) => s,
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
563
clamav/libclamav_rust/src/fuzzy_hash.rs
Normal file
563
clamav/libclamav_rust/src/fuzzy_hash.rs
Normal file
@@ -0,0 +1,563 @@
|
||||
/*
|
||||
* Fuzzy hash implementations, matching, and signature support
|
||||
*
|
||||
* Copyright (C) 2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Authors: Micah Snyder, Mickey Sola, Scott Hutton
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
convert::{TryFrom, TryInto},
|
||||
ffi::CStr,
|
||||
mem::ManuallyDrop,
|
||||
os::raw::c_char,
|
||||
panic,
|
||||
slice,
|
||||
};
|
||||
|
||||
use image::{imageops::FilterType::Lanczos3, DynamicImage, ImageBuffer, Luma, Pixel, Rgb};
|
||||
use log::{debug, error, warn};
|
||||
use num_traits::{NumCast, ToPrimitive, Zero};
|
||||
use rustdct::DctPlanner;
|
||||
use thiserror::Error;
|
||||
use transpose::transpose;
|
||||
|
||||
use crate::{ffi_error, ffi_util::FFIError, rrf_call, sys, validate_str_param};
|
||||
|
||||
/// CdiffError enumerates all possible errors returned by this library.
|
||||
#[derive(Error, Debug)]
|
||||
pub enum FuzzyHashError {
|
||||
#[error("Invalid format")]
|
||||
Format,
|
||||
|
||||
#[error("Unknown algorithm: {0}")]
|
||||
UnknownAlgorithm(String),
|
||||
|
||||
#[error("Failed to convert hamming distance to unsigned 32bit integer: {0}")]
|
||||
FormatHammingDistance(String),
|
||||
|
||||
#[error("Invalid hamming distance: {0}")]
|
||||
InvalidHammingDistance(u32),
|
||||
|
||||
#[error("Invalid hash: {0}")]
|
||||
FormatHashBytes(String),
|
||||
|
||||
#[error("Failed to load image: {0}")]
|
||||
ImageLoad(image::ImageError),
|
||||
|
||||
#[error("Failed to load image due to bug in image decoder")]
|
||||
ImageLoadPanic(),
|
||||
|
||||
#[error("Invalid parameter: {0}")]
|
||||
InvalidParameter(String),
|
||||
|
||||
#[error("{0} parmeter is NULL")]
|
||||
NullParam(&'static str),
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Debug)]
|
||||
pub struct ImageFuzzyHash {
|
||||
bytes: [u8; 8],
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Debug)]
|
||||
pub enum FuzzyHash {
|
||||
Image(ImageFuzzyHash),
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for ImageFuzzyHash {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
if value.len() != 16 {
|
||||
return Err("Image fuzzy hash must be 16 characters in length");
|
||||
}
|
||||
|
||||
let mut hashbytes = [0; 8];
|
||||
if hex::decode_to_slice(value, &mut hashbytes).is_ok() {
|
||||
Ok(ImageFuzzyHash { bytes: hashbytes })
|
||||
} else {
|
||||
Err("Failed to decode image fuzzy hash bytes from hex to bytes")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for FuzzyHash {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
FuzzyHash::Image(hash_bytes) => {
|
||||
write!(f, "{}", hex::encode(hash_bytes.bytes))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct FuzzyHashMap {
|
||||
hashmap: HashMap<FuzzyHash, Vec<FuzzyHashMeta>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct FuzzyHashMeta {
|
||||
lsigid: u32,
|
||||
subsigid: u32,
|
||||
#[cfg(feature = "not_ready")]
|
||||
hamming_distance: u32,
|
||||
}
|
||||
|
||||
/// Initialize the hashmap
|
||||
#[no_mangle]
|
||||
pub extern "C" fn fuzzy_hashmap_new() -> sys::fuzzyhashmap_t {
|
||||
Box::into_raw(Box::new(FuzzyHashMap::default())) as sys::fuzzyhashmap_t
|
||||
}
|
||||
|
||||
/// Free the hashmap
|
||||
#[no_mangle]
|
||||
pub extern "C" fn fuzzy_hash_free_hashmap(fuzzy_hashmap: sys::fuzzyhashmap_t) {
|
||||
if fuzzy_hashmap.is_null() {
|
||||
warn!("Attempted to free a NULL hashmap pointer. Please report this at: https://github.com/Cisco-Talos/clamav/issues");
|
||||
} else {
|
||||
let _ = unsafe { Box::from_raw(fuzzy_hashmap as *mut FuzzyHashMap) };
|
||||
}
|
||||
}
|
||||
|
||||
/// C interface for FuzzyHashMap::check().
|
||||
/// Handles all the unsafe ffi stuff.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// No parameters may be NULL
|
||||
#[export_name = "fuzzy_hash_check"]
|
||||
pub unsafe extern "C" fn _fuzzy_hash_check(
|
||||
fuzzy_hashmap: sys::fuzzyhashmap_t,
|
||||
mdata: *mut sys::cli_ac_data,
|
||||
image_fuzzy_hash: sys::image_fuzzy_hash_t,
|
||||
) -> bool {
|
||||
let hash_bytes = image_fuzzy_hash.hash;
|
||||
|
||||
let hashmap = ManuallyDrop::new(Box::from_raw(fuzzy_hashmap as *mut FuzzyHashMap));
|
||||
|
||||
debug!(
|
||||
"Checking image fuzzy hash '{}' for signature match",
|
||||
hex::encode(hash_bytes)
|
||||
);
|
||||
|
||||
if let Some(meta_vec) = hashmap.check(hash_bytes) {
|
||||
for meta in meta_vec {
|
||||
sys::lsig_increment_subsig_match(mdata, meta.lsigid, meta.subsigid);
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// C interface for FuzzyHashMap::load_subsignature().
|
||||
/// Handles all the unsafe ffi stuff.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `hexsig` and `err` must not be NULL
|
||||
#[export_name = "fuzzy_hash_load_subsignature"]
|
||||
pub unsafe extern "C" fn _fuzzy_hash_load_subsignature(
|
||||
fuzzy_hashmap: sys::fuzzyhashmap_t,
|
||||
hexsig: *const c_char,
|
||||
lsig_id: u32,
|
||||
subsig_id: u32,
|
||||
err: *mut *mut FFIError,
|
||||
) -> bool {
|
||||
let hexsig = validate_str_param!(hexsig);
|
||||
|
||||
let mut hashmap = ManuallyDrop::new(Box::from_raw(fuzzy_hashmap as *mut FuzzyHashMap));
|
||||
|
||||
rrf_call!(
|
||||
err = err,
|
||||
hashmap.load_subsignature(hexsig, lsig_id, subsig_id)
|
||||
)
|
||||
}
|
||||
|
||||
/// C interface for fuzzy_hash_calculate_image().
|
||||
/// Handles all the unsafe ffi stuff.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `file_bytes` and `hash_out` must not be NULL
|
||||
#[export_name = "fuzzy_hash_calculate_image"]
|
||||
pub unsafe extern "C" fn _fuzzy_hash_calculate_image(
|
||||
file_bytes: *const u8,
|
||||
file_size: usize,
|
||||
hash_out: *mut u8,
|
||||
hash_out_len: usize,
|
||||
err: *mut *mut FFIError,
|
||||
) -> bool {
|
||||
if hash_out.is_null() {
|
||||
return ffi_error!(err = err, FuzzyHashError::NullParam("hash_out"));
|
||||
}
|
||||
|
||||
let buffer = if file_bytes.is_null() {
|
||||
return ffi_error!(err = err, FuzzyHashError::NullParam("file_bytes"));
|
||||
} else {
|
||||
slice::from_raw_parts(file_bytes, file_size)
|
||||
};
|
||||
|
||||
let hash_result = fuzzy_hash_calculate_image(buffer);
|
||||
let hash_bytes = match hash_result {
|
||||
Ok(hash) => hash,
|
||||
Err(error) => return ffi_error!(err = err, error),
|
||||
};
|
||||
|
||||
if hash_out_len < hash_bytes.len() {
|
||||
return ffi_error!(
|
||||
err = err,
|
||||
FuzzyHashError::InvalidParameter(format!(
|
||||
"hash_bytes output parameter too small to hold the hash: {} < {}",
|
||||
hash_out_len,
|
||||
hash_bytes.len()
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
hash_out.copy_from(hash_bytes.as_ptr(), hash_bytes.len());
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
impl FuzzyHashMap {
|
||||
/// Check for fuzzy hash matches.
|
||||
///
|
||||
/// In this initial version, we're just doing a simple hash lookup and the
|
||||
/// hamming distance is not considered.
|
||||
///
|
||||
/// TODO: In a future version, replace this with an implementation that can find
|
||||
/// any hashes within the signature meta.hamming_distance.
|
||||
pub fn check(&self, hash: [u8; 8]) -> Option<&Vec<FuzzyHashMeta>> {
|
||||
let hash = FuzzyHash::Image(ImageFuzzyHash { bytes: hash });
|
||||
self.hashmap.get(&hash)
|
||||
}
|
||||
|
||||
/// Load a fuzzy hash subsignature
|
||||
/// Parse a fuzzy hash logical sig subsignature.
|
||||
/// Add the fuzzy hash to the matcher so it can be matched.
|
||||
pub fn load_subsignature(
|
||||
&mut self,
|
||||
hexsig: &str,
|
||||
lsig_id: u32,
|
||||
subsig_id: u32,
|
||||
) -> Result<(), FuzzyHashError> {
|
||||
let mut hexsig_split = hexsig.split('#');
|
||||
|
||||
let algorithm = match hexsig_split.next() {
|
||||
Some(x) => x,
|
||||
None => return Err(FuzzyHashError::Format),
|
||||
};
|
||||
|
||||
let hash = match hexsig_split.next() {
|
||||
Some(x) => x,
|
||||
None => return Err(FuzzyHashError::Format),
|
||||
};
|
||||
|
||||
let distance: u32 = match hexsig_split.next() {
|
||||
Some(x) => match x.parse::<u32>() {
|
||||
Ok(n) => n,
|
||||
Err(_) => {
|
||||
return Err(FuzzyHashError::FormatHammingDistance(x.to_string()));
|
||||
}
|
||||
},
|
||||
None => 0,
|
||||
};
|
||||
|
||||
// TODO: Support non-zero distance
|
||||
if distance != 0 {
|
||||
error!(
|
||||
"Non-zero hamming distances for image fuzzy hashes are not supported in this version."
|
||||
);
|
||||
return Err(FuzzyHashError::InvalidHammingDistance(distance));
|
||||
}
|
||||
|
||||
match algorithm {
|
||||
"fuzzy_img" => {
|
||||
// Convert the hash string to an image fuzzy hash bytes struct
|
||||
let image_fuzzy_hash = hash
|
||||
.try_into()
|
||||
.map_err(|e| FuzzyHashError::FormatHashBytes(format!("{}: {}", e, hash)))?;
|
||||
|
||||
let fuzzy_hash = FuzzyHash::Image(image_fuzzy_hash);
|
||||
|
||||
let meta: FuzzyHashMeta = FuzzyHashMeta {
|
||||
lsigid: lsig_id,
|
||||
subsigid: subsig_id,
|
||||
#[cfg(feature = "not_ready")]
|
||||
hamming_distance: distance,
|
||||
};
|
||||
|
||||
// If the hash key does not exist in the hashmap, insert an empty vec.
|
||||
// Then add the current meta struct to the entry.
|
||||
self.hashmap
|
||||
.entry(fuzzy_hash)
|
||||
.or_insert_with(Vec::new)
|
||||
.push(meta);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
_ => {
|
||||
error!("Unknown fuzzy hash algorithm: {}", algorithm);
|
||||
Err(FuzzyHashError::UnknownAlgorithm(algorithm.to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a buffer and size, generate an image fuzzy hash
|
||||
///
|
||||
/// This algorithm attempts to reproduce the results of the `phash()` function
|
||||
/// from the Python `imagehash` package.
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// 1) I found that `image.grayscale() uses different RGB coefficients than
|
||||
/// the python `image.convert("L"). The docs for PIL.Image.convert() state:
|
||||
///
|
||||
/// When translating a color image to greyscale (mode "L"),
|
||||
/// the library uses the ITU-R 601-2 luma transform::
|
||||
///
|
||||
/// L = R * 299/1000 + G * 587/1000 + B * 114/1000
|
||||
///
|
||||
/// You can get near-identical** grayscale results by making a clone (or forking)
|
||||
/// the image-rs crate, and changing the coefficients to match those above:
|
||||
///
|
||||
/// diff --git a/src/color.rs b/src/color.rs
|
||||
/// index 78b5c587..92c99337 100644
|
||||
/// --- a/src/color.rs
|
||||
/// +++ b/src/color.rs
|
||||
/// @@ -462,7 +462,7 @@ where
|
||||
/// }
|
||||
///
|
||||
/// /// Coefficients to transform from sRGB to a CIE Y (luminance) value.
|
||||
/// -const SRGB_LUMA: [f32; 3] = [0.2126, 0.7152, 0.0722];
|
||||
/// +const SRGB_LUMA: [f32; 3] = [0.299, 0.587, 0.114];
|
||||
///
|
||||
/// #[inline]
|
||||
/// fn rgb_to_luma<T: Primitive>(rgb: &[T]) -> T {
|
||||
///
|
||||
/// **Note that I say "near-identical" because rounding
|
||||
/// appears to be slightly different and values are sometimes off-by-one.
|
||||
///
|
||||
/// This change doesn't appear to be required to match the phash_simple()
|
||||
/// function, but to match the phash() function where the median is used instead
|
||||
/// of the mean -- this change is required.
|
||||
///
|
||||
/// 2) scipy.fftpack.dct behaves differently on twodimensional arrays than
|
||||
/// single-dimensional arrays.
|
||||
/// See https://docs.scipy.org/doc/scipy/reference/generated/scipy.fftpack.dct.html:
|
||||
///
|
||||
/// Note the optional "axis" argument:
|
||||
/// Axis along which the dct is computed; the default is over the last axis
|
||||
/// (i.e., axis=-1).
|
||||
///
|
||||
/// For the Python `imagehash` package:
|
||||
/// - The `phash_simple()` function is doing a DCT-2 transform on a 2-dimensionals
|
||||
/// 32x32 array which means, just on the 2nd axis (just the rows).
|
||||
/// - The `phash()` function is doing a 2D DCT-2 transform, by running the DCT-2 on
|
||||
/// both X and Y axis, which is the same as transposing before or after each
|
||||
/// DCT-2 call.
|
||||
///
|
||||
/// 3) I observed that the DCT2 results from Python are consistently 2x greater
|
||||
/// than those from Rust. If I multiply every value by 2 after running the DCT,
|
||||
/// then the results are the same.
|
||||
///
|
||||
/// 4) We need to get a subset of the 2-D array representing the lower
|
||||
/// frequencies of the image, the same way the Python implementation does it.
|
||||
///
|
||||
/// The way the python implementation does this is with this line:
|
||||
/// ```python
|
||||
/// dctlowfreq = dct[:hash_size, :hash_size]
|
||||
/// ```
|
||||
///
|
||||
/// You can't actually do that with a Python array of arrays... this is numpy
|
||||
/// 2-D array manipulation magic, where you can index 2-D arrays in slices.
|
||||
/// It works like this:
|
||||
/// ```ipython3
|
||||
/// In [0]: x = [[0, 1, 2, 3, 4], [4, 5, 6, 7, 8], [8, 9, 10, 11, 12], [12, 13, 14, 15, 16], [16, 17, 18, 19, 20]]
|
||||
/// In [1]: h = 3
|
||||
/// In [2]: n = np.asarray(x)
|
||||
/// In [3]: lf = n[:h, 1:h+1]
|
||||
/// In [4]: n
|
||||
/// array([[ 0, 1, 2, 3, 4],
|
||||
/// [ 4, 5, 6, 7, 8],
|
||||
/// [ 8, 9, 10, 11, 12],
|
||||
/// [12, 13, 14, 15, 16],
|
||||
/// [16, 17, 18, 19, 20]])
|
||||
///
|
||||
/// In [5]: lf
|
||||
/// array([[ 0, 1, 2],
|
||||
/// [ 4, 5, 6],
|
||||
/// [ 8, 9, 10]])
|
||||
/// ```
|
||||
///
|
||||
/// We can do something similar, manually, to get the low-frequency selection.
|
||||
///
|
||||
/// param: hash_out is an output variable
|
||||
/// param: hash_out_len indicates the size of the hash_out buffer
|
||||
pub fn fuzzy_hash_calculate_image(buffer: &[u8]) -> Result<Vec<u8>, FuzzyHashError> {
|
||||
|
||||
// Load image and attempt to catch panics in case the decoders encounter unexpected issues
|
||||
let result = panic::catch_unwind(|| -> Result<DynamicImage, FuzzyHashError> {
|
||||
let image = image::load_from_memory(buffer).map_err(FuzzyHashError::ImageLoad)?;
|
||||
Ok(image)
|
||||
});
|
||||
|
||||
let og_image = match result {
|
||||
Ok(image) => image?,
|
||||
Err(_) => return Err(FuzzyHashError::ImageLoadPanic()),
|
||||
};
|
||||
|
||||
// Drop the alpha channel (if exists).
|
||||
let buff_rgb8 = og_image.to_rgb8();
|
||||
|
||||
// Convert image to grayscale.
|
||||
let buff_luma8 = grayscale(&buff_rgb8);
|
||||
|
||||
// Convert back to a DynamicImage type so we can resize it.
|
||||
let image_gs = DynamicImage::ImageLuma8(buff_luma8);
|
||||
|
||||
// Shrink to a 32x32 (1024 pixel) image.
|
||||
let image_small = image::DynamicImage::resize_exact(&image_gs, 32, 32, Lanczos3);
|
||||
|
||||
// Convert the data to a Vec of floats.
|
||||
let mut imgbuff_f32 = image_small.to_luma32f().into_raw();
|
||||
|
||||
//
|
||||
// Compute a 2D DCT-2 in-place.
|
||||
//
|
||||
let dct2 = DctPlanner::new().plan_dct2(32);
|
||||
|
||||
// Use a scratch space so we can transpose and run DCT's without allocating any extra space.
|
||||
// We'll switch back and forth between the buffer for the original small image (buffer1) and the scratch buffer (buffer2).
|
||||
let buffer1: &mut [f32] = imgbuff_f32.as_mut_slice();
|
||||
let buffer2: &mut [f32] = &mut [0.0; 1024];
|
||||
|
||||
// Transpose the image so we can run DCT on the X axis (columns) first.
|
||||
transpose(buffer1, buffer2, 32, 32);
|
||||
|
||||
// Run DCT2 on the columns.
|
||||
for (row_in, row_out) in buffer2.chunks_mut(32).zip(buffer1.chunks_mut(32)) {
|
||||
dct2.process_dct2_with_scratch(row_in, row_out);
|
||||
}
|
||||
// Multiply each value x2, to match results from scipy.fftpack.dct() implementation.
|
||||
// Note: Unsure why this is required, but it is.
|
||||
buffer2.iter_mut().for_each(|f| *f *= 2.0);
|
||||
|
||||
// Transpose the image back so we can run DCT on the Y axis (rows).
|
||||
transpose(buffer2, buffer1, 32, 32);
|
||||
|
||||
// Run DCT2 on the rows.
|
||||
for (row_in, row_out) in buffer1.chunks_mut(32).zip(buffer2.chunks_mut(32)) {
|
||||
dct2.process_dct2_with_scratch(row_in, row_out);
|
||||
}
|
||||
// Multiply each value x2, to match results from scipy.fftpack.dct() implementation.
|
||||
// Note: Unsure why this is required, but it is.
|
||||
buffer1.iter_mut().for_each(|f| *f *= 2.0);
|
||||
|
||||
//
|
||||
// Construct a DCT low frequency vector using the top-left most 8x8 values of the 32x32 DCT array.
|
||||
//
|
||||
let dct_low_freq = buffer1
|
||||
// 2D array is 32-elements wide.
|
||||
.chunks(32)
|
||||
// Grab the first 8 rows.
|
||||
.take(8)
|
||||
// But only take the first 8 elements (columns) from each row.
|
||||
.flat_map(|chunk| chunk.chunks(8).take(1))
|
||||
// Flatten the 8x8 selection down to a vector of floats.
|
||||
.flatten()
|
||||
.copied()
|
||||
.collect::<Vec<f32>>();
|
||||
|
||||
// Calculate average (median) of the DCT low frequency vector.
|
||||
let mut dct_low_freq_copy = dct_low_freq.clone();
|
||||
dct_low_freq_copy.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
||||
let median: f32 = (dct_low_freq_copy[31] + dct_low_freq_copy[32]) / 2.0;
|
||||
|
||||
// Construct hash vector by reducing DCT values to 1 or 0 by comparing terms vs median.
|
||||
let hashvec: Vec<u64> = dct_low_freq
|
||||
.into_iter()
|
||||
.map(|x| if x > median { 1 } else { 0 })
|
||||
.collect();
|
||||
|
||||
// Construct hash vec<u8> from bits.
|
||||
let hash_bytes: Vec<u8> = hashvec
|
||||
.chunks(8)
|
||||
.map(|chunk| {
|
||||
let chunk = chunk.to_owned();
|
||||
chunk
|
||||
.iter()
|
||||
.rev()
|
||||
.enumerate()
|
||||
.fold(None, |accum, (n, val)| {
|
||||
accum.or(Some(0)).map(|accum| accum | ((*val as u8) << n))
|
||||
})
|
||||
})
|
||||
.take_while(|x| x.is_some())
|
||||
.flatten()
|
||||
.collect();
|
||||
|
||||
debug!("Image hash: {}", hex::encode(&hash_bytes));
|
||||
|
||||
Ok(hash_bytes)
|
||||
}
|
||||
|
||||
/// Use these instead:
|
||||
/// L = R * 299/1000 + G * 587/1000 + B * 114/1000
|
||||
const SRGB_LUMA: [f32; 3] = [299.0 / 1000.0, 587.0 / 1000.0, 114.0 / 1000.0];
|
||||
|
||||
#[inline]
|
||||
fn rgb_to_luma(rgb: &[u8]) -> u8 {
|
||||
let l = SRGB_LUMA[0] * rgb[0].to_f32().unwrap()
|
||||
+ SRGB_LUMA[1] * rgb[1].to_f32().unwrap()
|
||||
+ SRGB_LUMA[2] * rgb[2].to_f32().unwrap();
|
||||
NumCast::from(l.round()).unwrap()
|
||||
}
|
||||
|
||||
/// Convert the supplied image to grayscale. Alpha channel is discarded.
|
||||
///
|
||||
/// This is a customized implemententation of the grayscale feature from the `image` crate.
|
||||
/// This allows us to:
|
||||
/// - use RGB->LUMA constants that match those used by the Python Pillow package.
|
||||
/// - round the luma floating point value to the nearest integer rather than truncating.
|
||||
///
|
||||
/// See also: https://github.com/image-rs/image/issues/1554
|
||||
fn grayscale(image: &ImageBuffer<Rgb<u8>, Vec<u8>>) -> ImageBuffer<Luma<u8>, Vec<u8>> {
|
||||
let (width, height) = image.dimensions();
|
||||
let mut out = ImageBuffer::new(width, height);
|
||||
|
||||
for y in 0..height {
|
||||
for x in 0..width {
|
||||
let pixel = image.get_pixel(x, y);
|
||||
|
||||
let mut pix = Luma([Zero::zero()]);
|
||||
let gray = pix.channels_mut();
|
||||
let rgb = pixel.channels();
|
||||
gray[0] = rgb_to_luma(rgb);
|
||||
|
||||
let pixel = Luma::from_slice(gray); //.into_color(); // no-op for luma->luma
|
||||
|
||||
out.put_pixel(x, y, *pixel);
|
||||
}
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
31
clamav/libclamav_rust/src/lib.rs
Normal file
31
clamav/libclamav_rust/src/lib.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* libclamav features written in Rust
|
||||
*
|
||||
* Copyright (C) 2021-2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Authors: Micah Snyder, Mickey Sola
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
/// cbindgen:ignore
|
||||
pub mod sys;
|
||||
|
||||
pub mod cdiff;
|
||||
pub mod evidence;
|
||||
pub mod ffi_util;
|
||||
pub mod fuzzy_hash;
|
||||
pub mod logging;
|
||||
pub mod util;
|
||||
99
clamav/libclamav_rust/src/logging.rs
Normal file
99
clamav/libclamav_rust/src/logging.rs
Normal file
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Rust logging module
|
||||
*
|
||||
* Copyright (C) 2021-2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Authors: Mickey Sola
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
use std::{
|
||||
ffi::{CStr, CString},
|
||||
os::raw::c_char,
|
||||
};
|
||||
|
||||
use log::{set_max_level, Level, LevelFilter, Metadata, Record};
|
||||
|
||||
use crate::sys;
|
||||
|
||||
pub struct ClamLogger;
|
||||
|
||||
impl log::Log for ClamLogger {
|
||||
fn enabled(&self, metadata: &Metadata) -> bool {
|
||||
metadata.level() <= Level::Debug
|
||||
}
|
||||
|
||||
fn log(&self, record: &Record) {
|
||||
if self.enabled(record.metadata()) {
|
||||
let msg = CString::new(format!("{}\n", record.args())).unwrap();
|
||||
let ptr = msg.as_ptr();
|
||||
|
||||
match record.level() {
|
||||
Level::Debug => unsafe {
|
||||
sys::cli_dbgmsg_no_inline(ptr);
|
||||
},
|
||||
Level::Error => unsafe {
|
||||
sys::cli_errmsg(ptr);
|
||||
},
|
||||
Level::Info => unsafe {
|
||||
sys::cli_infomsg_simple(ptr);
|
||||
},
|
||||
Level::Warn => unsafe {
|
||||
sys::cli_warnmsg(ptr);
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&self) {}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn clrs_log_init() -> bool {
|
||||
log::set_boxed_logger(Box::new(ClamLogger))
|
||||
.map(|()| set_max_level(LevelFilter::Debug))
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::clrs_log_init;
|
||||
use log::{debug, error, info, warn};
|
||||
|
||||
#[test]
|
||||
fn parse_move_works() {
|
||||
let init_status = clrs_log_init();
|
||||
assert!(init_status);
|
||||
debug!("Hello");
|
||||
info!("darkness");
|
||||
warn!("my old");
|
||||
error!("friend.");
|
||||
}
|
||||
}
|
||||
|
||||
/// API exported for C code to log to standard error using Rust.
|
||||
/// This would be be an alternative to fputs, and reliably prints
|
||||
/// non-ASCII UTF8 characters on Windows, where fputs does not.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn clrs_eprint(c_buf: *const c_char) -> () {
|
||||
if c_buf.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
let msg = unsafe { CStr::from_ptr(c_buf) }.to_string_lossy();
|
||||
eprint!("{}", msg);
|
||||
}
|
||||
1400
clamav/libclamav_rust/src/sys.rs
Normal file
1400
clamav/libclamav_rust/src/sys.rs
Normal file
File diff suppressed because it is too large
Load Diff
48
clamav/libclamav_rust/src/util.rs
Normal file
48
clamav/libclamav_rust/src/util.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Assorted utility functions and macros.
|
||||
*
|
||||
* Copyright (C) 2021-2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Authors: Scott Hutton
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
use std::fs::File;
|
||||
|
||||
/// Obtain a std::fs::File from an i32 in a platform-independent manner.
|
||||
///
|
||||
/// On Unix-like platforms, this is done with File::from_raw_fd().
|
||||
/// On Windows, this is done through the `libc` crate's `get_osfhandle()` function.
|
||||
/// All other platforms will panic!()
|
||||
pub fn file_from_fd_or_handle(fd: i32) -> File {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::io::FromRawFd;
|
||||
unsafe { File::from_raw_fd(fd) }
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use std::os::windows::io::{FromRawHandle, RawHandle};
|
||||
unsafe {
|
||||
let handle = libc::get_osfhandle(fd);
|
||||
File::from_raw_handle(handle as RawHandle)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(windows, unix)))]
|
||||
compile_error!("implemented only for unix and windows targets")
|
||||
}
|
||||
Reference in New Issue
Block a user