Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions doc/userguide/rules/multi-buffer-matching.rst
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,46 @@ 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-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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggest adding something like

(same as `index <value>`)

any Match with any index
or_absent Match with any index or matches on an empty list

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be any_or_absent for clarity?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok (both work in the code)

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 nb Matches a number of times
index i Match specific index
oob_or i 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;)


List of multi-buffers
---------------------

Multiple buffer matching is currently enabled for use with the
following keywords:

Expand Down
121 changes: 111 additions & 10 deletions rust/src/detect/uint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,20 @@ pub struct DetectUintData<T> {
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<u32>),
Count(DetectUintData<u32>),
}
Expand All @@ -72,13 +79,20 @@ pub struct DetectUintArrayData<T> {

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::<u32>(s)?;
Ok((s, DetectUintIndex::NumberMatches(du32)))
Expand Down Expand Up @@ -112,7 +126,8 @@ fn parse_uint_subslice(parts: &[&str]) -> Option<(i32, i32)> {
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, _) = opt(is_a(" ")).parse(s)?;
let (s, du32) = detect_parse_uint::<u32>(s)?;
Expand All @@ -129,7 +144,7 @@ fn parse_uint_index(parts: &[&str]) -> Option<DetectUintIndex> {
// 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
Expand Down Expand Up @@ -320,15 +335,15 @@ pub(crate) fn detect_uint_match_at_index<T, U: DetectIntType>(
}
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;
Expand Down Expand Up @@ -988,6 +1003,92 @@ pub unsafe extern "C" fn SCDetectU16Free(ctx: &mut DetectUintData<u16>) {
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();
}
}
return std::ptr::null_mut();
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
36 changes: 21 additions & 15 deletions rust/src/detect/vlan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -49,7 +49,7 @@ pub fn detect_parse_vlan_id(s: &str) -> Option<DetectUintArrayData<u16>> {
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 {
Expand All @@ -59,8 +59,8 @@ pub fn detect_parse_vlan_id(s: &str) -> Option<DetectUintArrayData<u16>> {
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)
Expand Down Expand Up @@ -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 {
Expand All @@ -127,12 +130,12 @@ pub unsafe extern "C" fn SCDetectVlanIdPrefilterMatch(
pub unsafe extern "C" fn SCDetectVlanIdPrefilter(
ctx: &DetectUintArrayData<u16>,
) -> 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,
};
Expand All @@ -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,
}
Expand Down Expand Up @@ -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,
}
Expand All @@ -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,
}
Expand All @@ -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,
}
Expand All @@ -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,
}
Expand All @@ -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,
}
Expand All @@ -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,
}
Expand Down
2 changes: 2 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,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 \
Expand Down Expand Up @@ -823,6 +824,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 \
Expand Down
7 changes: 6 additions & 1 deletion src/detect-email.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -330,7 +335,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);
Expand Down
Loading
Loading