Coverage Report

Created: 2026-03-31 07:45

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata7/src/detect-fragoffset.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 Breno Silva <breno.silva@gmail.com>
22
 *
23
 * Implements fragoffset keyword
24
 */
25
26
#include "suricata-common.h"
27
#include "decode.h"
28
#include "decode-ipv4.h"
29
#include "decode-ipv6.h"
30
31
#include "detect.h"
32
#include "detect-parse.h"
33
#include "detect-engine-prefilter-common.h"
34
#include "detect-engine-build.h"
35
36
#include "detect-fragoffset.h"
37
38
#include "util-byte.h"
39
#include "util-unittest.h"
40
#include "util-debug.h"
41
42
73
#define PARSE_REGEX "^\\s*(?:(<|>))?\\s*([0-9]+)"
43
44
static DetectParseRegex parse_regex;
45
46
static int DetectFragOffsetMatch(DetectEngineThreadCtx *,
47
        Packet *, const Signature *, const SigMatchCtx *);
48
static int DetectFragOffsetSetup(DetectEngineCtx *, Signature *, const char *);
49
#ifdef UNITTESTS
50
void DetectFragOffsetRegisterTests(void);
51
#endif
52
void DetectFragOffsetFree(DetectEngineCtx *, void *);
53
54
static int PrefilterSetupFragOffset(DetectEngineCtx *de_ctx, SigGroupHead *sgh);
55
static bool PrefilterFragOffsetIsPrefilterable(const Signature *s);
56
57
/**
58
 * \brief Registration function for fragoffset
59
 */
60
void DetectFragOffsetRegister (void)
61
73
{
62
73
    sigmatch_table[DETECT_FRAGOFFSET].name = "fragoffset";
63
73
    sigmatch_table[DETECT_FRAGOFFSET].desc = "match on specific decimal values of the IP fragment offset field";
64
73
    sigmatch_table[DETECT_FRAGOFFSET].url = "/rules/header-keywords.html#fragoffset";
65
73
    sigmatch_table[DETECT_FRAGOFFSET].Match = DetectFragOffsetMatch;
66
73
    sigmatch_table[DETECT_FRAGOFFSET].Setup = DetectFragOffsetSetup;
67
73
    sigmatch_table[DETECT_FRAGOFFSET].Free = DetectFragOffsetFree;
68
#ifdef UNITTESTS
69
    sigmatch_table[DETECT_FRAGOFFSET].RegisterTests = DetectFragOffsetRegisterTests;
70
#endif
71
73
    sigmatch_table[DETECT_FRAGOFFSET].SupportsPrefilter = PrefilterFragOffsetIsPrefilterable;
72
73
    sigmatch_table[DETECT_FRAGOFFSET].SetupPrefilter = PrefilterSetupFragOffset;
73
74
73
    DetectSetupParseRegexes(PARSE_REGEX, &parse_regex);
75
73
}
76
77
static inline int FragOffsetMatch(const uint16_t poffset, const uint8_t mode,
78
                                  const uint16_t doffset)
79
20.0k
{
80
20.0k
    switch (mode)  {
81
312
        case FRAG_LESS:
82
312
            if (poffset < doffset)
83
311
                return 1;
84
1
            break;
85
10.2k
        case FRAG_MORE:
86
10.2k
            if (poffset > doffset)
87
13
                return 1;
88
10.2k
            break;
89
10.2k
        default:
90
9.39k
            if (poffset == doffset)
91
4.50k
                return 1;
92
20.0k
    }
93
15.1k
    return 0;
94
20.0k
}
95
96
/**
97
 * \brief This function is used to match fragoffset rule option set on a packet
98
 *
99
 * \param t pointer to thread vars
100
 * \param det_ctx pointer to the pattern matcher thread
101
 * \param p pointer to the current packet
102
 * \param m pointer to the sigmatch that we will cast into DetectFragOffsetData
103
 *
104
 * \retval 0 no match or frag is not set
105
 * \retval 1 match
106
 *
107
 */
108
static int DetectFragOffsetMatch (DetectEngineThreadCtx *det_ctx,
109
        Packet *p, const Signature *s, const SigMatchCtx *ctx)
