Coverage Report

Created: 2026-06-07 07:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata7/src/detect-icode.c
Line
Count
Source
1
/* Copyright (C) 2007-2021 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 Gerardo Iglesias <iglesiasg@gmail.com>
22
 *
23
 * Implements icode keyword support
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-uint.h"
33
#include "detect-engine-build.h"
34
35
#include "detect-icode.h"
36
37
#include "util-byte.h"
38
#include "util-unittest.h"
39
#include "util-unittest-helper.h"
40
#include "util-debug.h"
41
42
/**
43
 *\brief Regex for parsing our icode options
44
 */
45
46
static int DetectICodeMatch(DetectEngineThreadCtx *, Packet *,
47
        const Signature *, const SigMatchCtx *);
48
static int DetectICodeSetup(DetectEngineCtx *, Signature *, const char *);
49
#ifdef UNITTESTS
50
static void DetectICodeRegisterTests(void);
51
#endif
52
void DetectICodeFree(DetectEngineCtx *, void *);
53
54
static int PrefilterSetupICode(DetectEngineCtx *de_ctx, SigGroupHead *sgh);
55
static bool PrefilterICodeIsPrefilterable(const Signature *s);
56
57
/**
58
 * \brief Registration function for icode: keyword
59
 */
60
void DetectICodeRegister (void)
61
75
{
62
75
    sigmatch_table[DETECT_ICODE].name = "icode";
63
75
    sigmatch_table[DETECT_ICODE].desc = "match on specific ICMP id-value";
64
75
    sigmatch_table[DETECT_ICODE].url = "/rules/header-keywords.html#icode";
65
75
    sigmatch_table[DETECT_ICODE].Match = DetectICodeMatch;
66
75
    sigmatch_table[DETECT_ICODE].Setup = DetectICodeSetup;
67
75
    sigmatch_table[DETECT_ICODE].Free = DetectICodeFree;
68
#ifdef UNITTESTS
69
    sigmatch_table[DETECT_ICODE].RegisterTests = DetectICodeRegisterTests;
70
#endif
71
75
    sigmatch_table[DETECT_ICODE].SupportsPrefilter = PrefilterICodeIsPrefilterable;
72
75
    sigmatch_table[DETECT_ICODE].SetupPrefilter = PrefilterSetupICode;
73
75
}
74
75
/**
76
 * \brief This function is used to match icode rule option set on a packet with those passed via
77
 * icode:
78
 *
79
 * \param t pointer to thread vars
80
 * \param det_ctx pointer to the pattern matcher thread
81
 * \param p pointer to the current packet
82
 * \param ctx pointer to the sigmatch that we will cast into DetectU8Data
83
 *
84
 * \retval 0 no match
85
 * \retval 1 match
86
 */
87
static int DetectICodeMatch (DetectEngineThreadCtx *det_ctx, Packet *p,
88
        const Signature *s, const SigMatchCtx *ctx)
89
5.19k
{
90
5.19k
    if (PKT_IS_PSEUDOPKT(p))
91
778
        return 0;
92
93
4.41k
    uint8_t picode;
94
4.41k
    if (PKT_IS_ICMPV4(p)) {
95
2.67k
        picode = ICMPV4_GET_CODE(p);
96
2.67k
    } else if (PKT_IS_ICMPV6(p)) {
97
140
        picode = ICMPV6_GET_CODE(p);
98
1.60k
    } else {
99
        /* Packet not ICMPv4 nor ICMPv6 */
100
1.60k
        return 0;
101
1.60k
    }
102
103
2.81k
    const DetectU8Data *icd = (const DetectU8Data *)ctx;
104
2.81k
    return DetectU8Match(picode, icd);
105
4.41k
}
106
107
/**
108
 * \brief this function is used to add the parsed icode data into the current signature
109
 *
110
 * \param de_ctx pointer to the Detection Engine Context
111
 * \param s pointer to the Current Signature
112
 * \param icodestr pointer to the user provided icode options
113
 *
114
 * \retval 0 on Success
115
 * \retval -1 on Failure
116
 */
