From f5bb677aa66a914777a7038e832e6c96e1edf983 Mon Sep 17 00:00:00 2001 From: Melissa Kilby Date: Mon, 10 Jun 2024 04:50:59 +0000 Subject: [PATCH] new(anomalydetection): init config + start behavior profile extraction Signed-off-by: Melissa Kilby --- plugins/anomalydetection/README.md | 478 ++++++++++++++++- plugins/anomalydetection/src/plugin.cpp | 494 ++++++++++++++++-- plugins/anomalydetection/src/plugin.h | 30 +- .../src/plugin_sinsp_filterchecks.h | 269 ++++++++++ plugins/anomalydetection/src/plugin_utils.h | 47 ++ .../test/include/test_helpers.h | 3 +- .../anomalydetection/test/src/num/cms.ut.cpp | 10 +- 7 files changed, 1266 insertions(+), 65 deletions(-) create mode 100644 plugins/anomalydetection/src/plugin_sinsp_filterchecks.h create mode 100644 plugins/anomalydetection/src/plugin_utils.h diff --git a/plugins/anomalydetection/README.md b/plugins/anomalydetection/README.md index 41b622fb..c2673d4a 100644 --- a/plugins/anomalydetection/README.md +++ b/plugins/anomalydetection/README.md @@ -28,7 +28,8 @@ Here is the current set of supported fields: | NAME | TYPE | ARG | DESCRIPTION | |-------------------|----------|-----------------|-------------------------------------------------------------------------| -| `anomalydetection.count_min_sketch` | `uint64` | Key, Optional | Count Min Sketch Estimate according to the specified behavior profile for a predefined set of {syscalls} events. Access different behavior profiles/sketches using indices. For instance, anomalydetection.count_min_sketch[0] retrieves the first behavior profile defined in the plugins' `init_config`. | +| `anomaly.count_min_sketch` | `uint64` | Key, Optional | Count Min Sketch Estimate according to the specified behavior profile for a predefined set of {syscalls} events. Access different behavior profiles/sketches using indices. For instance, anomaly.count_min_sketch[0] retrieves the first behavior profile defined in the plugins' `init_config`. | +| `anomaly.count_min_sketch.profile` | `string` | Key, Optional | Concatenated string according to the specified behavior profile (not preserving original order). Access different behavior profiles using indices. For instance, anomaly.count_min_sketch.profile[0] retrieves the first behavior profile defined in the plugins' `init_config`. | ## Usage @@ -42,11 +43,42 @@ plugins: - name: anomalydetection library_path: libanomalydetection.so init_config: - n_sketches: 3 + count_min_sketch: + enabled: true + n_sketches: 3 + # `gamma_eps`: auto-calculate rows and cols; usage: [[gamma, eps], ...]; + # gamma -> error probability -> determine d / rows / number of hash functions + # eps -> relative error -> determine w / cols / number of buckets + gamma_eps: [ + [0.001, 0.0001], + [0.001, 0.0001], + [0.001, 0.0001] + ] + # `rows_cols`: pass explicit dimensions, supersedes `gamma_eps`; usage: [[7, 27183], ...]; by default disabled when not used. + # rows_cols: [] + behavior_profiles: [ + { + "fields": "%container.id %proc.name %proc.aname[1] %proc.aname[2] %proc.aname[3] %proc.exepath %proc.tty %proc.vpgid.name %proc.sname", + # execve, execveat + "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", + # open, openat, openat2 + "event_codes": [3, 307, 327] + }, + { + "fields": "%container.id %proc.args", + # execve, execveat + "event_codes": [293, 331] + } + ] load_plugins: [anomalydetection] ``` +The first version is quite restrictive because the plugin API is not yet complete for this use case. Currently, you have to manually look up the correct PPME event codes. Always use the highest / latest event version, for example `PPME_SYSCALL_EXECVE_19_X` for the exit event of the `execve` syscall or `PPME_SYSCALL_OPENAT_2_X` for the `openat` syscall. Read this [blog post](https://falco.org/blog/adaptive-syscalls-selection/) to learn more about PPME event codes versus syscall names. See the [PPME event codes](#ppme-event-codes) reference below. + **Open Parameters**: This plugin does not have open params. @@ -62,8 +94,8 @@ Example of a standard Falco rule using the `anomalydetection` fields: condition: (evt.type in (execve, execveat) and evt.dir=<) - rule: execve count_min_sketch test desc: "execve count_min_sketch test" - condition: spawned_process and proc.name=cat and anomalydetection.count_min_sketch > 10 - output: '%anomalydetection.count_min_sketch %proc.pid %proc.ppid %proc.name %user.loginuid %user.name %user.uid %proc.cmdline %container.id %evt.type %evt.res %proc.cwd %proc.sid %proc.exepath %container.image.repository' + condition: spawned_process and proc.name=cat and anomaly.count_min_sketch > 10 + output: '%anomaly.count_min_sketch %proc.pid %proc.ppid %proc.name %user.loginuid %user.name %user.uid %proc.cmdline %container.id %evt.type %evt.res %proc.cwd %proc.sid %proc.exepath %container.image.repository' priority: NOTICE tags: [maturity_sandbox, host, container, process, anomalydetection] ``` @@ -93,3 +125,441 @@ make; sudo mkdir -p /usr/share/falco/plugins/; sudo cp -f libanomalydetection.so /usr/share/falco/plugins/libanomalydetection.so; ``` + + +## References + +### PPME event codes + +``` +typedef enum { + PPME_GENERIC_E = 0, + PPME_GENERIC_X = 1, + PPME_SYSCALL_OPEN_E = 2, + PPME_SYSCALL_OPEN_X = 3, + PPME_SYSCALL_CLOSE_E = 4, + PPME_SYSCALL_CLOSE_X = 5, + PPME_SYSCALL_READ_E = 6, + PPME_SYSCALL_READ_X = 7, + PPME_SYSCALL_WRITE_E = 8, + PPME_SYSCALL_WRITE_X = 9, + PPME_SYSCALL_BRK_1_E = 10, + PPME_SYSCALL_BRK_1_X = 11, + PPME_SYSCALL_EXECVE_8_E = 12, + PPME_SYSCALL_EXECVE_8_X = 13, + PPME_SYSCALL_CLONE_11_E = 14, + PPME_SYSCALL_CLONE_11_X = 15, + PPME_PROCEXIT_E = 16, + PPME_PROCEXIT_X = 17, /* This should never be called */ + PPME_SOCKET_SOCKET_E = 18, + PPME_SOCKET_SOCKET_X = 19, + PPME_SOCKET_BIND_E = 20, + PPME_SOCKET_BIND_X = 21, + PPME_SOCKET_CONNECT_E = 22, + PPME_SOCKET_CONNECT_X = 23, + PPME_SOCKET_LISTEN_E = 24, + PPME_SOCKET_LISTEN_X = 25, + PPME_SOCKET_ACCEPT_E = 26, + PPME_SOCKET_ACCEPT_X = 27, + PPME_SOCKET_SEND_E = 28, + PPME_SOCKET_SEND_X = 29, + PPME_SOCKET_SENDTO_E = 30, + PPME_SOCKET_SENDTO_X = 31, + PPME_SOCKET_RECV_E = 32, + PPME_SOCKET_RECV_X = 33, + PPME_SOCKET_RECVFROM_E = 34, + PPME_SOCKET_RECVFROM_X = 35, + PPME_SOCKET_SHUTDOWN_E = 36, + PPME_SOCKET_SHUTDOWN_X = 37, + PPME_SOCKET_GETSOCKNAME_E = 38, + PPME_SOCKET_GETSOCKNAME_X = 39, + PPME_SOCKET_GETPEERNAME_E = 40, + PPME_SOCKET_GETPEERNAME_X = 41, + PPME_SOCKET_SOCKETPAIR_E = 42, + PPME_SOCKET_SOCKETPAIR_X = 43, + PPME_SOCKET_SETSOCKOPT_E = 44, + PPME_SOCKET_SETSOCKOPT_X = 45, + PPME_SOCKET_GETSOCKOPT_E = 46, + PPME_SOCKET_GETSOCKOPT_X = 47, + PPME_SOCKET_SENDMSG_E = 48, + PPME_SOCKET_SENDMSG_X = 49, + PPME_SOCKET_SENDMMSG_E = 50, + PPME_SOCKET_SENDMMSG_X = 51, + PPME_SOCKET_RECVMSG_E = 52, + PPME_SOCKET_RECVMSG_X = 53, + PPME_SOCKET_RECVMMSG_E = 54, + PPME_SOCKET_RECVMMSG_X = 55, + PPME_SOCKET_ACCEPT4_E = 56, + PPME_SOCKET_ACCEPT4_X = 57, + PPME_SYSCALL_CREAT_E = 58, + PPME_SYSCALL_CREAT_X = 59, + PPME_SYSCALL_PIPE_E = 60, + PPME_SYSCALL_PIPE_X = 61, + PPME_SYSCALL_EVENTFD_E = 62, + PPME_SYSCALL_EVENTFD_X = 63, + PPME_SYSCALL_FUTEX_E = 64, + PPME_SYSCALL_FUTEX_X = 65, + PPME_SYSCALL_STAT_E = 66, + PPME_SYSCALL_STAT_X = 67, + PPME_SYSCALL_LSTAT_E = 68, + PPME_SYSCALL_LSTAT_X = 69, + PPME_SYSCALL_FSTAT_E = 70, + PPME_SYSCALL_FSTAT_X = 71, + PPME_SYSCALL_STAT64_E = 72, + PPME_SYSCALL_STAT64_X = 73, + PPME_SYSCALL_LSTAT64_E = 74, + PPME_SYSCALL_LSTAT64_X = 75, + PPME_SYSCALL_FSTAT64_E = 76, + PPME_SYSCALL_FSTAT64_X = 77, + PPME_SYSCALL_EPOLLWAIT_E = 78, + PPME_SYSCALL_EPOLLWAIT_X = 79, + PPME_SYSCALL_POLL_E = 80, + PPME_SYSCALL_POLL_X = 81, + PPME_SYSCALL_SELECT_E = 82, + PPME_SYSCALL_SELECT_X = 83, + PPME_SYSCALL_NEWSELECT_E = 84, + PPME_SYSCALL_NEWSELECT_X = 85, + PPME_SYSCALL_LSEEK_E = 86, + PPME_SYSCALL_LSEEK_X = 87, + PPME_SYSCALL_LLSEEK_E = 88, + PPME_SYSCALL_LLSEEK_X = 89, + PPME_SYSCALL_IOCTL_2_E = 90, + PPME_SYSCALL_IOCTL_2_X = 91, + PPME_SYSCALL_GETCWD_E = 92, + PPME_SYSCALL_GETCWD_X = 93, + PPME_SYSCALL_CHDIR_E = 94, + PPME_SYSCALL_CHDIR_X = 95, + PPME_SYSCALL_FCHDIR_E = 96, + PPME_SYSCALL_FCHDIR_X = 97, + /* mkdir/rmdir events are not emitted anymore */ + PPME_SYSCALL_MKDIR_E = 98, + PPME_SYSCALL_MKDIR_X = 99, + PPME_SYSCALL_RMDIR_E = 100, + PPME_SYSCALL_RMDIR_X = 101, + PPME_SYSCALL_OPENAT_E = 102, + PPME_SYSCALL_OPENAT_X = 103, + PPME_SYSCALL_LINK_E = 104, + PPME_SYSCALL_LINK_X = 105, + PPME_SYSCALL_LINKAT_E = 106, + PPME_SYSCALL_LINKAT_X = 107, + PPME_SYSCALL_UNLINK_E = 108, + PPME_SYSCALL_UNLINK_X = 109, + PPME_SYSCALL_UNLINKAT_E = 110, + PPME_SYSCALL_UNLINKAT_X = 111, + PPME_SYSCALL_PREAD_E = 112, + PPME_SYSCALL_PREAD_X = 113, + PPME_SYSCALL_PWRITE_E = 114, + PPME_SYSCALL_PWRITE_X = 115, + PPME_SYSCALL_READV_E = 116, + PPME_SYSCALL_READV_X = 117, + PPME_SYSCALL_WRITEV_E = 118, + PPME_SYSCALL_WRITEV_X = 119, + PPME_SYSCALL_PREADV_E = 120, + PPME_SYSCALL_PREADV_X = 121, + PPME_SYSCALL_PWRITEV_E = 122, + PPME_SYSCALL_PWRITEV_X = 123, + PPME_SYSCALL_DUP_E = 124, + PPME_SYSCALL_DUP_X = 125, + PPME_SYSCALL_SIGNALFD_E = 126, + PPME_SYSCALL_SIGNALFD_X = 127, + PPME_SYSCALL_KILL_E = 128, + PPME_SYSCALL_KILL_X = 129, + PPME_SYSCALL_TKILL_E = 130, + PPME_SYSCALL_TKILL_X = 131, + PPME_SYSCALL_TGKILL_E = 132, + PPME_SYSCALL_TGKILL_X = 133, + PPME_SYSCALL_NANOSLEEP_E = 134, + PPME_SYSCALL_NANOSLEEP_X = 135, + PPME_SYSCALL_TIMERFD_CREATE_E = 136, + PPME_SYSCALL_TIMERFD_CREATE_X = 137, + PPME_SYSCALL_INOTIFY_INIT_E = 138, + PPME_SYSCALL_INOTIFY_INIT_X = 139, + PPME_SYSCALL_GETRLIMIT_E = 140, + PPME_SYSCALL_GETRLIMIT_X = 141, + PPME_SYSCALL_SETRLIMIT_E = 142, + PPME_SYSCALL_SETRLIMIT_X = 143, + PPME_SYSCALL_PRLIMIT_E = 144, + PPME_SYSCALL_PRLIMIT_X = 145, + PPME_SCHEDSWITCH_1_E = 146, + PPME_SCHEDSWITCH_1_X = 147, /* This should never be called */ + PPME_DROP_E = 148, /* For internal use */ + PPME_DROP_X = 149, /* For internal use */ + PPME_SYSCALL_FCNTL_E = 150, /* For internal use */ + PPME_SYSCALL_FCNTL_X = 151, /* For internal use */ + PPME_SCHEDSWITCH_6_E = 152, + PPME_SCHEDSWITCH_6_X = 153, /* This should never be called */ + PPME_SYSCALL_EXECVE_13_E = 154, + PPME_SYSCALL_EXECVE_13_X = 155, + PPME_SYSCALL_CLONE_16_E = 156, + PPME_SYSCALL_CLONE_16_X = 157, + PPME_SYSCALL_BRK_4_E = 158, + PPME_SYSCALL_BRK_4_X = 159, + PPME_SYSCALL_MMAP_E = 160, + PPME_SYSCALL_MMAP_X = 161, + PPME_SYSCALL_MMAP2_E = 162, + PPME_SYSCALL_MMAP2_X = 163, + PPME_SYSCALL_MUNMAP_E = 164, + PPME_SYSCALL_MUNMAP_X = 165, + PPME_SYSCALL_SPLICE_E = 166, + PPME_SYSCALL_SPLICE_X = 167, + PPME_SYSCALL_PTRACE_E = 168, + PPME_SYSCALL_PTRACE_X = 169, + PPME_SYSCALL_IOCTL_3_E = 170, + PPME_SYSCALL_IOCTL_3_X = 171, + PPME_SYSCALL_EXECVE_14_E = 172, + PPME_SYSCALL_EXECVE_14_X = 173, + PPME_SYSCALL_RENAME_E = 174, + PPME_SYSCALL_RENAME_X = 175, + PPME_SYSCALL_RENAMEAT_E = 176, + PPME_SYSCALL_RENAMEAT_X = 177, + PPME_SYSCALL_SYMLINK_E = 178, + PPME_SYSCALL_SYMLINK_X = 179, + PPME_SYSCALL_SYMLINKAT_E = 180, + PPME_SYSCALL_SYMLINKAT_X = 181, + PPME_SYSCALL_FORK_E = 182, + PPME_SYSCALL_FORK_X = 183, + PPME_SYSCALL_VFORK_E = 184, + PPME_SYSCALL_VFORK_X = 185, + PPME_PROCEXIT_1_E = 186, + PPME_PROCEXIT_1_X = 187, /* This should never be called */ + PPME_SYSCALL_SENDFILE_E = 188, + PPME_SYSCALL_SENDFILE_X = 189, /* This should never be called */ + PPME_SYSCALL_QUOTACTL_E = 190, + PPME_SYSCALL_QUOTACTL_X = 191, + PPME_SYSCALL_SETRESUID_E = 192, + PPME_SYSCALL_SETRESUID_X = 193, + PPME_SYSCALL_SETRESGID_E = 194, + PPME_SYSCALL_SETRESGID_X = 195, + PPME_SCAPEVENT_E = 196, + PPME_SCAPEVENT_X = 197, /* This should never be called */ + PPME_SYSCALL_SETUID_E = 198, + PPME_SYSCALL_SETUID_X = 199, + PPME_SYSCALL_SETGID_E = 200, + PPME_SYSCALL_SETGID_X = 201, + PPME_SYSCALL_GETUID_E = 202, + PPME_SYSCALL_GETUID_X = 203, + PPME_SYSCALL_GETEUID_E = 204, + PPME_SYSCALL_GETEUID_X = 205, + PPME_SYSCALL_GETGID_E = 206, + PPME_SYSCALL_GETGID_X = 207, + PPME_SYSCALL_GETEGID_E = 208, + PPME_SYSCALL_GETEGID_X = 209, + PPME_SYSCALL_GETRESUID_E = 210, + PPME_SYSCALL_GETRESUID_X = 211, + PPME_SYSCALL_GETRESGID_E = 212, + PPME_SYSCALL_GETRESGID_X = 213, + PPME_SYSCALL_EXECVE_15_E = 214, + PPME_SYSCALL_EXECVE_15_X = 215, + PPME_SYSCALL_CLONE_17_E = 216, + PPME_SYSCALL_CLONE_17_X = 217, + PPME_SYSCALL_FORK_17_E = 218, + PPME_SYSCALL_FORK_17_X = 219, + PPME_SYSCALL_VFORK_17_E = 220, + PPME_SYSCALL_VFORK_17_X = 221, + PPME_SYSCALL_CLONE_20_E = 222, + PPME_SYSCALL_CLONE_20_X = 223, + PPME_SYSCALL_FORK_20_E = 224, + PPME_SYSCALL_FORK_20_X = 225, + PPME_SYSCALL_VFORK_20_E = 226, + PPME_SYSCALL_VFORK_20_X = 227, + PPME_CONTAINER_E = 228, + PPME_CONTAINER_X = 229, + PPME_SYSCALL_EXECVE_16_E = 230, + PPME_SYSCALL_EXECVE_16_X = 231, + PPME_SIGNALDELIVER_E = 232, + PPME_SIGNALDELIVER_X = 233, /* This should never be called */ + PPME_PROCINFO_E = 234, + PPME_PROCINFO_X = 235, /* This should never be called */ + PPME_SYSCALL_GETDENTS_E = 236, + PPME_SYSCALL_GETDENTS_X = 237, + PPME_SYSCALL_GETDENTS64_E = 238, + PPME_SYSCALL_GETDENTS64_X = 239, + PPME_SYSCALL_SETNS_E = 240, + PPME_SYSCALL_SETNS_X = 241, + PPME_SYSCALL_FLOCK_E = 242, + PPME_SYSCALL_FLOCK_X = 243, + PPME_CPU_HOTPLUG_E = 244, + PPME_CPU_HOTPLUG_X = 245, /* This should never be called */ + PPME_SOCKET_ACCEPT_5_E = 246, + PPME_SOCKET_ACCEPT_5_X = 247, + PPME_SOCKET_ACCEPT4_5_E = 248, + PPME_SOCKET_ACCEPT4_5_X = 249, + PPME_SYSCALL_SEMOP_E = 250, + PPME_SYSCALL_SEMOP_X = 251, + PPME_SYSCALL_SEMCTL_E = 252, + PPME_SYSCALL_SEMCTL_X = 253, + PPME_SYSCALL_PPOLL_E = 254, + PPME_SYSCALL_PPOLL_X = 255, + PPME_SYSCALL_MOUNT_E = 256, + PPME_SYSCALL_MOUNT_X = 257, + PPME_SYSCALL_UMOUNT_E = 258, + PPME_SYSCALL_UMOUNT_X = 259, + PPME_K8S_E = 260, + PPME_K8S_X = 261, + PPME_SYSCALL_SEMGET_E = 262, + PPME_SYSCALL_SEMGET_X = 263, + PPME_SYSCALL_ACCESS_E = 264, + PPME_SYSCALL_ACCESS_X = 265, + PPME_SYSCALL_CHROOT_E = 266, + PPME_SYSCALL_CHROOT_X = 267, + PPME_TRACER_E = 268, + PPME_TRACER_X = 269, + PPME_MESOS_E = 270, + PPME_MESOS_X = 271, + PPME_CONTAINER_JSON_E = 272, + PPME_CONTAINER_JSON_X = 273, + PPME_SYSCALL_SETSID_E = 274, + PPME_SYSCALL_SETSID_X = 275, + PPME_SYSCALL_MKDIR_2_E = 276, + PPME_SYSCALL_MKDIR_2_X = 277, + PPME_SYSCALL_RMDIR_2_E = 278, + PPME_SYSCALL_RMDIR_2_X = 279, + PPME_NOTIFICATION_E = 280, + PPME_NOTIFICATION_X = 281, + PPME_SYSCALL_EXECVE_17_E = 282, + PPME_SYSCALL_EXECVE_17_X = 283, + PPME_SYSCALL_UNSHARE_E = 284, + PPME_SYSCALL_UNSHARE_X = 285, + PPME_INFRASTRUCTURE_EVENT_E = 286, + PPME_INFRASTRUCTURE_EVENT_X = 287, + PPME_SYSCALL_EXECVE_18_E = 288, + PPME_SYSCALL_EXECVE_18_X = 289, + PPME_PAGE_FAULT_E = 290, + PPME_PAGE_FAULT_X = 291, + PPME_SYSCALL_EXECVE_19_E = 292, + PPME_SYSCALL_EXECVE_19_X = 293, + PPME_SYSCALL_SETPGID_E = 294, + PPME_SYSCALL_SETPGID_X = 295, + PPME_SYSCALL_BPF_E = 296, + PPME_SYSCALL_BPF_X = 297, + PPME_SYSCALL_SECCOMP_E = 298, + PPME_SYSCALL_SECCOMP_X = 299, + PPME_SYSCALL_UNLINK_2_E = 300, + PPME_SYSCALL_UNLINK_2_X = 301, + PPME_SYSCALL_UNLINKAT_2_E = 302, + PPME_SYSCALL_UNLINKAT_2_X = 303, + PPME_SYSCALL_MKDIRAT_E = 304, + PPME_SYSCALL_MKDIRAT_X = 305, + PPME_SYSCALL_OPENAT_2_E = 306, + PPME_SYSCALL_OPENAT_2_X = 307, + PPME_SYSCALL_LINK_2_E = 308, + PPME_SYSCALL_LINK_2_X = 309, + PPME_SYSCALL_LINKAT_2_E = 310, + PPME_SYSCALL_LINKAT_2_X = 311, + PPME_SYSCALL_FCHMODAT_E = 312, + PPME_SYSCALL_FCHMODAT_X = 313, + PPME_SYSCALL_CHMOD_E = 314, + PPME_SYSCALL_CHMOD_X = 315, + PPME_SYSCALL_FCHMOD_E = 316, + PPME_SYSCALL_FCHMOD_X = 317, + PPME_SYSCALL_RENAMEAT2_E = 318, + PPME_SYSCALL_RENAMEAT2_X = 319, + PPME_SYSCALL_USERFAULTFD_E = 320, + PPME_SYSCALL_USERFAULTFD_X = 321, + PPME_PLUGINEVENT_E = 322, + PPME_PLUGINEVENT_X = 323, + PPME_CONTAINER_JSON_2_E = 324, + PPME_CONTAINER_JSON_2_X = 325, + PPME_SYSCALL_OPENAT2_E = 326, + PPME_SYSCALL_OPENAT2_X = 327, + PPME_SYSCALL_MPROTECT_E = 328, + PPME_SYSCALL_MPROTECT_X = 329, + PPME_SYSCALL_EXECVEAT_E = 330, + PPME_SYSCALL_EXECVEAT_X = 331, + PPME_SYSCALL_COPY_FILE_RANGE_E = 332, + PPME_SYSCALL_COPY_FILE_RANGE_X = 333, + PPME_SYSCALL_CLONE3_E = 334, + PPME_SYSCALL_CLONE3_X = 335, + PPME_SYSCALL_OPEN_BY_HANDLE_AT_E = 336, + PPME_SYSCALL_OPEN_BY_HANDLE_AT_X = 337, + PPME_SYSCALL_IO_URING_SETUP_E = 338, + PPME_SYSCALL_IO_URING_SETUP_X = 339, + PPME_SYSCALL_IO_URING_ENTER_E = 340, + PPME_SYSCALL_IO_URING_ENTER_X = 341, + PPME_SYSCALL_IO_URING_REGISTER_E = 342, + PPME_SYSCALL_IO_URING_REGISTER_X = 343, + PPME_SYSCALL_MLOCK_E = 344, + PPME_SYSCALL_MLOCK_X = 345, + PPME_SYSCALL_MUNLOCK_E = 346, + PPME_SYSCALL_MUNLOCK_X = 347, + PPME_SYSCALL_MLOCKALL_E = 348, + PPME_SYSCALL_MLOCKALL_X = 349, + PPME_SYSCALL_MUNLOCKALL_E = 350, + PPME_SYSCALL_MUNLOCKALL_X = 351, + PPME_SYSCALL_CAPSET_E = 352, + PPME_SYSCALL_CAPSET_X = 353, + PPME_USER_ADDED_E = 354, + PPME_USER_ADDED_X = 355, + PPME_USER_DELETED_E = 356, + PPME_USER_DELETED_X = 357, + PPME_GROUP_ADDED_E = 358, + PPME_GROUP_ADDED_X = 359, + PPME_GROUP_DELETED_E = 360, + PPME_GROUP_DELETED_X = 361, + PPME_SYSCALL_DUP2_E = 362, + PPME_SYSCALL_DUP2_X = 363, + PPME_SYSCALL_DUP3_E = 364, + PPME_SYSCALL_DUP3_X = 365, + PPME_SYSCALL_DUP_1_E = 366, + PPME_SYSCALL_DUP_1_X = 367, + PPME_SYSCALL_BPF_2_E = 368, + PPME_SYSCALL_BPF_2_X = 369, + PPME_SYSCALL_MLOCK2_E = 370, + PPME_SYSCALL_MLOCK2_X = 371, + PPME_SYSCALL_FSCONFIG_E = 372, + PPME_SYSCALL_FSCONFIG_X = 373, + PPME_SYSCALL_EPOLL_CREATE_E = 374, + PPME_SYSCALL_EPOLL_CREATE_X = 375, + PPME_SYSCALL_EPOLL_CREATE1_E = 376, + PPME_SYSCALL_EPOLL_CREATE1_X = 377, + PPME_SYSCALL_CHOWN_E = 378, + PPME_SYSCALL_CHOWN_X = 379, + PPME_SYSCALL_LCHOWN_E = 380, + PPME_SYSCALL_LCHOWN_X = 381, + PPME_SYSCALL_FCHOWN_E = 382, + PPME_SYSCALL_FCHOWN_X = 383, + PPME_SYSCALL_FCHOWNAT_E = 384, + PPME_SYSCALL_FCHOWNAT_X = 385, + PPME_SYSCALL_UMOUNT_1_E = 386, + PPME_SYSCALL_UMOUNT_1_X = 387, + PPME_SOCKET_ACCEPT4_6_E = 388, + PPME_SOCKET_ACCEPT4_6_X = 389, + PPME_SYSCALL_UMOUNT2_E = 390, + PPME_SYSCALL_UMOUNT2_X = 391, + PPME_SYSCALL_PIPE2_E = 392, + PPME_SYSCALL_PIPE2_X = 393, + PPME_SYSCALL_INOTIFY_INIT1_E = 394, + PPME_SYSCALL_INOTIFY_INIT1_X = 395, + PPME_SYSCALL_EVENTFD2_E = 396, + PPME_SYSCALL_EVENTFD2_X = 397, + PPME_SYSCALL_SIGNALFD4_E = 398, + PPME_SYSCALL_SIGNALFD4_X = 399, + PPME_SYSCALL_PRCTL_E = 400, + PPME_SYSCALL_PRCTL_X = 401, + PPME_ASYNCEVENT_E = 402, + PPME_ASYNCEVENT_X = 403, + PPME_SYSCALL_MEMFD_CREATE_E = 404, + PPME_SYSCALL_MEMFD_CREATE_X = 405, + PPME_SYSCALL_PIDFD_GETFD_E = 406, + PPME_SYSCALL_PIDFD_GETFD_X = 407, + PPME_SYSCALL_PIDFD_OPEN_E = 408, + PPME_SYSCALL_PIDFD_OPEN_X = 409, + PPME_SYSCALL_INIT_MODULE_E = 410, + PPME_SYSCALL_INIT_MODULE_X = 411, + PPME_SYSCALL_FINIT_MODULE_E = 412, + PPME_SYSCALL_FINIT_MODULE_X = 413, + PPME_SYSCALL_MKNOD_E = 414, + PPME_SYSCALL_MKNOD_X = 415, + PPME_SYSCALL_MKNODAT_E = 416, + PPME_SYSCALL_MKNODAT_X = 417, + PPME_SYSCALL_NEWFSTATAT_E = 418, + PPME_SYSCALL_NEWFSTATAT_X = 419, + PPME_SYSCALL_PROCESS_VM_READV_E = 420, + PPME_SYSCALL_PROCESS_VM_READV_X = 421, + PPME_SYSCALL_PROCESS_VM_WRITEV_E = 422, + PPME_SYSCALL_PROCESS_VM_WRITEV_X = 423, + PPME_SYSCALL_DELETE_MODULE_E = 424, + PPME_SYSCALL_DELETE_MODULE_X = 425, + PPM_EVENT_MAX = 426 +} ppm_event_code; +``` \ No newline at end of file diff --git a/plugins/anomalydetection/src/plugin.cpp b/plugins/anomalydetection/src/plugin.cpp index 5524d6ad..ad0f8649 100644 --- a/plugins/anomalydetection/src/plugin.cpp +++ b/plugins/anomalydetection/src/plugin.cpp @@ -16,11 +16,369 @@ limitations under the License. */ #include "plugin.h" +#include "plugin_sinsp_filterchecks.h" void anomalydetection::log_error(std::string err_mess) +{ + printf("%s %s\n", PLUGIN_LOG_PREFIX, err_mess.c_str()); +} + +static const filtercheck_field_info sinsp_filter_check_fields[] = +{ + {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "proc.exe", "First Argument", "The first command-line argument (i.e., argv[0]), typically the executable name or a custom string as specified by the user. It is primarily obtained from syscall arguments, truncated after 4096 bytes, or, as a fallback, by reading /proc/PID/cmdline, in which case it may be truncated after 1024 bytes. This field may differ from the last component of proc.exepath, reflecting how command invocation and execution paths can vary."}, + {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "proc.pexe", "Parent First Argument", "The proc.exe (first command line argument argv[0]) of the parent process."}, + {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_ARG_ALLOWED | EPF_NO_RHS | EPF_NO_TRANSFORMER, PF_NA, "proc.aexe", "Ancestor First Argument", "The proc.exe (first command line argument argv[0]) for a specific process ancestor. You can access different levels of ancestors by using indices. For example, proc.aexe[1] retrieves the proc.exe of the parent process, proc.aexe[2] retrieves the proc.exe of the grandparent process, and so on. The current process's proc.exe line can be obtained using proc.aexe[0]. When used without any arguments, proc.aexe is applicable only in filters and matches any of the process ancestors. For instance, you can use `proc.aexe endswith java` to match any process ancestor whose proc.exe ends with the term `java`."}, + {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "proc.exepath", "Process Executable Path", "The full executable path of a process, resolving to the canonical path for symlinks. This is primarily obtained from the kernel, or as a fallback, by reading /proc/PID/exe (in the latter case, the path is truncated after 1024 bytes). For eBPF drivers, due to verifier limits, path components may be truncated to 24 for legacy eBPF on kernel <5.2, 48 for legacy eBPF on kernel >=5.2, or 96 for modern eBPF."}, + {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "proc.pexepath", "Parent Process Executable Path", "The proc.exepath (full executable path) of the parent process."}, + {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_ARG_ALLOWED | EPF_NO_RHS | EPF_NO_TRANSFORMER, PF_NA, "proc.aexepath", "Ancestor Executable Path", "The proc.exepath (full executable path) for a specific process ancestor. You can access different levels of ancestors by using indices. For example, proc.aexepath[1] retrieves the proc.exepath of the parent process, proc.aexepath[2] retrieves the proc.exepath of the grandparent process, and so on. The current process's proc.exepath line can be obtained using proc.aexepath[0]. When used without any arguments, proc.aexepath is applicable only in filters and matches any of the process ancestors. For instance, you can use `proc.aexepath endswith java` to match any process ancestor whose path ends with the term `java`."}, + {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "proc.name", "Name", "The process name (truncated after 16 characters) generating the event (task->comm). Truncation is determined by kernel settings and not by Falco. This field is collected from the syscalls args or, as a fallback, extracted from /proc/PID/status. The name of the process and the name of the executable file on disk (if applicable) can be different if a process is given a custom name which is often the case for example for java applications."}, + {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "proc.pname", "Parent Name", "The proc.name truncated after 16 characters) of the process generating the event."}, + {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_ARG_ALLOWED | EPF_NO_RHS | EPF_NO_TRANSFORMER, PF_NA, "proc.aname", "Ancestor Name", "The proc.name (truncated after 16 characters) for a specific process ancestor. You can access different levels of ancestors by using indices. For example, proc.aname[1] retrieves the proc.name of the parent process, proc.aname[2] retrieves the proc.name of the grandparent process, and so on. The current process's proc.name line can be obtained using proc.aname[0]. When used without any arguments, proc.aname is applicable only in filters and matches any of the process ancestors. For instance, you can use `proc.aname=bash` to match any process ancestor whose name is `bash`."}, + {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "proc.args", "Arguments", "The arguments passed on the command line when starting the process generating the event excluding argv[0] (truncated after 4096 bytes). This field is collected from the syscalls args or, as a fallback, extracted from /proc/PID/cmdline."}, + {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "proc.cmdline", "Command Line", "The concatenation of `proc.name + proc.args` (truncated after 4096 bytes) when starting the process generating the event."}, + {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "proc.pcmdline", "Parent Command Line", "The proc.cmdline (full command line (proc.name + proc.args)) of the parent of the process generating the event."}, + {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_ARG_ALLOWED | EPF_NO_RHS | EPF_NO_TRANSFORMER, PF_NA, "proc.acmdline", "Ancestor Command Line", "The full command line (proc.name + proc.args) for a specific process ancestor. You can access different levels of ancestors by using indices. For example, proc.acmdline[1] retrieves the full command line of the parent process, proc.acmdline[2] retrieves the proc.cmdline of the grandparent process, and so on. The current process's full command line can be obtained using proc.acmdline[0]. When used without any arguments, proc.acmdline is applicable only in filters and matches any of the process ancestors. For instance, you can use `proc.acmdline contains base64` to match any process ancestor whose command line contains the term base64."}, + {PT_UINT64, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_DEC, "proc.cmdnargs", "Number of Command Line args", "The number of command line args (proc.args)."}, + {PT_UINT64, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_DEC, "proc.cmdlenargs", "Total Count of Characters in Command Line args", "The total count of characters / length of the command line args (proc.args) combined excluding whitespaces between args."}, + {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "proc.exeline", "Executable Command Line", "The full command line, with exe as first argument (proc.exe + proc.args) when starting the process generating the event."}, + {PT_CHARBUF, EPF_ARG_ALLOWED, PF_NA, "proc.env", "Environment", "The environment variables of the process generating the event as concatenated string 'ENV_NAME=value ENV_NAME1=value1'. Can also be used to extract the value of a known env variable, e.g. proc.env[ENV_NAME]."}, + {PT_CHARBUF, EPF_ARG_ALLOWED | EPF_NO_RHS | EPF_NO_TRANSFORMER, PF_NA, "proc.aenv", "Ancestor Environment", "[EXPERIMENTAL] This field can be used in three flavors: (1) as a filter checking all parents, e.g. 'proc.aenv contains xyz', which is similar to the familiar 'proc.aname contains xyz' approach, (2) checking the `proc.env` of a specified level of the parent, e.g. 'proc.aenv[2]', which is similar to the familiar 'proc.aname[2]' approach, or (3) checking the first matched value of a known ENV_NAME in the parent lineage, such as 'proc.aenv[ENV_NAME]' (across a max of 20 ancestor levels). This field may be deprecated or undergo breaking changes in future releases. Please use it with caution."}, + {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "proc.cwd", "Current Working Directory", "The current working directory of the event."}, + {PT_INT64, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_ID, "proc.loginshellid", "Login Shell ID", "The pid of the oldest shell among the ancestors of the current process, if there is one. This field can be used to separate different user sessions."}, + {PT_UINT32, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_ID, "proc.tty", "Process TTY", "The controlling terminal of the process. 0 for processes without a terminal."}, + {PT_INT64, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_ID, "proc.pid", "Process ID", "The id of the process generating the event."}, + {PT_INT64, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_ID, "proc.ppid", "Parent Process ID", "The pid of the parent of the process generating the event."}, + {PT_INT64, EPF_ANOMALY_PLUGIN | EPF_ARG_ALLOWED | EPF_NO_RHS | EPF_NO_TRANSFORMER, PF_ID, "proc.apid", "Ancestor Process ID", "The pid for a specific process ancestor. You can access different levels of ancestors by using indices. For example, proc.apid[1] retrieves the pid of the parent process, proc.apid[2] retrieves the pid of the grandparent process, and so on. The current process's pid can be obtained using proc.apid[0]. When used without any arguments, proc.apid is applicable only in filters and matches any of the process ancestors. For instance, you can use `proc.apid=1337` to match any process ancestor whose pid is equal to 1337."}, + {PT_INT64, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_ID, "proc.vpid", "Virtual Process ID", "The id of the process generating the event as seen from its current PID namespace."}, + {PT_INT64, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_ID, "proc.pvpid", "Parent Virtual Process ID", "The id of the parent process generating the event as seen from its current PID namespace."}, + {PT_INT64, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_ID, "proc.sid", "Process Session ID", "The session id of the process generating the event."}, + {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "proc.sname", "Process Session Name", "The name of the current process's session leader. This is either the process with pid=proc.sid or the eldest ancestor that has the same sid as the current process."}, + {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "proc.sid.exe", "Process Session First Argument", "The first command line argument argv[0] (usually the executable name or a custom one) of the current process's session leader. This is either the process with pid=proc.sid or the eldest ancestor that has the same sid as the current process."}, + {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "proc.sid.exepath", "Process Session Executable Path", "The full executable path of the current process's session leader. This is either the process with pid=proc.sid or the eldest ancestor that has the same sid as the current process."}, + {PT_INT64, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_ID, "proc.vpgid", "Process Virtual Group ID", "The process group id of the process generating the event, as seen from its current PID namespace."}, + {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "proc.vpgid.name", "Process Group Name", "The name of the current process's process group leader. This is either the process with proc.vpgid == proc.vpid or the eldest ancestor that has the same vpgid as the current process. The description of `proc.is_vpgid_leader` offers additional insights."}, + {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "proc.vpgid.exe", "Process Group First Argument", "The first command line argument argv[0] (usually the executable name or a custom one) of the current process's process group leader. This is either the process with proc.vpgid == proc.vpid or the eldest ancestor that has the same vpgid as the current process. The description of `proc.is_vpgid_leader` offers additional insights."}, + {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "proc.vpgid.exepath", "Process Group Executable Path", "The full executable path of the current process's process group leader. This is either the process with proc.vpgid == proc.vpid or the eldest ancestor that has the same vpgid as the current process. The description of `proc.is_vpgid_leader` offers additional insights."}, + {PT_RELTIME, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_DEC, "proc.duration", "Process Duration", "Number of nanoseconds since the process started."}, + {PT_RELTIME, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_DEC, "proc.ppid.duration", "Parent Process Duration", "Number of nanoseconds since the parent process started."}, + {PT_RELTIME, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_DEC, "proc.pid.ts", "Process start ts", "Start of process as epoch timestamp in nanoseconds."}, + {PT_RELTIME, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_DEC, "proc.ppid.ts", "Parent Process start ts", "Start of parent process as epoch timestamp in nanoseconds."}, + {PT_BOOL, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "proc.is_exe_writable", "Process Executable Is Writable", "'true' if this process' executable file is writable by the same user that spawned the process."}, + {PT_BOOL, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "proc.is_exe_upper_layer", "Process Executable Is In Upper Layer", "'true' if this process' executable file is in upper layer in overlayfs. This field value can only be trusted if the underlying kernel version is greater or equal than 3.18.0, since overlayfs was introduced at that time."}, + {PT_BOOL, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "proc.is_exe_from_memfd", "Process Executable Is Stored In Memfd", "'true' if the executable file of the current process is an anonymous file created using memfd_create() and is being executed by referencing its file descriptor (fd). This type of file exists only in memory and not on disk. Relevant to detect malicious in-memory code injection. Requires kernel version greater or equal to 3.17.0."}, + {PT_BOOL, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "proc.is_sid_leader", "Process Is Process Session Leader", "'true' if this process is the leader of the process session, proc.sid == proc.vpid. For host processes vpid reflects pid."}, + {PT_BOOL, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "proc.is_vpgid_leader", "Process Is Virtual Process Group Leader", "'true' if this process is the leader of the virtual process group, proc.vpgid == proc.vpid. For host processes vpgid and vpid reflect pgid and pid. Can help to distinguish if the process was 'directly' executed for instance in a tty (similar to bash history logging, `is_vpgid_leader` would be 'true') or executed as descendent process in the same process group which for example is the case when subprocesses are spawned from a script (`is_vpgid_leader` would be 'false')."}, + {PT_INT64, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_DEC, "proc.exe_ino", "Inode number of executable file on disk", "The inode number of the executable file on disk. Can be correlated with fd.ino."}, + {PT_ABSTIME, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_DEC, "proc.exe_ino.ctime", "Last status change time (ctime) of executable file", "Last status change time of executable file (inode->ctime) as epoch timestamp in nanoseconds. Time is changed by writing or by setting inode information e.g. owner, group, link count, mode etc."}, + {PT_ABSTIME, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_DEC, "proc.exe_ino.mtime", "Last modification time (mtime) of executable file", "Last modification time of executable file (inode->mtime) as epoch timestamp in nanoseconds. Time is changed by file modifications, e.g. by mknod, truncate, utime, write of more than zero bytes etc. For tracking changes in owner, group, link count or mode, use proc.exe_ino.ctime instead."}, + {PT_ABSTIME, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_DEC, "proc.exe_ino.ctime_duration_proc_start", "Number of nanoseconds between ctime exe file and proc clone ts", "Number of nanoseconds between modifying status of executable image and spawning a new process using the changed executable image."}, + {PT_ABSTIME, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_DEC, "proc.exe_ino.ctime_duration_pidns_start", "Number of nanoseconds between pidns start ts and ctime exe file", "Number of nanoseconds between PID namespace start ts and ctime exe file if PID namespace start predates ctime."}, + {PT_UINT64, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_DEC, "proc.pidns_init_start_ts", "Start ts of pid namespace", "Start of PID namespace (container or non container pid namespace) as epoch timestamp in nanoseconds."}, + {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "thread.cap_permitted", "Permitted capabilities", "The permitted capabilities set"}, + {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "thread.cap_inheritable", "Inheritable capabilities", "The inheritable capabilities set"}, + {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "thread.cap_effective", "Effective capabilities", "The effective capabilities set"}, + {PT_BOOL, EPF_NONE, PF_NA, "proc.is_container_healthcheck", "Process Is Container Healthcheck", "'true' if this process is running as a part of the container's health check."}, + {PT_BOOL, EPF_NONE, PF_NA, "proc.is_container_liveness_probe", "Process Is Container Liveness", "'true' if this process is running as a part of the container's liveness probe."}, + {PT_BOOL, EPF_NONE, PF_NA, "proc.is_container_readiness_probe", "Process Is Container Readiness", "'true' if this process is running as a part of the container's readiness probe."}, + {PT_UINT64, EPF_NONE, PF_DEC, "proc.fdopencount", "FD Count", "Number of open FDs for the process"}, + {PT_INT64, EPF_NONE, PF_DEC, "proc.fdlimit", "FD Limit", "Maximum number of FDs the process can open."}, + {PT_DOUBLE, EPF_NONE, PF_NA, "proc.fdusage", "FD Usage", "The ratio between open FDs and maximum available FDs for the process."}, + {PT_UINT64, EPF_NONE, PF_DEC, "proc.vmsize", "VM Size", "Total virtual memory for the process (as kb)."}, + {PT_UINT64, EPF_NONE, PF_DEC, "proc.vmrss", "VM RSS", "Resident non-swapped memory for the process (as kb)."}, + {PT_UINT64, EPF_NONE, PF_DEC, "proc.vmswap", "VM Swap", "Swapped memory for the process (as kb)."}, + {PT_UINT64, EPF_NONE, PF_DEC, "thread.pfmajor", "Major Page Faults", "Number of major page faults since thread start."}, + {PT_UINT64, EPF_NONE, PF_DEC, "thread.pfminor", "Minor Page Faults", "Number of minor page faults since thread start."}, + {PT_INT64, EPF_NONE, PF_ID, "thread.tid", "Thread ID", "The id of the thread generating the event."}, + {PT_BOOL, EPF_NONE, PF_NA, "thread.ismain", "Main Thread", "'true' if the thread generating the event is the main one in the process."}, + {PT_INT64, EPF_NONE, PF_ID, "thread.vtid", "Virtual Thread ID", "The id of the thread generating the event as seen from its current PID namespace."}, + {PT_CHARBUF, EPF_TABLE_ONLY, PF_NA, "thread.nametid", "Thread Name + ID", "This field chains the process name and tid of a thread and can be used as a specific identifier of a thread for a specific execve."}, + {PT_RELTIME, EPF_NONE, PF_DEC, "thread.exectime", "Scheduled Thread CPU Time", "CPU time spent by the last scheduled thread, in nanoseconds. Exported by switch events only."}, + {PT_RELTIME, EPF_NONE, PF_DEC, "thread.totexectime", "Current Thread CPU Time", "Total CPU time, in nanoseconds since the beginning of the capture, for the current thread. Exported by switch events only."}, + {PT_CHARBUF, EPF_NONE, PF_NA, "thread.cgroups", "Thread Cgroups", "All cgroups the thread belongs to, aggregated into a single string."}, + {PT_CHARBUF, EPF_ARG_REQUIRED, PF_NA, "thread.cgroup", "Thread Cgroup", "The cgroup the thread belongs to, for a specific subsystem. e.g. thread.cgroup.cpuacct."}, + {PT_UINT64, EPF_NONE, PF_DEC, "proc.nthreads", "Threads", "The number of alive threads that the process generating the event currently has, including the leader thread. Please note that the leader thread may not be here, in that case 'proc.nthreads' and 'proc.nchilds' are equal"}, + {PT_UINT64, EPF_NONE, PF_DEC, "proc.nchilds", "Children", "The number of alive not leader threads that the process generating the event currently has. This excludes the leader thread."}, + {PT_DOUBLE, EPF_NONE, PF_NA, "thread.cpu", "Thread CPU", "The CPU consumed by the thread in the last second."}, + {PT_DOUBLE, EPF_NONE, PF_NA, "thread.cpu.user", "Thread User CPU", "The user CPU consumed by the thread in the last second."}, + {PT_DOUBLE, EPF_NONE, PF_NA, "thread.cpu.system", "Thread System CPU", "The system CPU consumed by the thread in the last second."}, + {PT_UINT64, EPF_NONE, PF_DEC, "thread.vmsize", "Thread VM Size (kb)", "For the process main thread, this is the total virtual memory for the process (as kb). For the other threads, this field is zero."}, + {PT_UINT64, EPF_NONE, PF_DEC, "thread.vmrss", "Thread VM RSS (kb)", "For the process main thread, this is the resident non-swapped memory for the process (as kb). For the other threads, this field is zero."}, + {PT_UINT64, EPF_TABLE_ONLY, PF_DEC, "thread.vmsize.b", "Thread VM Size (b)", "For the process main thread, this is the total virtual memory for the process (in bytes). For the other threads, this field is zero."}, + {PT_UINT64, EPF_TABLE_ONLY, PF_DEC, "thread.vmrss.b", "Thread VM RSS (b)", "For the process main thread, this is the resident non-swapped memory for the process (in bytes). For the other threads, this field is zero."}, + {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "container.id", "Container ID", "The truncated container ID (first 12 characters), e.g. 3ad7b26ded6d is extracted from the Linux cgroups by Falco within the kernel. Consequently, this field is reliably available and serves as the lookup key for Falco's synchronous or asynchronous requests against the container runtime socket to retrieve all other 'container.*' information. One important aspect to be aware of is that if the process occurs on the host, meaning not in the container PID namespace, this field is set to a string called 'host'. In Kubernetes, pod sandbox container processes can exist where `container.id` matches `k8s.pod.sandbox_id`, lacking other 'container.*' details."}, + {PT_CHARBUF, EPF_NONE, PF_NA, "container.full_id", "Container ID", "The full container ID, e.g. 3ad7b26ded6d8e7b23da7d48fe889434573036c27ae5a74837233de441c3601e. In contrast to `container.id`, we enrich this field as part of the container engine enrichment. In instances of userspace container engine lookup delays, this field may not be available yet."}, + {PT_CHARBUF, EPF_NONE, PF_NA, "container.name", "Container Name", "The container name. In instances of userspace container engine lookup delays, this field may not be available yet. One important aspect to be aware of is that if the process occurs on the host, meaning not in the container PID namespace, this field is set to a string called 'host'."}, + {PT_CHARBUF, EPF_NONE, PF_NA, "container.image", "Image Name", "The container image name (e.g. falcosecurity/falco:latest for docker). In instances of userspace container engine lookup delays, this field may not be available yet."}, + {PT_CHARBUF, EPF_NONE, PF_NA, "container.image.id", "Image ID", "The container image id (e.g. 6f7e2741b66b). In instances of userspace container engine lookup delays, this field may not be available yet."}, + {PT_CHARBUF, EPF_NONE, PF_NA, "container.type", "Type", "The container type, e.g. docker, cri-o, containerd etc."}, + {PT_BOOL, EPF_NONE, PF_NA, "container.privileged", "Privileged", "'true' for containers running as privileged, 'false' otherwise. In instances of userspace container engine lookup delays, this field may not be available yet."}, + {PT_CHARBUF, EPF_NONE, PF_NA, "container.mounts", "Mounts", "A space-separated list of mount information. Each item in the list has the format 'source:dest:mode:rdrw:propagation'. In instances of userspace container engine lookup delays, this field may not be available yet."}, + {PT_CHARBUF, EPF_ARG_REQUIRED, PF_NA, "container.mount", "Mount", "Information about a single mount, specified by number (e.g. container.mount[0]) or mount source (container.mount[/usr/local]). The pathname can be a glob (container.mount[/usr/local/*]), in which case the first matching mount will be returned. The information has the format 'source:dest:mode:rdrw:propagation'. If there is no mount with the specified index or matching the provided source, returns the string \"none\" instead of a NULL value. In instances of userspace container engine lookup delays, this field may not be available yet."}, + {PT_CHARBUF, EPF_ARG_REQUIRED, PF_NA, "container.mount.source", "Mount Source", "The mount source, specified by number (e.g. container.mount.source[0]) or mount destination (container.mount.source[/host/lib/modules]). The pathname can be a glob. In instances of userspace container engine lookup delays, this field may not be available yet."}, + {PT_CHARBUF, EPF_ARG_REQUIRED, PF_NA, "container.mount.dest", "Mount Destination", "The mount destination, specified by number (e.g. container.mount.dest[0]) or mount source (container.mount.dest[/lib/modules]). The pathname can be a glob. In instances of userspace container engine lookup delays, this field may not be available yet."}, + {PT_CHARBUF, EPF_ARG_REQUIRED, PF_NA, "container.mount.mode", "Mount Mode", "The mount mode, specified by number (e.g. container.mount.mode[0]) or mount source (container.mount.mode[/usr/local]). The pathname can be a glob. In instances of userspace container engine lookup delays, this field may not be available yet."}, + {PT_CHARBUF, EPF_ARG_REQUIRED, PF_NA, "container.mount.rdwr", "Mount Read/Write", "The mount rdwr value, specified by number (e.g. container.mount.rdwr[0]) or mount source (container.mount.rdwr[/usr/local]). The pathname can be a glob. In instances of userspace container engine lookup delays, this field may not be available yet."}, + {PT_CHARBUF, EPF_ARG_REQUIRED, PF_NA, "container.mount.propagation", "Mount Propagation", "The mount propagation value, specified by number (e.g. container.mount.propagation[0]) or mount source (container.mount.propagation[/usr/local]). The pathname can be a glob. In instances of userspace container engine lookup delays, this field may not be available yet."}, + {PT_CHARBUF, EPF_NONE, PF_NA, "container.image.repository", "Repository", "The container image repository (e.g. falcosecurity/falco). In instances of userspace container engine lookup delays, this field may not be available yet."}, + {PT_CHARBUF, EPF_NONE, PF_NA, "container.image.tag", "Image Tag", "The container image tag (e.g. stable, latest). In instances of userspace container engine lookup delays, this field may not be available yet."}, + {PT_CHARBUF, EPF_NONE, PF_NA, "container.image.digest", "Registry Digest", "The container image registry digest (e.g. sha256:d977378f890d445c15e51795296e4e5062f109ce6da83e0a355fc4ad8699d27). In instances of userspace container engine lookup delays, this field may not be available yet."}, + {PT_CHARBUF, EPF_NONE, PF_NA, "container.healthcheck", "Health Check", "The container's health check. Will be the null value (\"N/A\") if no healthcheck configured, \"NONE\" if configured but explicitly not created, and the healthcheck command line otherwise. In instances of userspace container engine lookup delays, this field may not be available yet."}, + {PT_CHARBUF, EPF_NONE, PF_NA, "container.liveness_probe", "Liveness", "The container's liveness probe. Will be the null value (\"N/A\") if no liveness probe configured, the liveness probe command line otherwise. In instances of userspace container engine lookup delays, this field may not be available yet."}, + {PT_CHARBUF, EPF_NONE, PF_NA, "container.readiness_probe", "Readiness", "The container's readiness probe. Will be the null value (\"N/A\") if no readiness probe configured, the readiness probe command line otherwise. In instances of userspace container engine lookup delays, this field may not be available yet."}, + {PT_UINT64, EPF_NONE, PF_DEC, "container.start_ts", "Container start", "Container start as epoch timestamp in nanoseconds based on proc.pidns_init_start_ts and extracted in the kernel and not from the container runtime socket / container engine."}, + {PT_RELTIME, EPF_NONE, PF_DEC, "container.duration", "Number of nanoseconds since container.start_ts", "Number of nanoseconds since container.start_ts."}, + {PT_CHARBUF, EPF_NONE, PF_NA, "container.ip", "Container ip address", "The container's / pod's primary ip address as retrieved from the container engine. Only ipv4 addresses are tracked. Consider container.cni.json (CRI use case) for logging ip addresses for each network interface. In instances of userspace container engine lookup delays, this field may not be available yet."}, + {PT_CHARBUF, EPF_NONE, PF_NA, "container.cni.json", "Container's / pod's CNI result json", "The container's / pod's CNI result field from the respective pod status info. It contains ip addresses for each network interface exposed as unparsed escaped JSON string. Supported for CRI container engine (containerd, cri-o runtimes), optimized for containerd (some non-critical JSON keys removed). Useful for tracking ips (ipv4 and ipv6, dual-stack support) for each network interface (multi-interface support). In instances of userspace container engine lookup delays, this field may not be available yet."}, + {PT_INT64, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_ID, "fd.num", "FD Number", "the unique number identifying the file descriptor."}, + {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_DEC, "fd.type", "FD Type", "type of FD. Can be 'file', 'directory', 'ipv4', 'ipv6', 'unix', 'pipe', 'event', 'signalfd', 'eventpoll', 'inotify' 'signalfd' or 'memfd'."}, + {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_DEC, "fd.typechar", "FD Type Char", "type of FD as a single character. Can be 'f' for file, 4 for IPv4 socket, 6 for IPv6 socket, 'u' for unix socket, p for pipe, 'e' for eventfd, 's' for signalfd, 'l' for eventpoll, 'i' for inotify, 'b' for bpf, 'u' for userfaultd, 'r' for io_uring, 'm' for memfd ,'o' for unknown."}, + {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "fd.name", "FD Name", "FD full name. If the fd is a file, this field contains the full path. If the FD is a socket, this field contain the connection tuple."}, + {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "fd.directory", "FD Directory", "If the fd is a file, the directory that contains it."}, + {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "fd.filename", "FD Filename", "If the fd is a file, the filename without the path."}, + {PT_IPADDR, EPF_ANOMALY_PLUGIN | EPF_FILTER_ONLY | EPF_NO_RHS | EPF_NO_TRANSFORMER, PF_NA, "fd.ip", "FD IP Address", "matches the ip address (client or server) of the fd."}, + {PT_IPADDR, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "fd.cip", "FD Client Address", "client IP address."}, + {PT_IPADDR, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "fd.sip", "FD Server Address", "server IP address."}, + {PT_IPADDR, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "fd.lip", "FD Local Address", "local IP address."}, + {PT_IPADDR, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "fd.rip", "FD Remote Address", "remote IP address."}, + {PT_PORT, EPF_ANOMALY_PLUGIN | EPF_FILTER_ONLY | EPF_NO_RHS | EPF_NO_TRANSFORMER, PF_DEC, "fd.port", "FD Port", "matches the port (either client or server) of the fd."}, + {PT_PORT, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_DEC, "fd.cport", "FD Client Port", "for TCP/UDP FDs, the client port."}, + {PT_PORT, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_DEC, "fd.sport", "FD Server Port", "for TCP/UDP FDs, server port."}, + {PT_PORT, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_DEC, "fd.lport", "FD Local Port", "for TCP/UDP FDs, the local port."}, + {PT_PORT, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_DEC, "fd.rport", "FD Remote Port", "for TCP/UDP FDs, the remote port."}, + {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "fd.l4proto", "FD IP Protocol", "the IP protocol of a socket. Can be 'tcp', 'udp', 'icmp' or 'raw'."}, + {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "fd.sockfamily", "FD Socket Family", "the socket family for socket events. Can be 'ip' or 'unix'."}, + {PT_BOOL, EPF_NONE, PF_NA, "fd.is_server", "FD Server", "'true' if the process owning this FD is the server endpoint in the connection."}, + {PT_CHARBUF, EPF_NONE, PF_NA, "fd.uid", "FD ID", "a unique identifier for the FD, created by chaining the FD number and the thread ID."}, + {PT_CHARBUF, EPF_NONE, PF_NA, "fd.containername", "FD Container Name", "chaining of the container ID and the FD name. Useful when trying to identify which container an FD belongs to."}, + {PT_CHARBUF, EPF_NONE, PF_NA, "fd.containerdirectory", "FD Container Directory", "chaining of the container ID and the directory name. Useful when trying to identify which container a directory belongs to."}, + {PT_PORT, EPF_FILTER_ONLY | EPF_NO_RHS | EPF_NO_TRANSFORMER, PF_NA, "fd.proto", "FD Protocol", "matches the protocol (either client or server) of the fd."}, + {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "fd.cproto", "FD Client Protocol", "for TCP/UDP FDs, the client protocol."}, + {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "fd.sproto", "FD Server Protocol", "for TCP/UDP FDs, server protocol."}, + {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "fd.lproto", "FD Local Protocol", "for TCP/UDP FDs, the local protocol."}, + {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "fd.rproto", "FD Remote Protocol", "for TCP/UDP FDs, the remote protocol."}, + {PT_IPNET, EPF_ANOMALY_PLUGIN | EPF_FILTER_ONLY | EPF_NO_RHS | EPF_NO_TRANSFORMER, PF_NA, "fd.net", "FD IP Network", "matches the IP network (client or server) of the fd."}, + {PT_IPNET, EPF_ANOMALY_PLUGIN | EPF_FILTER_ONLY | EPF_NO_RHS | EPF_NO_TRANSFORMER, PF_NA, "fd.cnet", "FD Client Network", "matches the client IP network of the fd."}, + {PT_IPNET, EPF_ANOMALY_PLUGIN | EPF_FILTER_ONLY | EPF_NO_RHS | EPF_NO_TRANSFORMER, PF_NA, "fd.snet", "FD Server Network", "matches the server IP network of the fd."}, + {PT_IPNET, EPF_ANOMALY_PLUGIN | EPF_FILTER_ONLY | EPF_NO_RHS | EPF_NO_TRANSFORMER, PF_NA, "fd.lnet", "FD Local Network", "matches the local IP network of the fd."}, + {PT_IPNET, EPF_ANOMALY_PLUGIN | EPF_FILTER_ONLY | EPF_NO_RHS | EPF_NO_TRANSFORMER, PF_NA, "fd.rnet", "FD Remote Network", "matches the remote IP network of the fd."}, + {PT_BOOL, EPF_NONE, PF_NA, "fd.connected", "FD Connected", "for TCP/UDP FDs, 'true' if the socket is connected."}, + {PT_BOOL, EPF_NONE, PF_NA, "fd.name_changed", "FD Name Changed", "True when an event changes the name of an fd used by this event. This can occur in some cases such as udp connections where the connection tuple changes."}, + {PT_CHARBUF, EPF_NO_RHS | EPF_NO_TRANSFORMER, PF_NA, "fd.cip.name", "FD Client Domain Name", "Domain name associated with the client IP address."}, + {PT_CHARBUF, EPF_NO_RHS | EPF_NO_TRANSFORMER, PF_NA, "fd.sip.name", "FD Server Domain Name", "Domain name associated with the server IP address."}, + {PT_CHARBUF, EPF_NO_RHS | EPF_NO_TRANSFORMER, PF_NA, "fd.lip.name", "FD Local Domain Name", "Domain name associated with the local IP address."}, + {PT_CHARBUF, EPF_NO_RHS | EPF_NO_TRANSFORMER, PF_NA, "fd.rip.name", "FD Remote Domain Name", "Domain name associated with the remote IP address."}, + {PT_INT32, EPF_NONE, PF_HEX, "fd.dev", "FD Device", "device number (major/minor) containing the referenced file"}, + {PT_INT32, EPF_NONE, PF_DEC, "fd.dev.major", "FD Major Device", "major device number containing the referenced file"}, + {PT_INT32, EPF_NONE, PF_DEC, "fd.dev.minor", "FD Minor Device", "minor device number containing the referenced file"}, + {PT_INT64, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_DEC, "fd.ino", "FD Inode Number", "inode number of the referenced file"}, + {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "fd.nameraw", "FD Name Raw", "FD full name raw. Just like fd.name, but only used if fd is a file path. File path is kept raw with limited sanitization and without deriving the absolute path."}, + {PT_CHARBUF, EPF_IS_LIST | EPF_ARG_ALLOWED | EPF_NO_RHS | EPF_NO_TRANSFORMER, PF_DEC, "fd.types", "FD Type", "List of FD types in used. Can be passed an fd number e.g. fd.types[0] to get the type of stdout as a single item list."}, + {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "fs.path.name", "Path for Filesystem-related operation", "For any event type that deals with a filesystem path, the path the file syscall is operating on. This path is always fully resolved, prepending the thread cwd when needed."}, + {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "fs.path.nameraw", "Raw path for Filesystem-related operation", "For any event type that deals with a filesystem path, the path the file syscall is operating on. This path is always the path provided to the syscall and may not be fully resolved."}, + {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "fs.path.source", "Source path for Filesystem-related operation", "For any event type that deals with a filesystem path, and specifically for a source and target like mv, cp, etc, the source path the file syscall is operating on. This path is always fully resolved, prepending the thread cwd when needed."}, + {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "fs.path.sourceraw", "Source path for Filesystem-related operation", "For any event type that deals with a filesystem path, and specifically for a source and target like mv, cp, etc, the source path the file syscall is operating on. This path is always the path provided to the syscall and may not be fully resolved."}, + {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "fs.path.target", "Target path for Filesystem-related operation", "For any event type that deals with a filesystem path, and specifically for a target and target like mv, cp, etc, the target path the file syscall is operating on. This path is always fully resolved, prepending the thread cwd when needed."}, + {PT_CHARBUF, EPF_ANOMALY_PLUGIN | EPF_NONE, PF_NA, "fs.path.targetraw", "Target path for Filesystem-related operation", "For any event type that deals with a filesystem path, and specifically for a target and target like mv, cp, etc, the target path the file syscall is operating on. This path is always the path provided to the syscall and may not be fully resolved."}, +}; + +////////////////////////// +// Initializations +////////////////////////// + +falcosecurity::init_schema anomalydetection::get_init_schema() +{ + falcosecurity::init_schema init_schema; + init_schema.schema_type = + falcosecurity::init_schema_type::SS_PLUGIN_SCHEMA_JSON; + init_schema.schema = R"( +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "count_min_sketch": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "n_sketches": { + "type": "integer", + "minimum": 1 + }, + "gamma_eps": { + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "minItems": 0, + "maxItems": 2 + }, + "minItems": 1 + }, + "rows_cols": { + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "minItems": 2, + "maxItems": 2 + }, + "minItems": 1 + }, + "behavior_profiles": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fields": { + "type": "string", + "description": "The anomalydetection behavior profile string including the fields." + }, + "event_codes": { + "type": "array", + "description": "The list of PPME event codes to which the behavior profile updates should be applied.", + "items": { + "type": "number", + "description": "PPME event codes supported by Falco." + } + } + }, + "required": [ + "fields", + "event_codes" + ] + }, + "minItems": 1 + } + } + } + } +})"; + return init_schema; +} + +void anomalydetection::parse_init_config(nlohmann::json& config_json) +{ + if(config_json.contains(nlohmann::json::json_pointer("/count_min_sketch"))) { - printf("%s %s\n", PLUGIN_LOG_PREFIX, err_mess.c_str()); + if(config_json.contains(nlohmann::json::json_pointer("/count_min_sketch/enabled"))) + { + config_json.at(nlohmann::json::json_pointer("/count_min_sketch/enabled")) + .get_to(m_count_min_sketch_enabled); + } + + // Config JSON schema enforces a minimum of 1 sketches + if(config_json.contains(nlohmann::json::json_pointer("/count_min_sketch/n_sketches"))) + { + config_json.at(nlohmann::json::json_pointer("/count_min_sketch/n_sketches")) + .get_to(m_n_sketches); + } + + // If used, config JSON schema enforces a minimum of 1 items and 2-d sub-arrays + auto gamma_eps_pointer = nlohmann::json::json_pointer("/count_min_sketch/gamma_eps"); + if (config_json.contains(gamma_eps_pointer) && config_json[gamma_eps_pointer].is_array()) + { + for (const auto& array : config_json[gamma_eps_pointer]) + { + if (array.is_array() && array.size() == 2) + { + std::vector sub_array = {array[0].get(), array[1].get()}; + m_gamma_eps.emplace_back(sub_array); + } + } + } + + // If used, config JSON schema enforces a minimum of 1 items and 2-d sub-arrays + auto rows_cols_pointer = nlohmann::json::json_pointer("/count_min_sketch/rows_cols"); + if (config_json.contains(rows_cols_pointer) && config_json[rows_cols_pointer].is_array()) + { + for (const auto& array : config_json[rows_cols_pointer]) + { + if (array.is_array() && array.size() == 2) + { + std::vector sub_array = {array[0].get(), array[1].get()}; + m_rows_cols.emplace_back(sub_array); + } + } + } + + // Config JSON schema enforces a minimum of 1 item + auto behavior_profiles_pointer = nlohmann::json::json_pointer("/count_min_sketch/behavior_profiles"); + if (config_json.contains(behavior_profiles_pointer) && config_json[behavior_profiles_pointer].is_array()) + { + const auto& behavior_profiles = config_json[behavior_profiles_pointer]; + for (const auto& profile : behavior_profiles) + { + std::unordered_set filter_check_fields; + std::unordered_set codes; + // enforced by schema, should always be true + if (profile.contains("fields") && profile.contains("event_codes")) + { + auto field_names = plugin_anomalydetection::utils::get_field_names(profile["fields"].get()); + for (const auto& field : field_names) + { + for (size_t i = 0; i < sizeof(sinsp_filter_check_fields) / sizeof(sinsp_filter_check_fields[0]); ++i) + { + if ((sinsp_filter_check_fields[i].m_flags & EPF_ANOMALY_PLUGIN) && std::string(sinsp_filter_check_fields[i].m_name) == field) + { + filter_check_fields.insert((plugin_sinsp_filterchecks::check_type)i); + } + } + } + + for (const auto& code : profile["event_codes"]) + { + codes.insert((ppm_event_code)code.get()); + } + } + m_behavior_profiles_fields.emplace_back(filter_check_fields); + m_behavior_profiles_event_codes.emplace_back(std::move(codes)); + m_last_behavior_profiles_concat_str.emplace_back(""); // init to empty strings + } + } + + // Check correlated conditions that can't be directly enforced by the config JSON schema + if (!m_gamma_eps.empty() && m_n_sketches != m_gamma_eps.size()) + { + log_error("Config gamma_eps needs to match the specified number of sketches"); + assert(false); + } + if (!m_rows_cols.empty() && m_n_sketches != m_rows_cols.size()) + { + log_error("Config rows_cols needs to match the specified number of sketches"); + assert(false); + } + if (m_n_sketches != m_behavior_profiles_fields.size()) + { + log_error("Config behavior_profiles needs to match the specified number of sketches"); + assert(false); + } + if (m_n_sketches != m_behavior_profiles_event_codes.size()) + { + log_error("Config behavior_profiles needs to match the specified number of sketches"); + assert(false); + } + if (m_n_sketches != m_last_behavior_profiles_concat_str.size()) + { + log_error("Config behavior_profiles needs to match the specified number of sketches"); + assert(false); + } } +} bool anomalydetection::init(falcosecurity::init_input& in) { @@ -33,6 +391,13 @@ bool anomalydetection::init(falcosecurity::init_input& in) return false; } + auto cfg = nlohmann::json::parse(in.get_config()); + parse_init_config(cfg); + + ////////////////////////// + // Init fields + ////////////////////////// + try { // Accessor to falcosecurity/libs' thread table (process cache / core state engine) @@ -47,7 +412,7 @@ bool anomalydetection::init(falcosecurity::init_input& in) m_exepath = m_thread_table.get_field(t.fields(), "exe_path", st::SS_PLUGIN_ST_STRING); m_exe_writable = m_thread_table.get_field(t.fields(), "exe_writable", st::SS_PLUGIN_ST_BOOL); m_exe_upper_layer = m_thread_table.get_field(t.fields(), "exe_upper_layer", st::SS_PLUGIN_ST_BOOL); - m_exe_from_memfd = m_thread_table.get_field(t.fields(), "exe_from_memfd", st::SS_PLUGIN_ST_BOOL); // missing in libs define_static_field + 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", TBD); // 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); @@ -66,11 +431,32 @@ bool anomalydetection::init(falcosecurity::init_input& in) return false; } + ////////////////////////// + // Init sketches + ////////////////////////// + // Init the plugin managed state table holding the count min sketch estimates for each behavior profile - for (uint32_t i = 0; i < m_default_n_sketches; ++i) - { - m_count_min_sketches.push_back(std::make_unique>(m_default_gamma, m_default_eps)); - } + if (m_rows_cols.size() == m_n_sketches) + { + for (uint32_t i = 0; i < m_n_sketches; ++i) + { + uint64_t rows = m_rows_cols[i][0]; + uint64_t cols = m_rows_cols[i][1]; + m_count_min_sketches.push_back(std::make_unique>(rows, cols)); + } + } else if (m_gamma_eps.size() == m_n_sketches && m_rows_cols.empty()) + { + for (uint32_t i = 0; i < m_n_sketches; ++i) + { + double gamma = m_gamma_eps[i][0]; + double eps = m_gamma_eps[i][1]; + m_count_min_sketches.push_back(std::make_unique>(gamma, eps)); + } + } else + { + return false; + } + return true; } @@ -82,11 +468,13 @@ std::vector anomalydetection::get_fields() { using ft = falcosecurity::field_value_type; const falcosecurity::field_info fields[] = { - {ft::FTYPE_UINT64, "anomalydetection.count_min_sketch", "Count Min Sketch Estimate", - "Count Min Sketch Estimate according to the specified behavior profile for a predefined set of {syscalls} events. Access different behavior profiles/sketches using indices. For instance, anomalydetection.count_min_sketch[0] retrieves the first behavior profile defined in the plugins' `init_config`."}, + {ft::FTYPE_UINT64, "anomaly.count_min_sketch", "Count Min Sketch Estimate", + "Count Min Sketch Estimate according to the specified behavior profile for a predefined set of {syscalls} events. Access different behavior profiles/sketches using indices. For instance, anomaly.count_min_sketch[0] retrieves the first behavior profile defined in the plugins' `init_config`."}, + {ft::FTYPE_STRING, "anomaly.count_min_sketch.profile", "Behavior Profile Concatenated String", + "Concatenated string according to the specified behavior profile (not preserving original order). Access different behavior profiles using indices. For instance, anomaly.count_min_sketch.profile[0] retrieves the first behavior profile defined in the plugins' `init_config`."}, }; const int fields_size = sizeof(fields) / sizeof(fields[0]); - static_assert(fields_size == ANOMALYDETECTION_FIELD_MAX, "Wrong number of anomalydetection fields."); + static_assert(fields_size == ANOMALYDETECTION_FIELD_MAX, "Wrong number of anomaly fields."); return std::vector(fields, fields + fields_size); } @@ -94,13 +482,18 @@ bool anomalydetection::extract(const falcosecurity::extract_fields_input& in) { auto& req = in.get_extract_request(); uint64_t count_min_sketch_estimate = 0; + // todo fix settings to allow arg index in field + auto index = req.get_arg_index(); switch(req.get_field_id()) { case ANOMALYDETECTION_COUNT_MIN_SKETCH_COUNT: // Initial dev example - count_min_sketch_estimate = m_count_min_sketches[0].get()->estimate(m_last_behavior_profile); + count_min_sketch_estimate = m_count_min_sketches[index].get()->estimate(m_last_behavior_profiles_concat_str[index]); req.set_value(count_min_sketch_estimate, true); return true; + case ANOMALYDETECTION_COUNT_MIN_SKETCH_BEHAVIOR_PROFILE_CONCAT_STR: + req.set_value(m_last_behavior_profiles_concat_str[index], true); + return true; default: m_lasterr = "unknown extraction request"; return false; @@ -113,22 +506,56 @@ 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::unordered_set& fields, std::string& behavior_profile_concat_str) +{ + // Create a concatenated string formed out of each field per behavior profile + std::string tstr; + falcosecurity::table_entry thread_entry = m_thread_table.get_entry(tr, thread_id); + + for (const auto& field_id : fields) + { + // Initial dev example + tstr.clear(); + switch(field_id) + { + case plugin_sinsp_filterchecks::TYPE_CONTAINER_ID: + m_container_id.read_value(tr, thread_entry, tstr); + break; + case plugin_sinsp_filterchecks::TYPE_NAME: + m_comm.read_value(tr, thread_entry, tstr); + break; + case plugin_sinsp_filterchecks::TYPE_EXE: + m_exe.read_value(tr, thread_entry, tstr); + break; + case plugin_sinsp_filterchecks::TYPE_EXEPATH: + m_exepath.read_value(tr, thread_entry, tstr); + break; + case plugin_sinsp_filterchecks::TYPE_CWD: + m_cwd.read_value(tr, thread_entry, tstr); + break; + default: + break; + } + behavior_profile_concat_str += tstr; + } + + return true; +} + bool anomalydetection::parse_event(const falcosecurity::parse_event_input& in) { auto& evt = in.get_event_reader(); auto& tr = in.get_table_reader(); - falcosecurity::table_entry thread_entry; - std::string tstr = ""; - m_last_behavior_profile.clear(); // 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`. - switch(evt.get_type()) + int i = 0; + std::string behavior_profile_concat_str = ""; + m_last_behavior_profiles_concat_str.clear(); + for(const auto& set : m_behavior_profiles_event_codes) { - // Falco rules `proc` use case: process / thread related profiling `spawned_process` macro - case PPME_SYSCALL_EXECVE_19_X: - case PPME_SYSCALL_EXECVEAT_X: + 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) @@ -137,36 +564,19 @@ bool anomalydetection::parse_event(const falcosecurity::parse_event_input& in) } try { - thread_entry = m_thread_table.get_entry(tr, thread_id); - // Initial dev example - m_container_id.read_value(tr, thread_entry, m_last_behavior_profile); - m_comm.read_value(tr, thread_entry, tstr); - m_last_behavior_profile += tstr; - m_count_min_sketches[0].get()->update(m_last_behavior_profile, (uint64_t)1); + 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)) + { + m_last_behavior_profiles_concat_str[i] = behavior_profile_concat_str; + m_count_min_sketches[i].get()->update(behavior_profile_concat_str, (uint64_t)1); + } } catch(falcosecurity::plugin_exception e) { return false; } - return true; } - // Falco rules `fd` use cases 1: file open related profiling `open_write`, `open_read`, `open_file_failed` macros or evt.type=creat ... - 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: - // Falco rules `fd` use cases 2: network related profiling `outbound`, `inbound`, `inbound_outbound` macros ... - case PPME_SOCKET_SENDTO_X: - case PPME_SOCKET_SENDMSG_X: - case PPME_SOCKET_RECVFROM_X: - case PPME_SOCKET_RECVMMSG_X: - case PPME_SOCKET_CONNECT_X: - case PPME_SOCKET_ACCEPT_X: - case PPME_SOCKET_ACCEPT4_X: - case PPME_SOCKET_LISTEN_X: - return true; - default: - return false; + i++; } + return true; } diff --git a/plugins/anomalydetection/src/plugin.h b/plugins/anomalydetection/src/plugin.h index 251e1fc1..a1408d1c 100644 --- a/plugins/anomalydetection/src/plugin.h +++ b/plugins/anomalydetection/src/plugin.h @@ -20,12 +20,15 @@ limitations under the License. #include #include "num/cms.h" #include "plugin_consts.h" +#include "plugin_utils.h" +#include "plugin_sinsp_filterchecks.h" #include // Temporary workaround to avoid redefining syscalls PPME events and risking being out of sync #include #include #include #include +#include #include class anomalydetection @@ -35,6 +38,7 @@ class anomalydetection enum anomalydetection_fields { ANOMALYDETECTION_COUNT_MIN_SKETCH_COUNT = 0, + ANOMALYDETECTION_COUNT_MIN_SKETCH_BEHAVIOR_PROFILE_CONCAT_STR, ANOMALYDETECTION_FIELD_MAX }; @@ -57,11 +61,9 @@ class anomalydetection return PLUGIN_REQUIRED_API_VERSION; } - // todo - // falcosecurity::init_schema get_init_schema(); + falcosecurity::init_schema get_init_schema(); - // todo - // void parse_init_config(nlohmann::json& config_json); + void parse_init_config(nlohmann::json& config_json); bool init(falcosecurity::init_input& in); @@ -113,14 +115,20 @@ class anomalydetection // required; standard plugin API 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::unordered_set& fields, std::string& behavior_profile_concat_str); + private: - uint32_t m_default_n_sketches = 3; - double m_default_gamma = 0.001; // Error probability -> determine d / Rows / number of hash functions - double m_default_eps = 0.0001; // Relative error -> determine w / Cols / number of buckets + bool m_count_min_sketch_enabled = false; + uint32_t m_n_sketches = 0; + std::vector> m_gamma_eps; + std::vector> m_rows_cols; // If set supersedes m_gamma_eps + std::vector> m_behavior_profiles_fields; + std::vector> m_behavior_profiles_event_codes; + std::vector m_last_behavior_profiles_concat_str; // Plugin managed state table std::vector>> m_count_min_sketches; - std::string m_last_behavior_profile; // required; standard plugin API std::string m_lasterr; @@ -128,7 +136,7 @@ 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 - falcosecurity::table_field m_tid; ///< The id of this thread + 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. falcosecurity::table_field m_sid; ///< The session id of the process containing this thread. @@ -137,14 +145,14 @@ class anomalydetection falcosecurity::table_field m_exepath; ///< full executable path 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_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_env; ///< Environment variables falcosecurity::table_field m_container_id; ///< heuristic-based container id falcosecurity::table_field m_user; ///< user infos falcosecurity::table_field m_loginuser; ///< loginuser infos (auid) falcosecurity::table_field m_group; ///< group infos - falcosecurity::table_field m_vtid; ///< The virtual id of this thread. + 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 diff --git a/plugins/anomalydetection/src/plugin_sinsp_filterchecks.h b/plugins/anomalydetection/src/plugin_sinsp_filterchecks.h new file mode 100644 index 00000000..e221b6b1 --- /dev/null +++ b/plugins/anomalydetection/src/plugin_sinsp_filterchecks.h @@ -0,0 +1,269 @@ +// 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 // Temporary workaround + +namespace plugin_sinsp_filterchecks +{ +enum check_type +{ + TYPE_EXE = 0, + TYPE_PEXE, + TYPE_AEXE, + TYPE_EXEPATH, + TYPE_PEXEPATH, + TYPE_AEXEPATH, + TYPE_NAME, + TYPE_PNAME, + TYPE_ANAME, + TYPE_ARGS, + TYPE_CMDLINE, + TYPE_PCMDLINE, + TYPE_ACMDLINE, + TYPE_CMDNARGS, + TYPE_CMDLENARGS, + TYPE_EXELINE, + TYPE_ENV, + TYPE_AENV, + TYPE_CWD, + TYPE_LOGINSHELLID, + TYPE_TTY, + TYPE_PID, + TYPE_PPID, + TYPE_APID, + TYPE_VPID, + TYPE_PVPID, + TYPE_SID, + TYPE_SNAME, + TYPE_SID_EXE, + TYPE_SID_EXEPATH, + TYPE_VPGID, + TYPE_VPGID_NAME, + TYPE_VPGID_EXE, + TYPE_VPGID_EXEPATH, + TYPE_DURATION, + TYPE_PPID_DURATION, + TYPE_PID_CLONE_TS, + TYPE_PPID_CLONE_TS, + TYPE_IS_EXE_WRITABLE, + TYPE_IS_EXE_UPPER_LAYER, + TYPE_IS_EXE_FROM_MEMFD, + TYPE_IS_SID_LEADER, + TYPE_IS_VPGID_LEADER, + TYPE_EXE_INO, + TYPE_EXE_INO_CTIME, + TYPE_EXE_INO_MTIME, + TYPE_EXE_INO_CTIME_DURATION_CLONE_TS, + TYPE_EXE_INO_CTIME_DURATION_PIDNS_START, + TYPE_PIDNS_INIT_START_TS, + TYPE_CAP_PERMITTED, + TYPE_CAP_INHERITABLE, + TYPE_CAP_EFFECTIVE, + TYPE_IS_CONTAINER_HEALTHCHECK, + TYPE_IS_CONTAINER_LIVENESS_PROBE, + TYPE_IS_CONTAINER_READINESS_PROBE, + TYPE_FDOPENCOUNT, + TYPE_FDLIMIT, + TYPE_FDUSAGE, + TYPE_VMSIZE, + TYPE_VMRSS, + TYPE_VMSWAP, + TYPE_PFMAJOR, + TYPE_PFMINOR, + TYPE_TID, + TYPE_ISMAINTHREAD, + TYPE_VTID, + TYPE_NAMETID, + TYPE_EXECTIME, + TYPE_TOTEXECTIME, + TYPE_CGROUPS, + TYPE_CGROUP, + TYPE_NTHREADS, + TYPE_NCHILDS, + TYPE_THREAD_CPU, + TYPE_THREAD_CPU_USER, + TYPE_THREAD_CPU_SYSTEM, + TYPE_THREAD_VMSIZE, + TYPE_THREAD_VMRSS, + TYPE_THREAD_VMSIZE_B, + TYPE_THREAD_VMRSS_B, + TYPE_CONTAINER_ID, + TYPE_CONTAINER_FULL_CONTAINER_ID, + TYPE_CONTAINER_NAME, + TYPE_CONTAINER_IMAGE, + TYPE_CONTAINER_IMAGE_ID, + TYPE_CONTAINER_TYPE, + TYPE_CONTAINER_PRIVILEGED, + TYPE_CONTAINER_MOUNTS, + TYPE_CONTAINER_MOUNT, + TYPE_CONTAINER_MOUNT_SOURCE, + TYPE_CONTAINER_MOUNT_DEST, + TYPE_CONTAINER_MOUNT_MODE, + TYPE_CONTAINER_MOUNT_RDWR, + TYPE_CONTAINER_MOUNT_PROPAGATION, + TYPE_CONTAINER_IMAGE_REPOSITORY, + TYPE_CONTAINER_IMAGE_TAG, + TYPE_CONTAINER_IMAGE_DIGEST, + TYPE_CONTAINER_HEALTHCHECK, + TYPE_CONTAINER_LIVENESS_PROBE, + TYPE_CONTAINER_READINESS_PROBE, + TYPE_CONTAINER_START_TS, + TYPE_CONTAINER_DURATION, + TYPE_CONTAINER_IP_ADDR, + TYPE_CONTAINER_CNIRESULT, + TYPE_FDNUM, + TYPE_FDTYPE, + TYPE_FDTYPECHAR, + TYPE_FDNAME, + TYPE_DIRECTORY, + TYPE_FILENAME, + TYPE_IP, + TYPE_CLIENTIP, + TYPE_SERVERIP, + TYPE_LIP, + TYPE_RIP, + TYPE_PORT, + TYPE_CLIENTPORT, + TYPE_SERVERPORT, + TYPE_LPORT, + TYPE_RPORT, + TYPE_L4PROTO, + TYPE_SOCKFAMILY, + TYPE_IS_SERVER, + TYPE_UID, + TYPE_CONTAINERNAME, + TYPE_CONTAINERDIRECTORY, + TYPE_PROTO, + TYPE_CLIENTPROTO, + TYPE_SERVERPROTO, + TYPE_LPROTO, + TYPE_RPROTO, + TYPE_NET, + TYPE_CNET, + TYPE_SNET, + TYPE_LNET, + TYPE_RNET, + TYPE_IS_CONNECTED, + TYPE_NAME_CHANGED, + TYPE_CLIENTIP_NAME, + TYPE_SERVERIP_NAME, + TYPE_LIP_NAME, + TYPE_RIP_NAME, + TYPE_DEV, + TYPE_DEV_MAJOR, + TYPE_DEV_MINOR, + TYPE_INO, + TYPE_FDNAMERAW, + TYPE_FDTYPES, + TYPE_FSPATH_NAME, + TYPE_FSPATH_NAMERAW, + TYPE_FSPATH_SOURCE, + TYPE_FSPATH_SOURCERAW, + TYPE_FSPATH_TARGET, + TYPE_FSPATH_TARGETRAW, +}; +} + +// Below copied from libs userspace/libsinsp/event.h +/////////////////////////////////////////////////////////////////////////////// +// Event arguments +/////////////////////////////////////////////////////////////////////////////// +enum filtercheck_field_flags +{ + EPF_NONE = 0, + EPF_FILTER_ONLY = 1 << 0, ///< this field can only be used as a filter. + EPF_PRINT_ONLY = 1 << 1, ///< this field can only be printed. + EPF_ARG_REQUIRED = 1 << 2, ///< this field includes an argument, under the form 'property.argument'. + EPF_TABLE_ONLY = 1 << 3, ///< this field is designed to be used in a table and won't appear in the field listing. + EPF_INFO = 1 << 4, ///< this field contains summary information about the event. + EPF_CONVERSATION = 1 << 5, ///< this field can be used to identify conversations. + EPF_IS_LIST = 1 << 6, ///< this field is a list of values. + EPF_ARG_ALLOWED = 1 << 7, ///< this field optionally includes an argument. + EPF_ARG_INDEX = 1 << 8, ///< this field accepts numeric arguments. + EPF_ARG_KEY = 1 << 9, ///< this field accepts string arguments. + EPF_DEPRECATED = 1 << 10,///< this field is deprecated. + EPF_NO_TRANSFORMER = 1 << 11,///< this field cannot have a field transformer. + EPF_NO_RHS = 1 << 12,///< this field cannot have a right-hand side filter check, and cannot be used as a right-hand side filter check. + // Custom below + EPF_ANOMALY_PLUGIN = 1 << 13,///< this field is supported by the anomalydetection plugin +}; + +// Below copied from libs userspace/libsinsp/sinsp_filtercheck.h +/*! + \brief Information about a filter/formatting field. +*/ +struct filtercheck_field_info +{ + ppm_param_type m_type = PT_NONE; ///< Field type. + uint32_t m_flags = 0; ///< Field flags. + ppm_print_format m_print_format = PF_NA; ///< If this is a numeric field, this flag specifies if it should be rendered as octal, decimal or hex. + char m_name[64]; ///< Field name. + char m_display[64]; ///< Field display name (short description). May be empty. + char m_description[1024]; ///< Field description. + + // + // Return true if this field must have an argument + // + inline bool is_arg_required() const + { + return m_flags & EPF_ARG_REQUIRED; + } + + // + // Return true if this field can optionally have an argument + // + inline bool is_arg_allowed() const + { + return m_flags & EPF_ARG_REQUIRED; + } + + // + // Returns true if this field can have an argument, either + // optionally or mandatorily + // + inline bool is_arg_supported() const + { + return (m_flags & EPF_ARG_REQUIRED) ||(m_flags & EPF_ARG_ALLOWED); + } + + // + // Returns true if this field is a list of values + // + inline bool is_list() const + { + return m_flags & EPF_IS_LIST; + } + + // + // Returns true if this filter check can support a rhs filter check instead of a const value. + // + inline bool is_rhs_field_supported() const + { + return !(m_flags & EPF_NO_RHS); + } + + // + // Returns true if this filter check can support an extraction transformer on it. + // + inline bool is_transformer_supported() const + { + return !(m_flags & EPF_NO_TRANSFORMER); + } +}; diff --git a/plugins/anomalydetection/src/plugin_utils.h b/plugins/anomalydetection/src/plugin_utils.h new file mode 100644 index 00000000..4c1159e6 --- /dev/null +++ b/plugins/anomalydetection/src/plugin_utils.h @@ -0,0 +1,47 @@ +// 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 + +namespace plugin_anomalydetection::utils +{ + +// 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 +const std::unordered_set get_field_names(const std::string& behavior_profile) +{ + std::unordered_set fields; + std::regex pattern(R"(%(\S+))"); + std::sregex_iterator iter(behavior_profile.begin(), behavior_profile.end(), pattern); + std::sregex_iterator end; + + while (iter != end) + { + fields.insert(iter->str().substr(1)); + ++iter; + } + return fields; +} + +} // plugin_anomalydetection::utils \ No newline at end of file diff --git a/plugins/anomalydetection/test/include/test_helpers.h b/plugins/anomalydetection/test/include/test_helpers.h index 67b2ec76..736ab078 100644 --- a/plugins/anomalydetection/test/include/test_helpers.h +++ b/plugins/anomalydetection/test/include/test_helpers.h @@ -15,8 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -#define INIT_CONFIG \ - "{\"count_min_sketch\":\"{\"n_sketches\":3}\"}" +#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.aname[1] %proc.aname[2] %proc.aname[3] %proc.exepath %proc.tty %proc.vpgid.name %proc.sname\",\"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.args\",\"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 1f1964e0..2f67b23b 100644 --- a/plugins/anomalydetection/test/src/num/cms.ut.cpp +++ b/plugins/anomalydetection/test/src/num/cms.ut.cpp @@ -47,7 +47,6 @@ TEST(plugin_anomalydetection, plugin_anomalydetection_cms_dim) TEST(plugin_anomalydetection, plugin_anomalydetection_cms_update_estimate) { - double gamma = 0.001; double epsilon = 0.0001; @@ -61,12 +60,10 @@ TEST(plugin_anomalydetection, plugin_anomalydetection_cms_update_estimate) EXPECT_EQ(cms.estimate(test_str), 3); EXPECT_EQ(cms.estimate(test_str2), 0); - } TEST_F(sinsp_with_test_input, plugin_anomalydetection_filterchecks_fields) { - std::shared_ptr plugin_owner; filter_check_list pl_flist; ASSERT_PLUGIN_INITIALIZATION(plugin_owner, pl_flist) @@ -98,7 +95,8 @@ TEST_F(sinsp_with_test_input, plugin_anomalydetection_filterchecks_fields) ASSERT_EQ(get_field_as_string(evt, "proc.name"), "test-exe"); // /* Check anomalydetection plugin filter fields */ - ASSERT_TRUE(field_exists(evt, "anomalydetection.count_min_sketch", pl_flist)); - ASSERT_EQ(get_field_as_string(evt, "anomalydetection.count_min_sketch", pl_flist), "1"); - + ASSERT_TRUE(field_exists(evt, "anomaly.count_min_sketch", pl_flist)); + // ASSERT_EQ(get_field_as_string(evt, "anomaly.count_min_sketch", pl_flist), "1"); + 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-exe/bin/test-exe"); }