Coverage Report

Created: 2026-03-31 07:45

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata7/src/detect-engine-alert.c
Line
Count
Source
1
/* Copyright (C) 2007-2022 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
#include "suricata-common.h"
19
20
#include "detect.h"
21
#include "detect-engine-alert.h"
22
#include "detect-engine-threshold.h"
23
#include "detect-engine-tag.h"
24
25
#include "decode.h"
26
#include "packet.h"
27
28
#include "flow.h"
29
#include "flow-private.h"
30
31
#ifdef QA_SIMULATION
32
#include "util-exception-policy.h"
33
#endif
34
35
#include "util-profiling.h"
36
#include "util-validate.h"
37
38
#include "action-globals.h"
39
40
/** tag signature we use for tag alerts */
41
static Signature g_tag_signature;
42
/** tag packet alert structure for tag alerts */
43
static PacketAlert g_tag_pa;
44
45
void PacketAlertTagInit(void)
46
71
{
47
71
    memset(&g_tag_signature, 0x00, sizeof(g_tag_signature));
48
49
71
    g_tag_signature.id = TAG_SIG_ID;
50
71
    g_tag_signature.gid = TAG_SIG_GEN;
51
71
    g_tag_signature.num = TAG_SIG_ID;
52
71
    g_tag_signature.rev = 1;
53
71
    g_tag_signature.prio = 2;
54
55
71
    memset(&g_tag_pa, 0x00, sizeof(g_tag_pa));
56
57
71
    g_tag_pa.action = ACTION_ALERT;
58
71
    g_tag_pa.s = &g_tag_signature;
59
71
}
60
61
/**
62
 * \brief Handle a packet and check if needs a threshold logic
63
 *        Also apply rule action if necessary.
64
 *
65
 * \param de_ctx Detection Context
66
 * \param sig Signature pointer
67
 * \param p Packet structure
68
 *
69
 * \retval 1 alert is not suppressed
70
 * \retval 0 alert is suppressed
71
 */
72
static int PacketAlertHandle(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
73
                             const Signature *s, Packet *p, PacketAlert *pa)
74
461k
{
75
461k
    SCEnter();
76
461k
    int ret = 1;
77
461k
    const DetectThresholdData *td = NULL;
78
461k
    const SigMatchData *smd;
79
80
461k
    if (!(PKT_IS_IPV4(p) || PKT_IS_IPV6(p))) {
81
1.20k
        SCReturnInt(1);
82
1.20k
    }
83
84
    /* handle suppressions first */
85
459k
    if (s->sm_arrays[DETECT_SM_LIST_SUPPRESS] != NULL) {
86
0
        KEYWORD_PROFILING_SET_LIST(det_ctx, DETECT_SM_LIST_SUPPRESS);
87
0
        smd = NULL;
88
0
        do {
89
0
            td = SigGetThresholdTypeIter(s, &smd, DETECT_SM_LIST_SUPPRESS);
90
0
            if (td != NULL) {
91
0
                SCLogDebug("td %p", td);
92
93
                /* PacketAlertThreshold returns 2 if the alert is suppressed but
94
                 * we do need to apply rule actions to the packet. */
95
0
                KEYWORD_PROFILING_START;
96
0
                ret = PacketAlertThreshold(de_ctx, det_ctx, td, p, s, pa);
97
0
                if (ret == 0 || ret == 2) {
98
0
                    KEYWORD_PROFILING_END(det_ctx, DETECT_THRESHOLD, 0);
99
                    /* It doesn't match threshold, remove it */
100
0
                    SCReturnInt(ret);
101
0
                }
102
0
                KEYWORD_PROFILING_END(det_ctx, DETECT_THRESHOLD, 1);
103
0
            }
104
0
        } while (smd != NULL);
105
0
    }
106
107
    /* if we're still here, consider thresholding */
108
459k
    if (s->sm_arrays[DETECT_SM_LIST_THRESHOLD] != NULL) {
109
2.58k
        KEYWORD_PROFILING_SET_LIST(det_ctx, DETECT_SM_LIST_THRESHOLD);
110
2.58k
        smd = NULL;
111
2.58k
        do {
112
2.58k
            td = SigGetThresholdTypeIter(s, &smd, DETECT_SM_LIST_THRESHOLD);
113
2.58k
            if (td != NULL) {
114
2.58k
                SCLogDebug("td %p", td);
115
116
                /* PacketAlertThreshold returns 2 if the alert is suppressed but
117
                 * we do need to apply rule actions to the packet. */
118
2.58k
                KEYWORD_PROFILING_START;
119
2.58k
                ret = PacketAlertThreshold(de_ctx, det_ctx, td, p, s, pa);
120
2.58k
                if (ret == 0 || ret == 2) {
121
2.26k
                    KEYWORD_PROFILING_END(det_ctx, DETECT_THRESHOLD ,0);
122
                    /* It doesn't match threshold, remove it */
123
2.26k
                    SCReturnInt(ret);
124
2.26k
                }
125
319
                KEYWORD_PROFILING_END(det_ctx, DETECT_THRESHOLD, 1);
126
319
            }
127
2.58k
        } while (smd != NULL);
128
2.58k
    }
129
459k
    SCReturnInt(1);
130
459k
}
131
132
#ifdef UNITTESTS
133
/**
134
 * \brief Check if a certain sid alerted, this is used in the test functions
135
 *
136
 * \param p   Packet on which we want to check if the signature alerted or not
137
 * \param sid Signature id of the signature that has to be checked for a match
138
 *
139
 * \retval match A value > 0 on a match; 0 on no match
140
 */
