Coverage Report

Created: 2026-09-06 07:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata8/src/detect-icmp-seq.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
/**
19
 * \file
20
 *
21
 * \author Breno Silva <breno.silva@gmail.com>
22
 *
23
 * Implements the icmp_seq keyword
24
 */
25
26
#include "suricata-common.h"
27
#include "decode.h"
28
29
#include "detect.h"
30
#include "detect-parse.h"
31
#include "detect-engine-prefilter-common.h"
32
#include "detect-engine-build.h"
33
34
#include "detect-icmp-seq.h"
35
36
#include "util-byte.h"
37
#include "util-unittest.h"
38
#include "util-unittest-helper.h"
39
#include "util-debug.h"
40
41
79
#define PARSE_REGEX "^\\s*(\"\\s*)?([0-9]+)(\\s*\")?\\s*$"
42
43
static DetectParseRegex parse_regex;
44
45
static int DetectIcmpSeqMatch(DetectEngineThreadCtx *, Packet *,
46
        const Signature *, const SigMatchCtx *);
47
static int DetectIcmpSeqSetup(DetectEngineCtx *, Signature *, const char *);
48
#ifdef UNITTESTS
49
static void DetectIcmpSeqRegisterTests(void);
50
#endif
51
void DetectIcmpSeqFree(DetectEngineCtx *, void *);
52
static int PrefilterSetupIcmpSeq(DetectEngineCtx *de_ctx, SigGroupHead *sgh);
53
static bool PrefilterIcmpSeqIsPrefilterable(const Signature *s);
54
55
/**
56
 * \brief Registration function for icmp_seq
57
 */
58
void DetectIcmpSeqRegister (void)
59
79
{
60
79
    sigmatch_table[DETECT_ICMP_SEQ].name = "icmp_seq";
61
79
    sigmatch_table[DETECT_ICMP_SEQ].desc = "check for a ICMP sequence number";
62
79
    sigmatch_table[DETECT_ICMP_SEQ].url = "/rules/header-keywords.html#icmp-seq";
63
79
    sigmatch_table[DETECT_ICMP_SEQ].Match = DetectIcmpSeqMatch;
64
79
    sigmatch_table[DETECT_ICMP_SEQ].Setup = DetectIcmpSeqSetup;
65
79
    sigmatch_table[DETECT_ICMP_SEQ].Free = DetectIcmpSeqFree;
66
#ifdef UNITTESTS
67
    sigmatch_table[DETECT_ICMP_SEQ].RegisterTests = DetectIcmpSeqRegisterTests;
68
#endif
69
79
    sigmatch_table[DETECT_ICMP_SEQ].SupportsPrefilter = PrefilterIcmpSeqIsPrefilterable;
70
79
    sigmatch_table[DETECT_ICMP_SEQ].SetupPrefilter = PrefilterSetupIcmpSeq;
71
72
79
    DetectSetupParseRegexes(PARSE_REGEX, &parse_regex);
73
79
}
74
75
static inline bool GetIcmpSeq(Packet *p, uint16_t *seq)
76
43.9k
{
77
43.9k
    uint16_t seqn;
78
79
43.9k
    if (PacketIsICMPv4(p)) {
80
7.86k
        switch (p->icmp_s.type) {
81
1.88k
            case ICMP_ECHOREPLY:
82
5.55k
            case ICMP_ECHO:
83
5.65k
            case ICMP_TIMESTAMP:
84
5.65k
            case ICMP_TIMESTAMPREPLY:
85
5.67k
            case ICMP_INFO_REQUEST:
86
5.67k
            case ICMP_INFO_REPLY:
87
5.67k
            case ICMP_ADDRESS:
88
5.68k
            case ICMP_ADDRESSREPLY:
89
5.68k
                SCLogDebug("ICMPV4_GET_SEQ(p) %"PRIu16" (network byte order), "
90
5.68k
                        "%"PRIu16" (host byte order)", ICMPV4_GET_SEQ(p),
91
5.68k
                        SCNtohs(ICMPV4_GET_SEQ(p)));
92
93
5.68k
                seqn = ICMPV4_GET_SEQ(p);
94
5.68k
                break;
95
2.18k
            default:
96
2.18k
                SCLogDebug("Packet has no seq field");
97
2.18k
                return false;
98
7.86k
        }
99
36.0k
    } else if (PacketIsICMPv6(p)) {
100
1.56k
        switch (ICMPV6_GET_TYPE(PacketGetICMPv6(p))) {
101
38
            case ICMP6_ECHO_REQUEST:
102
282
            case ICMP6_ECHO_REPLY:
103
282
                SCLogDebug("ICMPV6_GET_SEQ(p) %"PRIu16" (network byte order), "
104
282
                        "%"PRIu16" (host byte order)", ICMPV6_GET_SEQ(p),
105
282
                        SCNtohs(ICMPV6_GET_SEQ(p)));
106
107
282
                seqn = ICMPV6_GET_SEQ(p);
108
282
                break;
109
1.28k
            default:
110
1.28k
                SCLogDebug("Packet has no seq field");
111
1.28k
                return false;
112
1.56k
        }
113
34.4k
    } else {
114
34.4k
        SCLogDebug("Packet not ICMPV4 nor ICMPV6");
115
34.4k
        return false;
116
34.4k
    }
117
118
5.96k
    *seq = seqn;
119
5.96k
    return true;
120
43.9k
}
121
122
/**
123
 * \brief This function is used to match icmp_seq rule option set on a packet
124
 *
125
 * \param t pointer to thread vars
126
 * \param det_ctx pointer to the pattern matcher thread
127
 * \param p pointer to the current packet
128
 * \param m pointer to the sigmatch that we will cast into DetectIcmpSeqData
129
 *
130
 * \retval 0 no match
131
 * \retval 1 match
132
 */
