Coverage Report

Created: 2026-01-16 07:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata7/src/detect-id.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 Pablo Rincon Crespo <pablo.rincon.crespo@gmail.com>
22
 *
23
 * Implements the id keyword
24
 */
25
26
#include "suricata-common.h"
27
#include "decode.h"
28
#include "detect.h"
29
30
#include "detect-parse.h"
31
#include "detect-engine.h"
32
#include "detect-engine-mpm.h"
33
#include "detect-engine-prefilter-common.h"
34
35
#include "detect-id.h"
36
#include "flow.h"
37
#include "flow-var.h"
38
39
#include "util-debug.h"
40
#include "util-byte.h"
41
#include "util-unittest.h"
42
#include "util-unittest-helper.h"
43
44
/**
45
 * \brief Regex for parsing "id" option, matching number or "number"
46
 */
47
73
#define PARSE_REGEX  "^\\s*([0-9]{1,5}|\"[0-9]{1,5}\")\\s*$"
48
49
static DetectParseRegex parse_regex;
50
51
static int DetectIdMatch (DetectEngineThreadCtx *, Packet *,
52
        const Signature *, const SigMatchCtx *);
53
static int DetectIdSetup (DetectEngineCtx *, Signature *, const char *);
54
#ifdef UNITTESTS
55
static void DetectIdRegisterTests(void);
56
#endif
57
void DetectIdFree(DetectEngineCtx *, void *);
58
59
static int PrefilterSetupId(DetectEngineCtx *de_ctx, SigGroupHead *sgh);
60
static bool PrefilterIdIsPrefilterable(const Signature *s);
61
62
/**
63
 * \brief Registration function for keyword: id
64
 */
65
void DetectIdRegister (void)
66
73
{
67
73
    sigmatch_table[DETECT_ID].name = "id";
68
73
    sigmatch_table[DETECT_ID].desc = "match on a specific IP ID value";
69
73
    sigmatch_table[DETECT_ID].url = "/rules/header-keywords.html#id";
70
73
    sigmatch_table[DETECT_ID].Match = DetectIdMatch;
71
73
    sigmatch_table[DETECT_ID].Setup = DetectIdSetup;
72
73
    sigmatch_table[DETECT_ID].Free  = DetectIdFree;
73
#ifdef UNITTESTS
74
    sigmatch_table[DETECT_ID].RegisterTests = DetectIdRegisterTests;
75
#endif
76
73
    sigmatch_table[DETECT_ID].SupportsPrefilter = PrefilterIdIsPrefilterable;
77
73
    sigmatch_table[DETECT_ID].SetupPrefilter = PrefilterSetupId;
78
79
73
    DetectSetupParseRegexes(PARSE_REGEX, &parse_regex);
80
73
}
81
82
/**
83
 * \brief This function is used to match the specified id on a packet
84
 *
85
 * \param t pointer to thread vars
86
 * \param det_ctx pointer to the pattern matcher thread
87
 * \param p pointer to the current packet
88
 * \param m pointer to the sigmatch that we will cast into DetectIdData
89
 *
90
 * \retval 0 no match
91
 * \retval 1 match
92
 */
93
static int DetectIdMatch (DetectEngineThreadCtx *det_ctx, Packet *p,
94
                          const Signature *s, const SigMatchCtx *ctx)
95
454
{
96
454
    const DetectIdData *id_d = (const DetectIdData *)ctx;
97
98
    /**
99
     * To match a ipv4 packet with a "id" rule
100
     */
101
454
    if (!PKT_IS_IPV4(p) || PKT_IS_PSEUDOPKT(p)) {
102
43
        return 0;
103
43
    }
104
105
411
    if (id_d->id == IPV4_GET_IPID(p)) {
106
227
        SCLogDebug("IPV4 Proto and matched with ip_id: %u.\n",
107
227
                    id_d->id);
108
227
        return 1;
109
227
    }
110
111
184
    return 0;
112
411
}
113
114
/**
115
 * \brief This function is used to parse IPV4 ip_id passed via keyword: "id"
116
 *
117
 * \param idstr Pointer to the user provided id option
118
 *
119
 * \retval id_d pointer to DetectIdData on success
120
 * \retval NULL on failure
121
 */