110
11.2k
{
111
11.2k
    uint16_t frag = 0;
112
11.2k
    const DetectFragOffsetData *fragoff = (const DetectFragOffsetData *)ctx;
113
114
11.2k
    if (PKT_IS_PSEUDOPKT(p))
115
382
        return 0;
116
117
10.8k
    if (PKT_IS_IPV4(p)) {
118
9.48k
        frag = IPV4_GET_IPOFFSET(p);
119
9.48k
    } else if (PKT_IS_IPV6(p)) {
120
1.39k
        if (IPV6_EXTHDR_ISSET_FH(p)) {
121
480
            frag = IPV6_EXTHDR_GET_FH_OFFSET(p);
122
914
        } else {
123
914
            return 0;
124
914
        }
125
1.39k
    } else {
126
0
        SCLogDebug("No IPv4 or IPv6 packet");
127
0
        return 0;
128
0
    }
129
130
9.96k
    return FragOffsetMatch(frag, fragoff->mode, fragoff->frag_off);
131
10.8k
}
132
133
/**
134
 * \brief This function is used to parse fragoffset option passed via fragoffset: keyword
135
 *
136
 * \param de_ctx Pointer to the detection engine context
137
 * \param fragoffsetstr Pointer to the user provided fragoffset options
138
 *
139
 * \retval fragoff pointer to DetectFragOffsetData on success
140
 * \retval NULL on failure
141
 */
142
static DetectFragOffsetData *DetectFragOffsetParse (DetectEngineCtx *de_ctx, const char *fragoffsetstr)
143
5.49k
{
144
5.49k
    DetectFragOffsetData *fragoff = NULL;
145
5.49k
    char *substr[3] = {NULL, NULL, NULL};
146
5.49k
    int res = 0;
147
5.49k
    size_t pcre2_len;
148
5.49k
    int i;
149
5.49k
    const char *str_ptr;
150
5.49k
    char *mode = NULL;
151
5.49k
    pcre2_match_data *match = NULL;
152
153
5.49k
    int ret = DetectParsePcreExec(&parse_regex, &match, fragoffsetstr, 0, 0);
154
5.49k
    if (ret < 1 || ret > 4) {
155
871
        SCLogError("Parse error %s", fragoffsetstr);
156
871
        goto error;
157
871
    }
158
159
13.8k
    for (i = 1; i < ret; i++) {
160
9.24k
        res = SC_Pcre2SubstringGet(match, i, (PCRE2_UCHAR8 **)&str_ptr, &pcre2_len);
161
9.24k
        if (res < 0) {
162
0
            SCLogError("pcre2_substring_get_bynumber failed");
163
0
            goto error;
164
0
        }
165
9.24k
        substr[i-1] = (char *)str_ptr;
166
9.24k
    }
167
168
4.62k
    fragoff = SCMalloc(sizeof(DetectFragOffsetData));
169
4.62k
    if (unlikely(fragoff == NULL))
170
0
        goto error;
171
172
4.62k
    fragoff->frag_off = 0;
173
4.62k
    fragoff->mode = 0;
174
175
4.62k
    mode = substr[0];
176
177
4.62k
    if(mode != NULL)    {
178
179
6.78k
        while(*mode != '\0')    {
180
3.39k
            switch(*mode)   {
181
2.79k
                case '>':
182
2.79k
                    fragoff->mode = FRAG_MORE;
183
2.79k
                    break;
184
601
                case '<':
185
601
                    fragoff->mode = FRAG_LESS;
186
601
                    break;
187
3.39k
            }
188
3.39k
            mode++;
189
3.39k
        }
190
3.39k
    }
191
192
4.62k
    if (StringParseUint16(&fragoff->frag_off, 10, 0, substr[1]) < 0) {
193
44
        SCLogError("specified frag offset %s is not "
194
44
                   "valid",
195
44
                substr[1]);
196
44
        goto error;
197
44
    }
198
199
18.3k
    for (i = 0; i < 3; i++) {
200
13.7k
        if (substr[i] != NULL)
201
13.7k
            pcre2_substring_free((PCRE2_UCHAR8 *)substr[i]);
202
13.7k
    }
203
204
4.58k
    pcre2_match_data_free(match);
205
4.58k
    return fragoff;
206
207
915
error:
208
915
    if (match) {
209
915
        pcre2_match_data_free(match);
210
915
    }
211
3.66k
    for (i = 0; i < 3; i++) {
212
2.74k
        if (substr[i] != NULL)
213
2.74k
            pcre2_substring_free((PCRE2_UCHAR8 *)substr[i]);
214
2.74k
    }
215
915
    if (fragoff != NULL) DetectFragOffsetFree(de_ctx, fragoff);
216
915
    return NULL;
217
218
4.62k
}
219
220
/**
221
 * \brief this function is used to add the parsed fragoffset data into the current signature
222
 *
223
 * \param de_ctx pointer to the Detection Engine Context
224
 * \param s pointer to the Current Signature
225
 * \param fragoffsetstr pointer to the user provided fragoffset option
226
 *
227
 * \retval 0 on Success
228
 * \retval -1 on Failure
229
 */
