From f77f8667a1d61845c201c75a07615061877fb846 Mon Sep 17 00:00:00 2001 From: Melissa Kilby Date: Mon, 6 Feb 2023 04:52:55 +0000 Subject: [PATCH] cleanup(tests): add unit tests for configure_interesting_sets Signed-off-by: Melissa Kilby --- unit_tests/CMakeLists.txt | 3 + .../test_configure_interesting_sets.cpp | 214 ++++++++++++++++++ 2 files changed, 217 insertions(+) create mode 100644 unit_tests/falco/app/actions/test_configure_interesting_sets.cpp diff --git a/unit_tests/CMakeLists.txt b/unit_tests/CMakeLists.txt index 0eed2c1d..37883d9d 100644 --- a/unit_tests/CMakeLists.txt +++ b/unit_tests/CMakeLists.txt @@ -25,16 +25,19 @@ FetchContent_MakeAvailable(googletest) file(GLOB_RECURSE ENGINE_TESTS ${CMAKE_CURRENT_SOURCE_DIR}/engine/*.cpp) file(GLOB_RECURSE FALCO_TESTS ${CMAKE_CURRENT_SOURCE_DIR}/falco/*.cpp) +file(GLOB_RECURSE FALCO_APP_ACTIONS_TESTS ${CMAKE_CURRENT_SOURCE_DIR}/falco/app/actions/*.cpp) set(FALCO_UNIT_TESTS_SOURCES "${ENGINE_TESTS}" "${FALCO_TESTS}" + "${FALCO_APP_ACTIONS_TESTS}" ) set(FALCO_UNIT_TESTS_INCLUDES PRIVATE ${CMAKE_SOURCE_DIR}/userspace ${CMAKE_BINARY_DIR}/userspace/falco # we need it to include indirectly `config_falco.h` file + ${CMAKE_BINARY_DIR}/userspace/falco/app/actions ${CMAKE_SOURCE_DIR}/userspace/engine # we need it to include indirectly `falco_common.h` file ) diff --git a/unit_tests/falco/app/actions/test_configure_interesting_sets.cpp b/unit_tests/falco/app/actions/test_configure_interesting_sets.cpp new file mode 100644 index 00000000..688fad85 --- /dev/null +++ b/unit_tests/falco/app/actions/test_configure_interesting_sets.cpp @@ -0,0 +1,214 @@ +/* +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 "evttype_index_ruleset.h" +#include +#include + +using namespace std; +using namespace libsinsp::filter; + +static std::shared_ptr create_factory() +{ + std::shared_ptr ret(new sinsp_filter_factory(NULL)); + return ret; +} + +static std::shared_ptr create_ast( + std::shared_ptr f, std::string fltstr) +{ + libsinsp::filter::parser parser(fltstr); + std::shared_ptr ret(parser.parse()); + return ret; +} + +static std::shared_ptr create_filter( + std::shared_ptr f, + std::shared_ptr ast) +{ + sinsp_filter_compiler compiler(f, ast.get()); + std::shared_ptr filter(compiler.compile()); + return filter; +} + +static std::shared_ptr create_ruleset( + std::shared_ptr f) +{ + std::shared_ptr ret(new evttype_index_ruleset(f)); + return ret; +} + +static std::shared_ptr get_test_rulesets(std::unordered_set fltstrs) +{ + auto f = create_factory(); + auto r = create_ruleset(f); + + for (const auto &fltstr : fltstrs) + { + auto rule_ast = create_ast(f, fltstr); + auto rule_filter = create_filter(f, rule_ast); + falco_rule rule; + rule.name = fltstr; + rule.source = falco_common::syscall_source; + r->add(rule, rule_filter, rule_ast); + r->enable(fltstr, true, 0); + } + return r; +} + +template +std::set unordered_set_to_ordered(std::unordered_set unordered_set) +{ + std::set s; + for(const auto& val : unordered_set) + { + s.insert(val); + } + return s; +} +template std::set unordered_set_to_ordered(std::unordered_set unordered_set); + +void compare_evttypes_names(std::unordered_set &actual, std::unordered_set &expected) +{ + ASSERT_EQ(actual.size(), expected.size()); + std::set actual_sorted = unordered_set_to_ordered(actual); + std::set expected_sorted = unordered_set_to_ordered(expected); + + auto final = actual_sorted.begin(); + auto matching = expected_sorted.begin(); + + for(; final != actual_sorted.end(); final++, matching++) + { + ASSERT_TRUE(*matching == *final); + } +} + +std::unordered_set extract_rules_event_names(std::unique_ptr& inspector, std::shared_ptr r) +{ + std::set rule_events; + r->enabled_evttypes(rule_events, 0); + std::unordered_set ppme_events_codes(rule_events.begin(), rule_events.end()); + return inspector->get_events_names(ppme_events_codes); +} + +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; +} + +void compare_syscalls_subset_names(std::unordered_set &total_enforced, std::unordered_set &subset, bool inverted = false) +{ + ASSERT_GE(total_enforced.size(), subset.size()); + /* Check if each item in subset is in the total_enforced set. */ + unsigned long int counter = 0; + for (const auto &ppm_sc_name : subset) + { + if (total_enforced.find(ppm_sc_name) != total_enforced.end()) + { + counter++; + } + } + if (inverted) + { + ASSERT_EQ(0, counter); + } else + { + ASSERT_EQ(subset.size(), counter); + } +} + +std::unordered_set erase_io_syscalls(std::unordered_set &ppm_sc_of_interest) +{ + std::unordered_set erased_io_syscalls_names = {}; + std::unordered_set cur_ppm_sc_set = ppm_sc_of_interest; + const int bitmask = EC_SYSCALL - 1; + for (const auto &ppm_sc_code : cur_ppm_sc_set) + { + switch(g_infotables.m_syscall_info_table[ppm_sc_code].category & bitmask) + { + case EC_IO_READ: + case EC_IO_WRITE: + ppm_sc_of_interest.erase(ppm_sc_code); + erased_io_syscalls_names.insert(g_infotables.m_syscall_info_table[ppm_sc_code].name); + } + } + return erased_io_syscalls_names; +} + +TEST(ConfigureInterestingSets, configure_interesting_sets) +{ + + std::unique_ptr inspector(new sinsp()); + + /* Test scenario: + * + * Include one I/O syscall + * Include one non syscall event type + * Include one exclusionary syscall definition test ruleset + * Check sinsp enforced syscalls dependencies for: + * - spawned processes + * - network related syscalls + */ + std::unordered_set fltstrs = { + "(evt.type=connect or evt.type=accept)", + "evt.type in (open, ptrace, mmap, execve, read, container)", + "evt.type in (open, execve, mprotect) and not evt.type=mprotect"}; + std::string test_io_syscall = "read"; + std::string test_non_syscall = "container"; + std::unordered_set expected_syscalls_names = { + "connect", "accept", "open", "ptrace", "mmap", "execve"}; + expected_syscalls_names.insert(test_io_syscall); + std::unordered_set expected_evttypes_names = expected_syscalls_names; + expected_evttypes_names.insert(test_non_syscall); + std::unordered_set base_syscalls_sinsp_state_spawned_process = {"clone", "clone3", "fork", "vfork"}; + std::unordered_set base_syscalls_sinsp_state_network = {"socket", "bind", "close"}; + + auto r = get_test_rulesets(fltstrs); + ASSERT_EQ(r->enabled_count(0), fltstrs.size()); + + /* Test if event types names were extracted from each rule in test ruleset. */ + std::unordered_set rules_evttypes_names = extract_rules_event_names(inspector, r); + compare_evttypes_names(rules_evttypes_names, expected_evttypes_names); + + /* Same test again for syscalls events. */ + 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); + compare_evttypes_names(rules_syscalls_names, expected_syscalls_names); + + /* Enforce sinsp state syscalls and test if ruleset syscalls are in final set of syscalls. */ + std::unordered_set ppm_sc_of_interest = inspector->enforce_simple_ppm_sc_set(rules_ppm_sc_set); + std::unordered_set rules_syscalls_names_enforced = inspector->get_syscalls_names(ppm_sc_of_interest); + compare_syscalls_subset_names(rules_syscalls_names_enforced, expected_syscalls_names); + + /* Test if sinsp state enforcement activated required syscalls for test ruleset. */ + compare_syscalls_subset_names(rules_syscalls_names_enforced, base_syscalls_sinsp_state_spawned_process); + compare_syscalls_subset_names(rules_syscalls_names_enforced, base_syscalls_sinsp_state_network); + + /* Test that no I/O syscalls are in the final set. */ + std::unordered_set erased_io_syscalls_names = erase_io_syscalls(ppm_sc_of_interest); + rules_syscalls_names_enforced = inspector->get_syscalls_names(ppm_sc_of_interest); + compare_syscalls_subset_names(rules_syscalls_names_enforced, erased_io_syscalls_names, true); +}