From: Yehor Velykozhon -X (yvelykoz - SOFTSERVE INC at Cisco) Date: Wed, 30 Jul 2025 17:09:15 +0000 (+0000) Subject: Pull request #4827: Refactoring of detection engine core functionality X-Git-Tag: 3.9.3.0~13 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=c67a170d21d84a39149168fd0094d4a014ab0b31;p=thirdparty%2Fsnort3.git Pull request #4827: Refactoring of detection engine core functionality Merge in SNORT/snort3 from ~YVELYKOZ/snort3:de_core_refactoring to master Squashed commit of the following: commit a48bafe1be6519781d05d7bee502fdcb1549b8b8 Author: Yehor Velykozhon Date: Tue Jul 22 12:51:27 2025 +0300 detection: update the authors commit e76f8104df4aae15979cdc502ae32e9b12773c67 Author: Yehor Velykozhon Date: Fri Apr 4 14:54:05 2025 +0300 detection: extract children-related evaluation logic into separated functions commit 2375d55ec15a2ff0a7457b945bdbd437e29a9184 Author: Yehor Velykozhon Date: Thu Aug 29 11:27:49 2024 +0300 detection: extract current node evaluation logic into separated function --- diff --git a/src/detection/detect_trace.cc b/src/detection/detect_trace.cc index 2db91bab1..ccf23ac2d 100644 --- a/src/detection/detect_trace.cc +++ b/src/detection/detect_trace.cc @@ -28,12 +28,15 @@ #include "protocols/packet.h" #include "utils/stats.h" #include "utils/util.h" +#include "utils/util_cstring.h" #include "detection_options.h" +#include "extract.h" #include "fp_create.h" #include "fp_utils.h" #include "ips_context.h" #include "pattern_match_data.h" +#include "treenodes.h" using namespace snort; using namespace std; @@ -139,26 +142,62 @@ void node_eval_trace(const detection_option_tree_node_t* node, const Cursor& cur } } -#else - -void clear_trace_cursor_info() +void ips_variables_trace(const Packet* const p) { -} + if ( !trace_enabled(detection_trace, TRACE_RULE_VARS) ) + return; -void print_pkt_info(Packet*, const char*) -{ -} + char var_buf[100]; + std::string rule_vars; + rule_vars.reserve(sizeof(var_buf)); + uint32_t dbg_extract_vars[]{0,0}; -void print_pattern(const PatternMatchData*, Packet*) -{ -} + for ( unsigned i = 0; i < NUM_IPS_OPTIONS_VARS; ++i ) + { + GetVarValueByIndex(&(dbg_extract_vars[i]), (int8_t)i); + safe_snprintf(var_buf, sizeof(var_buf), "var[%u]=0x%X ", i, dbg_extract_vars[i]); + rule_vars.append(var_buf); + } -void dump_buffer(const uint8_t*, unsigned, Packet*) -{ + debug_logf(detection_trace, TRACE_RULE_VARS, p, "Rule options variables: %s\n", + rule_vars.c_str()); } -void node_eval_trace(const detection_option_tree_node_t*, const Cursor&, Packet*) +void print_option_tree(detection_option_tree_node_t* node, int level) { + if ( !trace_enabled(detection_trace, TRACE_OPTION_TREE) ) + return; + + char buf[32]; + const char* opt; + + if ( node->option_type != RULE_OPTION_TYPE_LEAF_NODE ) + opt = ((IpsOption*)node->option_data)->get_name(); + else + { + const OptTreeNode* otn = (OptTreeNode*)node->option_data; + const SigInfo& si = otn->sigInfo; + snprintf(buf, sizeof(buf), "%u:%u:%u", si.gid, si.sid, si.rev); + opt = buf; + } + + const char* srtn = node->otn ? " (rtn)" : ""; + + debug_logf(detection_trace, TRACE_OPTION_TREE, nullptr, "%3d %3d %p %*s%s\n", + level+1, node->num_children, node->option_data, (int)(level + strlen(opt)), opt, srtn); + + for ( int i=0; inum_children; i++ ) + print_option_tree(node->children[i], level+1); } +#else + +void clear_trace_cursor_info() { } +void print_pkt_info(Packet*, const char*) { } +void print_pattern(const PatternMatchData*, Packet*) { } +void dump_buffer(const uint8_t*, unsigned, Packet*) { } +void node_eval_trace(const detection_option_tree_node_t*, const Cursor&, Packet*) { } +void ips_variables_trace(const Packet* const) { } +void print_option_tree(detection_option_tree_node_t*, int) { } + #endif diff --git a/src/detection/detect_trace.h b/src/detection/detect_trace.h index bc6cd75ac..69c910b94 100644 --- a/src/detection/detect_trace.h +++ b/src/detection/detect_trace.h @@ -56,6 +56,8 @@ void print_pkt_info(snort::Packet* p, const char*); void print_pattern(const PatternMatchData* pmd, snort::Packet*); void dump_buffer(const uint8_t* buff, unsigned len, snort::Packet*); void node_eval_trace(const detection_option_tree_node_t* node, const Cursor& cursor, snort::Packet*); +void ips_variables_trace(const snort::Packet* const p); +void print_option_tree(detection_option_tree_node_t* node, int level); #endif diff --git a/src/detection/detection_options.cc b/src/detection/detection_options.cc index 317b9df8c..eafea7461 100644 --- a/src/detection/detection_options.cc +++ b/src/detection/detection_options.cc @@ -17,14 +17,8 @@ // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. //-------------------------------------------------------------------------- -/* -** @file detection_options.c -** @author Steven Sturges -** @brief Support functions for rule option tree -** -** This implements tree processing for rule options, evaluating common -** detection options only once per pattern match. -*/ +// detection_options.cc author Steve Sturges +// detection_options.cc author Yehor Velykozhon #ifdef HAVE_CONFIG_H #include "config.h" @@ -49,6 +43,7 @@ #include "parser/parser.h" #include "profiler/rule_profiler_defs.h" #include "protocols/packet_manager.h" +#include "time/packet_time.h" #include "utils/util_cstring.h" #include "detection_continuation.h" @@ -74,9 +69,6 @@ struct detection_option_key_t void* option_data; }; -// FIXIT-L find a better place for this -static inline bool operator==(const struct timeval& a, const struct timeval& b) -{ return a.tv_sec == b.tv_sec && a.tv_usec == b.tv_usec; } class DetectionOptionHashKeyOps : public HashKeyOperations { @@ -102,7 +94,7 @@ public: const detection_option_key_t* key1 = (const detection_option_key_t*)k1; const detection_option_key_t* key2 = (const detection_option_key_t*)k2; - assert(key1 && key2); + assert(key1 and key2); if ( key1->option_type != key2->option_type ) return false; @@ -155,7 +147,7 @@ static uint32_t detection_option_tree_hash(detection_option_tree_node_t* node) for ( int i = 0; i < node->num_children; i++) { -#if (defined(__ia64) || defined(__amd64) || defined(_LP64)) +#if (defined(__ia64) or defined(__amd64) or defined(_LP64)) { /* Cleanup warning because of cast from 64bit ptr to 32bit int * warning on 64bit OSs */ @@ -172,13 +164,6 @@ static uint32_t detection_option_tree_hash(detection_option_tree_node_t* node) mix(a,b,c); a += node->children[i]->num_children; mix(a,b,c); -#if 0 - a += (uint32_t)node->children[i]->option_data; - /* Recurse & hash up this guy's children */ - b += detection_option_tree_hash(node->children[i]); - c += node->children[i]->num_children; - mix(a,b,c); -#endif } finalize(a,b,c); @@ -228,7 +213,7 @@ public: bool key_compare(const void* k1, const void* k2, size_t) override { - assert(k1 && k2); + assert(k1 and k2); const detection_option_key_t* key_r = (const detection_option_key_t*)k1; const detection_option_key_t* key_l = (const detection_option_key_t*)k2; @@ -261,12 +246,12 @@ public: }; -static DetectionOptionHash* DetectionHashTableNew() +static DetectionOptionHash* allocate_option_hash_table() { return new DetectionOptionHash(HASH_RULE_OPTIONS, sizeof(detection_option_key_t)); } -static DetectionOptionTreeHash* DetectionTreeHashTableNew() +static DetectionOptionTreeHash* allocate_tree_hash_table() { return new DetectionOptionTreeHash(HASH_RULE_TREE, sizeof(detection_option_key_t)); } @@ -274,7 +259,7 @@ static DetectionOptionTreeHash* DetectionTreeHashTableNew() void* add_detection_option(SnortConfig* sc, option_type_t type, void* option_data) { if ( !sc->detection_option_hash_table ) - sc->detection_option_hash_table = DetectionHashTableNew(); + sc->detection_option_hash_table = allocate_option_hash_table(); detection_option_key_t key; key.option_type = type; @@ -287,45 +272,13 @@ void* add_detection_option(SnortConfig* sc, option_type_t type, void* option_dat return nullptr; } -void print_option_tree(detection_option_tree_node_t* node, int level) -{ -#ifdef DEBUG_MSGS - if ( !trace_enabled(detection_trace, TRACE_OPTION_TREE) ) - return; - - char buf[32]; - const char* opt; - - if ( node->option_type != RULE_OPTION_TYPE_LEAF_NODE ) - opt = ((IpsOption*)node->option_data)->get_name(); - else - { - const OptTreeNode* otn = (OptTreeNode*)node->option_data; - const SigInfo& si = otn->sigInfo; - snprintf(buf, sizeof(buf), "%u:%u:%u", si.gid, si.sid, si.rev); - opt = buf; - } - - const char* srtn = node->otn ? " (rtn)" : ""; - - debug_logf(detection_trace, TRACE_OPTION_TREE, nullptr, "%3d %3d %p %*s%s\n", - level+1, node->num_children, node->option_data, (int)(level + strlen(opt)), opt, srtn); - - for ( int i=0; inum_children; i++ ) - print_option_tree(node->children[i], level+1); -#else - UNUSED(node); - UNUSED(level); -#endif -} - void* add_detection_option_tree(SnortConfig* sc, detection_option_tree_node_t* option_tree) { static std::mutex build_mutex; std::lock_guard lock(build_mutex); if ( !sc->detection_option_tree_hash_table ) - sc->detection_option_tree_hash_table = DetectionTreeHashTableNew(); + sc->detection_option_tree_hash_table = allocate_tree_hash_table(); detection_option_key_t key; key.option_data = (void*)option_tree; @@ -338,60 +291,212 @@ void* add_detection_option_tree(SnortConfig* sc, detection_option_tree_node_t* o return nullptr; } -int detection_option_node_evaluate( - const detection_option_tree_node_t* node, detection_option_eval_data_t& eval_data, - const Cursor& orig_cursor) +static inline bool was_evaluated(const detection_option_tree_node_t* node, + const dot_node_state_t& state, uint64_t cur_eval_context_num, const Packet* p) { - assert(node and eval_data.p); + // FIXIT-P: re-order for non-pcap traffic + add attributes + if ( node->is_relative ) + return false; - node_eval_trace(node, orig_cursor, eval_data.p); + // FIXIT-L: once good order is determined, move to dot_node_state_t::operator== + auto last_check = state.last_check; - auto& state = node->state[get_instance_id()]; - RuleContext profile(state); + if ( last_check.ts.tv_sec != p->pkth->ts.tv_sec or + last_check.ts.tv_usec != p->pkth->ts.tv_usec ) + return false; - uint64_t cur_eval_context_num = eval_data.p->context->context_num; - auto p = eval_data.p; + if ( last_check.run_num != get_run_num() ) + return false; + + if ( last_check.context_num != cur_eval_context_num ) + return false; - // see if evaluated it before ... - if ( !node->is_relative ) + if ( last_check.rebuild_flag != (p->packet_flags & PKT_REBUILT_STREAM) ) + return false; + + if (p->packet_flags & PKT_ALLOW_MULTIPLE_DETECT) + return false; + + if ( last_check.flowbit_failed ) + return false; + + if ( !(p->packet_flags & PKT_IP_RULE_2ND) and !p->is_udp_tunneled() ) { - auto last_check = state.last_check; + debug_log(detection_trace, TRACE_RULE_EVAL, p, + "Was evaluated before, returning last check result\n"); + return true; + } - if ( last_check.ts == p->pkth->ts && - last_check.run_num == get_run_num() && - last_check.context_num == cur_eval_context_num && - last_check.rebuild_flag == (p->packet_flags & PKT_REBUILT_STREAM) && - !(p->packet_flags & PKT_ALLOW_MULTIPLE_DETECT) ) + return false; +} + +static inline bool match_rtn(const detection_option_tree_node_t* node, Packet* p) +{ + assert(p and node); + // FIXIT-L: all those checks could be moved to OptTreeNode + if ( !node->otn or node->otn->sigInfo.file_id ) + return true; + + const SnortProtocolId snort_protocol_id = p->get_snort_protocol_id(); + // FIXIT-L: rework to use bool + int check_ports = 1; + + if ( snort_protocol_id != UNKNOWN_PROTOCOL_ID ) + { + const auto& sig_info = node->otn->sigInfo; + + if ( std::any_of(sig_info.services.cbegin(), sig_info.services.cend(), + [snort_protocol_id] (const SignatureServiceInfo& svc) + { return snort_protocol_id == svc.snort_protocol_id; }) ) + check_ports = 0; + + if ( !sig_info.services.empty() and check_ports ) { - if ( !last_check.flowbit_failed && - !(p->packet_flags & PKT_IP_RULE_2ND) && - !p->is_udp_tunneled() ) - { - debug_log(detection_trace, TRACE_RULE_EVAL, p, - "Was evaluated before, returning last check result\n"); - return last_check.result; - } + debug_logf(detection_trace, TRACE_RULE_EVAL, p, + "SID %u not matched because of service mismatch %d\n", + sig_info.sid, snort_protocol_id); + return false; } } - state.last_check.ts = eval_data.p->pkth->ts; - state.last_check.run_num = get_run_num(); - state.last_check.context_num = cur_eval_context_num; - state.last_check.flowbit_failed = 0; - state.last_check.rebuild_flag = p->packet_flags & PKT_REBUILT_STREAM; + return fp_eval_rtn(getRtnFromOtn(node->otn), p, check_ports); +} - // Save some stuff off for repeated pattern tests - PmdLastCheck* content_last = nullptr; +static inline int match_leaf(const detection_option_tree_node_t* node, + detection_option_eval_data_t& eval_data) +{ + const Packet* p = eval_data.p; + OptTreeNode* otn = (OptTreeNode*)node->option_data; - if ( node->option_type != RULE_OPTION_TYPE_LEAF_NODE ) + if ( otn->detection_filter ) { - IpsOption* opt = (IpsOption*)node->option_data; - PatternMatchData* pmd = opt->get_pattern(0, RULE_WO_DIR); + debug_log(detection_trace, TRACE_RULE_EVAL, p, + "Evaluating detection filter\n"); - if ( pmd and pmd->is_literal() and pmd->last_check ) - content_last = pmd->last_check + get_instance_id(); + if ( detection_filter_test(otn->detection_filter, p) ) + { + debug_log(detection_trace, TRACE_RULE_EVAL, p, + "Header check failed\n"); + + return (int)IpsOption::NO_MATCH; + } } + otn->state[get_instance_id()].matches++; + + eval_data.leaf_reached = 1; + + if ( eval_data.flowbit_noalert ) + return (int)IpsOption::MATCH; + +#ifdef DEBUG_MSGS + const SigInfo& si = otn->sigInfo; + debug_logf(detection_trace, TRACE_RULE_EVAL, p, + "Matched rule gid:sid:rev %u:%u:%u\n", si.gid, si.sid, si.rev); +#endif + + fpAddMatch(p->context->otnx, otn); + + return (int)IpsOption::MATCH; +} + +static inline int match_flowbit(const detection_option_tree_node_t* node, + detection_option_eval_data_t& eval_data, Cursor& cursor) +{ + int rval = node->evaluate(node->option_data, cursor, eval_data.p); + + assert((flowbits_setter(node->option_data) and rval == (int)IpsOption::MATCH) + or !flowbits_setter(node->option_data) or !eval_data.p->flow); + + return rval; +} + +static inline int match_node(const detection_option_tree_node_t* node, + detection_option_eval_data_t& eval_data, Cursor& cursor, + IpsOption*& buf_selector) +{ + IpsOption* opt = (IpsOption*)node->option_data; + if ( opt->is_buffer_setter() ) + buf_selector = opt; + + int rval = node->evaluate(node->option_data, cursor, eval_data.p); + + return rval; +} + +static inline bool skip_on_retry(const detection_option_tree_node_t* node, + const detection_option_tree_node_t* child_node, dot_node_state_t* child_state, + int loop_count, int& result) +{ + assert(loop_count > 0); + assert(child_state); + + if ( child_node->option_type == RULE_OPTION_TYPE_LEAF_NODE ) + return true; + + bool matched_all_suboption = child_state->result == child_node->num_children; + if ( matched_all_suboption ) + return true; + + if ( child_node->option_type != RULE_OPTION_TYPE_CONTENT ) + return false; + + if ( child_state->result != (int)IpsOption::NO_MATCH ) + return false; + + if ( !child_node->is_relative ) + { + // If it's a non-relative content or pcre, no reason + // to check again. Only increment result once. + // Should hit this condition on first loop iteration. + if ( loop_count == 1 ) + ++result; + return true; + } + + IpsOption* opt = (IpsOption*)node->option_data; + if ( opt->is_buffer_setter() ) + return false; + + // FIXIT-L: should be moved to IpsOption option + // Check for an unbounded relative search. If this + // failed before, it's going to fail again so don't + // go down this path again + opt = (IpsOption*)child_node->option_data; + PatternMatchData* pmd = opt->get_pattern(0, RULE_WO_DIR); + + if ( pmd and pmd->is_literal() and pmd->is_unbounded() and !pmd->is_negated() ) + { + // Only increment result once. Should hit this + // condition on first loop iteration + if ( loop_count == 1 ) + ++result; + + return true; + } + + return false; +} + +int detection_option_node_evaluate( + const detection_option_tree_node_t* node, detection_option_eval_data_t& eval_data, + const Cursor& orig_cursor) +{ + assert(node and eval_data.p); + + node_eval_trace(node, orig_cursor, eval_data.p); + + auto& state = node->state[get_instance_id()]; + RuleContext profile(state); + uint64_t cur_eval_context_num = eval_data.p->context->context_num; + auto p = eval_data.p; + + if ( was_evaluated(node, state, cur_eval_context_num, eval_data.p) ) + return state.last_check.result; + + state.last_check.set(*eval_data.p, state.last_check.result); + assert(state.last_check.context_num == eval_data.p->context->context_num); + bool continue_loop = true; int loop_count = 0; @@ -399,119 +504,26 @@ int detection_option_node_evaluate( uint32_t tmp_byte_extract_vars[NUM_IPS_OPTIONS_VARS]; IpsOption* buf_selector = eval_data.buf_selector; Cursor cursor = orig_cursor; - int rval; - // No, haven't evaluated this one before... Check it. do { - rval = (int)IpsOption::NO_MATCH; // FIXIT-L refactor to eliminate casts to int. - if ( node->otn and !node->otn->sigInfo.file_id ) - { - SnortProtocolId snort_protocol_id = p->get_snort_protocol_id(); - int check_ports = 1; - - if ( snort_protocol_id != UNKNOWN_PROTOCOL_ID ) - { - const auto& sig_info = node->otn->sigInfo; + int rval = (int)IpsOption::NO_MATCH; // FIXIT-L refactor to eliminate casts to int. - if ( std::any_of(sig_info.services.cbegin(), sig_info.services.cend(), - [snort_protocol_id] (const SignatureServiceInfo& svc) - { return snort_protocol_id == svc.snort_protocol_id; }) ) - check_ports = 0; - - if ( !sig_info.services.empty() and check_ports ) - { - debug_logf(detection_trace, TRACE_RULE_EVAL, p, - "SID %u not matched because of service mismatch %d\n", - sig_info.sid, snort_protocol_id); - break; // out of case - } - } - - if ( !fp_eval_rtn(getRtnFromOtn(node->otn), p, check_ports) ) - break; - } + if ( !match_rtn(node, p) ) + break; switch ( node->option_type ) { case RULE_OPTION_TYPE_LEAF_NODE: - { - OptTreeNode* otn = (OptTreeNode*)node->option_data; - bool f_result = true; - - if ( otn->detection_filter ) - { - debug_log(detection_trace, TRACE_RULE_EVAL, p, - "Evaluating detection filter\n"); - f_result = !detection_filter_test(otn->detection_filter, - p->ptrs.ip_api.get_src(), p->ptrs.ip_api.get_dst(), - p->pkth->ts.tv_sec); - } - - if ( !f_result ) - { - debug_log(detection_trace, TRACE_RULE_EVAL, p, "Header check failed\n"); - } - else - { - otn->state[get_instance_id()].matches++; - - if ( !eval_data.flowbit_noalert ) - { -#ifdef DEBUG_MSGS - const SigInfo& si = otn->sigInfo; - debug_logf(detection_trace, TRACE_RULE_EVAL, p, - "Matched rule gid:sid:rev %u:%u:%u\n", si.gid, si.sid, si.rev); -#endif - fpAddMatch(p->context->otnx, otn); - } - result = rval = (int)IpsOption::MATCH; - eval_data.leaf_reached = 1; - } - } - break; - - case RULE_OPTION_TYPE_CONTENT: - if ( node->evaluate ) - { - // This will be set in the fast pattern matcher if we found - // a content and the rule option specifies not that - // content. Essentially we've already evaluated this rule - // option via the content option processing since only not - // contents that are not relative in any way will have this - // flag set - if ( content_last ) - { - if ( content_last->ts == p->pkth->ts && - content_last->run_num == get_run_num() && - content_last->context_num == cur_eval_context_num && - content_last->rebuild_flag == (p->packet_flags & PKT_REBUILT_STREAM) ) - { - rval = (int)IpsOption::NO_MATCH; - break; - } - } - rval = node->evaluate(node->option_data, cursor, p); - } + result = rval = match_leaf(node, eval_data); break; case RULE_OPTION_TYPE_FLOWBIT: - if ( node->evaluate ) - { - rval = node->evaluate(node->option_data, cursor, eval_data.p); - assert((flowbits_setter(node->option_data) and rval == (int)IpsOption::MATCH) - or !flowbits_setter(node->option_data) or !eval_data.p->flow); - } + rval = match_flowbit(node, eval_data, cursor); break; default: - if ( node->evaluate ) - { - IpsOption* opt = (IpsOption*)node->option_data; - if ( opt->is_buffer_setter() ) - buf_selector = opt; - rval = node->evaluate(node->option_data, cursor, p); - } + rval = match_node(node, eval_data, cursor, buf_selector); break; } @@ -520,10 +532,11 @@ int detection_option_node_evaluate( debug_log(detection_trace, TRACE_RULE_EVAL, p, "no match\n"); state.last_check.result = result; // See if the option has failed due to incomplete input or its failed evaluation - if (orig_cursor.awaiting_data()) + if ( orig_cursor.awaiting_data() ) Continuation::postpone(orig_cursor, *node, eval_data); else Continuation::postpone(cursor, *node, eval_data); + return result; } if ( rval == (int)IpsOption::FAILED_BIT ) @@ -533,7 +546,8 @@ int detection_option_node_evaluate( // clear the timestamp so failed flowbit gets eval'd again state.last_check.flowbit_failed = 1; state.last_check.result = result; - return 0; + + return (int)IpsOption::NO_MATCH; } // Cache the current flowbit_noalert flag, and set it @@ -545,23 +559,7 @@ int detection_option_node_evaluate( debug_log(detection_trace, TRACE_RULE_EVAL, p, "flowbit no alert\n"); } -#ifdef DEBUG_MSGS - if ( trace_enabled(detection_trace, TRACE_RULE_VARS) ) - { - char var_buf[100]; - std::string rule_vars; - rule_vars.reserve(sizeof(var_buf)); - uint32_t dbg_extract_vars[]{0,0}; - for ( unsigned i = 0; i < NUM_IPS_OPTIONS_VARS; ++i ) - { - GetVarValueByIndex(&(dbg_extract_vars[i]), (int8_t)i); - safe_snprintf(var_buf, sizeof(var_buf), "var[%u]=0x%X ", i, dbg_extract_vars[i]); - rule_vars.append(var_buf); - } - debug_logf(detection_trace, TRACE_RULE_VARS, p, "Rule options variables: %s\n", - rule_vars.c_str()); - } -#endif + ips_variables_trace(p); if ( PacketLatency::fastpath() ) { @@ -572,131 +570,73 @@ int detection_option_node_evaluate( return result; } + // Passed, check the children. + if ( node->num_children ) { - // Passed, check the children. - if ( node->num_children ) - { - // Back up byte_extract vars so they don't get overwritten between rules - // If node has only 1 child - no need to back up on current step - for ( unsigned i = 0; node->num_children > 1 && i < NUM_IPS_OPTIONS_VARS; ++i ) + // Back up byte_extract vars so they don't get overwritten between rules + // If node has only 1 child - no need to back up on current step + for ( unsigned i = 0; node->num_children > 1 and i < NUM_IPS_OPTIONS_VARS; ++i ) GetVarValueByIndex(&(tmp_byte_extract_vars[i]), (int8_t)i); - for ( int i = 0; i < node->num_children; ++i ) - { - detection_option_tree_node_t* child_node = node->children[i]; - dot_node_state_t* child_state = child_node->state + get_instance_id(); - - for ( unsigned j = 0; node->num_children > 1 && j < NUM_IPS_OPTIONS_VARS; ++j ) - SetVarValueByIndex(tmp_byte_extract_vars[j], (int8_t)j); - - if ( loop_count > 0 ) - { - if ( child_state->result == (int)IpsOption::NO_MATCH ) - { - if ( child_node->option_type == RULE_OPTION_TYPE_CONTENT ) - { - if ( !child_node->is_relative ) - { - // If it's a non-relative content or pcre, no reason - // to check again. Only increment result once. - // Should hit this condition on first loop iteration. - if ( loop_count == 1 ) - ++result; - - continue; - } - else - { - IpsOption* opt = (IpsOption*)node->option_data; - - if ( !opt->is_buffer_setter() ) - { - // Check for an unbounded relative search. If this - // failed before, it's going to fail again so don't - // go down this path again - opt = (IpsOption*)child_node->option_data; - PatternMatchData* pmd = opt->get_pattern(0, RULE_WO_DIR); - - if ( pmd and pmd->is_literal() and pmd->is_unbounded() and !pmd->is_negated() ) - { - // Only increment result once. Should hit this - // condition on first loop iteration - if (loop_count == 1) - ++result; - - continue; - } - } - } - } - } - else if ( child_node->option_type == RULE_OPTION_TYPE_LEAF_NODE ) - // Leaf node matched, don't eval again - continue; - - else if ( child_state->result == child_node->num_children ) - // This branch of the tree matched or has options that - // don't need to be evaluated again, so don't need to - // evaluate this option again - continue; - } - - eval_data.buf_selector = buf_selector; - child_state->result = detection_option_node_evaluate( - node->children[i], eval_data, cursor); - - if ( child_node->option_type == RULE_OPTION_TYPE_LEAF_NODE ) - { - // Leaf node won't have any children but will return success - // or failure; regardless we must count them here - result += 1; - } - else if (child_state->result == child_node->num_children) - // Indicate that the child's tree branches are done - ++result; - - if ( PacketLatency::fastpath() ) - { - state.last_check.result = result; - return result; - } - } + for ( int i = 0; i < node->num_children; ++i ) + { + detection_option_tree_node_t* child_node = node->children[i]; + dot_node_state_t* child_state = child_node->state + get_instance_id(); + + for ( unsigned j = 0; node->num_children > 1 and j < NUM_IPS_OPTIONS_VARS; ++j ) + SetVarValueByIndex(tmp_byte_extract_vars[j], (int8_t)j); + + if ( loop_count > 0 and skip_on_retry(node, child_node, child_state, loop_count, result) ) + continue; - // If all children branches matched, we don't need to reeval any of - // the children so don't need to reeval this content/pcre rule - // option at a new offset. - // Else, reset the DOE ptr to last eval for offset/depth, - // distance/within adjustments for this same content/pcre rule option. - // If the node and its sub-tree propagate MATCH back, - // then all its continuations are recalled. - if ( result == node->num_children ) + eval_data.buf_selector = buf_selector; + child_state->result = detection_option_node_evaluate(node->children[i], eval_data, cursor); + + if ( child_node->option_type == RULE_OPTION_TYPE_LEAF_NODE ) { - continue_loop = false; - Continuation::recall(state, p); + // Leaf node won't have any children but will return success + // or failure; regardless we must count them here + result += 1; } + else if ( child_state->result == child_node->num_children ) + // Indicate that the child's tree branches are done + ++result; - if ( eval_data.leaf_reached and !eval_data.otn->sigInfo.file_id and - node->option_type != RULE_OPTION_TYPE_LEAF_NODE and - ((IpsOption*)node->option_data)->is_buffer_setter() ) + if ( PacketLatency::fastpath() ) { - debug_logf(detection_trace, TRACE_BUFFER, p, "Collecting \"%s\" buffer of size %u\n", - cursor.get_name(), cursor.size()); - p->context->matched_buffers.emplace_back(cursor.get_name(), cursor.buffer(), cursor.size()); - pc.buf_dumps++; + state.last_check.result = result; + return result; } + } + + // If all children branches matched, we don't need to reeval any of + // the children so don't need to reeval this content/pcre rule + // option at a new offset. + // Else, reset the DOE ptr to last eval for offset/depth, + // distance/within adjustments for this same content/pcre rule option. + // If the node and its sub-tree propagate MATCH back, + // then all its continuations are recalled. + if ( result == node->num_children ) + { + continue_loop = false; + Continuation::recall(state, p); + } - // Don't need to reset since it's only checked after we've gone - // through the loop at least once and the result will have - // been set again already - //for (i = 0; i < node->num_children; i++) - // node->children[i]->result; + if ( eval_data.leaf_reached and !eval_data.otn->sigInfo.file_id and + node->option_type != RULE_OPTION_TYPE_LEAF_NODE and + ((IpsOption*)node->option_data)->is_buffer_setter() ) + { + debug_logf(detection_trace, TRACE_BUFFER, p, "Collecting \"%s\" buffer of size %u\n", + cursor.get_name(), cursor.size()); + p->context->matched_buffers.emplace_back(cursor.get_name(), cursor.buffer(), cursor.size()); + pc.buf_dumps++; } } // Reset the flowbit_noalert flag in eval data eval_data.flowbit_noalert = tmp_noalert_flag; - if ( continue_loop && rval == (int)IpsOption::MATCH && node->relative_children ) + if ( continue_loop and rval == (int)IpsOption::MATCH and node->relative_children ) { IpsOption* opt = (IpsOption*)node->option_data; continue_loop = opt->retry(cursor); @@ -733,13 +673,13 @@ static void detection_option_node_update_otn_stats(detection_option_tree_node_t* { dot_node_state_t local_stats(node->state[thread_id]); /* cumulative stats for this node */ - if (stats) + if ( stats ) { local_stats.elapsed += stats->elapsed; local_stats.elapsed_match += stats->elapsed_match; local_stats.elapsed_no_match += stats->elapsed_no_match; - if (stats->checks > local_stats.checks) + if ( stats->checks > local_stats.checks ) local_stats.checks = stats->checks; local_stats.latency_suspends += stats->latency_suspends; @@ -756,7 +696,7 @@ static void detection_option_node_update_otn_stats(detection_option_tree_node_t* state.elapsed_match = local_stats.elapsed_match; state.elapsed_no_match = local_stats.elapsed_no_match; - if (local_stats.checks > state.checks) + if ( local_stats.checks > state.checks ) state.checks = local_stats.checks; state.latency_timeouts = local_stats.latency_timeouts; @@ -830,7 +770,7 @@ void free_detection_option_root(void** existing_tree) { detection_option_tree_root_t* root; - if (!existing_tree || !*existing_tree) + if ( !existing_tree or !*existing_tree ) return; root = (detection_option_tree_root_t*)*existing_tree; @@ -840,3 +780,477 @@ void free_detection_option_root(void** existing_tree) *existing_tree = nullptr; } + +//------------------------------------------------------------------------- +// UNIT TESTS +//------------------------------------------------------------------------- + +#ifdef UNIT_TEST + +#include "catch/snort_catch.h" + +#include "filters/sfthd.h" + +void set_was_evaluated(detection_option_tree_node_t& n, Packet* p, + dot_node_state_t& s, uint64_t& c_num) +{ + n.is_relative = false; + auto& last_check = s.last_check; + + if ( !p->pkth ) + p->pkth = new DAQ_PktHdr_t(); + + last_check.ts = p->pkth->ts; + last_check.run_num = get_run_num(); + last_check.context_num = c_num = 2; + last_check.rebuild_flag = p->packet_flags = p->packet_flags | PKT_REBUILT_STREAM; + last_check.flowbit_failed = false; +} + +TEST_CASE("Detection Engine: was_evaluated", "[de_core]") +{ + Packet p; + detection_option_tree_node_t m_node(RULE_OPTION_TYPE_OTHER, nullptr); + dot_node_state_t m_state; + uint64_t m_context_num; + + SECTION("Basic passed case") + { + set_was_evaluated(m_node, &p, m_state, m_context_num); + REQUIRE(true == was_evaluated(&m_node, m_state, m_context_num, &p)); + } + SECTION("Failed due to relative option") + { + set_was_evaluated(m_node, &p, m_state, m_context_num); + m_node.is_relative = true; + REQUIRE(false == was_evaluated(&m_node, m_state, m_context_num, &p)); + } + SECTION("Failed due to timestamp difference") + { + set_was_evaluated(m_node, &p, m_state, m_context_num); + m_state.last_check.ts.tv_sec = 1; + bool fail_due_last_check = false == was_evaluated(&m_node, m_state, m_context_num, &p); + REQUIRE(fail_due_last_check); + + set_was_evaluated(m_node, &p, m_state, m_context_num); + const_cast(p.pkth)->ts.tv_sec = 1; + bool fail_due_packet = false == was_evaluated(&m_node, m_state, m_context_num, &p); + REQUIRE(fail_due_packet); + } + SECTION("Failed due to run number") + { + set_was_evaluated(m_node, &p, m_state, m_context_num); + m_state.last_check.run_num = 1; + bool fail_due_last_check = false == was_evaluated(&m_node, m_state, m_context_num, &p); + REQUIRE(fail_due_last_check); + + set_was_evaluated(m_node, &p, m_state, m_context_num); + int orig_run_num = get_run_num(); + set_run_num(1); + bool fail_due_global_run_num = false == was_evaluated(&m_node, m_state, m_context_num, &p); + REQUIRE(fail_due_global_run_num); + set_run_num(orig_run_num); + } + SECTION("Failed due to context number") + { + set_was_evaluated(m_node, &p, m_state, m_context_num); + m_state.last_check.context_num = 1; + REQUIRE(false == was_evaluated(&m_node, m_state, m_context_num, &p)); + } + SECTION("Failed due to not rebuilt stream packet") + { + set_was_evaluated(m_node, &p, m_state, m_context_num); + p.packet_flags = 0; + REQUIRE(false == was_evaluated(&m_node, m_state, m_context_num, &p)); + } + SECTION("Failed due to multiple detection") + { + set_was_evaluated(m_node, &p, m_state, m_context_num); + p.packet_flags |= PKT_ALLOW_MULTIPLE_DETECT; + REQUIRE(false == was_evaluated(&m_node, m_state, m_context_num, &p)); + } + SECTION("Failed due to failed flowbit in state.last_check") + { + set_was_evaluated(m_node, &p, m_state, m_context_num); + m_state.last_check.flowbit_failed = 1; + REQUIRE(false == was_evaluated(&m_node, m_state, m_context_num, &p)); + } + // FIXIT-L: what is the correct name of this condition? not inner tcp evaluation? + SECTION("Failed due to ip layer and udp check") + { + set_was_evaluated(m_node, &p, m_state, m_context_num); + p.packet_flags = 0; + bool fail_due_packet_flag = false == was_evaluated(&m_node, m_state, m_context_num, &p); + REQUIRE(fail_due_packet_flag); + set_was_evaluated(m_node, &p, m_state, m_context_num); + p.proto_bits |= PROTO_BIT__UDP_TUNNELED; + p.ptrs.udph = (const snort::udp::UDPHdr*)0x1; // This is a hack to avoid irrelevant assert in "is_udp_tunneled" + bool fail_due_udp_tunneled = false == was_evaluated(&m_node, m_state, m_context_num, &p); + REQUIRE(fail_due_udp_tunneled); + } +} + +TEST_CASE("Detection Engine: match_rtn", "[de_core]") +{ + std::unique_ptr otn(new OptTreeNode()); + detection_option_tree_node_t m_node(RULE_OPTION_TYPE_OTHER, nullptr); + m_node.otn = otn.get(); + + // Needed to pass protocol/service check + std::unique_ptr context(new IpsContext(1)); + context->set_snort_protocol_id(UNKNOWN_PROTOCOL_ID); + context->packet->ptrs.type = PktType::PDU; // in order to get snort_protocol_id from IpsContext; + Packet* p = context->packet; + + RuleTreeNode rtn; + otn->proto_node_num = 1; + // will be de-allocated in ~Otn + otn->proto_nodes = (RuleTreeNode**)snort_calloc(1, sizeof(RuleTreeNode*)); + otn->proto_nodes[0] = &rtn; + + std::unique_ptr ips_policy(new IpsPolicy()); + ips_policy->policy_id = 0; + set_ips_policy(ips_policy.get()); + + // Needed to pass fp_eval_rtn + rtn.set_enabled(); + std::unique_ptr fp_list(new RuleFpList); + rtn.rule_func = fp_list.get(); + + SECTION("Passed due to middle-node evaluation") + { + detection_option_tree_node_t middle_node(RULE_OPTION_TYPE_OTHER, nullptr); + bool non_rtn_containig_node = !middle_node.otn; + + REQUIRE(non_rtn_containig_node); + REQUIRE(true == match_rtn(&middle_node, p)); + } + SECTION("Passed due to file id rule") + { + bool rtn_containig_node = m_node.otn; + REQUIRE(rtn_containig_node); + + const_cast(m_node.otn)->sigInfo.file_id = 1; + REQUIRE(true == match_rtn(&m_node, p)); + } + SECTION("Check protocol") + { + SECTION("Failed due to no known service") + { + context->set_snort_protocol_id(SNORT_PROTO_TCP); + otn->sigInfo.services.push_back({"not-tcp", SNORT_PROTO_UDP}); + auto mock_r_func = [](snort::Packet*, RuleTreeNode*, RuleFpList*, int) -> int + { assert(false); return 1; }; + rtn.rule_func->RuleHeadFunc = mock_r_func; + + REQUIRE(false == match_rtn(&m_node, p)); + } + SECTION("Match due to empty service list") + { + context->set_snort_protocol_id(SNORT_PROTO_TCP); + auto mock_r_func = [](snort::Packet*, RuleTreeNode*, RuleFpList*, int) -> int + { return 1; }; + rtn.rule_func->RuleHeadFunc = mock_r_func; + + REQUIRE(true == match_rtn(&m_node, p)); + } + } + SECTION("Evaluation of fp_eval_rtn") + { + SECTION("RTN matched") + { + auto mock_r_func = [](snort::Packet*, RuleTreeNode*, RuleFpList*, int) -> int { return 1; }; + rtn.rule_func->RuleHeadFunc = mock_r_func; + + REQUIRE(true == match_rtn(&m_node, p)); + } + SECTION("RTN mismatched") + { + auto mock_r_func = [](snort::Packet*, RuleTreeNode*, RuleFpList*, int) -> int { return 0; }; + rtn.rule_func->RuleHeadFunc = mock_r_func; + + REQUIRE(false == match_rtn(&m_node, p)); + } + SECTION("Ports are checked in case of unknown protocol_id") + { + auto mock_r_func = [](snort::Packet*, RuleTreeNode*, RuleFpList*, int check_ports) -> int + { return check_ports; }; + rtn.rule_func->RuleHeadFunc = mock_r_func; + + REQUIRE(true == match_rtn(&m_node, p)); + } + SECTION("RTN user_mode priority over service-check") + { + context->set_snort_protocol_id(SNORT_PROTO_TCP); + otn->sigInfo.services.push_back({"tcp", SNORT_PROTO_TCP}); + rtn.flags |= RuleTreeNode::USER_MODE; + auto mock_r_func = [](snort::Packet*, RuleTreeNode*, RuleFpList*, int check_ports) -> int + { return check_ports; }; + rtn.rule_func->RuleHeadFunc = mock_r_func; + + REQUIRE(true == match_rtn(&m_node, p)); + } + } +} + +TEST_CASE("Detection Engine: match_leaf", "[de_core]") +{ + std::unique_ptr otn(new OptTreeNode()); + detection_option_tree_node_t m_node(RULE_OPTION_TYPE_OTHER, otn.get()); + detection_option_eval_data_t m_e_data; + Packet p; + m_e_data.p = &p; + otn->state = new OtnState[1](); + set_instance_id(0); + + SECTION("Detection filter passed, state.matches increased") + { + // de-allocated in ~Otn + otn->detection_filter = new THD_NODE(); + detection_filter_term(); // need to make early exit from detection_filter logic + m_e_data.flowbit_noalert = true; // Avoid extra work in such way + uint64_t curr_matches = otn->state[get_instance_id()].matches; + + // Needed for detection_filter_test + std::unique_ptr ips_policy(new IpsPolicy()); + ips_policy->policy_id = 0; + set_ips_policy(ips_policy.get()); + + REQUIRE(match_leaf(&m_node, m_e_data)); + REQUIRE(curr_matches +1 == otn->state[get_instance_id()].matches); + } + SECTION("state.matches counter is increased") + { + uint64_t curr_matches = otn->state[get_instance_id()].matches; + m_e_data.flowbit_noalert = true; // Avoid extra work in such way + + REQUIRE(match_leaf(&m_node, m_e_data)); + REQUIRE(curr_matches +1 == otn->state[get_instance_id()].matches); + } + SECTION("leaf_reached flag is set") + { + char curr_flag = m_e_data.leaf_reached; + m_e_data.flowbit_noalert = true; // Avoid extra work in such way + + REQUIRE(match_leaf(&m_node, m_e_data)); + REQUIRE(curr_flag != m_e_data.leaf_reached); + REQUIRE(m_e_data.leaf_reached); + + SECTION("Verify that flag is set, not toggled") + { + m_e_data.leaf_reached = 1; + char reached = m_e_data.leaf_reached; + m_e_data.flowbit_noalert = true; // Avoid extra work in such way + + REQUIRE(match_leaf(&m_node, m_e_data)); + REQUIRE(reached == m_e_data.leaf_reached); + } + } + SECTION("Alert the match") + { + std::unique_ptr context(new IpsContext(1)); + context->set_snort_protocol_id(UNKNOWN_PROTOCOL_ID); + m_e_data.p = context->packet; + + // Forcing underlying function return nullptr since the test target not the fpAddMatch function. + // In such way, the fpAddMatch will return 2, which is still fine for us. + otn->proto_node_num = 1; + + otn->proto_nodes = (RuleTreeNode**)snort_calloc(1, sizeof(RuleTreeNode*)); + otn->proto_nodes[0] = nullptr; + + std::unique_ptr ips_policy(new IpsPolicy()); + ips_policy->policy_id = 0; + set_ips_policy(ips_policy.get()); + + REQUIRE(match_leaf(&m_node, m_e_data)); + } +} + +class MockIpsBufSetter : public IpsOption +{ +public: + MockIpsBufSetter(const char* s) : IpsOption(s) + { }; + + CursorActionType get_cursor_type() const override + { return CAT_SET_SUB_SECTION; }; +}; + +class MockIpsOptRead : public IpsOption +{ +public: + MockIpsOptRead(const char* s) : IpsOption(s) + { }; + + CursorActionType get_cursor_type() const override + { return CAT_READ; }; +}; + +TEST_CASE("Detection Engine: match_node", "[de_core]") +{ + detection_option_eval_data_t m_e_data; + Cursor m_c; + detection_option_tree_node_t m_node(RULE_OPTION_TYPE_OTHER, nullptr); + auto mock_eval = [](void*, class Cursor&, snort::Packet*) -> int { return 1; }; + m_node.evaluate = mock_eval; + MockIpsBufSetter mock_ips_setter ("mock_ips_setter"); + MockIpsOptRead mock_ips_read ("mock_ips_read"); + + SECTION("Buffer setter is set from option") + { + m_node.option_data = &mock_ips_setter; + + IpsOption* buf_selector = nullptr; + REQUIRE(match_node(&m_node, m_e_data, m_c, buf_selector)); + REQUIRE(buf_selector == &mock_ips_setter); + } + SECTION("Empty buffer setter") + { + m_node.option_data = &mock_ips_read; + + IpsOption* buf_selector = nullptr; + REQUIRE(match_node(&m_node, m_e_data, m_c, buf_selector)); + REQUIRE(buf_selector != &mock_ips_read); + REQUIRE_FALSE(buf_selector); + } +} + +class MockIpsOptPMD : public IpsOption +{ +public: + MockIpsOptPMD(const char* s) : IpsOption(s) + { pmd = new PatternMatchData(); }; + + ~MockIpsOptPMD() + { delete pmd; } + + CursorActionType get_cursor_type() const override + { return CAT_READ; }; + + PatternMatchData* get_pattern(SnortProtocolId, RuleDirection) override + { return pmd; } + +private: + PatternMatchData* pmd; +}; + +TEST_CASE("Detection Engine: skip_on_retry", "[de_core]") +{ + Packet p; + detection_option_tree_node_t m_node(RULE_OPTION_TYPE_OTHER, nullptr); + detection_option_tree_node_t m_child_node(RULE_OPTION_TYPE_OTHER, nullptr); + detection_option_eval_data_t m_e_data; + m_e_data.p = &p; + dot_node_state_t m_child_state; + int result = 0; + + SECTION("Skip child leaf node") + { + m_child_node.option_type = RULE_OPTION_TYPE_LEAF_NODE; + REQUIRE(true == skip_on_retry(nullptr, &m_child_node, &m_child_state, 1, result)); + REQUIRE_FALSE(result); + } + SECTION("Skip if all following options are matched") + { + m_child_state.result = m_child_node.num_children = 0; + REQUIRE(true == skip_on_retry(nullptr, &m_child_node, &m_child_state, 1, result)); + } + + SECTION("Do not skip all non-content nodes") + { + m_child_state.result = 1; // to avoid early exit + REQUIRE(false == skip_on_retry(nullptr, &m_child_node, &m_child_state, 1, result)); + } + SECTION("Retry of IPS content logic") + { + m_child_node.children = (detection_option_tree_node_t**)snort_calloc(2, sizeof(detection_option_tree_node_t*)); + m_child_node.num_children = 2; // to avoid early exit + m_child_node.option_type = RULE_OPTION_TYPE_CONTENT; + + SECTION("Not skipping if matched previously") + { + m_child_state.result = 1; + REQUIRE(false == skip_on_retry(nullptr, &m_child_node, &m_child_state, 1, result)); + } + SECTION("Non-relative") + { + SECTION("Increase result on 1st loop") + { + int curr_result = result; + m_child_node.is_relative = false; + + REQUIRE(true == skip_on_retry(nullptr, &m_child_node, &m_child_state, 1, result)); + REQUIRE(curr_result +1 == result); + } + SECTION("Do not affect result on non-1st loop") + { + int curr_result = result; + m_child_node.is_relative = false; + + REQUIRE(true == skip_on_retry(nullptr, &m_child_node, &m_child_state, 2, result)); + REQUIRE(curr_result == result); + } + } + + m_child_node.is_relative = true; + MockIpsBufSetter mock_ips_setter ("mock_ips_setter"); + MockIpsOptRead mock_ips_read ("mock_ips_read"); + MockIpsOptPMD mock_ips_pmd ("mock_ips_pmd"); + + SECTION("Current Ips option is buffer setter") + { + + m_node.option_data = &mock_ips_setter; + + REQUIRE(false == skip_on_retry(&m_node, &m_child_node, &m_child_state, 1, result)); + } + SECTION("Child node PMD evaluation") + { + m_node.option_data = &mock_ips_read; // to avoid early exit + + m_child_node.option_data = &mock_ips_pmd; + PatternMatchData* pmd = mock_ips_pmd.get_pattern(0, RULE_WO_DIR); + + SECTION("PMD for not exists") + { + m_child_node.option_data = &mock_ips_pmd; + REQUIRE(false == skip_on_retry(&m_node, &m_child_node, &m_child_state, 1, result)); + } + SECTION("PMD not literal") + { + REQUIRE(false == skip_on_retry(&m_node, &m_child_node, &m_child_state, 1, result)); + } + SECTION("PMD not unbounded") + { + pmd->set_literal(); + pmd->depth = 1; + REQUIRE(false == skip_on_retry(&m_node, &m_child_node, &m_child_state, 1, result)); + } + SECTION("PMD is negated") + { + pmd->set_literal(); + pmd->set_negated(); + REQUIRE(false == skip_on_retry(&m_node, &m_child_node, &m_child_state, 1, result)); + } + SECTION("Skipping on the 1st loop") + { + int curr_result = result; + pmd->set_literal(); + + REQUIRE(true == skip_on_retry(&m_node, &m_child_node, &m_child_state, 1, result)); + REQUIRE(curr_result +1 == result); + } + SECTION("Skipping on the non-1st loop") + { + int curr_result = result; + pmd->set_literal(); + + REQUIRE(true == skip_on_retry(&m_node, &m_child_node, &m_child_state, 2, result)); + REQUIRE(curr_result == result); + } + } + } +} + +#endif diff --git a/src/detection/detection_options.h b/src/detection/detection_options.h index 2b00a8957..8405bd70a 100644 --- a/src/detection/detection_options.h +++ b/src/detection/detection_options.h @@ -17,7 +17,8 @@ // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. //-------------------------------------------------------------------------- -// detection_options.h author Steven Sturges +// detection_options.cc author Steve Sturges +// detection_options.cc author Yehor Velykozhon #ifndef DETECTION_OPTIONS_H #define DETECTION_OPTIONS_H @@ -33,6 +34,7 @@ #include +#include "detection/ips_context.h" #include "detection/rule_option_types.h" #include "latency/rule_latency_state.h" #include "main/thread_config.h" @@ -66,6 +68,16 @@ struct dot_node_state_t uint16_t run_num; char result; char flowbit_failed; + + void set(const snort::Packet& p, int last_result) + { + ts = p.pkth->ts; + run_num = get_run_num(); + context_num = p.context->context_num; + flowbit_failed = 0; + rebuild_flag = p.packet_flags & PKT_REBUILT_STREAM; + result = last_result; + } } last_check; void* conts; uint64_t context_num; @@ -137,7 +149,7 @@ struct detection_option_tree_node_t : public detection_option_tree_bud_t dot_node_state_t* state; int is_relative; option_type_t option_type; - + detection_option_tree_node_t(option_type_t type, void* data) : evaluate(nullptr), option_data(data), is_relative(0), option_type(type) { diff --git a/src/filters/detection_filter.cc b/src/filters/detection_filter.cc index a91acff29..ec70e9e36 100644 --- a/src/filters/detection_filter.cc +++ b/src/filters/detection_filter.cc @@ -25,6 +25,7 @@ #include "hash/xhash.h" #include "log/messages.h" +#include "protocols/packet.h" #include "utils/util.h" #include "sfthd.h" @@ -54,15 +55,20 @@ void DetectionFilterConfigFree(DetectionFilterConfig* config) snort_free(config); } -int detection_filter_test(void* pv, const SfIp* sip, const SfIp* dip, long curtime) +int detection_filter_test(THD_NODE* pv, const Packet* const p) { + assert(p); // cppcheck-suppress unreadVariable RuleProfile profile(detectionFilterPerfStats); if (pv == nullptr) return 0; - return sfthd_test_rule(detection_filter_hash, (THD_NODE*)pv, + const SfIp* sip = p->ptrs.ip_api.get_src(); + const SfIp* dip = p->ptrs.ip_api.get_dst(); + long curtime = p->pkth->ts.tv_sec; + + return sfthd_test_rule(detection_filter_hash, pv, sip, dip, curtime, get_ips_policy()->policy_id); } diff --git a/src/filters/detection_filter.h b/src/filters/detection_filter.h index 3fd9cc8d6..74783050c 100644 --- a/src/filters/detection_filter.h +++ b/src/filters/detection_filter.h @@ -37,6 +37,7 @@ namespace snort extern THREAD_LOCAL snort::ProfileStats detectionFilterPerfStats; struct SfIp; +struct Packet; } struct DetectionFilterConfig @@ -52,8 +53,8 @@ void DetectionFilterConfigFree(DetectionFilterConfig*); void detection_filter_init(DetectionFilterConfig*); void detection_filter_term(); -int detection_filter_test(void*, const snort::SfIp* sip, const snort::SfIp* dip, long curtime); struct THD_NODE* detection_filter_create(DetectionFilterConfig*, struct THDX_STRUCT*); +int detection_filter_test(THD_NODE*, const snort::Packet* const p); #endif