detect/xbits: implement tx bits

Implement `xbits:set,mybit,track tx;` and `xbits:isset,mybit,track tx;`.

Store these in the AppLayerTxData.

Ticket: #6455.
This commit is contained in:
Victor Julien
2025-04-07 22:04:14 +02:00
committed by Victor Julien
parent 7c8a55de54
commit 10dcc383ae
11 changed files with 240 additions and 7 deletions
+1
View File
@@ -97,6 +97,7 @@ exclude = [
"AppLayerParserState",
"CLuaState",
"DetectEngineState",
"GenericVar",
"Flow",
"StreamingBufferConfig",
"HttpRangeContainerBlock",
+7 -1
View File
@@ -18,7 +18,7 @@
//! Parser registration functions and common interface module.
use std;
use crate::core::{self,DetectEngineState,AppLayerEventType};
use crate::core::{self,DetectEngineState,AppLayerEventType, GenericVar};
use crate::direction::Direction;
use crate::filecontainer::FileContainer;
use crate::flow::Flow;
@@ -147,6 +147,7 @@ pub struct AppLayerTxData {
de_state: *mut DetectEngineState,
pub events: *mut core::AppLayerDecoderEvents,
txbits: *mut GenericVar,
}
impl Default for AppLayerTxData {
@@ -175,6 +176,9 @@ impl AppLayerTxData {
if !self.events.is_null() {
core::sc_app_layer_decoder_events_free_events(&mut self.events);
}
if !self.txbits.is_null() {
core::sc_generic_var_free(self.txbits);
}
}
/// Create new AppLayerTxData for a transaction that covers both
@@ -196,6 +200,7 @@ impl AppLayerTxData {
detect_progress_tc: 0,
de_state: std::ptr::null_mut(),
events: std::ptr::null_mut(),
txbits: std::ptr::null_mut(),
}
}
@@ -222,6 +227,7 @@ impl AppLayerTxData {
flags,
de_state: std::ptr::null_mut(),
events: std::ptr::null_mut(),
txbits: std::ptr::null_mut(),
}
}
+16
View File
@@ -27,6 +27,7 @@ use crate::flow::Flow;
/// Opaque C types.
pub enum DetectEngineState {}
pub enum AppLayerDecoderEvents {}
pub enum GenericVar {}
#[repr(C)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
@@ -148,6 +149,9 @@ pub type SCFileContainerRecycle = extern "C" fn (
file_container: &FileContainer,
sbcfg: &StreamingBufferConfig);
pub type GenericVarFreeFunc =
extern "C" fn(gvar: *mut GenericVar);
// A Suricata context that is passed in from C. This is alternative to
// using functions from Suricata directly, so they can be wrapped so
// Rust unit tests will still compile when they are not linked
@@ -172,6 +176,8 @@ pub struct SuricataContext {
pub FileAppendData: SCFileAppendDataById,
pub FileAppendGAP: SCFileAppendGAPById,
pub FileContainerRecycle: SCFileContainerRecycle,
GenericVarFree: GenericVarFreeFunc,
}
#[allow(non_snake_case)]
@@ -210,6 +216,16 @@ pub fn sc_detect_engine_state_free(state: *mut DetectEngineState)
}
}
/// GenericVarFree wrapper.
pub fn sc_generic_var_free(gvar: *mut GenericVar)
{
unsafe {
if let Some(c) = SC {
(c.GenericVarFree)(gvar);
}
}
}
/// AppLayerParserTriggerRawStreamReassembly wrapper
pub fn sc_app_layer_parser_trigger_raw_stream_reassembly(flow: *const Flow, direction: i32) {
unsafe {
+2
View File
@@ -464,6 +464,7 @@ noinst_HEADERS = \
tm-threads-common.h \
tm-threads.h \
tree.h \
tx-bit.h \
interval-tree.h \
unix-manager.h \
util-action.h \
@@ -1036,6 +1037,7 @@ libsuricata_c_a_SOURCES = \
tm-queuehandlers.c \
tm-queues.c \
tm-threads.c \
tx-bit.c \
unix-manager.c \
util-action.c \
util-affinity.c \
+88 -6
View File
@@ -48,6 +48,8 @@
#include "flow-bit.h"
#include "host-bit.h"
#include "ippair-bit.h"
#include "tx-bit.h"
#include "util-var-name.h"
#include "util-unittest.h"
#include "util-debug.h"
@@ -59,6 +61,8 @@
#define PARSE_REGEX "^([a-z]+)" "(?:,\\s*([^,]+))?" "(?:,\\s*(?:track\\s+([^,]+)))" "(?:,\\s*(?:expire\\s+([^,]+)))?"
static DetectParseRegex parse_regex;
static int DetectXbitTxMatch(DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags, void *state,
void *txv, const Signature *s, const SigMatchCtx *ctx);
static int DetectXbitMatch (DetectEngineThreadCtx *, Packet *, const Signature *, const SigMatchCtx *);
static int DetectXbitSetup (DetectEngineCtx *, Signature *, const char *);
#ifdef UNITTESTS
@@ -71,6 +75,7 @@ void DetectXbitsRegister (void)
sigmatch_table[DETECT_XBITS].name = "xbits";
sigmatch_table[DETECT_XBITS].desc = "operate on bits";
sigmatch_table[DETECT_XBITS].url = "/rules/xbits.html";
sigmatch_table[DETECT_XBITS].AppLayerTxMatch = DetectXbitTxMatch;
sigmatch_table[DETECT_XBITS].Match = DetectXbitMatch;
sigmatch_table[DETECT_XBITS].Setup = DetectXbitSetup;
sigmatch_table[DETECT_XBITS].Free = DetectXbitFree;
@@ -158,6 +163,31 @@ static int DetectXbitMatchIPPair(Packet *p, const DetectXbitsData *xd)
return 0;
}
static int DetectXbitPostMatchTx(
DetectEngineThreadCtx *det_ctx, Packet *p, const Signature *s, const DetectXbitsData *xd)
{
if (p->flow == NULL)
return 0;
if (!det_ctx->tx_id_set)
return 0;
Flow *f = p->flow;
void *txv = AppLayerParserGetTx(f->proto, f->alproto, f->alstate, det_ctx->tx_id);
if (txv == NULL)
return 0;
AppLayerTxData *txd = AppLayerParserGetTxData(f->proto, f->alproto, txv);
if (txd == NULL) {
return 0;
}
if (xd->cmd != DETECT_XBITS_CMD_SET)
return 0;
SCLogDebug("sid %u: post-match SET for bit %u on tx:%" PRIu64 ", txd:%p", s->id, xd->idx,
det_ctx->tx_id, txd);
return TxBitSet(txd, xd->idx);
}
/*
* returns 0: no match
* 1: match
@@ -177,12 +207,35 @@ static int DetectXbitMatch (DetectEngineThreadCtx *det_ctx, Packet *p, const Sig
case VAR_TYPE_IPPAIR_BIT:
return DetectXbitMatchIPPair(p, (const DetectXbitsData *)fd);
break;
case VAR_TYPE_TX_BIT:
// TODO this is for PostMatch only. Can we validate somehow?
return DetectXbitPostMatchTx(det_ctx, p, s, fd);
break;
default:
break;
}
return 0;
}
static int DetectXbitTxMatch(DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags, void *state,
void *txv, const Signature *s, const SigMatchCtx *ctx)
{
const DetectXbitsData *xd = (const DetectXbitsData *)ctx;
BUG_ON(xd == NULL);
AppLayerTxData *txd = AppLayerParserGetTxData(f->proto, f->alproto, txv);
if (txd == NULL) {
return 0;
}
SCLogDebug("sid:%u: tx:%" PRIu64 ", txd->txbits:%p", s->id, det_ctx->tx_id, txd->txbits);
int r = TxBitIsset(txd, xd->idx);
if (r == 1) {
return DETECT_ENGINE_INSPECT_SIG_MATCH;
}
return DETECT_ENGINE_INSPECT_SIG_NO_MATCH;
}
/** \internal
* \brief parse xbits rule options
* \retval 0 ok
@@ -246,6 +299,9 @@ static int DetectXbitParse(DetectEngineCtx *de_ctx,
} else if (strcmp(hb_dir_str, "ip_pair") == 0) {
hb_dir = DETECT_XBITS_TRACK_IPPAIR;
var_type = VAR_TYPE_IPPAIR_BIT;
} else if (strcmp(hb_dir_str, "tx") == 0) {
hb_dir = DETECT_XBITS_TRACK_TX;
var_type = VAR_TYPE_TX_BIT;
} else {
// TODO
pcre2_match_data_free(match);
@@ -317,6 +373,13 @@ static int DetectXbitParse(DetectEngineCtx *de_ctx,
break;
}
if (hb_dir == DETECT_XBITS_TRACK_TX) {
if (fb_cmd != DETECT_XBITS_CMD_ISSET && fb_cmd != DETECT_XBITS_CMD_SET) {
SCLogError("tx xbits only support set and isset");
return -1;
}
}
cd = SCMalloc(sizeof(DetectXbitsData));
if (unlikely(cd == NULL))
return -1;
@@ -327,8 +390,8 @@ static int DetectXbitParse(DetectEngineCtx *de_ctx,
cd->type = var_type;
cd->expire = expire;
SCLogDebug("idx %" PRIu32 ", cmd %s, name %s",
cd->idx, fb_cmd_str, strlen(fb_name) ? fb_name : "(none)");
SCLogDebug("idx %" PRIu32 ", cmd %s, name %s", cd->idx, fb_cmd_str,
strlen(fb_name) ? fb_name : "(none)");
*cdout = cd;
return 0;
@@ -352,18 +415,37 @@ int DetectXbitSetup (DetectEngineCtx *de_ctx, Signature *s, const char *rawstr)
switch (cd->cmd) {
/* case DETECT_XBITS_CMD_NOALERT can't happen here */
case DETECT_XBITS_CMD_ISNOTSET:
case DETECT_XBITS_CMD_ISSET:
case DETECT_XBITS_CMD_ISSET: {
int list = DETECT_SM_LIST_MATCH;
if (cd->tracker == DETECT_XBITS_TRACK_TX) {
SCLogDebug("tx xbit isset");
if (s->init_data->hook.type != SIGNATURE_HOOK_TYPE_APP) {
SCLogError("tx xbits require an explicit rule hook");
SCFree(cd);
return -1;
}
list = s->init_data->hook.sm_list;
SCLogDebug("setting list %d", list);
if (list == -1) {
SCLogError("tx xbits failed to set up"); // TODO how would we get here?
SCFree(cd);
return -1;
}
}
SCLogDebug("adding match/txmatch");
/* checks, so packet list */
if (SigMatchAppendSMToList(
de_ctx, s, DETECT_XBITS, (SigMatchCtx *)cd, DETECT_SM_LIST_MATCH) == NULL) {
if (SigMatchAppendSMToList(de_ctx, s, DETECT_XBITS, (SigMatchCtx *)cd, list) == NULL) {
SCFree(cd);
return -1;
}
break;
}
// all other cases
// DETECT_XBITS_CMD_SET, DETECT_XBITS_CMD_UNSET, DETECT_XBITS_CMD_TOGGLE:
default:
SCLogDebug("adding post-match");
/* modifiers, only run when entire sig has matched */
if (SigMatchAppendSMToList(de_ctx, s, DETECT_XBITS, (SigMatchCtx *)cd,
DETECT_SM_LIST_POSTMATCH) == NULL) {
+1
View File
@@ -34,6 +34,7 @@
#define DETECT_XBITS_TRACK_IPSRC 0
#define DETECT_XBITS_TRACK_IPDST 1
#define DETECT_XBITS_TRACK_IPPAIR 2
#define DETECT_XBITS_TRACK_TX 3
#define DETECT_XBITS_EXPIRE_DEFAULT 30
+3
View File
@@ -21,6 +21,7 @@
#include "app-layer-register.h"
#include "app-layer-htp-range.h"
#include "app-layer-htp-file.h"
#include "util-var.h"
const SuricataContext suricata_context = {
SCLogMessage,
@@ -37,6 +38,8 @@ const SuricataContext suricata_context = {
FileAppendDataById,
FileAppendGAPById,
FileContainerRecycle,
GenericVarFree,
};
const SuricataContext *SCGetContext(void)
+3
View File
@@ -27,6 +27,7 @@
#include "util-debug.h"
#include "util-file.h"
#include "util-var.h"
// hack for include orders cf SCSha256
typedef struct HttpRangeContainerBlock HttpRangeContainerBlock;
@@ -56,6 +57,8 @@ typedef struct SuricataContext_ {
int (*FileAppendGAPById)(FileContainer *, const StreamingBufferConfig *, uint32_t track_id,
const uint8_t *data, uint32_t data_len);
void (*FileContainerRecycle)(FileContainer *ffc, const StreamingBufferConfig *);
void (*GenericVarFree)(GenericVar *);
} SuricataContext;
extern const SuricataContext suricata_context;
+85
View File
@@ -0,0 +1,85 @@
/* Copyright (C) 2014-2021 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.
*/
/**
* \file
*
* \author Victor Julien <[email protected]>
*
* Implements per ippair bits. Actually, not a bit,
* but called that way because of Snort's flowbits.
* It's a binary storage.
*
* \todo move away from a linked list implementation
* \todo use different datatypes, such as string, int, etc.
*/
#include "suricata-common.h"
#include "threads.h"
#include "tx-bit.h"
#include "detect.h"
#include "util-var.h"
#include "util-debug.h"
#include "rust.h"
static XBit *TxBitGet(AppLayerTxData *txd, uint32_t idx)
{
for (GenericVar *gv = txd->txbits; gv != NULL; gv = gv->next) {
if (gv->type == DETECT_XBITS && gv->idx == idx) {
return (XBit *)gv;
}
}
return NULL;
}
static int TxBitAdd(AppLayerTxData *txd, uint32_t idx)
{
XBit *xb = TxBitGet(txd, idx);
if (xb == NULL) {
xb = SCMalloc(sizeof(XBit));
if (unlikely(xb == NULL))
return -1;
xb->type = DETECT_XBITS;
xb->idx = idx;
xb->next = NULL;
xb->expire = 0; // not used by tx bits
GenericVarAppend(&txd->txbits, (GenericVar *)xb);
return 1;
}
return 0;
}
int TxBitIsset(AppLayerTxData *txd, uint32_t idx)
{
XBit *xb = TxBitGet(txd, idx);
if (xb != NULL) {
SCLogDebug("isset %u return 1", idx);
return 1;
}
SCLogDebug("isset %u r 0", idx);
return 0;
}
int TxBitSet(AppLayerTxData *txd, uint32_t idx)
{
int r = TxBitAdd(txd, idx);
SCLogDebug("set %u r %d", idx, r);
return (r == 1);
}
+32
View File
@@ -0,0 +1,32 @@
/* Copyright (C) 2007-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.
*/
/**
* \file
*
* \author Victor Julien <[email protected]>
*/
#ifndef SURICATA_TX_BIT_H
#define SURICATA_TX_BIT_H
#include "rust.h"
int TxBitIsset(AppLayerTxData *txd, uint32_t idx);
int TxBitSet(AppLayerTxData *txd, uint32_t idx);
#endif /* SURICATA_TX_BIT_H */
+2
View File
@@ -44,6 +44,8 @@ enum VarTypes {
VAR_TYPE_IPPAIR_BIT,
VAR_TYPE_IPPAIR_INT,
VAR_TYPE_IPPAIR_VAR,
VAR_TYPE_TX_BIT,
};
typedef struct GenericVar_ {