diff --git a/doc/userguide/rules/meta.rst b/doc/userguide/rules/meta.rst index 06e5040e73a5..5625f7c10af0 100644 --- a/doc/userguide/rules/meta.rst +++ b/doc/userguide/rules/meta.rst @@ -211,3 +211,29 @@ The format is:: If the value is src_ip then the source IP in the generated event (src_ip field in JSON) is the target of the attack. If target is set to dest_ip then the target is the destination IP in the generated event. + +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. + +The format is:: + + requires: feature geoip, version >= 7.0.0 + +To require multiple features, the feature sub-keyword must be +specified multiple times:: + + requires: feature geoip, feature lua + +The version sub-keyword may also be present multiple times, for +example a rule may express that it is for Suricata verisons greater +than or equal to 7.0.3, but less than version 8:: + + requires: version >= 7.0.4, version < 8 + +If no *minor* or *patch* version component is provided, it will +default to 0. diff --git a/rust/src/detect/mod.rs b/rust/src/detect/mod.rs index 41c7ff2455bd..d33c9ae7fabf 100644 --- a/rust/src/detect/mod.rs +++ b/rust/src/detect/mod.rs @@ -24,3 +24,4 @@ pub mod parser; pub mod stream_size; pub mod uint; pub mod uri; +pub mod requires; diff --git a/rust/src/detect/requires.rs b/rust/src/detect/requires.rs new file mode 100644 index 000000000000..05351757ac95 --- /dev/null +++ b/rust/src/detect/requires.rs @@ -0,0 +1,478 @@ +/* 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::{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::{ + branch::alt, + bytes::complete::{tag, take_till, take_while}, + character::complete::{char, multispace0}, + combinator::map_res, + sequence::preceded, + IResult, +}; + +#[derive(Debug, Eq, PartialEq)] +enum VersionCompareOp { + Gt, + Gte, + Lt, + Lte, +} + +#[derive(Debug, Eq, PartialEq)] +struct SuricataVersion { + major: u8, + minor: u8, + patch: u8, +} + +impl PartialOrd for SuricataVersion { + fn partial_cmp(&self, other: &Self) -> Option { + 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 SuricataVersion { + fn new(major: u8, minor: u8, patch: u8) -> Self { + Self { + major, + minor, + patch, + } + } +} + +#[derive(Debug, Eq, PartialEq)] +struct RuleRequireVersion { + pub op: VersionCompareOp, + pub version: SuricataVersion, +} + +#[derive(Debug)] +struct Requires { + pub features: Vec, + pub versions: Vec, +} + +impl Requires { + fn new() -> Self { + Self { + features: vec![], + versions: vec![], + } + } +} + +fn parse_op_gt(input: &str) -> IResult<&str, VersionCompareOp> { + let (input, _) = tag(">")(input)?; + Ok((input, VersionCompareOp::Gt)) +} + +fn parse_op_gte(input: &str) -> IResult<&str, VersionCompareOp> { + let (input, _) = tag(">=")(input)?; + Ok((input, VersionCompareOp::Gte)) +} + +fn parse_op_lt(input: &str) -> IResult<&str, VersionCompareOp> { + let (input, _) = tag("<")(input)?; + Ok((input, VersionCompareOp::Lt)) +} + +fn parse_op_lte(input: &str) -> IResult<&str, VersionCompareOp> { + let (input, _) = tag("<=")(input)?; + Ok((input, VersionCompareOp::Lte)) +} + +fn parse_op(input: &str) -> IResult<&str, VersionCompareOp> { + // Be careful of order, ie) attempt to parse >= before >. + alt((parse_op_gte, parse_op_gt, parse_op_lte, parse_op_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 == '-'), |s: &str| { + s.parse::() + })(input) +} + +fn parse_version(input: &str) -> IResult<&str, SuricataVersion> { + let (input, major) = parse_next_version_part(input)?; + let (input, minor) = if input.is_empty() { + (input, 0) + } else { + preceded(char('.'), parse_next_version_part)(input)? + }; + let (input, patch) = if input.is_empty() { + (input, 0) + } else { + preceded(char('.'), parse_next_version_part)(input)? + }; + + Ok((input, SuricataVersion::new(major, minor, patch))) +} + +fn parse_requires(mut input: &str) -> IResult<&str, Requires> { + let mut requires = Requires::new(); + + while !input.is_empty() { + let (rest, keyword) = preceded(multispace0, alt((tag("feature"), tag("version"))))(input)?; + let (rest, value) = preceded(multispace0, take_till(|c: char| c == ','))(rest)?; + + match keyword { + "feature" => { + requires.features.push(value.trim().to_string()); + } + "version" => { + let (value, op) = preceded(multispace0, parse_op)(value)?; + let (_, version) = preceded(multispace0, parse_version)(value)?; + let require_version = RuleRequireVersion { op, version }; + requires.versions.push(require_version); + } + _ => {} + } + + // Now consume any remaining "," or whitespace. + let (rest, _) = take_while(|c: char| c == ',' || c.is_whitespace())(rest)?; + input = rest; + } + Ok((input, requires)) +} + +static ERR_BAD_SURICATA_VERSION: &str = "Failed to parse running Suricata version\0"; +static ERR_BAD_REQUIRES: &str = "Failed to parse requires expression\0"; + +fn parse_suricata_version(version: &CStr) -> Result { + let version = version + .to_str() + .map_err(|_| ERR_BAD_SURICATA_VERSION.as_ptr() as *mut c_char)?; + let (_, version) = + parse_version(version).map_err(|_| ERR_BAD_SURICATA_VERSION.as_ptr() as *mut c_char)?; + Ok(version) +} + +#[derive(Debug, Eq, PartialEq)] +enum RequiresError { + /// The Suricata version greater than (too new) than the required + /// version. + VersionGt, + + /// The Suricata version is less than (too old) than the required + /// version. + VersionLt, + + /// The running Suricata is missing a required feature. + MissingFeature, +} + +impl RequiresError { + /// Return a pointer to a C compatible constant error message. + const fn c_errmsg(&self) -> *const c_char { + let msg = match self { + RequiresError::VersionGt => "Suricata version great than required\0", + RequiresError::VersionLt => "Suricata version less than required\0", + RequiresError::MissingFeature => "Suricata missing a required feature\0", + }; + msg.as_ptr() as *const c_char + } +} + +fn check_requires( + requires: &Requires, suricata_version: &SuricataVersion, +) -> Result<(), RequiresError> { + for version in &requires.versions { + match version.op { + VersionCompareOp::Gt => { + if suricata_version <= &version.version { + return Err(RequiresError::VersionLt); + } + } + VersionCompareOp::Gte => { + if suricata_version < &version.version { + return Err(RequiresError::VersionLt); + } + } + VersionCompareOp::Lt => { + if suricata_version >= &version.version { + return Err(RequiresError::VersionGt); + } + } + VersionCompareOp::Lte => { + if suricata_version > &version.version { + return Err(RequiresError::VersionGt); + } + } + } + } + + #[allow(clippy::never_loop)] + for _feature in &requires.features { + // TODO: Any feature is a missing feature for now. + return Err(RequiresError::MissingFeature); + } + + Ok(()) +} + +/// 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, +) -> 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(parse_requires) { + Ok(Ok((_, requires))) => requires, + _ => { + *errstr = ERR_BAD_REQUIRES.as_ptr() as *mut c_char; + return -1; + } + }; + + match check_requires(&requires, &suricata_version) { + Ok(()) => 0, + Err(err) => { + *errstr = err.c_errmsg(); + -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.versions[0], + &RuleRequireVersion { + op: VersionCompareOp::Gte, + version: SuricataVersion { + major: 7, + minor: 0, + patch: 0, + } + } + ); + + let (_, requires) = parse_requires("version >= 7.1").unwrap(); + assert_eq!( + &requires.versions[0], + &RuleRequireVersion { + op: VersionCompareOp::Gte, + version: SuricataVersion { + major: 7, + minor: 1, + patch: 0, + } + } + ); + + let (_, requires) = parse_requires("feature output::file-store, version >= 7.1.2").unwrap(); + assert_eq!( + &requires.versions[0], + &RuleRequireVersion { + op: VersionCompareOp::Gte, + version: SuricataVersion { + major: 7, + minor: 1, + patch: 2, + } + } + ); + + let (_, requires) = + parse_requires("feature: geoip, version >= 7.1.2, version < 8").unwrap(); + assert_eq!( + &requires.versions[0], + &RuleRequireVersion { + op: VersionCompareOp::Gte, + version: SuricataVersion { + major: 7, + minor: 1, + patch: 2, + } + } + ); + assert_eq!( + &requires.versions[1], + &RuleRequireVersion { + op: VersionCompareOp::Lt, + version: SuricataVersion { + major: 8, + minor: 0, + patch: 0, + } + } + ); + } + + #[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().1; + assert_eq!( + check_requires(&requires, &suricata_version), + Err(RequiresError::VersionLt) + ); + + // 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().1; + 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, version < 8").unwrap().1; + 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, version < 9").unwrap().1; + 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().1; + assert_eq!( + check_requires(&requires, &suricata_version), + Err(RequiresError::MissingFeature) + ); + } +} diff --git a/src/Makefile.am b/src/Makefile.am index 21e1dfe5fbeb..b7d2d9390c75 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -278,6 +278,7 @@ noinst_HEADERS = \ detect-rawbytes.h \ detect-reference.h \ detect-replace.h \ + detect-requires.h \ detect-rev.h \ detect-rfb-name.h \ detect-rfb-secresult.h \ @@ -892,6 +893,7 @@ libsuricata_c_a_SOURCES = \ detect-rawbytes.c \ detect-reference.c \ detect-replace.c \ + detect-requires.c \ detect-rev.c \ detect-rfb-name.c \ detect-rfb-secresult.c \ diff --git a/src/detect-engine-loader.c b/src/detect-engine-loader.c index 40919568503a..c68193865463 100644 --- a/src/detect-engine-loader.c +++ b/src/detect-engine-loader.c @@ -109,11 +109,11 @@ char *DetectLoadCompleteSigPath(const DetectEngineCtx *de_ctx, const char *sig_f * \param badsigs_tot Will store number of invalid signatures in the file * \retval 0 on success, -1 on error */ -static int DetectLoadSigFile(DetectEngineCtx *de_ctx, char *sig_file, - int *goodsigs, int *badsigs) +static int DetectLoadSigFile( + DetectEngineCtx *de_ctx, char *sig_file, int *goodsigs, int *badsigs, int *skippedsigs) { Signature *sig = NULL; - int good = 0, bad = 0; + int good = 0, bad = 0, skipped = 0; char line[DETECT_MAX_RULE_SIZE] = ""; size_t offset = 0; int lineno = 0, multiline = 0; @@ -196,6 +196,12 @@ static int DetectLoadSigFile(DetectEngineCtx *de_ctx, char *sig_file, if (!de_ctx->sigerror_ok) { bad++; } + if (de_ctx->sigerror_requires) { + SCLogInfo("Skipping signature due to missing requirements: %s from file %s at line " + "%" PRId32, + line, sig_file, lineno - multiline); + skipped++; + } } multiline = 0; } @@ -203,6 +209,7 @@ static int DetectLoadSigFile(DetectEngineCtx *de_ctx, char *sig_file, *goodsigs = good; *badsigs = bad; + *skippedsigs = skipped; return 0; } @@ -212,8 +219,8 @@ static int DetectLoadSigFile(DetectEngineCtx *de_ctx, char *sig_file, * \param sig_file Filename (or pattern) holding signatures * \retval -1 on error */ -static int ProcessSigFiles(DetectEngineCtx *de_ctx, char *pattern, - SigFileLoaderStat *st, int *good_sigs, int *bad_sigs) +static int ProcessSigFiles(DetectEngineCtx *de_ctx, char *pattern, SigFileLoaderStat *st, + int *good_sigs, int *bad_sigs, int *skipped_sigs) { int r = 0; @@ -250,7 +257,7 @@ static int ProcessSigFiles(DetectEngineCtx *de_ctx, char *pattern, } else { SCLogConfig("Loading rule file: %s", fname); } - r = DetectLoadSigFile(de_ctx, fname, good_sigs, bad_sigs); + r = DetectLoadSigFile(de_ctx, fname, good_sigs, bad_sigs, skipped_sigs); if (r < 0) { ++(st->bad_files); } @@ -259,6 +266,7 @@ static int ProcessSigFiles(DetectEngineCtx *de_ctx, char *pattern, st->good_sigs_total += *good_sigs; st->bad_sigs_total += *bad_sigs; + st->skipped_sigs_total += *skipped_sigs; #ifdef HAVE_GLOB_H } @@ -286,6 +294,7 @@ int SigLoadSignatures(DetectEngineCtx *de_ctx, char *sig_file, bool sig_file_exc char varname[128] = "rule-files"; int good_sigs = 0; int bad_sigs = 0; + int skipped_sigs = 0; if (strlen(de_ctx->config_prefix) > 0) { snprintf(varname, sizeof(varname), "%s.rule-files", @@ -307,8 +316,9 @@ int SigLoadSignatures(DetectEngineCtx *de_ctx, char *sig_file, bool sig_file_exc else { TAILQ_FOREACH(file, &rule_files->head, next) { sfile = DetectLoadCompleteSigPath(de_ctx, file->val); - good_sigs = bad_sigs = 0; - ret = ProcessSigFiles(de_ctx, sfile, sig_stat, &good_sigs, &bad_sigs); + good_sigs = bad_sigs = skipped_sigs = 0; + ret = ProcessSigFiles( + de_ctx, sfile, sig_stat, &good_sigs, &bad_sigs, &skipped_sigs); SCFree(sfile); if (de_ctx->failure_fatal && ret != 0) { @@ -327,7 +337,7 @@ int SigLoadSignatures(DetectEngineCtx *de_ctx, char *sig_file, bool sig_file_exc /* If a Signature file is specified from command-line, parse it too */ if (sig_file != NULL) { - ret = ProcessSigFiles(de_ctx, sig_file, sig_stat, &good_sigs, &bad_sigs); + ret = ProcessSigFiles(de_ctx, sig_file, sig_stat, &good_sigs, &bad_sigs, &skipped_sigs); if (ret != 0) { if (de_ctx->failure_fatal) { @@ -353,13 +363,16 @@ int SigLoadSignatures(DetectEngineCtx *de_ctx, char *sig_file, bool sig_file_exc /* we report the total of files and rules successfully loaded and failed */ if (strlen(de_ctx->config_prefix) > 0) SCLogInfo("tenant id %d: %" PRId32 " rule files processed. %" PRId32 - " rules successfully loaded, %" PRId32 " rules failed", + " rules successfully loaded, %" PRId32 " rules failed, %" PRId32 + " rules skipped", de_ctx->tenant_id, sig_stat->total_files, sig_stat->good_sigs_total, - sig_stat->bad_sigs_total); + sig_stat->bad_sigs_total, sig_stat->skipped_sigs_total); else SCLogInfo("%" PRId32 " rule files processed. %" PRId32 - " rules successfully loaded, %" PRId32 " rules failed", - sig_stat->total_files, sig_stat->good_sigs_total, sig_stat->bad_sigs_total); + " rules successfully loaded, %" PRId32 " rules failed, %" PRId32 + " rules skipped", + sig_stat->total_files, sig_stat->good_sigs_total, sig_stat->bad_sigs_total, + sig_stat->skipped_sigs_total); } if ((sig_stat->bad_sigs_total || sig_stat->bad_files) && de_ctx->failure_fatal) { diff --git a/src/detect-engine-register.c b/src/detect-engine-register.c index 0f459eccb67b..36896ff55480 100644 --- a/src/detect-engine-register.c +++ b/src/detect-engine-register.c @@ -115,6 +115,7 @@ #include "detect-flow.h" #include "detect-flow-age.h" #include "detect-flow-pkts.h" +#include "detect-requires.h" #include "detect-tcp-window.h" #include "detect-ftpbounce.h" #include "detect-isdataat.h" @@ -569,6 +570,7 @@ void SigTableSetup(void) DetectFlowPktsToServerRegister(); DetectFlowBytesToClientRegister(); DetectFlowBytesToServerRegister(); + DetectRequiresRegister(); DetectWindowRegister(); DetectRpcRegister(); DetectFtpbounceRegister(); diff --git a/src/detect-engine-register.h b/src/detect-engine-register.h index 273aa10d7c9b..b81d70a16707 100644 --- a/src/detect-engine-register.h +++ b/src/detect-engine-register.h @@ -115,6 +115,8 @@ enum DetectKeywordId { DETECT_FLOW_BYTES_TO_CLIENT, DETECT_FLOW_BYTES_TO_SERVER, + DETECT_REQUIRES, + DETECT_AL_TLS_VERSION, DETECT_AL_TLS_SUBJECT, DETECT_AL_TLS_ISSUERDN, diff --git a/src/detect-parse.c b/src/detect-parse.c index e1ac5f74b5a4..fc5349061656 100644 --- a/src/detect-parse.c +++ b/src/detect-parse.c @@ -2148,12 +2148,17 @@ static Signature *SigInitHelper(DetectEngineCtx *de_ctx, const char *sigstr, sig->gid = 1; int ret = SigParse(de_ctx, sig, sigstr, dir, &parser); - if (ret == -3) { + if (ret == -4) { + /* Rule requirements not met. */ de_ctx->sigerror_silent = true; de_ctx->sigerror_ok = true; + de_ctx->sigerror_requires = true; goto error; - } - else if (ret == -2) { + } else if (ret == -3) { + de_ctx->sigerror_silent = true; + de_ctx->sigerror_ok = true; + goto error; + } else if (ret == -2) { de_ctx->sigerror_silent = true; goto error; } else if (ret < 0) { diff --git a/src/detect-requires.c b/src/detect-requires.c new file mode 100644 index 000000000000..f13cd020d12d --- /dev/null +++ b/src/detect-requires.c @@ -0,0 +1,44 @@ +/* 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. + */ + +#include "suricata-common.h" +#include "detect-requires.h" +#include "detect-engine.h" +#include "rust.h" + +static int DetectRequiresSetup(DetectEngineCtx *de_ctx, Signature *s, const char *rawstr) +{ + const char *errmsg = NULL; + int res = SCDetectCheckRequires(rawstr, PROG_VER, &errmsg); + if (res == -1) { + // The requires expression is bad, log an error. + SCLogError("%s: %s", errmsg, rawstr); + de_ctx->sigerror = errmsg; + } else if (res == -4) { + // This Suricata instance didn't meet the requirements. + SCLogInfo("Suricata did not meet the rule requirements: %s: %s", errmsg, rawstr); + } + return res; +} + +void DetectRequiresRegister(void) +{ + sigmatch_table[DETECT_REQUIRES].name = "requires"; + sigmatch_table[DETECT_REQUIRES].desc = "require Suricata version or features"; + sigmatch_table[DETECT_REQUIRES].url = "/rules/meta-keywords.html#requires"; + sigmatch_table[DETECT_REQUIRES].Setup = DetectRequiresSetup; +} diff --git a/src/detect-requires.h b/src/detect-requires.h new file mode 100644 index 000000000000..70f1dc43b814 --- /dev/null +++ b/src/detect-requires.h @@ -0,0 +1,23 @@ +/* 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. + */ + +#ifndef __DETECT_REQUIRES_H__ +#define __DETECT_REQUIRES_H__ + +void DetectRequiresRegister(void); + +#endif /* __DETECT_REQUIRES_H__ */ diff --git a/src/detect.h b/src/detect.h index a3cd161fa654..1157f0d6940a 100644 --- a/src/detect.h +++ b/src/detect.h @@ -797,6 +797,7 @@ typedef struct SigFileLoaderStat_ { int total_files; int good_sigs_total; int bad_sigs_total; + int skipped_sigs_total; } SigFileLoaderStat; typedef struct DetectEngineThreadKeywordCtxItem_ { @@ -925,6 +926,9 @@ typedef struct DetectEngineCtx_ { bool sigerror_silent; bool sigerror_ok; + /** The rule errored out due to missing requirements. */ + bool sigerror_requires; + bool filedata_config_initialized; /* specify the configuration for mpm context factory */