Coverage Report

Created: 2026-01-16 07:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata7/src/detect-tcp-window.c
Line
Count
Source
1
/* Copyright (C) 2007-2020 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 window keyword.
24
 */
25
26
#include "suricata-common.h"
27
#include "decode.h"
28
29
#include "detect.h"
30
#include "detect-parse.h"
31
32
#include "detect-tcp-window.h"
33
#include "flow.h"
34
#include "flow-var.h"
35
36
#include "util-debug.h"
37
#include "util-unittest.h"
38
#include "util-unittest-helper.h"
39
#include "util-byte.h"
40
41
/**
42
 * \brief Regex for parsing our window option
43
 */
44
73
#define PARSE_REGEX  "^\\s*([!])?\\s*([0-9]{1,9}+)\\s*$"
45
46
static DetectParseRegex parse_regex;
47
48
static int DetectWindowMatch(DetectEngineThreadCtx *, Packet *,
49
        const Signature *, const SigMatchCtx *);
50
static int DetectWindowSetup(DetectEngineCtx *, Signature *, const char *);
51
#ifdef UNITTESTS
52
static void DetectWindowRegisterTests(void);
53
#endif
54
void DetectWindowFree(DetectEngineCtx *, void *);
55
56
/**
57
 * \brief Registration function for window: keyword
58
 */
59
void DetectWindowRegister (void)
60
73
{
61
73
    sigmatch_table[DETECT_WINDOW].name = "tcp.window";
62
73
    sigmatch_table[DETECT_WINDOW].alias = "window";
63
73
    sigmatch_table[DETECT_WINDOW].desc = "check for a specific TCP window size";
64
73
    sigmatch_table[DETECT_WINDOW].url = "/rules/header-keywords.html#window";
65
73
    sigmatch_table[DETECT_WINDOW].Match = DetectWindowMatch;
66
73
    sigmatch_table[DETECT_WINDOW].Setup = DetectWindowSetup;
67
73
    sigmatch_table[DETECT_WINDOW].Free  = DetectWindowFree;
68
#ifdef UNITTESTS
69
    sigmatch_table[DETECT_WINDOW].RegisterTests = DetectWindowRegisterTests;
70
#endif
71
73
    DetectSetupParseRegexes(PARSE_REGEX, &parse_regex);
72
73
}
73
74
/**
75
 * \brief This function is used to match the window size on a packet
76
 *
77
 * \param t pointer to thread vars
78
 * \param det_ctx pointer to the pattern matcher thread
79
 * \param p pointer to the current packet
80
 * \param m pointer to the sigmatch that we will cast into DetectWindowData
81
 *
82
 * \retval 0 no match
83
 * \retval 1 match
84
 */
85
static int DetectWindowMatch(DetectEngineThreadCtx *det_ctx, Packet *p,
86
        const Signature *s, const SigMatchCtx *ctx)
87
1.35k
{
88
1.35k
    const DetectWindowData *wd = (const DetectWindowData *)ctx;
89
90
1.35k
    if ( !(PKT_IS_TCP(p)) || wd == NULL || PKT_IS_PSEUDOPKT(p)) {
91
531
        return 0;
92
531
    }
93
94
821
    if ( (!wd->negated && wd->size == TCP_GET_WINDOW(p)) || (wd->negated && wd->size != TCP_GET_WINDOW(p))) {
95
60
        return 1;
96
60
    }
97
98
761
    return 0;
99
821
}
100
101
/**
102
 * \brief This function is used to parse window options passed via window: keyword
103
 *
104
 * \param de_ctx Pointer to the detection engine context
105
 * \param windowstr Pointer to the user provided window options (negation! and size)
106
 *
107
 * \retval wd pointer to DetectWindowData on success
108
 * \retval NULL on failure
109
 */
