detect: add arguments for multi-buffer

As for multi-integers, multi-buffers can now have the following
arguments
- count
- nb
- precise index
- all

Ticket: 5044
pull/14346/head
Philippe Antoine 3 months ago
parent 44c9e2ef4c
commit 9ac5323204

@ -90,6 +90,7 @@ include = [
"FtpDataStateValues",
"HTTP2TransactionState",
"DataRepType",
"DETECT_COUNT_INDEX",
]
# A list of items to not include in the generated bindings

@ -144,6 +144,8 @@ pub const SIGMATCH_INFO_MULTI_UINT: u32 = 0x80000; // BIT_U32(19)
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>

@ -1002,6 +1002,91 @@ 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),
// 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::*;

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

@ -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"
@ -641,6 +642,7 @@ void SigTableSetup(void)
DetectBytejumpRegister();
DetectBytemathRegister();
DetectEntropyRegister();
DetectMultiRegister();
DetectSameipRegister();
DetectGeoipRegister();
DetectL3ProtoRegister();

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

@ -56,6 +56,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"
@ -2156,6 +2158,81 @@ 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_COUNT:
// count should always be first and only
if (!DetectCountDoMatch(
det_ctx, f, flags, txv, engine->v2.GetMultiData, smd->ctx, eof)) {
return DETECT_ENGINE_INSPECT_SIG_NO_MATCH;
} else {
// only count
return DETECT_ENGINE_INSPECT_SIG_MATCH;
}
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;
}
uint32_t count = 0;
if (!engine->v2.GetMultiData(
det_ctx, txv, flags, DETECT_COUNT_INDEX, NULL, &count)) {
return DETECT_ENGINE_INSPECT_SIG_NO_MATCH;
}
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;
}
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;
}
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);
@ -2165,17 +2242,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;
}

@ -0,0 +1,135 @@
/* 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)
{
// First try to parse a count
DetectU32Data *du32 = SCDetectMultiCountParse(arg);
if (du32 != NULL) {
if (SCSigMatchAppendSMToList(de_ctx, s, DETECT_MULTI_COUNT, (SigMatchCtx *)du32,
s->init_data->list) != NULL) {
return 0;
}
SCLogError("error during count setup");
DetectDu32Free(de_ctx, du32);
return -1;
} // else not count
DetectMultiIndex index_type;
void *sm_ctx = SCDetectMultiIndexParse(arg, &index_type);
DetectAbsentData *dad;
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:
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;
}
}
bool DetectCountDoMatch(DetectEngineThreadCtx *det_ctx, Flow *f, const uint8_t flow_flags,
void *txv, InspectionMultiBufferGetDataPtr GetBuf, const SigMatchCtx *ctx, bool eof)
{
uint32_t count = 0;
DetectU32Data *du32 = (DetectU32Data *)ctx;
if (!eof && du32->mode != DETECT_UINT_GTE && du32->mode != DETECT_UINT_GT) {
return false;
}
if (!GetBuf(det_ctx, txv, flow_flags, DETECT_COUNT_INDEX, NULL, &count)) {
DEBUG_VALIDATE_BUG_ON(1);
return false;
}
return DetectU32Match(count, du32);
}
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_COUNT].name = "count";
sigmatch_table[DETECT_MULTI_COUNT].desc = "count number of buffers in a multi-buffer";
sigmatch_table[DETECT_MULTI_COUNT].Free = DetectDu32Free;
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;
}

@ -0,0 +1,26 @@
/* 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);
bool DetectCountDoMatch(DetectEngineThreadCtx *det_ctx, Flow *f, const uint8_t flow_flags,
void *txv, InspectionMultiBufferGetDataPtr GetBuf, const SigMatchCtx *ctx, bool eof);
int DetectMultiSetup(DetectEngineCtx *de_ctx, Signature *s, const char *arg);
#endif

@ -462,6 +462,13 @@ SigMatch *SCSigMatchAppendSMToList(
}
SCLogDebug("s->init_data->buffer_index %u", s->init_data->buffer_index);
}
} else if (s->init_data->curbuf->head != NULL &&
s->init_data->curbuf->head->type == DETECT_MULTI_COUNT) {
SCLogError("Cannot add other conditions after multi-buffer count, please reuse the "
"sticky buffer name to add conditions.");
new->ctx = NULL;
SigMatchFree(de_ctx, new);
return NULL;
}
BUG_ON(s->init_data->curbuf == NULL);

Loading…
Cancel
Save