From: Shivani Bhardwaj Date: Fri, 16 Feb 2024 08:07:23 +0000 (+0530) Subject: util/interval-tree: add utility fns X-Git-Tag: suricata-7.0.6~52 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d533a2ca91ecba7bfce38590ed3614a25e00ec4a;p=thirdparty%2Fsuricata.git util/interval-tree: add utility fns Add new utility files to deal with the interval trees. These cover the basic ops: 1. Creation/Destruction of the tree 2. Creation/Destruction of the nodes It also adds the support for finding overlaps for a given set of ports. This function is used by the detection engine is the Stage 2 of signature preparation. Ticket 6792 Bug 6414 Co-authored-by: Victor Julien (cherry picked from commit 54558f1b4acd5983d332864acc049216b9915210) --- diff --git a/src/Makefile.am b/src/Makefile.am index 3737ab444d..d885a8b7ea 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -623,6 +623,7 @@ noinst_HEADERS = \ util-validate.h \ util-var.h \ util-var-name.h \ + util-port-interval-tree.h \ win32-misc.h \ win32-service.h \ win32-syscall.h \ @@ -1228,6 +1229,7 @@ libsuricata_c_a_SOURCES = \ util-unittest-helper.c \ util-var.c \ util-var-name.c \ + util-port-interval-tree.c \ win32-misc.c \ win32-service.c \ win32-syscall.c diff --git a/src/detect-engine-port.h b/src/detect-engine-port.h index 490409d53d..641a4e2385 100644 --- a/src/detect-engine-port.h +++ b/src/detect-engine-port.h @@ -24,6 +24,34 @@ #ifndef __DETECT_PORT_H__ #define __DETECT_PORT_H__ +#include "interval-tree.h" +#include "detect.h" + +typedef struct SCPortIntervalNode { + uint16_t port; /* low port of a port range */ + uint16_t port2; /* high port of a port range */ + uint16_t max; /* max value of the high port in the subtree rooted at this node */ + + struct SigGroupHead_ *sh; /* SGHs corresponding to this port */ + + IRB_ENTRY(SCPortIntervalNode) irb; /* parent entry of the interval tree */ +} SCPortIntervalNode; + +IRB_HEAD(PI, SCPortIntervalNode); /* head of the interval tree */ +IRB_PROTOTYPE(PI, SCPortIntervalNode, irb, + SCPortIntervalCompare); /* prototype definition of the interval tree */ + +typedef struct SCPortIntervalTree_ { + struct PI tree; + SCPortIntervalNode *head; +} SCPortIntervalTree; + +SCPortIntervalTree *SCPortIntervalTreeInit(void); +void SCPortIntervalTreeFree(DetectEngineCtx *, SCPortIntervalTree *); +int SCPortIntervalInsert(DetectEngineCtx *, SCPortIntervalTree *, const DetectPort *); +void SCPortIntervalFindOverlappingRanges( + DetectEngineCtx *, const uint16_t, const uint16_t, const struct PI *, DetectPort **); + /* prototypes */ int DetectPortParse(const DetectEngineCtx *, DetectPort **head, const char *str); diff --git a/src/detect.h b/src/detect.h index 145e20d3cd..c26cdeaeb9 100644 --- a/src/detect.h +++ b/src/detect.h @@ -229,6 +229,7 @@ typedef struct DetectPort_ { struct DetectPort_ *prev; struct DetectPort_ *next; + struct DetectPort_ *last; /* Pointer to the last node in the list */ } DetectPort; /* Signature flags */ diff --git a/src/util-port-interval-tree.c b/src/util-port-interval-tree.c new file mode 100644 index 0000000000..fd49465764 --- /dev/null +++ b/src/util-port-interval-tree.c @@ -0,0 +1,293 @@ +/* Copyright (C) 2024 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Shivani Bhardwaj + */ + +#include "util-port-interval-tree.h" +#include "util-validate.h" +#include "detect-engine-siggroup.h" +#include "detect-engine-port.h" + +/** + * \brief Function to compare two interval nodes. This defines the order + * of insertion of a node in the interval tree. This also updates + * the max attribute of any node in a given tree if needed. + * + * \param a First node to compare + * \param b Second node to compare + * + * \return 1 if low of node a is bigger, -1 otherwise + */ +static int SCPortIntervalCompareAndUpdate(const SCPortIntervalNode *a, SCPortIntervalNode *b) +{ + if (a->port2 > b->max) { + b->max = a->port2; + } + if (a->port >= b->port) { + SCReturnInt(1); + } + SCReturnInt(-1); +} + +IRB_GENERATE(PI, SCPortIntervalNode, irb, SCPortIntervalCompareAndUpdate); + +/** + * \brief Function to initialize the interval tree. + * + * \return Pointer to the newly created interval tree + */ +SCPortIntervalTree *SCPortIntervalTreeInit(void) +{ + SCPortIntervalTree *it = SCCalloc(1, sizeof(SCPortIntervalTree)); + if (it == NULL) { + return NULL; + } + + return it; +} + +/** + * \brief Helper function to free a given node in the interval tree. + * + * \param de_ctx Detection Engine Context + * \param it Pointer to the interval tree + */ +static void SCPortIntervalNodeFree(DetectEngineCtx *de_ctx, SCPortIntervalTree *it) +{ + SCPortIntervalNode *node = NULL, *safe = NULL; + IRB_FOREACH_SAFE(node, PI, &it->tree, safe) + { + SigGroupHeadFree(de_ctx, node->sh); + PI_IRB_REMOVE(&it->tree, node); + SCFree(node); + } + it->head = NULL; +} + +/** + * \brief Function to free an entire interval tree. + * + * \param de_ctx Detection Engine Context + * \param it Pointer to the interval tree + */ +void SCPortIntervalTreeFree(DetectEngineCtx *de_ctx, SCPortIntervalTree *it) +{ + if (it) { + SCPortIntervalNodeFree(de_ctx, it); + SCFree(it); + } +} + +/** + * \brief Function to insert a node in the interval tree. + * + * \param de_ctx Detection Engine Context + * \param it Pointer to the interval tree + * \param p Pointer to a DetectPort object + * + * \return SC_OK if the node was inserted successfully, SC_EINVAL otherwise + */ +int SCPortIntervalInsert(DetectEngineCtx *de_ctx, SCPortIntervalTree *it, const DetectPort *p) +{ + DEBUG_VALIDATE_BUG_ON(p->port > p->port2); + + SCPortIntervalNode *pi = SCCalloc(1, sizeof(*pi)); + if (pi == NULL) { + return SC_EINVAL; + } + + pi->port = p->port; + pi->port2 = p->port2; + SigGroupHeadCopySigs(de_ctx, p->sh, &pi->sh); + + if (PI_IRB_INSERT(&it->tree, pi) != NULL) { + SCLogDebug("Node wasn't added to the tree: port: %d, port2: %d", pi->port, pi->port2); + SCFree(pi); + return SC_EINVAL; + } + return SC_OK; +} + +/** + * \brief Function to check if a port range overlaps with a given set of ports + * + * \param port Given low port + * \param port2 Given high port + * \param ptr Pointer to the node in the tree to be checked against + * + * \return true if an overlaps was found, false otherwise + */ +static bool SCPortIntervalIsOverlap( + const uint16_t port, const uint16_t port2, const SCPortIntervalNode *ptr) +{ + /* Two intervals i and i' are said to overlap if + * - i (intersection) i' != NIL + * - i.low <= i'.high + * - i'.low <= i.high + * + * There are four possible cases of overlaps as shown below which + * are all covered by the if condition that follows. + * + * Case 1: [.........] i + * [...................] i' + * + * Case 2: [...................] i + * [.........] i' + * + * Case 3: [........] i + * [..............] i' + * + * Case 4: [..............] i + * [.............] i' + */ + if (port <= ptr->port2 && ptr->port <= port2) { + return true; + } + + SCLogDebug("No overlap found for [%d, %d] w [%d, %d]", port, port2, ptr->port, ptr->port2); + return false; +} + +#define STACK_SIZE 100 + +/** + * \brief Function to find all the overlaps of given ports with the existing + * port ranges in the interval tree. This function takes in a low and + * a high port, considers it a continuos range and tries to match it + * against all the given port ranges in the interval tree. This search + * for overlap happens in min(O(k*log(n)), O(n*n)) time where, + * n = number of nodes in the tree, and, + * k = number of intervals with which an overlap was found + * + * \param de_ctx Detection Engine Context + * \param port Given low port + * \param port2 Given high port + * \param ptr Pointer to the root of the tree + * \param list A list of DetectPort objects to be filled + */ +static void SCPortIntervalFindOverlaps(DetectEngineCtx *de_ctx, const uint16_t port, + const uint16_t port2, SCPortIntervalNode *root, DetectPort **list) +{ + DetectPort *new_port = NULL; + int stack_depth = 0; + SCPortIntervalNode **stack = + (SCPortIntervalNode **)SCCalloc(STACK_SIZE, sizeof(SCPortIntervalNode *)); + if (stack == NULL) + return; + SCPortIntervalNode *current = root; + int stack_size = STACK_SIZE; + + while (current || stack_depth) { + while (current != NULL) { + if (current->max < port) { + current = NULL; + break; + } + const bool is_overlap = SCPortIntervalIsOverlap(port, port2, current); + + if (is_overlap && (new_port == NULL)) { + /* Allocate memory for port obj only if it's first overlap */ + new_port = DetectPortInit(); + if (new_port == NULL) { + goto error; + } + + SCLogDebug("Found overlaps for [%u:%u], creating new port", port, port2); + new_port->port = port; + new_port->port2 = port2; + SigGroupHeadCopySigs(de_ctx, current->sh, &new_port->sh); + + /* Since it is guaranteed that the ports received by this stage + * will be sorted, insert any new ports to the end of the list + * and avoid walking the entire list */ + if (*list == NULL) { + *list = new_port; + (*list)->last = new_port; + } else if (((*list)->last->port != new_port->port) && + ((*list)->last->port2 != new_port->port)) { + DEBUG_VALIDATE_BUG_ON(new_port->port < (*list)->last->port); + (*list)->last->next = new_port; + (*list)->last = new_port; + } else { + SCLogDebug("Port already exists in the list"); + goto error; + } + } else if (is_overlap && (new_port != NULL)) { + SCLogDebug("Found overlaps for [%u:%u], adding sigs", port, port2); + /* Only copy the relevant SGHs on later overlaps */ + SigGroupHeadCopySigs(de_ctx, current->sh, &new_port->sh); + } + stack[stack_depth++] = current; + if (stack_depth == stack_size) { + SCLogDebug("Stack depth %d maxed out, realloc'ing..", stack_depth); + stack_size *= 2; + void *tmp = SCRealloc(stack, stack_size * sizeof(SCPortIntervalNode *)); + if (tmp == NULL) { + SCLogError("Couldn't realloc the interval tree stack"); + goto error; + } + stack = tmp; + } + current = IRB_LEFT(current, irb); + } + + if (stack_depth == 0) { + SCLogDebug("Stack depth was exhausted"); + break; + } + + SCPortIntervalNode *popped = stack[stack_depth - 1]; + stack_depth--; + BUG_ON(popped == NULL); + current = IRB_RIGHT(popped, irb); + } + if (stack != NULL) + SCFree(stack); + return; +error: + if (new_port != NULL) + DetectPortFree(de_ctx, new_port); + if (stack != NULL) + SCFree(stack); + return; +} + +/** + * \brief Callee function to find all overlapping port ranges as asked + * by the detection engine during Stage 2 of signature grouping. + * + * \param de_ctx Detection Engine Context + * \param port Given low port + * \param port2 Given high port + * \param head Pointer to the head of the tree named PI + * \param list Pointer to the list of port objects that needs to be filled/updated + */ +void SCPortIntervalFindOverlappingRanges(DetectEngineCtx *de_ctx, const uint16_t port, + const uint16_t port2, const struct PI *head, DetectPort **list) +{ + if (head == NULL) { + SCLogDebug("Tree head should not be NULL. Nothing to do further."); + return; + } + SCPortIntervalNode *ptr = IRB_ROOT(head); + SCLogDebug("Finding overlaps for the range [%d, %d]", port, port2); + SCPortIntervalFindOverlaps(de_ctx, port, port2, ptr, list); +} diff --git a/src/util-port-interval-tree.h b/src/util-port-interval-tree.h new file mode 100644 index 0000000000..0eda61b72a --- /dev/null +++ b/src/util-port-interval-tree.h @@ -0,0 +1,29 @@ +/* Copyright (C) 2024 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Shivani Bhardwaj + */ + +#ifndef __UTIL_INTERVAL_TREE_H__ +#define __UTIL_INTERVAL_TREE_H__ + +#include "detect-engine-port.h" + +#endif /* __UTIL_INTERVAL_TREE_H__ */