]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
detect/app-layer-state: keyword for protocol state
authorVictor Julien <vjulien@oisf.net>
Fri, 14 Mar 2025 15:20:15 +0000 (16:20 +0100)
committerVictor Julien <victor@inliniac.net>
Mon, 7 Apr 2025 20:04:14 +0000 (22:04 +0200)
Allow matching on the app-layer protocol state.

doc/userguide/rules/app-layer.rst
src/Makefile.am
src/detect-app-layer-state.c [new file with mode: 0644]
src/detect-app-layer-state.h [new file with mode: 0644]
src/detect-engine-prefilter.c
src/detect-engine-register.c
src/detect-engine-register.h
src/flow.h

index 2804fcb5d1a0127cf9cc63a592a486baac4f282d..330b38cf750d4087fcc243390befc829e24a3ef4 100644 (file)
@@ -97,3 +97,16 @@ applayer_proto_detection_skipped
 
 Protocol detection was skipped because of :ref:`proto-detect-bail-out`.
 
+app-layer-state
+---------------
+
+Match on the detected app-layer protocol transaction state.
+
+Syntax::
+
+    app-layer-state:[<>]<state>;
+
+Examples::
+
+    app-layer-state:request_headers;
+    app-layer-state:>request_body;
index 50eeb79c319d0383a3d64f784e253e697398320b..6fd83f11b5e1048230717fac8f62d2a11b245463 100755 (executable)
@@ -88,6 +88,7 @@ noinst_HEADERS = \
        defrag-timeout.h \
        detect-app-layer-event.h \
        detect-app-layer-protocol.h \
+       detect-app-layer-state.h \
        detect-asn1.h \
        detect-base64-data.h \
        detect-base64-decode.h \
@@ -673,6 +674,7 @@ libsuricata_c_a_SOURCES = \
        defrag-timeout.c \
        detect-app-layer-event.c \
        detect-app-layer-protocol.c \
+       detect-app-layer-state.c \
        detect-asn1.c \
        detect-base64-data.c \
        detect-base64-decode.c \
