Coverage Report

Created: 2026-09-06 07:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata8/src/detect-engine-threshold.c
Line
Count
Source
1
/* Copyright (C) 2007-2024 Open Information Security Foundation
2
 *
3
 * You can copy, redistribute or modify this Program under the terms of
4
 * the GNU General Public License version 2 as published by the Free
5
 * Software Foundation.
6
 *
7
 * This program is distributed in the hope that it will be useful,
8
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10
 * GNU General Public License for more details.
11
 *
12
 * You should have received a copy of the GNU General Public License
13
 * version 2 along with this program; if not, write to the Free Software
14
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15
 * 02110-1301, USA.
16
 */
17
18
/**
19
 * \defgroup threshold Thresholding
20
 *
21
 * This feature is used to reduce the number of logged alerts for noisy rules.
22
 * This can be tuned to significantly reduce false alarms, and it can also be
23
 * used to write a newer breed of rules. Thresholding commands limit the number
24
 * of times a particular event is logged during a specified time interval.
25
 *
26
 * @{
27
 */
28
29
/**
30
 * \file
31
 *
32
 *  \author Breno Silva <breno.silva@gmail.com>
33
 *  \author Victor Julien <victor@inliniac.net>
34
 *
35
 *  Threshold part of the detection engine.
36
 */
