From 40c711f599f463186b38c0c3a3c2340e8ec48099 Mon Sep 17 00:00:00 2001 From: Mark Stemm Date: Thu, 13 Jun 2024 15:04:30 -0700 Subject: [PATCH] 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 --- userspace/engine/indexable_ruleset.cpp | 336 +++++++++++++++++++++++++ userspace/engine/indexable_ruleset.h | 177 +++++++++++++ 2 files changed, 513 insertions(+) create mode 100644 userspace/engine/indexable_ruleset.cpp create mode 100644 userspace/engine/indexable_ruleset.h diff --git a/userspace/engine/indexable_ruleset.cpp b/userspace/engine/indexable_ruleset.cpp new file mode 100644 index 00000000000..1eab9da5467 --- /dev/null +++ b/userspace/engine/indexable_ruleset.cpp @@ -0,0 +1,336 @@ +// 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 + +void indexable_ruleset::clear() +{ + for(size_t i = 0; i < m_rulesets.size(); i++) + { + m_rulesets[i] = std::make_shared(m_index_by_evttype, 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(m_index_by_evttype, m_rulesets.size())); + } + + return m_rulesets[ruleset_id]->num_filters(); +} + +void indexable_ruleset::enabled_evttypes(std::set &evttypes, uint16_t ruleset_id) +{ + evttypes.clear(); + for(const auto &e : enabled_event_codes(ruleset_id)) + { + evttypes.insert((uint16_t)e); + } +} + +libsinsp::events::set 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 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(m_index_by_evttype, 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 &tags, uint16_t ruleset_id) +{ + enable_disable_tags(tags, true, ruleset_id); +} + +void indexable_ruleset::disable_tags(const std::set &tags, uint16_t ruleset_id) +{ + enable_disable_tags(tags, false, ruleset_id); +} + +void indexable_ruleset::enable_disable_tags(const std::set &tags, bool enabled, uint16_t ruleset_id) +{ + while(m_rulesets.size() < (size_t)ruleset_id + 1) + { + m_rulesets.emplace_back(std::make_shared(m_index_by_evttype, m_rulesets.size())); + } + + for(const auto &wrap : m_filters) + { + std::set 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 &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 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 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 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 wrap) +{ + if(!m_index_by_evttype || 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 wrap) +{ + if(!m_index_by_evttype || 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()) + { + 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. + return ruleset.run_wrappers(evt, m_filter_all_event_types, m_ruleset_id, match); +} + +bool indexable_ruleset::ruleset_filters::run(indexable_ruleset &ruleset, sinsp_evt *evt, std::vector &matches) +{ + bool match_found = false; + + if(evt->get_type() < m_filter_by_event_type.size()) + { + if(ruleset.run_wrappers(evt, m_filter_by_event_type[evt->get_type()], m_ruleset_id, matches)) + { + match_found = true; + } + } + + if(match_found) + { + return true; + } + + // Finally, try filters that are not specific to an event type. + return ruleset.run_wrappers(evt, m_filter_by_event_type[evt->get_type()], m_ruleset_id, matches); +} + +libsinsp::events::set indexable_ruleset::ruleset_filters::sc_codes() +{ + libsinsp::events::set res; + for(const auto &wrap : m_filters) + { + res.insert(wrap->sc_codes().begin(), wrap->sc_codes().end()); + } + return res; +} + +libsinsp::events::set indexable_ruleset::ruleset_filters::event_codes() +{ + libsinsp::events::set res; + for(const auto &wrap : m_filters) + { + res.insert(wrap->event_codes().begin(), wrap->event_codes().end()); + } + return res; +} diff --git a/userspace/engine/indexable_ruleset.h b/userspace/engine/indexable_ruleset.h new file mode 100644 index 00000000000..ebba44aa660 --- /dev/null +++ b/userspace/engine/indexable_ruleset.h @@ -0,0 +1,177 @@ +// 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 +#include +#include + +#include +#include +#include + +class indexable_ruleset : public filter_ruleset +{ +public: + indexable_ruleset(bool index_by_evttype): + m_index_by_evttype(index_by_evttype){}; + + virtual ~indexable_ruleset(){}; + + // Required to implement filter_ruleset + void clear() override; + + uint64_t enabled_count(uint16_t ruleset_id) override; + + void enabled_evttypes( + std::set &evttypes, + uint16_t ruleset) override; + + libsinsp::events::set enabled_sc_codes( + uint16_t ruleset) override; + + libsinsp::events::set 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 &tags, + uint16_t ruleset_id) override; + + void disable_tags( + const std::set &tags, + uint16_t ruleset_id) override; + + bool run(sinsp_evt *evt, falco_rule &match, uint16_t ruleset_id) override; + bool run(sinsp_evt *evt, std::vector &matches, uint16_t ruleset_id) override; + + // Methods for working with filter wrappers + + struct filter_wrapper + { + virtual const std::string &name() = 0; + virtual const std::set &tags() = 0; + virtual const libsinsp::events::set &sc_codes() = 0; + virtual const libsinsp::events::set &event_codes() = 0; + }; + + typedef std::function &wrap)> filter_wrapper_func; + + typedef std::list> + filter_wrapper_list; + + void add_wrapper(std::shared_ptr wrap); + + uint64_t iterate(filter_wrapper_func func); + + virtual bool run_wrappers(sinsp_evt *evt, filter_wrapper_list &wrappers, uint16_t ruleset_id, std::vector &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 &tags, + bool enabled, + uint16_t ruleset_id); + + // A group of filters all having the same ruleset + class ruleset_filters + { + public: + ruleset_filters(bool index_by_evttype, uint16_t ruleset_id): + m_index_by_evttype(index_by_evttype), m_ruleset_id(ruleset_id) {} + + virtual ~ruleset_filters(){}; + + void add_filter(std::shared_ptr wrap); + void remove_filter(std::shared_ptr wrap); + + uint64_t num_filters(); + + inline const std::set> &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 &matches); + + libsinsp::events::set sc_codes(); + + libsinsp::events::set event_codes(); + + private: + void add_wrapper_to_list(filter_wrapper_list &wrappers, std::shared_ptr wrap); + void remove_wrapper_from_list(filter_wrapper_list &wrappers, std::shared_ptr wrap); + + // If false, or if a wrapper has no event codes, all + // filters will be added to m_filter_all_event_types + bool m_index_by_evttype; + + 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 m_filter_by_event_type; + + filter_wrapper_list m_filter_all_event_types; + + // All filters added. Used to make num_filters() fast. + std::set> m_filters; + }; + + // Vector indexes from ruleset id to set of rules. + std::vector> m_rulesets; + + // All filters added. The set of enabled filters is held in m_rulesets + std::set> m_filters; + + bool m_index_by_evttype; +};