Skip to content

Commit

Permalink
detect/threshold: implement backoff type
Browse files Browse the repository at this point in the history
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: OISF#7120.
  • Loading branch information
victorjulien committed Jun 28, 2024
1 parent a0d515b commit 12130df
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 20 deletions.
70 changes: 64 additions & 6 deletions src/detect-engine-threshold.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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:
Expand All @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down
87 changes: 74 additions & 13 deletions src/detect-threshold.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down Expand Up @@ -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;
Expand All @@ -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;
}
Expand All @@ -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)
Expand All @@ -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++) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down
4 changes: 3 additions & 1 deletion src/detect-threshold.h
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;

Expand Down

0 comments on commit 12130df

Please sign in to comment.