230
static int DetectFragOffsetSetup (DetectEngineCtx *de_ctx, Signature *s, const char *fragoffsetstr)
231
5.49k
{
232
5.49k
    DetectFragOffsetData *fragoff = NULL;
233
5.49k
    SigMatch *sm = NULL;
234
235
5.49k
    fragoff = DetectFragOffsetParse(de_ctx, fragoffsetstr);
236
5.49k
    if (fragoff == NULL) goto error;
237
238
4.58k
    sm = SigMatchAlloc();
239
4.58k
    if (sm == NULL) goto error;
240
241
4.58k
    sm->type = DETECT_FRAGOFFSET;
242
4.58k
    sm->ctx = (SigMatchCtx *)fragoff;
243
244
4.58k
    SigMatchAppendSMToList(s, sm, DETECT_SM_LIST_MATCH);
245
4.58k
    s->flags |= SIG_FLAG_REQUIRE_PACKET;
246
247
4.58k
    return 0;
248
249
915
error:
250
915
    if (fragoff != NULL) DetectFragOffsetFree(de_ctx, fragoff);
251
915
    if (sm != NULL) SCFree(sm);
252
915
    return -1;
253
254
4.58k
}
255
256
/**
257
 * \brief this function will free memory associated with DetectFragOffsetData
258
 *
259
 * \param ptr pointer to DetectFragOffsetData
260
 */
261
void DetectFragOffsetFree (DetectEngineCtx *de_ctx, void *ptr)
262
7.89k
{
263
7.89k
    DetectFragOffsetData *fragoff = (DetectFragOffsetData *)ptr;
264
7.89k
    SCFree(fragoff);
265
7.89k
}
266
267
static void
268
PrefilterPacketFragOffsetMatch(DetectEngineThreadCtx *det_ctx, Packet *p, const void *pectx)
269
11.2k
{
270
11.2k
    if (PKT_IS_PSEUDOPKT(p))
271
1.04k
        return;
272
273
10.1k
    uint16_t frag;
274
275
10.1k
    if (PKT_IS_IPV4(p)) {
276
10.0k
        frag = IPV4_GET_IPOFFSET(p);
277
10.0k
    } else if (PKT_IS_IPV6(p)) {
278
119
        if (IPV6_EXTHDR_ISSET_FH(p)) {
279
0
            frag = IPV6_EXTHDR_GET_FH_OFFSET(p);
280
119
        } else {
281
119
            return;
282
119
        }
283
119
    } else {
284
0
        SCLogDebug("No IPv4 or IPv6 packet");
285
0
        return;
286
0
    }
287
288
10.0k
    const PrefilterPacketHeaderCtx *ctx = pectx;
289
10.0k
    if (FragOffsetMatch(frag, ctx->v1.u8[0], ctx->v1.u16[1]))
290
312
    {
291
312
        PrefilterAddSids(&det_ctx->pmq, ctx->sigs_array, ctx->sigs_cnt);
292
312
    }
293
10.0k
}
294
295
static void
296
PrefilterPacketFragOffsetSet(PrefilterPacketHeaderValue *v, void *smctx)
297
24.9k
{
298
24.9k
    const DetectFragOffsetData *fb = smctx;
299
24.9k
    v->u8[0] = fb->mode;
300
24.9k
    v->u16[1] = fb->frag_off;
301
24.9k
}
302
303
static bool
304
PrefilterPacketFragOffsetCompare(PrefilterPacketHeaderValue v, void *smctx)
305
23.6k
{
306
23.6k
    const DetectFragOffsetData *fb = smctx;
307
23.6k
    if (v.u8[0] == fb->mode &&
308
23.4k
        v.u16[1] == fb->frag_off)
309
23.1k
    {
310
23.1k
        return true;
311
23.1k
    }
312
456
    return false;
313
23.6k
}
314
315
static int PrefilterSetupFragOffset(DetectEngineCtx *de_ctx, SigGroupHead *sgh)
316
13.2k
{
317
13.2k
    return PrefilterSetupPacketHeader(de_ctx, sgh, DETECT_FRAGOFFSET,
318
13.2k
        PrefilterPacketFragOffsetSet,
319
13.2k
        PrefilterPacketFragOffsetCompare,
320
13.2k
        PrefilterPacketFragOffsetMatch);
321
13.2k
}
322
323
static bool PrefilterFragOffsetIsPrefilterable(const Signature *s)
324
0
{
325
0
    const SigMatch *sm;
326
0
    for (sm = s->init_data->smlists[DETECT_SM_LIST_MATCH] ; sm != NULL; sm = sm->next) {
327
0
        switch (sm->type) {
328
0
            case DETECT_FRAGOFFSET:
329
0
                return true;
330
0
        }
331
0
    }
332
0
    return false;
333
0
}
334
335
#ifdef UNITTESTS
336
#include "util-unittest-helper.h"
337
#include "detect-engine.h"
338
#include "detect-engine-alert.h"
339
340
/**
341
 * \test DetectFragOffsetParseTest01 is a test for setting a valid fragoffset value
342
 */
