Coverage Report

Created: 2026-06-07 07:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata7/rust/src/detect/requires.rs
Line
Count
Source
1
/* Copyright (C) 2023 Open Information Security Foundation
2
 *
3
 * You can copy, redistribute or modify this Program under the terms of
4
 * the GNU General Public License version 2 as published by the Free
5
 * Software Foundation.
6
 *
7
 * This program is distributed in the hope that it will be useful,
8
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10
 * GNU General Public License for more details.
11
 *
12
 * You should have received a copy of the GNU General Public License
13
 * version 2 along with this program; if not, write to the Free Software
14
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15
 * 02110-1301, USA.
16
 */
17
18
use std::collections::{HashSet, VecDeque};
19
use std::{cmp::Ordering, ffi::CStr};
20
21
// std::ffi::{c_char, c_int} is recommended these days, but requires
22
// Rust 1.64.0.
23
use std::os::raw::{c_char, c_int};
24
25
use nom7::bytes::complete::take_while;
26
use nom7::combinator::map;
27
use nom7::multi::{many1, separated_list1};
28
use nom7::sequence::tuple;
29
use nom7::{
30
    branch::alt,
31
    bytes::complete::{tag, take_till},
32
    character::complete::{char, multispace0},
33
    combinator::map_res,
34
    sequence::preceded,
35
    IResult,
36
};
37
38
#[derive(Debug, Eq, PartialEq)]
39
enum RequiresError {
40
    /// Suricata is greater than the required version.
41
    VersionGt,
42
43
    /// Suricata is less than the required version.
44
    VersionLt(SuricataVersion),
45
46
    /// The running Suricata is missing a required feature.
47
    MissingFeature(String),
48
49
    /// The Suricata version, of Suricata itself is bad and failed to parse.
50
    BadSuricataVersion,
51
52
    /// The requires expression is bad and failed to parse.
53
    BadRequires,
54
55
    /// MultipleVersions
56
    MultipleVersions,
57
58
    /// Passed in requirements not a valid UTF-8 string.
59
    Utf8Error,
60
61
    /// An unknown requirement was provided.
62
    UnknownRequirement(String),
63
}
64
65
impl RequiresError {
66
    /// Return a pointer to a C compatible constant error message.
67
11.2k
    const fn c_errmsg(&self) -> *const c_char {
68
11.2k
        let msg = match self {
69
84
            Self::VersionGt => "Suricata version greater than required\0",
70
2.49k
            Self::VersionLt(_) => "Suricata version less than required\0",
71
607
            Self::MissingFeature(_) => "Suricata missing a required feature\0",
72
0
            Self::BadSuricataVersion => "Failed to parse running Suricata version\0",
73
526
            Self::BadRequires => "Failed to parse requires expression\0",
74
1
            Self::MultipleVersions => "Version may only be specified once\0",
75
0
            Self::Utf8Error => "Requires expression is not valid UTF-8\0",
76
7.54k
            Self::UnknownRequirement(_) => "Unknown requirements\0",
77
        };
78
11.2k
        msg.as_ptr() as *const c_char
79
11.2k
    }
80
}
81
82
#[derive(Clone, Debug, Eq, PartialEq)]
83
enum VersionCompareOp {
84
    Gt,
85
    Gte,
86
    Lt,
87
    Lte,
88
}
89
90
#[derive(Debug, Clone, Eq, PartialEq)]
91
struct SuricataVersion {
92
    major: u8,
93
    minor: u8,
94
    patch: u8,
95
}
96
97
impl PartialOrd for SuricataVersion {
98
40.3k
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
99
40.3k
        Some(self.cmp(other))
100
40.3k
    }
101
}
102
103
impl Ord for SuricataVersion {
104
40.3k
    fn cmp(&self, other: &Self) -> Ordering {
105
40.3k
        match self.major.cmp(&other.major) {
106
12.0k
            Ordering::Equal => match self.minor.cmp(&other.minor) {
107
9.24k
                Ordering::Equal => self.patch.cmp(&other.patch),
108
2.84k
                other => other,
109
            },
110
28.2k
            other => other,
111
        }
112
40.3k
    }
113
}
114
115
impl std::fmt::Display for SuricataVersion {
116
93
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
117
93
        write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
118
93
    }