133
static int DetectIcmpSeqMatch (DetectEngineThreadCtx *det_ctx, Packet *p,
134
        const Signature *s, const SigMatchCtx *ctx)
135
141
{
136
141
    DEBUG_VALIDATE_BUG_ON(PKT_IS_PSEUDOPKT(p));
137
141
    uint16_t seqn;
138
139
141
    if (!GetIcmpSeq(p, &seqn))
140
141
        return 0;
141
142
0
    const DetectIcmpSeqData *iseq = (const DetectIcmpSeqData *)ctx;
143
0
    if (seqn == iseq->seq)
144
0
        return 1;
145
146
0
    return 0;
147
0
}
148
149
/**
150
 * \brief This function is used to parse icmp_seq option passed via icmp_seq: keyword
151
 *
152
 * \param de_ctx Pointer to the detection engine context
153
 * \param icmpseqstr Pointer to the user provided icmp_seq options
154
 *
155
 * \retval iseq pointer to DetectIcmpSeqData on success
156
 * \retval NULL on failure
157
 */
158
static DetectIcmpSeqData *DetectIcmpSeqParse (DetectEngineCtx *de_ctx, const char *icmpseqstr)
159
2.03k
{
160
2.03k
    DetectIcmpSeqData *iseq = NULL;
161
2.03k
    char *substr[3] = {NULL, NULL, NULL};
162
2.03k
    int res = 0;
163
2.03k
    size_t pcre2_len;
164
2.03k
    int i;
165
2.03k
    const char *str_ptr;
166
167
2.03k
    pcre2_match_data *match = NULL;
168
2.03k
    int ret = DetectParsePcreExec(&parse_regex, &match, icmpseqstr, 0, 0);
169
2.03k
    if (ret < 1 || ret > 4) {
170
120
        SCLogError("Parse error %s", icmpseqstr);
171
120
        goto error;
172
120
    }
173
174
5.75k
    for (i = 1; i < ret; i++) {
175
3.83k
        res = SC_Pcre2SubstringGet(match, i, (PCRE2_UCHAR8 **)&str_ptr, &pcre2_len);
176
3.83k
        if (res < 0) {
177
0
            SCLogError("pcre2_substring_get_bynumber failed");
178
0
            goto error;
179
0
        }
180
3.83k
        substr[i-1] = (char *)str_ptr;
181
3.83k
    }
182
183
1.91k
    iseq = SCMalloc(sizeof(DetectIcmpSeqData));
184
1.91k
    if (unlikely(iseq == NULL))
185
0
        goto error;
186
187
1.91k
    iseq->seq = 0;
188
189
1.91k
    if (substr[0] != NULL && strlen(substr[0]) != 0) {
190
0
        if (substr[2] == NULL) {
191
0
            SCLogError("Missing quote in input");
192
0
            goto error;
193
0
        }
194
1.91k
    } else {
195
1.91k
        if (substr[2] != NULL) {
196
0
            SCLogError("Missing quote in input");
197
0
            goto error;
198
0
        }
199
1.91k
    }
200
201
1.91k
    uint16_t seq = 0;
202
1.91k
    if (StringParseUint16(&seq, 10, 0, substr[1]) < 0) {
203
104
        SCLogError("specified icmp seq %s is not "
204
104
                   "valid",
205
104
                substr[1]);
206
104
        goto error;
207
104
    }
208
1.81k
    iseq->seq = htons(seq);
209
210
7.26k
    for (i = 0; i < 3; i++) {
211
5.44k
        if (substr[i] != NULL)
212
5.44k
            pcre2_substring_free((PCRE2_UCHAR8 *)substr[i]);
213
5.44k
    }
214
215
1.81k
    pcre2_match_data_free(match);
216
1.81k
    return iseq;
217
218
224
error:
219
224
    if (match) {
220
224
        pcre2_match_data_free(match);
221
224
    }
222
896
    for (i = 0; i < 3; i++) {
223
672
        if (substr[i] != NULL)
224
672
            pcre2_substring_free((PCRE2_UCHAR8 *)substr[i]);
225
672
    }
226
224
    if (iseq != NULL) DetectIcmpSeqFree(de_ctx, iseq);
227
224
    return NULL;
228
229
1.91k
}
230
231
/**
232
 * \brief this function is used to add the parsed icmp_seq data into the current signature
233
 *
234
 * \param de_ctx pointer to the Detection Engine Context
235
 * \param s pointer to the Current Signature
236
 * \param icmpseqstr pointer to the user provided icmp_seq option
237
 *
238
 * \retval 0 on Success
239
 * \retval -1 on Failure
240
 */
