/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 | | } |