Catena cyber 1 day ago committed by GitHub
commit 3897cdfc4b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -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 would not fire in this case since both content conditions will
not be met. not be met.
Indexes for multi-buffers
-------------------------
As for :ref:`multi-integer <multi-integers>`, 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 <value> Matches a number of times
index <value> Match specific index
oob_or <value> 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 Multiple buffer matching is currently enabled for use with the
following keywords: following keywords:

@ -50,13 +50,20 @@ pub struct DetectUintData<T> {
pub mode: DetectUintMode, pub mode: DetectUintMode,
} }
#[repr(C)]
#[derive(Debug, PartialEq)]
pub struct DetectUintIndexPrecise {
pub oob: bool,
pub pos: i32,
}
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum DetectUintIndex { pub enum DetectUintIndex {
Any, Any,
AllOrAbsent, AllOrAbsent,
All, All,
OrAbsent, OrAbsent,
Index((bool, i32)), Index(DetectUintIndexPrecise),
NumberMatches(DetectUintData<u32>), NumberMatches(DetectUintData<u32>),
Count(DetectUintData<u32>), Count(DetectUintData<u32>),
} }
@ -72,22 +79,35 @@ pub struct DetectUintArrayData<T> {
fn parse_uint_index_precise(s: &str) -> IResult<&str, DetectUintIndex> { fn parse_uint_index_precise(s: &str) -> IResult<&str, DetectUintIndex> {
let (s, oob) = opt(tag("oob_or")).parse(s)?; 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, _) = opt(is_a(" ")).parse(s)?;
let (s, i32_index) = nom_i32.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> { 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, _) = opt(is_a(" ")).parse(s)?;
let (s, du32) = detect_parse_uint::<u32>(s)?; let (s, du32) = detect_parse_uint::<u32>(s)?;
Ok((s, DetectUintIndex::NumberMatches(du32))) Ok((s, DetectUintIndex::NumberMatches(du32)))
} }
fn parse_uint_index_val(s: &str) -> Option<DetectUintIndex> { fn parse_uint_index_val(s: &str) -> Option<DetectUintIndex> {
let (_s, arg1) = alt((parse_uint_index_precise, parse_uint_index_nb)) let (s, arg1) = alt((parse_uint_index_precise, parse_uint_index_nb))
.parse(s) .parse(s)
.ok()?; .ok()?;
if let Ok((s, _)) = opt(is_a::<&str, &str, nom8::error::Error<_>>(" ")).parse(s) {
if !s.is_empty() {
SCLogError!("Invalid trailing data for index : {s}");
return None;
}
}
Some(arg1) Some(arg1)
} }
@ -114,7 +134,8 @@ fn parse_uint_subslice(parts: &[&str]) -> Option<(i32, i32)> {
return Some((start, end)); return Some((start, end));
} }
fn parse_uint_count(s: &str) -> IResult<&str, DetectUintData<u32>> { fn parse_multi_count(s: &str) -> IResult<&str, DetectUintData<u32>> {
let (s, _) = opt(is_a(" ")).parse(s)?;
let (s, _) = tag("count").parse(s)?; let (s, _) = tag("count").parse(s)?;
let (s, _) = opt(is_a(" ")).parse(s)?; let (s, _) = opt(is_a(" ")).parse(s)?;
let (s, du32) = detect_parse_uint::<u32>(s)?; let (s, du32) = detect_parse_uint::<u32>(s)?;
@ -131,7 +152,7 @@ fn parse_uint_index(parts: &[&str]) -> Option<DetectUintIndex> {
// not only a literal, but some numeric value // not only a literal, but some numeric value
_ => return parse_uint_index_val(parts[1]), _ => 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) DetectUintIndex::Count(du)
} else { } else {
DetectUintIndex::Any DetectUintIndex::Any
@ -322,15 +343,15 @@ pub(crate) fn detect_uint_match_at_index<T, U: DetectIntType>(
} }
return 0; return 0;
} }
DetectUintIndex::Index((oob, idx)) => { DetectUintIndex::Index(prec) => {
let index = if *idx < 0 { let index = if prec.pos < 0 {
// negative values for backward indexing. // negative values for backward indexing.
((subslice.len() as i32) + idx) as usize ((subslice.len() as i32) + prec.pos) as usize
} else { } else {
*idx as usize prec.pos as usize
}; };
if subslice.len() <= index { if subslice.len() <= index {
if *oob && eof { if prec.oob && eof {
return 1; return 1;
} }
return 0; return 0;
@ -998,6 +1019,93 @@ pub unsafe extern "C" fn SCDetectU16Free(ctx: &mut DetectUintData<u16>) {
std::mem::drop(Box::from_raw(ctx)); 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<DetectUintIndex> {
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();
}
}
*it = DetectMultiIndex::DetectMultiIndexError;
return std::ptr::null_mut();
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

@ -17,7 +17,7 @@
use super::uint::{ use super::uint::{
detect_parse_array_uint, detect_uint_match_at_index, DetectUintArrayData, DetectUintData, detect_parse_array_uint, detect_uint_match_at_index, DetectUintArrayData, DetectUintData,
DetectUintIndex, DetectUintIndex, DetectUintIndexPrecise,
}; };
use std::ffi::{c_int, c_void, CStr}; use std::ffi::{c_int, c_void, CStr};
@ -49,7 +49,7 @@ pub fn detect_parse_vlan_id(s: &str) -> Option<DetectUintArrayData<u16>> {
SCLogError!("vlan id should be less than 4096"); SCLogError!("vlan id should be less than 4096");
return None; return None;
} }
match a.index { match &a.index {
DetectUintIndex::All => { DetectUintIndex::All => {
// keep previous behavior that vlan.id: all matched only if there was vlan // keep previous behavior that vlan.id: all matched only if there was vlan
return Some(DetectUintArrayData { return Some(DetectUintArrayData {
@ -59,8 +59,8 @@ pub fn detect_parse_vlan_id(s: &str) -> Option<DetectUintArrayData<u16>> {
end: a.end, end: a.end,
}); });
} }
DetectUintIndex::Index((_, i)) DetectUintIndex::Index(prec)
if !(-VLAN_MAX_LAYERS..=VLAN_MAX_LAYERS - 1).contains(&i) => if !(-VLAN_MAX_LAYERS..=VLAN_MAX_LAYERS - 1).contains(&prec.pos) =>
{ {
SCLogError!( SCLogError!(
"vlan id index should belong in range {:?}", "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 => DetectUintIndex::All,
DETECT_VLAN_ID_ALL_OR_ABSENT => DetectUintIndex::AllOrAbsent, DETECT_VLAN_ID_ALL_OR_ABSENT => DetectUintIndex::AllOrAbsent,
DETECT_VLAN_ID_OR_ABSENT => DetectUintIndex::OrAbsent, 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 { let ctx = DetectUintArrayData {
@ -127,12 +130,12 @@ pub unsafe extern "C" fn SCDetectVlanIdPrefilterMatch(
pub unsafe extern "C" fn SCDetectVlanIdPrefilter( pub unsafe extern "C" fn SCDetectVlanIdPrefilter(
ctx: &DetectUintArrayData<u16>, ctx: &DetectUintArrayData<u16>,
) -> DetectVlanIdDataPrefilter { ) -> DetectVlanIdDataPrefilter {
let layer = match ctx.index { let layer = match &ctx.index {
DetectUintIndex::Any => DETECT_VLAN_ID_ANY, DetectUintIndex::Any => DETECT_VLAN_ID_ANY,
DetectUintIndex::All => DETECT_VLAN_ID_ALL, DetectUintIndex::All => DETECT_VLAN_ID_ALL,
DetectUintIndex::AllOrAbsent => DETECT_VLAN_ID_ALL_OR_ABSENT, DetectUintIndex::AllOrAbsent => DETECT_VLAN_ID_ALL_OR_ABSENT,
DetectUintIndex::OrAbsent => DETECT_VLAN_ID_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::NumberMatches(_) => DETECT_VLAN_ID_ERROR,
DetectUintIndex::Count(_) => 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 { if ctx.start != 0 || ctx.end != 0 {
return false; return false;
} }
match ctx.index { match &ctx.index {
DetectUintIndex::Any => true, DetectUintIndex::Any => true,
DetectUintIndex::All => true, DetectUintIndex::All => true,
DetectUintIndex::AllOrAbsent => true, DetectUintIndex::AllOrAbsent => true,
DetectUintIndex::OrAbsent => true, DetectUintIndex::OrAbsent => true,
// do not prefilter for precise index with "or out of bounds" // do not prefilter for precise index with "or out of bounds"
DetectUintIndex::Index((oob, _)) => !oob, DetectUintIndex::Index(prec) => !prec.oob,
DetectUintIndex::NumberMatches(_) => false, DetectUintIndex::NumberMatches(_) => false,
DetectUintIndex::Count(_) => false, DetectUintIndex::Count(_) => false,
} }
@ -214,7 +217,7 @@ mod test {
arg2: 0, arg2: 0,
mode: DetectUintMode::DetectUintModeEqual, mode: DetectUintMode::DetectUintModeEqual,
}, },
index: DetectUintIndex::Index((false, 1)), index: DetectUintIndex::Index(DetectUintIndexPrecise { oob: false, pos: 1 }),
start: 0, start: 0,
end: 0, end: 0,
} }
@ -227,7 +230,10 @@ mod test {
arg2: 0, arg2: 0,
mode: DetectUintMode::DetectUintModeEqual, mode: DetectUintMode::DetectUintModeEqual,
}, },
index: DetectUintIndex::Index((false, -1)), index: DetectUintIndex::Index(DetectUintIndexPrecise {
oob: false,
pos: -1
}),
start: 0, start: 0,
end: 0, end: 0,
} }
@ -240,7 +246,7 @@ mod test {
arg2: 0, arg2: 0,
mode: DetectUintMode::DetectUintModeNe, mode: DetectUintMode::DetectUintModeNe,
}, },
index: DetectUintIndex::Index((false, 2)), index: DetectUintIndex::Index(DetectUintIndexPrecise { oob: false, pos: 2 }),
start: 0, start: 0,
end: 0, end: 0,
} }
@ -253,7 +259,7 @@ mod test {
arg2: 0, arg2: 0,
mode: DetectUintMode::DetectUintModeGt, mode: DetectUintMode::DetectUintModeGt,
}, },
index: DetectUintIndex::Index((false, 2)), index: DetectUintIndex::Index(DetectUintIndexPrecise { oob: false, pos: 2 }),
start: 0, start: 0,
end: 0, end: 0,
} }
@ -266,7 +272,7 @@ mod test {
arg2: 300, arg2: 300,
mode: DetectUintMode::DetectUintModeRange, mode: DetectUintMode::DetectUintModeRange,
}, },
index: DetectUintIndex::Index((false, 0)), index: DetectUintIndex::Index(DetectUintIndexPrecise { oob: false, pos: 0 }),
start: 0, start: 0,
end: 0, end: 0,
} }
@ -279,7 +285,7 @@ mod test {
arg2: 0, arg2: 0,
mode: DetectUintMode::DetectUintModeEqual, mode: DetectUintMode::DetectUintModeEqual,
}, },
index: DetectUintIndex::Index((false, 2)), index: DetectUintIndex::Index(DetectUintIndexPrecise { oob: false, pos: 2 }),
start: 0, start: 0,
end: 0, end: 0,
} }

