]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
detect: add pre_stream hook
authorVictor Julien <vjulien@oisf.net>
Wed, 14 May 2025 10:16:46 +0000 (12:16 +0200)
committerVictor Julien <victor@inliniac.net>
Tue, 10 Jun 2025 06:36:36 +0000 (08:36 +0200)
Meant to be used from the detection engine, to allow rules to drop
traffic before it modifies the stream state.

Ticket: #7712.

etc/schema.json
src/decode.c
src/decode.h
src/detect-engine-alert.c
src/detect-engine-build.c
src/detect-parse.c
src/detect.c
src/detect.h
src/flow-worker.c

index dad1e1dfe3ae8f81486643c331bdd2410cd197d5..e3bbbeff11df6c38e6ce7b4c5d2b5e577b59182f 100644 (file)
                                     "type": "integer",
                                     "description": "Number of packets dropped due to no NFQ verdict"
                                 },
+                                "pre_stream_hook": {
+                                    "description":
+                                            "Number of packets dropped in the pre_stream hook ",
+                                    "type": "integer"
+                                },
                                 "rules": {
                                     "type": "integer",
                                     "description": "Number of packets dropped due to rule actions"
index 42c6363d880add6db63bfcd95c6c2a4b84250eb5..f3ef6c22b3730cb97b367cc198d62bc1eb4fee02 100644 (file)
@@ -911,6 +911,8 @@ const char *PacketDropReasonToString(enum PacketDropReason r)
             return "default packet policy";
         case PKT_DROP_REASON_DEFAULT_APP_POLICY:
             return "default app policy";
+        case PKT_DROP_REASON_STREAM_PRE_HOOK:
+            return "pre stream hook";
         case PKT_DROP_REASON_NOT_SET:
         case PKT_DROP_REASON_MAX:
             return NULL;
@@ -957,6 +959,8 @@ static const char *PacketDropReasonToJsonString(enum PacketDropReason r)
             return "ips.drop_reason.default_packet_policy";
         case PKT_DROP_REASON_DEFAULT_APP_POLICY:
             return "ips.drop_reason.default_app_policy";
+        case PKT_DROP_REASON_STREAM_PRE_HOOK:
+            return "ips.drop_reason.pre_stream_hook";
         case PKT_DROP_REASON_NOT_SET:
         case PKT_DROP_REASON_MAX:
             return NULL;
index deee5a63f8e7b02145ad775ab26a105fcebda254..1224f75a8599889d9d4ba415136efd8007f3b8e1 100644 (file)
@@ -390,6 +390,7 @@ enum PacketDropReason {
     PKT_DROP_REASON_INNER_PACKET,          /**< drop issued by inner (tunnel) packet */
     PKT_DROP_REASON_DEFAULT_PACKET_POLICY, /**< drop issued by default packet policy */
     PKT_DROP_REASON_DEFAULT_APP_POLICY,    /**< drop issued by default app policy */
+    PKT_DROP_REASON_STREAM_PRE_HOOK,       /**< drop issued in the pre_stream hook */
     PKT_DROP_REASON_MAX,
 };
 
index e7f1f847c3cc26493c332a9f996ce44102cb5c86..d00514fe8e41e4f8fb486a64cab5e09d470a28f9 100644 (file)
@@ -191,11 +191,16 @@ static void PacketApplySignatureActions(Packet *p, const Signature *s, const Pac
 
     /* REJECT also sets ACTION_DROP, just make it more visible with this check */
     if (pa->action & ACTION_DROP_REJECT) {
+        const uint8_t drop_reason = ((s->flags & SIG_FLAG_FIREWALL) &&
+                                            s->firewall_table == FIREWALL_TABLE_PACKET_PRE_STREAM)
+                                            ? PKT_DROP_REASON_STREAM_PRE_HOOK
+                                            : PKT_DROP_REASON_RULES;
+
         /* PacketDrop will update the packet action, too */
         PacketDrop(p, pa->action,
                 (pa->flags & PACKET_ALERT_FLAG_RATE_FILTER_MODIFIED)
                         ? PKT_DROP_REASON_RULES_THRESHOLD
-                        : PKT_DROP_REASON_RULES);
+                        : drop_reason);
         SCLogDebug("[packet %p][DROP sid %u]", p, s->id);
 
         if (p->alerts.drop.action == 0) {
index 3915aff01f842a9c25b9a19319857a7e1d26cb89..9a524cd2af3544648dfff9cacd0ea01c26104814 100644 (file)
@@ -37,6 +37,7 @@
 #include "detect-flow.h"
 #include "detect-config.h"
 #include "detect-flowbits.h"
+
 #include "app-layer-events.h"
 
 #include "util-port-interval-tree.h"
@@ -1853,6 +1854,21 @@ static void DetectEngineAddDecoderEventSig(DetectEngineCtx *de_ctx, Signature *s
     SigGroupHeadAppendSig(de_ctx, &de_ctx->decoder_event_sgh, s);
 }
 
+static void DetectEngineAddSigToPreStreamHook(DetectEngineCtx *de_ctx, Signature *s)
+{
+    SCLogDebug("adding signature %" PRIu32 " to the pre_stream hook sgh", s->id);
+
+    if ((s->flags & (SIG_FLAG_TOSERVER | SIG_FLAG_TOCLIENT)) ==
+            (SIG_FLAG_TOSERVER | SIG_FLAG_TOCLIENT)) {
+        SigGroupHeadAppendSig(de_ctx, &de_ctx->pre_stream_sgh[0], s);
+        SigGroupHeadAppendSig(de_ctx, &de_ctx->pre_stream_sgh[1], s);
+    } else if (s->flags & SIG_FLAG_TOSERVER) {
+        SigGroupHeadAppendSig(de_ctx, &de_ctx->pre_stream_sgh[0], s);
+    } else if (s->flags & SIG_FLAG_TOCLIENT) {
+        SigGroupHeadAppendSig(de_ctx, &de_ctx->pre_stream_sgh[1], s);
+    }
+}
+
 /**
  * \brief Fill the global src group head, with the sigs included
  *
@@ -1884,6 +1900,9 @@ int SigPrepareStage2(DetectEngineCtx *de_ctx)
             IPOnlyAddSignature(de_ctx, &de_ctx->io_ctx, s);
         } else if (s->type == SIG_TYPE_DEONLY) {
             DetectEngineAddDecoderEventSig(de_ctx, s);
+        } else if (s->type == SIG_TYPE_PKT && s->init_data->hook.type == SIGNATURE_HOOK_TYPE_PKT &&
+                   s->init_data->hook.t.pkt.ph == SIGNATURE_HOOK_PKT_PRE_STREAM) {
+            DetectEngineAddSigToPreStreamHook(de_ctx, s);
         }
     }
 
@@ -1902,10 +1921,33 @@ static void DetectEngineBuildDecoderEventSgh(DetectEngineCtx *de_ctx)
     SigGroupHeadBuildMatchArray(de_ctx, de_ctx->decoder_event_sgh, max_idx);
 }
 
+static void DetectEngineBuildPreStreamHookSghs(DetectEngineCtx *de_ctx)
+{
+    uint32_t max_idx = DetectEngineGetMaxSigId(de_ctx);
+    if (de_ctx->pre_stream_sgh[0] != NULL) {
+        SigGroupHeadSetSigCnt(de_ctx->pre_stream_sgh[0], max_idx);
+        SigGroupHeadBuildMatchArray(de_ctx, de_ctx->pre_stream_sgh[0], max_idx);
+        PrefilterSetupRuleGroup(de_ctx, de_ctx->pre_stream_sgh[0]);
+    }
+    if (de_ctx->pre_stream_sgh[1] != NULL) {
+        SigGroupHeadSetSigCnt(de_ctx->pre_stream_sgh[1], max_idx);
+        SigGroupHeadBuildMatchArray(de_ctx, de_ctx->pre_stream_sgh[1], max_idx);
+        PrefilterSetupRuleGroup(de_ctx, de_ctx->pre_stream_sgh[1]);
+    }
+
+    if (de_ctx->pre_stream_sgh[0] != NULL || de_ctx->pre_stream_sgh[1] != NULL) {
+        de_ctx->PreStreamHook = DetectPreStream;
+    }
+}
+
 int SigPrepareStage3(DetectEngineCtx *de_ctx)
 {
     /* prepare the decoder event sgh */
     DetectEngineBuildDecoderEventSgh(de_ctx);
+
+    /* pre_stream hook sghs */
+    DetectEngineBuildPreStreamHookSghs(de_ctx);
+
     return 0;
 }
 
@@ -1918,6 +1960,12 @@ int SigAddressCleanupStage1(DetectEngineCtx *de_ctx)
     if (de_ctx->decoder_event_sgh)
         SigGroupHeadFree(de_ctx, de_ctx->decoder_event_sgh);
     de_ctx->decoder_event_sgh = NULL;
+    if (de_ctx->pre_stream_sgh[0])
+        SigGroupHeadFree(de_ctx, de_ctx->pre_stream_sgh[0]);
+    de_ctx->pre_stream_sgh[0] = NULL;
+    if (de_ctx->pre_stream_sgh[1])
+        SigGroupHeadFree(de_ctx, de_ctx->pre_stream_sgh[1]);
+    de_ctx->pre_stream_sgh[1] = NULL;
 
     for (int f = 0; f < FLOW_STATES; f++) {
         for (int p = 0; p < 256; p++) {
index c78e5fcd47285a8687797d9f69f7d133868bea1f..1ae277c20a289eb75eacf5a3690e9ab6bac3104a 100644 (file)
@@ -1240,6 +1240,8 @@ static enum SignatureHookPkt HookPktFromString(const char *str)
 {
     if (strcmp(str, "flow_start") == 0) {
         return SIGNATURE_HOOK_PKT_FLOW_START;
+    } else if (strcmp(str, "pre_stream") == 0) {
+        return SIGNATURE_HOOK_PKT_PRE_STREAM;
     } else if (strcmp(str, "all") == 0) {
         return SIGNATURE_HOOK_PKT_ALL;
     }
@@ -1254,6 +1256,8 @@ static const char *HookPktToString(const enum SignatureHookPkt ph)
             return "not set";
         case SIGNATURE_HOOK_PKT_FLOW_START:
             return "flow_start";
+        case SIGNATURE_HOOK_PKT_PRE_STREAM:
+            return "pre_stream";
         case SIGNATURE_HOOK_PKT_ALL:
             return "all";
     }
@@ -2454,7 +2458,11 @@ static void DetectFirewallRuleSetTable(Signature *s)
     enum FirewallTable table;
     if (s->flags & SIG_FLAG_FIREWALL) {
         if (s->type == SIG_TYPE_PKT) {
-            table = FIREWALL_TABLE_PACKET_FILTER;
+            if (s->init_data->hook.type == SIGNATURE_HOOK_TYPE_PKT &&
+                    s->init_data->hook.t.pkt.ph == SIGNATURE_HOOK_PKT_PRE_STREAM)
+                table = FIREWALL_TABLE_PACKET_PRE_STREAM;
+            else
+                table = FIREWALL_TABLE_PACKET_FILTER;
         } else if (s->type == SIG_TYPE_APP_TX) {
             table = FIREWALL_TABLE_APP_FILTER;
         } else {
index 4fcb84e008db93b94548496a1f6ba889e04a760a..c181072e8eb6e01a660246b9f853f292b32a449e 100644 (file)
@@ -198,6 +198,49 @@ end:
     SCReturn;
 }
 
+/** \internal
+ */
+static void DetectRunPacketHook(ThreadVars *th_v, const DetectEngineCtx *de_ctx,
+        DetectEngineThreadCtx *det_ctx, const SigGroupHead *sgh, Packet *p)
+{
+    SCEnter();
+    SCLogDebug("p->pcap_cnt %" PRIu64 " direction %s pkt_src %s", p->pcap_cnt,
+            p->flow ? (FlowGetPacketDirection(p->flow, p) == TOSERVER ? "toserver" : "toclient")
+                    : "noflow",
+            PktSrcToString(p->pkt_src));
+
+    /* Load the Packet's flow early, even though it might not be needed.
+     * Mark as a constant pointer, although the flow itself can change. */
+    Flow *const pflow = p->flow;
+
+    DetectRunScratchpad scratch = DetectRunSetup(de_ctx, det_ctx, p, pflow);
+    scratch.sgh = sgh;
+
+    /* if we didn't get a sig group head, we
+     * have nothing to do.... */
+    if (scratch.sgh == NULL) {
+        SCLogDebug("no sgh for this packet, nothing to match against");
+        goto end;
+    }
+
+    /* run the prefilters for packets */
+    DetectRunPrefilterPkt(th_v, de_ctx, det_ctx, p, &scratch);
+
+    //    PACKET_PROFILING_DETECT_START(p, PROF_DETECT_RULES); // TODO
+    /* inspect the rules against the packet */
+    const uint8_t pkt_policy = DetectRulePacketRules(th_v, de_ctx, det_ctx, p, pflow, &scratch);
+    //    PACKET_PROFILING_DETECT_END(p, PROF_DETECT_RULES);
+    if (pkt_policy & (ACTION_DROP | ACTION_ACCEPT)) {
+        goto end;
+    }
+
+end:
+    DetectRunPostRules(th_v, de_ctx, det_ctx, p, pflow, &scratch);
+
+    DetectRunCleanup(det_ctx, p, pflow);
+    SCReturn;
+}
+
 static void DetectRunPostMatch(ThreadVars *tv,
                                DetectEngineThreadCtx *det_ctx, Packet *p,
                                const Signature *s)
@@ -2252,6 +2295,17 @@ static void DetectNoFlow(ThreadVars *tv,
     DetectRun(tv, de_ctx, det_ctx, p);
 }
 
+uint8_t DetectPreStream(ThreadVars *tv, DetectEngineThreadCtx *det_ctx, Packet *p)
+{
+    const DetectEngineCtx *de_ctx = det_ctx->de_ctx;
+    const int direction = (PKT_IS_TOCLIENT(p) != 0);
+    const SigGroupHead *sgh = de_ctx->pre_stream_sgh[direction];
+
+    SCLogDebug("thread id: %u, packet %" PRIu64 ", sgh %p", tv->id, p->pcap_cnt, sgh);
+    DetectRunPacketHook(tv, de_ctx, det_ctx, sgh, p);
+    return p->action;
+}
+
 /** \brief Detection engine thread wrapper.
  *  \param tv thread vars
  *  \param p packet to inspect
index da613ff55e933718dec6dd31d23d78f6f0262382..86f1f235ea5470fc13e7edc6308d7b6715e73e3d 100644 (file)
@@ -539,6 +539,7 @@ typedef struct SignatureInitDataBuffer_ {
 enum SignatureHookPkt {
     SIGNATURE_HOOK_PKT_NOT_SET,
     SIGNATURE_HOOK_PKT_FLOW_START,
+    SIGNATURE_HOOK_PKT_PRE_STREAM,
     SIGNATURE_HOOK_PKT_ALL, /**< match each packet */
 };
 
@@ -549,6 +550,7 @@ enum SignatureHookType {
 };
 
 enum FirewallTable {
+    FIREWALL_TABLE_PACKET_PRE_STREAM,
     FIREWALL_TABLE_PACKET_FILTER,
     FIREWALL_TABLE_PACKET_TD,
     FIREWALL_TABLE_APP_FILTER,
@@ -905,6 +907,8 @@ typedef struct {
     uint32_t content_inspect_min_size;
 } DetectFileDataCfg;
 
+typedef uint8_t (*DetectPacketHookFunc)(ThreadVars *tv, DetectEngineThreadCtx *det_ctx, Packet *p);
+
 /**
  * \brief Function type for rate filter callback.
  *
@@ -1135,6 +1139,12 @@ typedef struct DetectEngineCtx_ {
 
     /* use provided data to be passed to rate_filter_callback. */
     void *rate_filter_callback_arg;
+
+    /* Hook for pre_stream engine if it is used. */
+    DetectPacketHookFunc PreStreamHook;
+    /** TCP pre_stream hook rule groups. One per direction. */
+    struct SigGroupHead_ *pre_stream_sgh[2];
+
 } DetectEngineCtx;
 
 /**
@@ -1698,6 +1708,7 @@ extern SigTableElmt *sigmatch_table;
 
 /* detection api */
 TmEcode Detect(ThreadVars *tv, Packet *p, void *data);
+uint8_t DetectPreStream(ThreadVars *tv, DetectEngineThreadCtx *det_ctx, Packet *p);
 
 SigMatch *SigMatchAlloc(void);
 Signature *SigFindSignatureBySidGid(DetectEngineCtx *, uint32_t, uint32_t);
index b4bd9a96dd0f4d1ee6c041a4eb18d5436d89c92d..da64e0753adb85a5e8da358a587aa78d4756d8b5 100644 (file)
@@ -366,6 +366,14 @@ static inline void UpdateCounters(ThreadVars *tv,
 static inline void FlowWorkerStreamTCPUpdate(ThreadVars *tv, FlowWorkerThreadData *fw, Packet *p,
         DetectEngineThreadCtx *det_ctx, const bool timeout)
 {
+    if (det_ctx != NULL && det_ctx->de_ctx->PreStreamHook != NULL) {
+        const uint8_t action = det_ctx->de_ctx->PreStreamHook(tv, det_ctx, p);
+        if (action & ACTION_DROP) {
+            PacketDrop(p, ACTION_DROP, PKT_DROP_REASON_STREAM_PRE_HOOK);
+            return;
+        }
+    }
+
     FLOWWORKER_PROFILING_START(p, PROFILE_FLOWWORKER_STREAM);
     StreamTcp(tv, p, fw->stream_thread, &fw->pq);
     FLOWWORKER_PROFILING_END(p, PROFILE_FLOWWORKER_STREAM);