119
}
120
121
impl SuricataVersion {
122
63.8k
    fn new(major: u8, minor: u8, patch: u8) -> Self {
123
63.8k
        Self {
124
63.8k
            major,
125
63.8k
            minor,
126
63.8k
            patch,
127
63.8k
        }
128
63.8k
    }
129
}
130
131
/// Parse a version expression.
132
///
133
/// Parse into a version expression into a nested array, for example:
134
///
135
///    version: >= 7.0.3 < 8 | >= 8.0.3
136
///
137
/// would result in something like:
138
///
139
/// [
140
///     [{op: gte, version: 7.0.3}, {op:lt, version: 8}],
141
///     [{op: gte, version: 8.0.3}],
142
/// ]
143
12.8k
fn parse_version_expression(input: &str) -> IResult<&str, Vec<Vec<RuleRequireVersion>>> {
144
12.8k
    let sep = preceded(multispace0, tag("|"));
145
12.8k
    let inner_parser = many1(tuple((parse_op, parse_version)));
146
12.8k
    let (input, versions) = separated_list1(sep, inner_parser)(input)?;
147
148
12.2k
    let versions = versions
149
12.2k
        .into_iter()
150
30.0k
        .map(|versions| {
151
30.0k
            versions
152
30.0k
                .into_iter()
153
43.0k
                .map(|(op, version)| RuleRequireVersion { op, version })
154
30.0k
                .collect()
155
30.0k
        })
156
12.2k
        .collect();
157
158
12.2k
    Ok((input, versions))
159
12.8k
}
160
161
#[derive(Debug, Eq, PartialEq)]
162
struct RuleRequireVersion {
163
    pub op: VersionCompareOp,
164
    pub version: SuricataVersion,
165
}
166
167
#[derive(Debug, Default, Eq, PartialEq)]
168
struct Requires {
169
    pub features: Vec<String>,
170
171
    /// The version expression.
172
    ///
173
    /// - All of the inner most must evaluate to true.
174
    /// - To pass, any of the outer must be true.
175
    pub version: Vec<Vec<RuleRequireVersion>>,
176
177
    /// Unknown parameters to requires.
178
    pub unknown: Vec<String>,
179
}
180
181
75.5k
fn parse_op(input: &str) -> IResult<&str, VersionCompareOp> {
182
75.5k
    preceded(
183
        multispace0,
184
75.5k
        alt((
185
75.5k
            map(tag(">="), |_| VersionCompareOp::Gte),
186
75.5k
            map(tag(">"), |_| VersionCompareOp::Gt),
187
75.5k
            map(tag("<="), |_| VersionCompareOp::Lte),
188
75.5k
            map(tag("<"), |_| VersionCompareOp::Lt),
189
        )),
190
75.5k
    )(input)
191
75.5k
}
192
193
/// Parse the next part of the version.
194
///
195
/// That is all chars up to eof, or the next '.' or '-'.
196
137k
fn parse_next_version_part(input: &str) -> IResult<&str, u8> {
197
137k
    map_res(
198
322k
        take_till(|c| c == '.' || c == '-' || c == ' '),
199
137k
        |s: &str| s.parse::<u8>(),
200
137k
    )(input)
201
137k
}
202
203
/// Parse a version string into a SuricataVersion.
204
68.3k
fn parse_version(input: &str) -> IResult<&str, SuricataVersion> {
205
68.3k
    let (input, major) = preceded(multispace0, parse_next_version_part)(input)?;
206
65.4k
    let (input, minor) = if input.is_empty() || input.starts_with(' ') {
207
26.3k
        (input, 0)
208
    } else {
209
39.1k
        preceded(char('.'), parse_next_version_part)(input)?
210
    };
211
64.4k
    let (input, patch) = if input.is_empty() || input.starts_with(' ') {
212
34.0k
        (input, 0)
213
    } else {
214
30.4k
        preceded(char('.'), parse_next_version_part)(input)?
215
    };
216
217
63.8k
    Ok((input, SuricataVersion::new(major, minor, patch)))
218
68.3k
}
219
220
58.7k
fn parse_key_value(input: &str) -> IResult<&str, (&str, &str)> {
221
    // Parse the keyword, any sequence of characters, numbers or "-" or "_".
222
58.7k
    let (input, key) = preceded(
223
        multispace0,
224
372k
        take_while(|c: char| c.is_alphanumeric() || c == '-' || c == '_'),
225
58.7k
    )(input)?;
226
1.10M
    let (input, value) = preceded(multispace0, take_till(|c: char| c == ','))(input)?;
227
58.7k
    Ok((input, (key, value)))
228
58.7k
}
229
230
20.7k
fn parse_requires(mut input: &str) -> Result<Requires, RequiresError> {
231
20.7k
    let mut requires = Requires::default();
232
233
78.9k
    while !input.is_empty() {
234
58.7k
        let (rest, (keyword, value)) =
235
58.7k
            parse_key_value(input).map_err(|_| RequiresError::BadRequires)?;
236
58.7k
        match keyword {
237
58.7k
            "feature" => {
238
5.65k
                requires.features.push(value.trim().to_string());
239
5.65k
            }
240
53.1k
            "version" => {
241
12.8k
                if !requires.version.is_empty() {
242
1
                    return Err(RequiresError::MultipleVersions);
243
12.8k
                }
244
12.2k
                let (_, versions) =
245
12.8k
                    parse_version_expression(value).map_err(|_| RequiresError::BadRequires)?;
246
12.2k
                requires.version = versions;
247
            }
248
            _ => {
249
                // Unknown keyword, allow by warn in case we extend
250
                // this in the future.
251
40.2k
                SCLogWarning!("Unknown requires keyword: {}", keyword);
252
40.2k
                requires.unknown.push(format!("{} {}", keyword, value));
253
            }
254
        }
255
256
        // No consume any remaining ',' or whitespace.
257
146k
        input = rest.trim_start_matches(|c: char| c == ',' || c.is_whitespace());
258
    }
259
20.2k
    Ok(requires)
260
20.7k
}
261
262
20.7k
fn parse_suricata_version(version: &CStr) -> Result<SuricataVersion, *const c_char> {
263
20.7k
    let version = version
264
20.7k
        .to_str()
265
20.7k
        .map_err(|_| RequiresError::BadSuricataVersion.c_errmsg())?;
266
20.7k
    let (_, version) =
267
20.7k
        parse_version(version).map_err(|_| RequiresError::BadSuricataVersion.c_errmsg())?;
268
20.7k
    Ok(version)
269
20.7k
}
270
271
37.9k
fn check_version(
272
37.9k
    version: &RuleRequireVersion, suricata_version: &SuricataVersion,
273
37.9k
) -> Result<(), RequiresError> {
274
37.9k
    match version.op {
275
        VersionCompareOp::Gt => {
276
13.7k
            if suricata_version <= &version.version {
277
8.96k
                return Err(RequiresError::VersionLt(version.version.clone()));
278
4.79k
            }
279
        }
280
        VersionCompareOp::Gte => {
281
14.7k
            if suricata_version < &version.version {
282
7.47k
                return Err(RequiresError::VersionLt(version.version.clone()));
283
7.31k
            }
284
        }
285
        VersionCompareOp::Lt => {
286
6.48k
            if suricata_version >= &version.version {
287
1.47k
                return Err(RequiresError::VersionGt);
288
5.00k
            }
289
        }
290
        VersionCompareOp::Lte => {
291
2.95k
            if suricata_version > &version.version {
292
623
                return Err(RequiresError::VersionGt);
293
2.33k
            }
294
        }
295
    }
296
19.4k
    Ok(())
297
37.9k
}
298
299
20.2k
fn check_requires(
300
20.2k
    requires: &Requires, suricata_version: &SuricataVersion, ignore_unknown: bool,
301
20.2k
) -> Result<(), RequiresError> {
302
20.2k
    if !ignore_unknown && !requires.unknown.is_empty() {
303
7.54k
        return Err(RequiresError::UnknownRequirement(
304
7.54k
            requires.unknown.join(","),
305
7.54k
        ));
306
12.6k
    }
307
308
12.6k
    if !requires.version.is_empty() {
309
10.9k
        let mut errs = VecDeque::new();
310
10.9k
        let mut ok = 0;
311
39.5k
        for or_versions in &requires.version {
312
28.5k
            let mut err = None;
313
48.0k
            for version in or_versions {
314
37.9k
                if let Err(_err) = check_version(version, suricata_version) {
315
18.5k
                    err = Some(_err);
316
18.5k
                    break;
317
19.4k
                }
318
            }
319
28.5k
            if let Some(err) = err {
320
18.5k
                errs.push_back(err);
321
18.5k
            } else {
322
10.0k
                ok += 1;
323
10.0k
            }
324
        }
325
10.9k
        if ok == 0 {
326
2.57k
            return Err(errs.pop_front().unwrap());
327
8.38k
        }
328
1.71k
    }
329
330
11.2k
    for feature in &requires.features {
331
1.71k
        if !crate::feature::requires(feature) {
332
607
            return Err(RequiresError::MissingFeature(feature.to_string()));
333
1.10k
        }
334
    }
335
336
9.49k
    Ok(())
337
20.2k
}
338
339
/// Status object to hold required features and the latest version of
340
/// Suricata required.
341
///
342
/// Full qualified name as it is exposed to C.
343
#[derive(Debug, Default)]
344
pub struct SCDetectRequiresStatus {
345
    min_version: Option<SuricataVersion>,
346
    features: HashSet<String>,
347
348
    /// Number of rules that didn't meet a feature.
349
    feature_count: u64,
350
351
    /// Number of rules where the Suricata version wasn't new enough.
352
    lt_count: u64,
353
354
    /// Number of rules where the Suricata version was too new.
355
    gt_count: u64,
356
}
357
358
#[no_mangle]
359
914
pub extern "C" fn SCDetectRequiresStatusNew() -> *mut SCDetectRequiresStatus {
360
914
    Box::into_raw(Box::default())
361
914
}
362
363
#[no_mangle]
364
913
pub unsafe extern "C" fn SCDetectRequiresStatusFree(status: *mut SCDetectRequiresStatus) {
365
913
    if !status.is_null() {
366
913
        std::mem::drop(Box::from_raw(status));
367
913
    }
368
913
}
369
370
#[no_mangle]
371
292
pub unsafe extern "C" fn SCDetectRequiresStatusLog(
372
292
    status: &mut SCDetectRequiresStatus, suricata_version: *const c_char, tenant_id: u32,
373
292
) {
374
292
    let suricata_version = CStr::from_ptr(suricata_version)
375
292
        .to_str()
376
292
        .unwrap_or("<unknown>");
377
378
292
    let mut parts = vec![];
379
292
    if status.lt_count > 0 {
380
93
        let min_version = status
381
93
            .min_version
382
93
            .as_ref()
383
93
            .map(|v| v.to_string())
384
93
            .unwrap_or_else(|| "<unknown>".to_string());
385
93
        let msg = format!(
386
93
            "{} {} skipped because the running Suricata version {} is less than {}",
387
            status.lt_count,
388
93
            if status.lt_count > 1 {
389
55
                "rules were"
390
            } else {
391
38
                "rule was"
392
            },
393
            suricata_version,
394
93
            &min_version
395
        );
396
93
        parts.push(msg);
397
199
    }
398
292
    if status.gt_count > 0 {
399
0
        let msg = format!(
400
0
            "{} {} for an older version Suricata",
401
            status.gt_count,
402
0
            if status.gt_count > 1 {
403
0
                "rules were skipped as they are"
404
            } else {
405
0
                "rule was skipped as it is"
406
            }
407
        );
408
0
        parts.push(msg);
409
292
    }
410
292
    if status.feature_count > 0 {
411
46
        let features = status
412
46
            .features
413
46
            .iter()
414
100
            .map(|f| f.to_string())
415
46
            .collect::<Vec<String>>()
416
46
            .join(", ");
417
46
        let msg = format!(
418
46
            "{}{} {} skipped because the running Suricata version does not have feature{}: [{}]",
419
46
            if tenant_id > 0 {
420
0
                format!("tenant id: {}  ", tenant_id)
421
            } else {
422
46
                String::new()
423
            },
424
            status.feature_count,
425
46
            if status.feature_count > 1 {
426
40
                "rules were"
427
            } else {
428
6
                "rule was"
429
            },
430
46
            if status.feature_count > 1 { "s" } else { "" },
431
46
            &features
432
        );
433
46
        parts.push(msg);
434
246
    }
435
436
292
    let msg = parts.join("; ");
437
438
292
    if status.lt_count > 0 {
439
93
        SCLogNotice!("{}", &msg);
440
199
    } else if status.gt_count > 0 || status.feature_count > 0 {
441
44
        SCLogInfo!("{}", &msg);
442
155
    }
443
292
}
444
445
/// Parse a "requires" rule option.
446
///
447
/// Return values:
448
///   *  0 - OK, rule should continue loading
449
///   * -1 - Error parsing the requires content
450
///   * -4 - Requirements not met, don't continue loading the rule, this
451
///     value is chosen so it can be passed back to the options parser
452
///     as its treated as a non-fatal silent error.
453
#[no_mangle]
454
20.7k
pub unsafe extern "C" fn SCDetectCheckRequires(
455
20.7k
    requires: *const c_char, suricata_version_string: *const c_char, errstr: *mut *const c_char,
456
20.7k
    status: &mut SCDetectRequiresStatus, ignore_unknown: c_int,
457
20.7k
) -> c_int {
458
    // First parse the running Suricata version.
459
20.7k
    let suricata_version = match parse_suricata_version(CStr::from_ptr(suricata_version_string)) {
460
20.7k
        Ok(version) => version,
461
0
        Err(err) => {
462
0
            *errstr = err;
463
0
            return -1;
464
        }
465
    };
466
467
20.7k
    let requires = match CStr::from_ptr(requires)
468
20.7k
        .to_str()
469
20.7k
        .map_err(|_| RequiresError::Utf8Error)
470
20.7k
        .and_then(parse_requires)
471
    {
472
20.2k
        Ok(requires) => requires,
473
527
        Err(err) => {
474
527
            *errstr = err.c_errmsg();
475
527
            return -1;
476
        }
477
    };
478
479
20.2k
    let ignore_unknown = ignore_unknown != 0;
480
481
20.2k
    match check_requires(&requires, &suricata_version, ignore_unknown) {
482
9.49k
        Ok(()) => 0,
483
10.7k
        Err(err) => {
484
10.7k
            match &err {
485
2.49k
                RequiresError::VersionLt(version) => {
486
2.49k
                    if let Some(min_version) = &status.min_version {
487
2.36k
                        if version > min_version {
488
45
                            status.min_version = Some(version.clone());
489
2.31k
                        }
490
133
                    } else {
491
133
                        status.min_version = Some(version.clone());
492
133
                    }
493
2.49k
                    status.lt_count += 1;
494
                }
495
607
                RequiresError::MissingFeature(feature) => {
496
607
                    status.features.insert(feature.to_string());
497
607
                    status.feature_count += 1;
498
607
                }
499
84
                RequiresError::VersionGt => {
500
84
                    status.gt_count += 1;
501
84
                }
502
7.54k
                RequiresError::UnknownRequirement(_) => {}
503
0
                _ => {}
504
            }
505
10.7k
            *errstr = err.c_errmsg();
506
10.7k
            return -4;
507
        }
508
    }
509
20.7k
}
510
511
#[cfg(test)]
512
mod test {
513
    use super::*;
514
515
    #[test]
516
    fn test_suricata_version() {
517
        // 7.1.1 < 7.1.2
518
        assert!(SuricataVersion::new(7, 1, 1) < SuricataVersion::new(7, 1, 2));
519
520
        // 7.1.1 <= 7.1.2
521
        assert!(SuricataVersion::new(7, 1, 1) <= SuricataVersion::new(7, 1, 2));
522
523
        // 7.1.1 <= 7.1.1
524
        assert!(SuricataVersion::new(7, 1, 1) <= SuricataVersion::new(7, 1, 1));
525
526
        // NOT 7.1.1 < 7.1.1
527
        assert!(SuricataVersion::new(7, 1, 1) >= SuricataVersion::new(7, 1, 1));
528
529
        // 7.3.1 < 7.22.1
530
        assert!(SuricataVersion::new(7, 3, 1) < SuricataVersion::new(7, 22, 1));
531
532
        // 7.22.1 >= 7.3.4
533
        assert!(SuricataVersion::new(7, 22, 1) >= SuricataVersion::new(7, 3, 4));
534
    }
535
536
    #[test]
537
    fn test_parse_op() {
538
        assert_eq!(parse_op(">").unwrap().1, VersionCompareOp::Gt);
539
        assert_eq!(parse_op(">=").unwrap().1, VersionCompareOp::Gte);
540
        assert_eq!(parse_op("<").unwrap().1, VersionCompareOp::Lt);
541
        assert_eq!(parse_op("<=").unwrap().1, VersionCompareOp::Lte);
542
543
        assert!(parse_op("=").is_err());
544
    }
545
546
    #[test]
547
    fn test_parse_version() {
548
        assert_eq!(
549
            parse_version("7").unwrap().1,
550
            SuricataVersion {
551
                major: 7,
552
                minor: 0,
553
                patch: 0,
554
            }
555
        );
556
557
        assert_eq!(
558
            parse_version("7.1").unwrap().1,
559
            SuricataVersion {
560
                major: 7,
561
                minor: 1,
562
                patch: 0,
563
            }
564
        );
565
566
        assert_eq!(
567
            parse_version("7.1.2").unwrap().1,
568
            SuricataVersion {
569
                major: 7,
570
                minor: 1,
571
                patch: 2,
572
            }
573
        );
574
575
        // Suricata pre-releases will have a suffix starting with a
576
        // '-', so make sure we accept those versions as well.
577
        assert_eq!(
578
            parse_version("8.0.0-dev").unwrap().1,
579
            SuricataVersion {
580
                major: 8,
581
                minor: 0,
582
                patch: 0,
583
            }
584
        );
585
586
        assert!(parse_version("7.1.2a").is_err());
587
        assert!(parse_version("a").is_err());
588
        assert!(parse_version("777").is_err());
589
        assert!(parse_version("product-1").is_err());
590
    }
591
592
    #[test]
593
    fn test_parse_requires() {
594
        let requires = parse_requires("  feature geoip").unwrap();
595
        assert_eq!(&requires.features[0], "geoip");
596
597
        let requires = parse_requires("  feature geoip,    feature    lua  ").unwrap();
598
        assert_eq!(&requires.features[0], "geoip");
599
        assert_eq!(&requires.features[1], "lua");
600
601
        let requires = parse_requires("version >=7").unwrap();
602
        assert_eq!(
603
            requires,
604
            Requires {
605
                features: vec![],
606
                version: vec![vec![RuleRequireVersion {
607
                    op: VersionCompareOp::Gte,
608
                    version: SuricataVersion {
609
                        major: 7,
610
                        minor: 0,
611
                        patch: 0,
612
                    }
613
                }]],
614
                unknown: vec![],
615
            }
616
        );
617
618
        let requires = parse_requires("version >= 7.1").unwrap();
619
        assert_eq!(
620
            requires,
621
            Requires {
622
                features: vec![],
623
                version: vec![vec![RuleRequireVersion {
624
                    op: VersionCompareOp::Gte,
625
                    version: SuricataVersion {
626
                        major: 7,
627
                        minor: 1,
628
                        patch: 0,
629
                    }
630
                }]],
631
                unknown: vec![],
632
            }
633
        );
634
635
        let requires = parse_requires("feature output::file-store, version >= 7.1.2").unwrap();
636
        assert_eq!(
637
            requires,
638
            Requires {
639
                features: vec!["output::file-store".to_string()],
640
                version: vec![vec![RuleRequireVersion {
641
                    op: VersionCompareOp::Gte,
642
                    version: SuricataVersion {
643
                        major: 7,
644
                        minor: 1,
645
                        patch: 2,
646
                    }
647
                }]],
648
                unknown: vec![],
649
            }
650
        );
651
652
        let requires = parse_requires("feature geoip, version >= 7.1.2 < 8").unwrap();
653
        assert_eq!(
654
            requires,
655
            Requires {
656
                features: vec!["geoip".to_string()],
657
                version: vec![vec![
658
                    RuleRequireVersion {
659
                        op: VersionCompareOp::Gte,
660
                        version: SuricataVersion {
661
                            major: 7,
662
                            minor: 1,
663
                            patch: 2,
664
                        },
665
                    },
666
                    RuleRequireVersion {
667
                        op: VersionCompareOp::Lt,
668
                        version: SuricataVersion {
669
                            major: 8,
670
                            minor: 0,
671
                            patch: 0,
672
                        }
673
                    }
674
                ]],
675
                unknown: vec![],
676
            }
677
        );
678
    }
679
680
    #[test]
681
    fn test_check_requires() {
682
        // Have 7.0.4, require >= 8.
683
        let suricata_version = SuricataVersion::new(7, 0, 4);
684
        let requires = parse_requires("version >= 8").unwrap();
685
        assert_eq!(
686
            check_requires(&requires, &suricata_version, false),
687
            Err(RequiresError::VersionLt(SuricataVersion {
688
                major: 8,
689
                minor: 0,
690
                patch: 0,
691
            })),
692
        );
693
694
        // Have 7.0.4, require 7.0.3.
695
        let suricata_version = SuricataVersion::new(7, 0, 4);
696
        let requires = parse_requires("version >= 7.0.3").unwrap();
697
        assert_eq!(check_requires(&requires, &suricata_version, false), Ok(()));
698
699
        // Have 8.0.0, require >= 7.0.0 and < 8.0
700
        let suricata_version = SuricataVersion::new(8, 0, 0);
701
        let requires = parse_requires("version >= 7.0.0 < 8").unwrap();
702
        assert_eq!(
703
            check_requires(&requires, &suricata_version, false),
704
            Err(RequiresError::VersionGt)
705
        );
706
707
        // Have 8.0.0, require >= 7.0.0 and < 9.0
708
        let suricata_version = SuricataVersion::new(8, 0, 0);
709
        let requires = parse_requires("version >= 7.0.0 < 9").unwrap();
710
        assert_eq!(check_requires(&requires, &suricata_version, false), Ok(()));
711
712
        // Require feature foobar.
713
        let suricata_version = SuricataVersion::new(8, 0, 0);
714
        let requires = parse_requires("feature foobar").unwrap();
715
        assert_eq!(
716
            check_requires(&requires, &suricata_version, false),
717
            Err(RequiresError::MissingFeature("foobar".to_string()))
718
        );
719
720
        // Require feature foobar, but this time we have the feature.
721
        let suricata_version = SuricataVersion::new(8, 0, 0);
722
        let requires = parse_requires("feature true_foobar").unwrap();
723
        assert_eq!(check_requires(&requires, &suricata_version, false), Ok(()));
724
725
        let suricata_version = SuricataVersion::new(8, 0, 1);
726
        let requires = parse_requires("version >= 7.0.3 < 8").unwrap();
727
        assert!(check_requires(&requires, &suricata_version, false).is_err());
728
729
        let suricata_version = SuricataVersion::new(7, 0, 1);
730
        let requires = parse_requires("version >= 7.0.3 < 8").unwrap();
731
        assert!(check_requires(&requires, &suricata_version, false).is_err());
732
733
        let suricata_version = SuricataVersion::new(7, 0, 3);
734
        let requires = parse_requires("version >= 7.0.3 < 8").unwrap();
735
        assert!(check_requires(&requires, &suricata_version, false).is_ok());
736
737
        let suricata_version = SuricataVersion::new(8, 0, 3);
738
        let requires = parse_requires("version >= 7.0.3 < 8 | >= 8.0.3").unwrap();
739
        assert!(check_requires(&requires, &suricata_version, false).is_ok());
740
741
        let suricata_version = SuricataVersion::new(8, 0, 2);
742
        let requires = parse_requires("version >= 7.0.3 < 8 | >= 8.0.3").unwrap();
743
        assert!(check_requires(&requires, &suricata_version, false).is_err());
744
745
        let suricata_version = SuricataVersion::new(7, 0, 2);
746
        let requires = parse_requires("version >= 7.0.3 < 8 | >= 8.0.3").unwrap();
747
        assert!(check_requires(&requires, &suricata_version, false).is_err());
748
749
        let suricata_version = SuricataVersion::new(7, 0, 3);
750
        let requires = parse_requires("version >= 7.0.3 < 8 | >= 8.0.3").unwrap();
751
        assert!(check_requires(&requires, &suricata_version, false).is_ok());
752
753
        // Example of something that requires a fix/feature that was
754
        // implemented in 7.0.5, 8.0.4, 9.0.3.
755
        let requires = parse_requires("version >= 7.0.5 < 8 | >= 8.0.4 < 9 | >= 9.0.3").unwrap();
756
        assert!(check_requires(&requires, &SuricataVersion::new(6, 0, 0), false).is_err());
757
        assert!(check_requires(&requires, &SuricataVersion::new(7, 0, 4), false).is_err());
758
        assert!(check_requires(&requires, &SuricataVersion::new(7, 0, 5), false).is_ok());
759
        assert!(check_requires(&requires, &SuricataVersion::new(8, 0, 3), false).is_err());
760
        assert!(check_requires(&requires, &SuricataVersion::new(8, 0, 4), false).is_ok());
761
        assert!(check_requires(&requires, &SuricataVersion::new(9, 0, 2), false).is_err());
762
        assert!(check_requires(&requires, &SuricataVersion::new(9, 0, 3), false).is_ok());
763
        assert!(check_requires(&requires, &SuricataVersion::new(10, 0, 0), false).is_ok());
764
765
        let requires = parse_requires("version >= 8 < 9").unwrap();
766
        assert!(check_requires(&requires, &SuricataVersion::new(6, 0, 0), false).is_err());
767
        assert!(check_requires(&requires, &SuricataVersion::new(7, 0, 0), false).is_err());
768
        assert!(check_requires(&requires, &SuricataVersion::new(8, 0, 0), false).is_ok());
769
        assert!(check_requires(&requires, &SuricataVersion::new(9, 0, 0), false).is_err());
770
771
        // Unknown keyword.
772
        let requires = parse_requires("feature true_lua, foo bar, version >= 7.0.3").unwrap();
773
        assert_eq!(
774
            requires,
775
            Requires {
776
                features: vec!["true_lua".to_string()],
777
                version: vec![vec![RuleRequireVersion {
778
                    op: VersionCompareOp::Gte,
779
                    version: SuricataVersion {
780
                        major: 7,
781
                        minor: 0,
782
                        patch: 3,
783
                    }
784
                }]],
785
                unknown: vec!["foo bar".to_string()],
786
            }
787
        );
788
789
        // This should not pass the requires check as it contains an
790
        // unknown requires keyword.
791
        //check_requires(&requires, &SuricataVersion::new(8, 0, 0)).unwrap();
792
        assert!(check_requires(&requires, &SuricataVersion::new(8, 0, 0), false).is_err());
793
    }
794
795
    #[test]
796
    fn test_parse_version_expression() {
797
        let version_str = ">= 7.0.3 < 8 | >= 8.0.3";
798
        let (rest, versions) = parse_version_expression(version_str).unwrap();
799
        assert!(rest.is_empty());
800
        assert_eq!(
801
            versions,
802
            vec![
803
                vec![
804
                    RuleRequireVersion {
805
                        op: VersionCompareOp::Gte,
806
                        version: SuricataVersion {
807
                            major: 7,
808
                            minor: 0,
809
                            patch: 3,
810
                        }
811
                    },
812
                    RuleRequireVersion {
813
                        op: VersionCompareOp::Lt,
814
                        version: SuricataVersion {
815
                            major: 8,
816
                            minor: 0,
817
                            patch: 0,
818
                        }
819
                    },
820
                ],
821
                vec![RuleRequireVersion {
822
                    op: VersionCompareOp::Gte,
823
                    version: SuricataVersion {
824
                        major: 8,
825
                        minor: 0,
826
                        patch: 3,
827
                    }
828
                },],
829
            ]
830
        );
831
    }
832
833
    #[test]
834
    fn test_requires_keyword() {
835
        let requires = parse_requires("keyword true_bar").unwrap();
836
        assert!(check_requires(&requires, &SuricataVersion::new(8, 0, 0), false).is_err());
837
    }
838
}