]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Pull request #4275: SSE: ips content update
authorYehor Velykozhon -X (yvelykoz - SOFTSERVE INC at Cisco) <yvelykoz@cisco.com>
Wed, 8 May 2024 05:03:54 +0000 (05:03 +0000)
committerSteve Chew (stechew) <stechew@cisco.com>
Wed, 8 May 2024 05:03:54 +0000 (05:03 +0000)
Merge in SNORT/snort3 from ~YVELYKOZ/snort3:sse_content_latest to master

Squashed commit of the following:

commit d5b93ee0683a3bcebd606fc8b3a9bb10b9de5948
Author: Yehor Velykozhon <yvelykoz@cisco.com>
Date:   Fri May 3 17:05:38 2024 +0300

    framework: bump API version

commit 8633039465df577b358687a840f208a29ec15376
Author: Yehor Velykozhon <yvelykoz@cisco.com>
Date:   Tue Apr 9 16:19:29 2024 +0300

    detection: introduce re-evaluation of ips content in next packet

src/detection/detection_continuation.h
src/framework/base_api.h
src/framework/cursor.cc
src/framework/cursor.h
src/ips_options/ips_content.cc

index a3ef4330ab54ba17d3fa78fd7da141410825e814..743a1fb7a43809e53e83ee7c35a8dfbd69ec0ac2 100644 (file)
@@ -64,18 +64,18 @@ private:
     struct State
     {
         State() : data(), root(), selector(nullptr), node(nullptr), waypoint(0),
-            original_waypoint(0), sid(0), packet_number(0), opt_parent(false)
+            original_waypoint(0), delta(0), sid(0), packet_number(0), opt_parent(false), re_eval(false)
         {
             for (uint8_t i = 0; i < NUM_IPS_OPTIONS_VARS; ++i)
                 byte_extract_vars[i] = 0;
         }
 
         State(const detection_option_tree_node_t& n, const detection_option_eval_data_t& d,
-            snort::IpsOption* s, unsigned wp, uint64_t id, bool p) : data(d),
+            snort::IpsOption* s, unsigned wp, unsigned dt, uint64_t id, bool p, bool r_e) : data(d),
             root(1, d.otn),
             selector(s), node(const_cast<detection_option_tree_node_t*>(&n)), waypoint(wp),
-            original_waypoint(wp), sid(id), packet_number(d.p->context->packet_number),
-            opt_parent(p)
+            original_waypoint(wp), delta(dt), sid(id), packet_number(d.p->context->packet_number),
+            opt_parent(p), re_eval(r_e)
         {
             for (uint8_t i = 0; i < NUM_IPS_OPTIONS_VARS; ++i)
                 snort::GetVarValueByIndex(&byte_extract_vars[i], i);
@@ -91,10 +91,12 @@ private:
         detection_option_tree_node_t* node;
         unsigned waypoint;
         const unsigned original_waypoint;
+        unsigned delta;
         uint64_t sid;
         uint64_t packet_number;
         uint32_t byte_extract_vars[NUM_IPS_OPTIONS_VARS];
         bool opt_parent;
+        bool re_eval;
     };
 
     using LState = snort::GroupedList<State>;
@@ -221,6 +223,7 @@ bool Continuation::State::eval(snort::Packet& p)
     }
 
     cursor.set_pos(waypoint);