122
static DetectIdData *DetectIdParse (const char *idstr)
123
3.43k
{
124
3.43k
    uint16_t temp;
125
3.43k
    DetectIdData *id_d = NULL;
126
3.43k
    int res = 0;
127
3.43k
    size_t pcre2len;
128
3.43k
    pcre2_match_data *match = NULL;
129
130
3.43k
    int ret = DetectParsePcreExec(&parse_regex, &match, idstr, 0, 0);
131
132
3.43k
    if (ret < 1 || ret > 3) {
133
810
        SCLogError("invalid id option '%s'. The id option "
134
810
                   "value must be in the range %u - %u",
135
810
                idstr, DETECT_IPID_MIN, DETECT_IPID_MAX);
136
810
        goto error;
137
810
    }
138
139
2.62k
    char copy_str[128] = "";
140
2.62k
    char *tmp_str;
141
2.62k
    pcre2len = sizeof(copy_str);
142
2.62k
    res = pcre2_substring_copy_bynumber(match, 1, (PCRE2_UCHAR8 *)copy_str, &pcre2len);
143
2.62k
    if (res < 0) {
144
0
        SCLogError("pcre2_substring_copy_bynumber failed");
145
0
        goto error;
146
0
    }
147
2.62k
    tmp_str = copy_str;
148
149
    /* Let's see if we need to scape "'s */
150
2.62k
    if (tmp_str[0] == '"')
151
0
    {
152
0
        tmp_str[strlen(tmp_str) - 1] = '\0';
153
0
        tmp_str += 1;
154
0
    }
155
156
    /* ok, fill the id data */
157
2.62k
    if (StringParseUint16(&temp, 10, 0, (const char *)tmp_str) <= 0) {
158
51
        SCLogError("invalid id option '%s'", tmp_str);
159
51
        goto error;
160
51
    }
161
162
    /* We have a correct id option */
163
2.57k
    id_d = SCMalloc(sizeof(DetectIdData));
164
2.57k
    if (unlikely(id_d == NULL))
165
0
        goto error;
166
167
2.57k
    id_d->id = temp;
168
169
2.57k
    SCLogDebug("detect-id: will look for ip_id: %u\n", id_d->id);
170
2.57k
    pcre2_match_data_free(match);
171
2.57k
    return id_d;
172
173
861
error:
174
861
    if (match) {
175
861
        pcre2_match_data_free(match);
176
861
    }
177
861
    return NULL;
178
2.57k
}
179
180
/**
181
 * \brief this function is used to add the parsed "id" option
182
 * \brief into the current signature
183
 *
184
 * \param de_ctx pointer to the Detection Engine Context
185
 * \param s pointer to the Current Signature
186
 * \param idstr pointer to the user provided "id" option
187
 *
188
 * \retval 0 on Success
189
 * \retval -1 on Failure
190
 */
191
int DetectIdSetup (DetectEngineCtx *de_ctx, Signature *s, const char *idstr)
192
5.82k
{
193
5.82k
    DetectIdData *id_d = NULL;
194
5.82k
    SigMatch *sm = NULL;
195
196
5.82k
    id_d = DetectIdParse(idstr);
197
5.82k
    if (id_d == NULL)
198
1.64k
        return -1;
199
200
    /* Okay so far so good, lets get this into a SigMatch
201
     * and put it in the Signature. */
202
4.17k
    sm = SigMatchAlloc();
203
4.17k
    if (sm == NULL) {
204
0
        DetectIdFree(de_ctx, id_d);
205
0
        return -1;
206
0
    }
207
208
4.17k
    sm->type = DETECT_ID;
209
4.17k
    sm->ctx = (SigMatchCtx *)id_d;
210
211
4.17k
    SigMatchAppendSMToList(s, sm, DETECT_SM_LIST_MATCH);
212
4.17k
    s->flags |= SIG_FLAG_REQUIRE_PACKET;
213
4.17k
    return 0;
214
4.17k
}
215
216
/**
217
 * \brief this function will free memory associated with DetectIdData
218
 *
219
 * \param id_d pointer to DetectIdData
220
 */