37
38
#include "suricata-common.h"
39
#include "detect.h"
40
#include "flow.h"
41
42
#include "detect-parse.h"
43
#include "detect-engine.h"
44
#include "detect-engine-threshold.h"
45
#include "detect-engine-address.h"
46
#include "detect-engine-address-ipv6.h"
47
48
#include "util-misc.h"
49
#include "util-time.h"
50
#include "util-error.h"
51
#include "util-debug.h"
52
#include "action-globals.h"
53
#include "util-validate.h"
54
55
#include "util-hash.h"
56
#include "util-thash.h"
57
#include "util-hash-lookup3.h"
58
#include "counters.h"
59
#include "util-random.h"
60
61
#include "thread-storage.h"
62
63
static void ThresholdCacheInit(void);
64
65
struct Thresholds {
66
    THashTableContext *thash;
67
} ctx;
68
69
static int ThresholdsInit(struct Thresholds *t);
70
static void ThresholdsDestroy(struct Thresholds *t);
71
72
void ThresholdInit(void)
73
78
{
74
78
    ThresholdsInit(&ctx);
75
78
    ThresholdCacheInit();
76
78
}
77
78
void ThresholdDestroy(void)
79
0
{
80
0
    ThresholdsDestroy(&ctx);
81
0
}
82
83
7.60k
#define SID    0
84
7.56k
#define GID    1
85
7.56k
#define REV    2
86
22.5k
#define TRACK  3
87
7.56k
#define TENANT 4
88
89
typedef struct ThresholdEntry_ {
90
    uint32_t key[5];
91
92
    SCTime_t tv_timeout;    /**< Timeout for new_action (for rate_filter)
93
                                 its not "seconds", that define the time interval */
94
    uint32_t seconds;       /**< Event seconds */
95
    uint32_t current_count; /**< Var for count control */
96
97
    union {
98
        struct {
99
            uint32_t next_value;
100
        } backoff;
101
        struct {
102
            SCTime_t tv1;  /**< Var for time control */
103
            Address addr;  /* used for src/dst/either tracking */
104
            Address addr2; /* used for both tracking */
105
        };
106
    };
107
108
} ThresholdEntry;
109
110
static int ThresholdEntrySet(void *dst, void *src)
111
25
{
112
25
    const ThresholdEntry *esrc = src;
113
25
    ThresholdEntry *edst = dst;
114
25
    memset(edst, 0, sizeof(*edst));
115
25
    *edst = *esrc;
116
25
    return 0;
117
25
}
118
119
static void ThresholdEntryFree(void *ptr)
120
0
{
121
    // nothing to free, base data is part of hash
122
0
}
123
124
static inline uint32_t HashAddress(const Address *a, const uint32_t seed)
125
1.24k
{
126
1.24k
    uint32_t key;
127
128
1.24k
    if (a->family == AF_INET) {
129
1.24k
        key = hashword(a->addr_data32, 1, seed);
130
1.24k
    } else if (a->family == AF_INET6) {
131
1
        key = hashword(a->addr_data32, 4, seed);
132
1
    } else
133
0
        key = 0;
134
135
1.24k
    return key;
136
1.24k
}
137
138
static inline int CompareAddress(const Address *a, const Address *b)
139
1.22k
{
140
1.22k
    if (a->family == b->family) {
141
1.22k
        switch (a->family) {
142
1.22k
            case AF_INET:
143
1.22k
                return (a->addr_data32[0] == b->addr_data32[0]);
144
0
            case AF_INET6:
145
0
                return CMP_ADDR(a, b);
146
1.22k
        }
147
1.22k
    }
148
0
    return 0;
149
1.22k
}
150
151
static uint32_t ThresholdEntryHash(const uint32_t seed, void *ptr)
152
7.50k
{
153
7.50k
    const ThresholdEntry *e = ptr;
154
7.50k
    uint32_t hash = hashword(e->key, sizeof(e->key) / sizeof(uint32_t), seed);
155
7.50k
    switch (e->key[TRACK]) {
156
0
        case TRACK_BOTH:
157
0
            hash += HashAddress(&e->addr2, seed);
158
            /* fallthrough */
159
1.23k
        case TRACK_SRC:
160
1.24k
        case TRACK_DST:
161
1.24k
            hash += HashAddress(&e->addr, seed);
162
1.24k
            break;
163
7.50k
    }
164
7.50k
    return hash;
165
7.50k
}
166
167
static bool ThresholdEntryCompare(void *a, void *b)
168
7.48k
{
169
7.48k
    const ThresholdEntry *e1 = a;
170
7.48k
    const ThresholdEntry *e2 = b;
171
7.48k
    SCLogDebug("sid1: %u sid2: %u", e1->key[SID], e2->key[SID]);
172
173
7.48k
    if (memcmp(e1->key, e2->key, sizeof(e1->key)) != 0)
174
0
        return false;
175
7.48k
    switch (e1->key[TRACK]) {
176
0
        case TRACK_BOTH:
177
0
            if (!(CompareAddress(&e1->addr2, &e2->addr2)))
178
0
                return false;
179
            /* fallthrough */
180
1.22k
        case TRACK_SRC:
181
1.22k
        case TRACK_DST:
182
1.22k
            if (!(CompareAddress(&e1->addr, &e2->addr)))
183
0
                return false;
184
1.22k
            break;
185
7.48k
    }
186
7.48k
    return true;
187
7.48k
}
188
189
static bool ThresholdEntryExpire(void *data, const SCTime_t ts)
190
0
{
191
0
    const ThresholdEntry *e = data;
192
0
    const SCTime_t entry = SCTIME_ADD_SECS(e->tv1, e->seconds);
193
0
    if (SCTIME_CMP_GT(ts, entry)) {
194
0
        return true;
195
0
    }
196
0
    return false;
197
0
}
198
199
static int ThresholdsInit(struct Thresholds *t)
200
78
{
201
78
    uint32_t hashsize = 16384;
202
78
    uint64_t memcap = 16 * 1024 * 1024;
203
204
78
    const char *str;
205
78
    if (SCConfGetNonNull("detect.thresholds.memcap", &str) == 1) {
206
0
        if (ParseSizeStringU64(str, &memcap) < 0) {
207
0
            SCLogError("Error parsing detect.thresholds.memcap from conf file - %s", str);
208
0
            return -1;
209
0
        }
210
0
    }
211
212
78
    intmax_t value = 0;
213
78
    if ((SCConfGetInt("detect.thresholds.hash-size", &value)) == 1) {
214
0
        if (value < 256 || value > INT_MAX) {
215
0
            SCLogError("'detect.thresholds.hash-size' value %" PRIiMAX
216
0
                       " out of range. Valid range 256-2147483647.",
217
0
                    value);
218
0
            return -1;
219
0
        }
220
0
        hashsize = (uint32_t)value;
221
0
    }
222
223
78
    t->thash = THashInit("thresholds", sizeof(ThresholdEntry), ThresholdEntrySet,
224
78
            ThresholdEntryFree, ThresholdEntryHash, ThresholdEntryCompare, ThresholdEntryExpire,
225
78
            NULL, 0, memcap, hashsize);
226
78
    if (t->thash == NULL) {
227
0
        SCLogError("failed to initialize thresholds hash table");
228
0
        return -1;
229
0
    }
230
78
    return 0;
231
78
}
232
233
static void ThresholdsDestroy(struct Thresholds *t)
234
0
{
235
0
    if (t->thash) {
236
0
        THashShutdown(t->thash);
237
0
    }
238
0
}
239
240
uint32_t ThresholdsExpire(const SCTime_t ts)
241
0
{
242
0
    return THashExpire(ctx.thash, ts);
243
0
}
244
245
0
#define TC_ADDRESS 0
246
0
#define TC_SID     1
247
0
#define TC_GID     2
248
0
#define TC_REV     3
249
0
#define TC_TENANT  4
250
251
typedef struct ThresholdCacheItem {
252
    int8_t track; // by_src/by_dst
253
    int8_t ipv;
254
    int8_t retval;
255
    uint32_t key[5];
256
    SCTime_t expires_at;
257
    RB_ENTRY(ThresholdCacheItem) rb;
258
} ThresholdCacheItem;
259
260
/* rbtree for expiry handling */
261
262
static int ThresholdCacheTreeCompareFunc(ThresholdCacheItem *a, ThresholdCacheItem *b)
263
0
{
264
0
    if (SCTIME_CMP_GTE(a->expires_at, b->expires_at)) {
265
0
        return 1;
266
0
    } else {
267
0
        return -1;
268
0
    }
269
0
}
270
271
RB_HEAD(THRESHOLD_CACHE, ThresholdCacheItem);
272
RB_PROTOTYPE(THRESHOLD_CACHE, ThresholdCacheItem, rb, ThresholdCacheTreeCompareFunc);
273
3
RB_GENERATE(THRESHOLD_CACHE, ThresholdCacheItem, rb, ThresholdCacheTreeCompareFunc);
Unexecuted instantiation: THRESHOLD_CACHE_RB_INSERT_COLOR
Unexecuted instantiation: THRESHOLD_CACHE_RB_REMOVE_COLOR
Unexecuted instantiation: THRESHOLD_CACHE_RB_INSERT
Unexecuted instantiation: THRESHOLD_CACHE_RB_REMOVE
Unexecuted instantiation: THRESHOLD_CACHE_RB_FIND
Unexecuted instantiation: THRESHOLD_CACHE_RB_NFIND
THRESHOLD_CACHE_RB_MINMAX
Line
Count
Source
273
RB_GENERATE(THRESHOLD_CACHE, ThresholdCacheItem, rb, ThresholdCacheTreeCompareFunc);
274
3
275
3
struct ThresholdCacheThreadCtx {
276
3
    HashTable *ht;
277
3
    struct THRESHOLD_CACHE tree;
278
3
    uint64_t housekeeping_ts;
279
3
280
3
    uint64_t lookup_cnt;
281
3
    uint64_t lookup_nosupport;
282
3
    uint64_t lookup_miss_expired;
283
3
    uint64_t lookup_miss;
284
3
    uint64_t lookup_hit;
285
3
    uint64_t housekeeping_check;
286
3
    uint64_t housekeeping_expired;
287
3
};
288
3
289
3
static ThreadStorageId thread_storage_id = { .id = -1 };
290
3
291
3
static void DumpCacheStats(struct ThresholdCacheThreadCtx *tctx)
292
3
{
293
0
    SCLogPerf("threshold thread cache stats: cnt:%" PRIu64 " nosupport:%" PRIu64
294
0
              " miss_expired:%" PRIu64 " miss:%" PRIu64 " hit:%" PRIu64
295
0
              ", housekeeping: checks:%" PRIu64 ", expired:%" PRIu64,
296
0
            tctx->lookup_cnt, tctx->lookup_nosupport, tctx->lookup_miss_expired, tctx->lookup_miss,
297
0
            tctx->lookup_hit, tctx->housekeeping_check, tctx->housekeeping_expired);
298
0
}
299
300
static inline struct ThresholdCacheThreadCtx *GetThreadCtx(DetectEngineThreadCtx *det_ctx)
301
5.38k
{
302
5.38k
    if (unlikely(det_ctx->tv == NULL || thread_storage_id.id < 0)) {
303
0
        return NULL;
304
0
    }
305
5.38k
    return ThreadGetStorageById(det_ctx->tv, thread_storage_id);
306
5.38k
}
307
308
static void ThresholdCacheExpire(DetectEngineThreadCtx *det_ctx, SCTime_t now)
309
3
{
310
3
    struct ThresholdCacheThreadCtx *tctx = GetThreadCtx(det_ctx);
311
3
    if (tctx == NULL)
312
0
        return;
313
3
    tctx->housekeeping_ts = SCTIME_SECS(now);
314
315
3
    ThresholdCacheItem *iter, *safe = NULL;
316
3
    int cnt = 0;
317
3
    RB_FOREACH_SAFE (iter, THRESHOLD_CACHE, &tctx->tree, safe) {
318
0
        tctx->housekeeping_check++;
319
320
0
        if (SCTIME_CMP_LT(iter->expires_at, now)) {
321
0
            THRESHOLD_CACHE_RB_REMOVE(&tctx->tree, iter);
322
0
            HashTableRemove(tctx->ht, iter, 0);
323
0
            SCLogDebug("iter %p expired", iter);
324
0
            tctx->housekeeping_expired++;
325
0
        }
326
327
0
        if (++cnt > 1)
328
0
            break;
329
0
    }
330
3
}
331
332
/* hash table for threshold look ups */
333
334
static uint32_t ThresholdCacheHashFunc(HashTable *ht, void *data, uint16_t datalen)
335
8
{
336
8
    ThresholdCacheItem *e = data;
337
8
    uint32_t hash =
338
8
            hashword(e->key, sizeof(e->key) / sizeof(uint32_t), ht->seed) * (e->ipv + e->track);
339
8
    hash = hash % ht->array_size;
340
8
    return hash;
341
8
}
342
343
static char ThresholdCacheHashCompareFunc(
344
        void *data1, uint16_t datalen1, void *data2, uint16_t datalen2)