117
static int DetectICodeSetup(DetectEngineCtx *de_ctx, Signature *s, const char *icodestr)
118
2.88k
{
119
120
2.88k
    DetectU8Data *icd = NULL;
121
2.88k
    SigMatch *sm = NULL;
122
123
2.88k
    icd = DetectU8Parse(icodestr);
124
2.88k
    if (icd == NULL)
125
180
        return -1;
126
127
2.70k
    sm = SigMatchAlloc();
128
2.70k
    if (sm == NULL) goto error;
129
130
2.70k
    sm->type = DETECT_ICODE;
131
2.70k
    sm->ctx = (SigMatchCtx *)icd;
132
133
2.70k
    SigMatchAppendSMToList(s, sm, DETECT_SM_LIST_MATCH);
134
2.70k
    s->flags |= SIG_FLAG_REQUIRE_PACKET;
135
136
2.70k
    return 0;
137
138
0
error:
139
0
    if (icd != NULL)
140
0
        rs_detect_u8_free(icd);
141
0
    if (sm != NULL) SCFree(sm);
142
0
    return -1;
143
2.70k
}
144
145
/**
146
 * \brief this function will free memory associated with DetectU8Data
147
 *
148
 * \param ptr pointer to DetectU8Data
149
 */
150
void DetectICodeFree(DetectEngineCtx *de_ctx, void *ptr)
151
3.47k
{
152
3.47k
    rs_detect_u8_free(ptr);
153
3.47k
}
154
155
/* prefilter code */
156
157
static void PrefilterPacketICodeMatch(DetectEngineThreadCtx *det_ctx,
158
        Packet *p, const void *pectx)
159
0
{
160
0
    if (PKT_IS_PSEUDOPKT(p)) {
161
0
        SCReturn;
162
0
    }
163
164
0
    uint8_t picode;
165
0
    if (PKT_IS_ICMPV4(p)) {
166
0
        picode = ICMPV4_GET_CODE(p);
167
0
    } else if (PKT_IS_ICMPV6(p)) {
168
0
        picode = ICMPV6_GET_CODE(p);
169
0
    } else {
170
        /* Packet not ICMPv4 nor ICMPv6 */
171
0
        return;
172
0
    }
173
174
0
    const PrefilterPacketU8HashCtx *h = pectx;
175
0
    const SigsArray *sa = h->array[picode];
176
0
    if (sa) {
177
0
        PrefilterAddSids(&det_ctx->pmq, sa->sigs, sa->cnt);
178
0
    }
179
0
}
180
181
static int PrefilterSetupICode(DetectEngineCtx *de_ctx, SigGroupHead *sgh)
182
0
{
183
0
    return PrefilterSetupPacketHeaderU8Hash(de_ctx, sgh, DETECT_ICODE, PrefilterPacketU8Set,
184
0
            PrefilterPacketU8Compare, PrefilterPacketICodeMatch);
185
0
}
186
187
static bool PrefilterICodeIsPrefilterable(const Signature *s)
188
0
{
189
0
    const SigMatch *sm;
190
0
    for (sm = s->init_data->smlists[DETECT_SM_LIST_MATCH] ; sm != NULL; sm = sm->next) {
191
0
        switch (sm->type) {
192
0
            case DETECT_ICODE:
193
0
                return true;
194
0
        }
195
0
    }
196
0
    return false;
197
0
}
198
199
#ifdef UNITTESTS
200
#include "detect-engine.h"
201
#include "detect-engine-mpm.h"
202
#include "detect-engine-alert.h"
203
204
/**
205
 * \test DetectICodeParseTest01 is a test for setting a valid icode value
206
 */
