From: Victor Julien Date: Mon, 24 Jun 2024 08:55:45 +0000 (+0200) Subject: detect/threshold: implement backoff type X-Git-Tag: suricata-8.0.0-beta1~1078 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=12130df21c875f9384f69182fe30db87d5688552;p=thirdparty%2Fsuricata.git detect/threshold: implement backoff type 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. --- diff --git a/src/detect-engine-threshold.c b/src/detect-engine-threshold.c index faedb656b0..31892f82f3 100644 --- a/src/detect-engine-threshold.c +++ b/src/detect-engine-threshold.c @@ -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; } diff --git a/src/detect-threshold.c b/src/detect-threshold.c index d368a8dbcb..bc590d87ce 100644 --- a/src/detect-threshold.c +++ b/src/detect-threshold.c @@ -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); diff --git a/src/detect-threshold.h b/src/detect-threshold.h index e1ef71bd10..7218a28f57 100644 --- a/src/detect-threshold.h +++ b/src/detect-threshold.h @@ -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;