141
int PacketAlertCheck(Packet *p, uint32_t sid)
142
{
143
    int match = 0;
144
145
    for (uint16_t i = 0; i < p->alerts.cnt; i++) {
146
        BUG_ON(p->alerts.alerts[i].s == NULL);
147
        if (p->alerts.alerts[i].s->id == sid)
148
            match++;
149
    }
150
151
    return match;
152
}
153
#endif
154
155
static inline void RuleActionToFlow(const uint8_t action, Flow *f)
156
2.26k
{
157
2.26k
    if (action & (ACTION_DROP | ACTION_REJECT_ANY | ACTION_PASS)) {
158
2.26k
        if (f->flags & (FLOW_ACTION_DROP | FLOW_ACTION_PASS)) {
159
            /* drop or pass already set. First to set wins. */
160
0
            SCLogDebug("not setting %s flow already set to %s",
161
0
                    (action & ACTION_PASS) ? "pass" : "drop",
162
0
                    (f->flags & FLOW_ACTION_DROP) ? "drop" : "pass");
163
2.26k
        } else {
164
2.26k
            if (action & (ACTION_DROP | ACTION_REJECT_ANY)) {
165
171
                f->flags |= FLOW_ACTION_DROP;
166
171
                SCLogDebug("setting flow action drop");
167
171
            }
168
2.26k
            if (action & ACTION_PASS) {
169
2.09k
                f->flags |= FLOW_ACTION_PASS;
170
2.09k
                SCLogDebug("setting flow action pass");
171
2.09k
                FlowSetNoPacketInspectionFlag(f);
172
2.09k
            }
173
2.26k
        }
174
2.26k
    }
175
2.26k
}
176
177
/** \brief Apply action(s) and Set 'drop' sig info,
178
 *         if applicable
179
 *  \param p packet
180
 *  \param s signature -- for id, sig pointer, not actions
181
 *  \param pa packet alert struct -- match, including actions after thresholding (rate_filter) */