345
0
{
346
0
    ThresholdCacheItem *tci1 = data1;
347
0
    ThresholdCacheItem *tci2 = data2;
348
0
    return tci1->ipv == tci2->ipv && tci1->track == tci2->track &&
349
0
           memcmp(tci1->key, tci2->key, sizeof(tci1->key)) == 0;
350
0
}
351
352
static void ThresholdCacheHashFreeFunc(void *data)
353
0
{
354
0
    SCFree(data);
355
0
}
356
357
/// \brief Thread local cache
358
static int SetupCache(DetectEngineThreadCtx *det_ctx, const Packet *p, const int8_t track,
359
        const int8_t retval, const uint32_t sid, const uint32_t gid, const uint32_t rev,
360
        SCTime_t expires)
361
5.37k
{
362
5.37k
    struct ThresholdCacheThreadCtx *tctx = GetThreadCtx(det_ctx);
363
5.37k
    if (!tctx) {
364
0
        return -1;
365
0
    }
366
367
5.37k
    uint32_t addr;
368
5.37k
    if (track == TRACK_SRC) {
369
0
        addr = p->src.addr_data32[0];
370
5.37k
    } else if (track == TRACK_DST) {
371
0
        addr = p->dst.addr_data32[0];
372
5.37k
    } else {
373
5.37k
        return -1;
374
5.37k
    }
375
376
0
    ThresholdCacheItem lookup = {
377
0
        .track = track,
378
0
        .ipv = 4,
379
0
        .retval = retval,
380
0
        .key[TC_ADDRESS] = addr,
381
0
        .key[TC_SID] = sid,
382
0
        .key[TC_GID] = gid,
383
0
        .key[TC_REV] = rev,
384
0
        .key[TC_TENANT] = p->tenant_id,
385
0
        .expires_at = expires,
386
0
    };
387
0
    ThresholdCacheItem *found = HashTableLookup(tctx->ht, &lookup, 0);
388
0
    if (!found) {
389
0
        ThresholdCacheItem *n = SCCalloc(1, sizeof(*n));
390
0
        if (n) {
391
0
            n->track = track;
392
0
            n->ipv = 4;
393
0
            n->retval = retval;
394
0
            n->key[TC_ADDRESS] = addr;
395
0
            n->key[TC_SID] = sid;
396
0
            n->key[TC_GID] = gid;
397
0
            n->key[TC_REV] = rev;
398
0
            n->key[TC_TENANT] = p->tenant_id;
399
0
            n->expires_at = expires;
400
401
0
            if (HashTableAdd(tctx->ht, n, 0) == 0) {
402
0
                ThresholdCacheItem *r = THRESHOLD_CACHE_RB_INSERT(&tctx->tree, n);
403
0
                DEBUG_VALIDATE_BUG_ON(r != NULL); // duplicate; should be impossible
404
0
                (void)r;                          // only used by DEBUG_VALIDATE_BUG_ON
405
0
                return 1;
406
0
            }
407
0
            SCFree(n);
408
0
        }
409
0
        return -1;
410
0
    } else {
411
0
        found->expires_at = expires;
412
0
        found->retval = retval;
413
414
0
        THRESHOLD_CACHE_RB_REMOVE(&tctx->tree, found);
415
0
        THRESHOLD_CACHE_RB_INSERT(&tctx->tree, found);
416
0
        return 1;
417
0
    }
418
0
}
419
420
/** \brief Check Thread local thresholding cache
421
 *  \note only supports IPv4
422
 *  \retval -1 cache miss - not found
423
 *  \retval -2 cache miss - found but expired
424
 *  \retval -3 error - cache not initialized
425
 *  \retval -4 error - unsupported tracker
426
 *  \retval ret cached return code
427
 */