221
void DetectIdFree(DetectEngineCtx *de_ctx, void *ptr)
222
4.17k
{
223
4.17k
    DetectIdData *id_d = (DetectIdData *)ptr;
224
4.17k
    SCFree(id_d);
225
4.17k
}
226
227
/* prefilter code */
228
229
static void
230
PrefilterPacketIdMatch(DetectEngineThreadCtx *det_ctx, Packet *p, const void *pectx)
231
45
{
232
45
    const PrefilterPacketHeaderCtx *ctx = pectx;
233
234
45
    if (!PKT_IS_IPV4(p) || PKT_IS_PSEUDOPKT(p)) {
235
13
        return;
236
13
    }
237
238
32
    if (!PrefilterPacketHeaderExtraMatch(ctx, p))
239
32
        return;
240
241
0
    if (IPV4_GET_IPID(p) == ctx->v1.u16[0])
242
0
    {
243
0
        SCLogDebug("packet matches IP id %u", ctx->v1.u16[0]);
244
0
        PrefilterAddSids(&det_ctx->pmq, ctx->sigs_array, ctx->sigs_cnt);
245
0
    }
246
0
}
247
248
static void
249
PrefilterPacketIdSet(PrefilterPacketHeaderValue *v, void *smctx)
250
216
{
251
216
    const DetectIdData *a = smctx;
252
216
    v->u16[0] = a->id;
253
216
}
254
255
static bool
256
PrefilterPacketIdCompare(PrefilterPacketHeaderValue v, void *smctx)
257
108
{
258
108
    const DetectIdData *a = smctx;
259
108
    if (v.u16[0] == a->id)
260
108
        return true;
261
0
    return false;
262
108
}
263
264
static int PrefilterSetupId(DetectEngineCtx *de_ctx, SigGroupHead *sgh)
265
1.46k
{
266
1.46k
    return PrefilterSetupPacketHeader(de_ctx, sgh, DETECT_ID,
267
1.46k
        PrefilterPacketIdSet,
268
1.46k
        PrefilterPacketIdCompare,
269
1.46k
        PrefilterPacketIdMatch);
270
1.46k
}
271
272
static bool PrefilterIdIsPrefilterable(const Signature *s)
273
0
{
274
0
    const SigMatch *sm;
275
0
    for (sm = s->init_data->smlists[DETECT_SM_LIST_MATCH] ; sm != NULL; sm = sm->next) {
276
0
        switch (sm->type) {
277
0
            case DETECT_ID:
278
0
                return true;
279
0
        }
280
0
    }
281
0
    return false;
282
0
}
283
284
#ifdef UNITTESTS /* UNITTESTS */
285
286
/**
287
 * \test DetectIdTestParse01 is a test to make sure that we parse the "id"
288
 *       option correctly when given valid id option
289
 */
290
static int DetectIdTestParse01 (void)
291
{
292
    DetectIdData *id_d = DetectIdParse(" 35402 ");
293
294
    FAIL_IF_NULL(id_d);
295
    FAIL_IF_NOT(id_d->id == 35402);
296
297
    DetectIdFree(NULL, id_d);
298
299
    PASS;
300
}
301
302
/**
303
 * \test DetectIdTestParse02 is a test to make sure that we parse the "id"
304
 *       option correctly when given an invalid id option
305
 *       it should return id_d = NULL
306
 */