182
static void PacketApplySignatureActions(Packet *p, const Signature *s, const PacketAlert *pa)
183
459k
{
184
459k
    SCLogDebug("packet %" PRIu64 " sid %u action %02x alert_flags %02x", p->pcap_cnt, s->id,
185
459k
            pa->action, pa->flags);
186
187
    /* REJECT also sets ACTION_DROP, just make it more visible with this check */
188
459k
    if (pa->action & ACTION_DROP_REJECT) {
189
        /* PacketDrop will update the packet action, too */
190
1.51k
        PacketDrop(p, pa->action,
191
1.51k
                (pa->flags & PACKET_ALERT_RATE_FILTER_MODIFIED) ? PKT_DROP_REASON_RULES_THRESHOLD
192
1.51k
                                                                : PKT_DROP_REASON_RULES);
193
1.51k
        SCLogDebug("[packet %p][DROP sid %u]", p, s->id);
194
195
1.51k
        if (p->alerts.drop.action == 0) {
196
0
            p->alerts.drop.num = s->num;
197
0
            p->alerts.drop.action = pa->action;
198
0
            p->alerts.drop.s = (Signature *)s;
199
0
        }
200
1.51k
        if ((p->flow != NULL) && (pa->flags & PACKET_ALERT_FLAG_APPLY_ACTION_TO_FLOW)) {
201
171
            RuleActionToFlow(pa->action, p->flow);
202
171
        }
203
204
1.51k
        DEBUG_VALIDATE_BUG_ON(!PacketCheckAction(p, ACTION_DROP));
205
458k
    } else {
206
458k
        if (pa->action & ACTION_PASS) {
207
5.14k
            SCLogDebug("[packet %p][PASS sid %u]", p, s->id);
208
            // nothing to set in the packet
209
452k
        } else if (pa->action & (ACTION_ALERT | ACTION_CONFIG)) {
210
            // nothing to set in the packet
211
445k
        } else if (pa->action != 0) {
212
0
            DEBUG_VALIDATE_BUG_ON(1); // should be unreachable
213
0
        }
214
215
458k
        if ((pa->action & ACTION_PASS) && (p->flow != NULL) &&
216
2.48k
                (pa->flags & PACKET_ALERT_FLAG_APPLY_ACTION_TO_FLOW)) {
217
2.09k
            RuleActionToFlow(pa->action, p->flow);
218
2.09k
        }
219
458k
    }
220
459k
}
221
222
void AlertQueueInit(DetectEngineThreadCtx *det_ctx)
223
146k
{
224
146k
    det_ctx->alert_queue_size = 0;
225
146k
    det_ctx->alert_queue = SCCalloc(packet_alert_max, sizeof(PacketAlert));
226
146k
    if (det_ctx->alert_queue == NULL) {
227
0
        FatalError("failed to allocate %" PRIu64 " bytes for the alert queue",
228
0
                (uint64_t)(packet_alert_max * sizeof(PacketAlert)));
229
0
    }
230
146k
    det_ctx->alert_queue_capacity = packet_alert_max;
231
146k
    SCLogDebug("alert queue initialized to %u elements (%" PRIu64 " bytes)", packet_alert_max,
232
146k
            (uint64_t)(packet_alert_max * sizeof(PacketAlert)));
233
146k
}
234
235
void AlertQueueFree(DetectEngineThreadCtx *det_ctx)
236
146k
{
237
146k
    SCFree(det_ctx->alert_queue);
238
146k
    det_ctx->alert_queue_capacity = 0;
239
146k
}
240
241
static inline uint16_t AlertQueueExpandDo(DetectEngineThreadCtx *det_ctx, uint16_t new_cap)
242
401
{
243
401
    DEBUG_VALIDATE_BUG_ON(det_ctx->alert_queue_capacity >= new_cap);
244
245
401
    void *tmp_queue = SCRealloc(det_ctx->alert_queue, new_cap * sizeof(PacketAlert));
246
401
    if (unlikely(tmp_queue == NULL)) {
247
        /* queue capacity didn't change */
248
0
        return det_ctx->alert_queue_capacity;
249
0
    }
250
401
    det_ctx->alert_queue = tmp_queue;
251
401
    det_ctx->alert_queue_capacity = new_cap;
252
401
    SCLogDebug("Alert queue size expanded: %u elements, bytes: %" PRIuMAX "",
253
401
            det_ctx->alert_queue_capacity, (uintmax_t)(new_cap * sizeof(PacketAlert)));
254
401
    return new_cap;
255
401
}
256
257
/** \internal
258
 * \retval the new capacity
259
 */
