diff --git a/userspace/engine/rule_loader_collector.cpp b/userspace/engine/rule_loader_collector.cpp index 89e625e1..7f679e74 100644 --- a/userspace/engine/rule_loader_collector.cpp +++ b/userspace/engine/rule_loader_collector.cpp @@ -14,591 +14,21 @@ See the License for the specific language governing permissions and limitations under the License. */ -#include "falco_engine.h" -#include "falco_utils.h" -#include "rule_loader.h" -#include "filter_macro_resolver.h" -#include "filter_evttype_resolver.h" -#include "filter_warning_resolver.h" -#include #include -#include +#include -#define MAX_VISIBILITY ((uint32_t) -1) +#include "falco_engine.h" +#include "rule_loader_collector.h" #define THROW(cond, err, ctx) { if ((cond)) { throw rule_loader::rule_load_exception(falco::load_result::LOAD_ERR_VALIDATE, (err), (ctx)); } } -using namespace falco; -static string s_container_info_fmt = "%container.info"; -static string s_default_extra_fmt = "%container.name (id=%container.id)"; - -using namespace std; -using namespace libsinsp::filter; - -static const std::string item_type_strings[] = { - "value for", - "exceptions", - "exception", - "exception values", - "exception value", - "rules content", - "rules content item", - "required_engine_version", - "required plugin versions", - "required plugin versions entry", - "required plugin versions alternative", - "list", - "list item", - "macro", - "macro condition", - "rule", - "rule condition", - "condition expression", - "rule output", - "rule output expression", - "rule priority" -}; - -const std::string& rule_loader::context::item_type_as_string(enum item_type it) -{ - return item_type_strings[it]; -} - -rule_loader::context::context(const std::string& name) -{ - // This ensures that every context has one location, even if - // that location is effectively the whole document. - location loc = {name, position(), rule_loader::context::RULES_CONTENT, ""}; - m_locs.push_back(loc); -} - -rule_loader::context::context(const YAML::Node &item, - const item_type item_type, - const std::string item_name, - const context& parent) -{ - init(parent.name(), position(item.Mark()), item_type, item_name, parent); -} - -rule_loader::context::context(const libsinsp::filter::parser::pos_info& pos, - const std::string& condition, - const context& parent) - : alt_content(condition) -{ - - // Contexts based on conditions don't use the - // filename. Instead the "name" is just the condition, and - // uses a short prefix of the condition. - std::string name = "\"" + condition.substr(0, 20) + "...\""; - std::replace(name.begin(), name.end(), '\n', ' '); - std::replace(name.begin(), name.end(), '\r', ' '); - - std::string item_name = ""; - - // Convert the parser position to a context location. Both - // they have the same basic info (position, line, column). - // parser line/columns are 1-indexed while yaml marks are - // 0-indexed, though. - position condpos; - condpos.pos = pos.idx; - condpos.line = pos.line-1; - condpos.column = pos.col-1; - - init(name, condpos, rule_loader::context::CONDITION_EXPRESSION, item_name, parent); -} - -const std::string& rule_loader::context::name() const -{ - // All valid contexts should have at least one location. - if(m_locs.empty()) - { - throw falco_exception("rule_loader::context without location?"); - } - - return m_locs.front().name; -} - -void rule_loader::context::init(const std::string& name, - const position& pos, - const item_type item_type, - const std::string item_name, - const context& parent) -{ - // Copy parent locations - m_locs = parent.m_locs; - - // Add current item to back - location loc = {name, pos, item_type, item_name}; - m_locs.push_back(loc); -} - -std::string rule_loader::context::as_string() -{ - std::ostringstream os; - - // All valid contexts should have at least one location. - if(m_locs.empty()) - { - throw falco_exception("rule_loader::context without location?"); - } - - bool first = true; - - for(auto& loc : m_locs) - { - os << (first ? "In " : " "); - first = false; - - os << item_type_as_string(loc.item_type); - if(!loc.item_name.empty()) - { - os << " '" << loc.item_name << "'"; - } - os << ": "; - - os << "(" - << loc.name << ":" - << loc.pos.line << ":" - << loc.pos.column - << ")" << std::endl; - } - - return os.str(); -} - -nlohmann::json rule_loader::context::as_json() -{ - nlohmann::json ret; - - ret["locations"] = nlohmann::json::array(); - - // All valid contexts should have at least one location. - if(m_locs.empty()) - { - throw falco_exception("rule_loader::context without location?"); - } - - for(auto& loc : m_locs) - { - nlohmann::json jloc, jpos; - - jloc["item_type"] = item_type_as_string(loc.item_type); - jloc["item_name"] = loc.item_name; - - jpos["name"] = loc.name; - jpos["line"] = loc.pos.line; - jpos["column"] = loc.pos.column; - jpos["offset"] = loc.pos.pos; - - jloc["position"] = jpos; - - ret["locations"].push_back(jloc); - } - - return ret; -} - -std::string rule_loader::context::snippet(const falco::load_result::rules_contents_t& rules_contents, - size_t snippet_width) const -{ - // All valid contexts should have at least one location. - if(m_locs.empty()) - { - throw falco_exception("rule_loader::context without location?"); - } - - rule_loader::context::location loc = m_locs.back(); - auto it = rules_contents.find(loc.name); - - if(alt_content.empty() && it == rules_contents.end()) - { - return "\n"; - } - - // If not using alt content, the last location's name must be found in rules_contents - const std::string& snip_content = (!alt_content.empty() ? alt_content : it->second.get()); - - if(snip_content.empty()) - { - return "\n"; - } - - size_t from = loc.pos.pos; - - // In some cases like this, where the content ends with a - // dangling property value: - // tags: - // The YAML::Mark position can be past the end of the file. - for(; from > 0 && from >= snip_content.size(); from--); - - // The snippet is generally the line that contains the - // position. So walk backwards from pos to the preceding - // newline, and walk forwards from pos to the following - // newline. - // - // However, some lines can be very very long, so the walk - // forwards/walk backwards is capped at a maximum of - // snippet_width/2 characters in either direction. - for(; from > 0 && snip_content.at(from) != '\n' && (loc.pos.pos - from) < (snippet_width/2); from--); - - size_t to = loc.pos.pos; - for(; to < snip_content.size()-1 && snip_content.at(to) != '\n' && (to - loc.pos.pos) < (snippet_width/2); to++); - - // Don't include the newlines - if(snip_content.at(from) == '\n') - { - from++; - } - if(snip_content.at(to) == '\n') - { - to--; - } - - std::string ret = snip_content.substr(from, to-from+1); - - if(snip_content.empty()) - { - return "\n"; - } - - // Replace the initial/end characters with '...' if the walk - // forwards/backwards was incomplete - if(loc.pos.pos - from >= (snippet_width/2)) - { - ret.replace(0, 3, "..."); - } - - if(to - loc.pos.pos >= (snippet_width/2)) - { - ret.replace(ret.size()-3, 3, "..."); - } - - ret += "\n"; - - // Add a blank line with a marker at the position within the snippet - ret += std::string(loc.pos.pos-from, ' ') + '^' + "\n"; - - return ret; -} - -rule_loader::result::result(const std::string &name) - : name(name), - success(true) -{ -} - -bool rule_loader::result::successful() -{ - return success; -} - -bool rule_loader::result::has_warnings() -{ - return (warnings.size() > 0); -} - -void rule_loader::result::add_error(load_result::error_code ec, const std::string& msg, const context& ctx) -{ - error err = {ec, msg, ctx}; - success = false; - - errors.push_back(err); -} - -void rule_loader::result::add_warning(load_result::warning_code wc, const std::string& msg, const context& ctx) -{ - warning warn = {wc, msg, ctx}; - - warnings.push_back(warn); -} - -const std::string& rule_loader::result::as_string(bool verbose, const rules_contents_t& contents) -{ - if(verbose) - { - return as_verbose_string(contents); - } - else - { - return as_summary_string(); - } -} - -const std::string& rule_loader::result::as_summary_string() -{ - std::ostringstream os; - - if(!res_summary_string.empty()) - { - return res_summary_string; - } - - if(!name.empty()) - { - os << name << ": "; - } - - if(success) - { - os << "Ok"; - - if (!warnings.empty()) - { - os << ", with warnings"; - } - } - else - { - os << "Invalid"; - } - - if(!errors.empty()) - { - os << std::endl; - - os << " " << errors.size() << " errors: ["; - bool first = true; - for(auto &err : errors) - { - if(!first) - { - os << " "; - } - first = false; - - os << load_result::error_code_str(err.ec) - << " (" << load_result::error_str(err.ec) << ")"; - } - os << "]"; - } - - if(!warnings.empty()) - { - os << std::endl; - - os << " " << warnings.size() << " warnings: ["; - bool first = true; - for(auto &warn : warnings) - { - if(!first) - { - os << " "; - } - first = false; - - os << load_result::warning_code_str(warn.wc) - << " (" << load_result::warning_str(warn.wc) << ")"; - } - os << "]"; - } - - res_summary_string = os.str(); - return res_summary_string; -} - -const std::string& rule_loader::result::as_verbose_string(const rules_contents_t& contents) -{ - std::ostringstream os; - - if(!res_verbose_string.empty()) - { - return res_verbose_string; - } - - if(!name.empty()) - { - os << name << ": "; - } - - if(success) - { - os << "Ok"; - - if (!warnings.empty()) - { - os << ", with warnings"; - } - } - else - { - os << "Invalid"; - } - - if (!errors.empty()) - { - os << std::endl; - - os << errors.size() - << " Errors:" << std::endl; - - for(auto &err : errors) - { - os << err.ctx.as_string(); - - os << "------" << std::endl; - os << err.ctx.snippet(contents); - os << "------" << std::endl; - - os << load_result::error_code_str(err.ec) - << " (" << load_result::error_str(err.ec) << "): " - << err.msg - << std::endl; - } - } - if (!warnings.empty()) - { - os << std::endl; - - os << warnings.size() - << " Warnings:" << std::endl; - - for(auto &warn : warnings) - { - os << warn.ctx.as_string(); - - os << "------" << std::endl; - os << warn.ctx.snippet(contents); - os << "------" << std::endl; - - os << load_result::warning_code_str(warn.wc) - << " (" << load_result::warning_str(warn.wc) << "): " - << warn.msg; - os << std::endl; - } - } - - res_verbose_string = os.str(); - return res_verbose_string; -} - -const nlohmann::json& rule_loader::result::as_json(const rules_contents_t& contents) -{ - nlohmann::json j; - - if(!res_json.empty()) - { - return res_json; - } - - j["name"] = name; - j["successful"] = success; - - j["errors"] = nlohmann::json::array(); - - for(auto &err : errors) - { - nlohmann::json jerr; - - jerr["context"] = err.ctx.as_json(); - jerr["context"]["snippet"] = err.ctx.snippet(contents); - - jerr["code"] = load_result::error_code_str(err.ec); - jerr["codedesc"] = load_result::error_desc(err.ec); - jerr["message"] = err.msg; - - j["errors"].push_back(jerr); - } - - j["warnings"] = nlohmann::json::array(); - - for(auto &warn : warnings) - { - nlohmann::json jwarn; - - jwarn["context"] = warn.ctx.as_json(); - jwarn["context"]["snippet"] = warn.ctx.snippet(contents); - - jwarn["code"] = load_result::warning_code_str(warn.wc); - jwarn["codedesc"] = load_result::warning_desc(warn.wc); - jwarn["message"] = warn.msg; - - j["warnings"].push_back(jwarn); - } - - res_json = j; - return res_json; -} - -rule_loader::engine_version_info::engine_version_info(context &ctx) - : ctx(ctx) -{ -} - -rule_loader::plugin_version_info::plugin_version_info() - : ctx("no-filename-given") -{ -} - -rule_loader::plugin_version_info::plugin_version_info(context &ctx) - : ctx(ctx) -{ -} - -rule_loader::list_info::list_info(context &ctx) - : ctx(ctx) -{ -} - -rule_loader::macro_info::macro_info(context &ctx) - : ctx(ctx), cond_ctx(ctx) -{ -} - -rule_loader::rule_exception_info::rule_exception_info(context &ctx) - : ctx(ctx) -{ -} - -rule_loader::rule_info::rule_info(context &ctx) - : ctx(ctx), cond_ctx(ctx), output_ctx(ctx) -{ -} - -// todo(jasondellaluce): this breaks string escaping in lists and exceptions -static void quote_item(string& e) -{ - if (e.find(" ") != string::npos && e[0] != '"' && e[0] != '\'') - { - e = '"' + e + '"'; - } -} - -static void paren_item(string& e) -{ - if(e[0] != '(') - { - e = '(' + e + ')'; - } -} - -static inline bool is_operator_defined(const string& op) +static inline bool is_operator_defined(const std::string& op) { auto ops = libsinsp::filter::parser::supported_operators(); return find(ops.begin(), ops.end(), op) != ops.end(); } -static inline bool is_operator_for_list(const string& op) -{ - auto ops = libsinsp::filter::parser::supported_operators(true); - return find(ops.begin(), ops.end(), op) != ops.end(); -} - -static bool is_format_valid(const falco_source& source, string fmt, string& err) -{ - try - { - shared_ptr formatter; - formatter = source.formatter_factory->create_formatter(fmt); - return true; - } - catch(exception &e) - { - err = e.what(); - return false; - } -} - template static inline void define_info(indexed_vector& infos, T& info, uint32_t id) { @@ -672,237 +102,7 @@ static void validate_exception_info( } } -static void build_rule_exception_infos( - const vector& exceptions, - set& exception_fields, - string& condition) -{ - string tmp; - for (auto &ex : exceptions) - { - string icond; - if(!ex.fields.is_list) - { - for (auto &val : ex.values) - { - THROW(val.is_list, - "Expected values array to contain a list of strings", - ex.ctx) - icond += icond.empty() - ? ("(" + ex.fields.item + " " - + ex.comps.item + " (") - : ", "; - exception_fields.insert(ex.fields.item); - tmp = val.item; - quote_item(tmp); - icond += tmp; - } - icond += icond.empty() ? "" : "))"; - } - else - { - icond = "("; - for (auto &values : ex.values) - { - THROW(ex.fields.items.size() != values.items.size(), - "Fields and values lists must have equal length", - ex.ctx); - icond += icond == "(" ? "" : " or "; - icond += "("; - uint32_t k = 0; - string istr; - for (auto &field : ex.fields.items) - { - icond += k == 0 ? "" : " and "; - if (values.items[k].is_list) - { - istr = "("; - for (auto &v : values.items[k].items) - { - tmp = v.item; - quote_item(tmp); - istr += istr == "(" ? "" : ", "; - istr += tmp; - } - istr += ")"; - } - else - { - istr = values.items[k].item; - if(is_operator_for_list(ex.comps.items[k].item)) - { - paren_item(istr); - } - else - { - quote_item(istr); - } - } - icond += " " + field.item; - icond += " " + ex.comps.items[k].item + " " + istr; - exception_fields.insert(field.item); - k++; - } - icond += ")"; - } - icond += ")"; - if (icond == "()") - { - icond = ""; - } - } - condition += icond.empty() ? "" : " and not " + icond; - } -} - -// todo(jasondellaluce): this breaks string escaping in lists -static bool resolve_list(string& cnd, const rule_loader::list_info& list) -{ - static string blanks = " \t\n\r"; - static string delims = blanks + "(),="; - string new_cnd; - size_t start, end; - bool used = false; - start = cnd.find(list.name); - while (start != string::npos) - { - // the characters surrounding the name must - // be delims of beginning/end of string - end = start + list.name.length(); - if ((start == 0 || delims.find(cnd[start - 1]) != string::npos) - && (end >= cnd.length() || delims.find(cnd[end]) != string::npos)) - { - // shift pointers to consume all whitespaces - while (start > 0 - && blanks.find(cnd[start - 1]) != string::npos) - { - start--; - } - while (end < cnd.length() - && blanks.find(cnd[end]) != string::npos) - { - end++; - } - // create substitution string by concatenating all values - string sub = ""; - for (auto &v : list.items) - { - if (!sub.empty()) - { - sub += ", "; - } - sub += v; - } - // if substituted list is empty, we need to - // remove a comma from the left or the right - if (sub.empty()) - { - if (start > 0 && cnd[start - 1] == ',') - { - start--; - } - else if (end < cnd.length() && cnd[end] == ',') - { - end++; - } - } - // compose new string with substitution - new_cnd = ""; - if (start > 0) - { - new_cnd += cnd.substr(0, start) + " "; - } - new_cnd += sub + " "; - if (end <= cnd.length()) - { - new_cnd += cnd.substr(end); - } - cnd = new_cnd; - start += sub.length() + 1; - used = true; - } - start = cnd.find(list.name, start + 1); - } - return used; -} - -static void resolve_macros( - indexed_vector& macros, - shared_ptr& ast, - uint32_t visibility, - const rule_loader::context &ctx) -{ - filter_macro_resolver macro_resolver; - for (auto &m : macros) - { - if (m.index < visibility) - { - macro_resolver.set_macro(m.name, m.cond_ast); - } - } - macro_resolver.run(ast); - - // Note: only complaining about the first unknown macro - THROW(!macro_resolver.get_unknown_macros().empty(), - std::string("Undefined macro '") - + *macro_resolver.get_unknown_macros().begin() - + "' used in filter.", - ctx); - - for (auto &m : macro_resolver.get_resolved_macros()) - { - macros.at(m)->used = true; - } -} - -// note: there is no visibility order between filter conditions and lists -static shared_ptr parse_condition( - string condition, - indexed_vector& lists, - const rule_loader::context &ctx) -{ - for (auto &l : lists) - { - if (resolve_list(condition, l)) - { - l.used = true; - } - } - libsinsp::filter::parser p(condition); - p.set_max_depth(1000); - try - { - shared_ptr res_ptr(p.parse()); - return res_ptr; - } - catch (const sinsp_exception& e) - { - rule_loader::context parsectx(p.get_pos(), condition, ctx); - - throw rule_loader::rule_load_exception( - load_result::LOAD_ERR_COMPILE_CONDITION, - e.what(), - parsectx); - } -} - -static void apply_output_substitutions( - rule_loader::configuration& cfg, - string& out) -{ - if (out.find(s_container_info_fmt) != string::npos) - { - if (cfg.replace_output_container_info) - { - out = replace(out, s_container_info_fmt, cfg.output_extra); - return; - } - out = replace(out, s_container_info_fmt, s_default_extra_fmt); - } - out += cfg.output_extra.empty() ? "" : " " + cfg.output_extra; -} - -void rule_loader::clear() +void rule_loader::collector::clear() { m_cur_index = 0; m_rule_infos.clear(); @@ -911,20 +111,35 @@ void rule_loader::clear() m_required_plugin_versions.clear(); } -const std::vector& rule_loader::required_plugin_versions() const +const std::vector& rule_loader::collector::required_plugin_versions() const { return m_required_plugin_versions; } -void rule_loader::define(configuration& cfg, engine_version_info& info) +const indexed_vector& rule_loader::collector::lists() const +{ + return m_list_infos; +} + +const indexed_vector& rule_loader::collector::macros() const +{ + return m_macro_infos; +} + +const indexed_vector& rule_loader::collector::rules() const +{ + return m_rule_infos; +} + +void rule_loader::collector::define(configuration& cfg, engine_version_info& info) { auto v = falco_engine::engine_version(); THROW(v < info.version, "Rules require engine version " - + to_string(info.version) + ", but engine version is " + to_string(v), + + std::to_string(info.version) + ", but engine version is " + std::to_string(v), info.ctx); } -void rule_loader::define(configuration& cfg, plugin_version_info& info) +void rule_loader::collector::define(configuration& cfg, plugin_version_info& info) { std::unordered_set plugin_names; for (const auto& req : info.alternatives) @@ -943,12 +158,12 @@ void rule_loader::define(configuration& cfg, plugin_version_info& info) m_required_plugin_versions.push_back(info.alternatives); } -void rule_loader::define(configuration& cfg, list_info& info) +void rule_loader::collector::define(configuration& cfg, list_info& info) { define_info(m_list_infos, info, m_cur_index++); } -void rule_loader::append(configuration& cfg, list_info& info) +void rule_loader::collector::append(configuration& cfg, list_info& info) { auto prev = m_list_infos.at(info.name); THROW(!prev, @@ -958,12 +173,12 @@ void rule_loader::append(configuration& cfg, list_info& info) append_info(prev, info, m_cur_index++); } -void rule_loader::define(configuration& cfg, macro_info& info) +void rule_loader::collector::define(configuration& cfg, macro_info& info) { define_info(m_macro_infos, info, m_cur_index++); } -void rule_loader::append(configuration& cfg, macro_info& info) +void rule_loader::collector::append(configuration& cfg, macro_info& info) { auto prev = m_macro_infos.at(info.name); THROW(!prev, @@ -974,12 +189,12 @@ void rule_loader::append(configuration& cfg, macro_info& info) append_info(prev, info, m_cur_index++); } -void rule_loader::define(configuration& cfg, rule_info& info) +void rule_loader::collector::define(configuration& cfg, rule_info& info) { auto source = cfg.sources.at(info.source); if (!source) { - cfg.res->add_warning(load_result::LOAD_UNKNOWN_SOURCE, + cfg.res->add_warning(falco::load_result::LOAD_UNKNOWN_SOURCE, "Unknown source " + info.source + ", skipping", info.ctx); return; @@ -1001,7 +216,7 @@ void rule_loader::define(configuration& cfg, rule_info& info) define_info(m_rule_infos, info, m_cur_index++); } -void rule_loader::append(configuration& cfg, rule_info& info) +void rule_loader::collector::append(configuration& cfg, rule_info& info) { auto prev = m_rule_infos.at(info.name); @@ -1055,7 +270,7 @@ void rule_loader::append(configuration& cfg, rule_info& info) append_info(prev, info, m_cur_index++); } -void rule_loader::enable(configuration& cfg, rule_info& info) +void rule_loader::collector::enable(configuration& cfg, rule_info& info) { auto prev = m_rule_infos.at(info.name); THROW(!prev, @@ -1063,247 +278,3 @@ void rule_loader::enable(configuration& cfg, rule_info& info) info.ctx); prev->enabled = info.enabled; } - -rule_loader::rule_load_exception::rule_load_exception(load_result::error_code ec, std::string msg, const context& ctx) - : ec(ec), msg(msg), ctx(ctx) -{ -} - -rule_loader::rule_load_exception::~rule_load_exception() -{ -} - -const char* rule_loader::rule_load_exception::what() -{ - errstr = load_result::error_code_str(ec) + ": " - + msg.c_str(); - - return errstr.c_str(); -} - -void rule_loader::compile_list_infos(configuration& cfg, indexed_vector& out) const -{ - string tmp; - vector used; - for (auto &list : m_list_infos) - { - list_info v = list; - v.items.clear(); - for (auto &item : list.items) - { - auto ref = m_list_infos.at(item); - if (ref && ref->index < list.visibility) - { - used.push_back(ref->name); - for (auto val : ref->items) - { - quote_item(val); - v.items.push_back(val); - } - } - else - { - tmp = item; - quote_item(tmp); - v.items.push_back(tmp); - } - } - v.used = false; - out.insert(v, v.name); - } - for (auto &v : used) - { - out.at(v)->used = true; - } -} - -// note: there is a visibility ordering between macros -void rule_loader::compile_macros_infos( - configuration& cfg, - indexed_vector& lists, - indexed_vector& out) const -{ - set used; - for (auto &m : m_macro_infos) - { - macro_info entry = m; - entry.cond_ast = parse_condition(m.cond, lists, m.cond_ctx); - entry.used = false; - out.insert(entry, m.name); - } - - for (auto &m : out) - { - resolve_macros(out, m.cond_ast, m.visibility, m.ctx); - } -} - - -void rule_loader::compile_rule_infos( - configuration& cfg, - indexed_vector& lists, - indexed_vector& macros, - indexed_vector& out) const -{ - string err, condition; - set warn_codes; - filter_warning_resolver warn_resolver; - for (auto &r : m_rule_infos) - { - // skip the rule if below the minimum priority - if (r.priority > cfg.min_priority) - { - continue; - } - - auto source = cfg.sources.at(r.source); - // note: this is not supposed to happen - - THROW(!source, - std::string("Unknown source ") + r.source, - r.ctx); - - // build filter AST by parsing the condition, building exceptions, - // and resolving lists and macros - falco_rule rule; - - condition = r.cond; - if (!r.exceptions.empty()) - { - build_rule_exception_infos( - r.exceptions, rule.exception_fields, condition); - } - auto ast = parse_condition(condition, lists, r.cond_ctx); - resolve_macros(macros, ast, MAX_VISIBILITY, r.ctx); - - // check for warnings in the filtering condition - warn_codes.clear(); - if (warn_resolver.run(ast.get(), warn_codes)) - { - for (auto &w : warn_codes) - { - cfg.res->add_warning(w, "", r.ctx); - } - } - - // build rule output message - rule.output = r.output; - if (r.source == falco_common::syscall_source) - { - apply_output_substitutions(cfg, rule.output); - } - - if(!is_format_valid(*cfg.sources.at(r.source), rule.output, err)) - { - throw rule_load_exception( - load_result::LOAD_ERR_COMPILE_OUTPUT, - err, - r.output_ctx); - } - - // construct rule definition and compile it to a filter - rule.name = r.name; - rule.source = r.source; - rule.description = r.desc; - rule.priority = r.priority; - rule.tags = r.tags; - - auto rule_id = out.insert(rule, rule.name); - out.at(rule_id)->id = rule_id; - - // This also compiles the filter, and might throw a - // falco_exception with details on the compilation - // failure. - try { - source->ruleset->add(*out.at(rule_id), ast); - } - catch (const falco_exception& e) - { - // Allow errors containing "nonexistent field" if - // skip_if_unknown_filter is true - std::string err = e.what(); - - if (err.find("nonexistent field") != string::npos && - r.skip_if_unknown_filter) - { - cfg.res->add_warning( - load_result::LOAD_UNKNOWN_FIELD, - e.what(), - r.cond_ctx); - } - else - { - throw rule_loader::rule_load_exception( - load_result::LOAD_ERR_COMPILE_CONDITION, - e.what(), - r.cond_ctx); - } - } - - // By default rules are enabled/disabled for the default ruleset - if(r.enabled) - { - source->ruleset->enable(rule.name, true, cfg.default_ruleset_id); - } - else - { - source->ruleset->disable(rule.name, true, cfg.default_ruleset_id); - } - - // populate set of event types and emit an special warning - set evttypes = { ppm_event_type::PPME_PLUGINEVENT_E }; - if(rule.source == falco_common::syscall_source) - { - evttypes.clear(); - filter_evttype_resolver().evttypes(ast, evttypes); - if ((evttypes.empty() || evttypes.size() > 100) - && r.warn_evttypes) - { - cfg.res->add_warning( - load_result::LOAD_NO_EVTTYPE, - "Rule matches too many evt.type values. This has a significant performance penalty.", - r.ctx); - } - } - } -} - -void rule_loader::compile(configuration& cfg, indexed_vector& out) const -{ - indexed_vector lists; - indexed_vector macros; - - // expand all lists, macros, and rules - try - { - compile_list_infos(cfg, lists); - compile_macros_infos(cfg, lists, macros); - compile_rule_infos(cfg, lists, macros, out); - } - catch(rule_load_exception &e) - { - cfg.res->add_error(e.ec, e.msg, e.ctx); - } - - // print info on any dangling lists or macros that were not used anywhere - for (auto &m : macros) - { - if (!m.used) - { - cfg.res->add_warning( - load_result::LOAD_UNUSED_MACRO, - "Macro not referred to by any other rule/macro", - m.ctx); - } - } - for (auto &l : lists) - { - if (!l.used) - { - cfg.res->add_warning( - load_result::LOAD_UNUSED_LIST, - "List not referred to by any other rule/macro", - l.ctx); - } - } -} diff --git a/userspace/engine/rule_loader_collector.h b/userspace/engine/rule_loader_collector.h index 5f070c54..1d773dab 100644 --- a/userspace/engine/rule_loader_collector.h +++ b/userspace/engine/rule_loader_collector.h @@ -16,364 +16,20 @@ limitations under the License. #pragma once -#include -#include #include -#include -#include -#include "falco_rule.h" -#include "falco_source.h" -#include "falco_load_result.h" +#include "rule_loader.h" #include "indexed_vector.h" +namespace rule_loader +{ /*! - \brief Ruleset loader of the falco engine + \brief Collector for the ruleset loader of the falco engine */ -class rule_loader +class collector { public: - - class context - { - public: - // The kinds of items that can be in rules - // content. These generally map to yaml items but a - // few are more specific (e.g. "within condition - // expression", "value for yaml node", etc.) - enum item_type { - VALUE_FOR = 0, - EXCEPTIONS, - EXCEPTION, - EXCEPTION_VALUES, - EXCEPTION_VALUE, - RULES_CONTENT, - RULES_CONTENT_ITEM, - REQUIRED_ENGINE_VERSION, - REQUIRED_PLUGIN_VERSIONS, - REQUIRED_PLUGIN_VERSIONS_ENTRY, - REQUIRED_PLUGIN_VERSIONS_ALTERNATIVE, - LIST, - LIST_ITEM, - MACRO, - MACRO_CONDITION, - RULE, - RULE_CONDITION, - CONDITION_EXPRESSION, - RULE_OUTPUT, - RULE_OUTPUT_EXPRESSION, - RULE_PRIORITY - }; - - static const std::string& item_type_as_string(enum item_type it); - - static const size_t default_snippet_width = 160; - - struct position - { - position() : pos(0), line(0), column(0) {}; - position(const YAML::Mark& mark) : pos(mark.pos), line(mark.line), column(mark.column) {}; - ~position() = default; - int pos; - int line; - int column; - }; - - struct location - { - // A name for the content this location refers - // to. Will generally be a filename, can also - // refer to a rule/macro condition when the - // location points into a condition string. - std::string name; - - // The original location in the document - position pos; - - // The kind of item at this location - // (e.g. "list", "macro", "rule", "exception", etc) - context::item_type item_type; - - // The name of this item (e.g. "Write Below Etc", - // etc). - std::string item_name; - }; - - context(const std::string& name); - context(const YAML::Node& item, - item_type item_type, - const std::string item_name, - const context& parent); - - // Build a context from a condition expression + - // parser position. This does not use the original - // yaml content because: - // - YAML block indicators will remove whitespace/newlines/wrapping - // from the YAML node containing the condition expression. - // - When compiling, the condition expression has expanded - // macro and list references with their values. - context(const libsinsp::filter::parser::pos_info& pos, - const std::string& condition, - const context& parent); - - virtual ~context() = default; - - // Return the content name (generally filename) for - // this context - const std::string& name() const; - - // Return a snippet of the provided rules content - // corresponding to this context. - // Uses the provided rules_contents to look up the original - // rules content for a given location name. - // (If this context has a non-empty alt_content, it - // will be used to create the snippet, ignoring the - // provided rules_contents). - std::string snippet(const falco::load_result::rules_contents_t& rules_contents, size_t snippet_width = default_snippet_width) const; - - std::string as_string(); - nlohmann::json as_json(); - - private: - void init(const std::string& name, - const position& pos, - const item_type item_type, - const std::string item_name, - const context& parent); - - // A chain of locations from the current item, its - // parent, possibly older ancestors. - std::vector m_locs; - - // If non-empty, this content will be used when - // creating snippets. Used for contexts involving - // condition expressions. - std::string alt_content; - }; - - struct warning - { - falco::load_result::warning_code wc; - std::string msg; - context ctx; - }; - - struct error - { - falco::load_result::error_code ec; - std::string msg; - context ctx; - }; - - class rule_load_exception : public std::exception - { - public: - rule_load_exception(falco::load_result::error_code ec, std::string msg, const context& ctx); - virtual ~rule_load_exception(); - const char* what(); - - falco::load_result::error_code ec; - std::string msg; - context ctx; - - std::string errstr; - }; - - /*! - \brief Contains the result of loading rule definitions - */ - class result : public falco::load_result - { - public: - result(const std::string &name); - virtual ~result() = default; - - virtual bool successful() override; - virtual bool has_warnings() override; - - virtual const std::string& as_string(bool verbose, const falco::load_result::rules_contents_t& contents) override; - virtual const nlohmann::json& as_json(const falco::load_result::rules_contents_t& contents) override; - - void add_error(falco::load_result::error_code ec, - const std::string& msg, - const context& ctx); - - void add_warning(falco::load_result::warning_code ec, - const std::string& msg, - const context& ctx); - protected: - - const std::string& as_summary_string(); - const std::string& as_verbose_string(const falco::load_result::rules_contents_t& contents); - std::string name; - bool success; - - std::vector errors; - std::vector warnings; - - std::string res_summary_string; - std::string res_verbose_string; - nlohmann::json res_json; - }; - - /*! - \brief Contains the info required to load rule definitions - */ - struct configuration - { - explicit configuration( - const std::string& cont, - const indexed_vector& srcs, - std::string name) - : content(cont), sources(srcs), name(name) - { - res.reset(new result(name)); - } - - const std::string& content; - const indexed_vector& sources; - std::string name; - std::unique_ptr res; - std::string output_extra; - uint16_t default_ruleset_id; - bool replace_output_container_info; - falco_common::priority_type min_priority; - }; - - /*! - \brief Represents infos about an engine version requirement - */ - struct engine_version_info - { - engine_version_info(context &ctx); - ~engine_version_info() = default; - - context ctx; - uint32_t version; - }; - - /*! - \brief Represents infos about a plugin version requirement - */ - struct plugin_version_info - { - struct requirement - { - requirement() = default; - requirement(const std::string n, const std::string v): - name(n), version(v) { } - - std::string name; - std::string version; - }; - - typedef std::vector requirement_alternatives; - - // This differs from the other _info structs by having - // a default constructor. This allows it to be used - // by falco_engine, which aliases the type. - plugin_version_info(); - plugin_version_info(context &ctx); - ~plugin_version_info() = default; - - context ctx; - requirement_alternatives alternatives; - }; - - /*! - \brief Represents infos about a list - */ - struct list_info - { - list_info(context &ctx); - ~list_info() = default; - - context ctx; - bool used; - size_t index; - size_t visibility; - std::string name; - std::vector items; - }; - - /*! - \brief Represents infos about a macro - */ - struct macro_info - { - macro_info(context &ctx); - ~macro_info() = default; - - context ctx; - context cond_ctx; - bool used; - size_t index; - size_t visibility; - std::string name; - std::string cond; - std::shared_ptr cond_ast; - }; - - /*! - \brief Represents infos about a single rule exception - */ - struct rule_exception_info - { - rule_exception_info(context &ctx); - ~rule_exception_info() = default; - - /*! - \brief This is necessary due to the dynamic-typed nature of - exceptions. Each of fields, comps, and values, can either be a - single value or a list of values. This is a simple hack to make - this easier to implement in C++, that is not non-dynamic-typed. - */ - struct entry { - bool is_list; - std::string item; - std::vector items; - - inline bool is_valid() const - { - return (is_list && !items.empty()) - || (!is_list && !item.empty()); - } - }; - - context ctx; - std::string name; - entry fields; - entry comps; - std::vector values; - }; - - /*! - \brief Represents infos about a rule - */ - struct rule_info - { - rule_info(context &ctx); - ~rule_info() = default; - - context ctx; - context cond_ctx; - context output_ctx; - size_t index; - size_t visibility; - std::string name; - std::string cond; - std::string source; - std::string desc; - std::string output; - std::set tags; - std::vector exceptions; - falco_common::priority_type priority; - bool enabled; - bool warn_evttypes; - bool skip_if_unknown_filter; - }; - - virtual ~rule_loader() = default; + virtual ~collector() = default; /*! \brief Erases all the internal state and definitions @@ -381,16 +37,25 @@ public: virtual void clear(); /*! - \brief Uses the internal state to compile a list of falco_rules - */ - virtual void compile(configuration& cfg, indexed_vector& out) const; - - /*! - \brief Returns the set of all required versions for each plugin according - to the internal definitions. + \brief Returns the set of all defined required plugin versions */ virtual const std::vector& required_plugin_versions() const; + /*! + \brief Returns the list of defined lists + */ + virtual const indexed_vector& lists() const; + + /*! + \brief Returns the list of defined macros + */ + virtual const indexed_vector& macros() const; + + /*! + \brief Returns the list of defined rules + */ + virtual const indexed_vector& rules() const; + /*! \brief Defines an info block. If a similar info block is found in the internal state (e.g. another rule with same name), then @@ -417,22 +82,11 @@ public: virtual void enable(configuration& cfg, rule_info& info); private: - void compile_list_infos( - configuration& cfg, - indexed_vector& out) const; - void compile_macros_infos( - configuration& cfg, - indexed_vector& lists, - indexed_vector& out) const; - void compile_rule_infos( - configuration& cfg, - indexed_vector& lists, - indexed_vector& macros, - indexed_vector& out) const; - uint32_t m_cur_index; indexed_vector m_rule_infos; indexed_vector m_macro_infos; indexed_vector m_list_infos; std::vector m_required_plugin_versions; }; + +}; // namespace rule_loader