428
static int CheckCache(DetectEngineThreadCtx *det_ctx, const Packet *p, const int8_t track,
429
        const uint32_t sid, const uint32_t gid, const uint32_t rev)
430
8
{
431
8
    struct ThresholdCacheThreadCtx *tctx = GetThreadCtx(det_ctx);
432
8
    if (!tctx) {
433
0
        return -3;
434
0
    }
435
436
8
    tctx->lookup_cnt++;
437
438
8
    uint32_t addr;
439
8
    if (track == TRACK_SRC) {
440
8
        addr = p->src.addr_data32[0];
441
8
    } else if (track == TRACK_DST) {
442
0
        addr = p->dst.addr_data32[0];
443
0
    } else {
444
0
        tctx->lookup_nosupport++;
445
0
        return -4; // error tracker not unsupported
446
0
    }
447
448
8
    if (SCTIME_SECS(p->ts) > tctx->housekeeping_ts) {
449
3
        ThresholdCacheExpire(det_ctx, p->ts);
450
3
    }
451
452
8
    ThresholdCacheItem lookup = {
453
8
        .track = track,
454
8
        .ipv = 4,
455
8
        .key[TC_ADDRESS] = addr,
456
8
        .key[TC_SID] = sid,
457
8
        .key[TC_GID] = gid,
458
8
        .key[TC_REV] = rev,
459
8
        .key[TC_TENANT] = p->tenant_id,
460
8
    };
461
8
    ThresholdCacheItem *found = HashTableLookup(tctx->ht, &lookup, 0);
462
8
    if (found) {
463
0
        if (SCTIME_CMP_GT(p->ts, found->expires_at)) {
464
0
            THRESHOLD_CACHE_RB_REMOVE(&tctx->tree, found);
465
0
            HashTableRemove(tctx->ht, found, 0);
466
0
            tctx->lookup_miss_expired++;
467
0
            return -2; // cache miss - found but expired
468
0
        }
469
0
        tctx->lookup_hit++;
470
0
        return found->retval;
471
0
    }
472
8
    tctx->lookup_miss++;
473
8
    return -1; // cache miss - not found
474
8
}
475
476
static void ThresholdCacheThreadFree(void *ptr)
477
0
{
478
0
    if (ptr != NULL) {
479
0
        struct ThresholdCacheThreadCtx *tctx = ptr;
480
0
        DumpCacheStats(tctx);
481
0
        HashTableFree(tctx->ht);
482
0
        SCFree(tctx);
483
0
    }
484
0
}
485
486
static void ThresholdCacheInit(void)
487
78
{
488
#ifdef UNITTESTS
489
    /* many tests don't manage the thread storage correctly, so skip the cache in unittests */
490
    if (!(RunmodeIsUnittests())) {
491
#endif
492
        /* Register thread storage. */
493
78
        thread_storage_id = ThreadStorageRegister(
494
78
                "threshold_cache", sizeof(void *), NULL, ThresholdCacheThreadFree);
495
78
        if (thread_storage_id.id < 0) {
496
0
            FatalError("Failed to register threshold_cache thread storage");
497
0
        }
498
#ifdef UNITTESTS
499
    }
500
#endif
501
78
}
502
503
int ThresholdCacheThreadInit(DetectEngineThreadCtx *det_ctx)
504
84.8k
{
505
84.8k
    if (thread_storage_id.id < 0)
506
0
        return 0;
507
    /* we can get called more than once per thread for MT */
508
84.8k
    if (ThreadGetStorageById(det_ctx->tv, thread_storage_id) != NULL)
509
84.8k
        return 0;
510
511
5
    struct ThresholdCacheThreadCtx *tctx = SCCalloc(1, sizeof(*tctx));
512
5
    if (tctx == NULL)
513
0
        return -1;
514
515
5
    uint32_t seed = (uint32_t)RandomGet();
516
517
5
    tctx->ht = HashTableInitWithSeed(256, ThresholdCacheHashFunc, ThresholdCacheHashCompareFunc,
518
5
            ThresholdCacheHashFreeFunc, seed);
519
5
    if (tctx->ht == NULL) {
520
0
        SCFree(tctx);
521
0
        return -1;
522
0
    }
523
524
5
    RB_INIT(&tctx->tree);
525
5
    ThreadSetStorageById(det_ctx->tv, thread_storage_id, tctx);
526
5
    return 0;
527
5
}
528
529
/**
530
 * \brief Return next DetectThresholdData for signature
531
 *
532
 * \param sig  Signature pointer
533
 * \param psm  Pointer to a Signature Match pointer
534
 * \param list List to return data from
535
 *
536
 * \retval tsh Return the threshold data from signature or NULL if not found
537
 */
538
const DetectThresholdData *SigGetThresholdTypeIter(
539
        const Signature *sig, const SigMatchData **psm, int list)