207
static int DetectICodeParseTest01(void)
208
{
209
    DetectU8Data *icd = DetectU8Parse("8");
210
    FAIL_IF_NULL(icd);
211
    FAIL_IF_NOT(icd->arg1 == 8);
212
    FAIL_IF_NOT(icd->mode == DETECT_UINT_EQ);
213
    DetectICodeFree(NULL, icd);
214
215
    PASS;
216
}
217
218
/**
219
 * \test DetectICodeParseTest02 is a test for setting a valid icode value
220
 *       with ">" operator
221
 */
222
static int DetectICodeParseTest02(void)
223
{
224
    DetectU8Data *icd = DetectU8Parse(">8");
225
    FAIL_IF_NULL(icd);
226
    FAIL_IF_NOT(icd->arg1 == 8);
227
    FAIL_IF_NOT(icd->mode == DETECT_UINT_GT);
228
    DetectICodeFree(NULL, icd);
229
230
    PASS;
231
}
232
233
/**
234
 * \test DetectICodeParseTest03 is a test for setting a valid icode value
235
 *       with "<" operator
236
 */
237
static int DetectICodeParseTest03(void)
238
{
239
    DetectU8Data *icd = DetectU8Parse("<8");
240
    FAIL_IF_NULL(icd);
241
    FAIL_IF_NOT(icd->arg1 == 8);
242
    FAIL_IF_NOT(icd->mode == DETECT_UINT_LT);
243
    DetectICodeFree(NULL, icd);
244
245
    PASS;
246
}
247
248
/**
249
 * \test DetectICodeParseTest04 is a test for setting a valid icode value
250
 *       with "<>" operator
251
 */
252
static int DetectICodeParseTest04(void)
253
{
254
    DetectU8Data *icd = DetectU8Parse("8<>20");
255
    FAIL_IF_NULL(icd);
256
    FAIL_IF_NOT(icd->arg1 == 8);
257
    FAIL_IF_NOT(icd->arg2 == 20);
258
    FAIL_IF_NOT(icd->mode == DETECT_UINT_RA);
259
    DetectICodeFree(NULL, icd);
260
261
    PASS;
262
}
263
264
/**
265
 * \test DetectICodeParseTest05 is a test for setting a valid icode value
266
 *       with spaces all around
267
 */
268
static int DetectICodeParseTest05(void)
269
{
270
    DetectU8Data *icd = DetectU8Parse("  8 ");
271
    FAIL_IF_NULL(icd);
272
    FAIL_IF_NOT(icd->arg1 == 8);
273
    FAIL_IF_NOT(icd->mode == DETECT_UINT_EQ);
274
    DetectICodeFree(NULL, icd);
275
276
    PASS;
277
}
278
279
/**
280
 * \test DetectICodeParseTest06 is a test for setting a valid icode value
281
 *       with ">" operator and spaces all around
282
 */
283
static int DetectICodeParseTest06(void)
284
{
285
    DetectU8Data *icd = DetectU8Parse("  >  8 ");
286
    FAIL_IF_NULL(icd);
287
    FAIL_IF_NOT(icd->arg1 == 8);
288
    FAIL_IF_NOT(icd->mode == DETECT_UINT_GT);
289
    DetectICodeFree(NULL, icd);
290
291
    PASS;
292
}
293
294
/**
295
 * \test DetectICodeParseTest07 is a test for setting a valid icode value
296
 *       with "<>" operator and spaces all around
297
 */
298
static int DetectICodeParseTest07(void)
299
{
300
    DetectU8Data *icd = DetectU8Parse("  8  <>  20 ");
301
    FAIL_IF_NULL(icd);
302
    FAIL_IF_NOT(icd->arg1 == 8);
303
    FAIL_IF_NOT(icd->arg2 == 20);
304
    FAIL_IF_NOT(icd->mode == DETECT_UINT_RA);
305
    DetectICodeFree(NULL, icd);
306
307
    PASS;
308
}
309
310
/**
311
 * \test DetectICodeParseTest08 is a test for setting an invalid icode value
312
 */
