Add an indexable ruleset that can split filters by ruleset/evttype

Now that custom rules loading implementations (and related, custom
rulesets) can be swapped into falco in a customizable way, there is
some functionality in evttype_index_ruleset that could be used by
other rulesets, specifically the part that segregates filters by
ruleset and enables/disables filters based on name substring + tags.

To allow for this, create a new base class indexable_ruleset that
takes a generic filter_wrapper object that can return a name, tags,
and sc/event codes, and segregates the filters by ruleset. It also
optionally segregates filters by event type.

The main interfaces are:

- an implementation of filter_wrapper to provide a name/tags/event
  codes.
- add_wrapper(), which provides a filter_wrapper to the
  indexable_ruleset.
- run_wrappers(), which must be implemented by the derived class and
  is called for event processing.

Most of the methods required by filter_ruleset are implemented by
indexable_ruleset and do not need to be implemented by the derived
class.

Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
This commit is contained in:
Mark Stemm 2024-06-13 15:04:30 -07:00
parent 3e91a27538
commit 605e69cf0e
2 changed files with 529 additions and 0 deletions

View File

@ -0,0 +1,347 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright (C) 2024 The Falco Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include "indexable_ruleset.h"
#include "falco_utils.h"
#include <algorithm>
void indexable_ruleset::clear()
{
for(size_t i = 0; i < m_rulesets.size(); i++)
{
m_rulesets[i] = std::make_shared<ruleset_filters>(i);
}
m_filters.clear();
}
uint64_t indexable_ruleset::enabled_count(uint16_t ruleset_id)
{
while(m_rulesets.size() < (size_t)ruleset_id + 1)
{
m_rulesets.emplace_back(std::make_shared<ruleset_filters>(m_rulesets.size()));
}
return m_rulesets[ruleset_id]->num_filters();
}
void indexable_ruleset::enabled_evttypes(std::set<uint16_t> &evttypes, uint16_t ruleset_id)
{
evttypes.clear();
for(const auto &e : enabled_event_codes(ruleset_id))
{
evttypes.insert((uint16_t)e);
}
}
libsinsp::events::set<ppm_sc_code> indexable_ruleset::enabled_sc_codes(uint16_t ruleset)
{
if(m_rulesets.size() < (size_t)ruleset + 1)
{
return {};
}
return m_rulesets[ruleset]->sc_codes();
}
libsinsp::events::set<ppm_event_code> indexable_ruleset::enabled_event_codes(uint16_t ruleset)
{
if(m_rulesets.size() < (size_t)ruleset + 1)
{
return {};
}
return m_rulesets[ruleset]->event_codes();
}
void indexable_ruleset::enable(const std::string &pattern, match_type match, uint16_t ruleset_id)
{
enable_disable(pattern, match, true, ruleset_id);
}
void indexable_ruleset::disable(const std::string &pattern, match_type match, uint16_t ruleset_id)
{
enable_disable(pattern, match, false, ruleset_id);
}
void indexable_ruleset::enable_disable(const std::string &pattern, match_type match, bool enabled, uint16_t ruleset_id)
{
while(m_rulesets.size() < (size_t)ruleset_id + 1)
{
m_rulesets.emplace_back(std::make_shared<ruleset_filters>(m_rulesets.size()));
}
for(const auto &wrap : m_filters)
{
bool matches;
std::string::size_type pos;
switch(match)
{
case match_type::exact:
pos = wrap->name().find(pattern);
matches = (pattern == "" || (pos == 0 &&
pattern.size() == wrap->name().size()));
break;
case match_type::substring:
matches = (pattern == "" || (wrap->name().find(pattern) != std::string::npos));
break;
case match_type::wildcard:
matches = falco::utils::matches_wildcard(pattern, wrap->name());
break;
default:
// should never happen
matches = false;
}
if(matches)
{
if(enabled)
{
m_rulesets[ruleset_id]->add_filter(wrap);
}
else
{
m_rulesets[ruleset_id]->remove_filter(wrap);
}
}
}
}
void indexable_ruleset::enable_tags(const std::set<std::string> &tags, uint16_t ruleset_id)
{
enable_disable_tags(tags, true, ruleset_id);
}
void indexable_ruleset::disable_tags(const std::set<std::string> &tags, uint16_t ruleset_id)
{
enable_disable_tags(tags, false, ruleset_id);
}
void indexable_ruleset::enable_disable_tags(const std::set<std::string> &tags, bool enabled, uint16_t ruleset_id)
{
while(m_rulesets.size() < (size_t)ruleset_id + 1)
{
m_rulesets.emplace_back(std::make_shared<ruleset_filters>(m_rulesets.size()));
}
for(const auto &wrap : m_filters)
{
std::set<std::string> intersect;
set_intersection(tags.begin(), tags.end(),
wrap->tags().begin(), wrap->tags().end(),
inserter(intersect, intersect.begin()));
if(!intersect.empty())
{
if(enabled)
{
m_rulesets[ruleset_id]->add_filter(wrap);
}
else
{
m_rulesets[ruleset_id]->remove_filter(wrap);
}
}
}
}
bool indexable_ruleset::run(sinsp_evt *evt, falco_rule &match, uint16_t ruleset_id)
{
if(m_rulesets.size() < (size_t)ruleset_id + 1)
{
return false;
}
return m_rulesets[ruleset_id]->run(*this, evt, match);
}
bool indexable_ruleset::run(sinsp_evt *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(*this, evt, matches);
}
void indexable_ruleset::add_wrapper(std::shared_ptr<filter_wrapper> wrap)
{
m_filters.insert(wrap);
}
uint64_t indexable_ruleset::iterate(filter_wrapper_func func)
{
uint64_t num_filters = 0;
for(const auto &ruleset_ptr : m_rulesets)
{
if(ruleset_ptr)
{
for(const auto &wrap : ruleset_ptr->get_filters())
{
num_filters++;
func(wrap);
}
}
}
return num_filters;
}
void indexable_ruleset::ruleset_filters::add_wrapper_to_list(filter_wrapper_list &wrappers, std::shared_ptr<filter_wrapper> wrap)
{
// This is O(n) but it's also uncommon
// (when loading rules only).
auto pos = std::find(wrappers.begin(),
wrappers.end(),
wrap);
if(pos == wrappers.end())
{
wrappers.push_back(wrap);
}
}
void indexable_ruleset::ruleset_filters::remove_wrapper_from_list(filter_wrapper_list &wrappers, std::shared_ptr<filter_wrapper> wrap)
{
// This is O(n) but it's also uncommon
// (when loading rules only).
auto pos = std::find(wrappers.begin(),
wrappers.end(),
wrap);
if(pos != wrappers.end())
{
wrappers.erase(pos);
}
}
void indexable_ruleset::ruleset_filters::add_filter(std::shared_ptr<filter_wrapper> wrap)
{
if(wrap->event_codes().empty())
{
// Should run for all event types
add_wrapper_to_list(m_filter_all_event_types, wrap);
}
else
{
for(auto &etype : wrap->event_codes())
{
if(m_filter_by_event_type.size() <= etype)
{
m_filter_by_event_type.resize(etype + 1);
}
add_wrapper_to_list(m_filter_by_event_type[etype], wrap);
}
}
m_filters.insert(wrap);
}
void indexable_ruleset::ruleset_filters::remove_filter(std::shared_ptr<filter_wrapper> wrap)
{
if(wrap->event_codes().empty())
{
remove_wrapper_from_list(m_filter_all_event_types, wrap);
}
else
{
for(auto &etype : wrap->event_codes())
{
if(etype < m_filter_by_event_type.size())
{
remove_wrapper_from_list(m_filter_by_event_type[etype], wrap);
}
}
}
m_filters.erase(wrap);
}
uint64_t indexable_ruleset::ruleset_filters::num_filters()
{
return m_filters.size();
}
bool indexable_ruleset::ruleset_filters::run(indexable_ruleset &ruleset, sinsp_evt *evt, falco_rule &match)
{
if(evt->get_type() < m_filter_by_event_type.size() &&
m_filter_by_event_type[evt->get_type()].size() > 0)
{
if(ruleset.run_wrappers(evt, m_filter_by_event_type[evt->get_type()], m_ruleset_id, match))
{
return true;
}
}
// Finally, try filters that are not specific to an event type.
if(m_filter_all_event_types.size() > 0)
{
if(ruleset.run_wrappers(evt, m_filter_all_event_types, m_ruleset_id, match))
{
return true;
}
}
return false;
}
bool indexable_ruleset::ruleset_filters::run(indexable_ruleset &ruleset, sinsp_evt *evt, std::vector<falco_rule> &matches)
{
if(evt->get_type() < m_filter_by_event_type.size() &&
m_filter_by_event_type[evt->get_type()].size() > 0)
{
if(ruleset.run_wrappers(evt, m_filter_by_event_type[evt->get_type()], m_ruleset_id, matches))
{
return true;
}
}
// Finally, try filters that are not specific to an event type.
if(m_filter_all_event_types.size() > 0)
{
if(ruleset.run_wrappers(evt, m_filter_all_event_types, m_ruleset_id, matches))
{
return true;
}
}
return false;
}
libsinsp::events::set<ppm_sc_code> indexable_ruleset::ruleset_filters::sc_codes()
{
libsinsp::events::set<ppm_sc_code> res;
for(const auto &wrap : m_filters)
{
res.insert(wrap->sc_codes().begin(), wrap->sc_codes().end());
}
return res;
}
libsinsp::events::set<ppm_event_code> indexable_ruleset::ruleset_filters::event_codes()
{
libsinsp::events::set<ppm_event_code> res;
for(const auto &wrap : m_filters)
{
res.insert(wrap->event_codes().begin(), wrap->event_codes().end());
}
return res;
}

