Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

7.0.x: backport treating unknown requirements as unsatisfied with opt-out flag - v2 #12226

Closed
wants to merge 2 commits into from
Closed
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
requires: add option to ignore unknown requirements
The new behavior in 8, and backported is to treat unknown requirements
as unsatisfied requirements.

For 7.0.8, add a configuration option, "ignore-unknown-requirements"
to completely ignore unknown requirements, effectively treating them
as available.

Ticket: #7434
jasonish committed Dec 4, 2024
commit 7b63231ef2bc921c520a45ef259191f8c1e9cc88
19 changes: 17 additions & 2 deletions doc/userguide/upgrade.rst
Original file line number Diff line number Diff line change
@@ -37,8 +37,23 @@ dedicated new configuration.
Upgrading to 7.0.8
------------------
- Unknown requirements in the ``requires`` keyword will now be treated
as unmet requirements, causing the rule to not be loaded. See
:ref:`keyword_requires`.
as unsatisfied requirements, causing the rule to not be loaded. See
:ref:`keyword_requires`. To opt out of this change and to ignore
uknown requirements, effectively treating them as satified the
``ignore-unknown-requirements`` configuration option can be used.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are you thinking about making this a 7.0.x only option? If so, we should probably state that this is not behavior that will continue in 8

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we want to keep this in 8 and beyond, I'd like it to live under detect.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally we wouldn't have it at all. So will add a note that it won't be around in 8.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we want to keep this in 8 and beyond, I'd like it to live under detect.

Also note the reasoning for the location: #12226 (comment)


Command line example::

--set ignore-unknown-requirements=true

Or as a top-level configuration option in ``suricata.yaml``:

.. code-block:: yaml

default-rule-path: /var/lib/suricata/rules
rule-files:
- suricata.rules
ignore-unknown-requirements: true

Upgrading 6.0 to 7.0
--------------------
65 changes: 34 additions & 31 deletions rust/src/detect/requires.rs
Original file line number Diff line number Diff line change
@@ -297,9 +297,9 @@ fn check_version(
}

