requires: support requires check for keyword

For example:

    requires: keyword foo;

Will require that Suricata supports the "foo" keyword.

Ticket: #7403
pull/12202/head
Jason Ish 2 years ago committed by Victor Julien
parent 8bcc844b6f
commit 289ff25f5b

@ -218,9 +218,10 @@ requires
--------
The ``requires`` keyword allows a rule to require specific Suricata
features to be enabled, or the Suricata version to match an
expression. Rules that do not meet the requirements will by ignored,
and Suricata will not treat them as errors.
features to be enabled, specific keywords to be available, or the
Suricata version to match an expression. Rules that do not meet the
requirements will be ignored, and Suricata will not treat them as
errors.
Requirements that follow the valid format of ``<keyword>
<expression>`` but are not known to Suricata are allowed for future
@ -235,7 +236,7 @@ still adhere to the basic known formats of Suricata rules.
The format is::
requires: feature geoip, version >= 7.0.0
requires: feature geoip, version >= 7.0.0, keyword foobar
To require multiple features, the feature sub-keyword must be
specified multiple times::
@ -250,7 +251,7 @@ and *or* expressions may expressed with ``|`` like::
requires: version >= 7.0.4 < 8 | >= 8.0.3
to express that a rules requires version 7.0.4 or greater, but less
to express that a rule requires version 7.0.4 or greater, but less
than 8, **OR** greater than or equal to 8.0.3. Which could be useful
if a keyword wasn't added until 7.0.4 and the 8.0.3 patch releases, as
it would not exist in 8.0.1.

@ -60,6 +60,9 @@ enum RequiresError {
/// An unknown requirement was provided.
UnknownRequirement(String),
/// Suricata does not have support for a required keyword.
MissingKeyword(String),
}
impl RequiresError {
@ -74,6 +77,7 @@ impl RequiresError {
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
}
@ -166,8 +170,12 @@ struct RuleRequireVersion {
#[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.
@ -245,6 +253,9 @@ fn parse_requires(mut input: &str) -> Result<Requires, RequiresError> {
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.
@ -333,6 +344,12 @@ fn check_requires(
}
}
for keyword in &requires.keywords {
if !crate::feature::has_keyword(keyword) {
return Err(RequiresError::MissingKeyword(keyword.to_string()));
}
}
Ok(())
}
@ -600,6 +617,7 @@ mod test {
requires,
Requires {
features: vec![],
keywords: vec![],
version: vec![vec![RuleRequireVersion {
op: VersionCompareOp::Gte,
version: SuricataVersion {
@ -617,6 +635,7 @@ mod test {
requires,
Requires {
features: vec![],
keywords: vec![],
version: vec![vec![RuleRequireVersion {
op: VersionCompareOp::Gte,
version: SuricataVersion {
@ -634,6 +653,7 @@ mod test {
requires,
Requires {
features: vec!["output::file-store".to_string()],
keywords: vec![],
version: vec![vec![RuleRequireVersion {
op: VersionCompareOp::Gte,
version: SuricataVersion {
@ -651,6 +671,7 @@ mod test {
requires,
Requires {
features: vec!["geoip".to_string()],
keywords: vec![],
version: vec![vec![
RuleRequireVersion {
op: VersionCompareOp::Gte,
@ -771,6 +792,7 @@ mod test {
requires,
Requires {
features: vec!["true_lua".to_string()],
keywords: vec![],
version: vec![vec![RuleRequireVersion {
op: VersionCompareOp::Gte,
version: SuricataVersion {
@ -830,6 +852,9 @@ mod test {
#[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());
}
}

@ -32,6 +32,15 @@ mod mock {
pub fn requires(feature: &str) -> bool {
return feature.starts_with("true");
}
/// Check for a keyword returning true if found.
///
/// This a "mock" variant of `has_keyword` that will return true
/// for any keyword starting with string `true`, and false for
/// anything else.
pub fn has_keyword(keyword: &str) -> bool {
return keyword.starts_with("true");
}
}
#[cfg(not(test))]
@ -41,6 +50,7 @@ mod real {
extern "C" {
fn RequiresFeature(feature: *const c_char) -> bool;
fn SigTableHasKeyword(keyword: *const c_char) -> bool;
}
/// Check for a feature returning true if found.
@ -51,6 +61,14 @@ mod real {
false
}
}
pub fn has_keyword(keyword: &str) -> bool {
if let Ok(keyword) = CString::new(keyword) {
unsafe { SigTableHasKeyword(keyword.as_ptr()) }
} else {
false
}
}
}
#[cfg(not(test))]

Loading…
Cancel
Save