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