diff --git a/plugins/anomalydetection/src/plugin.cpp b/plugins/anomalydetection/src/plugin.cpp index 311d1f31..13a8ecfb 100644 --- a/plugins/anomalydetection/src/plugin.cpp +++ b/plugins/anomalydetection/src/plugin.cpp @@ -226,6 +226,8 @@ bool anomalydetection::init(falcosecurity::init_input& in) // Accessor to falcosecurity/libs' thread table (process cache / core state engine) m_thread_table = t.get_table(THREAD_TABLE_NAME, st::SS_PLUGIN_ST_INT64); // Define accessors to falcosecurity/libs' thread table fields + + /* proc related */ m_tid = m_thread_table.get_field(t.fields(), "tid", st::SS_PLUGIN_ST_INT64); m_pid = m_thread_table.get_field(t.fields(), "pid", st::SS_PLUGIN_ST_INT64); m_ptid = m_thread_table.get_field(t.fields(), "ptid", st::SS_PLUGIN_ST_INT64); @@ -238,16 +240,37 @@ bool anomalydetection::init(falcosecurity::init_input& in) m_exe_from_memfd = m_thread_table.get_field(t.fields(), "exe_from_memfd", st::SS_PLUGIN_ST_BOOL); m_args = m_thread_table.get_field(t.fields(), "args", st::SS_PLUGIN_ST_TABLE); m_args_value = t.get_subtable_field(m_thread_table, m_args, "value", st::SS_PLUGIN_ST_STRING); - // m_env = m_thread_table.get_field(t.fields(), "env", TBD); - m_container_id = m_thread_table.get_field(t.fields(), "container_id", st::SS_PLUGIN_ST_STRING); - // m_user = m_thread_table.get_field(t.fields(), "user", TBD); - // m_loginuser = m_thread_table.get_field(t.fields(), "loginuser", TBD); - // m_group = m_thread_table.get_field(t.fields(), "group", TBD); + m_env = m_thread_table.get_field(t.fields(), "env", st::SS_PLUGIN_ST_TABLE); + m_env_value = t.get_subtable_field(m_thread_table, m_env, "value", st::SS_PLUGIN_ST_STRING); m_vtid = m_thread_table.get_field(t.fields(), "vtid", st::SS_PLUGIN_ST_INT64); m_vpid = m_thread_table.get_field(t.fields(), "vpid", st::SS_PLUGIN_ST_INT64); m_vpgid = m_thread_table.get_field(t.fields(), "vpgid", st::SS_PLUGIN_ST_INT64); m_tty = m_thread_table.get_field(t.fields(), "tty", st::SS_PLUGIN_ST_UINT32); m_cwd = m_thread_table.get_field(t.fields(), "cwd", st::SS_PLUGIN_ST_STRING); + + /* user related */ + // Not available until the next libs plugins API expansion + + // m_user = m_thread_table.get_field(t.fields(), "user", TBD); + // m_loginuser = m_thread_table.get_field(t.fields(), "loginuser", TBD); + // m_group = m_thread_table.get_field(t.fields(), "group", TBD); + + /* fd or fs related */ + m_fds = m_thread_table.get_field(t.fields(), "file_descriptors", st::SS_PLUGIN_ST_TABLE); + m_fd_name_value = t.get_subtable_field(m_thread_table, m_fds, "name", st::SS_PLUGIN_ST_STRING); + m_fd_nameraw_value = t.get_subtable_field(m_thread_table, m_fds, "name_raw", st::SS_PLUGIN_ST_STRING); + m_fd_ino_value = t.get_subtable_field(m_thread_table, m_fds, "ino", st::SS_PLUGIN_ST_UINT64); + // m_fd_type_value = t.get_subtable_field(m_thread_table, m_fds, "type", st::SS_PLUGIN_ST_UINT32); + m_fd_flags_value = t.get_subtable_field(m_thread_table, m_fds, "flags", st::SS_PLUGIN_ST_UINT32); + m_fd_dev_value = t.get_subtable_field(m_thread_table, m_fds, "dev", st::SS_PLUGIN_ST_UINT32); + m_fd_mount_id_value = t.get_subtable_field(m_thread_table, m_fds, "mount_id", st::SS_PLUGIN_ST_UINT32); + + /* container related */ + m_container_id = m_thread_table.get_field(t.fields(), "container_id", st::SS_PLUGIN_ST_STRING); + + /* Custom fields */ + m_lastevent_fd_field = m_thread_table.add_field( + t.fields(), "lastevent_fd", st::SS_PLUGIN_ST_INT64); } catch(falcosecurity::plugin_exception e) { @@ -318,7 +341,8 @@ bool anomalydetection::extract(const falcosecurity::extract_fields_input& in) { auto& req = in.get_extract_request(); auto& tr = in.get_table_reader(); - int64_t thread_id = in.get_event_reader().get_tid(); + auto& evt = in.get_event_reader(); + int64_t thread_id = evt.get_tid(); uint64_t count_min_sketch_estimate = 0; std::string behavior_profile_concat_str; auto index = req.get_arg_index(); @@ -330,14 +354,14 @@ bool anomalydetection::extract(const falcosecurity::extract_fields_input& in) switch(req.get_field_id()) { case ANOMALYDETECTION_COUNT_MIN_SKETCH_COUNT: - if(extract_filterchecks_concat_profile(thread_id, tr, m_behavior_profiles_fields[index], behavior_profile_concat_str)) + if(extract_filterchecks_concat_profile(evt, tr, m_behavior_profiles_fields[index], behavior_profile_concat_str)) { count_min_sketch_estimate = m_count_min_sketches[index].get()->estimate(behavior_profile_concat_str); req.set_value(count_min_sketch_estimate, true); } return true; case ANOMALYDETECTION_COUNT_MIN_SKETCH_BEHAVIOR_PROFILE_CONCAT_STR: - if(extract_filterchecks_concat_profile(thread_id, tr, m_behavior_profiles_fields[index], behavior_profile_concat_str)) + if(extract_filterchecks_concat_profile(evt, tr, m_behavior_profiles_fields[index], behavior_profile_concat_str)) { req.set_value(behavior_profile_concat_str, true); } @@ -354,17 +378,22 @@ bool anomalydetection::extract(const falcosecurity::extract_fields_input& in) // Parse capability ////////////////////////// -bool anomalydetection::extract_filterchecks_concat_profile(int64_t thread_id, const falcosecurity::table_reader &tr, const std::vector& fields, std::string& behavior_profile_concat_str) +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); + auto fd_table = m_thread_table.get_subtable( + tr, m_fds, thread_entry, + st::SS_PLUGIN_ST_INT64); // 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; + uint64_t tuint64 = UINT64_MAX; uint32_t tuint32 = UINT32_MAX; int64_t tint64 = -1; int64_t ptid = -1; @@ -684,6 +713,61 @@ bool anomalydetection::extract_filterchecks_concat_profile(int64_t thread_id, co m_comm.read_value(tr, *leader, tstr); break; } + + // + // fd or fs related + // + + case plugin_sinsp_filterchecks::TYPE_FDNAME: + { + 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: + { + 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); + break; + } + case PPME_SOCKET_CONNECT_X: + { + break; + } + case PPME_SYSCALL_OPENAT_2_X: + case PPME_SYSCALL_OPENAT2_X: + { + 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); + break; + } + case PPME_SYSCALL_OPEN_BY_HANDLE_AT_X: + { + 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); + break; + } + default: + break; + } + break; + } + case plugin_sinsp_filterchecks::TYPE_FDNAMERAW: + { + 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); + break; + break; + } + case plugin_sinsp_filterchecks::TYPE_INO: + { + break; + } default: break; } @@ -692,21 +776,110 @@ bool anomalydetection::extract_filterchecks_concat_profile(int64_t thread_id, co 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(); auto& tr = in.get_table_reader(); + auto& tw = in.get_table_writer(); + int64_t thread_id = evt.get_tid(); // note: Plugin event parsing guaranteed to happen after libs' `sinsp_parser::process_event` has finished. // Needs to stay in sync w/ libs updates. // Ultimately gated by `base_syscalls` restrictions if Falco is used w/ `base_syscalls`. + + + // The plugin currently cannot access for examle m_lastevent_fd from libs + // Write this info to tinfo within the plugin + switch(evt.get_type()) + { + case PPME_SYSCALL_OPEN_X: // fd param 0 + case PPME_SOCKET_ACCEPT_5_X: + case PPME_SOCKET_ACCEPT4_6_X: + case PPME_SYSCALL_CREAT_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_SOCKET_CONNECT_X: // fd param 2 + { + auto res_param = get_syscall_evt_param(in.get_event_reader().get_buf(), + 2); + 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_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; + } + + // Loop over behavior profiles, extract profile fields and update the count_min_sketch counts. int i = 0; std::string behavior_profile_concat_str; for(const auto& set : m_behavior_profiles_event_codes) { if(set.find((ppm_event_code)evt.get_type()) != set.end()) { - int64_t thread_id = in.get_event_reader().get_tid(); if(thread_id <= 0) { return false; @@ -714,7 +887,7 @@ bool anomalydetection::parse_event(const falcosecurity::parse_event_input& in) try { behavior_profile_concat_str.clear(); - if (i < m_n_sketches && extract_filterchecks_concat_profile(thread_id, tr, m_behavior_profiles_fields[i], behavior_profile_concat_str) && !behavior_profile_concat_str.empty()) + if (i < m_n_sketches && extract_filterchecks_concat_profile(evt, tr, m_behavior_profiles_fields[i], behavior_profile_concat_str) && !behavior_profile_concat_str.empty()) { m_count_min_sketches[i].get()->update(behavior_profile_concat_str, (uint64_t)1); } diff --git a/plugins/anomalydetection/src/plugin.h b/plugins/anomalydetection/src/plugin.h index 705b55b0..594655b4 100644 --- a/plugins/anomalydetection/src/plugin.h +++ b/plugins/anomalydetection/src/plugin.h @@ -31,7 +31,13 @@ limitations under the License. #include #include -# define UINT32_MAX (4294967295U) +#define UINT32_MAX (4294967295U) + +struct sinsp_param +{ + uint16_t param_len; + uint8_t* param_pointer; +}; class anomalydetection { @@ -118,7 +124,7 @@ class anomalydetection bool parse_event(const falcosecurity::parse_event_input& in); // Custom helper function within event parsing - bool extract_filterchecks_concat_profile(int64_t thread_id, const falcosecurity::table_reader &tr, const std::vector& fields, std::string& behavior_profile_concat_str); + 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); private: @@ -138,6 +144,8 @@ class anomalydetection falcosecurity::table m_thread_table; // Accessors to the fixed fields of falcosecurity/libs' thread table -> non comprehensive re-definition of sinsp_threadinfo // Reference in falcosecurity/libs: userspace/libsinsp/threadinfo.h + + /* proc related*/ falcosecurity::table_field m_tid; ///< The id of this thread falcosecurity::table_field m_pid; ///< The id of the process containing this thread. In single thread threads, this is equal to tid. falcosecurity::table_field m_ptid; ///< The id of the process that started this thread. @@ -148,20 +156,39 @@ class anomalydetection falcosecurity::table_field m_exe_writable; falcosecurity::table_field m_exe_upper_layer; ///< True if the executable file belongs to upper layer in overlayfs falcosecurity::table_field m_exe_from_memfd; ///< True if the executable is stored in fileless memory referenced by memfd - falcosecurity::table_field m_args; ///< Command line arguments (e.g. "-d1") - falcosecurity::table_field m_args_value; ///< String value entry from the args array - falcosecurity::table_field m_env; ///< Environment variables - falcosecurity::table_field m_container_id; ///< heuristic-based container id - falcosecurity::table_field m_uid; ///< user uid - falcosecurity::table_field m_user; ///< user infos - falcosecurity::table_field m_loginuid; ///< auid - falcosecurity::table_field m_loginuser; ///< loginuser infos (auid) + falcosecurity::table_field m_args; ///< args subtable + falcosecurity::table_field m_args_value; ///< Value entry to command line arguments (e.g. "-d1") from the args array + falcosecurity::table_field m_env; ///< Environment variables subtable + falcosecurity::table_field m_env_value; ///< Value entry falcosecurity::table_field m_group; ///< group infos falcosecurity::table_field m_vtid; ///< The virtual id of this thread. falcosecurity::table_field m_vpid; ///< The virtual id of the process containing this thread. In single thread threads, this is equal to vtid. falcosecurity::table_field m_vpgid; // The virtual process group id, as seen from its pid namespace falcosecurity::table_field m_tty; ///< Number of controlling terminal falcosecurity::table_field m_cwd; ///< current working directory + + /* user related */ + // Not available until the next libs plugins API expansion + falcosecurity::table_field m_uid; ///< user uid + falcosecurity::table_field m_user; ///< user infos + falcosecurity::table_field m_loginuid; ///< auid + falcosecurity::table_field m_loginuser; ///< loginuser infos (auid) + + /* fd or fs related */ + falcosecurity::table_field m_fds; ///< fd subtable + falcosecurity::table_field m_fd_name_value; + falcosecurity::table_field m_fd_nameraw_value; + falcosecurity::table_field m_fd_ino_value; + falcosecurity::table_field m_fd_type_value; + falcosecurity::table_field m_fd_flags_value; + falcosecurity::table_field m_fd_dev_value; + falcosecurity::table_field m_fd_mount_id_value; + + /* container related */ + falcosecurity::table_field m_container_id; ///< heuristic-based container id + + /* Custom fields*/ + falcosecurity::table_field m_lastevent_fd_field; }; // required; standard plugin API diff --git a/plugins/anomalydetection/test/include/test_helpers.h b/plugins/anomalydetection/test/include/test_helpers.h index 6aeb37e1..ee4bb3e4 100644 --- a/plugins/anomalydetection/test/include/test_helpers.h +++ b/plugins/anomalydetection/test/include/test_helpers.h @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -#define INIT_CONFIG "{\"count_min_sketch\":{\"enabled\":true,\"n_sketches\":3,\"gamma_eps\":[[0.001,0.0001],[0.001,0.0001],[0.001,0.0001]],\"behavior_profiles\":[{\"fields\":\"%container.id %proc.name %proc.pname %proc.exepath %proc.pexepath %proc.tty %proc.vpid %proc.pvid]\",\"event_codes\":[293,331]},{\"fields\":\"%container.id %proc.name %proc.aname[1] %proc.aname[2] %proc.aname[3] %proc.exepath %proc.tty %proc.vpgid.name %proc.sname %fd.name\",\"event_codes\":[3,307,327]},{\"fields\":\"%container.id %proc.cmdline %proc.name %proc.aname[0] %proc.aname[1] %proc.aname[2] %proc.aname[3] %proc.aname[4] %proc.aname[5] %proc.aname[6] %proc.aname[7] %proc.pid %proc.apid[0] %proc.apid[1] %proc.apid[2] %proc.apid[3] %proc.apid[4] %proc.apid[5] %proc.apid[6] %proc.apid[7] %proc.exepath %proc.aexepath[0] %proc.aexepath[1] %proc.aexepath[2] %proc.aexepath[3] %proc.aexepath[4] %proc.aexepath[5] %proc.aexepath[6] %proc.aexepath[7] %proc.vpgid %proc.vpgid.name %proc.sid %proc.sname\",\"event_codes\":[293,331]}]}}" +#define INIT_CONFIG "{\"count_min_sketch\":{\"enabled\":true,\"n_sketches\":3,\"gamma_eps\":[[0.001,0.0001],[0.001,0.0001],[0.001,0.0001]],\"behavior_profiles\":[{\"fields\":\"%container.id %proc.name %proc.pname %proc.exepath %proc.pexepath %proc.tty %proc.vpid %proc.pvid]\",\"event_codes\":[293,331]},{\"fields\":\"%fd.ino %fd.nameraw %fd.name\",\"event_codes\":[3,307,327]},{\"fields\":\"%container.id %proc.cmdline %proc.name %proc.aname[0] %proc.aname[1] %proc.aname[2] %proc.aname[3] %proc.aname[4] %proc.aname[5] %proc.aname[6] %proc.aname[7] %proc.pid %proc.apid[0] %proc.apid[1] %proc.apid[2] %proc.apid[3] %proc.apid[4] %proc.apid[5] %proc.apid[6] %proc.apid[7] %proc.exepath %proc.aexepath[0] %proc.aexepath[1] %proc.aexepath[2] %proc.aexepath[3] %proc.aexepath[4] %proc.aexepath[5] %proc.aexepath[6] %proc.aexepath[7] %proc.vpgid %proc.vpgid.name %proc.sid %proc.sname\",\"event_codes\":[293,331]}]}}" #define ASSERT_PLUGIN_INITIALIZATION(p_o, p_l) \ { \ diff --git a/plugins/anomalydetection/test/src/num/cms.ut.cpp b/plugins/anomalydetection/test/src/num/cms.ut.cpp index 3b329404..8cdbfbe2 100644 --- a/plugins/anomalydetection/test/src/num/cms.ut.cpp +++ b/plugins/anomalydetection/test/src/num/cms.ut.cpp @@ -105,7 +105,7 @@ TEST_F(sinsp_with_test_input, plugin_anomalydetection_filterchecks_fields) ASSERT_TRUE(field_exists(evt, "anomaly.count_min_sketch.profile", pl_flist)); ASSERT_EQ(get_field_as_string(evt, "anomaly.count_min_sketch.profile", pl_flist), "test-exeinit/bin/test-exe/sbin/init3481820"); ASSERT_EQ(get_field_as_string(evt, "anomaly.count_min_sketch.profile[0]", pl_flist), "test-exeinit/bin/test-exe/sbin/init3481820"); - ASSERT_EQ(get_field_as_string(evt, "anomaly.count_min_sketch.profile[1]", pl_flist), "test-exeinit/bin/test-exe34818initinit"); + ASSERT_EQ(get_field_as_string(evt, "anomaly.count_min_sketch.profile[1]", pl_flist), ""); ASSERT_EQ(get_field_as_string(evt, "anomaly.count_min_sketch.profile[2]", pl_flist), "test-exe -c 'echo aGVsbG8K | base64 -d'test-exetest-exeinit20201/bin/test-exe/bin/test-exe/sbin/init1init0init"); } @@ -115,7 +115,6 @@ TEST_F(sinsp_with_test_input, plugin_anomalydetection_filterchecks_fields_proc_l filter_check_list pl_flist; ASSERT_PLUGIN_INITIALIZATION(plugin_owner, pl_flist) uint64_t not_relevant_64 = 0; - uint32_t not_relevant_32 = 0; uint64_t pgid = 9999; uint32_t loginuid = UINT32_MAX - 1, euid = 2000U; scap_const_sized_buffer empty_bytebuf = {.buf = nullptr, .size = 0}; @@ -135,3 +134,23 @@ TEST_F(sinsp_with_test_input, plugin_anomalydetection_filterchecks_fields_proc_l ASSERT_EQ(get_field_as_string(evt, "anomaly.count_min_sketch.profile[2]", pl_flist), "p6_t1_comm -c 'echo aGVsbG8K | base64 -d'p6_t1_commp6_t1_commp5_t1_commp4_t1_commp3_t1_commp2_t1_comminit8787827672251/usr/bin/p6_t1_exepath/usr/bin/p6_t1_exepath/usr/bin/p5_t1_exepath/usr/bin/p4_t1_exepath/usr/bin/p3_t1_exepath/usr/bin/p2_t1_exepath/sbin/init9999p5_t1_comm0init"); } + +TEST_F(sinsp_with_test_input, plugin_anomalydetection_filterchecks_fields_fd) +{ + 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(); + + int64_t fd = 4; + add_event(increasing_ts(), 3, PPME_SYSCALL_OPEN_E, 3, "/tmp/../../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); + fd = 5; + add_event(increasing_ts(), 3, PPME_SYSCALL_OPEN_E, 3, "/tmp/../../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); + ASSERT_EQ(get_field_as_string(evt, "fd.name"), "/the_file2"); + ASSERT_EQ(get_field_as_string(evt, "anomaly.count_min_sketch.profile[1]", pl_flist), "/tmp/../../the_file2/the_file2"); +}