fn check_requires(
requires: &Requires, suricata_version: &SuricataVersion,
requires: &Requires, suricata_version: &SuricataVersion, ignore_unknown: bool,
) -> Result<(), RequiresError> {
if !requires.unknown.is_empty() {
if !ignore_unknown && !requires.unknown.is_empty() {
return Err(RequiresError::UnknownRequirement(
requires.unknown.join(","),
));
@@ -453,7 +453,7 @@ pub unsafe extern "C" fn SCDetectRequiresStatusLog(
#[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,
status: &mut SCDetectRequiresStatus, ignore_unknown: c_int,
) -> c_int {
// First parse the running Suricata version.
let suricata_version = match parse_suricata_version(CStr::from_ptr(suricata_version_string)) {
@@ -476,7 +476,9 @@ pub unsafe extern "C" fn SCDetectCheckRequires(
}
};

match check_requires(&requires, &suricata_version) {
let ignore_unknown = ignore_unknown != 0;

match check_requires(&requires, &suricata_version, ignore_unknown) {
Ok(()) => 0,
Err(err) => {
match &err {
@@ -497,6 +499,7 @@ pub unsafe extern "C" fn SCDetectCheckRequires(
RequiresError::VersionGt => {
status.gt_count += 1;
}
RequiresError::UnknownRequirement(_) => {}
_ => {}
}
*errstr = err.c_errmsg();
@@ -680,7 +683,7 @@ mod test {
let suricata_version = SuricataVersion::new(7, 0, 4);
let requires = parse_requires("version >= 8").unwrap();
assert_eq!(
check_requires(&requires, &suricata_version),
check_requires(&requires, &suricata_version, false),
Err(RequiresError::VersionLt(SuricataVersion {
major: 8,
minor: 0,
@@ -691,79 +694,79 @@ mod test {
// 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(()));
assert_eq!(check_requires(&requires, &suricata_version, false), 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),
check_requires(&requires, &suricata_version, false),
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(()));
assert_eq!(check_requires(&requires, &suricata_version, false), 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),
check_requires(&requires, &suricata_version, false),
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(()));
assert_eq!(check_requires(&requires, &suricata_version, false), 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());
assert!(check_requires(&requires, &suricata_version, false).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());
assert!(check_requires(&requires, &suricata_version, false).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());
assert!(check_requires(&requires, &suricata_version, false).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());
assert!(check_requires(&requires, &suricata_version, false).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());
assert!(check_requires(&requires, &suricata_version, false).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());
assert!(check_requires(&requires, &suricata_version, false).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());
assert!(check_requires(&requires, &suricata_version, false).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());
assert!(check_requires(&requires, &SuricataVersion::new(6, 0, 0), false).is_err());
assert!(check_requires(&requires, &SuricataVersion::new(7, 0, 4), false).is_err());
assert!(check_requires(&requires, &SuricataVersion::new(7, 0, 5), false).is_ok());
assert!(check_requires(&requires, &SuricataVersion::new(8, 0, 3), false).is_err());
assert!(check_requires(&requires, &SuricataVersion::new(8, 0, 4), false).is_ok());
assert!(check_requires(&requires, &SuricataVersion::new(9, 0, 2), false).is_err());
assert!(check_requires(&requires, &SuricataVersion::new(9, 0, 3), false).is_ok());
assert!(check_requires(&requires, &SuricataVersion::new(10, 0, 0), false).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());
assert!(check_requires(&requires, &SuricataVersion::new(6, 0, 0), false).is_err());
assert!(check_requires(&requires, &SuricataVersion::new(7, 0, 0), false).is_err());
assert!(check_requires(&requires, &SuricataVersion::new(8, 0, 0), false).is_ok());
assert!(check_requires(&requires, &SuricataVersion::new(9, 0, 0), false).is_err());

// Unknown keyword.
let requires = parse_requires("feature true_lua, foo bar, version >= 7.0.3").unwrap();
@@ -786,7 +789,7 @@ mod test {
// 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());
assert!(check_requires(&requires, &SuricataVersion::new(8, 0, 0), false).is_err());
}

#[test]
@@ -830,6 +833,6 @@ 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_err());
assert!(check_requires(&requires, &SuricataVersion::new(8, 0, 0), false).is_err());
}
}
9 changes: 8 additions & 1 deletion src/detect-requires.c
Original file line number Diff line number Diff line change
@@ -20,6 +20,10 @@
#include "detect-engine.h"
#include "rust.h"

/* Set to true if unknown requirements should be ingored. In Suricata
* 8, unknown requirements are treated as unsatisfied requirements. */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah I think this answers my question. So do we need a warning/doc reference about this being 7 only behavior?

static int g_ignore_unknown_requirements = 0;

static int DetectRequiresSetup(DetectEngineCtx *de_ctx, Signature *s, const char *rawstr)
{
if (de_ctx->requirements == NULL) {
@@ -28,7 +32,8 @@ static int DetectRequiresSetup(DetectEngineCtx *de_ctx, Signature *s, const char
}

const char *errmsg = NULL;
int res = SCDetectCheckRequires(rawstr, PROG_VER, &errmsg, de_ctx->requirements);
int res = SCDetectCheckRequires(
rawstr, PROG_VER, &errmsg, de_ctx->requirements, g_ignore_unknown_requirements);
if (res == -1) {
// The requires expression is bad, log an error.
SCLogError("%s: %s", errmsg, rawstr);
@@ -43,6 +48,8 @@ static int DetectRequiresSetup(DetectEngineCtx *de_ctx, Signature *s, const char

void DetectRequiresRegister(void)
{
ConfGetBool("ignore-unknown-requirements", &g_ignore_unknown_requirements);

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";