540
7.59k
{
541
7.59k
    const SigMatchData *smd = NULL;
542
7.59k
    const DetectThresholdData *tsh = NULL;
543
544
7.59k
    if (sig == NULL)
545
0
        return NULL;
546
547
7.59k
    if (*psm == NULL) {
548
7.59k
        smd = sig->sm_arrays[list];
549
7.59k
    } else {
550
        /* Iteration in progress, using provided value */
551
0
        smd = *psm;
552
0
    }
553
554
7.59k
    while (1) {
555
7.59k
        if (smd->type == DETECT_THRESHOLD || smd->type == DETECT_DETECTION_FILTER) {
556
7.59k
            tsh = (DetectThresholdData *)smd->ctx;
557
558
7.59k
            if (smd->is_last) {
559
7.59k
                *psm = NULL;
560
7.59k
            } else {
561
0
                *psm = smd + 1;
562
0
            }
563
7.59k
            return tsh;
564
7.59k
        }
565
566
0
        if (smd->is_last) {
567
0
            break;
568
0
        }
569
0
        smd++;
570
0
    }
571
0
    *psm = NULL;
572
0
    return NULL;
573
7.59k
}
574
575
typedef struct FlowThresholdEntryList_ {
576
    struct FlowThresholdEntryList_ *next;
577
    ThresholdEntry threshold;
578
} FlowThresholdEntryList;
579
580
static void FlowThresholdEntryListFree(FlowThresholdEntryList *list)
581
22
{
582
56
    for (FlowThresholdEntryList *i = list; i != NULL;) {
583
34
        FlowThresholdEntryList *next = i->next;
584
34
        SCFree(i);
585
34
        i = next;
586
34
    }
587
22
}
588
589
/** struct for storing per flow thresholds. This will be stored in the Flow::flowvar list, so it
590
 * needs to follow the GenericVar header format. */
591
typedef struct FlowVarThreshold_ {
592
    uint16_t type;
593
    uint8_t pad[6];
594
    struct GenericVar_ *next;
595
    FlowThresholdEntryList *thresholds;
596
} FlowVarThreshold;
597
598
void FlowThresholdVarFree(void *ptr)
599
22
{
600
22
    FlowVarThreshold *t = ptr;
601
22
    FlowThresholdEntryListFree(t->thresholds);
602
22
    SCFree(t);
603
22
}
604
605
static FlowVarThreshold *FlowThresholdVarGet(Flow *f)
606
120
{
607
120
    if (f == NULL)
608
0
        return NULL;
609
610
144
    for (GenericVar *gv = f->flowvar; gv != NULL; gv = gv->next) {
611
100
        if (gv->type == DETECT_THRESHOLD)
612
76
            return (FlowVarThreshold *)gv;
613
100
    }
614
615
44
    return NULL;
616
120
}
617
618
static ThresholdEntry *ThresholdFlowLookupEntry(
619
        Flow *f, uint32_t sid, uint32_t gid, uint32_t rev, uint32_t tenant_id)
620
86
{
621
86
    FlowVarThreshold *t = FlowThresholdVarGet(f);
622
86
    if (t == NULL)
623
22
        return NULL;
624
625
104
    for (FlowThresholdEntryList *e = t->thresholds; e != NULL; e = e->next) {
626
92
        if (e->threshold.key[SID] == sid && e->threshold.key[GID] == gid &&
627
52
                e->threshold.key[REV] == rev && e->threshold.key[TENANT] == tenant_id) {
628
52
            return &e->threshold;
629
52
        }
630
92
    }
631
12
    return NULL;
632
64
}
633
634
static int AddEntryToFlow(Flow *f, FlowThresholdEntryList *e, SCTime_t packet_time)
635
34
{
636
34
    DEBUG_VALIDATE_BUG_ON(e == NULL);
637
638
34
    FlowVarThreshold *t = FlowThresholdVarGet(f);
639
34
    if (t == NULL) {
640
22
        t = SCCalloc(1, sizeof(*t));
641
22
        if (t == NULL) {
642
0
            return -1;
643
0
        }
644
22
        t->type = DETECT_THRESHOLD;
645
22
        GenericVarAppend(&f->flowvar, (GenericVar *)t);
646
22
    }
647
648
34
    e->next = t->thresholds;
649
34
    t->thresholds = e;
650
34
    return 0;
651
34
}
652
653
static int ThresholdHandlePacketSuppress(Packet *p,
654
        const DetectThresholdData *td, uint32_t sid, uint32_t gid)