+    cursor.set_delta(delta);
 
     if (cursor.awaiting_data(true) or cursor.size() == 0)
     {
@@ -246,14 +249,22 @@ bool Continuation::State::eval(snort::Packet& p)
 
     const detection_option_tree_node_t* root_node = root.children[0];
 
-    if (opt_parent)
+    cursor.set_re_eval(re_eval);
+
+    if (!opt_parent)
     {
-        for (int i = 0; i < root_node->num_children; ++i)
-            result += detection_option_node_evaluate(root_node->children[i], data, cursor);
+        assert(!re_eval);
+        result = detection_option_node_evaluate(root_node, data, cursor);
     }
-    else
+    else if (re_eval)
     {
         result = detection_option_node_evaluate(root_node, data, cursor);
+        root_node->state[snort::get_instance_id()].last_check.ts = {};
+    }
+    else
+    {
+        for (int i = 0; i < root_node->num_children; ++i)
+            result += detection_option_node_evaluate(root_node->children[i], data, cursor);
     }
 
     if (data.leaf_reached and !data.otn->sigInfo.file_id)
@@ -287,6 +298,7 @@ void Continuation::add(const Cursor& cursor,
     auto selector = data.buf_selector;
     auto pos = cursor.get_next_pos();
     auto sid = cursor.id();
+    auto delta = cursor.get_delta();
     auto nst = node.state + snort::get_instance_id();
     assert(nst);
 
@@ -301,7 +313,7 @@ void Continuation::add(const Cursor& cursor,
     if (states_cnt < states_cnt_max)
     {
         ++states_cnt;
-        new LState(states, (LState*&)nst->conts, node, data, selector, pos, sid, opt_parent);
+        new LState(states, (LState*&)nst->conts, node, data, selector, pos, delta, sid, opt_parent, cursor.is_re_eval());
     }
     else
     {
@@ -316,7 +328,7 @@ void Continuation::add(const Cursor& cursor,
             st->leave_group();
         delete st;
 
-        new LState(states, (LState*&)nst->conts, node, data, selector, pos, sid, opt_parent);
+        new LState(states, (LState*&)nst->conts, node, data, selector, pos, delta, sid, opt_parent, cursor.is_re_eval());
     }
 
     snort::pc.cont_creations++;
index f2ca5bf8ccb15536ad9462affc2d60a7220e218c..d50853ef2076c6ecbfdfb27f098a41dce66bd7b1 100644 (file)
@@ -29,7 +29,7 @@
 
 // this is the current version of the base api
 // must be prefixed to subtype version
-#define BASE_API_VERSION 18
+#define BASE_API_VERSION 19
 
 // set options to API_OPTIONS to ensure compatibility
 #ifndef API_OPTIONS
index 5a280fdc2f20eea7d0431eb51e631e58696c4601..ca70efbb7a1e9eb336c69c362d638c47770554af 100644 (file)
@@ -49,6 +49,10 @@ Cursor::Cursor(const Cursor& rhs)
     extensible = rhs.extensible;
     buf_id = rhs.buf_id;
     is_accumulated = rhs.is_accumulated;
+    re_eval = rhs.re_eval;
+
+    if (re_eval)
+        delta = rhs.delta;
 
     if (rhs.data)
     {
@@ -234,6 +238,79 @@ TEST_CASE("Boundaries", "[cursor]")
         CHECK(r1);
         CHECK(!r2);
     }
+
+    SECTION("Delta")
+    {
+        SECTION("Set delta in bounds")
+        {
+            bool r1 = false;
+            unsigned d1 = sizeof(buf_1) - 1;
+
+            cursor.set("1", buf_1, sizeof(buf_1), true);
+            r1 = cursor.set_delta(d1);
+
+            CHECK(r1);
+            CHECK(cursor.get_delta() == d1);
+        }
+        SECTION("Set delta as buffer size")
+        {
+            bool r1 = false;
+            unsigned d1 = sizeof(buf_1);
+
+            cursor.set("1", buf_1, sizeof(buf_1), true);
+            r1 = cursor.set_delta(d1);
+
+            CHECK(r1);
+            CHECK(cursor.get_delta() == d1);
+        }
+        SECTION("Set delta bigger than buffer size")
+        {
+            bool r1 = false;
+            unsigned d1 = sizeof(buf_1) + 1;
+
+            cursor.set("1", buf_1, sizeof(buf_1), true);
+            r1 = cursor.set_delta(d1);
+
+            CHECK(!r1);
+            CHECK(cursor.get_delta() == d1);
+        }
+        SECTION("Set delta as negative")
+        {
+            bool r1 = false;
+            unsigned d1 = -1;
+
+            cursor.set("1", buf_1, sizeof(buf_1), true);
+            r1 = cursor.set_delta(d1);
+
+            CHECK(!r1);
+            CHECK(cursor.get_delta() == d1);
+        }
+        SECTION("Copy constructor")
+        {
+            SECTION("re_eval false")
+            {
+                cursor.set("1", buf_1, sizeof(buf_1), true);
+                cursor.set_delta(sizeof(buf_1) - 1);
+                cursor.set_re_eval(false);
+                unsigned r = Cursor(cursor).get_delta();
+                CHECK(r != cursor.get_delta());
+                CHECK(r == 0);
+            }
+            SECTION("re_eval true")
+            {
+                cursor.set("1", buf_1, sizeof(buf_1), true);
+                unsigned d1 = sizeof(buf_1) - 1;
+                cursor.set_delta(d1);
+                cursor.set_re_eval(true);
+
+                Cursor c = Cursor(cursor);
+                CHECK(c.get_delta() == cursor.get_delta());
+                CHECK(c.get_delta() == d1);
+                bool r1 = c.is_re_eval();
+                CHECK(r1);
+            }
+        }
+    }
 }
 
 #endif
index 5772aba5562afc46ab57547296aa1b814c29cf81..39e55e5b3129081334df7f04516ea6221701ca83 100644 (file)
@@ -148,6 +148,7 @@ public:
     bool set_pos(unsigned n)
     {
         current_pos = n;
+        re_eval = false;
         return !(current_pos > buf_size);
     }
 
@@ -175,10 +176,8 @@ public:
 
     bool set_delta(unsigned n)
     {
-        if (n > buf_size)
-            return false;
         delta = n;
-        return true;
+        return n <= buf_size;
     }
 
     void set_data(CursorData* cd);
@@ -195,6 +194,12 @@ public:
         return current_pos - buf_size;
     }
 
+    bool is_re_eval() const
+    { return re_eval; }
+
+    void set_re_eval(bool val)
+    { re_eval = val; }
+
     typedef std::vector<CursorData*> CursorDataVec;
 
 private:
@@ -208,6 +213,7 @@ private:
     bool extensible = false;       // if the buffer could have more data in a continuation
     uint64_t buf_id = 0;           // source buffer ID
     bool is_accumulated = false;
+    bool re_eval = false;
 };
 
 #endif
index 3eec1f8d1fab3cd0e6eb055df789bad383b070e7..a81da92680b407de52a35715aa044f8312ba2f20 100644 (file)
@@ -165,15 +165,24 @@ protected:
     ContentData* config;
 };
 