241
static int DetectIcmpSeqSetup (DetectEngineCtx *de_ctx, Signature *s, const char *icmpseqstr)
242
2.03k
{
243
2.03k
    DetectIcmpSeqData *iseq = NULL;
244
245
2.03k
    iseq = DetectIcmpSeqParse(de_ctx, icmpseqstr);
246
2.03k
    if (iseq == NULL) goto error;
247
248
1.81k
    if (SCSigMatchAppendSMToList(
249
1.81k
                de_ctx, s, DETECT_ICMP_SEQ, (SigMatchCtx *)iseq, DETECT_SM_LIST_MATCH) == NULL) {
250
0
        goto error;
251
0
    }
252
1.81k
    s->flags |= SIG_FLAG_REQUIRE_PACKET;
253
254
1.81k
    return 0;
255
256
224
error:
257
224
    if (iseq != NULL)
258
0
        DetectIcmpSeqFree(de_ctx, iseq);
259
224
    return -1;
260
261
1.81k
}
262
263
/**
264
 * \brief this function will free memory associated with DetectIcmpSeqData
265
 *
266
 * \param ptr pointer to DetectIcmpSeqData
267
 */
268
void DetectIcmpSeqFree (DetectEngineCtx *de_ctx, void *ptr)
269
20.4k
{
270
20.4k
    DetectIcmpSeqData *iseq = (DetectIcmpSeqData *)ptr;
271
20.4k
    SCFree(iseq);
272
20.4k
}
273
274
/* prefilter code */
275
276
static void
277
PrefilterPacketIcmpSeqMatch(DetectEngineThreadCtx *det_ctx, Packet *p, const void *pectx)
278
22
{
279
22
    DEBUG_VALIDATE_BUG_ON(PKT_IS_PSEUDOPKT(p));
280
281
22
    const PrefilterPacketHeaderCtx *ctx = pectx;
282
22
    uint16_t seqn;
283
284
22
    if (!GetIcmpSeq(p, &seqn))
285
22
        return;
286
287
0
    if (seqn == ctx->v1.u16[0])
288
0
    {
289
0
        SCLogDebug("packet matches ICMP SEQ %u", ctx->v1.u16[0]);
290
0
        PrefilterAddSids(&det_ctx->pmq, ctx->sigs_array, ctx->sigs_cnt);
291
0
    }
292
0
}
293
294
static void
295
PrefilterPacketIcmpSeqSet(PrefilterPacketHeaderValue *v, void *smctx)
296
1.01k
{
297
1.01k
    const DetectIcmpSeqData *a = smctx;
298
1.01k
    v->u16[0] = a->seq;
299
1.01k
}
300
301
static bool
302
PrefilterPacketIcmpSeqCompare(PrefilterPacketHeaderValue v, void *smctx)
303
721
{
304
721
    const DetectIcmpSeqData *a = smctx;
305
721
    if (v.u16[0] == a->seq)
306
677
        return true;
307
44
    return false;
308
721
}
309
310
static int PrefilterSetupIcmpSeq(DetectEngineCtx *de_ctx, SigGroupHead *sgh)
311
5.17k
{
312
5.17k
    return PrefilterSetupPacketHeader(de_ctx, sgh, DETECT_ICMP_SEQ, SIG_MASK_REQUIRE_REAL_PKT,
313
5.17k
            PrefilterPacketIcmpSeqSet, PrefilterPacketIcmpSeqCompare, PrefilterPacketIcmpSeqMatch);
314
5.17k
}
315
316
static bool PrefilterIcmpSeqIsPrefilterable(const Signature *s)
317
0
{
318
0
    const SigMatch *sm;
319
0
    for (sm = s->init_data->smlists[DETECT_SM_LIST_MATCH] ; sm != NULL; sm = sm->next) {
320
0
        switch (sm->type) {
321
0
            case DETECT_ICMP_SEQ:
322
0
                return true;
323
0
        }
324
0
    }
325
0
    return false;
326
0
}
327
328
#ifdef UNITTESTS
329
#include "detect-engine.h"
330
#include "detect-engine-mpm.h"
331
#include "detect-engine-alert.h"
332
333
/**
334
 * \test DetectIcmpSeqParseTest01 is a test for setting a valid icmp_seq value
335
 */
336
static int DetectIcmpSeqParseTest01 (void)
337
{
338
    DetectIcmpSeqData *iseq = NULL;
339
    iseq = DetectIcmpSeqParse(NULL, "300");
340
    FAIL_IF_NULL(iseq);
341
    FAIL_IF_NOT(htons(iseq->seq) == 300);
342
    DetectIcmpSeqFree(NULL, iseq);
343
    PASS;
344
}
345
346
/**
347
 * \test DetectIcmpSeqParseTest02 is a test for setting a valid icmp_seq value
348
 *       with spaces all around
349
 */
350
static int DetectIcmpSeqParseTest02 (void)
351
{
352
    DetectIcmpSeqData *iseq = NULL;
353
    iseq = DetectIcmpSeqParse(NULL, "  300  ");
354
    FAIL_IF_NULL(iseq);
355
    FAIL_IF_NOT(htons(iseq->seq) == 300);
356
    DetectIcmpSeqFree(NULL, iseq);
357
    PASS;
358
}
359
360
/**
361
 * \test DetectIcmpSeqParseTest03 is a test for setting an invalid icmp_seq value
362
 */
363
static int DetectIcmpSeqParseTest03 (void)
364
{
365
    DetectIcmpSeqData *iseq = DetectIcmpSeqParse(NULL, "badc");
366
    FAIL_IF_NOT_NULL(iseq);
367
    PASS;
368
}
369
370
static void DetectIcmpSeqRegisterTests (void)
371
{
372
    UtRegisterTest("DetectIcmpSeqParseTest01", DetectIcmpSeqParseTest01);
373
    UtRegisterTest("DetectIcmpSeqParseTest02", DetectIcmpSeqParseTest02);
374
    UtRegisterTest("DetectIcmpSeqParseTest03", DetectIcmpSeqParseTest03);
375
}
376
#endif /* UNITTESTS */