260
static uint16_t AlertQueueExpand(DetectEngineThreadCtx *det_ctx)
261
401
{
262
#ifdef QA_SIMULATION
263
    if (unlikely(g_eps_is_alert_queue_fail_mode))
264
        return det_ctx->alert_queue_capacity;
265
#endif
266
401
    if (det_ctx->alert_queue_capacity == UINT16_MAX) {
267
0
        return det_ctx->alert_queue_capacity;
268
0
    }
269
270
401
    uint16_t new_cap;
271
401
    if (det_ctx->alert_queue_capacity > (UINT16_MAX / 2)) {
272
0
        new_cap = UINT16_MAX;
273
401
    } else {
274
401
        new_cap = det_ctx->alert_queue_capacity * 2;
275
401
    }
276
401
    return AlertQueueExpandDo(det_ctx, new_cap);
277
401
}
278
279
/** \internal
280
 */
281
static inline PacketAlert PacketAlertSet(
282
        DetectEngineThreadCtx *det_ctx, const Signature *s, uint64_t tx_id, uint8_t alert_flags)
283
1.05M
{
284
1.05M
    PacketAlert pa;
285
1.05M
    pa.num = s->num;
286
1.05M
    pa.action = s->action;
287
1.05M
    pa.s = (Signature *)s;
288
1.05M
    pa.flags = alert_flags;
289
    /* Set tx_id if the frame has it */
290
1.05M
    pa.tx_id = tx_id;
291
1.05M
    pa.frame_id = (alert_flags & PACKET_ALERT_FLAG_FRAME) ? det_ctx->frame_id : 0;
292
1.05M
    return pa;
293
1.05M
}
294
295
/**
296
 * \brief Append signature to local packet alert queue for later preprocessing
297
 */
298
void AlertQueueAppend(DetectEngineThreadCtx *det_ctx, const Signature *s, Packet *p, uint64_t tx_id,
299
        uint8_t alert_flags)
300
463k
{
301
    /* first time we see a drop action signature, set that in the packet */
302
    /* we do that even before inserting into the queue, so we save it even if appending fails */
303
463k
    if (p->alerts.drop.action == 0 && s->action & ACTION_DROP) {
304
1.80k
        p->alerts.drop = PacketAlertSet(det_ctx, s, tx_id, alert_flags);
305
1.80k
        SCLogDebug("Set PacketAlert drop action. s->num %" PRIu32 "", s->num);
306
1.80k
    }
307
308
463k
    uint16_t pos = det_ctx->alert_queue_size;
309
463k
    if (pos == det_ctx->alert_queue_capacity) {
310
        /* we must grow the alert queue */
311
330
        if (pos == AlertQueueExpand(det_ctx)) {
312
            /* this means we failed to expand the queue */
313
0
            p->alerts.discarded++;
314
0
            return;
315
0
        }
316
330
    }
317
463k
    det_ctx->alert_queue[pos] = PacketAlertSet(det_ctx, s, tx_id, alert_flags);
318
319
463k
    SCLogDebug("Appending sid %" PRIu32 ", s->num %" PRIu32 " to alert queue", s->id, s->num);
320
463k
    det_ctx->alert_queue_size++;
321
463k
    return;
322
463k
}
323
324
/** \internal
325
 * \brief sort helper for sorting alerts by priority
326
 *
327
 * Sorting is done first based on num and then using tx_id, if nums are equal.
328
 * The Signature::num field is set based on internal priority. Higher priority
329
 * rules have lower nums.
330
 */
