Coverage Report

Created: 2026-09-28 07:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata8/src/util-port-interval-tree.c
Line
Count
Source
1
/* Copyright (C) 2024 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 Shivani Bhardwaj <shivani@oisf.net>
22
 */
23
24
#include "util-port-interval-tree.h"
25
#include "util-validate.h"
26
#include "detect-engine-siggroup.h"
27
#include "detect-engine-port.h"
28
29
/**
30
 * \brief Function to compare two interval nodes. This defines the order
31
 *        of insertion of a node in the interval tree. This also updates
32
 *        the max attribute of any node in a given tree if needed.
33
 *
34
 * \param a First node to compare
35
 * \param b Second node to compare
36
 *
37
 * \return 1 if low of node a is bigger, -1 otherwise
38
 */
39
static int SCPortIntervalCompareAndUpdate(const SCPortIntervalNode *a, SCPortIntervalNode *b)
40
36.4k
{
41
36.4k
    if (a->port2 > b->max) {
42
13.4k
        b->max = a->port2;
43
13.4k
    }
44
36.4k
    if (a->port >= b->port) {
45
23.7k
        SCReturnInt(1);
46
23.7k
    }
47
36.4k
    SCReturnInt(-1);
48
36.4k
}
49
50
// cppcheck-suppress nullPointerRedundantCheck
51
6.95M
IRB_GENERATE(PI, SCPortIntervalNode, irb, SCPortIntervalCompareAndUpdate);
PI_IRB_INSERT_COLOR
Line
Count
Source
51
IRB_GENERATE(PI, SCPortIntervalNode, irb, SCPortIntervalCompareAndUpdate);
PI_IRB_REMOVE_COLOR
Line
Count
Source
51
IRB_GENERATE(PI, SCPortIntervalNode, irb, SCPortIntervalCompareAndUpdate);
PI_IRB_INSERT
Line
Count
Source
51
IRB_GENERATE(PI, SCPortIntervalNode, irb, SCPortIntervalCompareAndUpdate);
PI_IRB_REMOVE
Line
Count
Source
51
IRB_GENERATE(PI, SCPortIntervalNode, irb, SCPortIntervalCompareAndUpdate);
Unexecuted instantiation: PI_IRB_FIND
Unexecuted instantiation: PI_IRB_NFIND
PI_IRB_MINMAX
Line
Count
Source
51
IRB_GENERATE(PI, SCPortIntervalNode, irb, SCPortIntervalCompareAndUpdate);
52
6.95M
53
6.95M
/**
54
6.95M
 * \brief Function to initialize the interval tree.
55
6.95M
 *
56
6.95M
 * \return Pointer to the newly created interval tree
57
6.95M
 */
58
6.95M
SCPortIntervalTree *SCPortIntervalTreeInit(void)
59
6.95M
{
60
460k
    SCPortIntervalTree *it = SCCalloc(1, sizeof(SCPortIntervalTree));
61
460k
    if (it == NULL) {
62
0
        return NULL;
63
0
    }
64
65
460k
    return it;
66
460k
}
67
68
/**
69
 * \brief Helper function to free a given node in the interval tree.
70
 *
71
 * \param de_ctx Detection Engine Context
72
 * \param it Pointer to the interval tree
73
 */
74
static void SCPortIntervalNodeFree(DetectEngineCtx *de_ctx, SCPortIntervalTree *it)
75
460k
{
76
460k
    SCPortIntervalNode *node = NULL, *safe = NULL;
77
460k
    IRB_FOREACH_SAFE(node, PI, &it->tree, safe)
78
307k
    {
79
307k
        SigGroupHeadFree(de_ctx, node->sh);
80
307k
        PI_IRB_REMOVE(&it->tree, node);
81
307k
        SCFree(node);
82
307k
    }
83
460k
    it->head = NULL;
84
460k
}
85
86
/**
87
 * \brief Function to free an entire interval tree.
88
 *
89
 * \param de_ctx Detection Engine Context
90
 * \param it Pointer to the interval tree
91
 */
92
void SCPortIntervalTreeFree(DetectEngineCtx *de_ctx, SCPortIntervalTree *it)
93
460k
{
94
460k
    if (it) {
95
460k
        SCPortIntervalNodeFree(de_ctx, it);
96
460k
        SCFree(it);
97
460k
    }
98
460k
}
99
100
/**
101
 * \brief Function to insert a node in the interval tree.
102
 *
103
 * \param de_ctx Detection Engine Context
104
 * \param it Pointer to the interval tree
105
 * \param p Pointer to a DetectPort object
106
 *
107
 * \return SC_OK if the node was inserted successfully, SC_EINVAL otherwise
108
 */
