From 91c185a17817c60af61ed1d1220d50bc11891e94 Mon Sep 17 00:00:00 2001 From: Melissa Kilby Date: Tue, 24 Jan 2023 23:16:45 +0000 Subject: [PATCH] cleanup(app_actions): include evttypes from rules in configure_interesting_sets Signed-off-by: Melissa Kilby --- userspace/falco/app/actions/actions.h | 1 + .../app/actions/configure_interesting_sets.h | 23 + userspace/falco/app/actions/helpers.h | 6 +- .../app/actions/helpers_interesting_sets.cpp | 183 +++++++- .../falco/app/actions/init_inspectors.cpp | 5 - .../falco/app/actions/load_rules_files.cpp | 44 -- .../app/actions/print_ignored_events.cpp | 9 +- .../app/actions/print_syscall_events.cpp | 1 - userspace/falco/app/app.cpp | 1 + userspace/falco/application.cpp | 249 +++++++++++ userspace/falco/application.h | 414 ++++++++++++++++++ 11 files changed, 866 insertions(+), 70 deletions(-) create mode 100644 userspace/falco/app/actions/configure_interesting_sets.h create mode 100644 userspace/falco/application.cpp create mode 100644 userspace/falco/application.h diff --git a/userspace/falco/app/actions/actions.h b/userspace/falco/app/actions/actions.h index 740de6d3..a746d1fb 100644 --- a/userspace/falco/app/actions/actions.h +++ b/userspace/falco/app/actions/actions.h @@ -24,6 +24,7 @@ namespace app { namespace actions { falco::app::run_result attach_inotify_signals(falco::app::state& s); +falco::app::run_result configure_interesting_sets(falco::app::state& s); falco::app::run_result configure_syscall_buffer_size(falco::app::state& s); falco::app::run_result create_requested_paths(falco::app::state& s); falco::app::run_result create_signal_handlers(falco::app::state& s); diff --git a/userspace/falco/app/actions/configure_interesting_sets.h b/userspace/falco/app/actions/configure_interesting_sets.h new file mode 100644 index 00000000..11c2f0f3 --- /dev/null +++ b/userspace/falco/app/actions/configure_interesting_sets.h @@ -0,0 +1,23 @@ +/* +Copyright (C) 2023 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 +#include + +std::string concat_syscalls_names(std::unordered_set const syscalls_names); +// TODO interim helper methods below shall be integrated into sinsp APIs +std::unordered_set get_syscalls_ppm_codes(const std::unordered_set syscalls_names); +std::unordered_set get_difference_syscalls_names(std::unordered_set syscalls_names_reference, std::unordered_set syscalls_names_comparison); diff --git a/userspace/falco/app/actions/helpers.h b/userspace/falco/app/actions/helpers.h index d8bdb934..3b5d4342 100644 --- a/userspace/falco/app/actions/helpers.h +++ b/userspace/falco/app/actions/helpers.h @@ -25,7 +25,11 @@ namespace actions { bool check_rules_plugin_requirements(falco::app::state& s, std::string& err); void print_enabled_event_sources(falco::app::state& s); -void configure_interesting_sets(falco::app::state& s); +void extract_rules_event_names(falco::app::state& s, std::unique_ptr& inspector, std::unordered_set& rules_evttypes_names); +void activate_interesting_events(falco::app::state& s, std::unique_ptr& inspector); +void activate_interesting_syscalls(falco::app::state& s, std::unique_ptr& inspector, const std::unordered_set& rules_evttypes_names); +void activate_interesting_kernel_tracepoints(falco::app::state& s, std::unique_ptr& inspector); +void check_for_ignored_events(falco::app::state& s); void format_plugin_info(std::shared_ptr p, std::ostream& os); falco::app::run_result open_offline_inspector(falco::app::state& s); falco::app::run_result open_live_inspector( diff --git a/userspace/falco/app/actions/helpers_interesting_sets.cpp b/userspace/falco/app/actions/helpers_interesting_sets.cpp index c0eddf65..828605bc 100644 --- a/userspace/falco/app/actions/helpers_interesting_sets.cpp +++ b/userspace/falco/app/actions/helpers_interesting_sets.cpp @@ -15,19 +15,74 @@ limitations under the License. */ #include "helpers.h" +#include "actions.h" +#include "configure_interesting_sets.h" +#include +#include +#include using namespace falco::app; using namespace falco::app::actions; -void falco::app::actions::configure_interesting_sets(falco::app::state& s) -{ - /// TODO: in the next future we need to change the interface of `enforce_simple_ppm_sc_set` - /// and `enforce_sinsp_state_tp` APIs, they shouldn't require an inspector to be called! - std::unique_ptr inspector(new sinsp()); +extern sinsp_evttables g_infotables; - /* Please note: here we fill these 2 sets because we are interested in only some features, if we leave - * them empty `libsinsp` will fill them with all the available syscalls and all the available tracepoints! - */ +std::string concat_syscalls_names(std::unordered_set const syscalls_names) +{ + std::set syscalls_names_ordered = {}; + for (const auto &n : syscalls_names) + { + syscalls_names_ordered.insert(n); + } + std::stringstream ss; + std::copy(syscalls_names_ordered.begin(), syscalls_names_ordered.end(), + std::ostream_iterator(ss, ", ")); + std::string syscalls_names_str = ss.str(); + return syscalls_names_str.substr(0, syscalls_names_str.size() - 2); +} + +std::unordered_set get_syscalls_ppm_codes(const std::unordered_set syscalls_names) +{ + std::unordered_set ppm_sc_set = {}; + for (int ppm_sc_code = 0; ppm_sc_code < PPM_SC_MAX; ++ppm_sc_code) + { + std::string ppm_sc_name = g_infotables.m_syscall_info_table[ppm_sc_code].name; + if (syscalls_names.find(ppm_sc_name) != syscalls_names.end()) + { + ppm_sc_set.insert(ppm_sc_code); + } + } + return ppm_sc_set; +} + +std::unordered_set get_difference_syscalls_names(std::unordered_set syscalls_names_reference, std::unordered_set syscalls_names_comparison) +{ + std::unordered_set out = syscalls_names_comparison; + for (const auto &ppm_sc_name : syscalls_names_reference) + { + if (syscalls_names_comparison.find(ppm_sc_name) != syscalls_names_comparison.end()) + { + out.erase(ppm_sc_name); + } + } + return out; +} + +void falco::app::actions::check_for_ignored_events(falco::app::state& s) +{ + /* Get the events from the rules. */ + std::set rule_events; + std::string source = falco_common::syscall_source; + s.engine->evttypes_for_ruleset(source, rule_events); + + /* Get PPME events we consider interesting from the application state as idx codes. */ + std::unique_ptr inspector(new sinsp()); + std::unordered_set ppme_events_codes(rule_events.begin(), rule_events.end()); + + auto event_names = inspector->get_events_names(ppme_events_codes); + for (const auto& n : inspector->get_events_names(s.ppm_event_info_of_interest)) + { + event_names.erase(n); + } /* Here the `libsinsp` state set is not enough, we need other syscalls used in the rules, * so we use the `simple_set`, this `simple_set` contains all the syscalls of the `libsinsp` state @@ -36,7 +91,39 @@ void falco::app::actions::configure_interesting_sets(falco::app::state& s) s.ppm_sc_of_interest = inspector->enforce_simple_ppm_sc_set(); s.ppm_event_info_of_interest = inspector->get_event_set_from_ppm_sc_set(s.ppm_sc_of_interest); - /* Fill-up the set of event infos of interest */ + if(event_names.empty()) + { + return; + } + + /* Get the names of the ignored events (syscall and non syscall events) and print them. */ + std::cerr << "Loaded rules match ignored event types: warning (ignored-evttype): " + concat_syscalls_names(event_names) << std::endl; + std::cerr << "If syscalls in rules include high volume I/O syscalls (-> activate via `-A` flag), else (2) syscalls might be associated with syscalls undefined on your architecture (https://marcin.juszkiewicz.com.pl/download/tables/syscalls.html)" << std::endl; + +} + +void falco::app::actions::extract_rules_event_names(falco::app::state& s, std::unique_ptr& inspector, std::unordered_set& rules_evttypes_names) +{ + /* Get all (positive) PPME events from all rules as idx codes. + * Events names from negative filter expression statements are NOT included. + * PPME events in libsinsp are needed to map each event type into it's enter and exit event if applicable (e.g. for syscall events). + */ + std::set rule_events; + std::string source = falco_common::syscall_source; + s.engine->evttypes_for_ruleset(source, rule_events); + std::unordered_set ppme_events_codes(rule_events.begin(), rule_events.end()); + + /* Translate PPME event idx codes to consolidated event names. + * Those are the exact event type (evt.type) names from the rules and hence also contain non syscall names, e.g. "container". + */ + rules_evttypes_names = inspector->get_events_names(ppme_events_codes); +} + +void falco::app::actions::activate_interesting_events(falco::app::state& s, std::unique_ptr& inspector) +{ + s.ppm_event_info_of_interest = inspector->get_event_set_from_ppm_sc_set(s.ppm_sc_of_interest); + + /* Fill-up the set of event infos of interest. This is needed to ensure the critical non syscall PPME events are activated as well, e.g. container or proc exit events. */ for (uint32_t ev = 2; ev < PPM_EVENT_MAX; ev++) { if (!sinsp::is_old_version_event(ev) @@ -53,10 +140,84 @@ void falco::app::actions::configure_interesting_sets(falco::app::state& s) } } - /* In this case we get the tracepoints for the `libsinsp` state and we remove - * the `sched_switch` tracepoint since it is highly noisy and not so useful + /* Reading a scap file we have no concepts of ignored events we read all we need. */ + if(!s.options.all_events && !s.is_capture_mode()) + { + /* Here we have already initialized the application state with the interesting syscalls, + * so we have to check if any event types used by the loaded rules are not considered by + * Falco interesting set. + */ + falco::app::actions::check_for_ignored_events(s); + } + +} + +void falco::app::actions::activate_interesting_syscalls(falco::app::state& s, std::unique_ptr& inspector, const std::unordered_set& rules_evttypes_names) +{ + + /* Translate PPME event names to PPM syscall idx codes. + * PPM syscall idx codes can be viewed as condensed libsinsp lookup table to map a system call name to it's actual system syscall id (as defined by the Linux kernel). + * Hence here we don't need syscall enter and exit distinction. + */ + std::unordered_set rules_ppm_sc_set = get_syscalls_ppm_codes(rules_evttypes_names); + std::unordered_set rules_syscalls_names = inspector->get_syscalls_names(rules_ppm_sc_set); + if (rules_syscalls_names.size() > 0) + { + falco_logger::log(LOG_INFO, "(" + std::to_string(rules_syscalls_names.size()) + ") syscalls activated in rules: " + concat_syscalls_names(rules_syscalls_names) + "\n"); + } + + /* + * + * DEFAULT OPTION: + * + * Current enforce_simple_ppm_sc_set approach includes multiple steps: + * (1) Enforce all positive syscalls from each Falco rule + * (2) Enforce a static set of syscalls in addition to the syscalls defined in Falco's rules + * (3) Enforce `libsinsp` state set (non-adaptive, not conditioned by rules, but based on PPME event table flags indicating generic sinsp state modifications) + * -> Final set is union of (1), (2) and (3) + * + */ + + /* Derive union of rules_ppm_sc_set (all syscalls defined in Falco rules) and enforced syscalls for libsinsp state and declare ppm_sc_of_interest. */ + s.ppm_sc_of_interest = inspector->enforce_simple_ppm_sc_set(rules_ppm_sc_set); + + /* Derive the diff between the additional syscalls added via libsinsp state enforcement and the syscalls from each Falco rule. */ + std::unordered_set non_rules_syscalls_names = get_difference_syscalls_names(rules_syscalls_names, inspector->get_syscalls_names(s.ppm_sc_of_interest)); + + if (non_rules_syscalls_names.size() > 0) + { + falco_logger::log(LOG_INFO, "+(" + std::to_string(non_rules_syscalls_names.size()) + ") syscalls activated (Falco's set of additional syscalls including syscalls needed for state engine): " + concat_syscalls_names(non_rules_syscalls_names) + "\n"); + } + + std::unordered_set final_syscalls_names = inspector->get_syscalls_names(s.ppm_sc_of_interest); + if (final_syscalls_names.size() > 0) + { + falco_logger::log(LOG_INFO, "(" + std::to_string(final_syscalls_names.size()) + ") syscalls in total activated (final set): " + concat_syscalls_names(final_syscalls_names) + "\n"); + } + +} + +void falco::app::actions::activate_interesting_kernel_tracepoints(falco::app::state& s, std::unique_ptr& inspector) +{ + /* Kernel tracepoints activation + * + * Activate all tracepoints except `sched_switch` tracepoint since it is highly noisy and not so useful * for our state/events enrichment. */ s.tp_of_interest = inspector->enforce_sinsp_state_tp(); s.tp_of_interest.erase(SCHED_SWITCH); } + +falco::app::run_result falco::app::actions::configure_interesting_sets(falco::app::state& s) +{ + + std::unique_ptr inspector(new sinsp()); + std::unordered_set rules_evttypes_names; + + falco::app::actions::extract_rules_event_names(s, inspector, rules_evttypes_names); // when reaching this code all evttypes are valid + falco::app::actions::activate_interesting_syscalls(s, inspector, rules_evttypes_names); + falco::app::actions::activate_interesting_events(s, inspector); + falco::app::actions::activate_interesting_kernel_tracepoints(s, inspector); + + return run_result::ok(); +} diff --git a/userspace/falco/app/actions/init_inspectors.cpp b/userspace/falco/app/actions/init_inspectors.cpp index 500b3cf0..b28580f3 100644 --- a/userspace/falco/app/actions/init_inspectors.cpp +++ b/userspace/falco/app/actions/init_inspectors.cpp @@ -48,11 +48,6 @@ static void init_syscall_inspector(falco::app::state& s, std::shared_ptr inspector->set_snaplen(s.options.snaplen); } - if(!s.options.all_events) - { - configure_interesting_sets(s); - } - inspector->set_hostname_and_port_resolution_mode(false); } diff --git a/userspace/falco/app/actions/load_rules_files.cpp b/userspace/falco/app/actions/load_rules_files.cpp index 293a782d..d0119ef4 100644 --- a/userspace/falco/app/actions/load_rules_files.cpp +++ b/userspace/falco/app/actions/load_rules_files.cpp @@ -24,40 +24,6 @@ limitations under the License. using namespace falco::app; using namespace falco::app::actions; -static void check_for_ignored_events(falco::app::state& s) -{ - /* Get the events from the rules. */ - std::set rule_events; - std::string source = falco_common::syscall_source; - s.engine->evttypes_for_ruleset(source, rule_events); - - /* Get the events we consider interesting from the application state `ppm_sc` codes. */ - std::unique_ptr inspector(new sinsp()); - std::unordered_set events(rule_events.begin(), rule_events.end()); - - auto event_names = inspector->get_events_names(events); - for (const auto& n : inspector->get_events_names(s.ppm_event_info_of_interest)) - { - event_names.erase(n); - } - - if(event_names.empty()) - { - return; - } - - /* Get the names of the ignored events and print them. */ - std::cerr << "Rules match ignored syscall: warning (ignored-evttype):" << std::endl; - std::cerr << "Loaded rules match the following events: "; - bool first = true; - for(const auto& it : event_names) - { - std::cerr << (first ? "" : ", ") << it.c_str(); - first = false; - } - std::cerr << std::endl << "These events might be associated with syscalls undefined on your architecture (please take a look here: https://marcin.juszkiewicz.com.pl/download/tables/syscalls.html). If syscalls are instead defined, you have to run Falco with `-A` to catch these events" << std::endl; -} - falco::app::run_result falco::app::actions::load_rules_files(falco::app::state& s) { std::string all_rules; @@ -150,16 +116,6 @@ falco::app::run_result falco::app::actions::load_rules_files(falco::app::state& s.engine->enable_rule_by_tag(s.options.enabled_rule_tags, true); } - /* Reading a scap file we have no concepts of ignored events we read all we need. */ - if(!s.options.all_events && !s.is_capture_mode()) - { - /* Here we have already initialized the application state with the interesting syscalls, - * so we have to check if any event types used by the loaded rules are not considered by - * Falco interesting set. - */ - check_for_ignored_events(s); - } - if(s.options.all_events && s.options.modern_bpf) { /* Right now the modern BPF probe doesn't support the -A flag, we implemented just diff --git a/userspace/falco/app/actions/print_ignored_events.cpp b/userspace/falco/app/actions/print_ignored_events.cpp index 5bbf9b74..f0e8661d 100644 --- a/userspace/falco/app/actions/print_ignored_events.cpp +++ b/userspace/falco/app/actions/print_ignored_events.cpp @@ -33,14 +33,7 @@ falco::app::run_result falco::app::actions::print_ignored_events(falco::app::sta return run_result::ok(); } - /* Fill the application syscall and tracepoint sets. - * The execution will be interrupted after this call so - * we don't care if we populate these sets even if the `-A` flag - * is not set. - */ - configure_interesting_sets(s); - - /* Search for all the ignored syscalls. */ + /* Search for all the ignored syscalls after having set the syscalls and events of interest in configure_interesting_sets() app action. */ std::unordered_set all_events; for (uint32_t j = 0; j < PPM_EVENT_MAX; j++) { diff --git a/userspace/falco/app/actions/print_syscall_events.cpp b/userspace/falco/app/actions/print_syscall_events.cpp index 7a5e5a52..733f61c2 100644 --- a/userspace/falco/app/actions/print_syscall_events.cpp +++ b/userspace/falco/app/actions/print_syscall_events.cpp @@ -74,7 +74,6 @@ falco::app::run_result falco::app::actions::print_syscall_events(falco::app::sta { if(s.options.list_syscall_events) { - configure_interesting_sets(s); const auto events = get_event_entries(true, s.ppm_event_info_of_interest); if(s.options.markdown) diff --git a/userspace/falco/app/app.cpp b/userspace/falco/app/app.cpp index a8a65501..215f27b7 100644 --- a/userspace/falco/app/app.cpp +++ b/userspace/falco/app/app.cpp @@ -70,6 +70,7 @@ bool falco::app::run(int argc, char** argv, bool& restart, std::string& errstr) falco::app::actions::daemonize, falco::app::actions::init_outputs, falco::app::actions::init_clients, + falco::app::actions::configure_interesting_sets, falco::app::actions::configure_syscall_buffer_size, falco::app::actions::start_grpc_server, falco::app::actions::start_webserver, diff --git a/userspace/falco/application.cpp b/userspace/falco/application.cpp new file mode 100644 index 00000000..98fe7e05 --- /dev/null +++ b/userspace/falco/application.cpp @@ -0,0 +1,249 @@ +/* +Copyright (C) 2022 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. +*/ + +// The falco "app" holds application-level configuration and contains +// the implementation of any subcommand-like behaviors like --list, -i +// (print_ignored_events), etc. + +// It also contains the code to initialize components like the +// inspector, falco engine, etc. + +#include "application.h" +#include "falco_common.h" + +using namespace std::placeholders; + +static inline bool should_take_action_to_signal(std::atomic& v) +{ + // we expected the signal to be received, and we try to set action-taken flag + int value = APP_SIGNAL_SET; + while (!v.compare_exchange_weak( + value, + APP_SIGNAL_ACTION_TAKEN, + std::memory_order_seq_cst, + std::memory_order_seq_cst)) + { + // application already took action, there's no need to do it twice + if (value == APP_SIGNAL_ACTION_TAKEN) + { + return false; + } + + // signal did was not really received, so we "fake" receiving it + if (value == APP_SIGNAL_NOT_SET) + { + v.store(APP_SIGNAL_SET, std::memory_order_seq_cst); + } + + // reset "expected" CAS variable and keep looping until we succeed + value = APP_SIGNAL_SET; + } + return true; +} + +namespace falco { +namespace app { + +std::atomic g_terminate(APP_SIGNAL_NOT_SET); +std::atomic g_restart(APP_SIGNAL_NOT_SET); +std::atomic g_reopen_outputs(APP_SIGNAL_NOT_SET); + +application::run_result::run_result() + : success(true), errstr(""), proceed(true) +{ +} + +application::run_result::~run_result() +{ +} + +application::state::state() + : loaded_sources(), + enabled_sources(), + source_infos(), + plugin_configs(), + ppm_sc_of_interest(), + tp_of_interest(), + syscall_buffer_bytes_size(DEFAULT_DRIVER_BUFFER_BYTES_DIM) +{ + config = std::make_shared(); + engine = std::make_shared(); + offline_inspector = std::make_shared(); + outputs = nullptr; +} + +application::state::~state() +{ +} + +application::application() + : m_initialized(false) +{ +} + +application::~application() +{ +} + +void application::terminate(bool verbose) +{ + if (should_take_action_to_signal(falco::app::g_terminate)) + { + if (verbose) + { + falco_logger::log(LOG_INFO, "SIGINT received, exiting...\n"); + } + } +} + +void application::reopen_outputs(bool verbose) +{ + if (should_take_action_to_signal(falco::app::g_reopen_outputs)) + { + if (verbose) + { + falco_logger::log(LOG_INFO, "SIGUSR1 received, reopening outputs...\n"); + } + if(m_state != nullptr && m_state->outputs != nullptr) + { + m_state->outputs->reopen_outputs(); + } + falco::app::g_reopen_outputs.store(APP_SIGNAL_NOT_SET); + } +} + +void application::restart(bool verbose) +{ + if (should_take_action_to_signal(falco::app::g_restart)) + { + if (verbose) + { + falco_logger::log(LOG_INFO, "SIGHUP received, restarting...\n"); + } + } +} + +bool application::init(int argc, char **argv, std::string &errstr) +{ + if(m_initialized) + { + throw falco_exception("Application already initialized"); + } + + m_state.reset(new state()); + + if(!m_options.parse(argc, argv, errstr)) + { + return false; + } + + for(char **arg = argv; *arg; arg++) + { + if(m_state->cmdline.size() > 0) + { + m_state->cmdline += " "; + } + m_state->cmdline += *arg; + } + + m_initialized = true; + return true; +} + +bool application::run(std::string &errstr, bool &restart) +{ + run_result res; + + // The order here is the order in which the methods will be + // called. Before changing the order, ensure that all + // dependencies are honored (e.g. don't process events before + // loading plugins, opening inspector, etc.). + std::list> run_steps = { + std::bind(&application::load_config, this), + std::bind(&application::print_help, this), + std::bind(&application::print_version, this), + std::bind(&application::print_page_size, this), + std::bind(&application::print_generated_gvisor_config, this), + std::bind(&application::require_config_file, this), + std::bind(&application::print_plugin_info, this), + std::bind(&application::list_plugins, this), + std::bind(&application::load_plugins, this), + std::bind(&application::init_inspectors, this), + std::bind(&application::init_falco_engine, this), + std::bind(&application::list_fields, this), + std::bind(&application::select_event_sources, this), + std::bind(&application::validate_rules_files, this), + std::bind(&application::load_rules_files, this), + std::bind(&application::print_support, this), + std::bind(&application::create_signal_handlers, this), + std::bind(&application::attach_inotify_signals, this), + std::bind(&application::create_requested_paths, this), + std::bind(&application::daemonize, this), + std::bind(&application::init_outputs, this), + std::bind(&application::init_clients, this), + std::bind(&application::configure_interesting_sets, this), + std::bind(&application::print_ignored_events, this), + std::bind(&application::print_syscall_events, this), + std::bind(&application::configure_syscall_buffer_size, this), +#ifndef MINIMAL_BUILD + std::bind(&application::start_grpc_server, this), + std::bind(&application::start_webserver, this), +#endif + std::bind(&application::process_events, this) + }; + + std::list> teardown_steps = { + std::bind(&application::unregister_signal_handlers, this, _1), +#ifndef MINIMAL_BUILD + std::bind(&application::stop_grpc_server, this, _1), + std::bind(&application::stop_webserver, this, _1) +#endif + }; + + for (auto &func : run_steps) + { + res = func(); + + if(!res.proceed) + { + break; + } + } + + for (auto &func : teardown_steps) + { + std::string errstr; + + if(!func(errstr)) + { + // Note only printing warning here--we want all functions + // to occur even if some return errors. + fprintf(stderr, "Could not tear down in run(): %s\n", errstr.c_str()); + } + } + + if(!res.success) + { + errstr = res.errstr; + } + + restart = should_restart(); + + return res.success; +} + +}; // namespace app +}; // namespace falco diff --git a/userspace/falco/application.h b/userspace/falco/application.h new file mode 100644 index 00000000..38bf23f8 --- /dev/null +++ b/userspace/falco/application.h @@ -0,0 +1,414 @@ +/* +Copyright (C) 2022 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. +*/ + +#pragma once + +#include "semaphore.h" +#include "configuration.h" +#include "stats_writer.h" +#ifndef MINIMAL_BUILD +#include "grpc_server.h" +#include "webserver.h" +#include "indexed_vector.h" +#endif + +#include "app_cmdline_options.h" + +#include +#include +#include + +#define APP_SIGNAL_NOT_SET 0 // The signal flag is not set +#define APP_SIGNAL_SET 1 // The signal flag has been set +#define APP_SIGNAL_ACTION_TAKEN 2 // The signal flag has been set and the application took action + +namespace falco { +namespace app { + +// these are used to control the lifecycle of the application +// through signal handlers or internal calls +extern std::atomic g_terminate; +extern std::atomic g_restart; +extern std::atomic g_reopen_outputs; + +class application { +public: + application(); + virtual ~application(); + application(application&&) = default; + application& operator = (application&&) = default; + application(const application&) = delete; + application& operator = (const application&) = delete; + + bool init(int argc, char **argv, std::string &errstr); + + // Returns whether the application completed with errors or + // not. errstr will contain details when run() returns false. + // + // If restart (generally set by signal handlers) is + // true, the application should be restarted instead of + // exiting. + bool run(std::string &errstr, bool &restart); + +private: + // Holds the state used and shared by the below methods that + // actually implement the application. Declared as a + // standalone class to allow for a bit of separation between + // application state and instance variables, and to also defer + // initializing this state until application::init. + struct state + { + // Holds the info mapped for each loaded event source + struct source_info + { + // The index of the given event source in the state's falco_engine, + // as returned by falco_engine::add_source + std::size_t engine_idx; + // The filtercheck list containing all fields compatible + // with the given event source + filter_check_list filterchecks; + // The inspector assigned to this event source. If in capture mode, + // all event source will share the same inspector. If the event + // source is a plugin one, the assigned inspector must have that + // plugin registered in its plugin manager + std::shared_ptr inspector; + }; + + state(); + virtual ~state(); + + std::shared_ptr config; + std::shared_ptr outputs; + std::shared_ptr engine; + + // The set of loaded event sources (by default, the syscall event + // source plus all event sources coming from the loaded plugins) + std::unordered_set loaded_sources; + + // The set of enabled event sources (can be altered by using + // the --enable-source and --disable-source options) + std::unordered_set enabled_sources; + + // Used to load all plugins to get their info. In capture mode, + // this is also used to open the capture file and read its events + std::shared_ptr offline_inspector; + + // List of all the information mapped to each event source + // indexed by event source name + indexed_vector source_infos; + + // List of all plugin configurations indexed by plugin name as returned + // by their sinsp_plugin::name method + indexed_vector plugin_configs; + + std::string cmdline; + + // Set of events we want the driver to capture + std::unordered_set ppm_event_info_of_interest; + + // Set of syscalls we want the driver to capture + std::unordered_set ppm_sc_of_interest; + + // Set of tracepoints we want the driver to capture + std::unordered_set tp_of_interest; + + // Dimension of the syscall buffer in bytes. + uint64_t syscall_buffer_bytes_size; + +#ifndef MINIMAL_BUILD + falco::grpc::server grpc_server; + std::thread grpc_server_thread; + + falco_webserver webserver; +#endif + }; + + // Used in the below methods to indicate how to proceed. + struct run_result { + // Successful result + inline static run_result ok() + { + run_result r; + r.success = true; + r.errstr = ""; + r.proceed = true; + return r; + } + + // Successful result that causes the program to stop + inline static run_result exit() + { + run_result r = ok(); + r.proceed = false; + return r; + } + + // Failure result that causes the program to stop with an error + inline static run_result fatal(const std::string& err) + { + run_result r; + r.success = false; + r.errstr = err; + r.proceed = false; + return r; + } + + // Merges two run results into one + inline static run_result merge(const run_result& a, const run_result& b) + { + auto res = ok(); + res.proceed = a.proceed && b.proceed; + res.success = a.success && b.success; + res.errstr = a.errstr; + if (!b.errstr.empty()) + { + res.errstr += res.errstr.empty() ? "" : "\n"; + res.errstr += b.errstr; + } + return res; + } + + run_result(); + virtual ~run_result(); + run_result(run_result&&) = default; + run_result& operator = (run_result&&) = default; + run_result(const run_result&) = default; + run_result& operator = (const run_result&) = default; + + + // If true, the method completed successfully. + bool success; + // If success==false, details on the error. + std::string errstr; + // If true, subsequent methods should be performed. If + // false, subsequent methods should *not* be performed + // and falco should tear down/exit/restart. + bool proceed; + }; + + // used to synchronize different event source running in parallel + class source_sync_context + { + public: + source_sync_context(falco::semaphore& s) + : m_finished(false), m_joined(false), m_semaphore(s) { } + source_sync_context(source_sync_context&&) = default; + source_sync_context& operator = (source_sync_context&&) = default; + source_sync_context(const source_sync_context&) = delete; + source_sync_context& operator = (const source_sync_context&) = delete; + + inline void finish() + { + bool v = false; + while (!m_finished.compare_exchange_weak( + v, true, + std::memory_order_seq_cst, + std::memory_order_seq_cst)) + { + if (v) + { + throw falco_exception("source_sync_context has been finished twice"); + } + } + m_semaphore.release(); + } + + inline void join() + { + bool v = false; + while (!m_joined.compare_exchange_weak( + v, true, + std::memory_order_seq_cst, + std::memory_order_seq_cst)) + { + if (v) + { + throw falco_exception("source_sync_context has been joined twice"); + } + } + } + + inline bool joined() + { + return m_joined.load(std::memory_order_seq_cst); + } + + inline bool finished() + { + return m_finished.load(std::memory_order_seq_cst); + } + + private: + // set to true when the event processing loop finishes + std::atomic m_finished; + // set to true when the result has been collected after finishing + std::atomic m_joined; + // used to notify the waiting thread when finished gets set to true + falco::semaphore& m_semaphore; + }; + + // Convenience method. Read a sequence of filenames and fill + // in a vector of rules contents. + // Also fill in the provided rules_contents_t with a mapping from + // filename (reference) to content (reference). + // falco_exception if any file could not be read. + template + void read_files(InputIterator begin, InputIterator end, + std::vector& rules_contents, + falco::load_result::rules_contents_t& rc) + { + // Read the contents in a first pass + for(auto it = begin; it != end; it++) + { + std::string &filename = *it; + std::ifstream is; + is.open(filename); + if (!is.is_open()) + { + throw falco_exception("Could not open file " + filename + " for reading"); + } + + std::string rules_content((istreambuf_iterator(is)), + istreambuf_iterator()); + rules_contents.emplace_back(std::move(rules_content)); + } + + // Populate the map in a second pass to avoid + // references becoming invalid. + auto it = begin; + auto rit = rules_contents.begin(); + for(; it != end && rit != rules_contents.end(); it++, rit++) + { + rc.emplace(*it, *rit); + } + + // Both it and rit must be at the end, otherwise + // there's a bug in the above + if(it != end || rit != rules_contents.end()) + { + throw falco_exception("Unexpected mismatch in rules content name/rules content sets?"); + } + } + + // These methods comprise the code the application "runs". The + // order in which the methods run is in application.cpp. + run_result create_signal_handlers(); + run_result attach_inotify_signals(); + run_result daemonize(); + run_result init_falco_engine(); + run_result init_inspectors(); + run_result init_clients(); + run_result init_outputs(); + run_result list_fields(); + run_result list_plugins(); + run_result load_config(); + run_result require_config_file(); + run_result load_plugins(); + run_result load_rules_files(); + run_result create_requested_paths(); + run_result print_generated_gvisor_config(); + run_result print_help(); + run_result print_ignored_events(); + run_result print_plugin_info(); + run_result print_support(); + run_result print_syscall_events(); + run_result print_version(); + run_result print_page_size(); + run_result process_events(); + run_result select_event_sources(); + run_result configure_interesting_sets(); + application::run_result configure_syscall_buffer_size(); +#ifndef MINIMAL_BUILD + run_result start_grpc_server(); + run_result start_webserver(); +#endif + run_result validate_rules_files(); + + // These methods comprise application teardown. The order in + // which the methods run is in application.cpp. + bool close_inspector(std::string &errstr); + bool unregister_signal_handlers(std::string &errstr); +#ifndef MINIMAL_BUILD + bool stop_grpc_server(std::string &errstr); + bool stop_webserver(std::string &errstr); +#endif + + // Methods called by the above methods + int create_dir(const std::string &path); + bool create_handler(int sig, void (*func)(int), run_result &ret); + void configure_output_format(); + void check_for_ignored_events(); + bool check_rules_plugin_requirements(std::string& err); + void extract_rules_event_names(std::unique_ptr& inspector, std::unordered_set& rules_evttypes_names); // should be called before syscalls and events activations + void activate_interesting_syscalls(std::unique_ptr& inspector, const std::unordered_set& rules_evttypes_names); + void activate_interesting_events(std::unique_ptr& inspector); // should be called after calling activate_interesting_syscalls + void activate_interesting_kernel_tracepoints(std::unique_ptr& inspector); // independent of syscalls and events activations in terms of order + void format_plugin_info(std::shared_ptr p, std::ostream& os) const; + run_result open_offline_inspector(); + run_result open_live_inspector(std::shared_ptr inspector, const std::string& source); + void add_source_to_engine(const std::string& src); + void print_enabled_event_sources(); + void init_syscall_inspector(std::shared_ptr inspector, const falco::app::cmdline_options& opts); + run_result do_inspect( + std::shared_ptr inspector, + const std::string& source, // an empty source represents capture mode + std::shared_ptr statsw, + syscall_evt_drop_mgr &sdropmgr, + bool check_drops_and_timeouts, + uint64_t duration_to_tot_ns, + uint64_t &num_evts); + void process_inspector_events( + std::shared_ptr inspector, + std::shared_ptr statsw, + std::string source, // an empty source represents capture mode + application::source_sync_context* sync, + run_result* res) noexcept; + + /* Returns true if we are in capture mode. */ + inline bool is_capture_mode() const + { + return !m_options.trace_filename.empty(); + } + + inline bool is_gvisor_enabled() const + { + return !m_options.gvisor_config.empty(); + } + + // used in signal handlers to control the flow of the application + void terminate(bool verbose=true); + void restart(bool verbose=true); + void reopen_outputs(bool verbose=true); + inline bool should_terminate() + { + return g_terminate.load(std::memory_order_seq_cst) != APP_SIGNAL_NOT_SET; + } + inline bool should_restart() + { + return g_restart.load(std::memory_order_seq_cst) != APP_SIGNAL_NOT_SET; + } + inline bool should_reopen_outputs() + { + return g_reopen_outputs.load(std::memory_order_seq_cst) != APP_SIGNAL_NOT_SET; + } + + std::unique_ptr m_state; + cmdline_options m_options; + bool m_initialized; +}; + +}; // namespace app +}; // namespace falco