From: Victor Julien Date: Fri, 21 Mar 2025 08:42:09 +0000 (+0100) Subject: detect/xbits: implement tx bits X-Git-Tag: suricata-8.0.0-beta1~21 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=10dcc383ae9a5021050e958e49bcd2d78af7824d;p=thirdparty%2Fsuricata.git detect/xbits: implement tx bits Implement `xbits:set,mybit,track tx;` and `xbits:isset,mybit,track tx;`. Store these in the AppLayerTxData. Ticket: #6455. --- diff --git a/rust/cbindgen.toml b/rust/cbindgen.toml index bca938c83c..b3a9b18ad3 100644 --- a/rust/cbindgen.toml +++ b/rust/cbindgen.toml @@ -97,6 +97,7 @@ exclude = [ "AppLayerParserState", "CLuaState", "DetectEngineState", + "GenericVar", "Flow", "StreamingBufferConfig", "HttpRangeContainerBlock", diff --git a/rust/src/applayer.rs b/rust/src/applayer.rs index 0f0fcd7c2c..debfb56cbb 100644 --- a/rust/src/applayer.rs +++ b/rust/src/applayer.rs @@ -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(), } } diff --git a/rust/src/core.rs b/rust/src/core.rs index 59be1e52fb..60978771c0 100644 --- a/rust/src/core.rs +++ b/rust/src/core.rs @@ -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 { diff --git a/src/Makefile.am b/src/Makefile.am index 6fd83f11b5..2a98ebbeb3 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 \ diff --git a/src/detect-xbits.c b/src/detect-xbits.c index 31ba97bafc..a02a8ea50f 100644 --- a/src/detect-xbits.c +++ b/src/detect-xbits.c @@ -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) { diff --git a/src/detect-xbits.h b/src/detect-xbits.h index 07d662ea51..e572e4d368 100644 --- a/src/detect-xbits.h +++ b/src/detect-xbits.h @@ -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 diff --git a/src/rust-context.c b/src/rust-context.c index 1b7131a65d..ef28c03351 100644 --- a/src/rust-context.c +++ b/src/rust-context.c @@ -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) diff --git a/src/rust-context.h b/src/rust-context.h index 15495c3bea..9aaf532953 100644 --- a/src/rust-context.h +++ b/src/rust-context.h @@ -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; diff --git a/src/tx-bit.c b/src/tx-bit.c new file mode 100644 index 0000000000..b26e8b3e9a --- /dev/null +++ b/src/tx-bit.c @@ -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 + * + * 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); +} diff --git a/src/tx-bit.h b/src/tx-bit.h new file mode 100644 index 0000000000..aba0b1ea7a --- /dev/null +++ b/src/tx-bit.h @@ -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 + */ + +#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 */ diff --git a/src/util-var.h b/src/util-var.h index 620a923d71..a67730045f 100644 --- a/src/util-var.h +++ b/src/util-var.h @@ -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_ {