307
static int DetectIdTestParse02 (void)
308
{
309
    DetectIdData *id_d = DetectIdParse("65537");
310
311
    FAIL_IF_NOT_NULL(id_d);
312
313
    PASS;
314
}
315
316
/**
317
 * \test DetectIdTestParse03 is a test to make sure that we parse the "id"
318
 *       option correctly when given an invalid id option
319
 *       it should return id_d = NULL
320
 */
321
static int DetectIdTestParse03 (void)
322
{
323
    DetectIdData *id_d = DetectIdParse("12what?");
324
325
    FAIL_IF_NOT_NULL(id_d);
326
327
    PASS;
328
}
329
330
/**
331
 * \test DetectIdTestParse04 is a test to make sure that we parse the "id"
332
 *       option correctly when given valid id option but wrapped with "'s
333
 */
334
static int DetectIdTestParse04 (void)
335
{
336
    /* yep, look if we trim blank spaces correctly and ignore "'s */
337
    DetectIdData *id_d = DetectIdParse(" \"35402\" ");
338
339
    FAIL_IF_NULL(id_d);
340
    FAIL_IF_NOT(id_d->id == 35402);
341
342
    DetectIdFree(NULL, id_d);
343
344
    PASS;
345
}
346
347
/**
348
 * \test DetectIdTestSig01
349
 * \brief Test to check "id" keyword with constructed packets
350
 */
351
static int DetectIdTestMatch01(void)
352
{
353
    uint8_t *buf = (uint8_t *)"Hi all!";
354
    uint16_t buflen = strlen((char *)buf);
355
    Packet *p[3];
356
    p[0] = UTHBuildPacket((uint8_t *)buf, buflen, IPPROTO_TCP);
357
    p[1] = UTHBuildPacket((uint8_t *)buf, buflen, IPPROTO_UDP);
358
    p[2] = UTHBuildPacket((uint8_t *)buf, buflen, IPPROTO_ICMP);
359
360
    FAIL_IF_NULL(p[0]);
361
    FAIL_IF_NULL(p[1]);
362
    FAIL_IF_NULL(p[2]);
363
364
    /* TCP IP id = 1234 */
365
    p[0]->ip4h->ip_id = htons(1234);
366
367
    /* UDP IP id = 5678 */
368
    p[1]->ip4h->ip_id = htons(5678);
369
370
    /* UDP IP id = 91011 */
371
    p[2]->ip4h->ip_id = htons(5101);
372
373
    const char *sigs[3];
374
    sigs[0]= "alert ip any any -> any any (msg:\"Testing id 1\"; id:1234; sid:1;)";
375
    sigs[1]= "alert ip any any -> any any (msg:\"Testing id 2\"; id:5678; sid:2;)";
376
    sigs[2]= "alert ip any any -> any any (msg:\"Testing id 3\"; id:5101; sid:3;)";
377
378
    uint32_t sid[3] = {1, 2, 3};
379
380
    uint32_t results[3][3] = {
381
                              /* packet 0 match sid 1 but should not match sid 2 */
382
                              {1, 0, 0},
383
                              /* packet 1 should not match */
384
                              {0, 1, 0},
385
                              /* packet 2 should not match */
386
                              {0, 0, 1} };
387
388
    FAIL_IF_NOT(UTHGenericTest(p, 3, sigs, sid, (uint32_t *)results, 3));
389
390
    UTHFreePackets(p, 3);
391
392
    PASS;
393
}
394
395
/**
396
 * \brief this function registers unit tests for DetectId
397
 */
398
void DetectIdRegisterTests(void)
399
{
400
    UtRegisterTest("DetectIdTestParse01", DetectIdTestParse01);
401
    UtRegisterTest("DetectIdTestParse02", DetectIdTestParse02);
402
    UtRegisterTest("DetectIdTestParse03", DetectIdTestParse03);
403
    UtRegisterTest("DetectIdTestParse04", DetectIdTestParse04);
404
    UtRegisterTest("DetectIdTestMatch01", DetectIdTestMatch01);
405
406
}
407
#endif /* UNITTESTS */