From 5984e0fd5296757574191496e183843d84f2a0c7 Mon Sep 17 00:00:00 2001 From: Andrea Terzolo Date: Fri, 22 Nov 2024 17:11:11 +0100 Subject: [PATCH] new(converter): add the scap file converter Signed-off-by: Andrea Terzolo --- driver/ppm_events_public.h | 2 + test/libscap/CMakeLists.txt | 5 + .../engines/savefile/convert_event_test.h | 139 ++++++ .../engines/savefile/converter.cpp | 23 + .../libscap/engine/savefile/CMakeLists.txt | 8 +- .../engine/savefile/converter/CMakeLists.txt | 23 + .../engine/savefile/converter/converter.cpp | 404 ++++++++++++++++++ .../engine/savefile/converter/converter.h | 42 ++ .../engine/savefile/converter/debug_macro.h | 33 ++ .../engine/savefile/converter/results.h | 25 ++ .../libscap/engine/savefile/converter/table.h | 26 ++ .../libscap/engine/savefile/converter/types.h | 68 +++ 12 files changed, 796 insertions(+), 2 deletions(-) create mode 100644 test/libscap/test_suites/engines/savefile/convert_event_test.h create mode 100644 test/libscap/test_suites/engines/savefile/converter.cpp create mode 100644 userspace/libscap/engine/savefile/converter/CMakeLists.txt create mode 100644 userspace/libscap/engine/savefile/converter/converter.cpp create mode 100644 userspace/libscap/engine/savefile/converter/converter.h create mode 100644 userspace/libscap/engine/savefile/converter/debug_macro.h create mode 100644 userspace/libscap/engine/savefile/converter/results.h create mode 100644 userspace/libscap/engine/savefile/converter/table.h create mode 100644 userspace/libscap/engine/savefile/converter/types.h diff --git a/driver/ppm_events_public.h b/driver/ppm_events_public.h index 2a6d9fca43..647856bfae 100644 --- a/driver/ppm_events_public.h +++ b/driver/ppm_events_public.h @@ -2056,6 +2056,8 @@ enum ppm_event_flags { // overhead to full event capture */ SUPPORT DROPPED EF_LARGE_PAYLOAD = (1 << 11), /* This event has a large payload, ie: up to UINT32_MAX bytes. DO NOT USE ON syscalls-driven events!!! */ + EF_TMP_CONVERTER_MANAGED = (1 << 12), /* todo!: this must be removed when we will mark ENTER + events as OLD_VERSION */ }; /* diff --git a/test/libscap/CMakeLists.txt b/test/libscap/CMakeLists.txt index cdcc76d182..ebac7e968b 100644 --- a/test/libscap/CMakeLists.txt +++ b/test/libscap/CMakeLists.txt @@ -92,6 +92,11 @@ if(BUILD_LIBSCAP_GVISOR) )# Used for includes endif() +file(GLOB_RECURSE SAVEFILE_TEST_SUITE + "${CMAKE_CURRENT_SOURCE_DIR}/test_suites/engines/savefile/*.cpp" +) +list(APPEND LIBSCAP_TESTS_SOURCES ${SAVEFILE_TEST_SUITE}) + # Summary logs set(LIBSCAP_UNIT_TESTS_PREFIX "[LIBSCAP UNIT TESTS]") message(STATUS "${LIBSCAP_UNIT_TESTS_PREFIX} LIBSCAP_TESTS_SOURCES: ${LIBSCAP_TESTS_SOURCES}") diff --git a/test/libscap/test_suites/engines/savefile/convert_event_test.h b/test/libscap/test_suites/engines/savefile/convert_event_test.h new file mode 100644 index 0000000000..99cc22b014 --- /dev/null +++ b/test/libscap/test_suites/engines/savefile/convert_event_test.h @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: Apache-2.0 +/* +Copyright (C) 2024 The Falco Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +#pragma once +#include +#include +#include +#include +#include +#include + +typedef std::shared_ptr safe_scap_evt_t; +safe_scap_evt_t new_safe_scap_evt(scap_evt *evt) { + return safe_scap_evt_t{evt, free}; +} +class convert_event_test : public testing::Test { + static constexpr uint16_t safe_margin = 100; + +protected: + virtual void TearDown() { + // At every iteration we want to clear the storage in the converter + scap_clear_converter_storage(); + } + safe_scap_evt_t create_safe_scap_event(uint64_t ts, + uint64_t tid, + ppm_event_code event_type, + uint32_t n, + ...) { + char error[SCAP_LASTERR_SIZE] = {'\0'}; + va_list args; + va_start(args, n); + scap_evt *evt = scap_create_event_v(error, ts, tid, event_type, n, args); + va_end(args); + if(evt == NULL) { + throw std::runtime_error("Error creating event: " + std::string(error)); + } + return new_safe_scap_evt(evt); + } + // The expected result can be either CONVERSION_CONTINUE or CONVERSION_COMPLETED + void assert_single_conversion_success(enum conversion_result expected_res, + safe_scap_evt_t evt_to_convert, + safe_scap_evt_t expected_evt) { + char error[SCAP_LASTERR_SIZE] = {'\0'}; + // We assume it's okay to create a new event with the same size as the expected event + auto storage = new_safe_scap_evt((scap_evt *)calloc(1, expected_evt->len)); + // First we check the conversion result matches the expected result + ASSERT_EQ(scap_convert_event(storage.get(), evt_to_convert.get(), error), expected_res) + << "Different conversion results: " << error; + if(!scap_compare_events(storage.get(), expected_evt.get(), error)) { + printf("\nExpected event:\n"); + scap_print_event(expected_evt.get(), PRINT_FULL); + printf("\nConverted event:\n"); + scap_print_event(storage.get(), PRINT_FULL); + FAIL() << error; + } + } + void assert_single_conversion_failure(safe_scap_evt_t evt_to_convert) { + char error[SCAP_LASTERR_SIZE] = {'\0'}; + // We assume it's okay to create a new event with the same size as the expected event + auto storage = new_safe_scap_evt((scap_evt *)calloc(1, evt_to_convert->len)); + // First we check the conversion result matches the expected result + ASSERT_EQ(scap_convert_event(storage.get(), evt_to_convert.get(), error), CONVERSION_ERROR) + << "The conversion is not failed: " << error; + } + void assert_single_conversion_skip(safe_scap_evt_t evt_to_convert) { + char error[SCAP_LASTERR_SIZE] = {'\0'}; + // We assume it's okay to create a new event with the same size as the expected event + auto storage = new_safe_scap_evt((scap_evt *)calloc(1, evt_to_convert->len)); + // First we check the conversion result matches the expected result + ASSERT_EQ(scap_convert_event(storage.get(), evt_to_convert.get(), error), CONVERSION_SKIP) + << "The conversion is not skipped: " << error; + } + void assert_full_conversion(safe_scap_evt_t evt_to_convert, safe_scap_evt_t expected_evt) { + char error[SCAP_LASTERR_SIZE] = {'\0'}; + // Here we need to allocate more space than the expected event because in the middle we + // could have larger events. We could also use `MAX_EVENT_SIZE` but probably it will just + // slowdown tests. + auto to_convert_evt = new_safe_scap_evt( + (scap_evt *)calloc(1, expected_evt->len + convert_event_test::safe_margin)); + auto new_evt = new_safe_scap_evt( + (scap_evt *)calloc(1, expected_evt->len + convert_event_test::safe_margin)); + // We copy the event to convert into the new larger storage since during the conversions it + // could contain larger events than the initial one. + // We copy it in the new event to match the for loop logic. + memcpy(new_evt.get(), evt_to_convert.get(), evt_to_convert->len); + int conv_num = 0; + conversion_result conv_res = CONVERSION_CONTINUE; + for(conv_num = 0; conv_num < MAX_CONVERSION_BOUNDARY && conv_res == CONVERSION_CONTINUE; + conv_num++) { + // Copy the new event into the one to convert for the next conversion. + memcpy(to_convert_evt.get(), new_evt.get(), new_evt->len); + conv_res = scap_convert_event((scap_evt *)new_evt.get(), + (scap_evt *)to_convert_evt.get(), + error); + } + switch(conv_res) { + case CONVERSION_ERROR: + FAIL() << "Unexpected CONVERSION_ERROR: " << error; + case CONVERSION_SKIP: + FAIL() << "Unexpected CONVERSION_SKIP"; + case CONVERSION_CONTINUE: + if(conv_num < MAX_CONVERSION_BOUNDARY) { + FAIL() << "Unexpected CONVERSION_CONTINUE without reaching max boundary"; + } else { + FAIL() << "Unexpected CONVERSION_CONTINUE reaching max boundary"; + } + default: + break; + } + if(!scap_compare_events(new_evt.get(), expected_evt.get(), error)) { + printf("\nExpected event:\n"); + scap_print_event(expected_evt.get(), PRINT_FULL); + printf("\nConverted event:\n"); + scap_print_event(new_evt.get(), PRINT_FULL); + FAIL() << error; + } + } + void assert_event_storage_presence(safe_scap_evt_t expected_evt) { + char error[SCAP_LASTERR_SIZE] = {'\0'}; + int64_t tid = expected_evt.get()->tid; + auto event = scap_retrieve_evt_from_converter_storage(tid); + if(!event) { + FAIL() << "Event with tid " << tid << " not found in the storage"; + } + if(!scap_compare_events(event, expected_evt.get(), error)) { + FAIL() << "Different events: " << error; + } + } +}; diff --git a/test/libscap/test_suites/engines/savefile/converter.cpp b/test/libscap/test_suites/engines/savefile/converter.cpp new file mode 100644 index 0000000000..2b46b80781 --- /dev/null +++ b/test/libscap/test_suites/engines/savefile/converter.cpp @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache-2.0 +/* +Copyright (C) 2024 The Falco Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +#include "convert_event_test.h" + +// todo!: remove it when we will add the first example. +TEST_F(convert_event_test, tmp_example) { + uint64_t ts = 12; + int64_t tid = 25; + + // At the moment we don't have event managed by the converter so this should fail. + assert_single_conversion_failure(create_safe_scap_event(ts, tid, PPME_SYSCALL_OPEN_E, 0)); +} diff --git a/userspace/libscap/engine/savefile/CMakeLists.txt b/userspace/libscap/engine/savefile/CMakeLists.txt index 66c03778c5..22418d3260 100644 --- a/userspace/libscap/engine/savefile/CMakeLists.txt +++ b/userspace/libscap/engine/savefile/CMakeLists.txt @@ -14,7 +14,11 @@ # # Since we have circular dependencies between libscap and the savefile engine, make this library # always static (directly linked into libscap) +add_subdirectory(converter) add_library(scap_engine_savefile STATIC scap_savefile.c scap_reader_gzfile.c scap_reader_buffered.c) -add_dependencies(scap_engine_savefile zlib) -target_link_libraries(scap_engine_savefile PRIVATE scap_engine_noop scap_platform_util ${ZLIB_LIB}) +add_dependencies(scap_engine_savefile zlib scap_savefile_converter) +target_link_libraries( + scap_engine_savefile PRIVATE scap_engine_noop scap_platform_util scap_savefile_converter + ${ZLIB_LIB} +) diff --git a/userspace/libscap/engine/savefile/converter/CMakeLists.txt b/userspace/libscap/engine/savefile/converter/CMakeLists.txt new file mode 100644 index 0000000000..4b09f99d69 --- /dev/null +++ b/userspace/libscap/engine/savefile/converter/CMakeLists.txt @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright (C) 2024 The Falco Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License +# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing permissions and limitations under +# the License. +# + +# Since we have circular dependencies between libscap and the savefile engine, make this library +# always static (directly linked into libscap) +add_library(scap_savefile_converter STATIC converter.cpp) + +target_include_directories( + scap_savefile_converter PRIVATE ${LIBS_DIR} ${LIBS_DIR}/userspace + ${LIBS_DIR}/userspace/libscap/engine/savefile +) diff --git a/userspace/libscap/engine/savefile/converter/converter.cpp b/userspace/libscap/engine/savefile/converter/converter.cpp new file mode 100644 index 0000000000..4a051b1eb0 --- /dev/null +++ b/userspace/libscap/engine/savefile/converter/converter.cpp @@ -0,0 +1,404 @@ +// SPDX-License-Identifier: Apache-2.0 +/* +Copyright (C) 2024 The Falco Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef std::shared_ptr safe_scap_evt_t; + +static inline safe_scap_evt_t safe_scap_evt(scap_evt *evt) { + return safe_scap_evt_t{evt, free}; +} + +// use a shared pointer to store the events +static std::unordered_map evt_storage = {}; + +extern const struct ppm_event_info g_event_info[]; + +static const char *get_event_name(ppm_event_code event_type) { + const struct ppm_event_info *event_info = &g_event_info[event_type]; + return event_info->name; +} + +static char get_direction_char(ppm_event_code event_type) { + if(PPME_IS_ENTER(event_type)) { + return 'E'; + } else { + return 'X'; + } +} + +static void clear_evt(uint64_t tid) { + if(evt_storage.find(tid) != evt_storage.end()) { + evt_storage[tid].reset(); + } +} + +static void store_evt(uint64_t tid, scap_evt *evt) { + // if there was a previous event for this tid, we can overwrite the pointer because it means we + // don't need it anymore. We need to keep the enter event until we retrieve it in the + // corresponding exit event, but if the same thread is doing another enter event it means the + // previous syscall is already completed. + + clear_evt(tid); + + scap_evt *tmp_evt = (scap_evt *)malloc(evt->len); + if(!tmp_evt) { + throw std::runtime_error("Cannot allocate memory for the enter event."); + } + memcpy(tmp_evt, evt, evt->len); + evt_storage[tid] = safe_scap_evt(tmp_evt); +} + +static scap_evt *retrieve_evt(uint64_t tid) { + if(evt_storage.find(tid) != evt_storage.end()) { + return evt_storage[tid].get(); + } + return nullptr; +} + +static uint16_t get_param_len(scap_evt *evt, uint8_t num_param) { + if(evt->nparams <= num_param) { + std::string error = "Try to access len of param num '" + std::to_string(num_param) + + "' for event " + get_event_name((ppm_event_code)evt->type) + "_" + + get_direction_char((ppm_event_code)evt->type) + + " (num parameters: " + std::to_string(evt->type) + ")."; + throw std::runtime_error(error); + } + + // todo!: we need to manage LARGE_PAYLOAD events + uint16_t off_len = sizeof(scap_evt) + sizeof(uint16_t) * num_param; + uint16_t len = 0; + memcpy(&len, (char *)evt + off_len, sizeof(uint16_t)); + return (uint32_t)len; +} + +static char *get_param_ptr(scap_evt *evt, uint8_t num_param) { + if(evt->nparams <= num_param) { + std::string error = "Try to access param num '" + std::to_string(num_param) + + "' for event " + get_event_name((ppm_event_code)evt->type) + "_" + + get_direction_char((ppm_event_code)evt->type) + + " (num parameters: " + std::to_string(evt->type) + ")."; + throw std::runtime_error(error); + } + + char *ptr = (char *)evt + sizeof(scap_evt) + sizeof(uint16_t) * evt->nparams; + uint16_t ptr_off = 0; + for(auto i = 0; i < num_param; i++) { + uint16_t len = 0; + memcpy(&len, (char *)evt + sizeof(scap_evt) + sizeof(uint16_t) * i, sizeof(uint16_t)); + ptr_off += len; + } + + return ptr + ptr_off; +} + +// This writes len + the param +static void push_default_parameter(scap_evt *evt, uint16_t *params_offset, uint8_t param_num) { + // Please ensure that `new_evt->type` is already the final type you want to obtain. + // Otherwise we will access the wrong entry in the event table. + const struct ppm_event_info *event_info = &(g_event_info[evt->type]); + uint16_t len = scap_get_size_bytes_from_type(event_info->params[param_num].type); + char *ptr = scap_get_default_value_from_type(event_info->params[param_num].type); + uint16_t lens_offset = sizeof(scap_evt) + param_num * sizeof(uint16_t); + + PRINT_MESSAGE( + "push default param (%d, type: %d) with len (%d) at {params_offest (%d), " + "lens_offset (%d)}\n", + param_num, + event_info->params[param_num].type, + len, + *params_offset, + lens_offset); + + // If value is NULL, the len should be 0 + memcpy((char *)evt + *params_offset, ptr, len); + *params_offset += len; + memcpy((char *)evt + lens_offset, &len, sizeof(uint16_t)); +} + +// This writes len + the param +static void push_parameter(scap_evt *new_evt, + scap_evt *tmp_evt, + uint16_t *params_offset, + uint8_t new_evt_param_num, + uint8_t tmp_evt_param_num) { + // we need to write the len into the event. + uint16_t lens_offset = sizeof(scap_evt) + new_evt_param_num * sizeof(uint16_t); + uint16_t len = get_param_len(tmp_evt, tmp_evt_param_num); + char *ptr = get_param_ptr(tmp_evt, tmp_evt_param_num); + + PRINT_MESSAGE( + "push param (%d, type: %d) with len (%d) at {params_offest: %d, " + "lens_offset: %d} from event type '%d', param '%d'\n", + new_evt_param_num, + g_event_info[tmp_evt->type].params[tmp_evt_param_num].type, + len, + *params_offset, + lens_offset, + tmp_evt->type, + tmp_evt_param_num); + + memcpy((char *)new_evt + *params_offset, ptr, len); + *params_offset += len; + memcpy((char *)new_evt + lens_offset, &len, sizeof(uint16_t)); +} + +static uint16_t copy_old_params(scap_evt *new_evt, scap_evt *evt_to_convert) { + // Copy the lengths array + uint16_t new_evt_offset = sizeof(scap_evt); + uint16_t old_evt_offset = sizeof(scap_evt); + uint16_t size_to_copy = evt_to_convert->nparams * sizeof(uint16_t); + memcpy((char *)new_evt + new_evt_offset, (char *)evt_to_convert + old_evt_offset, size_to_copy); + + PRINT_MESSAGE( + "Copy lengths array (size %d) from old event offset '%d' to new event " + "offset '%d'\n", + size_to_copy, + old_evt_offset, + new_evt_offset); + + // Copy the parameters (we left some space for the missing lengths) + new_evt_offset = sizeof(scap_evt) + new_evt->nparams * sizeof(uint16_t); + old_evt_offset = sizeof(scap_evt) + evt_to_convert->nparams * sizeof(uint16_t); + size_to_copy = + evt_to_convert->len - (sizeof(scap_evt) + evt_to_convert->nparams * sizeof(uint16_t)); + memcpy((char *)new_evt + new_evt_offset, (char *)evt_to_convert + old_evt_offset, size_to_copy); + + PRINT_MESSAGE( + "Copy parameters (size %d) from old event offset '%d' to new event " + "offset '%d'\n", + size_to_copy, + old_evt_offset, + new_evt_offset); + + return new_evt_offset + size_to_copy; +} + +extern "C" bool is_conversion_needed(scap_evt *evt_to_convert) { + assert(evt_to_convert->type < PPM_EVENT_MAX); + const struct ppm_event_info *event_info = &(g_event_info[evt_to_convert->type]); + + // todo!: we need to cleanup this logic when we can mark enter events as `EF_OLD_VERSION` + + // If the event is not yet managed by the converter we never need a conversion + if((event_info->flags & EF_TMP_CONVERTER_MANAGED) == 0) { + return false; + } + + // If the event is managed by the converter and it is an enter event it will always need a + // conversion. + if(PPME_IS_ENTER(evt_to_convert->type)) { + return true; + } + + // If it is an exit event it needs a conversion when: + // - it is an `EF_OLD_VERSION` + // - the number of parameters is different from the one in the event table + + // If we are a new event type we need to check the number of parameters. + assert(evt_to_convert->nparams <= event_info->nparams); + + // If the number of parameters is different from the one in the event table we need a + // conversion. + if((event_info->flags & EF_OLD_VERSION) || (evt_to_convert->nparams != event_info->nparams)) { + return true; + } + return false; +} + +extern "C" scap_evt *scap_retrieve_evt_from_converter_storage(uint64_t tid) { + return retrieve_evt(tid); +} + +extern "C" void scap_clear_converter_storage() { + evt_storage.clear(); +} + +static conversion_result convert_event(scap_evt *new_evt, + scap_evt *evt_to_convert, + const conversion_info *ci, + char *error) { + ///////////////////////////// + // Dispatch the action + ///////////////////////////// + + uint16_t params_offset = 0; + int param_to_populate = 0; + + switch(ci->action) { + case C_ACTION_SKIP: + return CONVERSION_SKIP; + + case C_ACTION_STORE: + store_evt(evt_to_convert->tid, evt_to_convert); + return CONVERSION_SKIP; + + case C_ACTION_ADD_PARAMS: + memcpy(new_evt, evt_to_convert, sizeof(scap_evt)); + // The new number of params is the previous one plus the number of conversion instructions. + new_evt->nparams = evt_to_convert->nparams + ci->instr.size(); + params_offset = copy_old_params(new_evt, evt_to_convert); + param_to_populate = evt_to_convert->nparams; + break; + + case C_ACTION_CHANGE_TYPE: + memcpy(new_evt, evt_to_convert, sizeof(scap_evt)); + // The new number of params is the number of conversion instructions. + new_evt->nparams = ci->instr.size(); + new_evt->type = ci->desired_type; + params_offset = sizeof(scap_evt) + new_evt->nparams * sizeof(uint16_t); + param_to_populate = 0; + break; + + default: + snprintf(error, SCAP_LASTERR_SIZE, "Unhandled conversion action '%d'.", ci->action); + return CONVERSION_ERROR; + } + + ///////////////////////////// + // Fill the event to its most recent version + ///////////////////////////// + + PRINT_MESSAGE("New event header (the len is still the old one):\n"); + PRINT_EVENT(new_evt, PRINT_HEADER); + + scap_evt *tmp_evt = NULL; + // If this is true at the end of the for loop we will free its memory. + bool used_enter_event = false; + + // We iterate over the instructions + for(int i = 0; i < ci->instr.size(); i++, param_to_populate++) { + PRINT_MESSAGE("Instruction n° %d. Param to populate: %d\n", i, param_to_populate); + + switch(ci->instr[i].flags) { + case C_INSTR_FROM_DEFAULT: + tmp_evt = NULL; + break; + + case C_INSTR_FROM_ENTER: + tmp_evt = retrieve_evt(evt_to_convert->tid); + if(!tmp_evt) { + // It could be due to different reasons: + // - we dropped the enter event in the capture + // - we jump here from a previous conversion. For example, we jump from `BRK_1_X` to + // `BRK_4_X` but in this case we don't have the enter event BRK_4_E because we don't + // convert `BRK_1_E` to `BRK_4_E`. It would be meaningless, they would't bring the + // same info. + break; + } + + // todo!: undestand if we can pretend this is an error or it is a normal situation. + if(tmp_evt->type != evt_to_convert->type - 1) { + snprintf(error, + SCAP_LASTERR_SIZE, + "The enter event for '%s_%c' is not the right one! Event found '%s_%c'.", + get_event_name((ppm_event_code)evt_to_convert->type), + get_direction_char((ppm_event_code)evt_to_convert->type), + get_event_name((ppm_event_code)tmp_evt->type), + get_direction_char((ppm_event_code)tmp_evt->type)); + return CONVERSION_ERROR; + } + used_enter_event = true; + break; + + case C_INSTR_FROM_OLD: + tmp_evt = evt_to_convert; + if(tmp_evt->nparams <= ci->instr[i].param_num) { + // todo!: this sounds like an error but let's see in the future. At the moment we + // fail + snprintf(error, + SCAP_LASTERR_SIZE, + "We want to take parameter '%d' from event '%d' but this event has only " + "'%d' parameters!", + ci->instr[i].param_num, + tmp_evt->type, + tmp_evt->nparams); + return CONVERSION_ERROR; + } + break; + + default: + snprintf(error, + SCAP_LASTERR_SIZE, + "Unknown instruction (flags: %d, param_num: %d).", + ci->instr[i].flags, + ci->instr[i].param_num); + return CONVERSION_ERROR; + } + + if(!tmp_evt) { + push_default_parameter(new_evt, ¶ms_offset, param_to_populate); + } else { + push_parameter(new_evt, + tmp_evt, + ¶ms_offset, + param_to_populate, + ci->instr[i].param_num); + } + } + + if(used_enter_event) { + // We can free the enter event because we don't need it anymore. + clear_evt(evt_to_convert->tid); + } + + new_evt->len = params_offset; + + PRINT_MESSAGE("Final event:\n"); + PRINT_EVENT(new_evt, PRINT_FULL); + return is_conversion_needed(new_evt) ? CONVERSION_CONTINUE : CONVERSION_COMPLETED; +} + +extern "C" conversion_result scap_convert_event(scap_evt *new_evt, + scap_evt *evt_to_convert, + char *error) { + // This should be checked by the caller but just double check here + if(!is_conversion_needed(evt_to_convert)) { + snprintf(error, + SCAP_LASTERR_SIZE, + "Conversion not need for event type '%d' nparams '%d'. Please double check", + evt_to_convert->type, + evt_to_convert->nparams); + return CONVERSION_ERROR; + } + + // If we need a conversion but we don't have an entry in the table we have an error. + auto conv_key = conversion_key{evt_to_convert->type, (uint8_t)evt_to_convert->nparams}; + if(g_conversion_table.find(conv_key) == g_conversion_table.end()) { + snprintf(error, + SCAP_LASTERR_SIZE, + "Event '%d' has '%d' parameters, but we don't handle it in the table.", + evt_to_convert->type, + evt_to_convert->nparams); + return CONVERSION_ERROR; + } + + // If we reached this point we have for sure an entry in the conversion table. + return convert_event(new_evt, evt_to_convert, &g_conversion_table[conv_key], error); +} diff --git a/userspace/libscap/engine/savefile/converter/converter.h b/userspace/libscap/engine/savefile/converter/converter.h new file mode 100644 index 0000000000..338486b103 --- /dev/null +++ b/userspace/libscap/engine/savefile/converter/converter.h @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: Apache-2.0 +/* +Copyright (C) 2024 The Falco Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*/ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +typedef struct ppm_evt_hdr scap_evt; + +// 50 consecutive conversions on the same event should be more than enough +#define MAX_CONVERSION_BOUNDARY 50 + +conversion_result scap_convert_event(scap_evt* new_evt, scap_evt* evt_to_convert, char* error); + +bool is_conversion_needed(scap_evt* evt_to_convert); + +// Only for testing purposes +scap_evt* scap_retrieve_evt_from_converter_storage(uint64_t tid); +void scap_clear_converter_storage(); + +#ifdef __cplusplus +}; +#endif diff --git a/userspace/libscap/engine/savefile/converter/debug_macro.h b/userspace/libscap/engine/savefile/converter/debug_macro.h new file mode 100644 index 0000000000..fbceca9376 --- /dev/null +++ b/userspace/libscap/engine/savefile/converter/debug_macro.h @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +/* +Copyright (C) 2024 The Falco Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*/ +#pragma once + +#include + +#define CONVERSION_DEBUGGING 0 + +#if CONVERSION_DEBUGGING +#define PRINT_MESSAGE(...) printf("[DEBUG]: " __VA_ARGS__) +#define PRINT_EVENT(ev, i) \ + printf("\n"); \ + scap_print_event(ev, i); \ + printf("\n"); +#else +#define PRINT_MESSAGE(...) +#define PRINT_EVENT(ev, i) +#endif diff --git a/userspace/libscap/engine/savefile/converter/results.h b/userspace/libscap/engine/savefile/converter/results.h new file mode 100644 index 0000000000..d573b2062b --- /dev/null +++ b/userspace/libscap/engine/savefile/converter/results.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +/* +Copyright (C) 2024 The Falco Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*/ +#pragma once + +typedef enum conversion_result { + CONVERSION_ERROR, + CONVERSION_CONTINUE, + CONVERSION_COMPLETED, + CONVERSION_SKIP, +} conversion_result; diff --git a/userspace/libscap/engine/savefile/converter/table.h b/userspace/libscap/engine/savefile/converter/table.h new file mode 100644 index 0000000000..15047d7b53 --- /dev/null +++ b/userspace/libscap/engine/savefile/converter/table.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +/* +Copyright (C) 2024 The Falco Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*/ +#pragma once + +#include +#include +#include + +#include + +static std::unordered_map g_conversion_table = {}; diff --git a/userspace/libscap/engine/savefile/converter/types.h b/userspace/libscap/engine/savefile/converter/types.h new file mode 100644 index 0000000000..1548701649 --- /dev/null +++ b/userspace/libscap/engine/savefile/converter/types.h @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: Apache-2.0 +/* +Copyright (C) 2024 The Falco Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*/ +#pragma once + +#include +#include + +enum conversion_instruction_flags { + C_NO_INSTR = 0, // This should be never called + C_INSTR_FROM_OLD, // Take the parameter from the old event + C_INSTR_FROM_ENTER, // Take the parameter from the enter event + C_INSTR_FROM_DEFAULT, // Generate the default parameter +}; + +// Conversion actions +enum conversion_action { + C_ACTION_UNKNOWN = 0, + C_ACTION_SKIP, + C_ACTION_STORE, + C_ACTION_ADD_PARAMS, + C_ACTION_CHANGE_TYPE, +}; + +struct conversion_instruction { + uint8_t flags = 0; + uint8_t param_num = 0; +}; + +struct conversion_key { + uint16_t event_code = 0; + uint8_t param_num = 0; + + // Comparison operator for equality (needed by std::unordered_map) + bool operator==(const conversion_key& other) const { + return event_code == other.event_code && param_num == other.param_num; + } +}; + +namespace std { +template<> +struct hash { + size_t operator()(const conversion_key& key) const { + // Combine the hash of event_code and param_num + return std::hash()(key.event_code) ^ (std::hash()(key.param_num) << 1); + } +}; +} // namespace std + +struct conversion_info { + uint8_t action = 0; + uint16_t desired_type = 0; // Needed only when action is `C_ACTION_CHANGE_TYPE` + std::vector instr = {}; +};