313
static int DetectICodeParseTest08(void)
314
{
315
    DetectU8Data *icd = DetectU8Parse("> 8 <> 20");
316
    FAIL_IF_NOT_NULL(icd);
317
318
    PASS;
319
}
320
321
/**
322
 * \test DetectICodeParseTest09 is a test for setting an invalid icode value
323
 *       with "<<" operator
324
 */
325
static int DetectICodeParseTest09(void)
326
{
327
    DetectU8Data *icd = DetectU8Parse("8<<20");
328
    FAIL_IF_NOT_NULL(icd);
329
330
    PASS;
331
}
332
333
/**
334
 * \test DetectICodeMatchTest01 is a test for checking the working of icode
335
 *       keyword by creating 5 rules and matching a crafted packet against
336
 *       them. 4 out of 5 rules shall trigger.
337
 */
338
static int DetectICodeMatchTest01(void)
339
{
340
341
    Packet *p = NULL;
342
    Signature *s = NULL;
343
    ThreadVars th_v;
344
    DetectEngineThreadCtx *det_ctx;
345
346
    memset(&th_v, 0, sizeof(th_v));
347
348
    p = UTHBuildPacket(NULL, 0, IPPROTO_ICMP);
349
350
    p->icmpv4h->code = 10;
351
352
    DetectEngineCtx *de_ctx = DetectEngineCtxInit();
353
    FAIL_IF_NULL(de_ctx);
354
355
    de_ctx->flags |= DE_QUIET;
356
357
    s = DetectEngineAppendSig(de_ctx, "alert icmp any any -> any any (icode:10; sid:1;)");
358
    FAIL_IF_NULL(s);
359
360
    s = DetectEngineAppendSig(de_ctx, "alert icmp any any -> any any (icode:<15; sid:2;)");
361
    FAIL_IF_NULL(s);
362
363
    s = DetectEngineAppendSig(de_ctx, "alert icmp any any -> any any (icode:>20; sid:3;)");
364
    FAIL_IF_NULL(s);
365
366
    s = DetectEngineAppendSig(de_ctx, "alert icmp any any -> any any (icode:8<>20; sid:4;)");
367
    FAIL_IF_NULL(s);
368
369
    s = DetectEngineAppendSig(de_ctx, "alert icmp any any -> any any (icode:20<>8; sid:5;)");
370
    FAIL_IF_NOT_NULL(s);
371
372
    SigGroupBuild(de_ctx);
373
    DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx);
374
375
    SigMatchSignatures(&th_v, de_ctx, det_ctx, p);
376
377
    FAIL_IF(PacketAlertCheck(p, 1) == 0);
378
    FAIL_IF(PacketAlertCheck(p, 2) == 0);
379
    FAIL_IF(PacketAlertCheck(p, 3));
380
    FAIL_IF(PacketAlertCheck(p, 4) == 0);
381
382
    DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx);
383
    DetectEngineCtxFree(de_ctx);
384
385
    UTHFreePackets(&p, 1);
386
387
    PASS;
388
}
389
390
/**
391
 * \brief this function registers unit tests for DetectICode
392
 */
393
void DetectICodeRegisterTests(void)
394
{
395
    UtRegisterTest("DetectICodeParseTest01", DetectICodeParseTest01);
396
    UtRegisterTest("DetectICodeParseTest02", DetectICodeParseTest02);
397
    UtRegisterTest("DetectICodeParseTest03", DetectICodeParseTest03);
398
    UtRegisterTest("DetectICodeParseTest04", DetectICodeParseTest04);
399
    UtRegisterTest("DetectICodeParseTest05", DetectICodeParseTest05);
400
    UtRegisterTest("DetectICodeParseTest06", DetectICodeParseTest06);
401
    UtRegisterTest("DetectICodeParseTest07", DetectICodeParseTest07);
402
    UtRegisterTest("DetectICodeParseTest08", DetectICodeParseTest08);
403
    UtRegisterTest("DetectICodeParseTest09", DetectICodeParseTest09);
404
    UtRegisterTest("DetectICodeMatchTest01", DetectICodeMatchTest01);
405
}
406
#endif /* UNITTESTS */