-bool ContentOption::retry(Cursor& c, const Cursor&)
+static inline bool retry(const PatternMatchData& pmd, const Cursor& c)
 {
-    if ( config->pmd.is_negated() )
+    if ( pmd.is_negated() )
+        return false;
+
+    // since we're already out-of-bound, no reason to retry
+    if ( c.get_pos() >= c.size() )
         return false;
 
-    if ( !config->pmd.depth )
+    if ( !pmd.depth )
         return true;
 
-    return c.get_delta() + config->pmd.pattern_size <= config->pmd.depth;
+    return c.get_delta() + pmd.pattern_size <= pmd.depth;
+}
+
+bool ContentOption::retry(Cursor& c, const Cursor&)
+{
+    return ::retry(config->pmd, c);
 }
 
 uint32_t ContentOption::hash() const
@@ -275,7 +284,7 @@ static bool uniSearchReal(ContentData* cd, Cursor& c)
 {
     // byte_extract variables are strictly unsigned, used for sizes and forward offsets
     // converting from uint32_t to int64_t ensures all extracted values remain positive
-    int64_t offset, depth;
+    int64_t offset, depth, orig_depth;
 
     if (cd->offset_var >= 0 && cd->offset_var < NUM_IPS_OPTIONS_VARS)
     {
@@ -290,10 +299,10 @@ static bool uniSearchReal(ContentData* cd, Cursor& c)
     {
         uint32_t extract;
         GetVarValueByIndex(&extract, cd->depth_var);
-        depth = extract;
+        orig_depth = depth = extract;
     }
     else
-        depth = cd->pmd.depth;
+        orig_depth = depth = cd->pmd.depth;
 
     uint32_t file_pos = c.get_file_pos();
 
@@ -306,7 +315,7 @@ static bool uniSearchReal(ContentData* cd, Cursor& c)
 
     int64_t pos = 0;
 
-    if ( !c.get_delta() )
+    if ( !c.get_delta() and !c.is_re_eval() )
     {
         // first - adjust from cursor or buffer start
         pos = (cd->pmd.is_relative() ? c.get_pos() : 0) + offset;
@@ -331,8 +340,48 @@ static bool uniSearchReal(ContentData* cd, Cursor& c)
             return false;
     }
 
-    if ( ( cd->depth_configured and depth <= 0 ) or pos + cd->pmd.pattern_size > c.size() )
+    if ( (cd->depth_configured and depth <= 0) or !c.size() )
+        return false;
+
+    unsigned last_position = c.size() - 1;
+
+    if ( last_position < pos )
+    {
+        if ( !cd->pmd.is_negated() )
+        {
+            unsigned next_pkt_pos = pos - last_position + c.size();
+
+            c.set_pos(next_pkt_pos);
+            c.set_re_eval(true);
+        }
+
+        return false;
+    }
+
+    unsigned min_bytes_to_match = pos + cd->pmd.pattern_size;
+
+    if ( min_bytes_to_match > c.size() )
+    {
+        constexpr unsigned current_byte_evaluated = 1;
+        unsigned depth_skipped = last_position - pos + current_byte_evaluated;
+
+        if ( !cd->pmd.is_negated() && depth >= depth_skipped + cd->pmd.pattern_size )
+        {
+            assert(cd->pmd.pattern_size >= depth_skipped);
+
+            // IPS content takes into account repeated parts of
+            // the pattern during retries. But in next PDU, such influence
+            // is harmful, so some extra bytes are needed
+            unsigned extra_offset = cd->pmd.pattern_size - cd->match_delta;
+            unsigned next_pkt_pos = extra_offset + c.size();
+
+            c.set_pos(next_pkt_pos);
+            c.set_re_eval(true);
+            c.set_delta(depth_skipped);
+        }
+
         return false;
+    }
 
     int64_t bytes_left = c.size() - pos;
 
@@ -355,6 +404,24 @@ static bool uniSearchReal(ContentData* cd, Cursor& c)
 
         return true;
     }
+    else if ( cd->depth_configured )
+    {
+        unsigned used_depth = depth + c.get_delta();
+
+        if ( orig_depth <= used_depth + cd->pmd.pattern_size )
+            return false;
+
+        // Continuation should be created only on the last evaluation of current node,
+        // where node means current ips option during detection process
+        if ( c.get_delta() == 0 and !c.is_re_eval() and !retry(cd->pmd, c) )
+            return false;
+
+        unsigned next_pkt_pos = c.size() + 0;
+
+        c.set_pos(next_pkt_pos);
+        c.set_re_eval(true);
+        c.set_delta(used_depth);
+    }
 
     return false;
 }