]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
detect/threshold: implement backoff type
authorVictor Julien <vjulien@oisf.net>
Mon, 24 Jun 2024 08:55:45 +0000 (10:55 +0200)
committerVictor Julien <vjulien@oisf.net>
Fri, 28 Jun 2024 07:46:34 +0000 (09:46 +0200)
Implement new `type backoff` for thresholding. This allows alerts to be
limited.

A count of 1 with a multiplier of 10 would generate alerts for matching packets:
1, 10, 100, 1000, 10000, 100000, etc.

A count of 1 with a multiplier of 2 would generate alerts for matching packets:
1, 2, 4, 8, 16, 32, etc.

Like with other thresholds, rule actions like drop and setting of
flowbits will still be performed for each matching packet.

Current implementation is only for the by_flow tracker and for per rule
threshold statements.

Tracking is done using uint32_t. When it reaches this value, the rest of
the packets in the tracker will use the silent match.

Ticket: #7120.

src/detect-engine-threshold.c
src/detect-threshold.c
src/detect-threshold.h

index faedb656b0b47c1ffd5e8124418bbfd121518c0c..31892f82f31426de9293ad0b59c0fcf46e9d8df6 100644 (file)
@@ -87,10 +87,17 @@ typedef struct ThresholdEntry_ {
     uint32_t seconds;       /**< Event seconds */
     uint32_t current_count; /**< Var for count control */
 
-    SCTime_t tv1; /**< Var for time control */
+    union {
+        struct {
+            uint32_t next_value;
+        } backoff;
+        struct {
+            SCTime_t tv1;  /**< Var for time control */
+            Address addr;  /* used for src/dst/either tracking */
+            Address addr2; /* used for both tracking */
+        };
+    };
 
-    Address addr;  /* used for src/dst/either tracking */
-    Address addr2; /* used for both tracking */
 } ThresholdEntry;
 
 static int ThresholdEntrySet(void *dst, void *src)
@@ -639,6 +646,24 @@ static inline void RateFilterSetAction(PacketAlert *pa, uint8_t new_action)
     }
 }
 
+/** \internal
+ *  \brief Apply the multiplier and return the new value.
+ *  If it would overflow the uint32_t we return UINT32_MAX.
+ */
+static uint32_t BackoffCalcNextValue(const uint32_t cur, const uint32_t m)
+{
+    /* goal is to see if cur * m would overflow uint32_t */
+    if (unlikely(UINT32_MAX / m < cur)) {
+        return UINT32_MAX;
+    }
+    return cur * m;
+}
+
+/**
+ *  \retval 2 silent match (no alert but apply actions)
+ *  \retval 1 normal match
+ *  \retval 0 no match
+ */
 static int ThresholdSetup(const DetectThresholdData *td, ThresholdEntry *te,
         const SCTime_t packet_time, const uint32_t sid, const uint32_t gid, const uint32_t rev,
         const uint32_t tenant_id)
@@ -648,11 +673,19 @@ static int ThresholdSetup(const DetectThresholdData *td, ThresholdEntry *te,
     te->key[REV] = rev;
     te->key[TRACK] = td->track;
     te->key[TENANT] = tenant_id;
-    te->seconds = td->seconds;
 
+    te->seconds = td->seconds;
     te->current_count = 1;
-    te->tv1 = packet_time;
-    te->tv_timeout = 0;
+
+    switch (td->type) {
+        case TYPE_BACKOFF:
+            te->backoff.next_value = td->count;
+            break;
+        default:
+            te->tv1 = packet_time;
+            te->tv_timeout = 0;
+            break;
+    }
 
     switch (td->type) {
         case TYPE_LIMIT:
@@ -663,6 +696,13 @@ static int ThresholdSetup(const DetectThresholdData *td, ThresholdEntry *te,
             if (td->count == 1)
                 return 1;
             return 0;
+        case TYPE_BACKOFF:
+            if (td->count == 1) {
+                te->backoff.next_value =
+                        BackoffCalcNextValue(te->backoff.next_value, td->multiplier);
+                return 1;
+            }
+            return 0;
         case TYPE_DETECTION:
             return 0;
     }
@@ -782,6 +822,24 @@ static int ThresholdCheckUpdate(const DetectThresholdData *td, ThresholdEntry *t
                 }
             }
             break;
+        case TYPE_BACKOFF:
+            SCLogDebug("backoff");
+
+            if (te->current_count < UINT32_MAX) {
+                te->current_count++;
+                if (te->backoff.next_value == te->current_count) {
+                    te->backoff.next_value =
+                            BackoffCalcNextValue(te->backoff.next_value, td->multiplier);
+                    SCLogDebug("te->backoff.next_value %u", te->backoff.next_value);
+                    ret = 1;
+                } else {
+                    ret = 2;
+                }
+            } else {
+                /* if count reaches UINT32_MAX, we just silent match on the rest of the flow */
+                ret = 2;
+            }
+            break;
     }
     return ret;
 }
index d368a8dbcbb2de4e761ca1bf9d7aafe944e90253..bc590d87ce0e8fc45e23921bf721a9dae9f1ad74 100644 (file)
@@ -60,8 +60,9 @@
 #include "util-cpu.h"
 #endif
 
-#define PARSE_REGEX_NAME  "(track|type|count|seconds)"
-#define PARSE_REGEX_VALUE "(limit|both|threshold|by_dst|by_src|by_both|by_rule|by_flow|\\d+)"
+#define PARSE_REGEX_NAME "(track|type|count|seconds|multiplier)"
+#define PARSE_REGEX_VALUE                                                                          \
+    "(limit|both|threshold|backoff|by_dst|by_src|by_both|by_rule|by_flow|\\d+)"
 
 #define PARSE_REGEX                                                                                \
     "^\\s*" PARSE_REGEX_NAME "\\s+" PARSE_REGEX_VALUE "\\s*,\\s*" PARSE_REGEX_NAME                 \
@@ -124,7 +125,8 @@ static DetectThresholdData *DetectThresholdParse(const char *rawstr)
     char *copy_str = NULL, *threshold_opt = NULL;
     int second_found = 0, count_found = 0;
     int type_found = 0, track_found = 0;
-    int second_pos = 0, count_pos = 0;
+    int multiplier_found = 0;
+    int second_pos = 0, count_pos = 0, multiplier_pos = 0;
     size_t pos = 0;
     int i = 0;
     pcre2_match_data *match = NULL;
@@ -146,15 +148,19 @@ static DetectThresholdData *DetectThresholdParse(const char *rawstr)
             type_found++;
         if (strstr(threshold_opt, "track"))
             track_found++;
+        if (strstr(threshold_opt, "multiplier"))
+            multiplier_found++;
     }
     SCFree(copy_str);
     copy_str = NULL;
 
-    if (count_found != 1 || second_found != 1 || type_found != 1 || track_found != 1)
+    if (!(count_found == 1 && (second_found == 1 || multiplier_found == 1) && track_found == 1 &&
+                type_found == 1)) {
         goto error;
+    }
 
     ret = DetectParsePcreExec(&parse_regex, &match, rawstr, 0, 0);
-    if (ret < 5) {
+    if (ret < 5 || ret > 9) {
         SCLogError("pcre_exec parse error, ret %" PRId32 ", string %s", ret, rawstr);
         goto error;
     }
@@ -180,6 +186,8 @@ static DetectThresholdData *DetectThresholdParse(const char *rawstr)
             de->type = TYPE_BOTH;
         if (strncasecmp(args[i], "threshold", strlen("threshold")) == 0)
             de->type = TYPE_THRESHOLD;
+        if (strcasecmp(args[i], "backoff") == 0)
+            de->type = TYPE_BACKOFF;
         if (strncasecmp(args[i], "by_dst", strlen("by_dst")) == 0)
             de->track = TRACK_DST;
         if (strncasecmp(args[i], "by_src", strlen("by_src")) == 0)
@@ -194,18 +202,56 @@ static DetectThresholdData *DetectThresholdParse(const char *rawstr)
             count_pos = i + 1;
         if (strncasecmp(args[i], "seconds", strlen("seconds")) == 0)
             second_pos = i + 1;
+        if (strcasecmp(args[i], "multiplier") == 0)
+            multiplier_pos = i + 1;
     }
 
