diff --git a/rust/cbindgen.toml b/rust/cbindgen.toml index 3a661747db1a..492420f37727 100644 --- a/rust/cbindgen.toml +++ b/rust/cbindgen.toml @@ -90,6 +90,7 @@ include = [ "FtpDataStateValues", "HTTP2TransactionState", "DataRepType", + "DETECT_COUNT_INDEX", ] # A list of items to not include in the generated bindings diff --git a/rust/src/detect/mod.rs b/rust/src/detect/mod.rs index fdf6c6029145..1a4eb3723bd9 100644 --- a/rust/src/detect/mod.rs +++ b/rust/src/detect/mod.rs @@ -144,6 +144,8 @@ pub const SIGMATCH_INFO_MULTI_UINT: u32 = 0x80000; // BIT_U32(19) pub const SIGMATCH_INFO_ENUM_UINT: u32 = 0x100000; // BIT_U32(20) pub const SIGMATCH_INFO_BITFLAGS_UINT: u32 = 0x200000; // BIT_U32(21) +pub const DETECT_COUNT_INDEX: u32 = 0xFFFFFFFF; + #[repr(u8)] #[derive(Copy, Clone, Debug, PartialEq, Eq)] // endian diff --git a/rust/src/detect/uint.rs b/rust/src/detect/uint.rs index 337cd85fbc62..96eb9a2d9d83 100644 --- a/rust/src/detect/uint.rs +++ b/rust/src/detect/uint.rs @@ -50,13 +50,20 @@ pub struct DetectUintData { pub mode: DetectUintMode, } +#[repr(C)] +#[derive(Debug, PartialEq)] +pub struct DetectUintIndexPrecise { + pub oob: bool, + pub pos: i32, +} + #[derive(Debug, PartialEq)] pub enum DetectUintIndex { Any, AllOrAbsent, All, OrAbsent, - Index((bool, i32)), + Index(DetectUintIndexPrecise), NumberMatches(DetectUintData), Count(DetectUintData), } @@ -74,7 +81,13 @@ fn parse_uint_index_precise(s: &str) -> IResult<&str, DetectUintIndex> { let (s, oob) = opt(tag("oob_or"))(s)?; let (s, _) = opt(is_a(" "))(s)?; let (s, i32_index) = nom_i32(s)?; - Ok((s, DetectUintIndex::Index((oob.is_some(), i32_index)))) + Ok(( + s, + DetectUintIndex::Index(DetectUintIndexPrecise { + oob: oob.is_some(), + pos: i32_index, + }), + )) } fn parse_uint_index_nb(s: &str) -> IResult<&str, DetectUintIndex> { @@ -112,7 +125,8 @@ fn parse_uint_subslice(parts: &[&str]) -> Option<(i32, i32)> { return Some((start, end)); } -fn parse_uint_count(s: &str) -> IResult<&str, DetectUintData> { +fn parse_multi_count(s: &str) -> IResult<&str, DetectUintData> { + let (s, _) = opt(is_a(" "))(s)?; let (s, _) = tag("count")(s)?; let (s, _) = opt(is_a(" "))(s)?; let (s, du32) = detect_parse_uint::(s)?; @@ -129,7 +143,7 @@ fn parse_uint_index(parts: &[&str]) -> Option { // not only a literal, but some numeric value _ => return parse_uint_index_val(parts[1]), } - } else if let Ok((_, du)) = parse_uint_count(parts[0]) { + } else if let Ok((_, du)) = parse_multi_count(parts[0]) { DetectUintIndex::Count(du) } else { DetectUintIndex::Any @@ -320,15 +334,15 @@ pub(crate) fn detect_uint_match_at_index( } return 0; } - DetectUintIndex::Index((oob, idx)) => { - let index = if *idx < 0 { + DetectUintIndex::Index(prec) => { + let index = if prec.pos < 0 { // negative values for backward indexing. - ((subslice.len() as i32) + idx) as usize + ((subslice.len() as i32) + prec.pos) as usize } else { - *idx as usize + prec.pos as usize }; if subslice.len() <= index { - if *oob && eof { + if prec.oob && eof { return 1; } return 0; @@ -959,6 +973,91 @@ pub unsafe extern "C" fn SCDetectU16Free(ctx: &mut DetectUintData) { std::mem::drop(Box::from_raw(ctx)); } +#[no_mangle] +pub unsafe extern "C" fn SCDetectMultiCountParse(ustr: *const std::os::raw::c_char) -> *mut c_void { + let ft_name: &CStr = CStr::from_ptr(ustr); //unsafe + if let Ok(s) = ft_name.to_str() { + if let Ok((_, ctx)) = parse_multi_count(s) { + let boxed = Box::new(ctx); + return Box::into_raw(boxed) as *mut c_void; + } + } + return std::ptr::null_mut(); +} + +/// just a u8 for FFI +#[derive(PartialEq, Eq, Clone, Debug)] +#[repr(u8)] +pub enum DetectMultiIndex { + DetectMultiIndexAny, + DetectMultiIndexAbsentOr, + DetectMultiIndexAll, + DetectMultiIndexAllOrAbsent, + DetectMultiIndexNb, + DetectMultiIndexPrecise, + DetectMultiIndexError, +} + +impl DetectUintIndex { + // just borrow + fn index(&self) -> DetectMultiIndex { + match self { + DetectUintIndex::All => DetectMultiIndex::DetectMultiIndexAll, + DetectUintIndex::AllOrAbsent => DetectMultiIndex::DetectMultiIndexAllOrAbsent, + DetectUintIndex::Any => DetectMultiIndex::DetectMultiIndexAny, + DetectUintIndex::OrAbsent => DetectMultiIndex::DetectMultiIndexAbsentOr, + DetectUintIndex::NumberMatches(_) => DetectMultiIndex::DetectMultiIndexNb, + DetectUintIndex::Index(_) => DetectMultiIndex::DetectMultiIndexPrecise, + DetectUintIndex::Count(_) => DetectMultiIndex::DetectMultiIndexError, + } + } + + // take ownership and move + fn into_box(self) -> *mut c_void { + match self { + DetectUintIndex::NumberMatches(du32) => { + let boxed = Box::new(du32); + Box::into_raw(boxed) as *mut c_void + } + DetectUintIndex::Index(prec) => { + let boxed = Box::new(prec); + Box::into_raw(boxed) as *mut c_void + } + _ => std::ptr::null_mut(), + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn SCDetectMultiIndexFree(ctx: &mut DetectUintIndexPrecise) { + std::mem::drop(Box::from_raw(ctx)); +} + +fn parse_multi_index(s: &str) -> Option { + match s { + "all" => Some(DetectUintIndex::All), + "all_or_absent" => Some(DetectUintIndex::AllOrAbsent), + "any" => Some(DetectUintIndex::Any), + "absent_or" => Some(DetectUintIndex::OrAbsent), + // not only a literal, but some numeric value + _ => return parse_uint_index_val(s), + } +} + +#[no_mangle] +pub unsafe extern "C" fn SCDetectMultiIndexParse( + ustr: *const std::os::raw::c_char, it: *mut DetectMultiIndex, +) -> *mut c_void { + let ft_name: &CStr = CStr::from_ptr(ustr); //unsafe + if let Ok(s) = ft_name.to_str() { + if let Some(ctx) = parse_multi_index(s) { + *it = ctx.index(); + return ctx.into_box(); + } + } + return std::ptr::null_mut(); +} + #[cfg(test)] mod tests { use super::*; diff --git a/rust/src/detect/vlan.rs b/rust/src/detect/vlan.rs index 079a35e686d7..9c401e2cf956 100644 --- a/rust/src/detect/vlan.rs +++ b/rust/src/detect/vlan.rs @@ -17,7 +17,7 @@ use super::uint::{ detect_parse_array_uint, detect_uint_match_at_index, DetectUintArrayData, DetectUintData, - DetectUintIndex, + DetectUintIndex, DetectUintIndexPrecise, }; use std::ffi::{c_int, c_void, CStr}; @@ -49,7 +49,7 @@ pub fn detect_parse_vlan_id(s: &str) -> Option> { SCLogError!("vlan id should be less than 4096"); return None; } - match a.index { + match &a.index { DetectUintIndex::All => { // keep previous behavior that vlan.id: all matched only if there was vlan return Some(DetectUintArrayData { @@ -59,8 +59,8 @@ pub fn detect_parse_vlan_id(s: &str) -> Option> { end: a.end, }); } - DetectUintIndex::Index((_, i)) => { - if !(-VLAN_MAX_LAYERS..=VLAN_MAX_LAYERS - 1).contains(&i) { + DetectUintIndex::Index(prec) => { + if !(-VLAN_MAX_LAYERS..=VLAN_MAX_LAYERS - 1).contains(&prec.pos) { SCLogError!( "vlan id index should belong in range {:?}", (-VLAN_MAX_LAYERS..=VLAN_MAX_LAYERS - 1) @@ -110,7 +110,10 @@ pub unsafe extern "C" fn SCDetectVlanIdPrefilterMatch( DETECT_VLAN_ID_ALL => DetectUintIndex::All, DETECT_VLAN_ID_ALL_OR_ABSENT => DetectUintIndex::AllOrAbsent, DETECT_VLAN_ID_OR_ABSENT => DetectUintIndex::OrAbsent, - i => DetectUintIndex::Index((false, i.into())), + i => DetectUintIndex::Index(DetectUintIndexPrecise { + oob: false, + pos: i.into(), + }), }; let ctx = DetectUintArrayData { @@ -127,12 +130,12 @@ pub unsafe extern "C" fn SCDetectVlanIdPrefilterMatch( pub unsafe extern "C" fn SCDetectVlanIdPrefilter( ctx: &DetectUintArrayData, ) -> DetectVlanIdDataPrefilter { - let layer = match ctx.index { + let layer = match &ctx.index { DetectUintIndex::Any => DETECT_VLAN_ID_ANY, DetectUintIndex::All => DETECT_VLAN_ID_ALL, DetectUintIndex::AllOrAbsent => DETECT_VLAN_ID_ALL_OR_ABSENT, DetectUintIndex::OrAbsent => DETECT_VLAN_ID_OR_ABSENT, - DetectUintIndex::Index((_, i)) => i as i8, + DetectUintIndex::Index(prec) => prec.pos as i8, DetectUintIndex::NumberMatches(_) => DETECT_VLAN_ID_ERROR, DetectUintIndex::Count(_) => DETECT_VLAN_ID_ERROR, }; @@ -148,13 +151,13 @@ pub unsafe extern "C" fn SCDetectVlanIdPrefilterable(ctx: *const c_void) -> bool if ctx.start != 0 || ctx.end != 0 { return false; } - match ctx.index { + match &ctx.index { DetectUintIndex::Any => true, DetectUintIndex::All => true, DetectUintIndex::AllOrAbsent => true, DetectUintIndex::OrAbsent => true, // do not prefilter for precise index with "or out of bounds" - DetectUintIndex::Index((oob, _)) => !oob, + DetectUintIndex::Index(prec) => !prec.oob, DetectUintIndex::NumberMatches(_) => false, DetectUintIndex::Count(_) => false, } @@ -214,7 +217,7 @@ mod test { arg2: 0, mode: DetectUintMode::DetectUintModeEqual, }, - index: DetectUintIndex::Index((false, 1)), + index: DetectUintIndex::Index(DetectUintIndexPrecise { oob: false, pos: 1 }), start: 0, end: 0, } @@ -227,7 +230,7 @@ mod test { arg2: 0, mode: DetectUintMode::DetectUintModeEqual, }, - index: DetectUintIndex::Index((false, -1)), + index: DetectUintIndex::Index(DetectUintIndexPrecise { oob: false, pos: -1 }), start: 0, end: 0, } @@ -240,7 +243,7 @@ mod test { arg2: 0, mode: DetectUintMode::DetectUintModeNe, }, - index: DetectUintIndex::Index((false, 2)), + index: DetectUintIndex::Index(DetectUintIndexPrecise { oob: false, pos: 2 }), start: 0, end: 0, } @@ -253,7 +256,7 @@ mod test { arg2: 0, mode: DetectUintMode::DetectUintModeGt, }, - index: DetectUintIndex::Index((false, 2)), + index: DetectUintIndex::Index(DetectUintIndexPrecise { oob: false, pos: 2 }), start: 0, end: 0, } @@ -266,7 +269,7 @@ mod test { arg2: 300, mode: DetectUintMode::DetectUintModeRange, }, - index: DetectUintIndex::Index((false, 0)), + index: DetectUintIndex::Index(DetectUintIndexPrecise { oob: false, pos: 0 }), start: 0, end: 0, } @@ -279,7 +282,7 @@ mod test { arg2: 0, mode: DetectUintMode::DetectUintModeEqual, }, - index: DetectUintIndex::Index((false, 2)), + index: DetectUintIndex::Index(DetectUintIndexPrecise { oob: false, pos: 2 }), start: 0, end: 0, } diff --git a/rust/src/mime/detect.rs b/rust/src/mime/detect.rs index b1a36ce6a025..a777d341623d 100644 --- a/rust/src/mime/detect.rs +++ b/rust/src/mime/detect.rs @@ -63,6 +63,26 @@ pub unsafe extern "C" fn SCDetectMimeEmailGetUrl( return 0; } +/// Intermediary function used in detect-email.c to access data from the MimeStateSMTP structure +/// for array header fields. +/// The hname parameter determines which data will be returned. +#[no_mangle] +pub unsafe extern "C" fn SCDetectMimeEmailGetCount( + ctx: &MimeStateSMTP, hname: *const std::os::raw::c_char, +) -> u32 { + let c_str = CStr::from_ptr(hname); //unsafe + let str = c_str.to_str().unwrap_or(""); + + let mut r = 0u32; + for h in &ctx.headers[..ctx.main_headers_nb] { + if mime::slice_equals_lowercase(&h.name, str.as_bytes()) { + r += 1; + } + } + + return r; +} + /// Intermediary function used in detect-email.c to access data from the MimeStateSMTP structure /// for array header fields. /// The hname parameter determines which data will be returned. diff --git a/src/Makefile.am b/src/Makefile.am index 66da2d1df0a7..48449612a029 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -253,6 +253,7 @@ noinst_HEADERS = \ detect-metadata.h \ detect-modbus.h \ detect-msg.h \ + detect-multi.h \ detect-nfs-version.h \ detect-noalert.h \ detect-nocase.h \ @@ -852,6 +853,7 @@ libsuricata_c_a_SOURCES = \ detect-metadata.c \ detect-modbus.c \ detect-msg.c \ + detect-multi.c \ detect-nfs-version.c \ detect-noalert.c \ detect-nocase.c \ diff --git a/src/detect-email.c b/src/detect-email.c index be5e9793f949..4d3000e201c9 100644 --- a/src/detect-email.c +++ b/src/detect-email.c @@ -21,6 +21,7 @@ #include "detect-parse.h" #include "app-layer-smtp.h" #include "detect-email.h" +#include "detect-multi.h" #include "rust.h" #include "detect-engine-content-inspection.h" @@ -208,6 +209,10 @@ static int DetectMimeEmailReceivedSetup(DetectEngineCtx *de_ctx, Signature *s, c if (SCDetectSignatureSetAppProto(s, ALPROTO_SMTP) < 0) return -1; + if (arg) { + return DetectMultiSetup(de_ctx, s, arg); + } + return 0; } @@ -220,6 +225,11 @@ static bool GetMimeEmailReceivedData(DetectEngineThreadCtx *det_ctx, const void return false; } + if (idx == DETECT_COUNT_INDEX) { + *buf_len = SCDetectMimeEmailGetCount(tx->mime_state, "received"); + return true; + } + if (SCDetectMimeEmailGetDataArray(tx->mime_state, buf, buf_len, "received", idx) != 1) { return false; } @@ -336,7 +346,7 @@ void DetectEmailRegister(void) kw.desc = "'Received' field from an email"; kw.url = "/rules/email-keywords.html#email.received"; kw.Setup = DetectMimeEmailReceivedSetup; - kw.flags = SIGMATCH_NOOPT | SIGMATCH_INFO_STICKY_BUFFER | SIGMATCH_INFO_MULTI_BUFFER; + kw.flags = SIGMATCH_OPTIONAL_OPT | SIGMATCH_INFO_STICKY_BUFFER | SIGMATCH_INFO_MULTI_BUFFER; SCDetectHelperKeywordRegister(&kw); g_mime_email_received_buffer_id = SCDetectHelperMultiBufferMpmRegister("email.received", "MIME EMAIL RECEIVED", ALPROTO_SMTP, STREAM_TOSERVER, GetMimeEmailReceivedData); diff --git a/src/detect-engine-register.c b/src/detect-engine-register.c index 8a490d75b429..b9c31332abc1 100644 --- a/src/detect-engine-register.c +++ b/src/detect-engine-register.c @@ -210,6 +210,7 @@ #include "detect-ja4-hash.h" #include "detect-ftp-command.h" #include "detect-entropy.h" +#include "detect-multi.h" #include "detect-ftp-command-data.h" #include "detect-ftp-completion-code.h" #include "detect-ftp-reply.h" @@ -641,6 +642,7 @@ void SigTableSetup(void) DetectBytejumpRegister(); DetectBytemathRegister(); DetectEntropyRegister(); + DetectMultiRegister(); DetectSameipRegister(); DetectGeoipRegister(); DetectL3ProtoRegister(); diff --git a/src/detect-engine-register.h b/src/detect-engine-register.h index cdfe9e22f189..fabfb014d1a1 100644 --- a/src/detect-engine-register.h +++ b/src/detect-engine-register.h @@ -94,6 +94,11 @@ enum DetectKeywordId { DETECT_URILEN, DETECT_ABSENT, DETECT_ENTROPY, + DETECT_MULTI_COUNT, + DETECT_MULTI_ALL, + DETECT_MULTI_ALL_OR_ABSENT, + DETECT_MULTI_NB, + DETECT_MULTI_INDEX, /* end of content inspection */ DETECT_METADATA, diff --git a/src/detect-engine.c b/src/detect-engine.c index 6c42fedd2553..a3eb09fa3e2d 100644 --- a/src/detect-engine.c +++ b/src/detect-engine.c @@ -56,6 +56,8 @@ #include "detect-engine-payload.h" #include "detect-fast-pattern.h" #include "detect-byte-extract.h" +#include "detect-multi.h" +#include "detect-engine-uint.h" #include "detect-content.h" #include "detect-uricontent.h" #include "detect-tcphdr.h" @@ -2156,6 +2158,81 @@ uint8_t DetectEngineInspectMultiBufferGeneric(DetectEngineCtx *de_ctx, transforms = engine->v2.transforms; } + bool stop_on_first_match = true; + const bool eof = + (AppLayerParserGetStateProgress(f->proto, f->alproto, txv, flags) > engine->progress); + SigMatchData *smd = engine->smd; + DetectUintIndexPrecise *prec; + switch (smd->type) { + case DETECT_MULTI_COUNT: + // count should always be first and only + if (!DetectCountDoMatch( + det_ctx, f, flags, txv, engine->v2.GetMultiData, smd->ctx, eof)) { + return DETECT_ENGINE_INSPECT_SIG_NO_MATCH; + } else { + // only count + return DETECT_ENGINE_INSPECT_SIG_MATCH; + } + case DETECT_MULTI_ALL: + // fallthrough + case DETECT_MULTI_ALL_OR_ABSENT: + if (!eof) { + return DETECT_ENGINE_INSPECT_SIG_NO_MATCH; + } + stop_on_first_match = false; + smd++; + break; + case DETECT_MULTI_NB: + if (!eof) { + DetectU32Data *du32 = (DetectU32Data *)smd->ctx; + if (du32->mode != DETECT_UINT_GTE && du32->mode != DETECT_UINT_GT) { + return DETECT_ENGINE_INSPECT_SIG_NO_MATCH; + } + } + stop_on_first_match = false; + smd++; + break; + case DETECT_MULTI_INDEX: + prec = (DetectUintIndexPrecise *)smd->ctx; + if (prec->pos < 0) { + if (!eof) { + return DETECT_ENGINE_INSPECT_SIG_NO_MATCH; + } + uint32_t count = 0; + if (!engine->v2.GetMultiData( + det_ctx, txv, flags, DETECT_COUNT_INDEX, NULL, &count)) { + return DETECT_ENGINE_INSPECT_SIG_NO_MATCH; + } + local_id = count + prec->pos; + } else { + local_id = prec->pos; + } + // get the buffer + InspectionBuffer *buffer = DetectGetMultiData(det_ctx, transforms, f, flags, txv, + engine->sm_list, local_id, engine->v2.GetMultiData); + if (buffer == NULL || buffer->inspect == NULL) { + // no buffer + if (eof) { + // no more buffers coming + if (prec->oob) { + // match as out of bounds + return DETECT_ENGINE_INSPECT_SIG_MATCH; + } + // will never match + return DETECT_ENGINE_INSPECT_SIG_CANT_MATCH; + } + // wait for more buffers + return DETECT_ENGINE_INSPECT_SIG_NO_MATCH; + } + const bool match = DetectEngineContentInspectionBuffer(de_ctx, det_ctx, s, smd, NULL, f, + buffer, DETECT_ENGINE_CONTENT_INSPECTION_MODE_STATE); + if (match) { + return DETECT_ENGINE_INSPECT_SIG_MATCH; + } + return DETECT_ENGINE_INSPECT_SIG_CANT_MATCH; + } + + uint32_t nb_matches = 0; do { InspectionBuffer *buffer = DetectGetMultiData(det_ctx, transforms, f, flags, txv, engine->sm_list, local_id, engine->v2.GetMultiData); @@ -2165,17 +2242,33 @@ uint8_t DetectEngineInspectMultiBufferGeneric(DetectEngineCtx *de_ctx, // The GetData functions set buffer->flags to DETECT_CI_FLAGS_SINGLE // This is not meant for streaming buffers - const bool match = DetectEngineContentInspectionBuffer(de_ctx, det_ctx, s, engine->smd, - NULL, f, buffer, DETECT_ENGINE_CONTENT_INSPECTION_MODE_STATE); + const bool match = DetectEngineContentInspectionBuffer(de_ctx, det_ctx, s, smd, NULL, f, + buffer, DETECT_ENGINE_CONTENT_INSPECTION_MODE_STATE); if (match) { - return DETECT_ENGINE_INSPECT_SIG_MATCH; + if (stop_on_first_match) + return DETECT_ENGINE_INSPECT_SIG_MATCH; + nb_matches++; } local_id++; } while (1); + if (!stop_on_first_match) { + switch (engine->smd->type) { + case DETECT_MULTI_ALL: + if (nb_matches == local_id && nb_matches > 0) + return DETECT_ENGINE_INSPECT_SIG_MATCH; + break; + case DETECT_MULTI_ALL_OR_ABSENT: + if (nb_matches == local_id) + return DETECT_ENGINE_INSPECT_SIG_MATCH; + break; + case DETECT_MULTI_NB: + if (DetectU32Match(nb_matches, (DetectU32Data *)engine->smd->ctx)) + return DETECT_ENGINE_INSPECT_SIG_MATCH; + } + return DETECT_ENGINE_INSPECT_SIG_NO_MATCH; + } if (local_id == 0) { // That means we did not get even one buffer value from the multi-buffer - const bool eof = (AppLayerParserGetStateProgress(f->proto, f->alproto, txv, flags) > - engine->progress); if (eof && engine->match_on_null) { return DETECT_ENGINE_INSPECT_SIG_MATCH; } diff --git a/src/detect-multi.c b/src/detect-multi.c new file mode 100644 index 000000000000..491a1904a16d --- /dev/null +++ b/src/detect-multi.c @@ -0,0 +1,135 @@ +/* Copyright (C) 2025 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. + */ + +#include "suricata-common.h" +#include "rust.h" + +#include "detect-multi.h" +#include "detect-engine-buffer.h" +#include "detect-engine-uint.h" +#include "detect-parse.h" +// DetectAbsentData +#include "detect-isdataat.h" + +#include "util-validate.h" + +static void DetectDu32Free(DetectEngineCtx *de_ctx, void *ptr) +{ + SCDetectU32Free(ptr); +} + +int DetectMultiSetup(DetectEngineCtx *de_ctx, Signature *s, const char *arg) +{ + // First try to parse a count + DetectU32Data *du32 = SCDetectMultiCountParse(arg); + if (du32 != NULL) { + if (SCSigMatchAppendSMToList(de_ctx, s, DETECT_MULTI_COUNT, (SigMatchCtx *)du32, + s->init_data->list) != NULL) { + return 0; + } + SCLogError("error during count setup"); + DetectDu32Free(de_ctx, du32); + return -1; + } // else not count + + DetectMultiIndex index_type; + void *sm_ctx = SCDetectMultiIndexParse(arg, &index_type); + DetectAbsentData *dad; + switch (index_type) { + case DetectMultiIndexAny: + // default case, nothing to do + return 0; + case DetectMultiIndexAbsentOr: + dad = SCMalloc(sizeof(DetectAbsentData)); + if (unlikely(dad == NULL)) + return -1; + + dad->or_else = true; + if (SCSigMatchAppendSMToList( + de_ctx, s, DETECT_ABSENT, (SigMatchCtx *)dad, s->init_data->list) == NULL) { + return 0; + } + sigmatch_table[DETECT_ABSENT].Free(de_ctx, dad); + return -1; + case DetectMultiIndexAll: + if (SCSigMatchAppendSMToList(de_ctx, s, DETECT_MULTI_ALL, NULL, s->init_data->list) != + NULL) { + return 0; + } + return -1; + case DetectMultiIndexAllOrAbsent: + if (SCSigMatchAppendSMToList( + de_ctx, s, DETECT_MULTI_ALL_OR_ABSENT, NULL, s->init_data->list) != NULL) { + return 0; + } + return -1; + case DetectMultiIndexNb: + if (SCSigMatchAppendSMToList(de_ctx, s, DETECT_MULTI_NB, sm_ctx, s->init_data->list) != + NULL) { + return 0; + } + return -1; + case DetectMultiIndexPrecise: + if (SCSigMatchAppendSMToList( + de_ctx, s, DETECT_MULTI_INDEX, sm_ctx, s->init_data->list) != NULL) { + return 0; + } + return -1; + default: + SCLogError("invalid argument for multi-buffer"); + return -1; + } +} + +bool DetectCountDoMatch(DetectEngineThreadCtx *det_ctx, Flow *f, const uint8_t flow_flags, + void *txv, InspectionMultiBufferGetDataPtr GetBuf, const SigMatchCtx *ctx, bool eof) +{ + uint32_t count = 0; + DetectU32Data *du32 = (DetectU32Data *)ctx; + + if (!eof && du32->mode != DETECT_UINT_GTE && du32->mode != DETECT_UINT_GT) { + return false; + } + if (!GetBuf(det_ctx, txv, flow_flags, DETECT_COUNT_INDEX, NULL, &count)) { + DEBUG_VALIDATE_BUG_ON(1); + return false; + } + + return DetectU32Match(count, du32); +} + +static void DetectMultiIndexFree(DetectEngineCtx *de_ctx, void *ptr) +{ + SCDetectMultiIndexFree(ptr); +} + +void DetectMultiRegister(void) +{ + // These are not used as a regular keyword + // But as option that can be set on multi-buffers + sigmatch_table[DETECT_MULTI_COUNT].name = "count"; + sigmatch_table[DETECT_MULTI_COUNT].desc = "count number of buffers in a multi-buffer"; + sigmatch_table[DETECT_MULTI_COUNT].Free = DetectDu32Free; + + sigmatch_table[DETECT_MULTI_NB].name = "multi_nb"; + sigmatch_table[DETECT_MULTI_NB].desc = "count number of matches in a multi-buffer"; + sigmatch_table[DETECT_MULTI_NB].Free = DetectDu32Free; + + sigmatch_table[DETECT_MULTI_INDEX].name = "multi_index"; + sigmatch_table[DETECT_MULTI_INDEX].desc = "try to match a multi-buffer at a specific index"; + sigmatch_table[DETECT_MULTI_INDEX].Free = DetectMultiIndexFree; +} diff --git a/src/detect-multi.h b/src/detect-multi.h new file mode 100644 index 000000000000..a4371345f5fa --- /dev/null +++ b/src/detect-multi.h @@ -0,0 +1,26 @@ +/* Copyright (C) 2025 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. + */ + +#ifndef SURICATA_DETECT_MULTI_H +#define SURICATA_DETECT_MULTI_H + +void DetectMultiRegister(void); +bool DetectCountDoMatch(DetectEngineThreadCtx *det_ctx, Flow *f, const uint8_t flow_flags, + void *txv, InspectionMultiBufferGetDataPtr GetBuf, const SigMatchCtx *ctx, bool eof); +int DetectMultiSetup(DetectEngineCtx *de_ctx, Signature *s, const char *arg); + +#endif diff --git a/src/detect-parse.c b/src/detect-parse.c index 1f4a14792959..34ba57913742 100644 --- a/src/detect-parse.c +++ b/src/detect-parse.c @@ -462,6 +462,13 @@ SigMatch *SCSigMatchAppendSMToList( } SCLogDebug("s->init_data->buffer_index %u", s->init_data->buffer_index); } + } else if (s->init_data->curbuf->head != NULL && + s->init_data->curbuf->head->type == DETECT_MULTI_COUNT) { + SCLogError("Cannot add other conditions after multi-buffer count, please reuse the " + "sticky buffer name to add conditions."); + new->ctx = NULL; + SigMatchFree(de_ctx, new); + return NULL; } BUG_ON(s->init_data->curbuf == NULL);