331
static int AlertQueueSortHelper(const void *a, const void *b)
332
183k
{
333
183k
    const PacketAlert *pa0 = a;
334
183k
    const PacketAlert *pa1 = b;
335
183k
    if (pa1->num == pa0->num) {
336
133k
        if (pa1->tx_id == PACKET_ALERT_NOTX) {
337
0
            return -1;
338
133k
        } else if (pa0->tx_id == PACKET_ALERT_NOTX) {
339
0
            return 1;
340
0
        }
341
133k
        return pa0->tx_id < pa1->tx_id ? 1 : -1;
342
133k
    }
343
50.6k
    return pa0->num > pa1->num ? 1 : -1;
344
183k
}
345
346
/** \internal
347
 * \brief Check if Signature action should be applied to flow and apply
348
 *
349
 */
350
static inline void FlowApplySignatureActions(
351
        Packet *p, PacketAlert *pa, const Signature *s, uint8_t alert_flags)
352
459k
{
353
    /* For DROP and PASS sigs we need to apply the action to the flow if
354
     * - sig is IP or PD only
355
     * - match is in applayer
356
     * - match is in stream */
357
459k
    if (pa->action & (ACTION_DROP | ACTION_PASS)) {
358
6.66k
        DEBUG_VALIDATE_BUG_ON(s->type == SIG_TYPE_NOT_SET);
359
6.66k
        DEBUG_VALIDATE_BUG_ON(s->type == SIG_TYPE_MAX);
360
361
6.66k
        enum SignaturePropertyFlowAction flow_action = signature_properties[s->type].flow_action;
362
6.66k
        if (flow_action == SIG_PROP_FLOW_ACTION_FLOW) {
363
4.96k
            pa->flags |= PACKET_ALERT_FLAG_APPLY_ACTION_TO_FLOW;
364
4.96k
        } else if (flow_action == SIG_PROP_FLOW_ACTION_FLOW_IF_STATEFUL) {
365
5
            if (pa->flags & (PACKET_ALERT_FLAG_STATE_MATCH | PACKET_ALERT_FLAG_STREAM_MATCH)) {
366
1
                pa->flags |= PACKET_ALERT_FLAG_APPLY_ACTION_TO_FLOW;
367
1
            }
368
5
        }
369
370
6.66k
        if (pa->flags & PACKET_ALERT_FLAG_APPLY_ACTION_TO_FLOW) {
371
4.96k
            SCLogDebug("packet %" PRIu64 " sid %u action %02x alert_flags %02x (set "
372
4.96k
                       "PACKET_ALERT_FLAG_APPLY_ACTION_TO_FLOW)",
373
4.96k
                    p->pcap_cnt, s->id, s->action, pa->flags);
374
4.96k
        }
375
6.66k
    }
376
459k
}
377
378
/**
379
 * \brief Check the threshold of the sigs that match, set actions, break on pass action
380
 *        This function iterate the packet alerts array, removing those that didn't match
381
 *        the threshold, and those that match after a signature with the action "pass".
382
 *        The array is sorted by action priority/order
383
 * \param de_ctx detection engine context
384
 * \param det_ctx detection engine thread context
385
 * \param p pointer to the packet
386
 */
