]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
detect/xbits: implement tx bits
authorVictor Julien <vjulien@oisf.net>
Fri, 21 Mar 2025 08:42:09 +0000 (09:42 +0100)
committerVictor Julien <victor@inliniac.net>
Mon, 7 Apr 2025 20:04:14 +0000 (22:04 +0200)
Implement `xbits:set,mybit,track tx;` and `xbits:isset,mybit,track tx;`.

Store these in the AppLayerTxData.

Ticket: #6455.

rust/cbindgen.toml
rust/src/applayer.rs
rust/src/core.rs
src/Makefile.am
src/detect-xbits.c
src/detect-xbits.h
src/rust-context.c
src/rust-context.h
src/tx-bit.c [new file with mode: 0644]
src/tx-bit.h [new file with mode: 0644]
src/util-var.h

index bca938c83c1ee322facc015b790ea34f45679336..b3a9b18ad3fa03df234acc97877a68c858241405 100644 (file)
@@ -97,6 +97,7 @@ exclude = [
     "AppLayerParserState",
     "CLuaState",
     "DetectEngineState",
+    "GenericVar",
     "Flow",
     "StreamingBufferConfig",
     "HttpRangeContainerBlock",
index 0f0fcd7c2c375d4e95c5036418b9703d9cb50a1a..debfb56cbbb26e03bdbd7ab622f70c3132e2cd71 100644 (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(),
         }
     }
 
index 59be1e52fb6f5822809feddddf1080dcdf806f83..60978771c0dca3b7e59f3a1bef09b74d7abfbd10 100644 (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 {
index 6fd83f11b5e1048230717fac8f62d2a11b245463..2a98ebbeb3984d4e48ac19946c2d65ba5e0b374b 100755 (executable)
@@ -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 \
index 31ba97bafc899be9ec6fd6d0caa865d51d14391c..a02a8ea50fc6cffd4b6d64352e68d80d669d714a 100644 (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) {
index 07d662ea51d117e677ce93bae51420cdae942072..e572e4d3689f29946aa23fe7dc9ef5a9a137fb43 100644 (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
 
index 1b7131a65d0e0ac2033da3780ec928ba7a6fec5d..ef28c03351300bb86b84e352ec7a0fb21d689bac 100644 (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)
index 15495c3bea8664a4f565fe0bee0151e7ca210965..9aaf53295310dd85ffe1e82ab83b9844903f86e8 100644 (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;
diff --git a/src/tx-bit.c b/src/tx-bit.c
new file mode 100644 (file)
index 0000000..b26e8b3
--- /dev/null
@@ -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 <victor@inliniac.net>
+ *
+ * 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 (file)
index 0000000..aba0b1e
--- /dev/null
@@ -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 <victor@inliniac.net>
+ */
+
+#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 */
index 620a923d710f64cb8442ddb27a5944b904a8ae51..a67730045fdbf74fbc2aca749561af658f311e61 100644 (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_ {