diff --git a/userspace/engine/evttype_index_ruleset.cpp b/userspace/engine/evttype_index_ruleset.cpp index 5666e5b8..92b6da1e 100644 --- a/userspace/engine/evttype_index_ruleset.cpp +++ b/userspace/engine/evttype_index_ruleset.cpp @@ -138,6 +138,45 @@ bool evttype_index_ruleset::ruleset_filters::run(gen_event *evt, falco_rule& mat return false; } +bool evttype_index_ruleset::ruleset_filters::run(gen_event *evt, std::vector& matches) +{ + bool match_found = false; + + if(evt->get_type() < m_filter_by_event_type.size()) + { + for(auto &wrap : m_filter_by_event_type[evt->get_type()]) + { + if(wrap->filter->run(evt)) + { + matches.push_back(wrap->rule); + match_found = true; + } + } + } + + if(match_found) + { + return true; + } + + // Finally, try filters that are not specific to an event type. + for(auto &wrap : m_filter_all_event_types) + { + if(wrap->filter->run(evt)) + { + matches.push_back(wrap->rule); + match_found = true; + } + } + + if(match_found) + { + return true; + } + + return false; +} + libsinsp::events::set evttype_index_ruleset::ruleset_filters::sc_codes() { libsinsp::events::set res; @@ -308,6 +347,16 @@ bool evttype_index_ruleset::run(gen_event *evt, falco_rule& match, uint16_t rule return m_rulesets[ruleset_id]->run(evt, match); } +bool evttype_index_ruleset::run(gen_event *evt, std::vector& matches, uint16_t ruleset_id) +{ + if(m_rulesets.size() < (size_t)ruleset_id + 1) + { + return false; + } + + return m_rulesets[ruleset_id]->run(evt, matches); +} + void evttype_index_ruleset::enabled_evttypes(std::set &evttypes, uint16_t ruleset_id) { evttypes.clear(); diff --git a/userspace/engine/evttype_index_ruleset.h b/userspace/engine/evttype_index_ruleset.h index a4680bc0..0d2df0f6 100644 --- a/userspace/engine/evttype_index_ruleset.h +++ b/userspace/engine/evttype_index_ruleset.h @@ -46,7 +46,8 @@ public: void clear() override; - bool run(gen_event *evt, falco_rule& match, uint16_t rulset_id); + bool run(gen_event *evt, falco_rule& match, uint16_t ruleset_id) override; + bool run(gen_event *evt, std::vector&matches, uint16_t ruleset_id) override; uint64_t enabled_count(uint16_t ruleset_id) override; @@ -118,8 +119,14 @@ private: uint64_t num_filters(); + // Evaluate an event against the ruleset and return the first rule + // that matched. bool run(gen_event *evt, falco_rule& match); + // Evaluate an event against the ruleset and return all the + // matching rules. + bool run(gen_event *evt, std::vector& matches); + libsinsp::events::set sc_codes(); libsinsp::events::set event_codes(); diff --git a/userspace/engine/falco_engine.cpp b/userspace/engine/falco_engine.cpp index 414384f6..1d277070 100644 --- a/userspace/engine/falco_engine.cpp +++ b/userspace/engine/falco_engine.cpp @@ -56,6 +56,7 @@ falco_engine::falco_engine(bool seed_rng) m_syscall_source_idx(SIZE_MAX), m_next_ruleset_id(0), m_min_priority(falco_common::PRIORITY_DEBUG), + m_rule_matching(falco_common::FIRST), m_sampling_ratio(1), m_sampling_multiplier(0), m_replace_container_info(false) { @@ -310,6 +311,11 @@ void falco_engine::set_min_priority(falco_common::priority_type priority) m_min_priority = priority; } +void falco_engine::set_rule_matching(falco_common::rule_matching rule_matching) +{ + m_rule_matching = rule_matching; +} + uint16_t falco_engine::find_ruleset_id(const std::string &ruleset) { auto it = m_known_rulesets.lower_bound(ruleset); @@ -353,7 +359,7 @@ std::shared_ptr falco_engine::create_formatter(const std::s return find_source(source)->formatter_factory->create_formatter(output); } -std::unique_ptr falco_engine::process_event(std::size_t source_idx, gen_event *ev, uint16_t ruleset_id) +std::unique_ptr> falco_engine::process_event(std::size_t source_idx, gen_event *ev, uint16_t ruleset_id) { // note: there are no thread-safety guarantees on the filter_ruleset::run() // method, but the thread-safety assumptions of falco_engine::process_event() @@ -377,24 +383,51 @@ std::unique_ptr falco_engine::process_event(std::size source = find_source(source_idx); } - if(should_drop_evt() || !source || !source->ruleset->run(ev, source->m_rule, ruleset_id)) + if(should_drop_evt() || !source) { - return std::unique_ptr(); + return nullptr; } - std::unique_ptr res(new rule_result()); - res->evt = ev; - res->rule = source->m_rule.name; - res->source = source->m_rule.source; - res->format = source->m_rule.output; - res->priority_num = source->m_rule.priority; - res->tags = source->m_rule.tags; - res->exception_fields = source->m_rule.exception_fields; - m_rule_stats_manager.on_event(source->m_rule); + if (m_rule_matching == falco_common::rule_matching::ALL) + { + if (!source->ruleset->run(ev, source->m_rules, ruleset_id)) + { + return nullptr; + } + } + else if (m_rule_matching == falco_common::rule_matching::FIRST) + { + falco_rule rule; + if (!source->ruleset->run(ev, rule, ruleset_id)) + { + return nullptr; + } + source->m_rules.push_back(rule); + } + + auto res = std::make_unique>(); + for(auto rule : source->m_rules) + { + rule_result rule_result; + rule_result.evt = ev; + rule_result.rule = rule.name; + rule_result.source = rule.source; + rule_result.format = rule.output; + rule_result.priority_num = rule.priority; + rule_result.tags = rule.tags; + rule_result.exception_fields = rule.exception_fields; + m_rule_stats_manager.on_event(rule); + res->push_back(rule_result); + } + + if (source->m_rules.size() > 0) + { + source->m_rules.clear(); + } return res; } -std::unique_ptr falco_engine::process_event(std::size_t source_idx, gen_event *ev) +std::unique_ptr> falco_engine::process_event(std::size_t source_idx, gen_event *ev) { return process_event(source_idx, ev, m_default_ruleset_id); } diff --git a/userspace/engine/falco_engine.h b/userspace/engine/falco_engine.h index 7fee24b2..f2a9b08c 100644 --- a/userspace/engine/falco_engine.h +++ b/userspace/engine/falco_engine.h @@ -106,6 +106,11 @@ public: // Only load rules having this priority or more severe. void set_min_priority(falco_common::priority_type priority); + // Whether or not continuing to evaluate rules for other potential matches + // even if a match already occurred. This option can be set to avoid shadowing + // of rules. + void set_rule_matching(falco_common::rule_matching rule_matching); + // // Return the ruleset id corresponding to this ruleset name, // creating a new one if necessary. If you provide any ruleset @@ -189,14 +194,14 @@ public: // event source is not thread-safe of its own, so invoking this method // concurrently with the same source_idx would inherently cause data races // and lead to undefined behavior. - std::unique_ptr process_event(std::size_t source_idx, gen_event *ev, uint16_t ruleset_id); + std::unique_ptr> process_event(std::size_t source_idx, gen_event *ev, uint16_t ruleset_id); // // Wrapper assuming the default ruleset. // // This inherits the same thread-safety guarantees. // - std::unique_ptr process_event(std::size_t source_idx, gen_event *ev); + std::unique_ptr> process_event(std::size_t source_idx, gen_event *ev); // // Configure the engine to support events with the provided @@ -320,6 +325,7 @@ private: uint16_t m_next_ruleset_id; std::map m_known_rulesets; falco_common::priority_type m_min_priority; + falco_common::rule_matching m_rule_matching; // // Here's how the sampling ratio and multiplier influence diff --git a/userspace/engine/falco_source.h b/userspace/engine/falco_source.h index c36e87a0..7823f7b8 100644 --- a/userspace/engine/falco_source.h +++ b/userspace/engine/falco_source.h @@ -53,7 +53,7 @@ struct falco_source // Used by the filter_ruleset interface. Filled in when a rule // matches an event. - mutable falco_rule m_rule; + mutable std::vector m_rules; inline bool is_field_defined(const std::string& field) const { diff --git a/userspace/engine/filter_ruleset.h b/userspace/engine/filter_ruleset.h index 9520f0ce..2bceb38c 100644 --- a/userspace/engine/filter_ruleset.h +++ b/userspace/engine/filter_ruleset.h @@ -65,7 +65,7 @@ public: \brief Processes an event and tries to find a match in a given ruleset. \return true if a match is found, false otherwise \param evt The event to be processed - \param match If true is returned, this is filled-out with the rule + \param match If true is returned, this is filled-out with the first rule that matched the event \param ruleset_id The id of the ruleset to be used */ @@ -73,6 +73,19 @@ public: gen_event *evt, falco_rule& match, uint16_t ruleset_id) = 0; + + /*! + \brief Processes an event and tries to find a match in a given ruleset. + \return true if a match is found, false otherwise + \param evt The event to be processed + \param matches If true is returned, this is filled-out with all the rules + that matched the event + \param ruleset_id The id of the ruleset to be used + */ + virtual bool run( + gen_event *evt, + std::vector& matches, + uint16_t ruleset_id) = 0; /*! \brief Returns the number of rules enabled in a given ruleset diff --git a/userspace/falco/app/actions/process_events.cpp b/userspace/falco/app/actions/process_events.cpp index 6ef9d6fc..a798b050 100644 --- a/userspace/falco/app/actions/process_events.cpp +++ b/userspace/falco/app/actions/process_events.cpp @@ -330,19 +330,22 @@ static falco::app::run_result do_inspect( // engine, which will match the event against the set // of rules. If a match is found, pass the event to // the outputs. - std::unique_ptr res = s.engine->process_event(source_engine_idx, ev); - if(res) + auto res = s.engine->process_event(source_engine_idx, ev); + if(res != nullptr) { - if (!rate_limiter_enabled || rate_limiter.claim()) + for(auto& rule_res : *res.get()) { - s.outputs->handle_event(res->evt, res->rule, res->source, res->priority_num, res->format, res->tags); - } - else - { - falco_logger::log(LOG_DEBUG, "Skipping rate-limited notification for rule " + res->rule + "\n"); + if (!rate_limiter_enabled || rate_limiter.claim()) + { + s.outputs->handle_event(rule_res.evt, rule_res.rule, rule_res.source, rule_res.priority_num, rule_res.format, rule_res.tags); + } + else + { + falco_logger::log(LOG_DEBUG, "Skipping rate-limited notification for rule " + rule_res.rule + "\n"); + } } } - + num_evts++; }