@ -248,6 +248,7 @@ noinst_HEADERS = \
detect-mark.h \ detect-mark.h \
detect-metadata.h \ detect-metadata.h \
detect-msg.h \ detect-msg.h \
detect-multi.h \
detect-noalert.h \ detect-noalert.h \
detect-nocase.h \ detect-nocase.h \
detect-offset.h \ detect-offset.h \
@ -823,6 +824,7 @@ libsuricata_c_a_SOURCES = \
detect-mark.c \ detect-mark.c \
detect-metadata.c \ detect-metadata.c \
detect-msg.c \ detect-msg.c \
detect-multi.c \
detect-noalert.c \ detect-noalert.c \
detect-nocase.c \ detect-nocase.c \
detect-offset.c \ detect-offset.c \

@ -21,6 +21,7 @@
#include "detect-parse.h" #include "detect-parse.h"
#include "app-layer-smtp.h" #include "app-layer-smtp.h"
#include "detect-email.h" #include "detect-email.h"
#include "detect-multi.h"
#include "rust.h" #include "rust.h"
#include "detect-engine-content-inspection.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) if (SCDetectSignatureSetAppProto(s, ALPROTO_SMTP) < 0)
return -1; return -1;
if (arg) {
return DetectMultiSetup(de_ctx, s, arg);
}
return 0; return 0;
} }
@ -336,7 +341,7 @@ void DetectEmailRegister(void)
kw.desc = "'Received' field from an email"; kw.desc = "'Received' field from an email";
kw.url = "/rules/email-keywords.html#email-received"; kw.url = "/rules/email-keywords.html#email-received";
kw.Setup = DetectMimeEmailReceivedSetup; 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); SCDetectHelperKeywordRegister(&kw);
g_mime_email_received_buffer_id = g_mime_email_received_buffer_id =
SCDetectHelperMultiBufferProgressMpmRegister("email.received", "MIME EMAIL RECEIVED", SCDetectHelperMultiBufferProgressMpmRegister("email.received", "MIME EMAIL RECEIVED",

@ -134,10 +134,22 @@ int DetectBufferGetActiveList(DetectEngineCtx *de_ctx, Signature *s)
s->init_data->transforms.cnt = 0; s->init_data->transforms.cnt = 0;
if (s->init_data->curbuf && s->init_data->curbuf->head != NULL) { if (s->init_data->curbuf && s->init_data->curbuf->head != NULL) {
const SigMatch *sm = s->init_data->curbuf->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 ||
sm->type == DETECT_ABSENT)) {
// just keep the same curbuf in case sticky_multi: all; transform; content: "abc";
// but update its id to the transfomed id
s->init_data->curbuf->id = new_list;
SCReturnInt(0);
}
if (SignatureInitDataBufferCheckExpand(s) < 0) { if (SignatureInitDataBufferCheckExpand(s) < 0) {
SCLogError("failed to expand rule buffer array"); SCLogError("failed to expand rule buffer array");
return -1; return -1;
} }
s->init_data->curbuf = &s->init_data->buffers[s->init_data->buffer_index++]; s->init_data->curbuf = &s->init_data->buffers[s->init_data->buffer_index++];
s->init_data->curbuf->multi_capable = s->init_data->curbuf->multi_capable =
DetectEngineBufferTypeSupportsMultiInstanceGetById(de_ctx, base_list); DetectEngineBufferTypeSupportsMultiInstanceGetById(de_ctx, base_list);

@ -54,6 +54,7 @@
#include "detect-content.h" #include "detect-content.h"
#include "detect-engine-payload.h" #include "detect-engine-payload.h"
#include "detect-engine-uint.h"
#include "stream.h" #include "stream.h"
@ -1234,8 +1235,24 @@ void RetrieveFPForSig(const DetectEngineCtx *de_ctx, Signature *s)
for (SigMatch *sm = s->init_data->buffers[x].head; sm != NULL; sm = sm->next) { for (SigMatch *sm = s->init_data->buffers[x].head; sm != NULL; sm = sm->next) {
// a buffer with absent keyword cannot be used as fast_pattern // a buffer with absent keyword cannot be used as fast_pattern
if (sm->type == DETECT_ABSENT) if (sm->type == DETECT_ABSENT || sm->type == DETECT_MULTI_ALL_OR_ABSENT)
break; break;
if (sm->type == DETECT_MULTI_NB) {
// Multi-buffers mode matches with "matches: 0" can succeed
// if there are no content that matches, so it cannot be used as fast pattern
const DetectU32Data *du32 = (DetectU32Data *)sm->ctx;
if (DetectU32Match(0, du32)) {
break;
}
}
if (sm->type == DETECT_MULTI_INDEX) {
// Multi-buffers mode index with "oob_or" can succeed
// if there are not enough elements, so it cannot be used as fast pattern
const DetectUintIndexPrecise *ip = (DetectUintIndexPrecise *)sm->ctx;
if (ip->oob) {
break;
}
}
if (sm->type != DETECT_CONTENT) if (sm->type != DETECT_CONTENT)
continue; continue;

@ -202,6 +202,7 @@
#include "detect-ja4-hash.h" #include "detect-ja4-hash.h"
#include "detect-ftp-command.h" #include "detect-ftp-command.h"
#include "detect-entropy.h" #include "detect-entropy.h"
#include "detect-multi.h"
#include "detect-ftp-command-data.h" #include "detect-ftp-command-data.h"
#include "detect-ftp-completion-code.h" #include "detect-ftp-completion-code.h"
#include "detect-ftp-reply.h" #include "detect-ftp-reply.h"
@ -723,6 +724,7 @@ void SigTableSetup(void)
DetectBytejumpRegister(); DetectBytejumpRegister();
DetectBytemathRegister(); DetectBytemathRegister();
DetectEntropyRegister(); DetectEntropyRegister();
DetectMultiRegister();
DetectSameipRegister(); DetectSameipRegister();
DetectGeoipRegister(); DetectGeoipRegister();
DetectL3ProtoRegister(); DetectL3ProtoRegister();

@ -103,6 +103,10 @@ enum DetectKeywordId {
DETECT_URILEN, DETECT_URILEN,
DETECT_ABSENT, DETECT_ABSENT,
DETECT_ENTROPY, DETECT_ENTROPY,
DETECT_MULTI_ALL,
DETECT_MULTI_ALL_OR_ABSENT,
DETECT_MULTI_NB,
DETECT_MULTI_INDEX,
/* end of content inspection */ /* end of content inspection */
DETECT_METADATA, DETECT_METADATA,

@ -57,6 +57,8 @@
#include "detect-engine-payload.h" #include "detect-engine-payload.h"
#include "detect-fast-pattern.h" #include "detect-fast-pattern.h"
#include "detect-byte-extract.h" #include "detect-byte-extract.h"
#include "detect-multi.h"
#include "detect-engine-uint.h"
#include "detect-content.h" #include "detect-content.h"
#include "detect-uricontent.h" #include "detect-uricontent.h"
#include "detect-tcphdr.h" #include "detect-tcphdr.h"
@ -2387,6 +2389,78 @@ uint8_t DetectEngineInspectMultiBufferGeneric(DetectEngineCtx *de_ctx,
transforms = engine->v2.transforms; transforms = engine->v2.transforms;
} }
bool stop_on_first_match = true;
const bool eof = DetectTxCompleted(f, txv, flags, engine);
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;
}
if (eof) {
return DETECT_ENGINE_INSPECT_SIG_CANT_MATCH;
}
// wait for buffer to be complete
buffer = DetectGetMultiData(det_ctx, transforms, f, flags, txv, engine->sm_list,
local_id + 1, engine->v2.GetMultiData);
if (buffer && buffer->inspect) {
// we have next buffer in the multi-buffer
return DETECT_ENGINE_INSPECT_SIG_CANT_MATCH;
}
return DETECT_ENGINE_INSPECT_SIG_NO_MATCH;
}
uint32_t nb_matches = 0;
do { do {
InspectionBuffer *buffer = DetectGetMultiData(det_ctx, transforms, f, flags, txv, InspectionBuffer *buffer = DetectGetMultiData(det_ctx, transforms, f, flags, txv,
engine->sm_list, local_id, engine->v2.GetMultiData); engine->sm_list, local_id, engine->v2.GetMultiData);
@ -2396,16 +2470,33 @@ uint8_t DetectEngineInspectMultiBufferGeneric(DetectEngineCtx *de_ctx,
// The GetData functions set buffer->flags to DETECT_CI_FLAGS_SINGLE // The GetData functions set buffer->flags to DETECT_CI_FLAGS_SINGLE
// This is not meant for streaming buffers // This is not meant for streaming buffers
const bool match = DetectEngineContentInspectionBuffer(de_ctx, det_ctx, s, engine->smd, const bool match = DetectEngineContentInspectionBuffer(de_ctx, det_ctx, s, smd, NULL, f,
NULL, f, buffer, DETECT_ENGINE_CONTENT_INSPECTION_MODE_STATE); buffer, DETECT_ENGINE_CONTENT_INSPECTION_MODE_STATE);
if (match) { if (match) {
return DETECT_ENGINE_INSPECT_SIG_MATCH; if (stop_on_first_match)
return DETECT_ENGINE_INSPECT_SIG_MATCH;
nb_matches++;
} }
local_id++; local_id++;
} while (1); } 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) { if (local_id == 0) {
// That means we did not get even one buffer value from the multi-buffer // That means we did not get even one buffer value from the multi-buffer
const bool eof = DetectTxCompleted(f, txv, flags, engine);
if (eof && engine->match_on_null) { if (eof && engine->match_on_null) {
return DETECT_ENGINE_INSPECT_SIG_MATCH; return DETECT_ENGINE_INSPECT_SIG_MATCH;
} }

@ -0,0 +1,129 @@
/* 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;
}
sigmatch_table[DETECT_MULTI_NB].Free(de_ctx, sm_ctx);
return -1;
case DetectMultiIndexPrecise:
prec = (DetectUintIndexPrecise *)sm_ctx;
if (prec->pos < 0) {
SCDetectMultiIndexFree(sm_ctx);
SCLogError("negative index is not yet supported");
return -1;
}
if (SCSigMatchAppendSMToList(
de_ctx, s, DETECT_MULTI_INDEX, sm_ctx, s->init_data->list) != NULL) {
return 0;
}
SCDetectMultiIndexFree(sm_ctx);
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;
// just a name for --engine-analysis
sigmatch_table[DETECT_MULTI_ALL].name = "__multi_all";
sigmatch_table[DETECT_MULTI_ALL_OR_ABSENT].name = "__multi_all_or_absent";
}
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;
}

@ -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

@ -36,6 +36,7 @@
#include "detect-content.h" #include "detect-content.h"
#include "detect-bsize.h" #include "detect-bsize.h"
#include "detect-isdataat.h" #include "detect-isdataat.h"
#include "detect-multi.h"
#include "detect-pcre.h" #include "detect-pcre.h"
#include "detect-uricontent.h" #include "detect-uricontent.h"
#include "detect-reference.h" #include "detect-reference.h"
@ -3115,6 +3116,9 @@ static int SigValidateCheckBuffers(
if (!DetectAbsentValidateContentCallback(s, b)) { if (!DetectAbsentValidateContentCallback(s, b)) {
SCReturnInt(0); SCReturnInt(0);
} }
if (!DetectMultiValidateContentCallback(s, b)) {
SCReturnInt(0);
}
} }
if (has_pmatch && has_frame) { if (has_pmatch && has_frame) {

Loading…
Cancel
Save