diff --git a/plugins/anomalydetection/src/plugin.cpp b/plugins/anomalydetection/src/plugin.cpp index 50c1a4a1..8a616c74 100644 --- a/plugins/anomalydetection/src/plugin.cpp +++ b/plugins/anomalydetection/src/plugin.cpp @@ -17,6 +17,9 @@ limitations under the License. #include "plugin.h" +#include +#include + void anomalydetection::log_error(std::string err_mess) { printf("%s %s\n", PLUGIN_LOG_PREFIX, err_mess.c_str()); @@ -411,18 +414,336 @@ bool anomalydetection::extract(const falcosecurity::extract_fields_input& in) // Parse capability ////////////////////////// +// Adopted from the k8smeta plugin, obtain a param from a sinsp event +static inline sinsp_param get_syscall_evt_param(void* evt, uint32_t num_param) +{ + uint32_t dataoffset = 0; + // pointer to the lengths array inside the event. + auto len = (uint16_t*)((uint8_t*)evt + + sizeof(falcosecurity::_internal::ss_plugin_event)); + for(uint32_t j = 0; j < num_param; j++) + { + // sum lengths of the previous params. + dataoffset += len[j]; + } + return {.param_len = len[num_param], + .param_pointer = + ((uint8_t*)&len + [((falcosecurity::_internal::ss_plugin_event*)evt) + ->nparams]) + + dataoffset}; +} + +std::string anomalydetection::extract_filterchecks_evt_params_fallbacks(const falcosecurity::event_reader &evt, const plugin_sinsp_filterchecks_field& field, const std::string& cwd) +{ + using st = falcosecurity::state_value_type; + + std::string tstr; + uint64_t tuint64 = UINT64_MAX; + uint32_t tuint32 = UINT32_MAX; + int64_t tint64 = -1; + bool tbool; + int64_t ptid = -1; + switch(field.id) + { + + // + // fd related + // + + case plugin_sinsp_filterchecks::TYPE_FDNUM: + { + switch(evt.get_type()) + { + case PPME_SYSCALL_OPEN_X: + case PPME_SYSCALL_CREAT_X: + case PPME_SYSCALL_OPENAT_2_X: + case PPME_SYSCALL_OPENAT2_X: + case PPME_SYSCALL_OPEN_BY_HANDLE_AT_X: + case PPME_SOCKET_ACCEPT_5_X: + case PPME_SOCKET_ACCEPT4_6_X: + { + auto res_param = get_syscall_evt_param(evt.get_buf(), + 0); + if (res_param.param_pointer == nullptr) + { + return tstr; + } + int64_t fd = *(int64_t*)(res_param.param_pointer); + tstr = std::to_string(fd); + break; + } + case PPME_SOCKET_CONNECT_X: + { + auto res_param = get_syscall_evt_param(evt.get_buf(), + 2); + if (res_param.param_pointer == nullptr) + { + return tstr; + } + int64_t fd = *(int64_t*)(res_param.param_pointer); + tstr = std::to_string(fd); + break; + } + default: + break; + } + break; + } + case plugin_sinsp_filterchecks::TYPE_FDNAME: + case plugin_sinsp_filterchecks::TYPE_DIRECTORY: + case plugin_sinsp_filterchecks::TYPE_FILENAME: + { + switch(evt.get_type()) + { + case PPME_SYSCALL_OPEN_X: + case PPME_SYSCALL_CREAT_X: + { + auto res_param = get_syscall_evt_param(evt.get_buf(), + 1); + if (res_param.param_pointer == nullptr) + { + return tstr; + } + tstr = (char*)(res_param.param_pointer); + if(!std::filesystem::path(tstr).is_absolute()) + { + tstr = plugin_anomalydetection::utils::concatenate_paths(cwd, tstr); + } else + { + // concatenate_paths takes care of resolving the path + tstr = plugin_anomalydetection::utils::concatenate_paths("", tstr); + } + } + break; + case PPME_SYSCALL_OPENAT_2_X: + case PPME_SYSCALL_OPENAT2_X: + { + auto res_param = get_syscall_evt_param(evt.get_buf(), + 2); + if (res_param.param_pointer == nullptr) + { + return tstr; + } + tstr = (char*)(res_param.param_pointer); + // cwd passed to the function here is the name extracted from the dirfd + tstr = plugin_anomalydetection::utils::concatenate_paths(cwd, tstr); + } + break; + case PPME_SYSCALL_OPEN_BY_HANDLE_AT_X: + { + + auto res_param = get_syscall_evt_param(evt.get_buf(), + 3); + if (res_param.param_pointer == nullptr) + { + return tstr; + } + tstr = (char*)(res_param.param_pointer); + if(!std::filesystem::path(tstr).is_absolute()) + { + tstr = plugin_anomalydetection::utils::concatenate_paths(cwd, tstr); + } else + { + // concatenate_paths takes care of resolving the path + tstr = plugin_anomalydetection::utils::concatenate_paths("", tstr); + } + } + break; + case PPME_SOCKET_ACCEPT_5_X: + case PPME_SOCKET_ACCEPT4_6_X: + case PPME_SOCKET_CONNECT_X: + { + // todo fix fallbacks as we lack access to sockinfo and it's highly more sophisticated / complex + // auto res_param = get_syscall_evt_param(evt.get_buf(), + // 1); + // if (res_param.param_pointer == nullptr) + // { + // return tstr; + // } + break; + } + default: + break; + } + break; + } + case plugin_sinsp_filterchecks::TYPE_INO: + { + switch(evt.get_type()) + { + case PPME_SYSCALL_OPEN_X: + case PPME_SYSCALL_CREAT_X: + { + auto res_param = get_syscall_evt_param(evt.get_buf(), + 5); + if (res_param.param_pointer == nullptr) + { + return tstr; + } + uint64_t ino = *(uint64_t*)(res_param.param_pointer); + tstr = std::to_string(ino); + } + break; + case PPME_SYSCALL_OPENAT_2_X: + case PPME_SYSCALL_OPENAT2_X: + { + + auto res_param = get_syscall_evt_param(evt.get_buf(), + 7); + if (res_param.param_pointer == nullptr) + { + return tstr; + } + uint64_t ino = *(uint64_t*)(res_param.param_pointer); + tstr = std::to_string(ino); + } + break; + case PPME_SYSCALL_OPEN_BY_HANDLE_AT_X: + { + + auto res_param = get_syscall_evt_param(evt.get_buf(), + 5); + if (res_param.param_pointer == nullptr) + { + return tstr; + } + uint64_t ino = *(uint64_t*)(res_param.param_pointer); + tstr = std::to_string(ino); + } + break; + default: + break; + } + break; + } + case plugin_sinsp_filterchecks::TYPE_DEV: + { + switch(evt.get_type()) + { + case PPME_SYSCALL_OPEN_X: + case PPME_SYSCALL_CREAT_X: + { + auto res_param = get_syscall_evt_param(evt.get_buf(), + 4); + if (res_param.param_pointer == nullptr) + { + return tstr; + } + uint32_t dev = *(uint32_t*)(res_param.param_pointer); + tstr = std::to_string(dev); + } + break; + case PPME_SYSCALL_OPENAT_2_X: + case PPME_SYSCALL_OPENAT2_X: + { + + auto res_param = get_syscall_evt_param(evt.get_buf(), + 6); + if (res_param.param_pointer == nullptr) + { + return tstr; + } + uint32_t dev = *(uint32_t*)(res_param.param_pointer); + tstr = std::to_string(dev); + } + break; + case PPME_SYSCALL_OPEN_BY_HANDLE_AT_X: + { + + auto res_param = get_syscall_evt_param(evt.get_buf(), + 4); + if (res_param.param_pointer == nullptr) + { + return tstr; + } + uint32_t dev = *(uint32_t*)(res_param.param_pointer); + tstr = std::to_string(dev); + } + break; + default: + break; + } + break; + } + case plugin_sinsp_filterchecks::TYPE_FDNAMERAW: + { + switch(evt.get_type()) + { + case PPME_SYSCALL_OPEN_X: + case PPME_SYSCALL_CREAT_X: + { + + auto res_param = get_syscall_evt_param(evt.get_buf(), + 1); + if (res_param.param_pointer == nullptr) + { + return tstr; + } + tstr = (char*)(res_param.param_pointer); + } + break; + case PPME_SYSCALL_OPENAT_2_X: + case PPME_SYSCALL_OPENAT2_X: + { + + auto res_param = get_syscall_evt_param(evt.get_buf(), + 2); + if (res_param.param_pointer == nullptr) + { + return tstr; + } + tstr = (char*)(res_param.param_pointer); + } + break; + case PPME_SYSCALL_OPEN_BY_HANDLE_AT_X: + { + + auto res_param = get_syscall_evt_param(evt.get_buf(), + 3); + if (res_param.param_pointer == nullptr) + { + return tstr; + } + tstr = (char*)(res_param.param_pointer); + } + break; + default: + break; + } + break; + } + default: + break; + } + return tstr; +} + bool anomalydetection::extract_filterchecks_concat_profile(const falcosecurity::event_reader &evt, const falcosecurity::table_reader &tr, const std::vector& fields, std::string& behavior_profile_concat_str) { using st = falcosecurity::state_value_type; int64_t thread_id = evt.get_tid(); - auto thread_entry = m_thread_table.get_entry(tr, thread_id); + std::string tstr; + std::optional thread_entry_opt; + try + { + thread_entry_opt = m_thread_table.get_entry(tr, thread_id); + } catch (const std::exception& e) + { + for (const auto& field : fields) + { + behavior_profile_concat_str += extract_filterchecks_evt_params_fallbacks(evt, field); + } + return true; + } + auto& thread_entry = thread_entry_opt.value(); // Create a concatenated string formed out of each field per behavior profile // No concept of null fields (instead its always an empty string) compared to libsinsp for (const auto& field : fields) { - std::string tstr = ""; + tstr.clear(); uint64_t tuint64 = UINT64_MAX; uint32_t tuint32 = UINT32_MAX; int64_t tint64 = -1; @@ -1073,8 +1394,6 @@ bool anomalydetection::extract_filterchecks_concat_profile(const falcosecurity:: // fd related // - // todo implement fallbacks from null fd table entry aka extract from evt args - case plugin_sinsp_filterchecks::TYPE_FDNUM: { switch(evt.get_type()) @@ -1088,11 +1407,12 @@ bool anomalydetection::extract_filterchecks_concat_profile(const falcosecurity:: case PPME_SYSCALL_OPENAT2_X: case PPME_SYSCALL_OPEN_BY_HANDLE_AT_X: { - auto fd_table = m_thread_table.get_subtable( - tr, m_fds, thread_entry, - st::SS_PLUGIN_ST_INT64); m_lastevent_fd_field.read_value(tr, thread_entry, tint64); tstr = std::to_string(tint64); + if (tstr.empty()) + { + tstr = extract_filterchecks_evt_params_fallbacks(evt, field); + } break; } default: @@ -1110,44 +1430,177 @@ bool anomalydetection::extract_filterchecks_concat_profile(const falcosecurity:: case PPME_SOCKET_ACCEPT4_6_X: case PPME_SYSCALL_CREAT_X: case PPME_SOCKET_CONNECT_X: + case PPME_SYSCALL_OPEN_BY_HANDLE_AT_X: + { + try + { + auto fd_table = m_thread_table.get_subtable( + tr, m_fds, thread_entry, + st::SS_PLUGIN_ST_INT64); + m_lastevent_fd_field.read_value(tr, thread_entry, tint64); + auto fd_entry = fd_table.get_entry(tr, tint64); + m_fd_name_value.read_value(tr, fd_entry, tstr); + } + catch(const std::exception& e) + { + } + if (tstr.empty()) + { + std::string cwd; + m_cwd.read_value(tr, thread_entry, cwd); + tstr = extract_filterchecks_evt_params_fallbacks(evt, field, cwd); + } + break; + } case PPME_SYSCALL_OPENAT_2_X: case PPME_SYSCALL_OPENAT2_X: - case PPME_SYSCALL_OPEN_BY_HANDLE_AT_X: { - auto fd_table = m_thread_table.get_subtable( + try + { + auto fd_table = m_thread_table.get_subtable( tr, m_fds, thread_entry, st::SS_PLUGIN_ST_INT64); - m_lastevent_fd_field.read_value(tr, thread_entry, tint64); - auto fd_entry = fd_table.get_entry(tr, tint64); - m_fd_name_value.read_value(tr, fd_entry, tstr); + m_lastevent_fd_field.read_value(tr, thread_entry, tint64); + auto fd_entry = fd_table.get_entry(tr, tint64); + m_fd_name_value.read_value(tr, fd_entry, tstr); + } + catch(const std::exception& e) + { + } + if (tstr.empty()) + { + auto res_param = get_syscall_evt_param(evt.get_buf(), + 1); + std::string cwd; + if (res_param.param_pointer != nullptr) + { + int64_t dirfd = *(uint64_t*)(res_param.param_pointer); + try + { + auto fd_table = m_thread_table.get_subtable( + tr, m_fds, thread_entry, + st::SS_PLUGIN_ST_INT64); + auto fd_entry = fd_table.get_entry(tr, dirfd); + if (dirfd == PPM_AT_FDCWD) + { + m_cwd.read_value(tr, thread_entry, cwd); + + } else + { + m_fd_name_value.read_value(tr, fd_entry, cwd); + } + } + catch(const std::exception& e) + { + } + } + tstr = extract_filterchecks_evt_params_fallbacks(evt, field, cwd); + } break; } default: // Clear the entire profile when invoking the fd related profile for non fd syscalls behavior_profile_concat_str.clear(); + break; } break; } case plugin_sinsp_filterchecks::TYPE_DIRECTORY: + case plugin_sinsp_filterchecks::TYPE_FILENAME: { switch(evt.get_type()) { case PPME_SYSCALL_OPEN_X: case PPME_SYSCALL_CREAT_X: + case PPME_SYSCALL_OPEN_BY_HANDLE_AT_X: + { + try + { + auto fd_table = m_thread_table.get_subtable( + tr, m_fds, thread_entry, + st::SS_PLUGIN_ST_INT64); + m_lastevent_fd_field.read_value(tr, thread_entry, tint64); + auto fd_entry = fd_table.get_entry(tr, tint64); + m_fd_name_value.read_value(tr, fd_entry, tstr); + } + catch(const std::exception& e) + { + } + if (tstr.empty()) + { + std::string cwd; + m_cwd.read_value(tr, thread_entry, cwd); + tstr = extract_filterchecks_evt_params_fallbacks(evt, field, cwd); + } + size_t pos = tstr.find_last_of('/'); + if (pos != std::string::npos) + { + if (field.id == plugin_sinsp_filterchecks::TYPE_DIRECTORY) + { + tstr = tstr.substr(0, pos); + } else + { + + tstr = tstr.substr(pos + 1); + } + } + break; + } case PPME_SYSCALL_OPENAT_2_X: case PPME_SYSCALL_OPENAT2_X: - case PPME_SYSCALL_OPEN_BY_HANDLE_AT_X: { - auto fd_table = m_thread_table.get_subtable( + try + { + auto fd_table = m_thread_table.get_subtable( tr, m_fds, thread_entry, st::SS_PLUGIN_ST_INT64); - m_lastevent_fd_field.read_value(tr, thread_entry, tint64); - auto fd_entry = fd_table.get_entry(tr, tint64); - m_fd_name_value.read_value(tr, fd_entry, tstr); + m_lastevent_fd_field.read_value(tr, thread_entry, tint64); + auto fd_entry = fd_table.get_entry(tr, tint64); + m_fd_name_value.read_value(tr, fd_entry, tstr); + } + catch(const std::exception& e) + { + } + if (tstr.empty()) + { + auto res_param = get_syscall_evt_param(evt.get_buf(), + 1); + std::string cwd; + if (res_param.param_pointer != nullptr) + { + int64_t dirfd = *(uint64_t*)(res_param.param_pointer); + try + { + auto fd_table = m_thread_table.get_subtable( + tr, m_fds, thread_entry, + st::SS_PLUGIN_ST_INT64); + auto fd_entry = fd_table.get_entry(tr, dirfd); + if (dirfd == PPM_AT_FDCWD) + { + m_cwd.read_value(tr, thread_entry, cwd); + + } else + { + m_fd_name_value.read_value(tr, fd_entry, cwd); + } + } + catch(const std::exception& e) + { + } + } + tstr = extract_filterchecks_evt_params_fallbacks(evt, field, cwd); + } size_t pos = tstr.find_last_of('/'); if (pos != std::string::npos) { - tstr = tstr.substr(0, pos); + if (field.id == plugin_sinsp_filterchecks::TYPE_DIRECTORY) + { + tstr = tstr.substr(0, pos); + } else + { + + tstr = tstr.substr(pos + 1); + } } break; } @@ -1160,10 +1613,11 @@ bool anomalydetection::extract_filterchecks_concat_profile(const falcosecurity:: default: // Clear the entire profile when invoking the fd related profile for non fd syscalls behavior_profile_concat_str.clear(); + break; } break; } - case plugin_sinsp_filterchecks::TYPE_FILENAME: + case plugin_sinsp_filterchecks::TYPE_INO: { switch(evt.get_type()) { @@ -1173,16 +1627,22 @@ bool anomalydetection::extract_filterchecks_concat_profile(const falcosecurity:: case PPME_SYSCALL_OPENAT2_X: case PPME_SYSCALL_OPEN_BY_HANDLE_AT_X: { - auto fd_table = m_thread_table.get_subtable( + try + { + auto fd_table = m_thread_table.get_subtable( tr, m_fds, thread_entry, st::SS_PLUGIN_ST_INT64); - m_lastevent_fd_field.read_value(tr, thread_entry, tint64); - auto fd_entry = fd_table.get_entry(tr, tint64); - m_fd_name_value.read_value(tr, fd_entry, tstr); - size_t pos = tstr.find_last_of('/'); - if (pos != std::string::npos) + m_lastevent_fd_field.read_value(tr, thread_entry, tint64); + auto fd_entry = fd_table.get_entry(tr, tint64); + m_fd_ino_value.read_value(tr, fd_entry, tint64); + tstr = std::to_string(tint64); + } + catch(const std::exception& e) { - tstr = tstr.substr(pos + 1); + } + if (tstr.empty()) + { + tstr = extract_filterchecks_evt_params_fallbacks(evt, field); } break; } @@ -1195,62 +1655,49 @@ bool anomalydetection::extract_filterchecks_concat_profile(const falcosecurity:: default: // Clear the entire profile when invoking the fd related profile for non fd syscalls behavior_profile_concat_str.clear(); + break; } break; } - case plugin_sinsp_filterchecks::TYPE_INO: + case plugin_sinsp_filterchecks::TYPE_DEV: { switch(evt.get_type()) { case PPME_SYSCALL_OPEN_X: - case PPME_SOCKET_ACCEPT_5_X: - case PPME_SOCKET_ACCEPT4_6_X: case PPME_SYSCALL_CREAT_X: - case PPME_SOCKET_CONNECT_X: case PPME_SYSCALL_OPENAT_2_X: case PPME_SYSCALL_OPENAT2_X: case PPME_SYSCALL_OPEN_BY_HANDLE_AT_X: { - auto fd_table = m_thread_table.get_subtable( + try + { + auto fd_table = m_thread_table.get_subtable( tr, m_fds, thread_entry, st::SS_PLUGIN_ST_INT64); - m_lastevent_fd_field.read_value(tr, thread_entry, tint64); - auto fd_entry = fd_table.get_entry(tr, tint64); - m_fd_ino_value.read_value(tr, fd_entry, tint64); - tstr = std::to_string(tint64); + m_lastevent_fd_field.read_value(tr, thread_entry, tint64); + auto fd_entry = fd_table.get_entry(tr, tint64); + m_fd_dev_value.read_value(tr, fd_entry, tuint32); + tstr = std::to_string(tuint32); + } + catch(const std::exception& e) + { + } + if (tstr.empty()) + { + tstr = extract_filterchecks_evt_params_fallbacks(evt, field); + } break; } - default: - // Clear the entire profile when invoking the fd related profile for non fd syscalls - behavior_profile_concat_str.clear(); - } - break; - } - case plugin_sinsp_filterchecks::TYPE_DEV: - { - switch(evt.get_type()) - { - case PPME_SYSCALL_OPEN_X: case PPME_SOCKET_ACCEPT_5_X: case PPME_SOCKET_ACCEPT4_6_X: - case PPME_SYSCALL_CREAT_X: case PPME_SOCKET_CONNECT_X: - case PPME_SYSCALL_OPENAT_2_X: - case PPME_SYSCALL_OPENAT2_X: - case PPME_SYSCALL_OPEN_BY_HANDLE_AT_X: { - auto fd_table = m_thread_table.get_subtable( - tr, m_fds, thread_entry, - st::SS_PLUGIN_ST_INT64); - m_lastevent_fd_field.read_value(tr, thread_entry, tint64); - auto fd_entry = fd_table.get_entry(tr, tint64); - m_fd_dev_value.read_value(tr, fd_entry, tuint32); - tstr = std::to_string(tuint32); break; } default: // Clear the entire profile when invoking the fd related profile for non fd syscalls behavior_profile_concat_str.clear(); + break; } break; } @@ -1264,12 +1711,22 @@ bool anomalydetection::extract_filterchecks_concat_profile(const falcosecurity:: case PPME_SYSCALL_OPENAT2_X: case PPME_SYSCALL_OPEN_BY_HANDLE_AT_X: { - auto fd_table = m_thread_table.get_subtable( + try + { + auto fd_table = m_thread_table.get_subtable( tr, m_fds, thread_entry, st::SS_PLUGIN_ST_INT64); - m_lastevent_fd_field.read_value(tr, thread_entry, tint64); - auto fd_entry = fd_table.get_entry(tr, tint64); - m_fd_nameraw_value.read_value(tr, fd_entry, tstr); + m_lastevent_fd_field.read_value(tr, thread_entry, tint64); + auto fd_entry = fd_table.get_entry(tr, tint64); + m_fd_nameraw_value.read_value(tr, fd_entry, tstr); + } + catch(const std::exception& e) + { + } + if (tstr.empty()) + { + tstr = extract_filterchecks_evt_params_fallbacks(evt, field); + } break; } case PPME_SOCKET_ACCEPT_5_X: @@ -1281,6 +1738,7 @@ bool anomalydetection::extract_filterchecks_concat_profile(const falcosecurity:: default: // Clear the entire profile when invoking the fd related profile for non fd syscalls behavior_profile_concat_str.clear(); + break; } break; } @@ -1405,12 +1863,22 @@ bool anomalydetection::extract_filterchecks_concat_profile(const falcosecurity:: case PPME_SOCKET_ACCEPT4_6_X: case PPME_SOCKET_CONNECT_X: { - auto fd_table = m_thread_table.get_subtable( + try + { + auto fd_table = m_thread_table.get_subtable( tr, m_fds, thread_entry, st::SS_PLUGIN_ST_INT64); - m_lastevent_fd_field.read_value(tr, thread_entry, tint64); - auto fd_entry = fd_table.get_entry(tr, tint64); - m_fd_name_value.read_value(tr, fd_entry, tstr); + m_lastevent_fd_field.read_value(tr, thread_entry, tint64); + auto fd_entry = fd_table.get_entry(tr, tint64); + m_fd_name_value.read_value(tr, fd_entry, tstr); + } + catch(const std::exception& e) + { + } + if (tstr.empty()) + { + tstr = extract_filterchecks_evt_params_fallbacks(evt, field); + } std::string delimiter = "->"; size_t pos = tstr.find(delimiter); if (pos != std::string::npos) @@ -1432,6 +1900,7 @@ bool anomalydetection::extract_filterchecks_concat_profile(const falcosecurity:: default: // Clear the entire profile when invoking the fd related profile for non fd syscalls behavior_profile_concat_str.clear(); + break; } break; } @@ -1444,26 +1913,6 @@ bool anomalydetection::extract_filterchecks_concat_profile(const falcosecurity:: return true; } -// Adopted from the k8smeta plugin, obtain a param from a sinsp event -static inline sinsp_param get_syscall_evt_param(void* evt, uint32_t num_param) -{ - uint32_t dataoffset = 0; - // pointer to the lengths array inside the event. - auto len = (uint16_t*)((uint8_t*)evt + - sizeof(falcosecurity::_internal::ss_plugin_event)); - for(uint32_t j = 0; j < num_param; j++) - { - // sum lengths of the previous params. - dataoffset += len[j]; - } - return {.param_len = len[num_param], - .param_pointer = - ((uint8_t*)&len - [((falcosecurity::_internal::ss_plugin_event*)evt) - ->nparams]) + - dataoffset}; -} - bool anomalydetection::parse_event(const falcosecurity::parse_event_input& in) { auto& evt = in.get_event_reader(); @@ -1484,6 +1933,9 @@ bool anomalydetection::parse_event(const falcosecurity::parse_event_input& in) case PPME_SOCKET_ACCEPT_5_X: case PPME_SOCKET_ACCEPT4_6_X: case PPME_SYSCALL_CREAT_X: + case PPME_SYSCALL_OPENAT_2_X: // fd param 0 + case PPME_SYSCALL_OPENAT2_X: + case PPME_SYSCALL_OPEN_BY_HANDLE_AT_X: { auto res_param = get_syscall_evt_param(in.get_event_reader().get_buf(), 0); @@ -1510,33 +1962,6 @@ bool anomalydetection::parse_event(const falcosecurity::parse_event_input& in) m_lastevent_fd_field.write_value(tw, thread_entry, fd); break; } - case PPME_SYSCALL_OPENAT_2_X: // fd param 0 - case PPME_SYSCALL_OPENAT2_X: - { - auto res_param = get_syscall_evt_param(in.get_event_reader().get_buf(), - 0); - if (res_param.param_pointer == nullptr) - { - return false; - } - int64_t fd = *(int64_t*)(res_param.param_pointer); - auto thread_entry = m_thread_table.get_entry(tr, thread_id); - m_lastevent_fd_field.write_value(tw, thread_entry, fd); - break; - } - case PPME_SYSCALL_OPEN_BY_HANDLE_AT_X: - { - auto res_param = get_syscall_evt_param(in.get_event_reader().get_buf(), - 0); - if (res_param.param_pointer == nullptr) - { - return false; - } - int64_t fd = *(int64_t*)(res_param.param_pointer); - auto thread_entry = m_thread_table.get_entry(tr, thread_id); - m_lastevent_fd_field.write_value(tw, thread_entry, fd); - break; - } default: break; } diff --git a/plugins/anomalydetection/src/plugin.h b/plugins/anomalydetection/src/plugin.h index 7bd6316f..d7cdc272 100644 --- a/plugins/anomalydetection/src/plugin.h +++ b/plugins/anomalydetection/src/plugin.h @@ -34,6 +34,7 @@ limitations under the License. #include #define UINT32_MAX (4294967295U) +#define PPM_AT_FDCWD -100 struct sinsp_param { @@ -125,9 +126,10 @@ class anomalydetection // required; standard plugin API bool parse_event(const falcosecurity::parse_event_input& in); - // Custom helper function within event parsing + // Custom helper functions within event parsing bool extract_filterchecks_concat_profile(const falcosecurity::event_reader &evt, const falcosecurity::table_reader &tr, const std::vector& fields, std::string& behavior_profile_concat_str); - + std::string extract_filterchecks_evt_params_fallbacks(const falcosecurity::event_reader &evt, const plugin_sinsp_filterchecks_field& field, const std::string& cwd = ""); + private: bool m_count_min_sketch_enabled = false; diff --git a/plugins/anomalydetection/src/plugin_utils.cpp b/plugins/anomalydetection/src/plugin_utils.cpp index fc0c8b5d..87934245 100644 --- a/plugins/anomalydetection/src/plugin_utils.cpp +++ b/plugins/anomalydetection/src/plugin_utils.cpp @@ -180,13 +180,189 @@ static const filtercheck_field_info sinsp_filter_check_fields[] = {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "custom.fd.name.part2", "Custom fd 'ip:port' part1", "[Incubating] For fd related network events only. Part 2 as string of the ip tuple in the format 'ip:port', e.g.'142.251.111.147:443' given fd.name '172.40.111.222:54321->142.251.111.147:443'. This is a custom plugin specific field for the anomaly behavior profiles only. It may be dperecated in the future."}, }; -// Temporary workaround; not as robust as libsinsp/eventformatter; -// ideally the plugin API exposes more libsinsp functionality in the near-term -// -// No need for performance optimization atm as the typical use case is to have -// less than 3-8 sketches + namespace plugin_anomalydetection::utils { + +// Adopter from libs, custom hand-rolled for performance reasons +static inline void rewind_to_parent_path(const char* targetbase, char** tc, const char** pc, uint32_t delta) +{ + if(*tc <= targetbase + 1) + { + (*pc) += delta; + return; + } + + (*tc)--; + + while((*tc) >= targetbase + 1 && *((*tc) - 1) != '/') + { + (*tc)--; + } + + (*pc) += delta; +} + +// Adopter from libs +struct g_invalidchar +{ + bool operator()(char c) const + { + // Exclude all non-printable characters and control characters while + // including a wide range of languages (emojis, cyrillic, chinese etc) + return !(isprint((unsigned)c)); + } +}; + +// Adopter from libs, custom hand-rolled for performance reasons +static inline void copy_and_sanitize_path(char* target, char* targetbase, const char *path, char separator) +{ + char* tc = target; + const char* pc = path; + g_invalidchar ic; + const bool empty_base = target == targetbase; + + while(true) + { + if(*pc == 0) + { + *tc = 0; + + // + // If the path ends with a separator, remove it, as the OS does. + // Properly manage case where path is just "/". + // + if((tc > (targetbase + 1)) && (*(tc - 1) == separator)) + { + *(tc - 1) = 0; + } + + return; + } + + if(ic(*pc)) + { + // + // Invalid char, substitute with a '.' + // + *tc = '.'; + tc++; + pc++; + } + else + { + // + // If path begins with '.' or '.' is the first char after a '/' + // + if(*pc == '.' && (tc == targetbase || *(tc - 1) == separator)) + { + // + // '../', rewind to the previous separator + // + if(*(pc + 1) == '.' && *(pc + 2) == separator) + { + rewind_to_parent_path(targetbase, &tc, &pc, 3); + } + // + // '..', with no separator. + // This is valid if we are at the end of the string, and in that case we rewind. + // + else if(*(pc + 1) == '.' && *(pc + 2) == 0) + { + rewind_to_parent_path(targetbase, &tc, &pc, 2); + } + // + // './', just skip it + // + else if(*(pc + 1) == separator) + { + pc += 2; + } + // + // '.', with no separator. + // This is valid if we are at the end of the string, and in that case we rewind. + // + else if(*(pc + 1) == 0) + { + pc++; + } + // + // Otherwise, we leave the string intact. + // + else + { + *tc = *pc; + pc++; + tc++; + } + } + else if(*pc == separator) + { + // + // separator: + // * if the last char is already a separator, skip it + // * if we are back at targetbase but targetbase was not empty before, it means we + // fully rewinded back to targetbase and the string is now empty. Skip separator. + // Example: "/foo/../a" -> "/a" BUT "foo/../a" -> "a" + // -> Otherwise: "foo/../a" -> "/a" + // + if((tc > targetbase && *(tc - 1) == separator) || (tc == targetbase && !empty_base)) + { + pc++; + } + else + { + *tc = *pc; + tc++; + pc++; + } + } + else + { + // + // Normal char, copy it + // + *tc = *pc; + tc++; + pc++; + } + } + } +} + +// Adopter from libs, custom hand-rolled for performance reasons +static inline bool concatenate_paths_(char* target, uint32_t targetlen, const char* path1, uint32_t len1, + const char* path2, uint32_t len2) +{ + if(targetlen < (len1 + len2 + 1)) + { + strlcpy(target, "/PATH_TOO_LONG", targetlen); + return false; + } + + if(len2 != 0 && path2[0] != '/') + { + memcpy(target, path1, len1); + copy_and_sanitize_path(target + len1, target, path2, '/'); + return true; + } + else + { + target[0] = 0; + copy_and_sanitize_path(target, target, path2, '/'); + return false; + } +} + +// Adopter from libs, custom hand-rolled for performance reasons +std::string concatenate_paths(std::string_view path1, std::string_view path2) +{ + char fullpath[SCAP_MAX_PATH_SIZE]; + concatenate_paths_(fullpath, SCAP_MAX_PATH_SIZE, path1.data(), (uint32_t)path1.length(), path2.data(), + path2.size()); + return std::string(fullpath); +} + const std::vector get_profile_fields(const std::string& behavior_profile) { std::vector fields; diff --git a/plugins/anomalydetection/src/plugin_utils.h b/plugins/anomalydetection/src/plugin_utils.h index e27faa0b..b3dcbad4 100644 --- a/plugins/anomalydetection/src/plugin_utils.h +++ b/plugins/anomalydetection/src/plugin_utils.h @@ -25,6 +25,8 @@ limitations under the License. #include #include +#define SCAP_MAX_PATH_SIZE 1024 + typedef struct plugin_sinsp_filterchecks_field { plugin_sinsp_filterchecks::check_type id; @@ -34,6 +36,9 @@ typedef struct plugin_sinsp_filterchecks_field namespace plugin_anomalydetection::utils { + // Adopter from libs, custom hand-rolled for performance reasons + std::string concatenate_paths(std::string_view path1, std::string_view path2); + // Temporary workaround; not as robust as libsinsp/eventformatter; // ideally the plugin API exposes more libsinsp functionality in the near-term // diff --git a/plugins/anomalydetection/test/src/num/cms.ut.cpp b/plugins/anomalydetection/test/src/num/cms.ut.cpp index 30eb6e3c..fe0ab575 100644 --- a/plugins/anomalydetection/test/src/num/cms.ut.cpp +++ b/plugins/anomalydetection/test/src/num/cms.ut.cpp @@ -146,16 +146,86 @@ TEST_F(sinsp_with_test_input, plugin_anomalydetection_filterchecks_fields_fd) sinsp_evt *evt; open_inspector(); + uint64_t ino = 777; int64_t fd = 4; add_event(increasing_ts(), 3, PPME_SYSCALL_OPEN_E, 3, "/tmp/subdir1/subdir2/subdir3/subdir4/../../the_file", 0, 0); - add_event_advance_ts(increasing_ts(), 3, PPME_SYSCALL_OPEN_X, 6, fd, "/tmp/some_other_file", 0, 0, 0, (uint64_t) 0); + add_event_advance_ts(increasing_ts(), 3, PPME_SYSCALL_OPEN_X, 6, fd, "/tmp/../../../some_other_file", 0, 0, 0, ino); fd = 5; add_event(increasing_ts(), 3, PPME_SYSCALL_OPEN_E, 3, "/tmp/subdir1/subdir2/subdir3/subdir4/../../the_file2", 0, 0); - evt = add_event_advance_ts(increasing_ts(), 3, PPME_SYSCALL_OPEN_X, 6, fd, "/tmp/some_other_file2", 0, 0, 0, (uint64_t) 0); + evt = add_event_advance_ts(increasing_ts(), 3, PPME_SYSCALL_OPEN_X, 6, fd, "/tmp/../../../some_other_file2", 0, 0, 0, ino); ASSERT_EQ(get_field_as_string(evt, "fd.num"), "5"); ASSERT_EQ(get_field_as_string(evt, "fd.name"), "/tmp/subdir1/subdir2/the_file2"); ASSERT_EQ(get_field_as_string(evt, "fd.directory"), "/tmp/subdir1/subdir2"); - ASSERT_EQ(get_field_as_string(evt, "anomaly.count_min_sketch.profile[1]", pl_flist), "-15/tmp/subdir1/subdir2/the_file2/tmp/subdir1/subdir2the_file200/tmp/subdir1/subdir2/subdir3/subdir4/../../the_file2"); + ASSERT_EQ(get_field_as_string(evt, "anomaly.count_min_sketch.profile[1]", pl_flist), "-15/tmp/subdir1/subdir2/the_file2/tmp/subdir1/subdir2the_file20777/tmp/subdir1/subdir2/subdir3/subdir4/../../the_file2"); + + evt = NULL; + uint64_t dirfd = 3, new_fd = 100; + add_event_advance_ts(increasing_ts(), 1, PPME_SYSCALL_OPENAT2_E, 5, dirfd, "", 0, 0, 0); + evt = add_event_advance_ts(increasing_ts(), 1, PPME_SYSCALL_OPENAT2_X, 8, new_fd, dirfd, "/tmp/dir1/../the_file", 0, 0, 0, 0, ino); + ASSERT_EQ(get_field_as_string(evt, "proc.pid"), "1"); + ASSERT_EQ(get_field_as_string(evt, "fd.name"), "/tmp/the_file"); + ASSERT_EQ(get_field_as_string(evt, "fd.nameraw"), "/tmp/dir1/../the_file"); + ASSERT_EQ(get_field_as_string(evt, "anomaly.count_min_sketch.profile[1]", pl_flist), "1100/tmp/the_file/tmpthe_file0777/tmp/dir1/../the_file"); + + evt = NULL; + fd = 4; + int64_t mountfd = 5; + add_event_advance_ts(increasing_ts(), 1, PPME_SYSCALL_OPEN_BY_HANDLE_AT_E, 0); + evt = add_event_advance_ts(increasing_ts(), 1, PPME_SYSCALL_OPEN_BY_HANDLE_AT_X, 6, fd, mountfd, PPM_O_RDWR, "/tmp/open_handle.txt", 0, ino); + ASSERT_EQ(get_field_as_string(evt, "anomaly.count_min_sketch.profile[1]", pl_flist), "14/tmp/open_handle.txt/tmpopen_handle.txt0777/tmp/open_handle.txt"); +} + +TEST_F(sinsp_with_test_input, plugin_anomalydetection_filterchecks_fields_fd_null_fd_table) +{ + std::shared_ptr plugin_owner; + filter_check_list pl_flist; + ASSERT_PLUGIN_INITIALIZATION(plugin_owner, pl_flist) + add_default_init_thread(); + + sinsp_evt *evt; + open_inspector(); + + uint64_t ino = 777; + int64_t fd = 4; + add_event(increasing_ts(), 1, PPME_SYSCALL_OPEN_E, 3, "subdir1//../the_file2", 0, 0); + evt = add_event_advance_ts(increasing_ts(), 1, PPME_SYSCALL_OPEN_X, 6, fd, "subdir1//../the_file2", 0, 0, 0, ino); + + sinsp_fdinfo* fdinfo = evt->get_thread_info()->get_fd(fd); + ASSERT_EQ(get_field_as_string(evt, "fd.num"), "4"); + ASSERT_EQ(get_field_as_string(evt, "fd.name"), "/root/the_file2"); + ASSERT_EQ(get_field_as_string(evt, "proc.cwd"), "/root/"); + fdinfo->m_name.clear(); + fdinfo->m_name_raw.clear(); + ASSERT_EQ(get_field_as_string(evt, "anomaly.count_min_sketch.profile[1]", pl_flist), "14/root/the_file2/rootthe_file20777subdir1//../the_file2"); + + evt = NULL; + uint64_t dirfd = 8, new_fd = 100; + fd = 8; + add_event(increasing_ts(), 1, PPME_SYSCALL_OPEN_E, 3, "/tmp/subdir1/subdir2/../the_file2", 0, 0); + evt = add_event_advance_ts(increasing_ts(), 1, PPME_SYSCALL_OPEN_X, 6, fd, "/tmp/subdir1/subdir2/../the_file2", 0, 0, 0, ino); + add_event_advance_ts(increasing_ts(), 1, PPME_SYSCALL_OPENAT2_E, 5, dirfd, "subdir1//../the_file", 0, 0, 0); + evt = add_event_advance_ts(increasing_ts(), 1, PPME_SYSCALL_OPENAT2_X, 8, new_fd, dirfd, "subdir1//../the_file", 0, 0, 0, 0, ino); + ASSERT_EQ(get_field_as_string(evt, "fd.num"), "100"); + ASSERT_EQ(get_field_as_string(evt, "fd.name"), "/tmp/subdir1/the_file2/the_file"); + ASSERT_EQ(get_field_as_string(evt, "fs.path.name"), "/root/the_file"); // todo fix in libs as its wrong + ASSERT_EQ(get_field_as_string(evt, "proc.cwd"), "/root/"); + fdinfo = evt->get_thread_info()->get_fd(new_fd); + fdinfo->m_name.clear(); + fdinfo->m_name_raw.clear(); + ASSERT_EQ(get_field_as_string(evt, "anomaly.count_min_sketch.profile[1]", pl_flist), "1100/tmp/subdir1/the_file/tmp/subdir1the_file0777subdir1//../the_file"); + + evt = NULL; + fd = 4; + int64_t mountfd = 5; + add_event_advance_ts(increasing_ts(), 1, PPME_SYSCALL_OPEN_BY_HANDLE_AT_E, 0); + evt = add_event_advance_ts(increasing_ts(), 1, PPME_SYSCALL_OPEN_BY_HANDLE_AT_X, 6, fd, mountfd, PPM_O_RDWR, "/tmp/open_handle.txt", 0, ino); + ASSERT_EQ(get_field_as_string(evt, "fd.num"), "4"); + ASSERT_EQ(get_field_as_string(evt, "fd.name"), "/tmp/open_handle.txt"); + ASSERT_EQ(get_field_as_string(evt, "proc.cwd"), "/root/"); + fdinfo = evt->get_thread_info()->get_fd(fd); + fdinfo->m_name.clear(); + fdinfo->m_name_raw.clear(); + ASSERT_EQ(get_field_as_string(evt, "anomaly.count_min_sketch.profile[1]", pl_flist), "14/tmp/open_handle.txt/tmpopen_handle.txt0777/tmp/open_handle.txt"); } TEST_F(sinsp_with_test_input, plugin_anomalydetection_filterchecks_fields_fd_network) @@ -193,7 +263,7 @@ TEST_F(sinsp_with_test_input, plugin_anomalydetection_filterchecks_fields_fd_net ASSERT_EQ(get_field_as_string(evt, "fd.cip"), "172.40.111.222"); ASSERT_EQ(get_field_as_string(evt, "fd.sip"), "142.251.111.147"); ASSERT_EQ(get_field_as_string(evt, "fd.num"), "8"); - ASSERT_EQ(get_field_as_string(evt, "anomaly.count_min_sketch.profile[1]", pl_flist), "1172.40.111.222:54321142.251.111.147:4438172.40.111.222:54321->142.251.111.147:44300"); + ASSERT_EQ(get_field_as_string(evt, "anomaly.count_min_sketch.profile[1]", pl_flist), "1172.40.111.222:54321142.251.111.147:4438172.40.111.222:54321->142.251.111.147:443"); client = test_utils::fill_sockaddr_in(DEFAULT_CLIENT_PORT, DEFAULT_IPV4_CLIENT_STRING); std::vector st = test_utils::pack_socktuple(reinterpret_cast(&client), reinterpret_cast(&server)); @@ -207,5 +277,52 @@ TEST_F(sinsp_with_test_input, plugin_anomalydetection_filterchecks_fields_fd_net ASSERT_EQ(get_field_as_string(evt, "fd.lip"), "142.251.111.147"); ASSERT_EQ(get_field_as_string(evt, "fd.cip"), "172.40.111.222"); ASSERT_EQ(get_field_as_string(evt, "fd.sip"), "142.251.111.147"); - ASSERT_EQ(get_field_as_string(evt, "anomaly.count_min_sketch.profile[1]", pl_flist), "1172.40.111.222:54321142.251.111.147:4436172.40.111.222:54321->142.251.111.147:44300"); + ASSERT_EQ(get_field_as_string(evt, "anomaly.count_min_sketch.profile[1]", pl_flist), "1172.40.111.222:54321142.251.111.147:4436172.40.111.222:54321->142.251.111.147:443"); +} + +TEST_F(sinsp_with_test_input, plugin_anomalydetection_filterchecks_fields_fd_network_null_fd_table) +{ + std::shared_ptr plugin_owner; + filter_check_list pl_flist; + ASSERT_PLUGIN_INITIALIZATION(plugin_owner, pl_flist) + add_default_init_thread(); + + open_inspector(); + sinsp_evt* evt = NULL; + sinsp_fdinfo* fdinfo = NULL; + int64_t client_fd = 8; + int64_t return_value = 0; + + add_event_advance_ts(increasing_ts(), 1, PPME_SOCKET_SOCKET_E, 3, (uint32_t) PPM_AF_INET, (uint32_t) SOCK_STREAM, (uint32_t) 0); + add_event_advance_ts(increasing_ts(), 1, PPME_SOCKET_SOCKET_X, 1, client_fd); + + sockaddr_in client = test_utils::fill_sockaddr_in(DEFAULT_CLIENT_PORT, DEFAULT_IPV4_CLIENT_STRING); + sockaddr_in server = test_utils::fill_sockaddr_in(DEFAULT_SERVER_PORT, DEFAULT_IPV4_SERVER_STRING); + + std::vector server_sockaddr = test_utils::pack_sockaddr(reinterpret_cast(&server)); + evt = add_event_advance_ts(increasing_ts(), 1, PPME_SOCKET_CONNECT_E, 2, client_fd, scap_const_sized_buffer{server_sockaddr.data(), server_sockaddr.size()}); + std::vector socktuple = test_utils::pack_socktuple(reinterpret_cast(&client), reinterpret_cast(&server)); + evt = add_event_advance_ts(increasing_ts(), 1, PPME_SOCKET_CONNECT_X, 3, return_value, scap_const_sized_buffer{socktuple.data(), socktuple.size()}, client_fd); + + /* We are able to recover the fdinfo in the connect exit event even when interleaved */ + fdinfo = evt->get_fd_info(); + fdinfo->m_name.clear(); + fdinfo->m_name_raw.clear(); + ASSERT_NE(fdinfo, nullptr); + ASSERT_EQ(get_field_as_string(evt, "fd.num"), "8"); + // no fallbacks atm + ASSERT_EQ(get_field_as_string(evt, "anomaly.count_min_sketch.profile[1]", pl_flist), "18"); + + client = test_utils::fill_sockaddr_in(DEFAULT_CLIENT_PORT, DEFAULT_IPV4_CLIENT_STRING); + std::vector st = test_utils::pack_socktuple(reinterpret_cast(&client), reinterpret_cast(&server)); + + int64_t new_connected_fd = 6; + add_event_advance_ts(increasing_ts(), 1, PPME_SOCKET_ACCEPT_5_E, 0); + add_event_advance_ts(increasing_ts(), 1, PPME_SOCKET_ACCEPT_5_X, 5, new_connected_fd, scap_const_sized_buffer{st.data(), st.size()}, (uint8_t) 0, (uint32_t) 0, (uint32_t) 5); + fdinfo = evt->get_fd_info(); + fdinfo->m_name.clear(); + fdinfo->m_name_raw.clear(); + ASSERT_EQ(get_field_as_string(evt, "fd.num"), "6"); + // no fallbacks atm + ASSERT_EQ(get_field_as_string(evt, "anomaly.count_min_sketch.profile[1]", pl_flist), "16"); }