387
void PacketAlertFinalize(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, Packet *p)
388
9.73M
{
389
9.73M
    SCEnter();
390
391
    /* sort the alert queue before thresholding and appending to Packet */
392
9.73M
    qsort(det_ctx->alert_queue, det_ctx->alert_queue_size, sizeof(PacketAlert),
393
9.73M
            AlertQueueSortHelper);
394
395
10.1M
    for (uint16_t i = 0; i < det_ctx->alert_queue_size; i++) {
396
461k
        PacketAlert *pa = &det_ctx->alert_queue[i];
397
461k
        const Signature *s = de_ctx->sig_array[pa->num];
398
461k
        int res = PacketAlertHandle(de_ctx, det_ctx, s, p, pa);
399
461k
        if (res > 0) {
400
            /* Now, if we have an alert, we have to check if we want
401
             * to tag this session or src/dst host */
402
459k
            if (s->sm_arrays[DETECT_SM_LIST_TMATCH] != NULL) {
403
1.68k
                KEYWORD_PROFILING_SET_LIST(det_ctx, DETECT_SM_LIST_TMATCH);
404
1.68k
                SigMatchData *smd = s->sm_arrays[DETECT_SM_LIST_TMATCH];
405
2.88k
                while (1) {
406
                    /* tags are set only for alerts */
407
2.88k
                    KEYWORD_PROFILING_START;
408
2.88k
                    sigmatch_table[smd->type].Match(det_ctx, p, (Signature *)s, smd->ctx);
409
2.88k
                    KEYWORD_PROFILING_END(det_ctx, smd->type, 1);
410
2.88k
                    if (smd->is_last)
411
1.68k
                        break;
412
1.20k
                    smd++;
413
1.20k
                }
414
1.68k
            }
415
416
459k
            bool skip_action_set = false;
417
459k
            if (p->action & ACTION_DROP) {
418
47
                if (pa->action & ACTION_PASS) {
419
0
                    skip_action_set = true;
420
0
                }
421
47
            }
422
459k
            if (!skip_action_set) {
423
                /* set actions on the flow */
424
459k
                FlowApplySignatureActions(p, pa, s, pa->flags);
425
426
459k
                SCLogDebug("det_ctx->alert_queue[i].action %02x (DROP %s, PASS %s)", pa->action,
427
459k
                        BOOL2STR(pa->action & ACTION_DROP), BOOL2STR(pa->action & ACTION_PASS));
428
429
                /* set actions on packet */
430
459k
                PacketApplySignatureActions(p, s, pa);
431
459k
            }
432
459k
        }
433
434
        /* Thresholding removes this alert */
435
461k
        if (res == 0 || res == 2 || (s->action & (ACTION_ALERT | ACTION_PASS)) == 0) {
436
9.90k
            SCLogDebug("sid:%u: skipping alert because of thresholding (res=%d) or NOALERT (%02x)",
437
9.90k
                    s->id, res, s->action);
438
            /* we will not copy this to the AlertQueue */
439
9.90k
            p->alerts.suppressed++;
440
451k
        } else if (p->alerts.cnt < packet_alert_max) {
441
426k
            p->alerts.alerts[p->alerts.cnt] = *pa;
442
426k
            SCLogDebug("Appending sid %" PRIu32 " alert to Packet::alerts at pos %u", s->id, i);
443
444
            /* pass w/o alert found, we're done. Alert is not logged. */
445
426k
            if ((pa->action & (ACTION_PASS | ACTION_ALERT)) == ACTION_PASS) {
446
5.02k
                SCLogDebug("sid:%u: is a pass rule, so break out of loop", s->id);
447
5.02k
                break;
448
5.02k
            }
449
421k
            p->alerts.cnt++;
450
451
            /* pass with alert, we're done. Alert is logged. */
452
421k
            if (pa->action & ACTION_PASS) {
453
126
                SCLogDebug("sid:%u: is a pass rule, so break out of loop", s->id);
454
126
                break;
455
126
            }
456
421k
        } else {
457
25.0k
            p->alerts.discarded++;
458
25.0k
        }
459
461k
    }
460
461
    /* At this point, we should have all the new alerts. Now check the tag
462
     * keyword context for sessions and hosts */
463
9.73M
    if (!(p->flags & PKT_PSEUDO_STREAM_END))
464
9.58M
        TagHandlePacket(de_ctx, det_ctx, p);
465
466
    /* Set flag on flow to indicate that it has alerts */
467
9.73M
    if (p->flow != NULL && p->alerts.cnt > 0) {
468
351k
        if (!FlowHasAlerts(p->flow)) {
469
30.5k
            FlowSetHasAlertsFlag(p->flow);
470
30.5k
            p->flags |= PKT_FIRST_ALERTS;
471
30.5k
        }
472
351k
    }
473
9.73M
}
474
475
#ifdef UNITTESTS
476
#include "tests/detect-engine-alert.c"
477
#endif