diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c9b08dfc20..727c3418278 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,6 +27,11 @@ if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" AND CMAKE_SYSTEM_NAME MATCHES "Linux endif() endif() +option(BUILD_FALCO_MODERN_BPF "Build modern BPF support for Falco" OFF) +if (BUILD_FALCO_MODERN_BPF) + add_definitions(-DHAS_MODERN_BPF) +endif() + # We shouldn't need to set this, see https://gitlab.kitware.com/cmake/cmake/-/issues/16419 option(EP_UPDATE_DISCONNECTED "ExternalProject update disconnected" OFF) if (${EP_UPDATE_DISCONNECTED}) @@ -114,6 +119,11 @@ set(DRIVER_NAME "falco") set(DRIVER_DEVICE_NAME "falco") set(DRIVERS_REPO "https://download.falco.org/driver") +# If no path is provided, try to search the BPF probe in: `home/.falco/falco-bpf.o` +# This is the same fallback that we had in the libraries: `SCAP_PROBE_BPF_FILEPATH`. +set(FALCO_PROBE_BPF_FILEPATH ".${DRIVER_NAME}/${DRIVER_NAME}-bpf.o") +add_definitions(-DFALCO_PROBE_BPF_FILEPATH="${FALCO_PROBE_BPF_FILEPATH}") + if(NOT DEFINED FALCO_COMPONENT_NAME) set(FALCO_COMPONENT_NAME "${CMAKE_PROJECT_NAME}") endif() diff --git a/cmake/modules/driver.cmake b/cmake/modules/driver.cmake index f0742ccac9a..2e6a35dd202 100644 --- a/cmake/modules/driver.cmake +++ b/cmake/modules/driver.cmake @@ -26,8 +26,8 @@ else() # In case you want to test against another driver version (or branch, or commit) just pass the variable - # ie., `cmake -DDRIVER_VERSION=dev ..` if(NOT DRIVER_VERSION) - set(DRIVER_VERSION "b4c198773bf05486e122f6d3f7f63be125242413") - set(DRIVER_CHECKSUM "SHA256=e85fa42a0b58ba21ca7efb38c20ce25207f4816245bdf154e6b9a037a1cce930") + set(DRIVER_VERSION "ec10e7b93aa3605208a36f2e301e79e0c66a9da9") + set(DRIVER_CHECKSUM "SHA256=707b751998534bf1d4537c945868a6f81239d57d1c8e2dd3ec7d48eaa5e9ea53") endif() # cd /path/to/build && cmake /path/to/source @@ -45,4 +45,4 @@ set(DRIVER_NAME "falco") set(DRIVER_PACKAGE_NAME "falco") set(DRIVER_COMPONENT_NAME "falco-driver") -add_subdirectory(${DRIVER_SOURCE_DIR} ${PROJECT_BINARY_DIR}/driver) \ No newline at end of file +add_subdirectory(${DRIVER_SOURCE_DIR} ${PROJECT_BINARY_DIR}/driver) diff --git a/cmake/modules/falcosecurity-libs.cmake b/cmake/modules/falcosecurity-libs.cmake index 430c0c26ecd..7a23d607ab5 100644 --- a/cmake/modules/falcosecurity-libs.cmake +++ b/cmake/modules/falcosecurity-libs.cmake @@ -27,8 +27,8 @@ else() # In case you want to test against another falcosecurity/libs version (or branch, or commit) just pass the variable - # ie., `cmake -DFALCOSECURITY_LIBS_VERSION=dev ..` if(NOT FALCOSECURITY_LIBS_VERSION) - set(FALCOSECURITY_LIBS_VERSION "b4c198773bf05486e122f6d3f7f63be125242413") - set(FALCOSECURITY_LIBS_CHECKSUM "SHA256=e85fa42a0b58ba21ca7efb38c20ce25207f4816245bdf154e6b9a037a1cce930") + set(FALCOSECURITY_LIBS_VERSION "ec10e7b93aa3605208a36f2e301e79e0c66a9da9") + set(FALCOSECURITY_LIBS_CHECKSUM "SHA256=707b751998534bf1d4537c945868a6f81239d57d1c8e2dd3ec7d48eaa5e9ea53") endif() # cd /path/to/build && cmake /path/to/source @@ -49,7 +49,6 @@ if(MUSL_OPTIMIZED_BUILD) add_definitions(-DMUSL_OPTIMIZED) endif() -set(SCAP_BPF_PROBE_ENV_VAR_NAME "FALCO_BPF_PROBE") set(SCAP_HOST_ROOT_ENV_VAR_NAME "HOST_ROOT") if(NOT LIBSCAP_DIR) @@ -61,6 +60,9 @@ set(LIBSINSP_DIR "${FALCOSECURITY_LIBS_SOURCE_DIR}") # configure gVisor support set(BUILD_LIBSCAP_GVISOR ${BUILD_FALCO_GVISOR} CACHE BOOL "") +# configure momdern BPF support +set(BUILD_LIBSCAP_MODERN_BPF ${BUILD_FALCO_MODERN_BPF} CACHE BOOL "") + # explicitly disable the tests/examples of this dependency set(CREATE_TEST_TARGETS OFF CACHE BOOL "") set(BUILD_LIBSCAP_EXAMPLES OFF CACHE BOOL "") @@ -84,4 +86,4 @@ endif() include(driver) include(libscap) -include(libsinsp) \ No newline at end of file +include(libsinsp) diff --git a/falco.yaml b/falco.yaml index 755e7f21994..88751bb531e 100644 --- a/falco.yaml +++ b/falco.yaml @@ -309,3 +309,10 @@ metadata_download: max_mb: 100 chunk_wait_us: 1000 watch_freq_sec: 1 + +# This is the dimension that a single buffer in our drivers will have. (BPF, kmod, modern BPF) +# Please note: +# - This number is expressed in bytes. +# - This number must be a multiple of your system page size, otherwise the allocation will fail. +# - If you leave `0`, every driver will set its internal default dimension. +single_buffer_dimension: 0 diff --git a/userspace/falco/app_actions/load_plugins.cpp b/userspace/falco/app_actions/load_plugins.cpp index f767e7c5878..03a6e17caf9 100644 --- a/userspace/falco/app_actions/load_plugins.cpp +++ b/userspace/falco/app_actions/load_plugins.cpp @@ -55,7 +55,8 @@ application::run_result application::load_plugins() } loaded_plugin = plugin; m_state->enabled_sources = {plugin->event_source()}; - m_state->inspector->set_input_plugin(p.m_name, p.m_open_params); + m_state->m_plugin_name = p.m_name; + m_state->m_plugin_open_params = p.m_open_params; } // Init filtercheck list for the plugin's source and add the diff --git a/userspace/falco/app_actions/open_inspector.cpp b/userspace/falco/app_actions/open_inspector.cpp index 3fcc4b94e97..5ed420fc5a5 100644 --- a/userspace/falco/app_actions/open_inspector.cpp +++ b/userspace/falco/app_actions/open_inspector.cpp @@ -22,73 +22,125 @@ limitations under the License. using namespace falco::app; +#define FALCO_BPF_ENV_VARIABLE "FALCO_BPF_PROBE" +#define MAX_PROBE_PATH_SIZE 4096 + typedef std::function inspector)> open_t; application::run_result application::open_inspector() { // Notify engine that we finished loading and enabling all rules m_state->engine->complete_rule_loading(); - - if(is_capture_mode()) + + /// TODO: in a future change we can unify how we open different engines... + /// today we use: flags, env variables, configuration files... + if(is_capture_mode()) /* Savefile engine. */ { // Try to open the trace file as a // capture file first. - try { - m_state->inspector->open(m_options.trace_filename); + try + { + m_state->inspector->open_savefile(m_options.trace_filename, 0); falco_logger::log(LOG_INFO, "Reading system call events from file: " + m_options.trace_filename + "\n"); } catch(sinsp_exception &e) { - return run_result::fatal("Could not open trace filename " + m_options.trace_filename + " for reading: " + e.what()); + return run_result::fatal("Cannot open trace filename " + m_options.trace_filename + " for reading: " + e.what()); } } - else + else if(m_state->m_plugin_name != "") /* plugin engine. */ { try { - if(m_options.userspace) - { - // open_udig() is the underlying method used in the capture code to parse userspace events from the kernel. - // - // Falco uses a ptrace(2) based userspace implementation. - // Regardless of the implementation, the underlying method remains the same. - m_state->inspector->open_udig(); - } - else if(m_options.gvisor_config != "") - { - falco_logger::log(LOG_INFO, "Enabled event collection from gVisor. Configuration path: " + m_options.gvisor_config); - m_state->inspector->open_gvisor(m_options.gvisor_config, m_options.gvisor_root); - } - else - { - m_state->inspector->open(); - } + m_state->inspector->open_plugin(m_state->m_plugin_name, m_state->m_plugin_open_params); + falco_logger::log(LOG_INFO, "Starting capture with plugin: " + m_state->m_plugin_name + "\n"); + } + catch(sinsp_exception &e) + { + return run_result::fatal("Cannot use '" + m_state->m_plugin_name + "' plugin: " + e.what()); + } + } + else if(m_options.userspace) /* udig engine. */ + { + // open_udig() is the underlying method used in the capture code to parse userspace events from the kernel. + // + // Falco uses a ptrace(2) based userspace implementation. + // Regardless of the implementation, the underlying method remains the same. + try + { + m_state->inspector->open_udig(m_state->config->m_single_buffer_dimension); + falco_logger::log(LOG_INFO, "Starting capture with udig\n"); + } + catch(sinsp_exception &e) + { + return run_result::fatal("Cannot use udig: " + std::string(e.what())); + } + } + else if(m_options.gvisor_config != "") /* gvisor engine. */ + { + try + { + m_state->inspector->open_gvisor(m_options.gvisor_config, m_options.gvisor_root); + falco_logger::log(LOG_INFO, "Starting capture with gVisor. Configuration path: " + m_options.gvisor_config); } catch(sinsp_exception &e) { - // If syscall input source is enabled and not through userspace instrumentation - if (is_syscall_source_enabled() && !m_options.userspace) + return run_result::fatal("Cannot use gVisor: " + std::string(e.what())); + } + } + else if(m_options.modern_bpf) /* modern BPF engine. */ + { + try + { + m_state->inspector->open_modern_bpf(m_state->config->m_single_buffer_dimension); + falco_logger::log(LOG_INFO, "Starting capture with modern BPF probe."); + } + catch(sinsp_exception &e) + { + return run_result::fatal("Cannot use the modern BPF probe: " + std::string(e.what())); + } + } + else if(getenv(FALCO_BPF_ENV_VARIABLE) != NULL) /* BPF engine. */ + { + try + { + const char *bpf_probe_path = std::getenv(FALCO_BPF_ENV_VARIABLE); + /* If the path is empty try to load the probe from the default path. */ + if(strncmp(bpf_probe_path, "", 1) == 0) { - // Try to insert the Falco kernel module - if(system("modprobe " DRIVER_NAME " > /dev/null 2> /dev/null")) + const char *home = std::getenv("HOME"); + if(!home) { - falco_logger::log(LOG_ERR, "Unable to load the driver.\n"); + return run_result::fatal("Cannot get the env variable 'HOME'"); } - m_state->inspector->open(); - } - else - { - return run_result::fatal(e.what()); + char full_path[MAX_PROBE_PATH_SIZE]; + snprintf(full_path, MAX_PROBE_PATH_SIZE, "%s/%s", home, FALCO_PROBE_BPF_FILEPATH); + bpf_probe_path = full_path; } + m_state->inspector->open_bpf(m_state->config->m_single_buffer_dimension, bpf_probe_path); + falco_logger::log(LOG_INFO, "Starting capture with BPF probe. BPF probe path: " + std::string(bpf_probe_path)); + } + catch(sinsp_exception &e) + { + return run_result::fatal("Cannot use the BPF probe: " + std::string(e.what())); + } + } + else /* Kernel module (default). */ + { + try + { + m_state->inspector->open_kmod(m_state->config->m_single_buffer_dimension); + falco_logger::log(LOG_INFO, "Starting capture with Kernel module."); + } + catch(sinsp_exception &e) + { + return run_result::fatal("Cannot use the kernel module: " + std::string(e.what())); } } - /// TODO: we can add a method to the inspector that tells us what - /// is the underline engine used. Right now we print something only - /// in case of BPF engine - if(m_state->inspector->is_bpf_enabled()) + if(m_state->config->m_single_buffer_dimension != 0) { - falco_logger::log(LOG_INFO, "Falco is using the BPF probe\n"); + falco_logger::log(LOG_INFO, "Buffer dimension: " + std::to_string(m_state->config->m_single_buffer_dimension) + " bytes\n"); } // This must be done after the open diff --git a/userspace/falco/app_cmdline_options.cpp b/userspace/falco/app_cmdline_options.cpp index 2833ebbdaf5..c40796b2b42 100644 --- a/userspace/falco/app_cmdline_options.cpp +++ b/userspace/falco/app_cmdline_options.cpp @@ -167,6 +167,9 @@ void cmdline_options::define() ("g,gvisor-config", "Parse events from gVisor using the specified configuration file. A falco-compatible configuration file can be generated with --gvisor-generate-config and can be used for both runsc and Falco.", cxxopts::value(gvisor_config), "") ("gvisor-generate-config", "Generate a configuration file that can be used for gVisor.", cxxopts::value(gvisor_generate_config_with_socket)->implicit_value("/tmp/gvisor.sock"), "") ("gvisor-root", "gVisor root directory for storage of container state. Equivalent to runsc --root flag.", cxxopts::value(gvisor_root), "") +#endif +#ifdef HAS_MODERN_BPF + ("modern-bpf", "Use BPF modern probe to capture new events.", cxxopts::value(modern_bpf)->default_value("false")) #endif ("i", "Print all events that are ignored by default (i.e. without the -A flag) and exit.", cxxopts::value(print_ignored_events)->default_value("false")) #ifndef MINIMAL_BUILD diff --git a/userspace/falco/app_cmdline_options.h b/userspace/falco/app_cmdline_options.h index 427c52f11fa..ec2e1e340dc 100644 --- a/userspace/falco/app_cmdline_options.h +++ b/userspace/falco/app_cmdline_options.h @@ -77,6 +77,7 @@ class cmdline_options { std::vector validate_rules_filenames; bool verbose; bool print_version_info; + bool modern_bpf; bool parse(int argc, char **argv, std::string &errstr); diff --git a/userspace/falco/application.h b/userspace/falco/application.h index a981d01e77e..ac06c7cf80f 100644 --- a/userspace/falco/application.h +++ b/userspace/falco/application.h @@ -65,6 +65,9 @@ class application { bool terminate; bool reopen_outputs; + std::string m_plugin_name; + std::string m_plugin_open_params; + std::shared_ptr config; std::shared_ptr outputs; std::shared_ptr engine; diff --git a/userspace/falco/configuration.cpp b/userspace/falco/configuration.cpp index 2ee28af6cdd..8cdc5ea1fb3 100644 --- a/userspace/falco/configuration.cpp +++ b/userspace/falco/configuration.cpp @@ -212,6 +212,10 @@ void falco_configuration::init(string conf_filename, const vector &cmdli m_webserver_ssl_enabled = m_config->get_scalar("webserver.ssl_enabled", false); m_webserver_ssl_certificate = m_config->get_scalar("webserver.ssl_certificate", "/etc/falco/falco.pem"); + // we put this value in the configuration file because in this way we can change the dimension + // at every reload. + m_single_buffer_dimension = m_config->get_scalar("single_buffer_dimension", 0); + std::list syscall_event_drop_acts; m_config->get_sequence(syscall_event_drop_acts, "syscall_event_drops.actions"); diff --git a/userspace/falco/configuration.h b/userspace/falco/configuration.h index 41fc1525ca7..99bdf4a39ef 100644 --- a/userspace/falco/configuration.h +++ b/userspace/falco/configuration.h @@ -268,6 +268,9 @@ class falco_configuration uint32_t m_metadata_download_chunk_wait_us; uint32_t m_metadata_download_watch_freq_sec; + // Dimension of a single buffer in our drivers (BPF, kmod, modern BPF) + uint64_t m_single_buffer_dimension; + std::vector m_plugins; private: