]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
util/interval-tree: add utility fns
authorShivani Bhardwaj <shivani@oisf.net>
Fri, 16 Feb 2024 08:07:23 +0000 (13:37 +0530)
committerVictor Julien <victor@inliniac.net>
Fri, 24 May 2024 17:11:03 +0000 (19:11 +0200)
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 <vjulien@oisf.net>
(cherry picked from commit 54558f1b4acd5983d332864acc049216b9915210)

src/Makefile.am
src/detect-engine-port.h
src/detect.h
src/util-port-interval-tree.c [new file with mode: 0644]
src/util-port-interval-tree.h [new file with mode: 0644]

index 3737ab444dfb8c4dcf7b9ca9ff5eb6f5f1afd029..d885a8b7ea6abccae61edc118c09212ee8de2417 100755 (executable)
@@ -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
index 490409d53df2a5ad584cd38daf2f9ef1a0cd90fc..641a4e238503b910f594636a12aa82a76144de14 100644 (file)
 #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);
 
index 145e20d3cd7903d405f61b77208d1bc68e2dbce3..c26cdeaeb97edc568c3c741d9184407e4afcc7e5 100644 (file)
@@ -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 (file)
index 0000000..fd49465
--- /dev/null
@@ -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 <shivani@oisf.net>
+ */
+
+#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 (file)
index 0000000..0eda61b
--- /dev/null
@@ -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 <shivani@oisf.net>
+ */
+
+#ifndef __UTIL_INTERVAL_TREE_H__
+#define __UTIL_INTERVAL_TREE_H__
+
+#include "detect-engine-port.h"
+
+#endif /* __UTIL_INTERVAL_TREE_H__ */