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
15 changes: 14 additions & 1 deletion doc/userguide/rules/integer-keywords.rst
Original file line number Diff line number Diff line change
Expand Up @@ -142,4 +142,17 @@ For the array [1,2,3,4,5,6], here are some examples:
* 3:-1 will have subslice [4,5]
* -4:4 will have subslice [3,4]

If one index is out of bounds, an empty subslice is used.
If one index is out of bounds, an empty subslice is used.

Count
-----

Multi-integer can also just count the number of occurences
without matching to a specific value.

The syntax is::
keyword: count [mode] value;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should we document the usages such as all, all1, nb etc that are also valid?

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.

TODOs :

  • update doc

That is what I meant

Unless you are referring to #13897 ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I had missed the TODO description in the PR, apologies.


Examples::

http2.window:count >5;
4 changes: 2 additions & 2 deletions etc/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1593,7 +1593,7 @@
"type": "string",
"suricata": {
"keywords": [
"enip.command"
"enip_command"
]
}
},
Expand Down Expand Up @@ -1692,7 +1692,7 @@
"type": "string",
"suricata": {
"keywords": [
"enip.command"
"enip_command"
]
}
},
Expand Down
1 change: 1 addition & 0 deletions rust/cbindgen.toml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ include = [
"FtpDataStateValues",
"HTTP2TransactionState",
"DataRepType",
"DETECT_COUNT_INDEX",
]

# A list of items to not include in the generated bindings
Expand Down
2 changes: 2 additions & 0 deletions rust/src/detect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ 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 <big|little|dce>
Expand Down
134 changes: 134 additions & 0 deletions rust/src/detect/uint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ pub enum DetectUintIndex {
OrAbsent,
Index((bool, i32)),
NumberMatches(DetectUintData<u32>),
Count(DetectUintData<u32>),
}

#[derive(Debug, PartialEq)]
Expand Down Expand Up @@ -109,6 +110,14 @@ fn parse_uint_subslice(parts: &[&str]) -> Option<(i32, i32)> {
return Some((start, end));
}

fn parse_multi_count(s: &str) -> IResult<&str, DetectUintData<u32>> {
let (s, _) = opt(is_a(" "))(s)?;
let (s, _) = tag("count")(s)?;
let (s, _) = opt(is_a(" "))(s)?;
let (s, du32) = detect_parse_uint::<u32>(s)?;
Ok((s, du32))
}

fn parse_uint_index(parts: &[&str]) -> Option<DetectUintIndex> {
let index = if parts.len() >= 2 {
match parts[1] {
Expand All @@ -119,6 +128,8 @@ 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_multi_count(parts[0]) {
DetectUintIndex::Count(du)
} else {
DetectUintIndex::Any
};
Expand All @@ -132,6 +143,19 @@ pub(crate) fn detect_parse_array_uint<T: DetectIntType>(s: &str) -> Option<Detec
}

let index = parse_uint_index(&parts)?;
if let DetectUintIndex::Count(_) = &index {
return Some(DetectUintArrayData {
du: DetectUintData::<T> {
arg1: T::min_value(),
arg2: T::min_value(),
mode: DetectUintMode::DetectUintModeEqual,
},
index,
start: 0,
end: 0,
});
}

let (_, du) = detect_parse_uint::<T>(parts[0]).ok()?;
let (start, end) = parse_uint_subslice(&parts)?;

Expand All @@ -152,6 +176,19 @@ pub(crate) fn detect_parse_array_uint_enum<T1: DetectIntType, T2: EnumString<T1>
}

let index = parse_uint_index(&parts)?;
if let DetectUintIndex::Count(_) = &index {
return Some(DetectUintArrayData {
du: DetectUintData::<T1> {
arg1: T1::min_value(),
arg2: T1::min_value(),
mode: DetectUintMode::DetectUintModeEqual,
},
index,
start: 0,
end: 0,
});
}

let du = detect_parse_uint_enum::<T1, T2>(parts[0])?;
let (start, end) = parse_uint_subslice(&parts)?;

Expand Down Expand Up @@ -229,6 +266,26 @@ pub(crate) fn detect_uint_match_at_index<T, U: DetectIntType>(
}
return 0;
}
DetectUintIndex::Count(du32) => {
if !eof {
match du32.mode {
DetectUintMode::DetectUintModeGt | DetectUintMode::DetectUintModeGte => {}
_ => {
return 0;
}
}
}
let mut nb = 0u32;
for response in subslice {
if get_value(response).is_some() {
nb += 1;
}
}
if detect_match_uint(du32, nb) {
return 1;
}
return 0;
}
DetectUintIndex::All => {
if !eof {
return 0;
Expand Down Expand Up @@ -764,6 +821,83 @@ 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,
DetectMultiIndexAll1,
DetectMultiIndexNb,
DetectMultiIndexPrecise,
DetectMultiIndexError,
}

impl DetectUintIndex {
// just borrow
fn index(&self) -> DetectMultiIndex {
match self {
DetectUintIndex::All => DetectMultiIndex::DetectMultiIndexAll,
DetectUintIndex::All1 => DetectMultiIndex::DetectMultiIndexAll1,
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
}
// TODO DetectUintIndex::Index(_)
_ => std::ptr::null_mut(),
}
}
}

fn parse_multi_index(s: &str) -> Option<DetectUintIndex> {
match s {
"all" => Some(DetectUintIndex::All),
"all1" => Some(DetectUintIndex::All1),
"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::*;
Expand Down
20 changes: 20 additions & 0 deletions rust/src/mime/detect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down Expand Up @@ -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 \
Expand Down
12 changes: 11 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 @@ -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;
}

Expand All @@ -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;
}
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions src/detect-engine-register.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -641,6 +642,7 @@ void SigTableSetup(void)
DetectBytejumpRegister();
DetectBytemathRegister();
DetectEntropyRegister();
DetectCountRegister();
DetectSameipRegister();
DetectGeoipRegister();
DetectL3ProtoRegister();
Expand Down
4 changes: 4 additions & 0 deletions src/detect-engine-register.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ enum DetectKeywordId {
DETECT_URILEN,
DETECT_ABSENT,
DETECT_ENTROPY,
DETECT_COUNT,
DETECT_MULTI_ALL,
DETECT_MULTI_ALL1,
DETECT_MULTI_NB,
/* end of content inspection */

DETECT_METADATA,
Expand Down
Loading
Loading