View File

@ -0,0 +1,182 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright (C) 2024 The Falco Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/* This describes the interface for an "indexable" ruleset, that is, a
* ruleset that can enable/disable abstract filters for various
* ruleset ids.
*
* It's used by evttype_index_ruleset as well as other rulesets that
* need the same functionality but don't want to copy the same code.
*/
#pragma once
#include "filter_ruleset.h"
#include <libsinsp/sinsp.h>
#include <libsinsp/filter.h>
#include <libsinsp/event.h>
#include <functional>
#include <memory>
#include <string>
class indexable_ruleset : public filter_ruleset
{
public:
indexable_ruleset() = default;
virtual ~indexable_ruleset() = default;
// Required to implement filter_ruleset
void clear() override;
uint64_t enabled_count(uint16_t ruleset_id) override;
void enabled_evttypes(
std::set<uint16_t> &evttypes,
uint16_t ruleset) override;
libsinsp::events::set<ppm_sc_code> enabled_sc_codes(
uint16_t ruleset) override;
libsinsp::events::set<ppm_event_code> enabled_event_codes(
uint16_t ruleset) override;
void enable(
const std::string &pattern,
match_type match,
uint16_t ruleset_id) override;
void disable(
const std::string &pattern,
match_type match,
uint16_t ruleset_id) override;
void enable_tags(
const std::set<std::string> &tags,
uint16_t ruleset_id) override;
void disable_tags(
const std::set<std::string> &tags,
uint16_t ruleset_id) override;
// Note that subclasses do *not* implement run. Instead, they
// implement run_wrappers.
bool run(sinsp_evt *evt, falco_rule &match, uint16_t ruleset_id) override;
bool run(sinsp_evt *evt, std::vector<falco_rule> &matches, uint16_t ruleset_id) override;
// Methods for working with filter wrappers
// A derived class should add "filter wrappers" that implement
// these methods. They return the necessary information
// required to segregate filters by name, tags, and event
// types.
struct filter_wrapper
{
virtual const std::string &name() = 0;
virtual const std::set<std::string> &tags() = 0;
virtual const libsinsp::events::set<ppm_sc_code> &sc_codes() = 0;
virtual const libsinsp::events::set<ppm_event_code> &event_codes() = 0;
};
typedef std::list<std::shared_ptr<filter_wrapper>>
filter_wrapper_list;
// Subclasses should call add_wrapper (most likely from
// filter_ruleset::add or ::add_compile_output) to add filters.
void add_wrapper(std::shared_ptr<filter_wrapper> wrap);
// If a subclass needs to iterate over all filters, they can
// call iterate with this function, which will be called for
// all filters.
typedef std::function<void(const std::shared_ptr<filter_wrapper> &wrap)> filter_wrapper_func;
uint64_t iterate(filter_wrapper_func func);
// A subclass must implement these methods. They are analogous
// to run() but take care of selecting filters that match a
// ruleset and possibly an event type.
virtual bool run_wrappers(sinsp_evt *evt, filter_wrapper_list &wrappers, uint16_t ruleset_id, std::vector<falco_rule> &matches) = 0;
virtual bool run_wrappers(sinsp_evt *evt, filter_wrapper_list &wrappers, uint16_t ruleset_id, falco_rule &match) = 0;
private:
// Helper used by enable()/disable()
void enable_disable(
const std::string &pattern,
match_type match,
bool enabled,
uint16_t ruleset_id);
// Helper used by enable_tags()/disable_tags()
void enable_disable_tags(
const std::set<std::string> &tags,
bool enabled,
uint16_t ruleset_id);
// A group of filters all having the same ruleset
class ruleset_filters
{
public:
ruleset_filters(uint16_t ruleset_id):
m_ruleset_id(ruleset_id) {}
virtual ~ruleset_filters(){};
void add_filter(std::shared_ptr<filter_wrapper> wrap);
void remove_filter(std::shared_ptr<filter_wrapper> wrap);
uint64_t num_filters();
inline const std::set<std::shared_ptr<filter_wrapper>> &get_filters() const
{
return m_filters;
}
// Evaluate an event against the ruleset and return the first rule
// that matched.
bool run(indexable_ruleset &ruleset, sinsp_evt *evt, falco_rule &match);
// Evaluate an event against the ruleset and return all the
// matching rules.
bool run(indexable_ruleset &ruleset, sinsp_evt *evt, std::vector<falco_rule> &matches);
libsinsp::events::set<ppm_sc_code> sc_codes();
libsinsp::events::set<ppm_event_code> event_codes();
private:
void add_wrapper_to_list(filter_wrapper_list &wrappers, std::shared_ptr<filter_wrapper> wrap);
void remove_wrapper_from_list(filter_wrapper_list &wrappers, std::shared_ptr<filter_wrapper> wrap);
uint16_t m_ruleset_id;
// Vector indexes from event type to a set of filters. There can
// be multiple filters for a given event type.
// NOTE: This is used only when the event sub-type is 0.
std::vector<filter_wrapper_list> m_filter_by_event_type;
filter_wrapper_list m_filter_all_event_types;
// All filters added. Used to make num_filters() fast.
std::set<std::shared_ptr<filter_wrapper>> m_filters;
};
// Vector indexes from ruleset id to set of rules.
std::vector<std::shared_ptr<ruleset_filters>> m_rulesets;
// All filters added. The set of enabled filters is held in m_rulesets
std::set<std::shared_ptr<filter_wrapper>> m_filters;
};