diff --git a/doc/userguide/rules/dnp3-keywords.rst b/doc/userguide/rules/dnp3-keywords.rst index 36f5afd699..4070313910 100644 --- a/doc/userguide/rules/dnp3-keywords.rst +++ b/doc/userguide/rules/dnp3-keywords.rst @@ -68,12 +68,14 @@ dnp3_ind This keyword matches on the DNP3 internal indicator flags in the response application header. +dnp3_ind uses :ref:`unsigned 16-bit integer ` with bitmasks. + Syntax ~~~~~~ :: - dnp3_ind:{,...} + dnp3_ind:[*+-=]{,...} Where flag is the name of the internal indicator: @@ -96,8 +98,7 @@ Where flag is the name of the internal indicator: * reserved_1 This keyword will match of any of the flags listed are set. To match -on multiple flags (AND type match), use dnp3_ind for each flag that -must be set. +on multiple flags use prefix ``+``. Examples ~~~~~~~~ diff --git a/doc/userguide/rules/integer-keywords.rst b/doc/userguide/rules/integer-keywords.rst index 81492ca4cd..9d5b582fd4 100644 --- a/doc/userguide/rules/integer-keywords.rst +++ b/doc/userguide/rules/integer-keywords.rst @@ -88,6 +88,16 @@ Some integers on the wire represent multiple bits. Some of these bits have a string/meaning associated to it. Rules can be written using a list (comma-separated) of these strings, where each item can be negated. +This list can be prefixed by a modifier: + +======== =================================== +Modifier Description +======== =================================== +``+`` match on all the bits, plus any others +``*`` match if any of the bits are set +``-`` match if not all the bits are set +``=`` match on all the bits, and only them +======== =================================== There is no right shift for trailing zeros applied here (even if there is one for ``byte_test`` and ``byte_math``). That means a rule with diff --git a/doc/userguide/rules/mqtt-keywords.rst b/doc/userguide/rules/mqtt-keywords.rst index d82471f70e..1a41b74032 100644 --- a/doc/userguide/rules/mqtt-keywords.rst +++ b/doc/userguide/rules/mqtt-keywords.rst @@ -62,7 +62,7 @@ mqtt.flags Match on a combination of MQTT header flags, separated by commas (``,``). Flags may be prefixed by ``!`` to indicate negation, i.e. a flag prefixed by ``!`` must `not` be set to match. -mqtt.flags uses an :ref:`unsigned 8-bits integer ` +mqtt.flags uses an :ref:`unsigned 8-bits integer ` with bitmasks. Valid flags are: @@ -146,7 +146,7 @@ mqtt.connect.flags Match on a combination of MQTT CONNECT flags, separated by commas (``,``). Flags may be prefixed by ``!`` to indicate negation, i.e. a flag prefixed by ``!`` must `not` be set to match. -mqtt.connect.flags uses an :ref:`unsigned 8-bits integer ` +mqtt.connect.flags uses an :ref:`unsigned 8-bits integer ` with bitmasks. Valid flags are: diff --git a/doc/userguide/rules/websocket-keywords.rst b/doc/userguide/rules/websocket-keywords.rst index 598bfe99fa..03d43df98f 100644 --- a/doc/userguide/rules/websocket-keywords.rst +++ b/doc/userguide/rules/websocket-keywords.rst @@ -25,7 +25,7 @@ The value can also be a list of strings (comma-separated), where each string is the name of a specific bit like `fin` and `comp`, and can be prefixed by `!` for negation. -websocket.flags uses an :ref:`unsigned 8-bits integer ` +websocket.flags uses an :ref:`unsigned 8-bits integer ` with bitmasks. Examples:: diff --git a/rust/src/detect/uint.rs b/rust/src/detect/uint.rs index e7ded334f3..39348e8809 100644 --- a/rust/src/detect/uint.rs +++ b/rust/src/detect/uint.rs @@ -16,11 +16,10 @@ */ use nom7::branch::alt; -use nom7::bytes::complete::{is_a, tag, tag_no_case, take_while}; -use nom7::character::complete::{char, digit1, hex_digit1, i32 as nom_i32}; +use nom7::bytes::complete::{is_a, tag, tag_no_case, take, take_while}; +use nom7::character::complete::{anychar, char, digit1, hex_digit1, i32 as nom_i32}; use nom7::combinator::{all_consuming, map_opt, opt, value, verify}; use nom7::error::{make_error, Error, ErrorKind}; -use nom7::multi::many1; use nom7::Err; use nom7::IResult; @@ -348,37 +347,47 @@ struct FlagItem { neg: bool, } -fn parse_flag_list_item>( - s: &str, -) -> IResult<&str, FlagItem> { - let (s, _) = opt(is_a(" "))(s)?; - let (s, neg) = opt(tag("!"))(s)?; - let neg = neg.is_some(); - let (s, vals) = take_while(|c| c != ' ' && c != ',')(s)?; - let value = T2::from_str(vals); - if value.is_none() { - SCLogError!("Bitflag unexpected value {}", vals); - return Err(Err::Error(make_error(s, ErrorKind::Switch))); - } - let value = value.unwrap().into_u(); - let (s, _) = opt(is_a(" ,"))(s)?; - Ok((s, FlagItem { neg, value })) -} - fn parse_flag_list>( - s: &str, + s: &str, singlechar: bool, ) -> IResult<&str, Vec>> { - return many1(parse_flag_list_item::)(s); + let mut r = Vec::new(); + let mut s2 = s; + while !s2.is_empty() { + let (s, _) = opt(is_a(" "))(s2)?; + let (s, neg) = opt(tag("!"))(s)?; + let neg = neg.is_some(); + let (s, vals) = if singlechar { + take(1usize)(s) + } else { + take_while(|c| c != ' ' && c != ',')(s) + }?; + let value = T2::from_str(vals); + if value.is_none() { + SCLogError!("Bitflag unexpected value {}", vals); + return Err(Err::Error(make_error(s, ErrorKind::Switch))); + } + let value = value.unwrap().into_u(); + let (s, _) = if singlechar { + Ok((s, None)) + } else { + opt(is_a(" ,"))(s) + }?; + r.push(FlagItem { neg, value }); + s2 = s; + } + return Ok((s2, r)); } pub fn detect_parse_uint_bitflags>( - s: &str, + s: &str, defmod: DetectBitflagModifier, singlechar: bool, ) -> Option> { if let Ok((_, ctx)) = detect_parse_uint::(s) { return Some(ctx); } // otherwise, try strings for bitmask - if let Ok((rem, l)) = parse_flag_list::(s) { + let (s, modifier) = parse_bitchars_modifier(s, defmod).ok()?; + let (s, _) = take_while::<_, &str, Error<_>>(|c| c == ' ' || c == '\t')(s).ok()?; + if let Ok((rem, l)) = parse_flag_list::(s, singlechar) { if !rem.is_empty() { SCLogError!("junk at the end of bitflags"); return None; @@ -398,16 +407,64 @@ pub fn detect_parse_uint_bitflags>( arg2 |= elem.value; } } - let ctx = DetectUintData:: { - arg1, - arg2, - mode: DetectUintMode::DetectUintModeBitmask, + let ctx = match modifier { + DetectBitflagModifier::Equal => DetectUintData:: { + arg1, + arg2: T1::min_value(), + mode: DetectUintMode::DetectUintModeEqual, + }, + DetectBitflagModifier::Plus => DetectUintData:: { + arg1, + arg2, + mode: DetectUintMode::DetectUintModeBitmask, + }, + DetectBitflagModifier::Any => DetectUintData:: { + arg1, + arg2: T1::min_value(), + mode: DetectUintMode::DetectUintModeNegBitmask, + }, + DetectBitflagModifier::Not => DetectUintData:: { + arg1, + arg2, + mode: DetectUintMode::DetectUintModeNegBitmask, + }, }; return Some(ctx); } return None; } +#[derive(Clone, Debug, PartialEq)] +pub enum DetectBitflagModifier { + Equal, + Plus, + Any, + Not, +} + +fn parse_bitchars_modifier( + s: &str, default: DetectBitflagModifier, +) -> IResult<&str, DetectBitflagModifier> { + let (s1, m) = anychar(s)?; + match m { + '!' => { + // exclamation mark is only accepted for legacy keywords + // excluded for newer to avoid ambiguity with negating single flag + if default == DetectBitflagModifier::Equal { + Ok((s1, DetectBitflagModifier::Not)) + } else { + Ok((s, default)) + } + } + '-' => Ok((s1, DetectBitflagModifier::Not)), + '+' => Ok((s1, DetectBitflagModifier::Plus)), + '*' => Ok((s1, DetectBitflagModifier::Any)), + '=' => Ok((s1, DetectBitflagModifier::Equal)), + // do not consume if not a known modifier: use default equal + _ => Ok((s, default)), + } +} + /// Parses a string for detection with integers, using enumeration strings /// /// Needs to specify T1 the integer type (like u8) diff --git a/rust/src/dnp3/detect.rs b/rust/src/dnp3/detect.rs index b3e77f6d5b..6f079b0732 100644 --- a/rust/src/dnp3/detect.rs +++ b/rust/src/dnp3/detect.rs @@ -15,7 +15,7 @@ * 02110-1301, USA. */ -use crate::detect::uint::{detect_parse_uint_bitflags, DetectUintData}; +use crate::detect::uint::{detect_parse_uint_bitflags, DetectBitflagModifier, DetectUintData}; use std::ffi::CStr; @@ -41,13 +41,17 @@ pub enum Dnp3IndFlag { No_func_code_support = 0x0001, } +fn dnp3_detect_ind_parse(s: &str) -> Option> { + detect_parse_uint_bitflags::(s, DetectBitflagModifier::Any, false) +} + #[no_mangle] pub unsafe extern "C" fn SCDnp3DetectIndParse( ustr: *const std::os::raw::c_char, ) -> *mut DetectUintData { let ft_name: &CStr = CStr::from_ptr(ustr); //unsafe if let Ok(s) = ft_name.to_str() { - if let Some(ctx) = detect_parse_uint_bitflags::(s) { + if let Some(ctx) = dnp3_detect_ind_parse(s) { let boxed = Box::new(ctx); return Box::into_raw(boxed) as *mut _; } @@ -61,24 +65,23 @@ mod test { #[test] fn dnp3_ind_parse() { - let ctx = detect_parse_uint_bitflags::("0").unwrap(); + let ctx = dnp3_detect_ind_parse("0").unwrap(); assert_eq!(ctx.arg1, 0); - let ctx = detect_parse_uint_bitflags::("1").unwrap(); + let ctx = dnp3_detect_ind_parse("1").unwrap(); assert_eq!(ctx.arg1, 1); - let ctx = detect_parse_uint_bitflags::("0x0").unwrap(); + let ctx = dnp3_detect_ind_parse("0x0").unwrap(); assert_eq!(ctx.arg1, 0); - let ctx = detect_parse_uint_bitflags::("0x0000").unwrap(); + let ctx = dnp3_detect_ind_parse("0x0000").unwrap(); assert_eq!(ctx.arg1, 0); - let ctx = detect_parse_uint_bitflags::("0x0001").unwrap(); + let ctx = dnp3_detect_ind_parse("0x0001").unwrap(); assert_eq!(ctx.arg1, 1); - let ctx = detect_parse_uint_bitflags::("0x8421").unwrap(); + let ctx = dnp3_detect_ind_parse("0x8421").unwrap(); assert_eq!(ctx.arg1, 0x8421); - assert!(detect_parse_uint_bitflags::("a").is_none()); - let ctx = detect_parse_uint_bitflags::("all_stations").unwrap(); + assert!(dnp3_detect_ind_parse("a").is_none()); + let ctx = dnp3_detect_ind_parse("all_stations").unwrap(); assert_eq!(ctx.arg1, 0x0100); - let ctx = detect_parse_uint_bitflags::("class_1_events , class_2_events") - .unwrap(); + let ctx = dnp3_detect_ind_parse("class_1_events , class_2_events").unwrap(); assert_eq!(ctx.arg1, 0x600); - assert!(detect_parse_uint_bitflags::("something").is_none()); + assert!(dnp3_detect_ind_parse("something",).is_none()); } } diff --git a/rust/src/mqtt/detect.rs b/rust/src/mqtt/detect.rs index 6f773f1802..d21b3e4280 100644 --- a/rust/src/mqtt/detect.rs +++ b/rust/src/mqtt/detect.rs @@ -20,8 +20,8 @@ use crate::core::{STREAM_TOCLIENT, STREAM_TOSERVER}; use crate::detect::uint::{ detect_match_uint, detect_parse_array_uint_enum, detect_parse_uint_bitflags, - detect_uint_match_at_index, DetectUintArrayData, DetectUintData, SCDetectU8Free, - SCDetectU8Parse, + detect_uint_match_at_index, DetectBitflagModifier, DetectUintArrayData, DetectUintData, + SCDetectU8Free, SCDetectU8Parse, }; use crate::detect::{ helper_keyword_register_multi_buffer, helper_keyword_register_sticky_buffer, @@ -674,12 +674,16 @@ pub enum MqttFlag { Retain = 0x1, } +fn mqtt_flags_parse(s: &str) -> Option> { + detect_parse_uint_bitflags::(s, DetectBitflagModifier::Plus, false) +} + unsafe extern "C" fn mqtt_parse_flags( ustr: *const std::os::raw::c_char, ) -> *mut DetectUintData { let ft_name: &CStr = CStr::from_ptr(ustr); //unsafe if let Ok(s) = ft_name.to_str() { - if let Some(ctx) = detect_parse_uint_bitflags::(s) { + if let Some(ctx) = mqtt_flags_parse(s) { let boxed = Box::new(ctx); return Box::into_raw(boxed) as *mut _; } @@ -753,12 +757,16 @@ pub enum MqttConnFlag { Clean_session = 0x2, } +fn mqtt_connflags_parse(s: &str) -> Option> { + detect_parse_uint_bitflags::(s, DetectBitflagModifier::Plus, false) +} + unsafe extern "C" fn mqtt_parse_conn_flags( ustr: *const std::os::raw::c_char, ) -> *mut DetectUintData { let ft_name: &CStr = CStr::from_ptr(ustr); //unsafe if let Ok(s) = ft_name.to_str() { - if let Some(ctx) = detect_parse_uint_bitflags::(s) { + if let Some(ctx) = mqtt_connflags_parse(s) { let boxed = Box::new(ctx); return Box::into_raw(boxed) as *mut _; } @@ -1191,60 +1199,49 @@ mod test { #[test] fn mqtt_parse_flags() { - let ctx = detect_parse_uint_bitflags::("retain").unwrap(); + let ctx = mqtt_flags_parse("retain").unwrap(); assert_eq!(ctx.arg1, 1); assert_eq!(ctx.arg2, 1); - let ctx = detect_parse_uint_bitflags::("dup").unwrap(); + let ctx = mqtt_flags_parse("dup").unwrap(); assert_eq!(ctx.arg1, 8); assert_eq!(ctx.arg2, 8); - let ctx = detect_parse_uint_bitflags::("retain,dup").unwrap(); + let ctx = mqtt_flags_parse("retain,dup").unwrap(); assert_eq!(ctx.arg1, 8 | 1); assert_eq!(ctx.arg2, 8 | 1); - let ctx = detect_parse_uint_bitflags::("dup, retain").unwrap(); + let ctx = mqtt_flags_parse("dup, retain").unwrap(); assert_eq!(ctx.arg1, 8 | 1); assert_eq!(ctx.arg2, 8 | 1); - let ctx = detect_parse_uint_bitflags::("retain,!dup").unwrap(); + let ctx = mqtt_flags_parse("retain,!dup").unwrap(); assert_eq!(ctx.arg1, 1 | 8); assert_eq!(ctx.arg2, 1); - assert!(detect_parse_uint_bitflags::("ref").is_none()); - assert!(detect_parse_uint_bitflags::("dup,!").is_none()); - assert!(detect_parse_uint_bitflags::("dup,!dup").is_none()); - assert!(detect_parse_uint_bitflags::("!retain,retain").is_none()); + assert!(mqtt_flags_parse("ref").is_none()); + assert!(mqtt_flags_parse("dup,!").is_none()); + assert!(mqtt_flags_parse("dup,!dup",).is_none()); + assert!(mqtt_flags_parse("!retain,retain",).is_none()); } #[test] fn mqtt_parse_conn_flags() { - let ctx = detect_parse_uint_bitflags::("username").unwrap(); + let ctx = mqtt_connflags_parse("username").unwrap(); assert_eq!(ctx.arg1, 0x80); assert_eq!(ctx.arg2, 0x80); - let ctx = detect_parse_uint_bitflags::( - "username,password,will,will_retain,clean_session", - ) - .unwrap(); + let ctx = mqtt_connflags_parse("username,password,will,will_retain,clean_session").unwrap(); assert_eq!(ctx.arg1, 0xE6); assert_eq!(ctx.arg2, 0xE6); - let ctx = detect_parse_uint_bitflags::( - "!username,!password,!will,!will_retain,!clean_session", - ) - .unwrap(); + let ctx = + mqtt_connflags_parse("!username,!password,!will,!will_retain,!clean_session").unwrap(); assert_eq!(ctx.arg1, 0xE6); assert_eq!(ctx.arg2, 0); - let ctx = detect_parse_uint_bitflags::(" username,password").unwrap(); + let ctx = mqtt_connflags_parse(" username,password").unwrap(); assert_eq!(ctx.arg1, 0xC0); assert_eq!(ctx.arg2, 0xC0); - assert!(detect_parse_uint_bitflags::("foobar").is_none()); - assert!(detect_parse_uint_bitflags::("will,!").is_none()); - assert!(detect_parse_uint_bitflags::("").is_none()); - assert!(detect_parse_uint_bitflags::("username, username").is_none()); - assert!(detect_parse_uint_bitflags::("!username, username").is_none()); - assert!( - detect_parse_uint_bitflags::("!username,password,!password") - .is_none() - ); - assert!(detect_parse_uint_bitflags::( - "will, username,password, !will, will" - ) - .is_none()); + assert!(mqtt_connflags_parse("foobar").is_none()); + assert!(mqtt_connflags_parse("will,!").is_none()); + assert!(mqtt_connflags_parse("").is_none()); + assert!(mqtt_connflags_parse("username, username").is_none()); + assert!(mqtt_connflags_parse("!username, username").is_none()); + assert!(mqtt_connflags_parse("!username,password,!password").is_none()); + assert!(mqtt_connflags_parse("will, username,password, !will, will",).is_none()); } #[test] diff --git a/rust/src/websocket/detect.rs b/rust/src/websocket/detect.rs index a65ef3436f..ccbf67fab9 100644 --- a/rust/src/websocket/detect.rs +++ b/rust/src/websocket/detect.rs @@ -18,8 +18,8 @@ use super::websocket::{WebSocketTransaction, ALPROTO_WEBSOCKET}; use crate::core::{STREAM_TOCLIENT, STREAM_TOSERVER}; use crate::detect::uint::{ - detect_parse_uint_bitflags, detect_parse_uint_enum, DetectUintData, SCDetectU32Free, - SCDetectU32Match, SCDetectU32Parse, SCDetectU8Free, SCDetectU8Match, + detect_parse_uint_bitflags, detect_parse_uint_enum, DetectBitflagModifier, DetectUintData, + SCDetectU32Free, SCDetectU32Match, SCDetectU32Parse, SCDetectU8Free, SCDetectU8Match, }; use crate::detect::{ helper_keyword_register_sticky_buffer, SigTableElmtStickyBuffer, SIGMATCH_INFO_BITFLAGS_UINT, @@ -61,7 +61,9 @@ unsafe extern "C" fn websocket_parse_flags( ) -> *mut DetectUintData { let ft_name: &CStr = CStr::from_ptr(ustr); //unsafe if let Ok(s) = ft_name.to_str() { - if let Some(ctx) = detect_parse_uint_bitflags::(s) { + if let Some(ctx) = + detect_parse_uint_bitflags::(s, DetectBitflagModifier::Plus, false) + { let boxed = Box::new(ctx); return Box::into_raw(boxed) as *mut _; }