diff --git a/doc/userguide/rules/dhcp-keywords.rst b/doc/userguide/rules/dhcp-keywords.rst index 05675a947e73..14d7365aa271 100644 --- a/doc/userguide/rules/dhcp-keywords.rst +++ b/doc/userguide/rules/dhcp-keywords.rst @@ -6,6 +6,8 @@ dhcp.leasetime DHCP lease time (integer). +dhcp.leasetime uses an :ref:`unsigned 64-bits integer `. + Syntax:: dhcp.leasetime:[op] @@ -25,6 +27,8 @@ dhcp.rebinding_time DHCP rebinding time (integer). +dhcp.rebinding_time uses an :ref:`unsigned 64-bits integer `. + Syntax:: dhcp.rebinding_time:[op] @@ -44,6 +48,8 @@ dhcp.renewal_time DHCP renewal time (integer). +dhcp.renewal_time uses an :ref:`unsigned 64-bits integer `. + Syntax:: dhcp.renewal_time:[op] diff --git a/doc/userguide/rules/file-keywords.rst b/doc/userguide/rules/file-keywords.rst index c708ee746c0d..f9ef85df42dd 100644 --- a/doc/userguide/rules/file-keywords.rst +++ b/doc/userguide/rules/file-keywords.rst @@ -244,6 +244,8 @@ filesize Match on the size of the file as it is being transferred. +filesize uses an :ref:`unsigned 64-bits integer `. + Syntax:: filesize:; diff --git a/doc/userguide/rules/flow-keywords.rst b/doc/userguide/rules/flow-keywords.rst index 6d451ce82aab..a150efda62ac 100644 --- a/doc/userguide/rules/flow-keywords.rst +++ b/doc/userguide/rules/flow-keywords.rst @@ -292,6 +292,8 @@ flow.age Flow age in seconds (integer) This keyword does not wait for the end of the flow, but will be checked at each packet. +flow.age uses an :ref:`unsigned 32-bits integer `. + Syntax:: flow.age: [op] @@ -314,6 +316,8 @@ flow.pkts_toclient Flow number of packets to client (integer) This keyword does not wait for the end of the flow, but will be checked at each packet. +flow.pkts_toclient uses an :ref:`unsigned 32-bits integer `. + Syntax:: flow.pkts_toclient: [op] @@ -334,6 +338,8 @@ flow.pkts_toserver Flow number of packets to server (integer) This keyword does not wait for the end of the flow, but will be checked at each packet. +flow.pkts_toserver uses an :ref:`unsigned 32-bits integer `. + Syntax:: flow.pkts_toserver: [op] @@ -354,6 +360,8 @@ flow.bytes_toclient Flow number of bytes to client (integer) This keyword does not wait for the end of the flow, but will be checked at each packet. +flow.bytes_toclient uses an :ref:`unsigned 64-bits integer `. + Syntax:: flow.bytes_toclient: [op] @@ -374,6 +382,8 @@ flow.bytes_toserver Flow number of bytes to server (integer) This keyword does not wait for the end of the flow, but will be checked at each packet. +flow.bytes_toserver uses an :ref:`unsigned 64-bits integer `. + Syntax:: flow.bytes_toserver: [op] diff --git a/doc/userguide/rules/header-keywords.rst b/doc/userguide/rules/header-keywords.rst index 36d1437647f3..efe4a1e5ac6c 100644 --- a/doc/userguide/rules/header-keywords.rst +++ b/doc/userguide/rules/header-keywords.rst @@ -15,6 +15,8 @@ For example:: ttl:10; +ttl uses an :ref:`unsigned 8-bits integer `. + At the end of the ttl keyword you can enter the value on which you want to match. The Time-to-live value determines the maximal amount of time a packet can be in the Internet-system. If this field is set @@ -431,6 +433,8 @@ tcp.mss Match on the TCP MSS option value. Will not match if the option is not present. +tcp.mss uses an :ref:`unsigned 16-bits integer `. + The format of the keyword:: tcp.mss:-; @@ -506,6 +510,8 @@ messages. The different messages are distinct by different names, but more important by numeric values. For more information see the table with message-types and codes. +itype uses an :ref:`unsigned 8-bits integer `. + The format of the itype keyword:: itype:min<>max; @@ -565,6 +571,8 @@ code of a ICMP message clarifies the message. Together with the ICMP-type it indicates with what kind of problem you are dealing with. A code has a different purpose with every ICMP-type. +icode uses an :ref:`unsigned 8-bits integer `. + The format of the icode keyword:: icode:min<>max; @@ -719,6 +727,8 @@ icmpv6.mtu Match on the ICMPv6 MTU optional value. Will not match if the MTU is not present. +icmpv6.mtu uses an :ref:`unsigned 32-bits integer `. + The format of the keyword:: icmpv6.mtu:-; diff --git a/doc/userguide/rules/http-keywords.rst b/doc/userguide/rules/http-keywords.rst index ba0d7621f339..2a826e358b64 100644 --- a/doc/userguide/rules/http-keywords.rst +++ b/doc/userguide/rules/http-keywords.rst @@ -237,6 +237,8 @@ The ``urilen`` keyword is used to match on the length of the request URI. It is possible to use the ``<`` and ``>`` operators, which indicate respectively *smaller than* and *larger than*. +urilen uses an :ref:`unsigned 64-bits integer `. + The format of ``urilen`` is:: urilen:3; diff --git a/doc/userguide/rules/http2-keywords.rst b/doc/userguide/rules/http2-keywords.rst index 1ad83554c6ef..6fe75a33c06d 100644 --- a/doc/userguide/rules/http2-keywords.rst +++ b/doc/userguide/rules/http2-keywords.rst @@ -31,6 +31,8 @@ http2.priority Match on the value of the HTTP2 priority field present in a PRIORITY or HEADERS frame. +http2.priority uses an :ref:`unsigned 8-bits integer `. + This keyword takes a numeric argument after a colon and supports additional qualifiers, such as: * ``>`` (greater than) @@ -49,6 +51,8 @@ http2.window Match on the value of the HTTP2 value field present in a WINDOWUPDATE frame. +http2.window uses an :ref:`unsigned 32-bits integer `. + This keyword takes a numeric argument after a colon and supports additional qualifiers, such as: * ``>`` (greater than) @@ -68,6 +72,8 @@ Match on the size of the HTTP2 Dynamic Headers Table. More information on the protocol can be found here: ``_ +http2.size_update uses an :ref:`unsigned 64-bits integer `. + This keyword takes a numeric argument after a colon and supports additional qualifiers, such as: * ``>`` (greater than) diff --git a/doc/userguide/rules/ike-keywords.rst b/doc/userguide/rules/ike-keywords.rst index e0d9557bc306..f892034af63e 100644 --- a/doc/userguide/rules/ike-keywords.rst +++ b/doc/userguide/rules/ike-keywords.rst @@ -61,6 +61,8 @@ ike.exchtype Match on the value of the Exchange Type. +ike.exchtype uses an :ref:`unsigned 8-bits integer `. + This keyword takes a numeric argument after a colon and supports additional qualifiers, such as: * ``>`` (greater than) @@ -106,6 +108,8 @@ ike.key_exchange_payload_length Match against the length of the public key exchange payload (e.g. Diffie-Hellman) of the server or client. +ike.key_exchange_payload_length uses an :ref:`unsigned 32-bits integer `. + This keyword takes a numeric argument after a colon and supports additional qualifiers, such as: * ``>`` (greater than) @@ -138,6 +142,8 @@ ike.nonce_payload_length Match against the length of the nonce of the server or client. +ike.nonce_payload_length uses an :ref:`unsigned 32-bits integer `. + This keyword takes a numeric argument after a colon and supports additional qualifiers, such as: * ``>`` (greater than) diff --git a/doc/userguide/rules/index.rst b/doc/userguide/rules/index.rst index e174c6787bc5..2450f4486be9 100644 --- a/doc/userguide/rules/index.rst +++ b/doc/userguide/rules/index.rst @@ -7,6 +7,7 @@ Suricata Rules meta header-keywords payload-keywords + integer-keywords transforms prefilter-keywords flow-keywords diff --git a/doc/userguide/rules/integer-keywords.rst b/doc/userguide/rules/integer-keywords.rst new file mode 100644 index 000000000000..d3efc32c7761 --- /dev/null +++ b/doc/userguide/rules/integer-keywords.rst @@ -0,0 +1,74 @@ +.. _rules-integer-keywords: + +Integer Keywords +================ + +Many keywords will match on an integer value on the network traffic. +These are unsigned integers that can be 8, 16, 32 or 64 bits. + +Simple example:: + + bsize:integer value; + +The integer value can be written as base-10 like ``100`` or as +an hexadecimal value like ``0x64``. + +The most direct exemple is to match for equality, but there are +different modes. + +Comparison modes +---------------- + +Integers can be matched for +* Equality +* Inequality +* Greater than +* Less than +* Range +* Negated range +* Bitmask +* Negated Bitmask + +Comparisons are strict by default. +That means a range between 1 and 4 will match 2 and 3, but neither 1 nor 4. + +Examples:: + + bsize:integer value; # equality + bsize:=integer value; # equality + bsize:!integer value; # inequality + bsize:!=integer value; # inequality + bsize:>integer value; # greater than + bsize:>=integer value; # greater than or equal + bsize:`. + The format of the keyword:: mqtt.protocol_version:-; diff --git a/doc/userguide/rules/payload-keywords.rst b/doc/userguide/rules/payload-keywords.rst index 9a609a217f04..ae9c2b231344 100644 --- a/doc/userguide/rules/payload-keywords.rst +++ b/doc/userguide/rules/payload-keywords.rst @@ -280,6 +280,8 @@ bsize With the ``bsize`` keyword, you can match on the length of the buffer. This adds precision to the content match, previously this could have been done with ``isdataat``. +bsize uses an :ref:`unsigned 64-bits integer `. + An optional operator can be specified; if no operator is present, the operator will default to '='. When a relational operator is used, e.g., '<', '>' or '<>' (range), the bsize value will be compared using the relational operator. Ranges are inclusive. @@ -336,6 +338,8 @@ This may be convenient in detecting buffer overflows. dsize cannot be used when using app/streamlayer protocol keywords (i.e. http.uri) +dsize uses an :ref:`unsigned 16-bits integer `. + Format:: dsize:[<>!]number; || dsize:min<>max; diff --git a/doc/userguide/rules/rfb-keywords.rst b/doc/userguide/rules/rfb-keywords.rst index 628b3d85c563..756fc07432ae 100644 --- a/doc/userguide/rules/rfb-keywords.rst +++ b/doc/userguide/rules/rfb-keywords.rst @@ -36,6 +36,8 @@ rfb.sectype Match on the value of the RFB security type field, e.g. ``2`` for VNC challenge-response authentication, ``0`` for no authentication, and ``30`` for Apple's custom Remote Desktop authentication. +rfb.sectype uses an :ref:`unsigned 32-bits integer `. + This keyword takes a numeric argument after a colon and supports additional qualifiers, such as: * ``>`` (greater than) diff --git a/doc/userguide/rules/tls-keywords.rst b/doc/userguide/rules/tls-keywords.rst index dc28c97cd583..2aaa880ce297 100644 --- a/doc/userguide/rules/tls-keywords.rst +++ b/doc/userguide/rules/tls-keywords.rst @@ -284,6 +284,8 @@ tls.cert_chain_len Matches on the TLS certificate chain length. +tls.cert_chain_len uses an :ref:`unsigned 32-bits integer `. + tls.cert_chain_len supports `<, >, <>, !` and using an exact value. Example:: diff --git a/rust/derive/src/lib.rs b/rust/derive/src/lib.rs index a2b7a6ad0442..a36f19390c0c 100644 --- a/rust/derive/src/lib.rs +++ b/rust/derive/src/lib.rs @@ -23,6 +23,7 @@ use proc_macro::TokenStream; mod applayerevent; mod applayerframetype; +mod stringenum; /// The `AppLayerEvent` derive macro generates a `AppLayerEvent` trait /// implementation for enums that define AppLayerEvents. @@ -50,3 +51,18 @@ pub fn derive_app_layer_event(input: TokenStream) -> TokenStream { pub fn derive_app_layer_frame_type(input: TokenStream) -> TokenStream { applayerframetype::derive_app_layer_frame_type(input) } + +#[proc_macro_derive(EnumStringU8, attributes(name))] +pub fn derive_enum_string_u8(input: TokenStream) -> TokenStream { + stringenum::derive_enum_string::(input, "u8") +} + +#[proc_macro_derive(EnumStringU16, attributes(name))] +pub fn derive_enum_string_u16(input: TokenStream) -> TokenStream { + stringenum::derive_enum_string::(input, "u16") +} + +#[proc_macro_derive(EnumStringU32, attributes(name))] +pub fn derive_enum_string_u32(input: TokenStream) -> TokenStream { + stringenum::derive_enum_string::(input, "u32") +} diff --git a/rust/derive/src/stringenum.rs b/rust/derive/src/stringenum.rs new file mode 100644 index 000000000000..0f0905e0116f --- /dev/null +++ b/rust/derive/src/stringenum.rs @@ -0,0 +1,96 @@ +/* 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. + */ + +extern crate proc_macro; +use super::applayerevent::transform_name; +use proc_macro::TokenStream; +use quote::quote; +use syn::{self, parse_macro_input, DeriveInput}; +use std::str::FromStr; + +pub fn derive_enum_string(input: TokenStream, ustr: &str) -> TokenStream where ::Err: std::fmt::Display { + let input = parse_macro_input!(input as DeriveInput); + let name = input.ident; + let mut values = Vec::new(); + let mut names = Vec::new(); + let mut fields = Vec::new(); + + if let syn::Data::Enum(ref data) = input.data { + for v in (&data.variants).into_iter() { + if let Some((_, val)) = &v.discriminant { + let fname = transform_name(&v.ident.to_string()); + names.push(fname); + fields.push(v.ident.clone()); + if let syn::Expr::Lit(l) = val { + if let syn::Lit::Int(li) = &l.lit { + if let Ok(value) = li.base10_parse::() { + values.push(value); + } else { + panic!("EnumString requires explicit {}", ustr); + } + } else { + panic!("EnumString requires explicit literal integer"); + } + } else { + panic!("EnumString requires explicit literal"); + } + } else { + panic!("EnumString requires explicit values"); + } + } + } else { + panic!("EnumString can only be derived for enums"); + } + + let is_suricata = std::env::var("CARGO_PKG_NAME").map(|var| var == "suricata").unwrap_or(false); + let crate_id = if is_suricata { + syn::Ident::new("crate", proc_macro2::Span::call_site()) + } else { + syn::Ident::new("suricata", proc_macro2::Span::call_site()) + }; + + let utype_str = syn::Ident::new(ustr, proc_macro2::Span::call_site()); + + let expanded = quote! { + impl #crate_id::detect::Enum<#utype_str> for #name { + fn from_u(v: #utype_str) -> Option { + match v { + #( #values => Some(#name::#fields) ,)* + _ => None, + } + } + fn into_u(self) -> #utype_str { + match self { + #( #name::#fields => #values ,)* + } + } + fn to_str(&self) -> &'static str { + match *self { + #( #name::#fields => #names ,)* + } + } + fn from_str(s: &str) -> Option { + match s { + #( #names => Some(#name::#fields) ,)* + _ => None + } + } + } + }; + + proc_macro::TokenStream::from(expanded) +} diff --git a/rust/src/detect/mod.rs b/rust/src/detect/mod.rs index d33c9ae7fabf..5f048a8e5722 100644 --- a/rust/src/detect/mod.rs +++ b/rust/src/detect/mod.rs @@ -25,3 +25,46 @@ pub mod stream_size; pub mod uint; pub mod uri; pub mod requires; + +/// Enum trait that will be implemented on enums that +/// derive StringEnum. +pub trait Enum { + /// Return the enum variant of the given numeric value. + fn from_u(v: T) -> Option where Self: Sized; + + /// Convert the enum variant to the numeric value. + fn into_u(self) -> T; + + /// Return the string for logging the enum value. + fn to_str(&self) -> &'static str; + + /// Get an enum variant from parsing a string. + fn from_str(s: &str) -> Option where Self: Sized; +} + +#[cfg(test)] +mod test { + use super::*; + use suricata_derive::EnumStringU8; + + #[derive(Clone, Debug, PartialEq, EnumStringU8)] + #[repr(u8)] + pub enum TestEnum { + Zero = 0, + BestValueEver = 42, + } + + #[test] + fn test_enum_string_u8() { + assert_eq!(TestEnum::from_u(0), Some(TestEnum::Zero)); + assert_eq!(TestEnum::from_u(1), None); + assert_eq!(TestEnum::from_u(42), Some(TestEnum::BestValueEver)); + assert_eq!(TestEnum::Zero.into_u(), 0); + assert_eq!(TestEnum::BestValueEver.into_u(), 42); + assert_eq!(TestEnum::Zero.to_str(), "zero"); + assert_eq!(TestEnum::BestValueEver.to_str(), "best_value_ever"); + assert_eq!(TestEnum::from_str("zero"), Some(TestEnum::Zero)); + assert_eq!(TestEnum::from_str("nope"), None); + assert_eq!(TestEnum::from_str("best_value_ever"), Some(TestEnum::BestValueEver)); + } +} diff --git a/rust/src/detect/uint.rs b/rust/src/detect/uint.rs index 8c758e3a5d69..f22869d86117 100644 --- a/rust/src/detect/uint.rs +++ b/rust/src/detect/uint.rs @@ -17,12 +17,14 @@ use nom7::branch::alt; use nom7::bytes::complete::{is_a, tag, tag_no_case, take_while}; -use nom7::character::complete::digit1; +use nom7::character::complete::{char, digit1, hex_digit1}; use nom7::combinator::{all_consuming, map_opt, opt, value, verify}; use nom7::error::{make_error, ErrorKind}; use nom7::Err; use nom7::IResult; +use super::Enum; + use std::ffi::CStr; #[derive(PartialEq, Eq, Clone, Debug)] @@ -35,6 +37,9 @@ pub enum DetectUintMode { DetectUintModeGte, DetectUintModeRange, DetectUintModeNe, + DetectUintModeNegRg, + DetectUintModeBitmask, + DetectUintModeNegBitmask, } #[derive(Debug)] @@ -45,6 +50,29 @@ pub struct DetectUintData { pub mode: DetectUintMode, } +/// Parses a string for detection with integers, using enumeration strings +/// +/// Needs to specify T1 the integer type (like u8) +/// And the Enumeration for the stringer. +/// Will try to parse numerical value first, as any integer detection keyword +/// And if this fails, will resort to using the enumeration strings. +/// +/// Returns Some DetectUintData on success, None on failure +pub fn detect_parse_uint_enum>(s: &str) -> Option> { + if let Ok((_, ctx)) = detect_parse_uint::(s) { + return Some(ctx); + } + if let Some(enum_val) = T2::from_str(s) { + let ctx = DetectUintData:: { + arg1: enum_val.into_u(), + arg2: T1::min_value(), + mode: DetectUintMode::DetectUintModeEqual, + }; + return Some(ctx); + } + return None; +} + pub trait DetectIntType: std::str::FromStr + std::cmp::PartialOrd @@ -73,8 +101,25 @@ pub fn detect_parse_uint_unit(i: &str) -> IResult<&str, u64> { return Ok((i, unit)); } +pub fn detect_parse_uint_value_hex(i: &str) -> IResult<&str, T> { + let (i, _) = tag("0x")(i)?; + let (i, arg1s) = hex_digit1(i)?; + match T::from_str_radix(arg1s, 16) { + Ok(arg1) => Ok((i, arg1)), + _ => Err(Err::Error(make_error(i, ErrorKind::Verify))), + } +} + +pub fn detect_parse_uint_value(i: &str) -> IResult<&str, T> { + let (i, arg1) = alt(( + detect_parse_uint_value_hex, + map_opt(digit1, |s: &str| s.parse::().ok()), + ))(i)?; + Ok((i, arg1)) +} + pub fn detect_parse_uint_with_unit(i: &str) -> IResult<&str, T> { - let (i, arg1) = map_opt(digit1, |s: &str| s.parse::().ok())(i)?; + let (i, arg1) = detect_parse_uint_value::(i)?; let (i, unit) = opt(detect_parse_uint_unit)(i)?; if arg1 >= T::one() { if let Some(u) = unit { @@ -107,19 +152,52 @@ pub fn detect_parse_uint_start_equal( pub fn detect_parse_uint_start_interval( i: &str, ) -> IResult<&str, DetectUintData> { - let (i, arg1) = map_opt(digit1, |s: &str| s.parse::().ok())(i)?; + let (i, neg) = opt(char('!'))(i)?; + let (i, arg1) = detect_parse_uint_value(i)?; let (i, _) = opt(is_a(" "))(i)?; let (i, _) = alt((tag("-"), tag("<>")))(i)?; let (i, _) = opt(is_a(" "))(i)?; - let (i, arg2) = verify(map_opt(digit1, |s: &str| s.parse::().ok()), |x| { + let (i, arg2) = verify(detect_parse_uint_value, |x| { x > &arg1 && *x - arg1 > T::one() })(i)?; + let mode = if neg.is_some() { + DetectUintMode::DetectUintModeNegRg + } else { + DetectUintMode::DetectUintModeRange + }; + Ok(( + i, + DetectUintData { + arg1, + arg2, + mode, + }, + )) +} + +pub fn detect_parse_uint_bitmask( + i: &str, +) -> IResult<&str, DetectUintData> { + let (i, _) = opt(is_a(" "))(i)?; + let (i, _) = tag("&")(i)?; + let (i, _) = opt(is_a(" "))(i)?; + let (i, arg1) = detect_parse_uint_value(i)?; + let (i, _) = opt(is_a(" "))(i)?; + let (i, neg) = opt(tag("!"))(i)?; + let (i, _) = tag("=")(i)?; + let (i, _) = opt(is_a(" "))(i)?; + let (i, arg2) = detect_parse_uint_value(i)?; + let mode = if neg.is_none() { + DetectUintMode::DetectUintModeBitmask + } else { + DetectUintMode::DetectUintModeNegBitmask + }; Ok(( i, DetectUintData { arg1, arg2, - mode: DetectUintMode::DetectUintModeRange, + mode, }, )) } @@ -127,21 +205,27 @@ pub fn detect_parse_uint_start_interval( fn detect_parse_uint_start_interval_inclusive( i: &str, ) -> IResult<&str, DetectUintData> { - let (i, arg1) = verify(map_opt(digit1, |s: &str| s.parse::().ok()), |x| { + let (i, neg) = opt(char('!'))(i)?; + let (i, arg1) = verify(detect_parse_uint_value::, |x| { *x > T::min_value() })(i)?; let (i, _) = opt(is_a(" "))(i)?; let (i, _) = alt((tag("-"), tag("<>")))(i)?; let (i, _) = opt(is_a(" "))(i)?; - let (i, arg2) = verify(map_opt(digit1, |s: &str| s.parse::().ok()), |x| { + let (i, arg2) = verify(detect_parse_uint_value::, |x| { *x > arg1 && *x < T::max_value() })(i)?; + let mode = if neg.is_some() { + DetectUintMode::DetectUintModeNegRg + } else { + DetectUintMode::DetectUintModeRange + }; Ok(( i, DetectUintData { arg1: arg1 - T::one(), arg2: arg2 + T::one(), - mode: DetectUintMode::DetectUintModeRange, + mode, }, )) } @@ -162,7 +246,7 @@ pub fn detect_parse_uint_mode(i: &str) -> IResult<&str, DetectUintMode> { fn detect_parse_uint_start_symbol(i: &str) -> IResult<&str, DetectUintData> { let (i, mode) = detect_parse_uint_mode(i)?; let (i, _) = opt(is_a(" "))(i)?; - let (i, arg1) = map_opt(digit1, |s: &str| s.parse::().ok())(i)?; + let (i, arg1) = detect_parse_uint_value(i)?; match mode { DetectUintMode::DetectUintModeNe => {} @@ -238,6 +322,21 @@ pub fn detect_match_uint(x: &DetectUintData, val: T) -> boo return true; } } + DetectUintMode::DetectUintModeNegRg => { + if val <= x.arg1 || val >= x.arg2 { + return true; + } + } + DetectUintMode::DetectUintModeBitmask => { + if val & x.arg1 == x.arg2 { + return true; + } + } + DetectUintMode::DetectUintModeNegBitmask => { + if val & x.arg1 != x.arg2 { + return true; + } + } } return false; } @@ -245,6 +344,7 @@ pub fn detect_match_uint(x: &DetectUintData, val: T) -> boo pub fn detect_parse_uint_notending(i: &str) -> IResult<&str, DetectUintData> { let (i, _) = opt(is_a(" "))(i)?; let (i, uint) = alt(( + detect_parse_uint_bitmask, detect_parse_uint_start_interval, detect_parse_uint_start_equal, detect_parse_uint_start_symbol, @@ -407,6 +507,64 @@ pub unsafe extern "C" fn rs_detect_u16_free(ctx: &mut DetectUintData) { mod tests { use super::*; + use suricata_derive::EnumStringU8; + + #[derive(Clone, Debug, PartialEq, EnumStringU8)] + #[repr(u8)] + pub enum TestEnum { + Zero = 0, + BestValueEver = 42, + } + + #[test] + fn test_detect_parse_uint_enum() { + let ctx = detect_parse_uint_enum::("best_value_ever").unwrap(); + assert_eq!(ctx.arg1, 42); + assert_eq!(ctx.mode, DetectUintMode::DetectUintModeEqual); + + let ctx = detect_parse_uint_enum::(">1").unwrap(); + assert_eq!(ctx.arg1, 1); + assert_eq!(ctx.mode, DetectUintMode::DetectUintModeGt); + } + + #[test] + fn test_parse_uint_bitmask() { + let (_, val) = detect_parse_uint::("&0x40!=0").unwrap(); + assert_eq!(val.arg1, 0x40); + assert_eq!(val.arg2, 0); + assert_eq!(val.mode, DetectUintMode::DetectUintModeNegBitmask); + assert!(!detect_match_uint(&val, 0xBF)); + assert!(detect_match_uint(&val, 0x40)); + let (_, val) = detect_parse_uint::("&0xc0=0x80").unwrap(); + assert_eq!(val.arg1, 0xc0); + assert_eq!(val.arg2, 0x80); + assert_eq!(val.mode, DetectUintMode::DetectUintModeBitmask); + assert!(detect_match_uint(&val, 0x80)); + assert!(!detect_match_uint(&val, 0x40)); + assert!(!detect_match_uint(&val, 0xc0)); + } + #[test] + fn test_parse_uint_hex() { + let (_, val) = detect_parse_uint::("0x100").unwrap(); + assert_eq!(val.arg1, 0x100); + let (_, val) = detect_parse_uint::("0xFF").unwrap(); + assert_eq!(val.arg1, 255); + let (_, val) = detect_parse_uint::("0xff").unwrap(); + assert_eq!(val.arg1, 255); + } + + #[test] + fn test_parse_uint_negated_range() { + let (_, val) = detect_parse_uint::("!1-6").unwrap(); + assert_eq!(val.arg1, 1); + assert_eq!(val.arg2, 6); + assert_eq!(val.mode, DetectUintMode::DetectUintModeNegRg); + assert!(detect_match_uint(&val, 1)); + assert!(!detect_match_uint(&val, 2)); + assert!(!detect_match_uint(&val, 5)); + assert!(detect_match_uint(&val, 6)); + } + #[test] fn test_parse_uint_unit() { let (_, val) = detect_parse_uint::(" 2kb").unwrap();