/src/suricata7/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 | 253k | { |
41 | 253k | if (a->port2 > b->max) { |
42 | 98.2k | b->max = a->port2; |
43 | 98.2k | } |
44 | 253k | if (a->port >= b->port) { |
45 | 173k | SCReturnInt(1); |
46 | 173k | } |
47 | 253k | SCReturnInt(-1); |
48 | 253k | } |
49 | | |
50 | | // cppcheck-suppress nullPointerRedundantCheck |
51 | 5.09M | IRB_GENERATE(PI, SCPortIntervalNode, irb, SCPortIntervalCompareAndUpdate); Line | Count | Source | 51 | | IRB_GENERATE(PI, SCPortIntervalNode, irb, SCPortIntervalCompareAndUpdate); |
Line | Count | Source | 51 | | IRB_GENERATE(PI, SCPortIntervalNode, irb, SCPortIntervalCompareAndUpdate); |
Line | Count | Source | 51 | | IRB_GENERATE(PI, SCPortIntervalNode, irb, SCPortIntervalCompareAndUpdate); |
Line | Count | Source | 51 | | IRB_GENERATE(PI, SCPortIntervalNode, irb, SCPortIntervalCompareAndUpdate); |
Unexecuted instantiation: PI_IRB_FIND Unexecuted instantiation: PI_IRB_NFIND Line | Count | Source | 51 | | IRB_GENERATE(PI, SCPortIntervalNode, irb, SCPortIntervalCompareAndUpdate); |
|
52 | 5.09M | |
53 | 5.09M | /** |
54 | 5.09M | * \brief Function to initialize the interval tree. |
55 | 5.09M | * |
56 | 5.09M | * \return Pointer to the newly created interval tree |
57 | 5.09M | */ |
58 | 5.09M | SCPortIntervalTree *SCPortIntervalTreeInit(void) |
59 | 5.09M | { |
60 | 523k | SCPortIntervalTree *it = SCCalloc(1, sizeof(SCPortIntervalTree)); |
61 | 523k | if (it == NULL) { |
62 | 0 | return NULL; |
63 | 0 | } |
64 | | |
65 | 523k | return it; |
66 | 523k | } |
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 | 523k | { |
76 | 523k | SCPortIntervalNode *node = NULL, *safe = NULL; |
77 | 523k | IRB_FOREACH_SAFE(node, PI, &it->tree, safe) |
78 | 229k | { |
79 | 229k | SigGroupHeadFree(de_ctx, node->sh); |
80 | 229k | PI_IRB_REMOVE(&it->tree, node); |
81 | 229k | SCFree(node); |
82 | 229k | } |
83 | 523k | it->head = NULL; |
84 | 523k | } |
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 | 523k | { |
94 | 523k | if (it) { |
95 | 523k | SCPortIntervalNodeFree(de_ctx, it); |
96 | 523k | SCFree(it); |
97 | 523k | } |
98 | 523k | } |
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 | 229k | { |
111 | 229k | DEBUG_VALIDATE_BUG_ON(p->port > p->port2); |
112 | | |
113 | 229k | SCPortIntervalNode *pi = SCCalloc(1, sizeof(*pi)); |
114 | 229k | if (pi == NULL) { |
115 | 0 | return SC_EINVAL; |
116 | 0 | } |
117 | | |
118 | 229k | pi->port = p->port; |
119 | 229k | pi->port2 = p->port2; |
120 | 229k | SigGroupHeadCopySigs(de_ctx, p->sh, &pi->sh); |
121 | | |
122 | 229k | 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 | 229k | return SC_OK; |
128 | 229k | } |
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 | 402k | { |
139 | 402k | DetectPort *cur = (*list)->last; |
140 | 402k | if (cur == NULL) |
141 | 0 | return; |
142 | | |
143 | 402k | DetectPort *prev = (*list)->last->prev; |
144 | 402k | if (prev == NULL) |
145 | 131k | return; |
146 | | |
147 | | /* rulegroup IDs are assigned much later so, compare SGHs */ |
148 | 271k | if (SigGroupHeadEqual(prev->sh, cur->sh)) { |
149 | 187k | if (prev->port2 == (cur->port - 1)) { |
150 | | /* Merge the port objects */ |
151 | 187k | prev->port2 = cur->port2; |
152 | 187k | (*list)->last = prev; |
153 | 187k | (*list)->last->next = NULL; |
154 | 187k | DetectPortFree(de_ctx, cur); |
155 | 187k | } |
156 | 187k | } |
157 | 271k | } |
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 | 1.78M | { |
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 | 1.78M | if (port <= ptr->port2 && ptr->port <= port2) { |
192 | 638k | return true; |
193 | 638k | } |
194 | | |
195 | 1.14M | SCLogDebug("No overlap found for [%d, %d] w [%d, %d]", port, port2, ptr->port, ptr->port2); |
196 | 1.14M | return false; |
197 | 1.78M | } |
198 | | |
199 | 146k | #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 | 146k | { |
219 | 146k | DetectPort *new_port = NULL; |
220 | 146k | int stack_depth = 0; |
221 | 146k | SCPortIntervalNode **stack = |
222 | 146k | (SCPortIntervalNode **)SCCalloc(STACK_SIZE, sizeof(SCPortIntervalNode *)); |
223 | 146k | if (stack == NULL) |
224 | 0 | return; |
225 | 146k | SCPortIntervalNode *current = root; |
226 | 146k | int stack_size = STACK_SIZE; |
227 | | |
228 | 674k | while (current || stack_depth) { |
229 | 1.06M | while (current != NULL) { |
230 | 619k | if (current->max < port) { |
231 | 91.2k | current = NULL; |
232 | 91.2k | break; |
233 | 91.2k | } |
234 | 528k | const bool is_overlap = SCPortIntervalIsOverlap(port, port2, current); |
235 | | |
236 | 528k | if (is_overlap && (new_port == NULL)) { |
237 | | /* Allocate memory for port obj only if it's first overlap */ |
238 | 145k | new_port = DetectPortInit(); |
239 | 145k | if (new_port == NULL) { |
240 | 0 | goto error; |
241 | 0 | } |
242 | | |
243 | 145k | SCLogDebug("Found overlaps for [%u:%u], creating new port", port, port2); |
244 | 145k | new_port->port = port; |
245 | 145k | new_port->port2 = port2; |
246 | 145k | 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 | 145k | if (*list == NULL) { |
252 | 52.1k | *list = new_port; |
253 | 52.1k | (*list)->last = new_port; |
254 | 93.6k | } else if (((*list)->last->port != new_port->port) && |
255 | 93.6k | ((*list)->last->port2 != new_port->port2)) { |
256 | 93.6k | DEBUG_VALIDATE_BUG_ON(new_port->port < (*list)->last->port); |
257 | 93.6k | (*list)->last->next = new_port; |
258 | 93.6k | new_port->prev = (*list)->last; |
259 | 93.6k | (*list)->last = new_port; |
260 | 93.6k | } else { |
261 | 0 | SCLogDebug("Port already exists in the list"); |
262 | 0 | goto error; |
263 | 0 | } |
264 | 382k | } else if (is_overlap && (new_port != NULL)) { |
265 | 69.0k | SCLogDebug("Found overlaps for [%u:%u], adding sigs", port, port2); |
266 | | /* Only copy the relevant SGHs on later overlaps */ |
267 | 69.0k | SigGroupHeadCopySigs(de_ctx, current->sh, &new_port->sh); |
268 | 69.0k | } |
269 | 528k | stack[stack_depth++] = current; |
270 | 528k | 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 | 528k | current = IRB_LEFT(current, irb); |
281 | 528k | } |
282 | | |
283 | 536k | if (stack_depth == 0) { |
284 | 7.70k | SCLogDebug("Stack depth was exhausted"); |
285 | 7.70k | break; |
286 | 7.70k | } |
287 | | |
288 | 528k | SCPortIntervalNode *popped = stack[stack_depth - 1]; |
289 | 528k | stack_depth--; |
290 | 528k | BUG_ON(popped == NULL); |
291 | 528k | current = IRB_RIGHT(popped, irb); |
292 | 528k | } |
293 | 146k | if (new_port != NULL) |
294 | 145k | SCPortIntervalSanitizeList(de_ctx, list); |
295 | 146k | if (stack != NULL) |
296 | 146k | SCFree(stack); |
297 | 146k | 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 | return; |
304 | 146k | } |
305 | | |
306 | | /** |
307 | | * \brief Callee function to find all overlapping port ranges as asked |
308 | | * by the detection engine during Stage 2 of signature grouping. |
309 | | * |
310 | | * \param de_ctx Detection Engine Context |
311 | | * \param port Given low port |
312 | | * \param port2 Given high port |
313 | | * \param head Pointer to the head of the tree named PI |
314 | | * \param list Pointer to the list of port objects that needs to be filled/updated |
315 | | */ |
316 | | void SCPortIntervalFindOverlappingRanges(DetectEngineCtx *de_ctx, const uint16_t port, |
317 | | const uint16_t port2, const struct PI *head, DetectPort **list) |
318 | 404k | { |
319 | 404k | if (head == NULL) { |
320 | 0 | SCLogDebug("Tree head should not be NULL. Nothing to do further."); |
321 | 0 | return; |
322 | 0 | } |
323 | 404k | SCPortIntervalNode *ptr = IRB_ROOT(head); |
324 | 404k | SCLogDebug("Finding overlaps for the range [%d, %d]", port, port2); |
325 | 404k | SCPortIntervalFindOverlaps(de_ctx, port, port2, ptr, list); |
326 | 404k | } |