diff --git a/src/detect-app-layer-state.c b/src/detect-app-layer-state.c
new file mode 100644 (file)
index 0000000..fc3c73e
--- /dev/null
@@ -0,0 +1,229 @@
+/* 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 <vjulien@oisf.net>
+ */
+
+#include "suricata-common.h"
+#include "threads.h"
+#include "decode.h"
+
+#include "app-layer.h"
+#include "app-layer-protos.h"
+#include "app-layer-parser.h"
+#include "app-layer-smtp.h"
+#include "detect.h"
+#include "detect-parse.h"
+#include "detect-engine.h"
+#include "detect-engine-state.h"
+#include "detect-engine-build.h"
+#include "detect-app-layer-state.h"
+
+#include "flow.h"
+#include "flow-var.h"
+#include "flow-util.h"
+
+#include "decode-events.h"
+#include "util-byte.h"
+#include "util-debug.h"
+#include "util-enum.h"
+#include "util-profiling.h"
+#include "util-unittest.h"
+#include "util-unittest-helper.h"
+#include "stream-tcp-util.h"
+
+typedef struct DetectAppLayerStateData_ {
+    uint8_t progress;
+    int8_t mode;
+} DetectAppLayerStateData;
+
+static int DetectAppLayerStateSetup(DetectEngineCtx *, Signature *, const char *);
+static void DetectAppLayerStateFree(DetectEngineCtx *, void *);
+static uint8_t DetectEngineAptStateInspect(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
+        const struct DetectEngineAppInspectionEngine_ *engine, const Signature *s, Flow *f,
+        uint8_t flags, void *alstate, void *tx, uint64_t tx_id);
+static int g_applayer_state_list_id = 0;
+
+void DetectAppLayerStateRegister(void)
+{
+    sigmatch_table[DETECT_APP_LAYER_STATE].name = "app-layer-state";
+    sigmatch_table[DETECT_APP_LAYER_STATE].desc =
+            "match on events generated by the App Layer Parsers and the protocol detection engine";
+    sigmatch_table[DETECT_APP_LAYER_STATE].url = "/rules/app-layer.html#app-layer-event";
+    sigmatch_table[DETECT_APP_LAYER_STATE].Setup = DetectAppLayerStateSetup;
+    sigmatch_table[DETECT_APP_LAYER_STATE].Free = DetectAppLayerStateFree;
+
+    DetectAppLayerInspectEngineRegister("app-layer-state", ALPROTO_UNKNOWN, SIG_FLAG_TOSERVER, 0,
+            DetectEngineAptStateInspect, NULL);
+    DetectAppLayerInspectEngineRegister("app-layer-state", ALPROTO_UNKNOWN, SIG_FLAG_TOCLIENT, 0,
+            DetectEngineAptStateInspect, NULL);
+
+    g_applayer_state_list_id = DetectBufferTypeGetByName("app-layer-state");
+}
+
+static uint8_t DetectEngineAptStateInspect(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
+        const struct DetectEngineAppInspectionEngine_ *engine, const Signature *s, Flow *f,
+        uint8_t flags, void *alstate, void *tx, uint64_t tx_id)
+{
+    int r = 0;
+    const AppProto alproto = f->alproto;
+    const uint8_t tx_progress =
+            (uint8_t)AppLayerParserGetStateProgress(f->proto, alproto, tx, flags);
+
+    const SigMatchData *smd = engine->smd;
+    while (1) {
+        const DetectAppLayerStateData *data = (const DetectAppLayerStateData *)smd->ctx;
+        KEYWORD_PROFILING_START;
+
+        bool match = false;
+        if (data->mode == -1) {
+            SCLogDebug("sid:%u tx_progress %u < keyword progress %u ?", s->id, tx_progress,
+                    data->progress);
+            if (tx_progress < data->progress) {
+                match = true;
+            }
+        } else if (data->mode == 1) {
+            SCLogDebug("sid:%u tx_progress %u > keyword progress %u ?", s->id, tx_progress,
+                    data->progress);
+            if (tx_progress > data->progress) {
+                match = true;
+            }
+        } else {
+            BUG_ON(1);
+        }
+
+        if (match) {
+            KEYWORD_PROFILING_END(det_ctx, smd->type, 1);
+
+            if (smd->is_last)
+                break;
+            smd++;
+            continue;
+        }
+
+        KEYWORD_PROFILING_END(det_ctx, smd->type, 0);
+        goto end;
+    }
+    r = 1;
+
+end:
+    if (r == 1) {
+        SCLogDebug("DETECT_ENGINE_INSPECT_SIG_MATCH");
+        return DETECT_ENGINE_INSPECT_SIG_MATCH;
+    } else {
+        if (AppLayerParserGetStateProgress(f->proto, alproto, tx, flags) ==
+                AppLayerParserGetStateProgressCompletionStatus(alproto, flags)) {
+            SCLogDebug("DETECT_ENGINE_INSPECT_SIG_CANT_MATCH");
+            return DETECT_ENGINE_INSPECT_SIG_CANT_MATCH;
+        } else {
+            SCLogDebug("DETECT_ENGINE_INSPECT_SIG_NO_MATCH");
+            return DETECT_ENGINE_INSPECT_SIG_NO_MATCH;
+        }
+    }
+}
+
+// TODO dedup with detect-parse.c
+static SignatureHook SetAppHook(const AppProto alproto, int progress)
+{
+    SignatureHook h = {
+        .type = SIGNATURE_HOOK_TYPE_APP,
+        .t.app.alproto = alproto,
+        .t.app.app_progress = progress,
+    };
+    return h;
+}
+
+static int DetectAppLayerStateSetup(DetectEngineCtx *de_ctx, Signature *s, const char *arg)
+{
+    if (s->alproto == ALPROTO_UNKNOWN) {
+        return -1;
+    }
+
+    int mode = 0;
+
+    if (strlen(arg) > 0) {
+        if (arg[0] == '<') {
+            mode = -1;
+            arg++;
+        } else if (arg[0] == '>') {
+            mode = 1;
+            arg++;
+        }
+    }
+
+    if (mode == 0) {
+        const char *h = arg;
+
+        const int progress_ts = AppLayerParserGetStateIdByName(
+                IPPROTO_TCP /* TODO */, s->alproto, h, STREAM_TOSERVER);
+        if (progress_ts >= 0) {
+            s->flags |= SIG_FLAG_TOSERVER;
+            s->init_data->hook = SetAppHook(s->alproto, progress_ts);
+        } else {
+            const int progress_tc = AppLayerParserGetStateIdByName(
+                    IPPROTO_TCP /* TODO */, s->alproto, h, STREAM_TOCLIENT);
+            if (progress_tc < 0) {
+                return -1;
+            }
+            s->flags |= SIG_FLAG_TOCLIENT;
+            s->init_data->hook = SetAppHook(s->alproto, progress_tc);
+        }
+        SCLogDebug("hook %u", s->init_data->hook.t.app.app_progress);
+        return 0;
+    }
+
+    int progress = 0;
+    const char *h = arg;
+    const int progress_ts =
+            AppLayerParserGetStateIdByName(IPPROTO_TCP /* TODO */, s->alproto, h, STREAM_TOSERVER);
+    if (progress_ts >= 0) {
+        s->flags |= SIG_FLAG_TOSERVER;
+        progress = progress_ts;
+    } else {
+        const int progress_tc = AppLayerParserGetStateIdByName(
+                IPPROTO_TCP /* TODO */, s->alproto, h, STREAM_TOCLIENT);
+        if (progress_tc < 0) {
+            return -1;
+        }
+        s->flags |= SIG_FLAG_TOCLIENT;
+        progress = progress_tc;
+    }
+
+    DetectAppLayerStateData *data = SCCalloc(1, sizeof(*data));
+    if (data == NULL)
+        return -1;
+
+    data->progress = (uint8_t)progress;
+    data->mode = (int8_t)mode;
+
+    if (SigMatchAppendSMToList(de_ctx, s, DETECT_APP_LAYER_STATE, (SigMatchCtx *)data,
+                g_applayer_state_list_id) == NULL) {
+        SCFree(data);
+        return -1;
+    }
+    s->flags |= SIG_FLAG_APPLAYER;
+
+    return 0;
+}
+
+static void DetectAppLayerStateFree(DetectEngineCtx *de_ctx, void *ptr)
+{
+    SCFree(ptr);
+}
diff --git a/src/detect-app-layer-state.h b/src/detect-app-layer-state.h
new file mode 100644 (file)
index 0000000..0428111
--- /dev/null
@@ -0,0 +1,29 @@
+/* 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 <vjulien@oisf.net>
+ */
+
+#ifndef SURICATA_DETECT_APP_LAYER_STATE_H
+#define SURICATA_DETECT_APP_LAYER_STATE_H
+
+void DetectAppLayerStateRegister(void);
+
+#endif /* SURICATA_DETECT_APP_LAYER_STATE_H */
index 5f6ab3f0171c18ed758bea2602b5e9f047dba88c..8bb53060540641efed83a754f5d08babcb68f840 100644 (file)
@@ -682,6 +682,8 @@ struct TxNonPFData {
     AppProto alproto;
     int dir;      /**< 0: toserver, 1: toclient */
     int progress; /**< progress state value to register at */
+    int sig_list; /**< special handling: normally 0, but for special cases (app-layer-state,
+                     app-layer-event) use the list id to create separate engines */
     uint32_t sigs_cnt;
     struct PrefilterNonPFDataSig *sigs;
     const char *engine_name; /**< pointer to name owned by DetectEngineCtx::non_pf_engine_names */
@@ -690,14 +692,15 @@ struct TxNonPFData {
 static uint32_t TxNonPFHash(HashListTable *h, void *data, uint16_t _len)
 {
     struct TxNonPFData *d = data;
-    return (d->alproto + d->progress + d->dir) % h->array_size;
+    return (d->alproto + d->progress + d->dir + d->sig_list) % h->array_size;
 }
 
 static char TxNonPFCompare(void *data1, uint16_t _len1, void *data2, uint16_t len2)
 {
     struct TxNonPFData *d1 = data1;
     struct TxNonPFData *d2 = data2;
-    return d1->alproto == d2->alproto && d1->progress == d2->progress && d1->dir == d2->dir;
+    return d1->alproto == d2->alproto && d1->progress == d2->progress && d1->dir == d2->dir &&
+           d1->sig_list == d2->sig_list;
 }
 
 static void TxNonPFFree(void *data)
@@ -708,8 +711,8 @@ static void TxNonPFFree(void *data)
 }
 
 static int TxNonPFAddSig(DetectEngineCtx *de_ctx, HashListTable *tx_engines_hash,
-        const AppProto alproto, const int dir, const int16_t progress, const char *name,
-        const Signature *s)
+        const AppProto alproto, const int dir, const int16_t progress, const int sig_list,
+        const char *name, const Signature *s)
 {
     const uint32_t max_sids = DetectEngineGetMaxSigId(de_ctx);
 
@@ -717,6 +720,7 @@ static int TxNonPFAddSig(DetectEngineCtx *de_ctx, HashListTable *tx_engines_hash
         .alproto = alproto,
         .dir = dir,
         .progress = progress,
+        .sig_list = sig_list,
         .sigs_cnt = 0,
         .sigs = NULL,
         .engine_name = NULL,
@@ -747,6 +751,7 @@ static int TxNonPFAddSig(DetectEngineCtx *de_ctx, HashListTable *tx_engines_hash
     add->dir = dir;
     add->alproto = alproto;
     add->progress = progress;
+    add->sig_list = sig_list;
     add->sigs = SCCalloc(max_sids, sizeof(struct PrefilterNonPFDataSig));
     if (add->sigs == NULL) {
         SCFree(add);
@@ -852,6 +857,8 @@ static int SetupNonPrefilter(DetectEngineCtx *de_ctx, SigGroupHead *sgh)
 #endif
     const int app_events_list_id = DetectBufferTypeGetByName("app-layer-events");
     SCLogDebug("app_events_list_id %d", app_events_list_id);
+    const int app_state_list_id = DetectBufferTypeGetByName("app-layer-state");
+    SCLogDebug("app_state_list_id %d", app_state_list_id);
     for (uint32_t sig = 0; sig < sgh->init->sig_cnt; sig++) {
         Signature *s = sgh->init->match_array[sig];
         if (s == NULL)
@@ -958,8 +965,11 @@ static int SetupNonPrefilter(DetectEngineCtx *de_ctx, SigGroupHead *sgh)
                                         AppProtoEquals(s->alproto, app->alproto))))
                         continue;
 
+                    int sig_list = 0;
+                    if (list_id == app_state_list_id)
+                        sig_list = app_state_list_id;
                     if (TxNonPFAddSig(de_ctx, tx_engines_hash, app->alproto, app->dir,
-                                app->progress, buf->name, s) != 0) {
+                                app->progress, sig_list, buf->name, s) != 0) {
                         goto error;
                     }
                     tx_non_pf = true;
@@ -1036,6 +1046,13 @@ static int SetupNonPrefilter(DetectEngineCtx *de_ctx, SigGroupHead *sgh)
             continue;
         }
 
