diff --git a/doc/userguide/rules/multi-buffer-matching.rst b/doc/userguide/rules/multi-buffer-matching.rst index fd54819d8769..6dc466243078 100644 --- a/doc/userguide/rules/multi-buffer-matching.rst +++ b/doc/userguide/rules/multi-buffer-matching.rst @@ -73,6 +73,60 @@ So with the Suricata behavior prior to Suricata 7, the signature would not fire in this case since both content conditions will not be met. +Indexes for multi-buffers +------------------------- + +As for :ref:`multi-integer `, multi-buffers can precise +which index to match on. + +The syntax is:: + buffer: index; content: "toto"; + +.. table:: **Index values for multi-buffers keywords** + + =============== =========================================================== + Value Description + =============== =========================================================== + [default] Match with any index + any Match with any index + any_or_absent Match with any index or matches on an empty list + all Match only if all and at least one indexes match + all_or_absent Match only if all indexes match or matches on an empty list + matches Matches a number of times + index Match specific index + oob_or Match with specific index or index out of bounds + =============== =========================================================== + +.. container:: example-rule + + alert ip any any -> any 5353 (:example-rule-emphasis:`email.received: all;` content:"abc"; sid:1;) + +.. container:: example-rule + + alert ip any any -> any 5353 (:example-rule-emphasis:`email.received: matches >2;` content:"def"; sid:1;) + +.. container:: example-rule + + alert ip any any -> any 5353 (:example-rule-emphasis:`email.received: index 0;` content:"ghi"; sid:1;) + + +**Please note that:** + +The index ``all`` will not match if there is no value. + +The index ``all_or_absent`` will match if there is no value +and behaves like ``all`` if there is at least one value. + +If needed, these keywords will wait for transaction completion to run, +to be sure to have the final number of elements. + +The index ``matches`` accepts all comparison modes as integer keywords. +For example ``matches>3`` will match only if more than 3 integers in the +array match the value. + +List of multi-buffers +--------------------- + Multiple buffer matching is currently enabled for use with the following keywords: diff --git a/rust/src/detect/uint.rs b/rust/src/detect/uint.rs index e210a245d7c0..620204a25387 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), } @@ -72,13 +79,20 @@ pub struct DetectUintArrayData { fn parse_uint_index_precise(s: &str) -> IResult<&str, DetectUintIndex> { let (s, oob) = opt(tag("oob_or")).parse(s)?; + let (s, _explicit) = opt(tag("index")).parse(s)?; let (s, _) = opt(is_a(" ")).parse(s)?; let (s, i32_index) = nom_i32.parse(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> { - let (s, _) = tag("nb").parse(s)?; + let (s, _) = alt((tag("nb"), tag("matches"))).parse(s)?; let (s, _) = opt(is_a(" ")).parse(s)?; let (s, du32) = detect_parse_uint::(s)?; Ok((s, DetectUintIndex::NumberMatches(du32))) @@ -114,7 +128,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(" ")).parse(s)?; let (s, _) = tag("count").parse(s)?; let (s, _) = opt(is_a(" ")).parse(s)?; let (s, du32) = detect_parse_uint::(s)?; @@ -131,7 +146,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 @@ -322,15 +337,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; @@ -998,6 +1013,92 @@ 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), + "any_or_absent" => 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 cd68c8488444..79875d7a31af 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 {:?}", @@ -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,10 @@ 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 +246,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 +259,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 +272,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 +285,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/src/Makefile.am b/src/Makefile.am index cea03c08a8c5..3711069dbd54 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -248,6 +248,7 @@ noinst_HEADERS = \ detect-mark.h \ detect-metadata.h \ detect-msg.h \ + detect-multi.h \ detect-noalert.h \ detect-nocase.h \ detect-offset.h \ @@ -824,6 +825,7 @@ libsuricata_c_a_SOURCES = \ detect-mark.c \ detect-metadata.c \ detect-msg.c \ + detect-multi.c \ detect-noalert.c \ detect-nocase.c \ detect-offset.c \ diff --git a/src/detect-email.c b/src/detect-email.c index 3091adb76566..57230b7728bd 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" @@ -205,6 +206,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; } @@ -336,7 +341,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 = SCDetectHelperMultiBufferProgressMpmRegister("email.received", "MIME EMAIL RECEIVED", diff --git a/src/detect-engine-register.c b/src/detect-engine-register.c index 845521acac1e..59f53954cf93 100644 --- a/src/detect-engine-register.c +++ b/src/detect-engine-register.c @@ -202,6 +202,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" @@ -621,6 +622,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 221b25ef6997..3ae38b819f88 100644 --- a/src/detect-engine-register.h +++ b/src/detect-engine-register.h @@ -103,6 +103,10 @@ enum DetectKeywordId { DETECT_URILEN, DETECT_ABSENT, DETECT_ENTROPY, + 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 14c1740003b7..dee784933ea8 100644 --- a/src/detect-engine.c +++ b/src/detect-engine.c @@ -57,6 +57,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" @@ -2344,6 +2346,69 @@ 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_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; + } + // TODO unreachable so far + // need to get count if eof, and have 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; + } + // smd + 1 is not NULL thanks to DetectMultiValidateContentCallback + const bool match = DetectEngineContentInspectionBuffer(de_ctx, det_ctx, s, smd + 1, + 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); @@ -2353,17 +2418,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..4df5e0f7a40d --- /dev/null +++ b/src/detect-multi.c @@ -0,0 +1,121 @@ +/* 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) +{ + DetectMultiIndex index_type; + DetectUintIndexPrecise *prec; + DetectAbsentData *dad; + void *sm_ctx = SCDetectMultiIndexParse(arg, &index_type); + 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: + prec = (DetectUintIndexPrecise *)sm_ctx; + if (prec->pos < 0) { + SCLogError("negative index is not yet supported"); + } + 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; + } +} + +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_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; +} + +bool DetectMultiValidateContentCallback(const Signature *s, const SignatureInitDataBuffer *b) +{ + const SigMatch *sm = b->head; + if (sm != NULL && sm->next == NULL && + (sm->type == DETECT_MULTI_ALL || sm->type == DETECT_MULTI_ALL_OR_ABSENT || + sm->type == DETECT_MULTI_NB || sm->type == DETECT_MULTI_INDEX)) { + SCLogError("signature with multi-buffer keyword: expects other keywords to test on such as " + "content"); + return false; + } + + return true; +} diff --git a/src/detect-multi.h b/src/detect-multi.h new file mode 100644 index 000000000000..d8a7b018a7c7 --- /dev/null +++ b/src/detect-multi.h @@ -0,0 +1,25 @@ +/* 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); +int DetectMultiSetup(DetectEngineCtx *de_ctx, Signature *s, const char *arg); +bool DetectMultiValidateContentCallback(const Signature *s, const SignatureInitDataBuffer *); + +#endif diff --git a/src/detect-parse.c b/src/detect-parse.c index 8829b6859cab..f3e7a65c6dea 100644 --- a/src/detect-parse.c +++ b/src/detect-parse.c @@ -36,6 +36,7 @@ #include "detect-content.h" #include "detect-bsize.h" #include "detect-isdataat.h" +#include "detect-multi.h" #include "detect-pcre.h" #include "detect-uricontent.h" #include "detect-reference.h" @@ -2944,6 +2945,9 @@ static int SigValidateCheckBuffers( if (!DetectAbsentValidateContentCallback(s, b)) { SCReturnInt(0); } + if (!DetectMultiValidateContentCallback(s, b)) { + SCReturnInt(0); + } } if (has_pmatch && has_frame) {