655
0
{
656
0
    int ret = 0;
657
0
    DetectAddress *m = NULL;
658
0
    switch (td->track) {
659
0
        case TRACK_DST:
660
0
            m = DetectAddressLookupInHead(&td->addrs, &p->dst);
661
0
            SCLogDebug("TRACK_DST");
662
0
            break;
663
0
        case TRACK_SRC:
664
0
            m = DetectAddressLookupInHead(&td->addrs, &p->src);
665
0
            SCLogDebug("TRACK_SRC");
666
0
            break;
667
        /* suppress if either src or dst is a match on the suppress
668
         * address list */
669
0
        case TRACK_EITHER:
670
0
            m = DetectAddressLookupInHead(&td->addrs, &p->src);
671
0
            if (m == NULL) {
672
0
                m = DetectAddressLookupInHead(&td->addrs, &p->dst);
673
0
            }
674
0
            break;
675
0
        case TRACK_RULE:
676
0
        case TRACK_FLOW:
677
0
        default:
678
0
            SCLogError("track mode %d is not supported", td->track);
679
0
            break;
680
0
    }
681
0
    if (m == NULL)
682
0
        ret = 1;
683
0
    else
684
0
        ret = 2; /* suppressed but still need actions */
685
686
0
    return ret;
687
0
}
688
689
static inline void RateFilterSetAction(PacketAlert *pa, uint8_t new_action)
690
0
{
691
0
    switch (new_action) {
692
0
        case TH_ACTION_ALERT:
693
0
            pa->flags |= PACKET_ALERT_FLAG_RATE_FILTER_MODIFIED;
694
0
            pa->action = ACTION_ALERT;
695
0
            break;
696
0
        case TH_ACTION_DROP:
697
0
            pa->flags |= PACKET_ALERT_FLAG_RATE_FILTER_MODIFIED;
698
0
            pa->action = ACTION_DROP;
699
0
            break;
700
0
        case TH_ACTION_REJECT:
701
0
            pa->flags |= PACKET_ALERT_FLAG_RATE_FILTER_MODIFIED;
702
0
            pa->action = (ACTION_REJECT | ACTION_DROP);
703
0
            break;
704
0
        case TH_ACTION_PASS:
705
0
            pa->flags |= PACKET_ALERT_FLAG_RATE_FILTER_MODIFIED;
706
0
            pa->action = ACTION_PASS;
707
0
            break;
708
0
        default:
709
            /* Weird, leave the default action */
710
0
            break;
711
0
    }
712
0
}
713
714
/** \internal
715
 *  \brief Apply the multiplier and return the new value.
716
 *  If it would overflow the uint32_t we return UINT32_MAX.
717
 */
718
static uint32_t BackoffCalcNextValue(const uint32_t cur, const uint32_t m)
719
0
{
720
    /* goal is to see if cur * m would overflow uint32_t */
721
0
    if (unlikely(UINT32_MAX / m < cur)) {
722
0
        return UINT32_MAX;
723
0
    }
724
0
    return cur * m;
725
0
}
726
727
/**
728
 *  \retval 2 silent match (no alert but apply actions)
729
 *  \retval 1 normal match
730
 *  \retval 0 no match
731
 */
732
static int ThresholdSetup(const DetectThresholdData *td, ThresholdEntry *te,
733
        const SCTime_t packet_time, const uint32_t sid, const uint32_t gid, const uint32_t rev,
734
        const uint32_t tenant_id)
735
1
{
736
1
    te->key[SID] = sid;
737
1
    te->key[GID] = gid;
738
1
    te->key[REV] = rev;
739
1
    te->key[TRACK] = td->track;
740
1
    te->key[TENANT] = tenant_id;
741
742
1
    te->seconds = td->seconds;
743
1
    te->current_count = 1;
744
745
1
    switch (td->type) {
746
0
        case TYPE_BACKOFF:
747
0
            te->backoff.next_value = td->count;
748
0
            break;
749
1
        default:
750
1
            te->tv1 = packet_time;
751
1
            te->tv_timeout = SCTIME_INITIALIZER;
752
1
            break;
753
1
    }
754
755
1
    switch (td->type) {
756
1
        case TYPE_LIMIT:
757
1
        case TYPE_RATE:
758
1
            return 1;
759
0
        case TYPE_THRESHOLD:
760
0
        case TYPE_BOTH:
761
0
            if (td->count == 1)
762
0
                return 1;
763
0
            return 0;
764
0
        case TYPE_BACKOFF:
765
0
            if (td->count == 1) {
766
0
                te->backoff.next_value =
767
0
                        BackoffCalcNextValue(te->backoff.next_value, td->multiplier);
768
0
                return 1;
769
0
            }
770
0
            return 0;
771
0
        case TYPE_DETECTION:
772
0
            return 0;
773
1
    }
774
0
    return 0;
775
1
}
776
777
static int ThresholdCheckUpdate(const DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
778
        const DetectThresholdData *td, ThresholdEntry *te,
779
        const Packet *p, // ts only? - cache too
780
        const uint32_t sid, const uint32_t gid, const uint32_t rev, PacketAlert *pa)