+        /* register special progress value to indicate we need to run it all the time */
+        int engine_progress = t->progress;
+        if (t->sig_list == app_state_list_id) {
+            SCLogDebug("engine %s for state list", t->engine_name);
+            engine_progress = -1;
+        }
+
         struct PrefilterNonPFDataTx *data =
                 SCCalloc(1, sizeof(*data) + t->sigs_cnt * sizeof(data->array[0]));
         if (data == NULL)
@@ -1044,7 +1061,7 @@ static int SetupNonPrefilter(DetectEngineCtx *de_ctx, SigGroupHead *sgh)
         for (uint32_t i = 0; i < t->sigs_cnt; i++) {
             data->array[i] = t->sigs[i].sid;
         }
-        if (PrefilterAppendTxEngine(de_ctx, sgh, PrefilterTxNonPF, t->alproto, t->progress,
+        if (PrefilterAppendTxEngine(de_ctx, sgh, PrefilterTxNonPF, t->alproto, engine_progress,
                     (void *)data, PrefilterNonPFDataFree, t->engine_name) < 0) {
             SCFree(data);
             goto error;
index 61f5d33bde905e0a2ec26f57def7021d40843847..51ea64403b5ba54d273243c280142cb2e9bc0691 100644 (file)
 #include "detect-replace.h"
 #include "detect-tos.h"
 #include "detect-app-layer-event.h"
+#include "detect-app-layer-state.h"
 #include "detect-lua.h"
 #include "detect-iprep.h"
 #include "detect-geoip.h"
@@ -593,6 +594,7 @@ void SigTableSetup(void)
     DetectTlsJa3SStringRegister();
 
     DetectAppLayerEventRegister();
+    DetectAppLayerStateRegister();
     /* end of order dependent regs */
 
     DetectFrameRegister();
index 2f19793a8ad1bf31521a2ce2fed93173499432bb..dd7fefb58b79eba28bb8f0afb0c0ffc5e2429c63 100644 (file)
@@ -201,6 +201,7 @@ enum DetectKeywordId {
     DETECT_FILE_DATA,
     DETECT_PKT_DATA,
     DETECT_APP_LAYER_EVENT,
+    DETECT_APP_LAYER_STATE,
 
     DETECT_HTTP2_FRAMETYPE,
     DETECT_HTTP2_ERRORCODE,
index 3e07ee8e11daf56a6e55bb34fe6397660819bed5..6b30909e52f09d92f3ae75e07390657a7245973d 100644 (file)
@@ -565,7 +565,6 @@ void FlowSwap(Flow *);
 void FlowRegisterTests(void);
 int FlowSetProtoFreeFunc(uint8_t, void (*Free)(void *));
 
-static inline void FlowSetNoPacketInspectionFlag(Flow *);
 static inline void FlowSetNoPayloadInspectionFlag(Flow *);
 
 int FlowGetPacketDirection(const Flow *, const Packet *);