-    if (args[count_pos] == NULL || args[second_pos] == NULL) {
-        goto error;
-    }
+    if (de->type != TYPE_BACKOFF) {
+        if (args[count_pos] == NULL || args[second_pos] == NULL) {
+            goto error;
+        }
 
-    if (StringParseUint32(&de->count, 10, strlen(args[count_pos]), args[count_pos]) <= 0) {
-        goto error;
-    }
+        if (StringParseUint32(&de->count, 10, strlen(args[count_pos]), args[count_pos]) <= 0) {
+            goto error;
+        }
+        if (StringParseUint32(&de->seconds, 10, strlen(args[second_pos]), args[second_pos]) <= 0) {
+            goto error;
+        }
+    } else {
+        if (args[count_pos] == NULL || args[multiplier_pos] == NULL) {
+            goto error;
+        }
 
-    if (StringParseUint32(&de->seconds, 10, strlen(args[second_pos]), args[second_pos]) <= 0) {
-        goto error;
+        if (second_found) {
+            goto error;
+        }
+
+        if (StringParseUint32(&de->count, 10, strlen(args[count_pos]), args[count_pos]) <= 0) {
+            goto error;
+        }
+        if (StringParseUint32(
+                    &de->multiplier, 10, strlen(args[multiplier_pos]), args[multiplier_pos]) <= 0) {
+            goto error;
+        }
+
+        /* impose some sanity limits on the count and multiplier values. Upper bounds are a bit
+         * artificial. */
+        if (!(de->count > 0 && de->count < 65536)) {
+            SCLogError("invalid count value '%u': must be in the range 1-65535", de->count);
+            goto error;
+        }
+        if (!(de->multiplier > 1 && de->multiplier < 65536)) {
+            SCLogError(
+                    "invalid multiplier value '%u': must be in the range 2-65535", de->multiplier);
+            goto error;
+        }
+
+        if (de->track != TRACK_FLOW) {
+            SCLogError("invalid track value: type backoff only supported for track by_flow");
+            goto error;
+        }
+
+        SCLogDebug("TYPE_BACKOFF count %u multiplier %u", de->count, de->multiplier);
     }
 
     for (i = 0; i < (ret - 1); i++) {
@@ -493,6 +539,20 @@ static int ThresholdTestParse07(void)
     PASS;
 }
 
+/** \test backoff by_flow */
+static int ThresholdTestParse08(void)
+{
+    DetectThresholdData *de =
+            DetectThresholdParse("count 10, track by_flow, multiplier 2, type backoff");
+    FAIL_IF_NULL(de);
+    FAIL_IF_NOT(de->type == TYPE_BACKOFF);
+    FAIL_IF_NOT(de->track == TRACK_FLOW);
+    FAIL_IF_NOT(de->count == 10);
+    FAIL_IF_NOT(de->multiplier == 2);
+    DetectThresholdFree(NULL, de);
+    PASS;
+}
+
 /**
  * \test DetectThresholdTestSig1 is a test for checking the working of limit keyword
  *       by setting up the signature and later testing its working by matching
@@ -1666,6 +1726,7 @@ static void ThresholdRegisterTests(void)
     UtRegisterTest("ThresholdTestParse05", ThresholdTestParse05);
     UtRegisterTest("ThresholdTestParse06", ThresholdTestParse06);
     UtRegisterTest("ThresholdTestParse07", ThresholdTestParse07);
+    UtRegisterTest("ThresholdTestParse08", ThresholdTestParse08);
     UtRegisterTest("DetectThresholdTestSig1", DetectThresholdTestSig1);
     UtRegisterTest("DetectThresholdTestSig2", DetectThresholdTestSig2);
     UtRegisterTest("DetectThresholdTestSig3", DetectThresholdTestSig3);
index e1ef71bd1089ef361065d76079e1f0d385d6b5bc..7218a28f571a8bacde05948937534772cac701d3 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (C) 2007-2013 Open Information Security Foundation
+/* Copyright (C) 2007-2024 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
@@ -30,6 +30,7 @@
 #define TYPE_DETECTION 4
 #define TYPE_RATE      5
 #define TYPE_SUPPRESS  6
+#define TYPE_BACKOFF   7
 
 #define TRACK_DST      1
 #define TRACK_SRC      2
@@ -59,6 +60,7 @@ typedef struct DetectThresholdData_ {
     uint8_t new_action; /**< new_action alert|drop|pass|log|sdrop|reject */
     uint32_t timeout;   /**< timeout */
     uint32_t flags;     /**< flags used to set option */
+    uint32_t multiplier; /**< backoff multiplier */
     DetectAddressHead addrs;
 } DetectThresholdData;