781
2
{
782
2
    int ret = 0;
783
2
    const SCTime_t packet_time = p->ts;
784
2
    const SCTime_t entry = SCTIME_ADD_SECS(te->tv1, td->seconds);
785
2
    switch (td->type) {
786
2
        case TYPE_LIMIT:
787
2
            SCLogDebug("limit");
788
789
2
            if (SCTIME_CMP_LTE(p->ts, entry)) {
790
2
                te->current_count++;
791
792
2
                if (te->current_count <= td->count) {
793
2
                    ret = 1;
794
2
                } else {
795
0
                    ret = 2;
796
797
0
                    if (PacketIsIPv4(p)) {
798
0
                        SetupCache(det_ctx, p, td->track, (int8_t)ret, sid, gid, rev, entry);
799
0
                    }
800
0
                }
801
2
            } else {
802
                /* entry expired, reset */
803
0
                te->tv1 = p->ts;
804
0
                te->current_count = 1;
805
0
                ret = 1;
806
0
            }
807
2
            break;
808
0
        case TYPE_THRESHOLD:
809
0
            if (SCTIME_CMP_LTE(p->ts, entry)) {
810
0
                te->current_count++;
811
812
0
                if (te->current_count >= td->count) {
813
0
                    ret = 1;
814
0
                    te->current_count = 0;
815
0
                }
816
0
            } else {
817
0
                te->tv1 = p->ts;
818
0
                te->current_count = 1;
819
0
            }
820
0
            break;
821
0
        case TYPE_BOTH:
822
0
            if (SCTIME_CMP_LTE(p->ts, entry)) {
823
                /* within time limit */
824
825
0
                te->current_count++;
826
0
                if (te->current_count == td->count) {
827
0
                    ret = 1;
828
0
                } else if (te->current_count > td->count) {
829
                    /* silent match */
830
0
                    ret = 2;
831
832
0
                    if (PacketIsIPv4(p)) {
833
0
                        SetupCache(det_ctx, p, td->track, (int8_t)ret, sid, gid, rev, entry);
834
0
                    }
835
0
                }
836
0
            } else {
837
                /* expired, so reset */
838
0
                te->tv1 = p->ts;
839
0
                te->current_count = 1;
840
841
                /* if we have a limit of 1, this is a match */
842
0
                if (te->current_count == td->count) {
843
0
                    ret = 1;
844
0
                }
845
0
            }
846
0
            break;
847
0
        case TYPE_DETECTION:
848
0
            SCLogDebug("detection_filter");
849
850
0
            if (SCTIME_CMP_LTE(p->ts, entry)) {
851
                /* within timeout */
852
0
                te->current_count++;
853
0
                if (te->current_count > td->count) {
854
0
                    ret = 1;
855
0
                }
856
0
            } else {
857
                /* expired, reset */
858
0
                te->tv1 = p->ts;
859
0
                te->current_count = 1;
860
0
            }
861
0
            break;
862
0
        case TYPE_RATE: {
863
0
            SCLogDebug("rate_filter");
864
0
            const uint8_t original_action = pa->action;
865
0
            ret = 1;
866
            /* Check if we have a timeout enabled, if so,
867
             * we still matching (and enabling the new_action) */
868
0
            if (SCTIME_CMP_NEQ(te->tv_timeout, SCTIME_INITIALIZER)) {
869
0
                if ((SCTIME_SECS(packet_time) - SCTIME_SECS(te->tv_timeout)) > td->timeout) {
870
                    /* Ok, we are done, timeout reached */
871
0
                    te->tv_timeout = SCTIME_INITIALIZER;
872
0
                } else {
873
                    /* Already matching */
874
0
                    RateFilterSetAction(pa, td->new_action);
875
0
                }
876
0
            } else {
877
                /* Update the matching state with the timeout interval */
878
0
                if (SCTIME_CMP_LTE(packet_time, entry)) {
879
0
                    te->current_count++;
880
0
                    if (te->current_count > td->count) {
881
                        /* Then we must enable the new action by setting a
882
                         * timeout */
883
0
                        te->tv_timeout = packet_time;
884
0
                        RateFilterSetAction(pa, td->new_action);
885
0
                    }
886
0
                } else {
887
0
                    te->tv1 = packet_time;
888
0
                    te->current_count = 1;
889
0
                }
890
0
            }
891
0
            if (de_ctx->RateFilterCallback && original_action != pa->action) {
892
0
                pa->action = de_ctx->RateFilterCallback(p, sid, gid, rev, original_action,
893
0
                        pa->action, de_ctx->rate_filter_callback_arg);
894
0
                if (pa->action == original_action) {
895
                    /* Reset back to original action, clear modified flag. */
896
0
                    pa->flags &= ~PACKET_ALERT_FLAG_RATE_FILTER_MODIFIED;
897
0
                }
898
0
            }
899
0
            break;
900
0
        }
901
0
        case TYPE_BACKOFF:
902
0
            SCLogDebug("backoff");
903
904
0
            if (te->current_count < UINT32_MAX) {
905
0
                te->current_count++;
906
0
                if (te->backoff.next_value == te->current_count) {
907
0
                    te->backoff.next_value =
908
0
                            BackoffCalcNextValue(te->backoff.next_value, td->multiplier);
909
0
                    SCLogDebug("te->backoff.next_value %u", te->backoff.next_value);
910
0
                    ret = 1;
911
0
                } else {
912
0
                    ret = 2;
913
0
                }
914
0
            } else {
915
                /* if count reaches UINT32_MAX, we just silent match on the rest of the flow */
916
0
                ret = 2;
917
0
            }
918
0
            break;
919
2
    }
920
2
    return ret;
921
2
}
922
923
static int ThresholdGetFromHash(const DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
924
        struct Thresholds *tctx, const Packet *p, const Signature *s, const DetectThresholdData *td,
925
        PacketAlert *pa)
926
7.50k
{
927
    /* fast track for count 1 threshold */
928
7.50k
    if (td->count == 1 && td->type == TYPE_THRESHOLD) {
929
0
        return 1;
930
0
    }
931
932
7.50k
    ThresholdEntry lookup;
933
7.50k
    memset(&lookup, 0, sizeof(lookup));
934
7.50k
    lookup.key[SID] = s->id;
935
7.50k
    lookup.key[GID] = s->gid;
936
7.50k
    lookup.key[REV] = s->rev;
937
7.50k
    lookup.key[TRACK] = td->track;
938
7.50k
    lookup.key[TENANT] = p->tenant_id;
939
7.50k
    if (td->track == TRACK_SRC) {
940
1.23k
        COPY_ADDRESS(&p->src, &lookup.addr);
941
6.27k
    } else if (td->track == TRACK_DST) {
942
11
        COPY_ADDRESS(&p->dst, &lookup.addr);
943
6.25k
    } else if (td->track == TRACK_BOTH) {
944
        /* make sure lower ip address is first */
945
0
        if (PacketIsIPv4(p)) {
946
0
            if (SCNtohl(p->src.addr_data32[0]) < SCNtohl(p->dst.addr_data32[0])) {
947
0
                COPY_ADDRESS(&p->src, &lookup.addr);
948
0
                COPY_ADDRESS(&p->dst, &lookup.addr2);
949
0
            } else {
950
0
                COPY_ADDRESS(&p->dst, &lookup.addr);
951
0
                COPY_ADDRESS(&p->src, &lookup.addr2);
952
0
            }
953
0
        } else {
954
0
            if (AddressIPv6Lt(&p->src, &p->dst)) {
955
0
                COPY_ADDRESS(&p->src, &lookup.addr);
956
0
                COPY_ADDRESS(&p->dst, &lookup.addr2);
957
0
            } else {
958
0
                COPY_ADDRESS(&p->dst, &lookup.addr);
959
0
                COPY_ADDRESS(&p->src, &lookup.addr2);
960
0
            }
961
0
        }
962
0
    }
963
964
7.50k
    struct THashDataGetResult res = THashGetFromHash(tctx->thash, &lookup);
965
7.50k
    if (res.data) {
966
7.50k
        SCLogDebug("found %p, is_new %s", res.data, BOOL2STR(res.is_new));
967
7.50k
        int r;
968
7.50k
        ThresholdEntry *te = res.data->data;
969
7.50k
        if (res.is_new) {
970
            // new threshold, set up
971
25
            r = ThresholdSetup(td, te, p->ts, s->id, s->gid, s->rev, p->tenant_id);
972
7.48k
        } else {
973
            // existing, check/update
974
7.48k
            r = ThresholdCheckUpdate(de_ctx, det_ctx, td, te, p, s->id, s->gid, s->rev, pa);
975
7.48k
        }
976
977
7.50k
        (void)THashDecrUsecnt(res.data);
978
7.50k
        THashDataUnlock(res.data);
979
7.50k
        return r;
980
7.50k
    }
981
0
    return 0; // TODO error?
982
7.50k
}
983
984
/**
985
 *  \retval 2 silent match (no alert but apply actions)
986
 *  \retval 1 normal match
987
 *  \retval 0 no match
988
 */
