Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new: allow falco to match multiple rules on same event #2705

Merged
merged 9 commits into from
Aug 9, 2023
16 changes: 15 additions & 1 deletion falco.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,6 @@ rules_file:
- /etc/falco/falco_rules.local.yaml
- /etc/falco/rules.d


#################
# Falco plugins #
#################
Expand Down Expand Up @@ -290,6 +289,21 @@ outputs:
rate: 0
max_burst: 1000

# [Experimental] `rule_matching`
#
# The `rule_matching` configuration key's values are:
# - `first`: Falco stops checking conditions of rules against upcoming event
# at the first matching rule
# - `all`: Falco will continue checking conditions of rules even if a matching
# one was already found
#
# Rules conditions are evaluated in the order they are defined in the rulesfiles.
# For this reason, when using `first` as value, only the first defined rule will
# trigger, possibly shadowing other rules.
# In case `all` is used as value, rules still trigger in the order they were
# defined.
rule_matching: first


##########################
# Falco outputs channels #
Expand Down
44 changes: 44 additions & 0 deletions userspace/engine/evttype_index_ruleset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,40 @@ 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<falco_rule>& 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;
}
}

return match_found;
}

libsinsp::events::set<ppm_sc_code> evttype_index_ruleset::ruleset_filters::sc_codes()
{
libsinsp::events::set<ppm_sc_code> res;
Expand Down Expand Up @@ -308,6 +342,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<falco_rule>& 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<uint16_t> &evttypes, uint16_t ruleset_id)
{
evttypes.clear();
Expand Down
9 changes: 8 additions & 1 deletion userspace/engine/evttype_index_ruleset.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ class evttype_index_ruleset: public filter_ruleset

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<falco_rule>&matches, uint16_t ruleset_id) override;

uint64_t enabled_count(uint16_t ruleset_id) override;

Expand Down Expand Up @@ -118,8 +119,14 @@ class evttype_index_ruleset: public filter_ruleset

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<falco_rule>& matches);

libsinsp::events::set<ppm_sc_code> sc_codes();

libsinsp::events::set<ppm_event_code> event_codes();
Expand Down
18 changes: 18 additions & 0 deletions userspace/engine/falco_common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ static std::vector<std::string> priority_names = {
"Debug"
};

static std::vector<std::string> rule_matching_names = {
"first",
"all"
};

bool falco_common::parse_priority(std::string v, priority_type& out)
{
for (size_t i = 0; i < priority_names.size(); i++)
Expand Down Expand Up @@ -79,4 +84,17 @@ std::string falco_common::format_priority(priority_type v, bool shortfmt)
throw falco_exception("Unknown priority enum value: " + std::to_string(v));
}
return out;
}

bool falco_common::parse_rule_matching(std::string v, rule_matching& out)
{
for (size_t i = 0; i < rule_matching_names.size(); i++)
{
Andreagit97 marked this conversation as resolved.
Show resolved Hide resolved
if (!strcasecmp(v.c_str(), rule_matching_names[i].c_str()))
{
out = (rule_matching) i;
return true;
}
}
return false;
}
8 changes: 8 additions & 0 deletions userspace/engine/falco_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,12 @@ namespace falco_common
priority_type parse_priority(std::string v);
bool format_priority(priority_type v, std::string& out, bool shortfmt=false);
std::string format_priority(priority_type v, bool shortfmt=false);

enum rule_matching
{
FIRST = 0,
ALL = 1
};

bool parse_rule_matching(std::string v, rule_matching& out);
};
62 changes: 49 additions & 13 deletions userspace/engine/falco_engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -353,7 +359,7 @@ std::shared_ptr<gen_event_formatter> falco_engine::create_formatter(const std::s
return find_source(source)->formatter_factory->create_formatter(output);
}

std::unique_ptr<falco_engine::rule_result> falco_engine::process_event(std::size_t source_idx, gen_event *ev, uint16_t ruleset_id)
std::unique_ptr<std::vector<falco_engine::rule_result>> 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()
Expand All @@ -377,24 +383,54 @@ std::unique_ptr<falco_engine::rule_result> 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 nullptr;
Andreagit97 marked this conversation as resolved.
Show resolved Hide resolved
}

switch (m_rule_matching)
{
case falco_common::rule_matching::ALL:
if (source->m_rules.size() > 0)
{
source->m_rules.clear();
}
if (!source->ruleset->run(ev, source->m_rules, ruleset_id))
{
return nullptr;
}
break;
case falco_common::rule_matching::FIRST:
if (source->m_rules.size() != 1)
{
source->m_rules.resize(1);
}
if (!source->ruleset->run(ev, source->m_rules[0], ruleset_id))
{
return nullptr;
}
break;
}

auto res = std::make_unique<std::vector<falco_engine::rule_result>>();
for(auto rule : source->m_rules)
{
return std::unique_ptr<struct rule_result>();
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);
}

std::unique_ptr<struct rule_result> 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);
return res;
}

std::unique_ptr<falco_engine::rule_result> falco_engine::process_event(std::size_t source_idx, gen_event *ev)
std::unique_ptr<std::vector<falco_engine::rule_result>> falco_engine::process_event(std::size_t source_idx, gen_event *ev)
{
return process_event(source_idx, ev, m_default_ruleset_id);
}
Expand Down
10 changes: 8 additions & 2 deletions userspace/engine/falco_engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ class falco_engine
// 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
Expand Down Expand Up @@ -189,14 +194,14 @@ class falco_engine
// 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<rule_result> process_event(std::size_t source_idx, gen_event *ev, uint16_t ruleset_id);
std::unique_ptr<std::vector<rule_result>> 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<rule_result> process_event(std::size_t source_idx, gen_event *ev);
std::unique_ptr<std::vector<rule_result>> process_event(std::size_t source_idx, gen_event *ev);
Andreagit97 marked this conversation as resolved.
Show resolved Hide resolved

//
// Configure the engine to support events with the provided
Expand Down Expand Up @@ -320,6 +325,7 @@ class falco_engine
uint16_t m_next_ruleset_id;
std::map<std::string, uint16_t> 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
Expand Down
2 changes: 1 addition & 1 deletion userspace/engine/falco_engine_version.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ limitations under the License.
*/

// The version of this Falco engine.
#define FALCO_ENGINE_VERSION (21)
#define FALCO_ENGINE_VERSION (22)

// This is the result of running the following command:
// FALCO="falco -c ./falco.yaml"
Expand Down
2 changes: 1 addition & 1 deletion userspace/engine/falco_source.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<falco_rule> m_rules;

inline bool is_field_defined(const std::string& field) const
{
Expand Down
15 changes: 14 additions & 1 deletion userspace/engine/filter_ruleset.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,27 @@ class filter_ruleset
\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
*/
virtual bool run(
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<falco_rule>& matches,
uint16_t ruleset_id) = 0;

/*!
\brief Returns the number of rules enabled in a given ruleset
Expand Down
1 change: 1 addition & 0 deletions userspace/falco/app/actions/init_falco_engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ falco::app::run_result falco::app::actions::init_falco_engine(falco::app::state&

configure_output_format(s);
s.engine->set_min_priority(s.config->m_min_priority);
s.engine->set_rule_matching(s.config->m_rule_matching);

return run_result::ok();
}
21 changes: 12 additions & 9 deletions userspace/falco/app/actions/process_events.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<falco_engine::rule_result> 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++;
}

Expand Down
Loading