343
static int DetectFragOffsetParseTest01 (void)
344
{
345
    DetectFragOffsetData *fragoff = DetectFragOffsetParse(NULL, "300");
346
347
    FAIL_IF_NULL(fragoff);
348
    FAIL_IF_NOT(fragoff->frag_off == 300);
349
350
    DetectFragOffsetFree(NULL, fragoff);
351
352
    PASS;
353
}
354
355
/**
356
 * \test DetectFragOffsetParseTest02 is a test for setting a valid fragoffset value
357
 *       with spaces all around
358
 */
359
static int DetectFragOffsetParseTest02 (void)
360
{
361
    DetectFragOffsetData *fragoff = DetectFragOffsetParse(NULL, ">300");
362
363
    FAIL_IF_NULL(fragoff);
364
    FAIL_IF_NOT(fragoff->frag_off == 300);
365
    FAIL_IF_NOT(fragoff->mode == FRAG_MORE);
366
367
    DetectFragOffsetFree(NULL, fragoff);
368
369
    PASS;
370
}
371
372
/**
373
 * \test DetectFragOffsetParseTest03 is a test for setting an invalid fragoffset value
374
 */
375
static int DetectFragOffsetParseTest03 (void)
376
{
377
    DetectFragOffsetData *fragoff = DetectFragOffsetParse(NULL, "badc");
378
379
    FAIL_IF_NOT_NULL(fragoff);
380
381
    PASS;
382
}
383
384
/**
385
 * \test DetectFragOffsetMatchTest01 is a test for checking the working of
386
 *       fragoffset keyword by creating 2 rules and matching a crafted packet
387
 *       against them. Only the first one shall trigger.
388
 */
389
static int DetectFragOffsetMatchTest01 (void)
390
{
391
    Packet *p = PacketGetFromAlloc();
392
393
    FAIL_IF_NULL(p);
394
    Signature *s = NULL;
395
    DecodeThreadVars dtv;
396
    ThreadVars th_v;
397
    DetectEngineThreadCtx *det_ctx = NULL;
398
    IPV4Hdr ip4h;
399
400
    memset(&ip4h, 0, sizeof(IPV4Hdr));
401
    memset(&dtv, 0, sizeof(DecodeThreadVars));
402
    memset(&th_v, 0, sizeof(ThreadVars));
403
404
    FlowInitConfig(FLOW_QUIET);
405
406
    p->src.family = AF_INET;
407
    p->dst.family = AF_INET;
408
    p->src.addr_data32[0] = 0x01020304;
409
    p->dst.addr_data32[0] = 0x04030201;
410
411
    ip4h.s_ip_src.s_addr = p->src.addr_data32[0];
412
    ip4h.s_ip_dst.s_addr = p->dst.addr_data32[0];
413
    ip4h.ip_off = 0x2222;
414
    p->ip4h = &ip4h;
415
416
    DetectEngineCtx *de_ctx = DetectEngineCtxInit();
417
    FAIL_IF_NULL(de_ctx);
418
419
    de_ctx->flags |= DE_QUIET;
420
421
    s = DetectEngineAppendSig(de_ctx, "alert ip any any -> any any (fragoffset:546; sid:1;)");
422
    FAIL_IF_NULL(s);
423
424
    s = DetectEngineAppendSig(de_ctx, "alert ip any any -> any any (fragoffset:5000; sid:2;)");
425
    FAIL_IF_NULL(s);
426
427
    SigGroupBuild(de_ctx);
428
    DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx);
429
430
    SigMatchSignatures(&th_v, de_ctx, det_ctx, p);
431
432
    FAIL_IF(PacketAlertCheck(p, 1) == 0);
433
    FAIL_IF(PacketAlertCheck(p, 2));
434
435
    DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx);
436
    DetectEngineCtxFree(de_ctx);
437
438
    FlowShutdown();
439
440
    SCFree(p);
441
    PASS;
442
}
443
444
void DetectFragOffsetRegisterTests (void)
445
{
446
    UtRegisterTest("DetectFragOffsetParseTest01", DetectFragOffsetParseTest01);
447
    UtRegisterTest("DetectFragOffsetParseTest02", DetectFragOffsetParseTest02);
448
    UtRegisterTest("DetectFragOffsetParseTest03", DetectFragOffsetParseTest03);
449
    UtRegisterTest("DetectFragOffsetMatchTest01", DetectFragOffsetMatchTest01);
450
}
451
#endif /* UNITTESTS */