109
int SCPortIntervalInsert(DetectEngineCtx *de_ctx, SCPortIntervalTree *it, const DetectPort *p)
110
307k
{
111
307k
    DEBUG_VALIDATE_BUG_ON(p->port > p->port2);
112
113
307k
    SCPortIntervalNode *pi = SCCalloc(1, sizeof(*pi));
114
307k
    if (pi == NULL) {
115
0
        return SC_EINVAL;
116
0
    }
117
118
307k
    pi->port = p->port;
119
307k
    pi->port2 = p->port2;
120
307k
    SigGroupHeadCopySigs(de_ctx, p->sh, &pi->sh);
121
122
307k
    if (PI_IRB_INSERT(&it->tree, pi) != NULL) {
123
0
        SCLogDebug("Node wasn't added to the tree: port: %d, port2: %d", pi->port, pi->port2);
124
0
        SCFree(pi);
125
0
        return SC_EINVAL;
126
0
    }
127
307k
    return SC_OK;
128
307k
}
129
130
/**
131
 * \brief Function to remove multiple sig entries corresponding to the same
132
 *        signature group and merge them into one.
133
 *
134
 * \param de_ctx Detection Engine Context
135
 * \param list Pointer to the list to be modified
136
 */
137
static void SCPortIntervalSanitizeList(DetectEngineCtx *de_ctx, DetectPort **list)
138
494k
{
139
494k
    DetectPort *cur = (*list)->last;
140
494k
    if (cur == NULL)
141
0
        return;
142
143
494k
    DetectPort *prev = (*list)->last->prev;
144
494k
    if (prev == NULL)
145
147k
        return;
146
147
    /* rulegroup IDs are assigned much later so, compare SGHs */
148
346k
    if (SigGroupHeadEqual(prev->sh, cur->sh)) {
149
228k
        if (prev->port2 == (cur->port - 1)) {
150
            /* Merge the port objects */
151
226k
            prev->port2 = cur->port2;
152
226k
            (*list)->last = prev;
153
226k
            (*list)->last->next = NULL;
154
226k
            DetectPortFree(de_ctx, cur);
155
226k
        }
156
228k
    }
157
346k
}
158
159
/**
160
 * \brief Function to check if a port range overlaps with a given set of ports
161
 *
162
 * \param port Given low port
163
 * \param port2 Given high port
164
 * \param ptr Pointer to the node in the tree to be checked against
165
 *
166
 * \return true if an overlaps was found, false otherwise
167
 */
168
static bool SCPortIntervalIsOverlap(
169
        const uint16_t port, const uint16_t port2, const SCPortIntervalNode *ptr)
170
3.68M
{
171
    /* Two intervals i and i' are said to overlap if
172
     * - i (intersection) i' != NIL
173
     * - i.low <= i'.high
174
     * - i'.low <= i.high
175
     *
176
     * There are four possible cases of overlaps as shown below which
177
     * are all covered by the if condition that follows.
178
     *
179
     * Case 1:         [.........] i
180
     *            [...................] i'
181
     *
182
     * Case 2:    [...................] i
183
     *                 [.........] i'
184
     *
185
     * Case 3:               [........] i
186
     *            [..............] i'
187
     *
188
     * Case 4:    [..............] i
189
     *                  [.............] i'
190
     */
191
3.68M
    if (port <= ptr->port2 && ptr->port <= port2) {
192
781k
        return true;
193
781k
    }
194
195
2.90M
    SCLogDebug("No overlap found for [%d, %d] w [%d, %d]", port, port2, ptr->port, ptr->port2);
196
2.90M
    return false;
197
3.68M
}
198
199
496k
#define STACK_SIZE 100
200
201
/**
202
 * \brief Function to find all the overlaps of given ports with the existing
203
 *        port ranges in the interval tree. This function takes in a low and
204
 *        a high port, considers it a continuos range and tries to match it
205
 *        against all the given port ranges in the interval tree. This search
206
 *        for overlap happens in min(O(k*log(n)), O(n*n)) time where,
207
 *        n = number of nodes in the tree, and,
208
 *        k = number of intervals with which an overlap was found
209
 *
210
 * \param de_ctx Detection Engine Context
211
 * \param port Given low port
212
 * \param port2 Given high port
213
 * \param ptr Pointer to the root of the tree
214
 * \param list A list of DetectPort objects to be filled
215
 */
216
static void SCPortIntervalFindOverlaps(DetectEngineCtx *de_ctx, const uint16_t port,
217
        const uint16_t port2, SCPortIntervalNode *root, DetectPort **list)