989
static int ThresholdHandlePacketFlow(const DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
990
        Flow *f, Packet *p, const DetectThresholdData *td, uint32_t sid, uint32_t gid, uint32_t rev,
991
        PacketAlert *pa)
992
86
{
993
86
    int ret = 0;
994
86
    ThresholdEntry *found = ThresholdFlowLookupEntry(f, sid, gid, rev, p->tenant_id);
995
86
    SCLogDebug("found %p sid %u gid %u rev %u", found, sid, gid, rev);
996
997
86
    if (found == NULL) {
998
34
        FlowThresholdEntryList *new = SCCalloc(1, sizeof(*new));
999
34
        if (new == NULL)
1000
0
            return 0;
1001
1002
        // new threshold, set up
1003
34
        ret = ThresholdSetup(td, &new->threshold, p->ts, sid, gid, rev, p->tenant_id);
1004
1005
34
        if (AddEntryToFlow(f, new, p->ts) == -1) {
1006
0
            SCFree(new);
1007
0
            return 0;
1008
0
        }
1009
52
    } else {
1010
        // existing, check/update
1011
52
        ret = ThresholdCheckUpdate(de_ctx, det_ctx, td, found, p, sid, gid, rev, pa);
1012
52
    }
1013
86
    return ret;
1014
86
}
1015
1016
/**
1017
 * \brief Make the threshold logic for signatures
1018
 *
1019
 * \param de_ctx Detection Context
1020
 * \param tsh_ptr Threshold element
1021
 * \param p Packet structure
1022
 * \param s Signature structure
1023
 *
1024
 * \retval 2 silent match (no alert but apply actions)
1025
 * \retval 1 alert on this event
1026
 * \retval 0 do not alert on this event
1027
 */
1028
int PacketAlertThreshold(const DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
1029
        const DetectThresholdData *td, Packet *p, const Signature *s, PacketAlert *pa)
1030
7.59k
{
1031
7.59k
    SCEnter();
1032
1033
7.59k
    int ret = 0;
1034
7.59k
    if (td == NULL) {
1035
0
        SCReturnInt(0);
1036
0
    }
1037
1038
7.59k
    if (td->type == TYPE_SUPPRESS) {
1039
0
        ret = ThresholdHandlePacketSuppress(p,td,s->id,s->gid);
1040
7.59k
    } else if (td->track == TRACK_SRC) {
1041
1.23k
        if (PacketIsIPv4(p) && (td->type == TYPE_LIMIT || td->type == TYPE_BOTH)) {
1042
8
            int cache_ret = CheckCache(det_ctx, p, td->track, s->id, s->gid, s->rev);
1043
8
            if (cache_ret >= 0) {
1044
0
                SCReturnInt(cache_ret);
1045
0
            }
1046
8
        }
1047
1048
1.23k
        ret = ThresholdGetFromHash(de_ctx, det_ctx, &ctx, p, s, td, pa);
1049
6.35k
    } else if (td->track == TRACK_DST) {
1050
11
        if (PacketIsIPv4(p) && (td->type == TYPE_LIMIT || td->type == TYPE_BOTH)) {
1051
0
            int cache_ret = CheckCache(det_ctx, p, td->track, s->id, s->gid, s->rev);
1052
0
            if (cache_ret >= 0) {
1053
0
                SCReturnInt(cache_ret);
1054
0
            }
1055
0
        }
1056
1057
11
        ret = ThresholdGetFromHash(de_ctx, det_ctx, &ctx, p, s, td, pa);
1058
6.34k
    } else if (td->track == TRACK_BOTH) {
1059
0
        ret = ThresholdGetFromHash(de_ctx, det_ctx, &ctx, p, s, td, pa);
1060
6.34k
    } else if (td->track == TRACK_RULE) {
1061
6.25k
        ret = ThresholdGetFromHash(de_ctx, det_ctx, &ctx, p, s, td, pa);
1062
6.25k
    } else if (td->track == TRACK_FLOW) {
1063
86
        if (p->flow) {
1064
86
            ret = ThresholdHandlePacketFlow(
1065
86
                    de_ctx, det_ctx, p->flow, p, td, s->id, s->gid, s->rev, pa);
1066
86
        }
1067
86
    }
1068
1069
7.59k
    SCReturnInt(ret);
1070
7.59k
}
1071
1072
/**
1073
 * @}
1074
 */