From 867f5bfa21af8fb0455d4217666f20bd3f198e66 Mon Sep 17 00:00:00 2001 From: Philippe Antoine Date: Wed, 24 Sep 2025 17:35:35 +0200 Subject: [PATCH] detect/integers: generic detect_parse_uint_bitflags Ticket: 6724 This will ease other keywords with bitflags, by having a generic function + an association between flag string and bit value. --- rust/src/detect/uint.rs | 68 ++++++++++++++++++++++++++++++++++++ rust/src/websocket/detect.rs | 60 ++++--------------------------- 2 files changed, 75 insertions(+), 53 deletions(-) diff --git a/rust/src/detect/uint.rs b/rust/src/detect/uint.rs index e3f847c0bb..768a310fe3 100644 --- a/rust/src/detect/uint.rs +++ b/rust/src/detect/uint.rs @@ -20,6 +20,7 @@ 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::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; @@ -281,6 +282,71 @@ pub(crate) fn detect_uint_match_at_index( } } +#[derive(Debug, PartialEq)] +struct FlagItem { + value: T, + 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() { + 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, +) -> IResult<&str, Vec>> { + return many1(parse_flag_list_item::)(s); +} + +pub fn detect_parse_uint_bitflags>( + s: &str, +) -> 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) { + if !rem.is_empty() { + SCLogError!("junk at the end of bitflags"); + return None; + } + let mut arg1 = T1::min_value(); + let mut arg2 = T1::min_value(); + for elem in l.iter() { + if elem.value & arg1 != T1::min_value() { + SCLogError!( + "Repeated bitflag for {}", + T2::from_u(elem.value).unwrap().to_str() + ); + return None; + } + arg1 |= elem.value; + if !elem.neg { + arg2 |= elem.value; + } + } + let ctx = DetectUintData:: { + arg1, + arg2, + mode: DetectUintMode::DetectUintModeBitmask, + }; + return Some(ctx); + } + return None; +} + /// Parses a string for detection with integers, using enumeration strings /// /// Needs to specify T1 the integer type (like u8) @@ -317,6 +383,7 @@ pub fn detect_parse_uint_enum>( pub trait DetectIntType: std::str::FromStr + std::cmp::PartialOrd + + std::ops::BitOrAssign + num::PrimInt + num::Bounded + num::ToPrimitive @@ -326,6 +393,7 @@ pub trait DetectIntType: impl DetectIntType for T where T: std::str::FromStr + std::cmp::PartialOrd + + std::ops::BitOrAssign + num::PrimInt + num::Bounded + num::ToPrimitive diff --git a/rust/src/websocket/detect.rs b/rust/src/websocket/detect.rs index b5fe91c8eb..a65ef3436f 100644 --- a/rust/src/websocket/detect.rs +++ b/rust/src/websocket/detect.rs @@ -18,7 +18,7 @@ use super::websocket::{WebSocketTransaction, ALPROTO_WEBSOCKET}; use crate::core::{STREAM_TOCLIENT, STREAM_TOSERVER}; use crate::detect::uint::{ - detect_parse_uint, detect_parse_uint_enum, DetectUintData, DetectUintMode, SCDetectU32Free, + detect_parse_uint_bitflags, detect_parse_uint_enum, DetectUintData, SCDetectU32Free, SCDetectU32Match, SCDetectU32Parse, SCDetectU8Free, SCDetectU8Match, }; use crate::detect::{ @@ -33,12 +33,6 @@ use suricata_sys::sys::{ Signature, }; -use nom7::branch::alt; -use nom7::bytes::complete::{is_a, tag}; -use nom7::combinator::{opt, value}; -use nom7::multi::many1; -use nom7::IResult; - use std::ffi::CStr; use std::os::raw::{c_int, c_void}; @@ -55,51 +49,11 @@ unsafe extern "C" fn websocket_parse_opcode( return std::ptr::null_mut(); } -struct WebSocketFlag { - neg: bool, - value: u8, -} - -fn parse_flag_list_item(s: &str) -> IResult<&str, WebSocketFlag> { - let (s, _) = opt(is_a(" "))(s)?; - let (s, neg) = opt(tag("!"))(s)?; - let neg = neg.is_some(); - let (s, value) = alt((value(0x80, tag("fin")), value(0x40, tag("comp"))))(s)?; - let (s, _) = opt(is_a(" ,"))(s)?; - Ok((s, WebSocketFlag { neg, value })) -} - -fn parse_flag_list(s: &str) -> IResult<&str, Vec> { - return many1(parse_flag_list_item)(s); -} - -fn parse_flags(s: &str) -> Option> { - // try first numerical value - if let Ok((_, ctx)) = detect_parse_uint::(s) { - return Some(ctx); - } - // otherwise, try strings for bitmask - if let Ok((_, l)) = parse_flag_list(s) { - let mut arg1 = 0; - let mut arg2 = 0; - for elem in l.iter() { - if elem.value & arg1 != 0 { - SCLogWarning!("Repeated bitflag for websocket.flags"); - return None; - } - arg1 |= elem.value; - if !elem.neg { - arg2 |= elem.value; - } - } - let ctx = DetectUintData:: { - arg1, - arg2, - mode: DetectUintMode::DetectUintModeBitmask, - }; - return Some(ctx); - } - return None; +#[repr(u8)] +#[derive(EnumStringU8)] +pub enum WebSocketFlag { + Fin = 0x80, + Comp = 0x40, } unsafe extern "C" fn websocket_parse_flags( @@ -107,7 +61,7 @@ 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) = parse_flags(s) { + if let Some(ctx) = detect_parse_uint_bitflags::(s) { let boxed = Box::new(ctx); return Box::into_raw(boxed) as *mut _; }