From: Yehor Velykozhon -X (yvelykoz - SOFTSERVE INC at Cisco) Date: Wed, 8 May 2024 05:03:54 +0000 (+0000) Subject: Pull request #4275: SSE: ips content update X-Git-Tag: 3.2.1.0~5 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5aaf1a304a6c4d2695ea9309af65231ea332a640;p=thirdparty%2Fsnort3.git Pull request #4275: SSE: ips content update Merge in SNORT/snort3 from ~YVELYKOZ/snort3:sse_content_latest to master Squashed commit of the following: commit d5b93ee0683a3bcebd606fc8b3a9bb10b9de5948 Author: Yehor Velykozhon Date: Fri May 3 17:05:38 2024 +0300 framework: bump API version commit 8633039465df577b358687a840f208a29ec15376 Author: Yehor Velykozhon Date: Tue Apr 9 16:19:29 2024 +0300 detection: introduce re-evaluation of ips content in next packet --- diff --git a/src/detection/detection_continuation.h b/src/detection/detection_continuation.h index a3ef4330a..743a1fb7a 100644 --- a/src/detection/detection_continuation.h +++ b/src/detection/detection_continuation.h @@ -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(&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; @@ -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++; diff --git a/src/framework/base_api.h b/src/framework/base_api.h index f2ca5bf8c..d50853ef2 100644 --- a/src/framework/base_api.h +++ b/src/framework/base_api.h @@ -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 diff --git a/src/framework/cursor.cc b/src/framework/cursor.cc index 5a280fdc2..ca70efbb7 100644 --- a/src/framework/cursor.cc +++ b/src/framework/cursor.cc @@ -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 diff --git a/src/framework/cursor.h b/src/framework/cursor.h index 5772aba55..39e55e5b3 100644 --- a/src/framework/cursor.h +++ b/src/framework/cursor.h @@ -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 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 diff --git a/src/ips_options/ips_content.cc b/src/ips_options/ips_content.cc index 3eec1f8d1..a81da9268 100644 --- a/src/ips_options/ips_content.cc +++ b/src/ips_options/ips_content.cc @@ -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; }