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
54 changes: 54 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,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-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
following keywords:

Expand Down
130 changes: 119 additions & 11 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,22 +79,35 @@ 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)))
}

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)
.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)
}

Expand All @@ -114,7 +134,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 @@ -131,7 +152,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 @@ -322,15 +343,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 @@ -998,6 +1019,93 @@ 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();
}
}
*it = DetectMultiIndex::DetectMultiIndexError;
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 {:?}",
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
Loading
Loading