110
static DetectWindowData *DetectWindowParse(DetectEngineCtx *de_ctx, const char *windowstr)
111
4.82k
{
112
4.82k
    DetectWindowData *wd = NULL;
113
4.82k
    int res = 0;
114
4.82k
    size_t pcre2len;
115
116
4.82k
    pcre2_match_data *match = NULL;
117
4.82k
    int ret = DetectParsePcreExec(&parse_regex, &match, windowstr, 0, 0);
118
4.82k
    if (ret < 1 || ret > 3) {
119
1.90k
        SCLogError("pcre_exec parse error, ret %" PRId32 ", string %s", ret, windowstr);
120
1.90k
        goto error;
121
1.90k
    }
122
123
2.92k
    wd = SCMalloc(sizeof(DetectWindowData));
124
2.92k
    if (unlikely(wd == NULL))
125
0
        goto error;
126
127
2.92k
    if (ret > 1) {
128
2.92k
        char copy_str[128] = "";
129
2.92k
        pcre2len = sizeof(copy_str);
130
2.92k
        res = SC_Pcre2SubstringCopy(match, 1, (PCRE2_UCHAR8 *)copy_str, &pcre2len);
131
2.92k
        if (res < 0) {
132
0
            SCLogError("pcre2_substring_copy_bynumber failed");
133
0
            goto error;
134
0
        }
135
136
        /* Detect if it's negated */
137
2.92k
        if (copy_str[0] == '!')
138
357
            wd->negated = 1;
139
2.56k
        else
140
2.56k
            wd->negated = 0;
141
142
2.92k
        if (ret > 2) {
143
2.92k
            pcre2len = sizeof(copy_str);
144
2.92k
            res = pcre2_substring_copy_bynumber(match, 2, (PCRE2_UCHAR8 *)copy_str, &pcre2len);
145
2.92k
            if (res < 0) {
146
0
                SCLogError("pcre2_substring_copy_bynumber failed");
147
0
                goto error;
148
0
            }
149
150
            /* Get the window size if it's a valid value (in packets, we
151
             * should alert if this doesn't happen from decode) */
152
2.92k
            if (StringParseUint16(&wd->size, 10, 0, copy_str) < 0) {
153
132
                goto error;
154
132
            }
155
2.92k
        }
156
2.92k
    }
157
158
2.92k
    pcre2_match_data_free(match);
159
2.78k
    return wd;
160
161
2.03k
error:
162
2.03k
    if (match) {
163
2.03k
        pcre2_match_data_free(match);
164
2.03k
    }
165
2.03k
    if (wd != NULL)
166
132
        DetectWindowFree(de_ctx, wd);
167
2.03k
    return NULL;
168
169
2.92k
}
170
171
/**
172
 * \brief this function is used to add the parsed window sizedata into the current signature
173
 *
174
 * \param de_ctx pointer to the Detection Engine Context
175
 * \param s pointer to the Current Signature
176
 * \param windowstr pointer to the user provided window options
177
 *
178
 * \retval 0 on Success
179
 * \retval -1 on Failure
180
 */
181
static int DetectWindowSetup (DetectEngineCtx *de_ctx, Signature *s, const char *windowstr)
182
4.82k
{
183
4.82k
    DetectWindowData *wd = NULL;
184
4.82k
    SigMatch *sm = NULL;
185
186
4.82k
    wd = DetectWindowParse(de_ctx, windowstr);
187
4.82k
    if (wd == NULL) goto error;
188
189
    /* Okay so far so good, lets get this into a SigMatch
190
     * and put it in the Signature. */
191
2.78k
    sm = SigMatchAlloc();
192
2.78k
    if (sm == NULL)
193
0
        goto error;
194
195
2.78k
    sm->type = DETECT_WINDOW;
196
2.78k
    sm->ctx = (SigMatchCtx *)wd;
197
198
2.78k
    SigMatchAppendSMToList(s, sm, DETECT_SM_LIST_MATCH);
199
2.78k
    s->flags |= SIG_FLAG_REQUIRE_PACKET;
200
201
2.78k
    return 0;
202
203
2.03k
error:
204
2.03k
    if (wd != NULL) DetectWindowFree(de_ctx, wd);
205
2.03k
    if (sm != NULL) SCFree(sm);
206
2.03k
    return -1;
207
208
2.78k
}
209
210
/**
211
 * \brief this function will free memory associated with DetectWindowData
212
 *
213
 * \param wd pointer to DetectWindowData
214
 */
215
void DetectWindowFree(DetectEngineCtx *de_ctx, void *ptr)
216
5.59k
{
217
5.59k
    DetectWindowData *wd = (DetectWindowData *)ptr;
218
5.59k
    SCFree(wd);
219
5.59k
}
220
221
#ifdef UNITTESTS /* UNITTESTS */
222
223
/**
224
 * \test DetectWindowTestParse01 is a test to make sure that we set the size correctly
225
 *  when given valid window opt
226
 */
