mirror of https://github.com/OISF/suricata
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
861 lines
28 KiB
Rust
861 lines
28 KiB
Rust
/* Copyright (C) 2023 Open Information Security Foundation
|
|
*
|
|
* You can copy, redistribute or modify this Program 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
|
|
* version 2 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::{HashSet, VecDeque};
|
|
use std::{cmp::Ordering, ffi::CStr};
|
|
|
|
// std::ffi::{c_char, c_int} is recommended these days, but requires
|
|
// Rust 1.64.0.
|
|
use std::os::raw::{c_char, c_int};
|
|
|
|
use nom7::bytes::complete::take_while;
|
|
use nom7::combinator::map;
|
|
use nom7::multi::{many1, separated_list1};
|
|
use nom7::sequence::tuple;
|
|
use nom7::{
|
|
branch::alt,
|
|
bytes::complete::{tag, take_till},
|
|
character::complete::{char, multispace0},
|
|
combinator::map_res,
|
|
sequence::preceded,
|
|
IResult,
|
|
};
|
|
|
|
#[derive(Debug, Eq, PartialEq)]
|
|
enum RequiresError {
|
|
/// Suricata is greater than the required version.
|
|
VersionGt,
|
|
|
|
/// Suricata is less than the required version.
|
|
VersionLt(SuricataVersion),
|
|
|
|
/// The running Suricata is missing a required feature.
|
|
MissingFeature(String),
|
|
|
|
/// The Suricata version, of Suricata itself is bad and failed to parse.
|
|
BadSuricataVersion,
|
|
|
|
/// The requires expression is bad and failed to parse.
|
|
BadRequires,
|
|
|
|
/// MultipleVersions
|
|
MultipleVersions,
|
|
|
|
/// Passed in requirements not a valid UTF-8 string.
|
|
Utf8Error,
|
|
|
|
/// An unknown requirement was provided.
|
|
UnknownRequirement(String),
|
|
|
|
/// Suricata does not have support for a required keyword.
|
|
MissingKeyword(String),
|
|
}
|
|
|
|
impl RequiresError {
|
|
/// Return a pointer to a C compatible constant error message.
|
|
const fn c_errmsg(&self) -> *const c_char {
|
|
let msg = match self {
|
|
Self::VersionGt => "Suricata version greater than required\0",
|
|
Self::VersionLt(_) => "Suricata version less than required\0",
|
|
Self::MissingFeature(_) => "Suricata missing a required feature\0",
|
|
Self::BadSuricataVersion => "Failed to parse running Suricata version\0",
|
|
Self::BadRequires => "Failed to parse requires expression\0",
|
|
Self::MultipleVersions => "Version may only be specified once\0",
|
|
Self::Utf8Error => "Requires expression is not valid UTF-8\0",
|
|
Self::UnknownRequirement(_) => "Unknown requirements\0",
|
|
Self::MissingKeyword(_) => "Suricata missing a required keyword\0",
|
|
};
|
|
msg.as_ptr() as *const c_char
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
enum VersionCompareOp {
|
|
Gt,
|
|
Gte,
|
|
Lt,
|
|
Lte,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
|
struct SuricataVersion {
|
|
major: u8,
|
|
minor: u8,
|
|
patch: u8,
|
|
}
|
|
|
|
impl PartialOrd for SuricataVersion {
|
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
Some(self.cmp(other))
|
|
}
|
|
}
|
|
|
|
impl Ord for SuricataVersion {
|
|
fn cmp(&self, other: &Self) -> Ordering {
|
|
match self.major.cmp(&other.major) {
|
|
Ordering::Equal => match self.minor.cmp(&other.minor) {
|
|
Ordering::Equal => self.patch.cmp(&other.patch),
|
|
other => other,
|
|
},
|
|
other => other,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Display for SuricataVersion {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
|
|
}
|
|
}
|
|
|
|
impl SuricataVersion {
|
|
fn new(major: u8, minor: u8, patch: u8) -> Self {
|
|
Self {
|
|
major,
|
|
minor,
|
|
patch,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Parse a version expression.
|
|
///
|
|
/// Parse into a version expression into a nested array, for example:
|
|
///
|
|
/// version: >= 7.0.3 < 8 | >= 8.0.3
|
|
///
|
|
/// would result in something like:
|
|
///
|
|
/// [
|
|
/// [{op: gte, version: 7.0.3}, {op:lt, version: 8}],
|
|
/// [{op: gte, version: 8.0.3}],
|
|
/// ]
|
|
fn parse_version_expression(input: &str) -> IResult<&str, Vec<Vec<RuleRequireVersion>>> {
|
|
let sep = preceded(multispace0, tag("|"));
|
|
let inner_parser = many1(tuple((parse_op, parse_version)));
|
|
let (input, versions) = separated_list1(sep, inner_parser)(input)?;
|
|
|
|
let versions = versions
|
|
.into_iter()
|
|
.map(|versions| {
|
|
versions
|
|
.into_iter()
|
|
.map(|(op, version)| RuleRequireVersion { op, version })
|
|
.collect()
|
|
})
|
|
.collect();
|
|
|
|
Ok((input, versions))
|
|
}
|
|
|
|
#[derive(Debug, Eq, PartialEq)]
|
|
struct RuleRequireVersion {
|
|
pub op: VersionCompareOp,
|
|
pub version: SuricataVersion,
|
|
}
|
|
|
|
#[derive(Debug, Default, Eq, PartialEq)]
|
|
struct Requires {
|
|
/// Features required to be enabled.
|
|
pub features: Vec<String>,
|
|
|
|
/// Rule keywords required to exist.
|
|
pub keywords: Vec<String>,
|
|
|
|
/// The version expression.
|
|
///
|
|
/// - All of the inner most must evaluate to true.
|
|
/// - To pass, any of the outer must be true.
|
|
pub version: Vec<Vec<RuleRequireVersion>>,
|
|
|
|
/// Unknown parameters to requires.
|
|
pub unknown: Vec<String>,
|
|
}
|
|
|
|
fn parse_op(input: &str) -> IResult<&str, VersionCompareOp> {
|
|
preceded(
|
|
multispace0,
|
|
alt((
|
|
map(tag(">="), |_| VersionCompareOp::Gte),
|
|
map(tag(">"), |_| VersionCompareOp::Gt),
|
|
map(tag("<="), |_| VersionCompareOp::Lte),
|
|
map(tag("<"), |_| VersionCompareOp::Lt),
|
|
)),
|
|
)(input)
|
|
}
|
|
|
|
/// Parse the next part of the version.
|
|
///
|
|
/// That is all chars up to eof, or the next '.' or '-'.
|
|
fn parse_next_version_part(input: &str) -> IResult<&str, u8> {
|
|
map_res(
|
|
take_till(|c| c == '.' || c == '-' || c == ' '),
|
|
|s: &str| s.parse::<u8>(),
|
|
)(input)
|
|
}
|
|
|
|
/// Parse a version string into a SuricataVersion.
|
|
fn parse_version(input: &str) -> IResult<&str, SuricataVersion> {
|
|
let (input, major) = preceded(multispace0, parse_next_version_part)(input)?;
|
|
let (input, minor) = if input.is_empty() || input.starts_with(' ') {
|
|
(input, 0)
|
|
} else {
|
|
preceded(char('.'), parse_next_version_part)(input)?
|
|
};
|
|
let (input, patch) = if input.is_empty() || input.starts_with(' ') {
|
|
(input, 0)
|
|
} else {
|
|
preceded(char('.'), parse_next_version_part)(input)?
|
|
};
|
|
|
|
Ok((input, SuricataVersion::new(major, minor, patch)))
|
|
}
|
|
|
|
fn parse_key_value(input: &str) -> IResult<&str, (&str, &str)> {
|
|
// Parse the keyword, any sequence of characters, numbers or "-" or "_".
|
|
let (input, key) = preceded(
|
|
multispace0,
|
|
take_while(|c: char| c.is_alphanumeric() || c == '-' || c == '_'),
|
|
)(input)?;
|
|
let (input, value) = preceded(multispace0, take_till(|c: char| c == ','))(input)?;
|
|
Ok((input, (key, value)))
|
|
}
|
|
|
|
fn parse_requires(mut input: &str) -> Result<Requires, RequiresError> {
|
|
let mut requires = Requires::default();
|
|
|
|
while !input.is_empty() {
|
|
let (rest, (keyword, value)) =
|
|
parse_key_value(input).map_err(|_| RequiresError::BadRequires)?;
|
|
match keyword {
|
|
"feature" => {
|
|
requires.features.push(value.trim().to_string());
|
|
}
|
|
"version" => {
|
|
if !requires.version.is_empty() {
|
|
return Err(RequiresError::MultipleVersions);
|
|
}
|
|
let (_, versions) =
|
|
parse_version_expression(value).map_err(|_| RequiresError::BadRequires)?;
|
|
requires.version = versions;
|
|
}
|
|
"keyword" => {
|
|
requires.keywords.push(value.trim().to_string());
|
|
}
|
|
_ => {
|
|
// Unknown keyword, allow by warn in case we extend
|
|
// this in the future.
|
|
SCLogWarning!("Unknown requires keyword: {}", keyword);
|
|
requires.unknown.push(format!("{} {}", keyword, value));
|
|
}
|
|
}
|
|
|
|
// No consume any remaining ',' or whitespace.
|
|
input = rest.trim_start_matches(|c: char| c == ',' || c.is_whitespace());
|
|
}
|
|
Ok(requires)
|
|
}
|
|
|
|
fn parse_suricata_version(version: &CStr) -> Result<SuricataVersion, *const c_char> {
|
|
let version = version
|
|
.to_str()
|
|
.map_err(|_| RequiresError::BadSuricataVersion.c_errmsg())?;
|
|
let (_, version) =
|
|
parse_version(version).map_err(|_| RequiresError::BadSuricataVersion.c_errmsg())?;
|
|
Ok(version)
|
|
}
|
|
|
|
fn check_version(
|
|
version: &RuleRequireVersion, suricata_version: &SuricataVersion,
|
|
) -> Result<(), RequiresError> {
|
|
match version.op {
|
|
VersionCompareOp::Gt => {
|
|
if suricata_version <= &version.version {
|
|
return Err(RequiresError::VersionLt(version.version.clone()));
|
|
}
|
|
}
|
|
VersionCompareOp::Gte => {
|
|
if suricata_version < &version.version {
|
|
return Err(RequiresError::VersionLt(version.version.clone()));
|
|
}
|
|
}
|
|
VersionCompareOp::Lt => {
|
|
if suricata_version >= &version.version {
|
|
return Err(RequiresError::VersionGt);
|
|
}
|
|
}
|
|
VersionCompareOp::Lte => {
|
|
if suricata_version > &version.version {
|
|
return Err(RequiresError::VersionGt);
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn check_requires(
|
|
requires: &Requires, suricata_version: &SuricataVersion,
|
|
) -> Result<(), RequiresError> {
|
|
if !requires.unknown.is_empty() {
|
|
return Err(RequiresError::UnknownRequirement(
|
|
requires.unknown.join(","),
|
|
));
|
|
}
|
|
|
|
if !requires.version.is_empty() {
|
|
let mut errs = VecDeque::new();
|
|
let mut ok = 0;
|
|
for or_versions in &requires.version {
|
|
let mut err = None;
|
|
for version in or_versions {
|
|
if let Err(_err) = check_version(version, suricata_version) {
|
|
err = Some(_err);
|
|
break;
|
|
}
|
|
}
|
|
if let Some(err) = err {
|
|
errs.push_back(err);
|
|
} else {
|
|
ok += 1;
|
|
}
|
|
}
|
|
if ok == 0 {
|
|
return Err(errs.pop_front().unwrap());
|
|
}
|
|
}
|
|
|
|
for feature in &requires.features {
|
|
if !crate::feature::requires(feature) {
|
|
return Err(RequiresError::MissingFeature(feature.to_string()));
|
|
}
|
|
}
|
|
|
|
for keyword in &requires.keywords {
|
|
if !crate::feature::has_keyword(keyword) {
|
|
return Err(RequiresError::MissingKeyword(keyword.to_string()));
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Status object to hold required features and the latest version of
|
|
/// Suricata required.
|
|
///
|
|
/// Full qualified name as it is exposed to C.
|
|
#[derive(Debug, Default)]
|
|
pub struct SCDetectRequiresStatus {
|
|
min_version: Option<SuricataVersion>,
|
|
features: HashSet<String>,
|
|
|
|
/// Number of rules that didn't meet a feature.
|
|
feature_count: u64,
|
|
|
|
/// Number of rules where the Suricata version wasn't new enough.
|
|
lt_count: u64,
|
|
|
|
/// Number of rules where the Suricata version was too new.
|
|
gt_count: u64,
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn SCDetectRequiresStatusNew() -> *mut SCDetectRequiresStatus {
|
|
Box::into_raw(Box::default())
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn SCDetectRequiresStatusFree(status: *mut SCDetectRequiresStatus) {
|
|
if !status.is_null() {
|
|
std::mem::drop(Box::from_raw(status));
|
|
}
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn SCDetectRequiresStatusLog(
|
|
status: &mut SCDetectRequiresStatus, suricata_version: *const c_char, tenant_id: u32,
|
|
) {
|
|
let suricata_version = CStr::from_ptr(suricata_version)
|
|
.to_str()
|
|
.unwrap_or("<unknown>");
|
|
|
|
let mut parts = vec![];
|
|
if status.lt_count > 0 {
|
|
let min_version = status
|
|
.min_version
|
|
.as_ref()
|
|
.map(|v| v.to_string())
|
|
.unwrap_or_else(|| "<unknown>".to_string());
|
|
let msg = format!(
|
|
"{} {} skipped because the running Suricata version {} is less than {}",
|
|
status.lt_count,
|
|
if status.lt_count > 1 {
|
|
"rules were"
|
|
} else {
|
|
"rule was"
|
|
},
|
|
suricata_version,
|
|
&min_version
|
|
);
|
|
parts.push(msg);
|
|
}
|
|
if status.gt_count > 0 {
|
|
let msg = format!(
|
|
"{} {} for an older version Suricata",
|
|
status.gt_count,
|
|
if status.gt_count > 1 {
|
|
"rules were skipped as they are"
|
|
} else {
|
|
"rule was skipped as it is"
|
|
}
|
|
);
|
|
parts.push(msg);
|
|
}
|
|
if status.feature_count > 0 {
|
|
let features = status
|
|
.features
|
|
.iter()
|
|
.map(|f| f.to_string())
|
|
.collect::<Vec<String>>()
|
|
.join(", ");
|
|
let msg = format!(
|
|
"{}{} {} skipped because the running Suricata version does not have feature{}: [{}]",
|
|
if tenant_id > 0 {
|
|
format!("tenant id: {} ", tenant_id)
|
|
} else {
|
|
String::new()
|
|
},
|
|
status.feature_count,
|
|
if status.feature_count > 1 {
|
|
"rules were"
|
|
} else {
|
|
"rule was"
|
|
},
|
|
if status.feature_count > 1 { "s" } else { "" },
|
|
&features
|
|
);
|
|
parts.push(msg);
|
|
}
|
|
|
|
let msg = parts.join("; ");
|
|
|
|
if status.lt_count > 0 {
|
|
SCLogNotice!("{}", &msg);
|
|
} else if status.gt_count > 0 || status.feature_count > 0 {
|
|
SCLogInfo!("{}", &msg);
|
|
}
|
|
}
|
|
|
|
/// Parse a "requires" rule option.
|
|
///
|
|
/// Return values:
|
|
/// * 0 - OK, rule should continue loading
|
|
/// * -1 - Error parsing the requires content
|
|
/// * -4 - Requirements not met, don't continue loading the rule, this
|
|
/// value is chosen so it can be passed back to the options parser
|
|
/// as its treated as a non-fatal silent error.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn SCDetectCheckRequires(
|
|
requires: *const c_char, suricata_version_string: *const c_char, errstr: *mut *const c_char,
|
|
status: &mut SCDetectRequiresStatus,
|
|
) -> c_int {
|
|
// First parse the running Suricata version.
|
|
let suricata_version = match parse_suricata_version(CStr::from_ptr(suricata_version_string)) {
|
|
Ok(version) => version,
|
|
Err(err) => {
|
|
*errstr = err;
|
|
return -1;
|
|
}
|
|
};
|
|
|
|
let requires = match CStr::from_ptr(requires)
|
|
.to_str()
|
|
.map_err(|_| RequiresError::Utf8Error)
|
|
.and_then(parse_requires)
|
|
{
|
|
Ok(requires) => requires,
|
|
Err(err) => {
|
|
*errstr = err.c_errmsg();
|
|
return -1;
|
|
}
|
|
};
|
|
|
|
match check_requires(&requires, &suricata_version) {
|
|
Ok(()) => 0,
|
|
Err(err) => {
|
|
match &err {
|
|
RequiresError::VersionLt(version) => {
|
|
if let Some(min_version) = &status.min_version {
|
|
if version > min_version {
|
|
status.min_version = Some(version.clone());
|
|
}
|
|
} else {
|
|
status.min_version = Some(version.clone());
|
|
}
|
|
status.lt_count += 1;
|
|
}
|
|
RequiresError::MissingFeature(feature) => {
|
|
status.features.insert(feature.to_string());
|
|
status.feature_count += 1;
|
|
}
|
|
RequiresError::VersionGt => {
|
|
status.gt_count += 1;
|
|
}
|
|
_ => {}
|
|
}
|
|
*errstr = err.c_errmsg();
|
|
return -4;
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_suricata_version() {
|
|
// 7.1.1 < 7.1.2
|
|
assert!(SuricataVersion::new(7, 1, 1) < SuricataVersion::new(7, 1, 2));
|
|
|
|
// 7.1.1 <= 7.1.2
|
|
assert!(SuricataVersion::new(7, 1, 1) <= SuricataVersion::new(7, 1, 2));
|
|
|
|
// 7.1.1 <= 7.1.1
|
|
assert!(SuricataVersion::new(7, 1, 1) <= SuricataVersion::new(7, 1, 1));
|
|
|
|
// NOT 7.1.1 < 7.1.1
|
|
assert!(SuricataVersion::new(7, 1, 1) >= SuricataVersion::new(7, 1, 1));
|
|
|
|
// 7.3.1 < 7.22.1
|
|
assert!(SuricataVersion::new(7, 3, 1) < SuricataVersion::new(7, 22, 1));
|
|
|
|
// 7.22.1 >= 7.3.4
|
|
assert!(SuricataVersion::new(7, 22, 1) >= SuricataVersion::new(7, 3, 4));
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_op() {
|
|
assert_eq!(parse_op(">").unwrap().1, VersionCompareOp::Gt);
|
|
assert_eq!(parse_op(">=").unwrap().1, VersionCompareOp::Gte);
|
|
assert_eq!(parse_op("<").unwrap().1, VersionCompareOp::Lt);
|
|
assert_eq!(parse_op("<=").unwrap().1, VersionCompareOp::Lte);
|
|
|
|
assert!(parse_op("=").is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_version() {
|
|
assert_eq!(
|
|
parse_version("7").unwrap().1,
|
|
SuricataVersion {
|
|
major: 7,
|
|
minor: 0,
|
|
patch: 0,
|
|
}
|
|
);
|
|
|
|
assert_eq!(
|
|
parse_version("7.1").unwrap().1,
|
|
SuricataVersion {
|
|
major: 7,
|
|
minor: 1,
|
|
patch: 0,
|
|
}
|
|
);
|
|
|
|
assert_eq!(
|
|
parse_version("7.1.2").unwrap().1,
|
|
SuricataVersion {
|
|
major: 7,
|
|
minor: 1,
|
|
patch: 2,
|
|
}
|
|
);
|
|
|
|
// Suricata pre-releases will have a suffix starting with a
|
|
// '-', so make sure we accept those versions as well.
|
|
assert_eq!(
|
|
parse_version("8.0.0-dev").unwrap().1,
|
|
SuricataVersion {
|
|
major: 8,
|
|
minor: 0,
|
|
patch: 0,
|
|
}
|
|
);
|
|
|
|
assert!(parse_version("7.1.2a").is_err());
|
|
assert!(parse_version("a").is_err());
|
|
assert!(parse_version("777").is_err());
|
|
assert!(parse_version("product-1").is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_requires() {
|
|
let requires = parse_requires(" feature geoip").unwrap();
|
|
assert_eq!(&requires.features[0], "geoip");
|
|
|
|
let requires = parse_requires(" feature geoip, feature lua ").unwrap();
|
|
assert_eq!(&requires.features[0], "geoip");
|
|
assert_eq!(&requires.features[1], "lua");
|
|
|
|
let requires = parse_requires("version >=7").unwrap();
|
|
assert_eq!(
|
|
requires,
|
|
Requires {
|
|
features: vec![],
|
|
keywords: vec![],
|
|
version: vec![vec![RuleRequireVersion {
|
|
op: VersionCompareOp::Gte,
|
|
version: SuricataVersion {
|
|
major: 7,
|
|
minor: 0,
|
|
patch: 0,
|
|
}
|
|
}]],
|
|
unknown: vec![],
|
|
}
|
|
);
|
|
|
|
let requires = parse_requires("version >= 7.1").unwrap();
|
|
assert_eq!(
|
|
requires,
|
|
Requires {
|
|
features: vec![],
|
|
keywords: vec![],
|
|
version: vec![vec![RuleRequireVersion {
|
|
op: VersionCompareOp::Gte,
|
|
version: SuricataVersion {
|
|
major: 7,
|
|
minor: 1,
|
|
patch: 0,
|
|
}
|
|
}]],
|
|
unknown: vec![],
|
|
}
|
|
);
|
|
|
|
let requires = parse_requires("feature output::file-store, version >= 7.1.2").unwrap();
|
|
assert_eq!(
|
|
requires,
|
|
Requires {
|
|
features: vec!["output::file-store".to_string()],
|
|
keywords: vec![],
|
|
version: vec![vec![RuleRequireVersion {
|
|
op: VersionCompareOp::Gte,
|
|
version: SuricataVersion {
|
|
major: 7,
|
|
minor: 1,
|
|
patch: 2,
|
|
}
|
|
}]],
|
|
unknown: vec![],
|
|
}
|
|
);
|
|
|
|
let requires = parse_requires("feature geoip, version >= 7.1.2 < 8").unwrap();
|
|
assert_eq!(
|
|
requires,
|
|
Requires {
|
|
features: vec!["geoip".to_string()],
|
|
keywords: vec![],
|
|
version: vec![vec![
|
|
RuleRequireVersion {
|
|
op: VersionCompareOp::Gte,
|
|
version: SuricataVersion {
|
|
major: 7,
|
|
minor: 1,
|
|
patch: 2,
|
|
},
|
|
},
|
|
RuleRequireVersion {
|
|
op: VersionCompareOp::Lt,
|
|
version: SuricataVersion {
|
|
major: 8,
|
|
minor: 0,
|
|
patch: 0,
|
|
}
|
|
}
|
|
]],
|
|
unknown: vec![],
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_requires() {
|
|
// Have 7.0.4, require >= 8.
|
|
let suricata_version = SuricataVersion::new(7, 0, 4);
|
|
let requires = parse_requires("version >= 8").unwrap();
|
|
assert_eq!(
|
|
check_requires(&requires, &suricata_version),
|
|
Err(RequiresError::VersionLt(SuricataVersion {
|
|
major: 8,
|
|
minor: 0,
|
|
patch: 0,
|
|
})),
|
|
);
|
|
|
|
// Have 7.0.4, require 7.0.3.
|
|
let suricata_version = SuricataVersion::new(7, 0, 4);
|
|
let requires = parse_requires("version >= 7.0.3").unwrap();
|
|
assert_eq!(check_requires(&requires, &suricata_version), Ok(()));
|
|
|
|
// Have 8.0.0, require >= 7.0.0 and < 8.0
|
|
let suricata_version = SuricataVersion::new(8, 0, 0);
|
|
let requires = parse_requires("version >= 7.0.0 < 8").unwrap();
|
|
assert_eq!(
|
|
check_requires(&requires, &suricata_version),
|
|
Err(RequiresError::VersionGt)
|
|
);
|
|
|
|
// Have 8.0.0, require >= 7.0.0 and < 9.0
|
|
let suricata_version = SuricataVersion::new(8, 0, 0);
|
|
let requires = parse_requires("version >= 7.0.0 < 9").unwrap();
|
|
assert_eq!(check_requires(&requires, &suricata_version), Ok(()));
|
|
|
|
// Require feature foobar.
|
|
let suricata_version = SuricataVersion::new(8, 0, 0);
|
|
let requires = parse_requires("feature foobar").unwrap();
|
|
assert_eq!(
|
|
check_requires(&requires, &suricata_version),
|
|
Err(RequiresError::MissingFeature("foobar".to_string()))
|
|
);
|
|
|
|
// Require feature foobar, but this time we have the feature.
|
|
let suricata_version = SuricataVersion::new(8, 0, 0);
|
|
let requires = parse_requires("feature true_foobar").unwrap();
|
|
assert_eq!(check_requires(&requires, &suricata_version), Ok(()));
|
|
|
|
let suricata_version = SuricataVersion::new(8, 0, 1);
|
|
let requires = parse_requires("version >= 7.0.3 < 8").unwrap();
|
|
assert!(check_requires(&requires, &suricata_version).is_err());
|
|
|
|
let suricata_version = SuricataVersion::new(7, 0, 1);
|
|
let requires = parse_requires("version >= 7.0.3 < 8").unwrap();
|
|
assert!(check_requires(&requires, &suricata_version).is_err());
|
|
|
|
let suricata_version = SuricataVersion::new(7, 0, 3);
|
|
let requires = parse_requires("version >= 7.0.3 < 8").unwrap();
|
|
assert!(check_requires(&requires, &suricata_version).is_ok());
|
|
|
|
let suricata_version = SuricataVersion::new(8, 0, 3);
|
|
let requires = parse_requires("version >= 7.0.3 < 8 | >= 8.0.3").unwrap();
|
|
assert!(check_requires(&requires, &suricata_version).is_ok());
|
|
|
|
let suricata_version = SuricataVersion::new(8, 0, 2);
|
|
let requires = parse_requires("version >= 7.0.3 < 8 | >= 8.0.3").unwrap();
|
|
assert!(check_requires(&requires, &suricata_version).is_err());
|
|
|
|
let suricata_version = SuricataVersion::new(7, 0, 2);
|
|
let requires = parse_requires("version >= 7.0.3 < 8 | >= 8.0.3").unwrap();
|
|
assert!(check_requires(&requires, &suricata_version).is_err());
|
|
|
|
let suricata_version = SuricataVersion::new(7, 0, 3);
|
|
let requires = parse_requires("version >= 7.0.3 < 8 | >= 8.0.3").unwrap();
|
|
assert!(check_requires(&requires, &suricata_version).is_ok());
|
|
|
|
// Example of something that requires a fix/feature that was
|
|
// implemented in 7.0.5, 8.0.4, 9.0.3.
|
|
let requires = parse_requires("version >= 7.0.5 < 8 | >= 8.0.4 < 9 | >= 9.0.3").unwrap();
|
|
assert!(check_requires(&requires, &SuricataVersion::new(6, 0, 0)).is_err());
|
|
assert!(check_requires(&requires, &SuricataVersion::new(7, 0, 4)).is_err());
|
|
assert!(check_requires(&requires, &SuricataVersion::new(7, 0, 5)).is_ok());
|
|
assert!(check_requires(&requires, &SuricataVersion::new(8, 0, 3)).is_err());
|
|
assert!(check_requires(&requires, &SuricataVersion::new(8, 0, 4)).is_ok());
|
|
assert!(check_requires(&requires, &SuricataVersion::new(9, 0, 2)).is_err());
|
|
assert!(check_requires(&requires, &SuricataVersion::new(9, 0, 3)).is_ok());
|
|
assert!(check_requires(&requires, &SuricataVersion::new(10, 0, 0)).is_ok());
|
|
|
|
let requires = parse_requires("version >= 8 < 9").unwrap();
|
|
assert!(check_requires(&requires, &SuricataVersion::new(6, 0, 0)).is_err());
|
|
assert!(check_requires(&requires, &SuricataVersion::new(7, 0, 0)).is_err());
|
|
assert!(check_requires(&requires, &SuricataVersion::new(8, 0, 0)).is_ok());
|
|
assert!(check_requires(&requires, &SuricataVersion::new(9, 0, 0)).is_err());
|
|
|
|
// Unknown keyword.
|
|
let requires = parse_requires("feature true_lua, foo bar, version >= 7.0.3").unwrap();
|
|
assert_eq!(
|
|
requires,
|
|
Requires {
|
|
features: vec!["true_lua".to_string()],
|
|
keywords: vec![],
|
|
version: vec![vec![RuleRequireVersion {
|
|
op: VersionCompareOp::Gte,
|
|
version: SuricataVersion {
|
|
major: 7,
|
|
minor: 0,
|
|
patch: 3,
|
|
}
|
|
}]],
|
|
unknown: vec!["foo bar".to_string()],
|
|
}
|
|
);
|
|
|
|
// This should not pass the requires check as it contains an
|
|
// unknown requires keyword.
|
|
//check_requires(&requires, &SuricataVersion::new(8, 0, 0)).unwrap();
|
|
assert!(check_requires(&requires, &SuricataVersion::new(8, 0, 0)).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_version_expression() {
|
|
let version_str = ">= 7.0.3 < 8 | >= 8.0.3";
|
|
let (rest, versions) = parse_version_expression(version_str).unwrap();
|
|
assert!(rest.is_empty());
|
|
assert_eq!(
|
|
versions,
|
|
vec![
|
|
vec![
|
|
RuleRequireVersion {
|
|
op: VersionCompareOp::Gte,
|
|
version: SuricataVersion {
|
|
major: 7,
|
|
minor: 0,
|
|
patch: 3,
|
|
}
|
|
},
|
|
RuleRequireVersion {
|
|
op: VersionCompareOp::Lt,
|
|
version: SuricataVersion {
|
|
major: 8,
|
|
minor: 0,
|
|
patch: 0,
|
|
}
|
|
},
|
|
],
|
|
vec![RuleRequireVersion {
|
|
op: VersionCompareOp::Gte,
|
|
version: SuricataVersion {
|
|
major: 8,
|
|
minor: 0,
|
|
patch: 3,
|
|
}
|
|
},],
|
|
]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_requires_keyword() {
|
|
let requires = parse_requires("keyword true_bar").unwrap();
|
|
assert!(check_requires(&requires, &SuricataVersion::new(8, 0, 0)).is_ok());
|
|
|
|
let requires = parse_requires("keyword bar").unwrap();
|
|
assert!(check_requires(&requires, &SuricataVersion::new(8, 0, 0)).is_err());
|
|
}
|
|
}
|