218
496k
{
219
496k
    DetectPort *new_port = NULL;
220
496k
    int stack_depth = 0;
221
496k
    SCPortIntervalNode **stack =
222
496k
            (SCPortIntervalNode **)SCCalloc(STACK_SIZE, sizeof(SCPortIntervalNode *));
223
496k
    if (stack == NULL)
224
0
        return;
225
496k
    SCPortIntervalNode *current = root;
226
496k
    int stack_size = STACK_SIZE;
227
228
4.18M
    while (current || stack_depth) {
229
7.39M
        while (current != NULL) {
230
4.24M
            if (current->max < port) {
231
562k
                current = NULL;
232
562k
                break;
233
562k
            }
234
3.68M
            const bool is_overlap = SCPortIntervalIsOverlap(port, port2, current);
235
236
3.68M
            if (is_overlap && (new_port == NULL)) {
237
                /* Allocate memory for port obj only if it's first overlap */
238
494k
                new_port = DetectPortInit();
239
494k
                if (new_port == NULL) {
240
0
                    goto error;
241
0
                }
242
243
494k
                SCLogDebug("Found overlaps for [%u:%u], creating new port", port, port2);
244
494k
                new_port->port = port;
245
494k
                new_port->port2 = port2;
246
494k
                SigGroupHeadCopySigs(de_ctx, current->sh, &new_port->sh);
247
248
                /* Since it is guaranteed that the ports received by this stage
249
                 * will be sorted, insert any new ports to the end of the list
250
                 * and avoid walking the entire list */
251
494k
                if (*list == NULL) {
252
147k
                    *list = new_port;
253
147k
                    (*list)->last = new_port;
254
346k
                } else if (((*list)->last->port != new_port->port) &&
255
346k
                           ((*list)->last->port2 != new_port->port2)) {
256
346k
                    DEBUG_VALIDATE_BUG_ON(new_port->port < (*list)->last->port);
257
346k
                    (*list)->last->next = new_port;
258
346k
                    new_port->prev = (*list)->last;
259
346k
                    (*list)->last = new_port;
260
346k
                } else {
261
0
                    SCLogDebug("Port already exists in the list");
262
0
                    goto error;
263
0
                }
264
3.19M
            } else if (is_overlap && (new_port != NULL)) {
265
287k
                SCLogDebug("Found overlaps for [%u:%u], adding sigs", port, port2);
266
                /* Only copy the relevant SGHs on later overlaps */
267
287k
                SigGroupHeadCopySigs(de_ctx, current->sh, &new_port->sh);
268
287k
            }
269
3.68M
            stack[stack_depth++] = current;
270
3.68M
            if (stack_depth == stack_size) {
271
0
                SCLogDebug("Stack depth %d maxed out, realloc'ing..", stack_depth);
272
0
                stack_size *= 2;
273
0
                void *tmp = SCRealloc(stack, stack_size * sizeof(SCPortIntervalNode *));
274
0
                if (tmp == NULL) {
275
0
                    SCLogError("Couldn't realloc the interval tree stack");
276
0
                    goto error;
277
0
                }
278
0
                stack = tmp;
279
0
            }
280
3.68M
            current = IRB_LEFT(current, irb);
281
3.68M
        }
282
283
3.71M
        if (stack_depth == 0) {
284
23.4k
            SCLogDebug("Stack depth was exhausted");
285
23.4k
            break;
286
23.4k
        }
287
288
3.68M
        SCPortIntervalNode *popped = stack[stack_depth - 1];
289
3.68M
        stack_depth--;
290
3.68M
        BUG_ON(popped == NULL);
291
3.68M
        current = IRB_RIGHT(popped, irb);
292
3.68M
    }
293
496k
    if (new_port != NULL)
294
494k
        SCPortIntervalSanitizeList(de_ctx, list);
295
496k
    if (stack != NULL)
296
496k
        SCFree(stack);
297
496k
    return;
298
0
error:
299
0
    if (new_port != NULL)
300
0
        DetectPortFree(de_ctx, new_port);
301
0
    if (stack != NULL)
302
0
        SCFree(stack);
303
0
}
304
305
/**
306
 * \brief Callee function to find all overlapping port ranges as asked
307
 *        by the detection engine during Stage 2 of signature grouping.
308
 *
309
 * \param de_ctx Detection Engine Context
310
 * \param port Given low port
311
 * \param port2 Given high port
312
 * \param head Pointer to the head of the tree named PI
313
 * \param list Pointer to the list of port objects that needs to be filled/updated
314
 */
315
void SCPortIntervalFindOverlappingRanges(DetectEngineCtx *de_ctx, const uint16_t port,
316
        const uint16_t port2, const struct PI *head, DetectPort **list)
317
496k
{
318
496k
    if (head == NULL) {
319
0
        SCLogDebug("Tree head should not be NULL. Nothing to do further.");
320
0
        return;
321
0
    }
322
496k
    SCPortIntervalNode *ptr = IRB_ROOT(head);
323
496k
    SCLogDebug("Finding overlaps for the range [%d, %d]", port, port2);
324
496k
    SCPortIntervalFindOverlaps(de_ctx, port, port2, ptr, list);
325
496k
}