227
static int DetectWindowTestParse01 (void)
228
{
229
    DetectWindowData *wd = NULL;
230
    wd = DetectWindowParse(NULL, "35402");
231
    FAIL_IF_NULL(wd);
232
    FAIL_IF_NOT(wd->size == 35402);
233
234
    DetectWindowFree(NULL, wd);
235
    PASS;
236
}
237
238
/**
239
 * \test DetectWindowTestParse02 is a test for setting the window opt negated
240
 */
241
static int DetectWindowTestParse02 (void)
242
{
243
    DetectWindowData *wd = NULL;
244
    wd = DetectWindowParse(NULL, "!35402");
245
    FAIL_IF_NULL(wd);
246
    FAIL_IF_NOT(wd->negated == 1);
247
    FAIL_IF_NOT(wd->size == 35402);
248
249
    DetectWindowFree(NULL, wd);
250
    PASS;
251
}
252
253
/**
254
 * \test DetectWindowTestParse03 is a test to check for an empty value
255
 */
256
static int DetectWindowTestParse03 (void)
257
{
258
    DetectWindowData *wd = NULL;
259
    wd = DetectWindowParse(NULL, "");
260
    FAIL_IF_NOT_NULL(wd);
261
262
    DetectWindowFree(NULL, wd);
263
    PASS;
264
}
265
266
/**
267
 * \test DetectWindowTestParse03 is a test to check for a big value
268
 */
269
static int DetectWindowTestParse04 (void)
270
{
271
    DetectWindowData *wd = NULL;
272
    wd = DetectWindowParse(NULL, "1235402");
273
    FAIL_IF_NOT_NULL(wd);
274
275
    DetectWindowFree(NULL, wd);
276
    PASS;
277
}
278
279
/**
280
 * \test DetectWindowTestPacket01 is a test to check window with constructed packets
281
 */
282
static int DetectWindowTestPacket01 (void)
283
{
284
    uint8_t *buf = (uint8_t *)"Hi all!";
285
    uint16_t buflen = strlen((char *)buf);
286
    Packet *p[3];
287
    p[0] = UTHBuildPacket((uint8_t *)buf, buflen, IPPROTO_TCP);
288
    p[1] = UTHBuildPacket((uint8_t *)buf, buflen, IPPROTO_TCP);
289
    p[2] = UTHBuildPacket((uint8_t *)buf, buflen, IPPROTO_ICMP);
290
291
    FAIL_IF(p[0] == NULL || p[1] == NULL || p[2] == NULL);
292
293
    /* TCP wwindow = 40 */
294
    p[0]->tcph->th_win = htons(40);
295
296
    /* TCP window = 41 */
297
    p[1]->tcph->th_win = htons(41);
298
299
    const char *sigs[2];
300
    sigs[0]= "alert tcp any any -> any any (msg:\"Testing window 1\"; window:40; sid:1;)";
301
    sigs[1]= "alert tcp any any -> any any (msg:\"Testing window 2\"; window:41; sid:2;)";
302
303
    uint32_t sid[2] = {1, 2};
304
305
    uint32_t results[3][2] = {
306
                              /* packet 0 match sid 1 but should not match sid 2 */
307
                              {1, 0},
308
                              /* packet 1 should not match */
309
                              {0, 1},
310
                              /* packet 2 should not match */
311
                              {0, 0} };
312
    FAIL_IF(UTHGenericTest(p, 3, sigs, sid, (uint32_t *)results, 2) == 0);
313
314
    UTHFreePackets(p, 3);
315
    PASS;
316
}
317
318
/**
319
 * \brief this function registers unit tests for DetectWindow
320
 */
321
void DetectWindowRegisterTests(void)
322
{
323
    UtRegisterTest("DetectWindowTestParse01", DetectWindowTestParse01);
324
    UtRegisterTest("DetectWindowTestParse02", DetectWindowTestParse02);
325
    UtRegisterTest("DetectWindowTestParse03", DetectWindowTestParse03);
326
    UtRegisterTest("DetectWindowTestParse04", DetectWindowTestParse04);
327
    UtRegisterTest("DetectWindowTestPacket01", DetectWindowTestPacket01);
328
}
329
#endif /* UNITTESTS */